1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,433 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "APZCCallbackHelper.h" 1.10 +#include "gfxPrefs.h" // For gfxPrefs::LayersTilesEnabled, LayersTileWidth/Height 1.11 +#include "mozilla/Preferences.h" 1.12 +#include "nsIScrollableFrame.h" 1.13 +#include "nsLayoutUtils.h" 1.14 +#include "nsIDOMElement.h" 1.15 +#include "nsIInterfaceRequestorUtils.h" 1.16 + 1.17 +namespace mozilla { 1.18 +namespace layers { 1.19 + 1.20 +bool 1.21 +APZCCallbackHelper::HasValidPresShellId(nsIDOMWindowUtils* aUtils, 1.22 + const FrameMetrics& aMetrics) 1.23 +{ 1.24 + MOZ_ASSERT(aUtils); 1.25 + 1.26 + uint32_t presShellId; 1.27 + nsresult rv = aUtils->GetPresShellId(&presShellId); 1.28 + MOZ_ASSERT(NS_SUCCEEDED(rv)); 1.29 + return NS_SUCCEEDED(rv) && aMetrics.mPresShellId == presShellId; 1.30 +} 1.31 + 1.32 +/** 1.33 + * Expands a given rectangle to the next tile boundary. Note, this will 1.34 + * expand the rectangle if it is already on tile boundaries. 1.35 + */ 1.36 +static CSSRect ExpandDisplayPortToTileBoundaries( 1.37 + const CSSRect& aDisplayPort, 1.38 + const CSSToLayerScale& aLayerPixelsPerCSSPixel) 1.39 +{ 1.40 + // Convert the given rect to layer coordinates so we can inflate to tile 1.41 + // boundaries (layer space corresponds to texture pixel space here). 1.42 + LayerRect displayPortInLayerSpace = aDisplayPort * aLayerPixelsPerCSSPixel; 1.43 + 1.44 + // Inflate the rectangle by 1 so that we always push to the next tile 1.45 + // boundary. This is desirable to stop from having a rectangle with a 1.46 + // moving origin occasionally being smaller when it coincidentally lines 1.47 + // up to tile boundaries. 1.48 + displayPortInLayerSpace.Inflate(1); 1.49 + 1.50 + // Now nudge the rectangle to the nearest equal or larger tile boundary. 1.51 + int32_t tileWidth = gfxPrefs::LayersTileWidth(); 1.52 + int32_t tileHeight = gfxPrefs::LayersTileHeight(); 1.53 + gfxFloat left = tileWidth * floor(displayPortInLayerSpace.x / tileWidth); 1.54 + gfxFloat right = tileWidth * ceil(displayPortInLayerSpace.XMost() / tileWidth); 1.55 + gfxFloat top = tileHeight * floor(displayPortInLayerSpace.y / tileHeight); 1.56 + gfxFloat bottom = tileHeight * ceil(displayPortInLayerSpace.YMost() / tileHeight); 1.57 + 1.58 + displayPortInLayerSpace = LayerRect(left, top, right - left, bottom - top); 1.59 + CSSRect displayPort = displayPortInLayerSpace / aLayerPixelsPerCSSPixel; 1.60 + 1.61 + return displayPort; 1.62 +} 1.63 + 1.64 +static void 1.65 +MaybeAlignAndClampDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics, 1.66 + const CSSPoint& aActualScrollOffset) 1.67 +{ 1.68 + // Correct the display-port by the difference between the requested scroll 1.69 + // offset and the resulting scroll offset after setting the requested value. 1.70 + if (!aFrameMetrics.GetUseDisplayPortMargins()) { 1.71 + CSSRect& displayPort = aFrameMetrics.mDisplayPort; 1.72 + displayPort += aFrameMetrics.GetScrollOffset() - aActualScrollOffset; 1.73 + 1.74 + // Expand the display port to the next tile boundaries, if tiled thebes layers 1.75 + // are enabled. 1.76 + if (gfxPrefs::LayersTilesEnabled()) { 1.77 + // We don't use LayersPixelsPerCSSPixel() here as mCumulativeResolution on 1.78 + // this FrameMetrics may be incorrect (and is about to be reset by mZoom). 1.79 + displayPort = 1.80 + ExpandDisplayPortToTileBoundaries(displayPort + aActualScrollOffset, 1.81 + aFrameMetrics.GetZoom() * 1.82 + ScreenToLayerScale(1.0)) 1.83 + - aActualScrollOffset; 1.84 + } 1.85 + 1.86 + // Finally, clamp the display port to the expanded scrollable rect. 1.87 + CSSRect scrollableRect = aFrameMetrics.GetExpandedScrollableRect(); 1.88 + displayPort = scrollableRect.Intersect(displayPort + aActualScrollOffset) 1.89 + - aActualScrollOffset; 1.90 + } else { 1.91 + LayerPoint shift = 1.92 + (aFrameMetrics.GetScrollOffset() - aActualScrollOffset) * 1.93 + aFrameMetrics.LayersPixelsPerCSSPixel(); 1.94 + LayerMargin margins = aFrameMetrics.GetDisplayPortMargins(); 1.95 + margins.left -= shift.x; 1.96 + margins.right += shift.x; 1.97 + margins.top -= shift.y; 1.98 + margins.bottom += shift.y; 1.99 + aFrameMetrics.SetDisplayPortMargins(margins); 1.100 + } 1.101 +} 1.102 + 1.103 +static void 1.104 +RecenterDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics) 1.105 +{ 1.106 + if (!aFrameMetrics.GetUseDisplayPortMargins()) { 1.107 + CSSSize compositionSize = aFrameMetrics.CalculateCompositedSizeInCssPixels(); 1.108 + aFrameMetrics.mDisplayPort.x = (compositionSize.width - aFrameMetrics.mDisplayPort.width) / 2; 1.109 + aFrameMetrics.mDisplayPort.y = (compositionSize.height - aFrameMetrics.mDisplayPort.height) / 2; 1.110 + } else { 1.111 + LayerMargin margins = aFrameMetrics.GetDisplayPortMargins(); 1.112 + margins.right = margins.left = margins.LeftRight() / 2; 1.113 + margins.top = margins.bottom = margins.TopBottom() / 2; 1.114 + aFrameMetrics.SetDisplayPortMargins(margins); 1.115 + } 1.116 +} 1.117 + 1.118 +static CSSPoint 1.119 +ScrollFrameTo(nsIScrollableFrame* aFrame, const CSSPoint& aPoint, bool& aSuccessOut) 1.120 +{ 1.121 + aSuccessOut = false; 1.122 + 1.123 + if (!aFrame) { 1.124 + return aPoint; 1.125 + } 1.126 + 1.127 + CSSPoint targetScrollPosition = aPoint; 1.128 + 1.129 + // If the frame is overflow:hidden on a particular axis, we don't want to allow 1.130 + // user-driven scroll on that axis. Simply set the scroll position on that axis 1.131 + // to whatever it already is. Note that this will leave the APZ's async scroll 1.132 + // position out of sync with the gecko scroll position, but APZ can deal with that 1.133 + // (by design). Note also that when we run into this case, even if both axes 1.134 + // have overflow:hidden, we want to set aSuccessOut to true, so that the displayport 1.135 + // follows the async scroll position rather than the gecko scroll position. 1.136 + CSSPoint geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition()); 1.137 + if (aFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_HIDDEN) { 1.138 + targetScrollPosition.y = geckoScrollPosition.y; 1.139 + } 1.140 + if (aFrame->GetScrollbarStyles().mHorizontal == NS_STYLE_OVERFLOW_HIDDEN) { 1.141 + targetScrollPosition.x = geckoScrollPosition.x; 1.142 + } 1.143 + 1.144 + // If the scrollable frame is currently in the middle of an async or smooth 1.145 + // scroll then we don't want to interrupt it (see bug 961280). 1.146 + // Also if the scrollable frame got a scroll request from something other than us 1.147 + // since the last layers update, then we don't want to push our scroll request 1.148 + // because we'll clobber that one, which is bad. 1.149 + if (!aFrame->IsProcessingAsyncScroll() && 1.150 + (!aFrame->OriginOfLastScroll() || aFrame->OriginOfLastScroll() == nsGkAtoms::apz)) { 1.151 + aFrame->ScrollToCSSPixelsApproximate(targetScrollPosition, nsGkAtoms::apz); 1.152 + geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition()); 1.153 + aSuccessOut = true; 1.154 + } 1.155 + // Return the final scroll position after setting it so that anything that relies 1.156 + // on it can have an accurate value. Note that even if we set it above re-querying it 1.157 + // is a good idea because it may have gotten clamped or rounded. 1.158 + return geckoScrollPosition; 1.159 +} 1.160 + 1.161 +void 1.162 +APZCCallbackHelper::UpdateRootFrame(nsIDOMWindowUtils* aUtils, 1.163 + FrameMetrics& aMetrics) 1.164 +{ 1.165 + // Precondition checks 1.166 + MOZ_ASSERT(aUtils); 1.167 + if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) { 1.168 + return; 1.169 + } 1.170 + 1.171 + // Set the scroll port size, which determines the scroll range. For example if 1.172 + // a 500-pixel document is shown in a 100-pixel frame, the scroll port length would 1.173 + // be 100, and gecko would limit the maximum scroll offset to 400 (so as to prevent 1.174 + // overscroll). Note that if the content here was zoomed to 2x, the document would 1.175 + // be 1000 pixels long but the frame would still be 100 pixels, and so the maximum 1.176 + // scroll range would be 900. Therefore this calculation depends on the zoom applied 1.177 + // to the content relative to the container. 1.178 + CSSSize scrollPort = aMetrics.CalculateCompositedSizeInCssPixels(); 1.179 + aUtils->SetScrollPositionClampingScrollPortSize(scrollPort.width, scrollPort.height); 1.180 + 1.181 + // Scroll the window to the desired spot 1.182 + nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId()); 1.183 + bool scrollUpdated = false; 1.184 + CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics.GetScrollOffset(), scrollUpdated); 1.185 + 1.186 + if (!scrollUpdated) { 1.187 + // For whatever reason we couldn't update the scroll offset on the scroll frame, 1.188 + // which means the data APZ used for its displayport calculation is stale. Fall 1.189 + // back to a sane default behaviour. Note that we don't tile-align the recentered 1.190 + // displayport because tile-alignment depends on the scroll position, and the 1.191 + // scroll position here is out of our control. See bug 966507 comment 21 for a 1.192 + // more detailed explanation. 1.193 + RecenterDisplayPort(aMetrics); 1.194 + } 1.195 + 1.196 + // Correct the display port due to the difference between mScrollOffset and the 1.197 + // actual scroll offset, possibly align it to tile boundaries (if tiled layers are 1.198 + // enabled), and clamp it to the scrollable rect. 1.199 + MaybeAlignAndClampDisplayPort(aMetrics, actualScrollOffset); 1.200 + 1.201 + aMetrics.SetScrollOffset(actualScrollOffset); 1.202 + 1.203 + // The mZoom variable on the frame metrics stores the CSS-to-screen scale for this 1.204 + // frame. This scale includes all of the (cumulative) resolutions set on the presShells 1.205 + // from the root down to this frame. However, when setting the resolution, we only 1.206 + // want the piece of the resolution that corresponds to this presShell, rather than 1.207 + // all of the cumulative stuff, so we need to divide out the parent resolutions. 1.208 + // Finally, we multiply by a ScreenToLayerScale of 1.0f because the goal here is to 1.209 + // take the async zoom calculated by the APZC and tell gecko about it (turning it into 1.210 + // a "sync" zoom) which will update the resolution at which the layer is painted. 1.211 + ParentLayerToLayerScale presShellResolution = 1.212 + aMetrics.GetZoom() 1.213 + / aMetrics.mDevPixelsPerCSSPixel 1.214 + / aMetrics.GetParentResolution() 1.215 + * ScreenToLayerScale(1.0f); 1.216 + aUtils->SetResolution(presShellResolution.scale, presShellResolution.scale); 1.217 + 1.218 + // Finally, we set the displayport. 1.219 + nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId()); 1.220 + if (!content) { 1.221 + return; 1.222 + } 1.223 + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(content); 1.224 + if (!element) { 1.225 + return; 1.226 + } 1.227 + if (!aMetrics.GetUseDisplayPortMargins()) { 1.228 + aUtils->SetDisplayPortForElement(aMetrics.mDisplayPort.x, 1.229 + aMetrics.mDisplayPort.y, 1.230 + aMetrics.mDisplayPort.width, 1.231 + aMetrics.mDisplayPort.height, 1.232 + element, 0); 1.233 + } else { 1.234 + gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled() 1.235 + ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) : 1.236 + gfx::IntSize(0, 0); 1.237 + LayerMargin margins = aMetrics.GetDisplayPortMargins(); 1.238 + aUtils->SetDisplayPortMarginsForElement(margins.left, 1.239 + margins.top, 1.240 + margins.right, 1.241 + margins.bottom, 1.242 + alignment.width, 1.243 + alignment.height, 1.244 + element, 0); 1.245 + CSSRect baseCSS = aMetrics.mCompositionBounds / aMetrics.GetZoomToParent(); 1.246 + nsRect base(baseCSS.x * nsPresContext::AppUnitsPerCSSPixel(), 1.247 + baseCSS.y * nsPresContext::AppUnitsPerCSSPixel(), 1.248 + baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(), 1.249 + baseCSS.height * nsPresContext::AppUnitsPerCSSPixel()); 1.250 + nsLayoutUtils::SetDisplayPortBaseIfNotSet(content, base); 1.251 + } 1.252 +} 1.253 + 1.254 +void 1.255 +APZCCallbackHelper::UpdateSubFrame(nsIContent* aContent, 1.256 + FrameMetrics& aMetrics) 1.257 +{ 1.258 + // Precondition checks 1.259 + MOZ_ASSERT(aContent); 1.260 + if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) { 1.261 + return; 1.262 + } 1.263 + 1.264 + nsCOMPtr<nsIDOMWindowUtils> utils = GetDOMWindowUtils(aContent); 1.265 + if (!utils) { 1.266 + return; 1.267 + } 1.268 + 1.269 + // We currently do not support zooming arbitrary subframes. They can only 1.270 + // be scrolled, so here we only have to set the scroll position and displayport. 1.271 + 1.272 + nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId()); 1.273 + bool scrollUpdated = false; 1.274 + CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics.GetScrollOffset(), scrollUpdated); 1.275 + 1.276 + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aContent); 1.277 + if (element) { 1.278 + if (!scrollUpdated) { 1.279 + RecenterDisplayPort(aMetrics); 1.280 + } 1.281 + MaybeAlignAndClampDisplayPort(aMetrics, actualScrollOffset); 1.282 + if (!aMetrics.GetUseDisplayPortMargins()) { 1.283 + utils->SetDisplayPortForElement(aMetrics.mDisplayPort.x, 1.284 + aMetrics.mDisplayPort.y, 1.285 + aMetrics.mDisplayPort.width, 1.286 + aMetrics.mDisplayPort.height, 1.287 + element, 0); 1.288 + } else { 1.289 + gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled() 1.290 + ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) : 1.291 + gfx::IntSize(0, 0); 1.292 + LayerMargin margins = aMetrics.GetDisplayPortMargins(); 1.293 + utils->SetDisplayPortMarginsForElement(margins.left, 1.294 + margins.top, 1.295 + margins.right, 1.296 + margins.bottom, 1.297 + alignment.width, 1.298 + alignment.height, 1.299 + element, 0); 1.300 + CSSRect baseCSS = aMetrics.mCompositionBounds / aMetrics.GetZoomToParent(); 1.301 + nsRect base(baseCSS.x * nsPresContext::AppUnitsPerCSSPixel(), 1.302 + baseCSS.y * nsPresContext::AppUnitsPerCSSPixel(), 1.303 + baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(), 1.304 + baseCSS.height * nsPresContext::AppUnitsPerCSSPixel()); 1.305 + nsLayoutUtils::SetDisplayPortBaseIfNotSet(aContent, base); 1.306 + } 1.307 + } 1.308 + 1.309 + aMetrics.SetScrollOffset(actualScrollOffset); 1.310 +} 1.311 + 1.312 +already_AddRefed<nsIDOMWindowUtils> 1.313 +APZCCallbackHelper::GetDOMWindowUtils(const nsIDocument* aDoc) 1.314 +{ 1.315 + nsCOMPtr<nsIDOMWindowUtils> utils; 1.316 + nsCOMPtr<nsIDOMWindow> window = aDoc->GetDefaultView(); 1.317 + if (window) { 1.318 + utils = do_GetInterface(window); 1.319 + } 1.320 + return utils.forget(); 1.321 +} 1.322 + 1.323 +already_AddRefed<nsIDOMWindowUtils> 1.324 +APZCCallbackHelper::GetDOMWindowUtils(const nsIContent* aContent) 1.325 +{ 1.326 + nsCOMPtr<nsIDOMWindowUtils> utils; 1.327 + nsIDocument* doc = aContent->GetCurrentDoc(); 1.328 + if (doc) { 1.329 + utils = GetDOMWindowUtils(doc); 1.330 + } 1.331 + return utils.forget(); 1.332 +} 1.333 + 1.334 +bool 1.335 +APZCCallbackHelper::GetScrollIdentifiers(const nsIContent* aContent, 1.336 + uint32_t* aPresShellIdOut, 1.337 + FrameMetrics::ViewID* aViewIdOut) 1.338 +{ 1.339 + if (!aContent || !nsLayoutUtils::FindIDFor(aContent, aViewIdOut)) { 1.340 + return false; 1.341 + } 1.342 + nsCOMPtr<nsIDOMWindowUtils> utils = GetDOMWindowUtils(aContent); 1.343 + return utils && (utils->GetPresShellId(aPresShellIdOut) == NS_OK); 1.344 +} 1.345 + 1.346 +class AcknowledgeScrollUpdateEvent : public nsRunnable 1.347 +{ 1.348 + typedef mozilla::layers::FrameMetrics::ViewID ViewID; 1.349 + 1.350 +public: 1.351 + AcknowledgeScrollUpdateEvent(const ViewID& aScrollId, const uint32_t& aScrollGeneration) 1.352 + : mScrollId(aScrollId) 1.353 + , mScrollGeneration(aScrollGeneration) 1.354 + { 1.355 + } 1.356 + 1.357 + NS_IMETHOD Run() { 1.358 + MOZ_ASSERT(NS_IsMainThread()); 1.359 + 1.360 + nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(mScrollId); 1.361 + if (sf) { 1.362 + sf->ResetOriginIfScrollAtGeneration(mScrollGeneration); 1.363 + } 1.364 + 1.365 + return NS_OK; 1.366 + } 1.367 + 1.368 +protected: 1.369 + ViewID mScrollId; 1.370 + uint32_t mScrollGeneration; 1.371 +}; 1.372 + 1.373 +void 1.374 +APZCCallbackHelper::AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId, 1.375 + const uint32_t& aScrollGeneration) 1.376 +{ 1.377 + nsCOMPtr<nsIRunnable> r1 = new AcknowledgeScrollUpdateEvent(aScrollId, aScrollGeneration); 1.378 + if (!NS_IsMainThread()) { 1.379 + NS_DispatchToMainThread(r1); 1.380 + } else { 1.381 + r1->Run(); 1.382 + } 1.383 +} 1.384 + 1.385 +void 1.386 +APZCCallbackHelper::UpdateCallbackTransform(const FrameMetrics& aApzcMetrics, const FrameMetrics& aActualMetrics) 1.387 +{ 1.388 + nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aApzcMetrics.GetScrollId()); 1.389 + if (!content) { 1.390 + return; 1.391 + } 1.392 + CSSPoint scrollDelta = aApzcMetrics.GetScrollOffset() - aActualMetrics.GetScrollOffset(); 1.393 + content->SetProperty(nsGkAtoms::apzCallbackTransform, new CSSPoint(scrollDelta), 1.394 + nsINode::DeleteProperty<CSSPoint>); 1.395 +} 1.396 + 1.397 +CSSPoint 1.398 +APZCCallbackHelper::ApplyCallbackTransform(const CSSPoint& aInput, const ScrollableLayerGuid& aGuid) 1.399 +{ 1.400 + // XXX: technically we need to walk all the way up the layer tree from the layer 1.401 + // represented by |aGuid.mScrollId| up to the root of the layer tree and apply 1.402 + // the input transforms at each level in turn. However, it is quite difficult 1.403 + // to do this given that the structure of the layer tree may be different from 1.404 + // the structure of the content tree. Also it may be impossible to do correctly 1.405 + // at this point because there are other CSS transforms and such interleaved in 1.406 + // between so applying the inputTransforms all in a row at the end may leave 1.407 + // some things transformed improperly. In practice we should rarely hit scenarios 1.408 + // where any of this matters, so I'm skipping it for now and just doing the single 1.409 + // transform for the layer that the input hit. 1.410 + 1.411 + if (aGuid.mScrollId != FrameMetrics::NULL_SCROLL_ID) { 1.412 + nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aGuid.mScrollId); 1.413 + if (content) { 1.414 + void* property = content->GetProperty(nsGkAtoms::apzCallbackTransform); 1.415 + if (property) { 1.416 + CSSPoint delta = (*static_cast<CSSPoint*>(property)); 1.417 + return aInput + delta; 1.418 + } 1.419 + } 1.420 + } 1.421 + return aInput; 1.422 +} 1.423 + 1.424 +nsIntPoint 1.425 +APZCCallbackHelper::ApplyCallbackTransform(const nsIntPoint& aPoint, 1.426 + const ScrollableLayerGuid& aGuid, 1.427 + const CSSToLayoutDeviceScale& aScale) 1.428 +{ 1.429 + LayoutDevicePoint point = LayoutDevicePoint(aPoint.x, aPoint.y); 1.430 + point = ApplyCallbackTransform(point / aScale, aGuid) * aScale; 1.431 + LayoutDeviceIntPoint ret = gfx::RoundedToInt(point); 1.432 + return nsIntPoint(ret.x, ret.y); 1.433 +} 1.434 + 1.435 +} 1.436 +}