diff -r 000000000000 -r 6474c204b198 gfx/layers/apz/util/APZCCallbackHelper.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,433 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCCallbackHelper.h" +#include "gfxPrefs.h" // For gfxPrefs::LayersTilesEnabled, LayersTileWidth/Height +#include "mozilla/Preferences.h" +#include "nsIScrollableFrame.h" +#include "nsLayoutUtils.h" +#include "nsIDOMElement.h" +#include "nsIInterfaceRequestorUtils.h" + +namespace mozilla { +namespace layers { + +bool +APZCCallbackHelper::HasValidPresShellId(nsIDOMWindowUtils* aUtils, + const FrameMetrics& aMetrics) +{ + MOZ_ASSERT(aUtils); + + uint32_t presShellId; + nsresult rv = aUtils->GetPresShellId(&presShellId); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return NS_SUCCEEDED(rv) && aMetrics.mPresShellId == presShellId; +} + +/** + * Expands a given rectangle to the next tile boundary. Note, this will + * expand the rectangle if it is already on tile boundaries. + */ +static CSSRect ExpandDisplayPortToTileBoundaries( + const CSSRect& aDisplayPort, + const CSSToLayerScale& aLayerPixelsPerCSSPixel) +{ + // Convert the given rect to layer coordinates so we can inflate to tile + // boundaries (layer space corresponds to texture pixel space here). + LayerRect displayPortInLayerSpace = aDisplayPort * aLayerPixelsPerCSSPixel; + + // Inflate the rectangle by 1 so that we always push to the next tile + // boundary. This is desirable to stop from having a rectangle with a + // moving origin occasionally being smaller when it coincidentally lines + // up to tile boundaries. + displayPortInLayerSpace.Inflate(1); + + // Now nudge the rectangle to the nearest equal or larger tile boundary. + int32_t tileWidth = gfxPrefs::LayersTileWidth(); + int32_t tileHeight = gfxPrefs::LayersTileHeight(); + gfxFloat left = tileWidth * floor(displayPortInLayerSpace.x / tileWidth); + gfxFloat right = tileWidth * ceil(displayPortInLayerSpace.XMost() / tileWidth); + gfxFloat top = tileHeight * floor(displayPortInLayerSpace.y / tileHeight); + gfxFloat bottom = tileHeight * ceil(displayPortInLayerSpace.YMost() / tileHeight); + + displayPortInLayerSpace = LayerRect(left, top, right - left, bottom - top); + CSSRect displayPort = displayPortInLayerSpace / aLayerPixelsPerCSSPixel; + + return displayPort; +} + +static void +MaybeAlignAndClampDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics, + const CSSPoint& aActualScrollOffset) +{ + // Correct the display-port by the difference between the requested scroll + // offset and the resulting scroll offset after setting the requested value. + if (!aFrameMetrics.GetUseDisplayPortMargins()) { + CSSRect& displayPort = aFrameMetrics.mDisplayPort; + displayPort += aFrameMetrics.GetScrollOffset() - aActualScrollOffset; + + // Expand the display port to the next tile boundaries, if tiled thebes layers + // are enabled. + if (gfxPrefs::LayersTilesEnabled()) { + // We don't use LayersPixelsPerCSSPixel() here as mCumulativeResolution on + // this FrameMetrics may be incorrect (and is about to be reset by mZoom). + displayPort = + ExpandDisplayPortToTileBoundaries(displayPort + aActualScrollOffset, + aFrameMetrics.GetZoom() * + ScreenToLayerScale(1.0)) + - aActualScrollOffset; + } + + // Finally, clamp the display port to the expanded scrollable rect. + CSSRect scrollableRect = aFrameMetrics.GetExpandedScrollableRect(); + displayPort = scrollableRect.Intersect(displayPort + aActualScrollOffset) + - aActualScrollOffset; + } else { + LayerPoint shift = + (aFrameMetrics.GetScrollOffset() - aActualScrollOffset) * + aFrameMetrics.LayersPixelsPerCSSPixel(); + LayerMargin margins = aFrameMetrics.GetDisplayPortMargins(); + margins.left -= shift.x; + margins.right += shift.x; + margins.top -= shift.y; + margins.bottom += shift.y; + aFrameMetrics.SetDisplayPortMargins(margins); + } +} + +static void +RecenterDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics) +{ + if (!aFrameMetrics.GetUseDisplayPortMargins()) { + CSSSize compositionSize = aFrameMetrics.CalculateCompositedSizeInCssPixels(); + aFrameMetrics.mDisplayPort.x = (compositionSize.width - aFrameMetrics.mDisplayPort.width) / 2; + aFrameMetrics.mDisplayPort.y = (compositionSize.height - aFrameMetrics.mDisplayPort.height) / 2; + } else { + LayerMargin margins = aFrameMetrics.GetDisplayPortMargins(); + margins.right = margins.left = margins.LeftRight() / 2; + margins.top = margins.bottom = margins.TopBottom() / 2; + aFrameMetrics.SetDisplayPortMargins(margins); + } +} + +static CSSPoint +ScrollFrameTo(nsIScrollableFrame* aFrame, const CSSPoint& aPoint, bool& aSuccessOut) +{ + aSuccessOut = false; + + if (!aFrame) { + return aPoint; + } + + CSSPoint targetScrollPosition = aPoint; + + // If the frame is overflow:hidden on a particular axis, we don't want to allow + // user-driven scroll on that axis. Simply set the scroll position on that axis + // to whatever it already is. Note that this will leave the APZ's async scroll + // position out of sync with the gecko scroll position, but APZ can deal with that + // (by design). Note also that when we run into this case, even if both axes + // have overflow:hidden, we want to set aSuccessOut to true, so that the displayport + // follows the async scroll position rather than the gecko scroll position. + CSSPoint geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition()); + if (aFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_HIDDEN) { + targetScrollPosition.y = geckoScrollPosition.y; + } + if (aFrame->GetScrollbarStyles().mHorizontal == NS_STYLE_OVERFLOW_HIDDEN) { + targetScrollPosition.x = geckoScrollPosition.x; + } + + // If the scrollable frame is currently in the middle of an async or smooth + // scroll then we don't want to interrupt it (see bug 961280). + // Also if the scrollable frame got a scroll request from something other than us + // since the last layers update, then we don't want to push our scroll request + // because we'll clobber that one, which is bad. + if (!aFrame->IsProcessingAsyncScroll() && + (!aFrame->OriginOfLastScroll() || aFrame->OriginOfLastScroll() == nsGkAtoms::apz)) { + aFrame->ScrollToCSSPixelsApproximate(targetScrollPosition, nsGkAtoms::apz); + geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition()); + aSuccessOut = true; + } + // Return the final scroll position after setting it so that anything that relies + // on it can have an accurate value. Note that even if we set it above re-querying it + // is a good idea because it may have gotten clamped or rounded. + return geckoScrollPosition; +} + +void +APZCCallbackHelper::UpdateRootFrame(nsIDOMWindowUtils* aUtils, + FrameMetrics& aMetrics) +{ + // Precondition checks + MOZ_ASSERT(aUtils); + if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) { + return; + } + + // Set the scroll port size, which determines the scroll range. For example if + // a 500-pixel document is shown in a 100-pixel frame, the scroll port length would + // be 100, and gecko would limit the maximum scroll offset to 400 (so as to prevent + // overscroll). Note that if the content here was zoomed to 2x, the document would + // be 1000 pixels long but the frame would still be 100 pixels, and so the maximum + // scroll range would be 900. Therefore this calculation depends on the zoom applied + // to the content relative to the container. + CSSSize scrollPort = aMetrics.CalculateCompositedSizeInCssPixels(); + aUtils->SetScrollPositionClampingScrollPortSize(scrollPort.width, scrollPort.height); + + // Scroll the window to the desired spot + nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId()); + bool scrollUpdated = false; + CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics.GetScrollOffset(), scrollUpdated); + + if (!scrollUpdated) { + // For whatever reason we couldn't update the scroll offset on the scroll frame, + // which means the data APZ used for its displayport calculation is stale. Fall + // back to a sane default behaviour. Note that we don't tile-align the recentered + // displayport because tile-alignment depends on the scroll position, and the + // scroll position here is out of our control. See bug 966507 comment 21 for a + // more detailed explanation. + RecenterDisplayPort(aMetrics); + } + + // Correct the display port due to the difference between mScrollOffset and the + // actual scroll offset, possibly align it to tile boundaries (if tiled layers are + // enabled), and clamp it to the scrollable rect. + MaybeAlignAndClampDisplayPort(aMetrics, actualScrollOffset); + + aMetrics.SetScrollOffset(actualScrollOffset); + + // The mZoom variable on the frame metrics stores the CSS-to-screen scale for this + // frame. This scale includes all of the (cumulative) resolutions set on the presShells + // from the root down to this frame. However, when setting the resolution, we only + // want the piece of the resolution that corresponds to this presShell, rather than + // all of the cumulative stuff, so we need to divide out the parent resolutions. + // Finally, we multiply by a ScreenToLayerScale of 1.0f because the goal here is to + // take the async zoom calculated by the APZC and tell gecko about it (turning it into + // a "sync" zoom) which will update the resolution at which the layer is painted. + ParentLayerToLayerScale presShellResolution = + aMetrics.GetZoom() + / aMetrics.mDevPixelsPerCSSPixel + / aMetrics.GetParentResolution() + * ScreenToLayerScale(1.0f); + aUtils->SetResolution(presShellResolution.scale, presShellResolution.scale); + + // Finally, we set the displayport. + nsCOMPtr content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId()); + if (!content) { + return; + } + nsCOMPtr element = do_QueryInterface(content); + if (!element) { + return; + } + if (!aMetrics.GetUseDisplayPortMargins()) { + aUtils->SetDisplayPortForElement(aMetrics.mDisplayPort.x, + aMetrics.mDisplayPort.y, + aMetrics.mDisplayPort.width, + aMetrics.mDisplayPort.height, + element, 0); + } else { + gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled() + ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) : + gfx::IntSize(0, 0); + LayerMargin margins = aMetrics.GetDisplayPortMargins(); + aUtils->SetDisplayPortMarginsForElement(margins.left, + margins.top, + margins.right, + margins.bottom, + alignment.width, + alignment.height, + element, 0); + CSSRect baseCSS = aMetrics.mCompositionBounds / aMetrics.GetZoomToParent(); + nsRect base(baseCSS.x * nsPresContext::AppUnitsPerCSSPixel(), + baseCSS.y * nsPresContext::AppUnitsPerCSSPixel(), + baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(), + baseCSS.height * nsPresContext::AppUnitsPerCSSPixel()); + nsLayoutUtils::SetDisplayPortBaseIfNotSet(content, base); + } +} + +void +APZCCallbackHelper::UpdateSubFrame(nsIContent* aContent, + FrameMetrics& aMetrics) +{ + // Precondition checks + MOZ_ASSERT(aContent); + if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) { + return; + } + + nsCOMPtr utils = GetDOMWindowUtils(aContent); + if (!utils) { + return; + } + + // We currently do not support zooming arbitrary subframes. They can only + // be scrolled, so here we only have to set the scroll position and displayport. + + nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId()); + bool scrollUpdated = false; + CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics.GetScrollOffset(), scrollUpdated); + + nsCOMPtr element = do_QueryInterface(aContent); + if (element) { + if (!scrollUpdated) { + RecenterDisplayPort(aMetrics); + } + MaybeAlignAndClampDisplayPort(aMetrics, actualScrollOffset); + if (!aMetrics.GetUseDisplayPortMargins()) { + utils->SetDisplayPortForElement(aMetrics.mDisplayPort.x, + aMetrics.mDisplayPort.y, + aMetrics.mDisplayPort.width, + aMetrics.mDisplayPort.height, + element, 0); + } else { + gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled() + ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) : + gfx::IntSize(0, 0); + LayerMargin margins = aMetrics.GetDisplayPortMargins(); + utils->SetDisplayPortMarginsForElement(margins.left, + margins.top, + margins.right, + margins.bottom, + alignment.width, + alignment.height, + element, 0); + CSSRect baseCSS = aMetrics.mCompositionBounds / aMetrics.GetZoomToParent(); + nsRect base(baseCSS.x * nsPresContext::AppUnitsPerCSSPixel(), + baseCSS.y * nsPresContext::AppUnitsPerCSSPixel(), + baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(), + baseCSS.height * nsPresContext::AppUnitsPerCSSPixel()); + nsLayoutUtils::SetDisplayPortBaseIfNotSet(aContent, base); + } + } + + aMetrics.SetScrollOffset(actualScrollOffset); +} + +already_AddRefed +APZCCallbackHelper::GetDOMWindowUtils(const nsIDocument* aDoc) +{ + nsCOMPtr utils; + nsCOMPtr window = aDoc->GetDefaultView(); + if (window) { + utils = do_GetInterface(window); + } + return utils.forget(); +} + +already_AddRefed +APZCCallbackHelper::GetDOMWindowUtils(const nsIContent* aContent) +{ + nsCOMPtr utils; + nsIDocument* doc = aContent->GetCurrentDoc(); + if (doc) { + utils = GetDOMWindowUtils(doc); + } + return utils.forget(); +} + +bool +APZCCallbackHelper::GetScrollIdentifiers(const nsIContent* aContent, + uint32_t* aPresShellIdOut, + FrameMetrics::ViewID* aViewIdOut) +{ + if (!aContent || !nsLayoutUtils::FindIDFor(aContent, aViewIdOut)) { + return false; + } + nsCOMPtr utils = GetDOMWindowUtils(aContent); + return utils && (utils->GetPresShellId(aPresShellIdOut) == NS_OK); +} + +class AcknowledgeScrollUpdateEvent : public nsRunnable +{ + typedef mozilla::layers::FrameMetrics::ViewID ViewID; + +public: + AcknowledgeScrollUpdateEvent(const ViewID& aScrollId, const uint32_t& aScrollGeneration) + : mScrollId(aScrollId) + , mScrollGeneration(aScrollGeneration) + { + } + + NS_IMETHOD Run() { + MOZ_ASSERT(NS_IsMainThread()); + + nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(mScrollId); + if (sf) { + sf->ResetOriginIfScrollAtGeneration(mScrollGeneration); + } + + return NS_OK; + } + +protected: + ViewID mScrollId; + uint32_t mScrollGeneration; +}; + +void +APZCCallbackHelper::AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId, + const uint32_t& aScrollGeneration) +{ + nsCOMPtr r1 = new AcknowledgeScrollUpdateEvent(aScrollId, aScrollGeneration); + if (!NS_IsMainThread()) { + NS_DispatchToMainThread(r1); + } else { + r1->Run(); + } +} + +void +APZCCallbackHelper::UpdateCallbackTransform(const FrameMetrics& aApzcMetrics, const FrameMetrics& aActualMetrics) +{ + nsCOMPtr content = nsLayoutUtils::FindContentFor(aApzcMetrics.GetScrollId()); + if (!content) { + return; + } + CSSPoint scrollDelta = aApzcMetrics.GetScrollOffset() - aActualMetrics.GetScrollOffset(); + content->SetProperty(nsGkAtoms::apzCallbackTransform, new CSSPoint(scrollDelta), + nsINode::DeleteProperty); +} + +CSSPoint +APZCCallbackHelper::ApplyCallbackTransform(const CSSPoint& aInput, const ScrollableLayerGuid& aGuid) +{ + // XXX: technically we need to walk all the way up the layer tree from the layer + // represented by |aGuid.mScrollId| up to the root of the layer tree and apply + // the input transforms at each level in turn. However, it is quite difficult + // to do this given that the structure of the layer tree may be different from + // the structure of the content tree. Also it may be impossible to do correctly + // at this point because there are other CSS transforms and such interleaved in + // between so applying the inputTransforms all in a row at the end may leave + // some things transformed improperly. In practice we should rarely hit scenarios + // where any of this matters, so I'm skipping it for now and just doing the single + // transform for the layer that the input hit. + + if (aGuid.mScrollId != FrameMetrics::NULL_SCROLL_ID) { + nsCOMPtr content = nsLayoutUtils::FindContentFor(aGuid.mScrollId); + if (content) { + void* property = content->GetProperty(nsGkAtoms::apzCallbackTransform); + if (property) { + CSSPoint delta = (*static_cast(property)); + return aInput + delta; + } + } + } + return aInput; +} + +nsIntPoint +APZCCallbackHelper::ApplyCallbackTransform(const nsIntPoint& aPoint, + const ScrollableLayerGuid& aGuid, + const CSSToLayoutDeviceScale& aScale) +{ + LayoutDevicePoint point = LayoutDevicePoint(aPoint.x, aPoint.y); + point = ApplyCallbackTransform(point / aScale, aGuid) * aScale; + LayoutDeviceIntPoint ret = gfx::RoundedToInt(point); + return nsIntPoint(ret.x, ret.y); +} + +} +}