gfx/layers/apz/util/APZCCallbackHelper.cpp

branch
TOR_BUG_3246
changeset 7
129ffea94266
equal deleted inserted replaced
-1:000000000000 0:7468ddb020ee
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "APZCCallbackHelper.h"
7 #include "gfxPrefs.h" // For gfxPrefs::LayersTilesEnabled, LayersTileWidth/Height
8 #include "mozilla/Preferences.h"
9 #include "nsIScrollableFrame.h"
10 #include "nsLayoutUtils.h"
11 #include "nsIDOMElement.h"
12 #include "nsIInterfaceRequestorUtils.h"
13
14 namespace mozilla {
15 namespace layers {
16
17 bool
18 APZCCallbackHelper::HasValidPresShellId(nsIDOMWindowUtils* aUtils,
19 const FrameMetrics& aMetrics)
20 {
21 MOZ_ASSERT(aUtils);
22
23 uint32_t presShellId;
24 nsresult rv = aUtils->GetPresShellId(&presShellId);
25 MOZ_ASSERT(NS_SUCCEEDED(rv));
26 return NS_SUCCEEDED(rv) && aMetrics.mPresShellId == presShellId;
27 }
28
29 /**
30 * Expands a given rectangle to the next tile boundary. Note, this will
31 * expand the rectangle if it is already on tile boundaries.
32 */
33 static CSSRect ExpandDisplayPortToTileBoundaries(
34 const CSSRect& aDisplayPort,
35 const CSSToLayerScale& aLayerPixelsPerCSSPixel)
36 {
37 // Convert the given rect to layer coordinates so we can inflate to tile
38 // boundaries (layer space corresponds to texture pixel space here).
39 LayerRect displayPortInLayerSpace = aDisplayPort * aLayerPixelsPerCSSPixel;
40
41 // Inflate the rectangle by 1 so that we always push to the next tile
42 // boundary. This is desirable to stop from having a rectangle with a
43 // moving origin occasionally being smaller when it coincidentally lines
44 // up to tile boundaries.
45 displayPortInLayerSpace.Inflate(1);
46
47 // Now nudge the rectangle to the nearest equal or larger tile boundary.
48 int32_t tileWidth = gfxPrefs::LayersTileWidth();
49 int32_t tileHeight = gfxPrefs::LayersTileHeight();
50 gfxFloat left = tileWidth * floor(displayPortInLayerSpace.x / tileWidth);
51 gfxFloat right = tileWidth * ceil(displayPortInLayerSpace.XMost() / tileWidth);
52 gfxFloat top = tileHeight * floor(displayPortInLayerSpace.y / tileHeight);
53 gfxFloat bottom = tileHeight * ceil(displayPortInLayerSpace.YMost() / tileHeight);
54
55 displayPortInLayerSpace = LayerRect(left, top, right - left, bottom - top);
56 CSSRect displayPort = displayPortInLayerSpace / aLayerPixelsPerCSSPixel;
57
58 return displayPort;
59 }
60
61 static void
62 MaybeAlignAndClampDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics,
63 const CSSPoint& aActualScrollOffset)
64 {
65 // Correct the display-port by the difference between the requested scroll
66 // offset and the resulting scroll offset after setting the requested value.
67 if (!aFrameMetrics.GetUseDisplayPortMargins()) {
68 CSSRect& displayPort = aFrameMetrics.mDisplayPort;
69 displayPort += aFrameMetrics.GetScrollOffset() - aActualScrollOffset;
70
71 // Expand the display port to the next tile boundaries, if tiled thebes layers
72 // are enabled.
73 if (gfxPrefs::LayersTilesEnabled()) {
74 // We don't use LayersPixelsPerCSSPixel() here as mCumulativeResolution on
75 // this FrameMetrics may be incorrect (and is about to be reset by mZoom).
76 displayPort =
77 ExpandDisplayPortToTileBoundaries(displayPort + aActualScrollOffset,
78 aFrameMetrics.GetZoom() *
79 ScreenToLayerScale(1.0))
80 - aActualScrollOffset;
81 }
82
83 // Finally, clamp the display port to the expanded scrollable rect.
84 CSSRect scrollableRect = aFrameMetrics.GetExpandedScrollableRect();
85 displayPort = scrollableRect.Intersect(displayPort + aActualScrollOffset)
86 - aActualScrollOffset;
87 } else {
88 LayerPoint shift =
89 (aFrameMetrics.GetScrollOffset() - aActualScrollOffset) *
90 aFrameMetrics.LayersPixelsPerCSSPixel();
91 LayerMargin margins = aFrameMetrics.GetDisplayPortMargins();
92 margins.left -= shift.x;
93 margins.right += shift.x;
94 margins.top -= shift.y;
95 margins.bottom += shift.y;
96 aFrameMetrics.SetDisplayPortMargins(margins);
97 }
98 }
99
100 static void
101 RecenterDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics)
102 {
103 if (!aFrameMetrics.GetUseDisplayPortMargins()) {
104 CSSSize compositionSize = aFrameMetrics.CalculateCompositedSizeInCssPixels();
105 aFrameMetrics.mDisplayPort.x = (compositionSize.width - aFrameMetrics.mDisplayPort.width) / 2;
106 aFrameMetrics.mDisplayPort.y = (compositionSize.height - aFrameMetrics.mDisplayPort.height) / 2;
107 } else {
108 LayerMargin margins = aFrameMetrics.GetDisplayPortMargins();
109 margins.right = margins.left = margins.LeftRight() / 2;
110 margins.top = margins.bottom = margins.TopBottom() / 2;
111 aFrameMetrics.SetDisplayPortMargins(margins);
112 }
113 }
114
115 static CSSPoint
116 ScrollFrameTo(nsIScrollableFrame* aFrame, const CSSPoint& aPoint, bool& aSuccessOut)
117 {
118 aSuccessOut = false;
119
120 if (!aFrame) {
121 return aPoint;
122 }
123
124 CSSPoint targetScrollPosition = aPoint;
125
126 // If the frame is overflow:hidden on a particular axis, we don't want to allow
127 // user-driven scroll on that axis. Simply set the scroll position on that axis
128 // to whatever it already is. Note that this will leave the APZ's async scroll
129 // position out of sync with the gecko scroll position, but APZ can deal with that
130 // (by design). Note also that when we run into this case, even if both axes
131 // have overflow:hidden, we want to set aSuccessOut to true, so that the displayport
132 // follows the async scroll position rather than the gecko scroll position.
133 CSSPoint geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
134 if (aFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_HIDDEN) {
135 targetScrollPosition.y = geckoScrollPosition.y;
136 }
137 if (aFrame->GetScrollbarStyles().mHorizontal == NS_STYLE_OVERFLOW_HIDDEN) {
138 targetScrollPosition.x = geckoScrollPosition.x;
139 }
140
141 // If the scrollable frame is currently in the middle of an async or smooth
142 // scroll then we don't want to interrupt it (see bug 961280).
143 // Also if the scrollable frame got a scroll request from something other than us
144 // since the last layers update, then we don't want to push our scroll request
145 // because we'll clobber that one, which is bad.
146 if (!aFrame->IsProcessingAsyncScroll() &&
147 (!aFrame->OriginOfLastScroll() || aFrame->OriginOfLastScroll() == nsGkAtoms::apz)) {
148 aFrame->ScrollToCSSPixelsApproximate(targetScrollPosition, nsGkAtoms::apz);
149 geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
150 aSuccessOut = true;
151 }
152 // Return the final scroll position after setting it so that anything that relies
153 // on it can have an accurate value. Note that even if we set it above re-querying it
154 // is a good idea because it may have gotten clamped or rounded.
155 return geckoScrollPosition;
156 }
157
158 void
159 APZCCallbackHelper::UpdateRootFrame(nsIDOMWindowUtils* aUtils,
160 FrameMetrics& aMetrics)
161 {
162 // Precondition checks
163 MOZ_ASSERT(aUtils);
164 if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
165 return;
166 }
167
168 // Set the scroll port size, which determines the scroll range. For example if
169 // a 500-pixel document is shown in a 100-pixel frame, the scroll port length would
170 // be 100, and gecko would limit the maximum scroll offset to 400 (so as to prevent
171 // overscroll). Note that if the content here was zoomed to 2x, the document would
172 // be 1000 pixels long but the frame would still be 100 pixels, and so the maximum
173 // scroll range would be 900. Therefore this calculation depends on the zoom applied
174 // to the content relative to the container.
175 CSSSize scrollPort = aMetrics.CalculateCompositedSizeInCssPixels();
176 aUtils->SetScrollPositionClampingScrollPortSize(scrollPort.width, scrollPort.height);
177
178 // Scroll the window to the desired spot
179 nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId());
180 bool scrollUpdated = false;
181 CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics.GetScrollOffset(), scrollUpdated);
182
183 if (!scrollUpdated) {
184 // For whatever reason we couldn't update the scroll offset on the scroll frame,
185 // which means the data APZ used for its displayport calculation is stale. Fall
186 // back to a sane default behaviour. Note that we don't tile-align the recentered
187 // displayport because tile-alignment depends on the scroll position, and the
188 // scroll position here is out of our control. See bug 966507 comment 21 for a
189 // more detailed explanation.
190 RecenterDisplayPort(aMetrics);
191 }
192
193 // Correct the display port due to the difference between mScrollOffset and the
194 // actual scroll offset, possibly align it to tile boundaries (if tiled layers are
195 // enabled), and clamp it to the scrollable rect.
196 MaybeAlignAndClampDisplayPort(aMetrics, actualScrollOffset);
197
198 aMetrics.SetScrollOffset(actualScrollOffset);
199
200 // The mZoom variable on the frame metrics stores the CSS-to-screen scale for this
201 // frame. This scale includes all of the (cumulative) resolutions set on the presShells
202 // from the root down to this frame. However, when setting the resolution, we only
203 // want the piece of the resolution that corresponds to this presShell, rather than
204 // all of the cumulative stuff, so we need to divide out the parent resolutions.
205 // Finally, we multiply by a ScreenToLayerScale of 1.0f because the goal here is to
206 // take the async zoom calculated by the APZC and tell gecko about it (turning it into
207 // a "sync" zoom) which will update the resolution at which the layer is painted.
208 ParentLayerToLayerScale presShellResolution =
209 aMetrics.GetZoom()
210 / aMetrics.mDevPixelsPerCSSPixel
211 / aMetrics.GetParentResolution()
212 * ScreenToLayerScale(1.0f);
213 aUtils->SetResolution(presShellResolution.scale, presShellResolution.scale);
214
215 // Finally, we set the displayport.
216 nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
217 if (!content) {
218 return;
219 }
220 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(content);
221 if (!element) {
222 return;
223 }
224 if (!aMetrics.GetUseDisplayPortMargins()) {
225 aUtils->SetDisplayPortForElement(aMetrics.mDisplayPort.x,
226 aMetrics.mDisplayPort.y,
227 aMetrics.mDisplayPort.width,
228 aMetrics.mDisplayPort.height,
229 element, 0);
230 } else {
231 gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled()
232 ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) :
233 gfx::IntSize(0, 0);
234 LayerMargin margins = aMetrics.GetDisplayPortMargins();
235 aUtils->SetDisplayPortMarginsForElement(margins.left,
236 margins.top,
237 margins.right,
238 margins.bottom,
239 alignment.width,
240 alignment.height,
241 element, 0);
242 CSSRect baseCSS = aMetrics.mCompositionBounds / aMetrics.GetZoomToParent();
243 nsRect base(baseCSS.x * nsPresContext::AppUnitsPerCSSPixel(),
244 baseCSS.y * nsPresContext::AppUnitsPerCSSPixel(),
245 baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(),
246 baseCSS.height * nsPresContext::AppUnitsPerCSSPixel());
247 nsLayoutUtils::SetDisplayPortBaseIfNotSet(content, base);
248 }
249 }
250
251 void
252 APZCCallbackHelper::UpdateSubFrame(nsIContent* aContent,
253 FrameMetrics& aMetrics)
254 {
255 // Precondition checks
256 MOZ_ASSERT(aContent);
257 if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
258 return;
259 }
260
261 nsCOMPtr<nsIDOMWindowUtils> utils = GetDOMWindowUtils(aContent);
262 if (!utils) {
263 return;
264 }
265
266 // We currently do not support zooming arbitrary subframes. They can only
267 // be scrolled, so here we only have to set the scroll position and displayport.
268
269 nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId());
270 bool scrollUpdated = false;
271 CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics.GetScrollOffset(), scrollUpdated);
272
273 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aContent);
274 if (element) {
275 if (!scrollUpdated) {
276 RecenterDisplayPort(aMetrics);
277 }
278 MaybeAlignAndClampDisplayPort(aMetrics, actualScrollOffset);
279 if (!aMetrics.GetUseDisplayPortMargins()) {
280 utils->SetDisplayPortForElement(aMetrics.mDisplayPort.x,
281 aMetrics.mDisplayPort.y,
282 aMetrics.mDisplayPort.width,
283 aMetrics.mDisplayPort.height,
284 element, 0);
285 } else {
286 gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled()
287 ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) :
288 gfx::IntSize(0, 0);
289 LayerMargin margins = aMetrics.GetDisplayPortMargins();
290 utils->SetDisplayPortMarginsForElement(margins.left,
291 margins.top,
292 margins.right,
293 margins.bottom,
294 alignment.width,
295 alignment.height,
296 element, 0);
297 CSSRect baseCSS = aMetrics.mCompositionBounds / aMetrics.GetZoomToParent();
298 nsRect base(baseCSS.x * nsPresContext::AppUnitsPerCSSPixel(),
299 baseCSS.y * nsPresContext::AppUnitsPerCSSPixel(),
300 baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(),
301 baseCSS.height * nsPresContext::AppUnitsPerCSSPixel());
302 nsLayoutUtils::SetDisplayPortBaseIfNotSet(aContent, base);
303 }
304 }
305
306 aMetrics.SetScrollOffset(actualScrollOffset);
307 }
308
309 already_AddRefed<nsIDOMWindowUtils>
310 APZCCallbackHelper::GetDOMWindowUtils(const nsIDocument* aDoc)
311 {
312 nsCOMPtr<nsIDOMWindowUtils> utils;
313 nsCOMPtr<nsIDOMWindow> window = aDoc->GetDefaultView();
314 if (window) {
315 utils = do_GetInterface(window);
316 }
317 return utils.forget();
318 }
319
320 already_AddRefed<nsIDOMWindowUtils>
321 APZCCallbackHelper::GetDOMWindowUtils(const nsIContent* aContent)
322 {
323 nsCOMPtr<nsIDOMWindowUtils> utils;
324 nsIDocument* doc = aContent->GetCurrentDoc();
325 if (doc) {
326 utils = GetDOMWindowUtils(doc);
327 }
328 return utils.forget();
329 }
330
331 bool
332 APZCCallbackHelper::GetScrollIdentifiers(const nsIContent* aContent,
333 uint32_t* aPresShellIdOut,
334 FrameMetrics::ViewID* aViewIdOut)
335 {
336 if (!aContent || !nsLayoutUtils::FindIDFor(aContent, aViewIdOut)) {
337 return false;
338 }
339 nsCOMPtr<nsIDOMWindowUtils> utils = GetDOMWindowUtils(aContent);
340 return utils && (utils->GetPresShellId(aPresShellIdOut) == NS_OK);
341 }
342
343 class AcknowledgeScrollUpdateEvent : public nsRunnable
344 {
345 typedef mozilla::layers::FrameMetrics::ViewID ViewID;
346
347 public:
348 AcknowledgeScrollUpdateEvent(const ViewID& aScrollId, const uint32_t& aScrollGeneration)
349 : mScrollId(aScrollId)
350 , mScrollGeneration(aScrollGeneration)
351 {
352 }
353
354 NS_IMETHOD Run() {
355 MOZ_ASSERT(NS_IsMainThread());
356
357 nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(mScrollId);
358 if (sf) {
359 sf->ResetOriginIfScrollAtGeneration(mScrollGeneration);
360 }
361
362 return NS_OK;
363 }
364
365 protected:
366 ViewID mScrollId;
367 uint32_t mScrollGeneration;
368 };
369
370 void
371 APZCCallbackHelper::AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
372 const uint32_t& aScrollGeneration)
373 {
374 nsCOMPtr<nsIRunnable> r1 = new AcknowledgeScrollUpdateEvent(aScrollId, aScrollGeneration);
375 if (!NS_IsMainThread()) {
376 NS_DispatchToMainThread(r1);
377 } else {
378 r1->Run();
379 }
380 }
381
382 void
383 APZCCallbackHelper::UpdateCallbackTransform(const FrameMetrics& aApzcMetrics, const FrameMetrics& aActualMetrics)
384 {
385 nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aApzcMetrics.GetScrollId());
386 if (!content) {
387 return;
388 }
389 CSSPoint scrollDelta = aApzcMetrics.GetScrollOffset() - aActualMetrics.GetScrollOffset();
390 content->SetProperty(nsGkAtoms::apzCallbackTransform, new CSSPoint(scrollDelta),
391 nsINode::DeleteProperty<CSSPoint>);
392 }
393
394 CSSPoint
395 APZCCallbackHelper::ApplyCallbackTransform(const CSSPoint& aInput, const ScrollableLayerGuid& aGuid)
396 {
397 // XXX: technically we need to walk all the way up the layer tree from the layer
398 // represented by |aGuid.mScrollId| up to the root of the layer tree and apply
399 // the input transforms at each level in turn. However, it is quite difficult
400 // to do this given that the structure of the layer tree may be different from
401 // the structure of the content tree. Also it may be impossible to do correctly
402 // at this point because there are other CSS transforms and such interleaved in
403 // between so applying the inputTransforms all in a row at the end may leave
404 // some things transformed improperly. In practice we should rarely hit scenarios
405 // where any of this matters, so I'm skipping it for now and just doing the single
406 // transform for the layer that the input hit.
407
408 if (aGuid.mScrollId != FrameMetrics::NULL_SCROLL_ID) {
409 nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aGuid.mScrollId);
410 if (content) {
411 void* property = content->GetProperty(nsGkAtoms::apzCallbackTransform);
412 if (property) {
413 CSSPoint delta = (*static_cast<CSSPoint*>(property));
414 return aInput + delta;
415 }
416 }
417 }
418 return aInput;
419 }
420
421 nsIntPoint
422 APZCCallbackHelper::ApplyCallbackTransform(const nsIntPoint& aPoint,
423 const ScrollableLayerGuid& aGuid,
424 const CSSToLayoutDeviceScale& aScale)
425 {
426 LayoutDevicePoint point = LayoutDevicePoint(aPoint.x, aPoint.y);
427 point = ApplyCallbackTransform(point / aScale, aGuid) * aScale;
428 LayoutDeviceIntPoint ret = gfx::RoundedToInt(point);
429 return nsIntPoint(ret.x, ret.y);
430 }
431
432 }
433 }

mercurial