|
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 } |