Line data Source code
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 :
8 : #include "TouchActionHelper.h"
9 : #include "gfxPlatform.h" // For gfxPlatform::UseTiling
10 : #include "gfxPrefs.h"
11 : #include "LayersLogging.h" // For Stringify
12 : #include "mozilla/dom/Element.h"
13 : #include "mozilla/dom/TabParent.h"
14 : #include "mozilla/IntegerPrintfMacros.h"
15 : #include "mozilla/layers/LayerTransactionChild.h"
16 : #include "mozilla/layers/ShadowLayers.h"
17 : #include "mozilla/layers/WebRenderLayerManager.h"
18 : #include "mozilla/layers/WebRenderBridgeChild.h"
19 : #include "mozilla/TouchEvents.h"
20 : #include "nsContentUtils.h"
21 : #include "nsContainerFrame.h"
22 : #include "nsIScrollableFrame.h"
23 : #include "nsLayoutUtils.h"
24 : #include "nsIInterfaceRequestorUtils.h"
25 : #include "nsIContent.h"
26 : #include "nsIDocument.h"
27 : #include "nsIDOMWindow.h"
28 : #include "nsIDOMWindowUtils.h"
29 : #include "nsRefreshDriver.h"
30 : #include "nsString.h"
31 : #include "nsView.h"
32 : #include "Layers.h"
33 :
34 : // #define APZCCH_LOGGING 1
35 : #ifdef APZCCH_LOGGING
36 : #define APZCCH_LOG(...) printf_stderr("APZCCH: " __VA_ARGS__)
37 : #else
38 : #define APZCCH_LOG(...)
39 : #endif
40 :
41 : namespace mozilla {
42 : namespace layers {
43 :
44 : using dom::TabParent;
45 :
46 : uint64_t APZCCallbackHelper::sLastTargetAPZCNotificationInputBlock = uint64_t(-1);
47 :
48 : void
49 0 : APZCCallbackHelper::AdjustDisplayPortForScrollDelta(
50 : mozilla::layers::FrameMetrics& aFrameMetrics,
51 : const CSSPoint& aActualScrollOffset)
52 : {
53 : // Correct the display-port by the difference between the requested scroll
54 : // offset and the resulting scroll offset after setting the requested value.
55 : ScreenPoint shift =
56 0 : (aFrameMetrics.GetScrollOffset() - aActualScrollOffset) *
57 0 : aFrameMetrics.DisplayportPixelsPerCSSPixel();
58 0 : ScreenMargin margins = aFrameMetrics.GetDisplayPortMargins();
59 0 : margins.left -= shift.x;
60 0 : margins.right += shift.x;
61 0 : margins.top -= shift.y;
62 0 : margins.bottom += shift.y;
63 0 : aFrameMetrics.SetDisplayPortMargins(margins);
64 0 : }
65 :
66 : static void
67 2 : RecenterDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics)
68 : {
69 2 : ScreenMargin margins = aFrameMetrics.GetDisplayPortMargins();
70 2 : margins.right = margins.left = margins.LeftRight() / 2;
71 2 : margins.top = margins.bottom = margins.TopBottom() / 2;
72 2 : aFrameMetrics.SetDisplayPortMargins(margins);
73 2 : }
74 :
75 : static CSSPoint
76 2 : ScrollFrameTo(nsIScrollableFrame* aFrame, const FrameMetrics& aMetrics, bool& aSuccessOut)
77 : {
78 2 : aSuccessOut = false;
79 2 : CSSPoint targetScrollPosition = aMetrics.GetScrollOffset();
80 :
81 2 : if (!aFrame) {
82 2 : return targetScrollPosition;
83 : }
84 :
85 0 : CSSPoint geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
86 :
87 : // If the repaint request was triggered due to a previous main-thread scroll
88 : // offset update sent to the APZ, then we don't need to do another scroll here
89 : // and we can just return.
90 0 : if (!aMetrics.GetScrollOffsetUpdated()) {
91 0 : return geckoScrollPosition;
92 : }
93 :
94 : // If the frame is overflow:hidden on a particular axis, we don't want to allow
95 : // user-driven scroll on that axis. Simply set the scroll position on that axis
96 : // to whatever it already is. Note that this will leave the APZ's async scroll
97 : // position out of sync with the gecko scroll position, but APZ can deal with that
98 : // (by design). Note also that when we run into this case, even if both axes
99 : // have overflow:hidden, we want to set aSuccessOut to true, so that the displayport
100 : // follows the async scroll position rather than the gecko scroll position.
101 0 : if (aFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_HIDDEN) {
102 0 : targetScrollPosition.y = geckoScrollPosition.y;
103 : }
104 0 : if (aFrame->GetScrollbarStyles().mHorizontal == NS_STYLE_OVERFLOW_HIDDEN) {
105 0 : targetScrollPosition.x = geckoScrollPosition.x;
106 : }
107 :
108 : // If the scrollable frame is currently in the middle of an async or smooth
109 : // scroll then we don't want to interrupt it (see bug 961280).
110 : // Also if the scrollable frame got a scroll request from a higher priority origin
111 : // since the last layers update, then we don't want to push our scroll request
112 : // because we'll clobber that one, which is bad.
113 0 : bool scrollInProgress = APZCCallbackHelper::IsScrollInProgress(aFrame);
114 0 : if (!scrollInProgress) {
115 0 : aFrame->ScrollToCSSPixelsApproximate(targetScrollPosition, nsGkAtoms::apz);
116 0 : geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
117 0 : aSuccessOut = true;
118 : }
119 : // Return the final scroll position after setting it so that anything that relies
120 : // on it can have an accurate value. Note that even if we set it above re-querying it
121 : // is a good idea because it may have gotten clamped or rounded.
122 0 : return geckoScrollPosition;
123 : }
124 :
125 : /**
126 : * Scroll the scroll frame associated with |aContent| to the scroll position
127 : * requested in |aMetrics|.
128 : * The scroll offset in |aMetrics| is updated to reflect the actual scroll
129 : * position.
130 : * The displayport stored in |aMetrics| and the callback-transform stored on
131 : * the content are updated to reflect any difference between the requested
132 : * and actual scroll positions.
133 : */
134 : static void
135 2 : ScrollFrame(nsIContent* aContent,
136 : FrameMetrics& aMetrics)
137 : {
138 : // Scroll the window to the desired spot
139 2 : nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId());
140 2 : if (sf) {
141 0 : sf->ResetScrollInfoIfGeneration(aMetrics.GetScrollGeneration());
142 0 : sf->SetScrollableByAPZ(!aMetrics.IsScrollInfoLayer());
143 : }
144 2 : bool scrollUpdated = false;
145 2 : CSSPoint apzScrollOffset = aMetrics.GetScrollOffset();
146 2 : CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics, scrollUpdated);
147 :
148 2 : if (scrollUpdated) {
149 0 : if (aMetrics.IsScrollInfoLayer()) {
150 : // In cases where the APZ scroll offset is different from the content scroll
151 : // offset, we want to interpret the margins as relative to the APZ scroll
152 : // offset except when the frame is not scrollable by APZ. Therefore, if the
153 : // layer is a scroll info layer, we leave the margins as-is and they will
154 : // be interpreted as relative to the content scroll offset.
155 0 : if (nsIFrame* frame = aContent->GetPrimaryFrame()) {
156 0 : frame->SchedulePaint();
157 : }
158 : } else {
159 : // Correct the display port due to the difference between mScrollOffset and the
160 : // actual scroll offset.
161 0 : APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aMetrics, actualScrollOffset);
162 : }
163 : } else {
164 : // For whatever reason we couldn't update the scroll offset on the scroll frame,
165 : // which means the data APZ used for its displayport calculation is stale. Fall
166 : // back to a sane default behaviour. Note that we don't tile-align the recentered
167 : // displayport because tile-alignment depends on the scroll position, and the
168 : // scroll position here is out of our control. See bug 966507 comment 21 for a
169 : // more detailed explanation.
170 2 : RecenterDisplayPort(aMetrics);
171 : }
172 :
173 2 : aMetrics.SetScrollOffset(actualScrollOffset);
174 :
175 : // APZ transforms inputs assuming we applied the exact scroll offset it
176 : // requested (|apzScrollOffset|). Since we may not have, record the difference
177 : // between what APZ asked for and what we actually applied, and apply it to
178 : // input events to compensate.
179 : // Note that if the main-thread had a change in its scroll position, we don't
180 : // want to record that difference here, because it can be large and throw off
181 : // input events by a large amount. It is also going to be transient, because
182 : // any main-thread scroll position change will be synced to APZ and we will
183 : // get another repaint request when APZ confirms. In the interval while this
184 : // is happening we can just leave the callback transform as it was.
185 : bool mainThreadScrollChanged =
186 2 : sf && sf->CurrentScrollGeneration() != aMetrics.GetScrollGeneration() && nsLayoutUtils::CanScrollOriginClobberApz(sf->LastScrollOrigin());
187 2 : if (aContent && !mainThreadScrollChanged) {
188 2 : CSSPoint scrollDelta = apzScrollOffset - actualScrollOffset;
189 4 : aContent->SetProperty(nsGkAtoms::apzCallbackTransform, new CSSPoint(scrollDelta),
190 4 : nsINode::DeleteProperty<CSSPoint>);
191 : }
192 2 : }
193 :
194 : static void
195 2 : SetDisplayPortMargins(nsIPresShell* aPresShell,
196 : nsIContent* aContent,
197 : const FrameMetrics& aMetrics)
198 : {
199 2 : if (!aContent) {
200 0 : return;
201 : }
202 :
203 2 : bool hadDisplayPort = nsLayoutUtils::HasDisplayPort(aContent);
204 2 : ScreenMargin margins = aMetrics.GetDisplayPortMargins();
205 2 : nsLayoutUtils::SetDisplayPortMargins(aContent, aPresShell, margins, 0);
206 2 : if (!hadDisplayPort) {
207 0 : nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
208 0 : aContent->GetPrimaryFrame(), nsLayoutUtils::RepaintMode::Repaint);
209 : }
210 :
211 2 : CSSRect baseCSS = aMetrics.CalculateCompositedRectInCssPixels();
212 : nsRect base(0, 0,
213 2 : baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(),
214 6 : baseCSS.height * nsPresContext::AppUnitsPerCSSPixel());
215 2 : nsLayoutUtils::SetDisplayPortBaseIfNotSet(aContent, base);
216 : }
217 :
218 : static already_AddRefed<nsIPresShell>
219 5 : GetPresShell(const nsIContent* aContent)
220 : {
221 10 : nsCOMPtr<nsIPresShell> result;
222 5 : if (nsIDocument* doc = aContent->GetComposedDoc()) {
223 5 : result = doc->GetShell();
224 : }
225 10 : return result.forget();
226 : }
227 :
228 : static void
229 2 : SetPaintRequestTime(nsIContent* aContent, const TimeStamp& aPaintRequestTime)
230 : {
231 4 : aContent->SetProperty(nsGkAtoms::paintRequestTime,
232 2 : new TimeStamp(aPaintRequestTime),
233 4 : nsINode::DeleteProperty<TimeStamp>);
234 2 : }
235 :
236 : void
237 0 : APZCCallbackHelper::UpdateRootFrame(FrameMetrics& aMetrics)
238 : {
239 0 : if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
240 0 : return;
241 : }
242 0 : nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
243 0 : if (!content) {
244 0 : return;
245 : }
246 :
247 0 : nsCOMPtr<nsIPresShell> shell = GetPresShell(content);
248 0 : if (!shell || aMetrics.GetPresShellId() != shell->GetPresShellId()) {
249 0 : return;
250 : }
251 :
252 0 : MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins());
253 :
254 0 : if (gfxPrefs::APZAllowZooming()) {
255 : // If zooming is disabled then we don't really want to let APZ fiddle
256 : // with these things. In theory setting the resolution here should be a
257 : // no-op, but setting the SPCSPS is bad because it can cause a stale value
258 : // to be returned by window.innerWidth/innerHeight (see bug 1187792).
259 :
260 0 : float presShellResolution = shell->GetResolution();
261 :
262 : // If the pres shell resolution has changed on the content side side
263 : // the time this repaint request was fired, consider this request out of date
264 : // and drop it; setting a zoom based on the out-of-date resolution can have
265 : // the effect of getting us stuck with the stale resolution.
266 0 : if (!FuzzyEqualsMultiplicative(presShellResolution, aMetrics.GetPresShellResolution())) {
267 0 : return;
268 : }
269 :
270 : // The pres shell resolution is updated by the the async zoom since the
271 : // last paint.
272 0 : presShellResolution = aMetrics.GetPresShellResolution()
273 0 : * aMetrics.GetAsyncZoom().scale;
274 0 : shell->SetResolutionAndScaleTo(presShellResolution);
275 : }
276 :
277 : // Do this as late as possible since scrolling can flush layout. It also
278 : // adjusts the display port margins, so do it before we set those.
279 0 : ScrollFrame(content, aMetrics);
280 :
281 0 : SetDisplayPortMargins(shell, content, aMetrics);
282 0 : SetPaintRequestTime(content, aMetrics.GetPaintRequestTime());
283 : }
284 :
285 : void
286 2 : APZCCallbackHelper::UpdateSubFrame(FrameMetrics& aMetrics)
287 : {
288 2 : if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
289 0 : return;
290 : }
291 2 : nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
292 2 : if (!content) {
293 0 : return;
294 : }
295 :
296 2 : MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins());
297 :
298 : // We don't currently support zooming for subframes, so nothing extra
299 : // needs to be done beyond the tasks common to this and UpdateRootFrame.
300 2 : ScrollFrame(content, aMetrics);
301 4 : if (nsCOMPtr<nsIPresShell> shell = GetPresShell(content)) {
302 2 : SetDisplayPortMargins(shell, content, aMetrics);
303 : }
304 2 : SetPaintRequestTime(content, aMetrics.GetPaintRequestTime());
305 : }
306 :
307 : bool
308 3 : APZCCallbackHelper::GetOrCreateScrollIdentifiers(nsIContent* aContent,
309 : uint32_t* aPresShellIdOut,
310 : FrameMetrics::ViewID* aViewIdOut)
311 : {
312 3 : if (!aContent) {
313 0 : return false;
314 : }
315 3 : *aViewIdOut = nsLayoutUtils::FindOrCreateIDFor(aContent);
316 3 : if (nsCOMPtr<nsIPresShell> shell = GetPresShell(aContent)) {
317 3 : *aPresShellIdOut = shell->GetPresShellId();
318 3 : return true;
319 : }
320 0 : return false;
321 : }
322 :
323 : void
324 2 : APZCCallbackHelper::InitializeRootDisplayport(nsIPresShell* aPresShell)
325 : {
326 : // Create a view-id and set a zero-margin displayport for the root element
327 : // of the root document in the chrome process. This ensures that the scroll
328 : // frame for this element gets an APZC, which in turn ensures that all content
329 : // in the chrome processes is covered by an APZC.
330 : // The displayport is zero-margin because this element is generally not
331 : // actually scrollable (if it is, APZC will set proper margins when it's
332 : // scrolled).
333 2 : if (!aPresShell) {
334 0 : return;
335 : }
336 :
337 2 : MOZ_ASSERT(aPresShell->GetDocument());
338 2 : nsIContent* content = aPresShell->GetDocument()->GetDocumentElement();
339 2 : if (!content) {
340 0 : return;
341 : }
342 :
343 : uint32_t presShellId;
344 : FrameMetrics::ViewID viewId;
345 2 : if (APZCCallbackHelper::GetOrCreateScrollIdentifiers(content, &presShellId, &viewId)) {
346 2 : nsPresContext* pc = aPresShell->GetPresContext();
347 : // This code is only correct for root content or toplevel documents.
348 2 : MOZ_ASSERT(!pc || pc->IsRootContentDocument() || !pc->GetParentPresContext());
349 2 : nsIFrame* frame = aPresShell->GetRootScrollFrame();
350 2 : if (!frame) {
351 1 : frame = aPresShell->GetRootFrame();
352 : }
353 4 : nsRect baseRect;
354 2 : if (frame) {
355 2 : baseRect =
356 4 : nsRect(nsPoint(0, 0), nsLayoutUtils::CalculateCompositionSizeForFrame(frame));
357 0 : } else if (pc) {
358 0 : baseRect = nsRect(nsPoint(0, 0), pc->GetVisibleArea().Size());
359 : }
360 2 : nsLayoutUtils::SetDisplayPortBaseIfNotSet(content, baseRect);
361 : // Note that we also set the base rect that goes with these margins in
362 : // nsRootBoxFrame::BuildDisplayList.
363 4 : nsLayoutUtils::SetDisplayPortMargins(content, aPresShell, ScreenMargin(), 0,
364 2 : nsLayoutUtils::RepaintMode::DoNotRepaint);
365 2 : nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
366 2 : content->GetPrimaryFrame(), nsLayoutUtils::RepaintMode::DoNotRepaint);
367 : }
368 : }
369 :
370 : nsPresContext*
371 71 : APZCCallbackHelper::GetPresContextForContent(nsIContent* aContent)
372 : {
373 71 : nsIDocument* doc = aContent->GetComposedDoc();
374 71 : if (!doc) {
375 0 : return nullptr;
376 : }
377 71 : nsIPresShell* shell = doc->GetShell();
378 71 : if (!shell) {
379 0 : return nullptr;
380 : }
381 71 : return shell->GetPresContext();
382 : }
383 :
384 : nsIPresShell*
385 71 : APZCCallbackHelper::GetRootContentDocumentPresShellForContent(nsIContent* aContent)
386 : {
387 71 : nsPresContext* context = GetPresContextForContent(aContent);
388 71 : if (!context) {
389 0 : return nullptr;
390 : }
391 71 : context = context->GetToplevelContentDocumentPresContext();
392 71 : if (!context) {
393 58 : return nullptr;
394 : }
395 13 : return context->PresShell();
396 : }
397 :
398 : static nsIPresShell*
399 5 : GetRootDocumentPresShell(nsIContent* aContent)
400 : {
401 5 : nsIDocument* doc = aContent->GetComposedDoc();
402 5 : if (!doc) {
403 0 : return nullptr;
404 : }
405 5 : nsIPresShell* shell = doc->GetShell();
406 5 : if (!shell) {
407 0 : return nullptr;
408 : }
409 5 : nsPresContext* context = shell->GetPresContext();
410 5 : if (!context) {
411 0 : return nullptr;
412 : }
413 5 : context = context->GetRootPresContext();
414 5 : if (!context) {
415 0 : return nullptr;
416 : }
417 5 : return context->PresShell();
418 : }
419 :
420 : CSSPoint
421 11 : APZCCallbackHelper::ApplyCallbackTransform(const CSSPoint& aInput,
422 : const ScrollableLayerGuid& aGuid)
423 : {
424 11 : CSSPoint input = aInput;
425 11 : if (aGuid.mScrollId == FrameMetrics::NULL_SCROLL_ID) {
426 6 : return input;
427 : }
428 10 : nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aGuid.mScrollId);
429 5 : if (!content) {
430 0 : return input;
431 : }
432 :
433 : // First, scale inversely by the root content document's pres shell
434 : // resolution to cancel the scale-to-resolution transform that the
435 : // compositor adds to the layer with the pres shell resolution. The points
436 : // sent to Gecko by APZ don't have this transform unapplied (unlike other
437 : // compositor-side transforms) because APZ doesn't know about it.
438 5 : if (nsIPresShell* shell = GetRootDocumentPresShell(content)) {
439 5 : input = input / shell->GetResolution();
440 : }
441 :
442 : // This represents any resolution on the Root Content Document (RCD)
443 : // that's not on the Root Document (RD). That is, on platforms where
444 : // RCD == RD, it's 1, and on platforms where RCD != RD, it's the RCD
445 : // resolution. 'input' has this resolution applied, but the scroll
446 : // delta retrieved below do not, so we need to apply them to the
447 : // delta before adding the delta to 'input'. (Technically, deltas
448 : // from scroll frames outside the RCD would already have this
449 : // resolution applied, but we don't have such scroll frames in
450 : // practice.)
451 5 : float nonRootResolution = 1.0f;
452 5 : if (nsIPresShell* shell = GetRootContentDocumentPresShellForContent(content)) {
453 0 : nonRootResolution = shell->GetCumulativeNonRootScaleResolution();
454 : }
455 : // Now apply the callback-transform. This is only approximately correct,
456 : // see the comment on GetCumulativeApzCallbackTransform for details.
457 5 : CSSPoint transform = nsLayoutUtils::GetCumulativeApzCallbackTransform(content->GetPrimaryFrame());
458 5 : return input + transform * nonRootResolution;
459 : }
460 :
461 : LayoutDeviceIntPoint
462 11 : APZCCallbackHelper::ApplyCallbackTransform(const LayoutDeviceIntPoint& aPoint,
463 : const ScrollableLayerGuid& aGuid,
464 : const CSSToLayoutDeviceScale& aScale)
465 : {
466 11 : LayoutDevicePoint point = LayoutDevicePoint(aPoint.x, aPoint.y);
467 11 : point = ApplyCallbackTransform(point / aScale, aGuid) * aScale;
468 11 : return LayoutDeviceIntPoint::Round(point);
469 : }
470 :
471 : void
472 11 : APZCCallbackHelper::ApplyCallbackTransform(WidgetEvent& aEvent,
473 : const ScrollableLayerGuid& aGuid,
474 : const CSSToLayoutDeviceScale& aScale)
475 : {
476 11 : if (aEvent.AsTouchEvent()) {
477 0 : WidgetTouchEvent& event = *(aEvent.AsTouchEvent());
478 0 : for (size_t i = 0; i < event.mTouches.Length(); i++) {
479 0 : event.mTouches[i]->mRefPoint = ApplyCallbackTransform(
480 0 : event.mTouches[i]->mRefPoint, aGuid, aScale);
481 : }
482 : } else {
483 11 : aEvent.mRefPoint = ApplyCallbackTransform(aEvent.mRefPoint, aGuid, aScale);
484 : }
485 11 : }
486 :
487 : nsEventStatus
488 6 : APZCCallbackHelper::DispatchWidgetEvent(WidgetGUIEvent& aEvent)
489 : {
490 6 : nsEventStatus status = nsEventStatus_eConsumeNoDefault;
491 6 : if (aEvent.mWidget) {
492 6 : aEvent.mWidget->DispatchEvent(&aEvent, status);
493 : }
494 6 : return status;
495 : }
496 :
497 : nsEventStatus
498 0 : APZCCallbackHelper::DispatchSynthesizedMouseEvent(EventMessage aMsg,
499 : uint64_t aTime,
500 : const LayoutDevicePoint& aRefPoint,
501 : Modifiers aModifiers,
502 : int32_t aClickCount,
503 : nsIWidget* aWidget)
504 : {
505 0 : MOZ_ASSERT(aMsg == eMouseMove || aMsg == eMouseDown ||
506 : aMsg == eMouseUp || aMsg == eMouseLongTap);
507 :
508 : WidgetMouseEvent event(true, aMsg, aWidget,
509 0 : WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
510 0 : event.mRefPoint = LayoutDeviceIntPoint::Truncate(aRefPoint.x, aRefPoint.y);
511 0 : event.mTime = aTime;
512 0 : event.button = WidgetMouseEvent::eLeftButton;
513 0 : event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
514 0 : if (aMsg == eMouseLongTap) {
515 0 : event.mFlags.mOnlyChromeDispatch = true;
516 : }
517 0 : event.mIgnoreRootScrollFrame = true;
518 0 : if (aMsg != eMouseMove) {
519 0 : event.mClickCount = aClickCount;
520 : }
521 0 : event.mModifiers = aModifiers;
522 : // Real touch events will generate corresponding pointer events. We set
523 : // convertToPointer to false to prevent the synthesized mouse events generate
524 : // pointer events again.
525 0 : event.convertToPointer = false;
526 0 : return DispatchWidgetEvent(event);
527 : }
528 :
529 : bool
530 0 : APZCCallbackHelper::DispatchMouseEvent(const nsCOMPtr<nsIPresShell>& aPresShell,
531 : const nsString& aType,
532 : const CSSPoint& aPoint,
533 : int32_t aButton,
534 : int32_t aClickCount,
535 : int32_t aModifiers,
536 : bool aIgnoreRootScrollFrame,
537 : unsigned short aInputSourceArg,
538 : uint32_t aPointerId)
539 : {
540 0 : NS_ENSURE_TRUE(aPresShell, true);
541 :
542 0 : bool defaultPrevented = false;
543 0 : nsContentUtils::SendMouseEvent(aPresShell, aType, aPoint.x, aPoint.y,
544 : aButton, nsIDOMWindowUtils::MOUSE_BUTTONS_NOT_SPECIFIED, aClickCount,
545 : aModifiers, aIgnoreRootScrollFrame, 0, aInputSourceArg, aPointerId, false,
546 0 : &defaultPrevented, false, /* aIsWidgetEventSynthesized = */ false);
547 0 : return defaultPrevented;
548 : }
549 :
550 :
551 : void
552 0 : APZCCallbackHelper::FireSingleTapEvent(const LayoutDevicePoint& aPoint,
553 : Modifiers aModifiers,
554 : int32_t aClickCount,
555 : nsIWidget* aWidget)
556 : {
557 0 : if (aWidget->Destroyed()) {
558 0 : return;
559 : }
560 : APZCCH_LOG("Dispatching single-tap component events to %s\n",
561 : Stringify(aPoint).c_str());
562 0 : int time = 0;
563 0 : DispatchSynthesizedMouseEvent(eMouseMove, time, aPoint, aModifiers, aClickCount, aWidget);
564 0 : DispatchSynthesizedMouseEvent(eMouseDown, time, aPoint, aModifiers, aClickCount, aWidget);
565 0 : DispatchSynthesizedMouseEvent(eMouseUp, time, aPoint, aModifiers, aClickCount, aWidget);
566 : }
567 :
568 : static dom::Element*
569 0 : GetDisplayportElementFor(nsIScrollableFrame* aScrollableFrame)
570 : {
571 0 : if (!aScrollableFrame) {
572 0 : return nullptr;
573 : }
574 0 : nsIFrame* scrolledFrame = aScrollableFrame->GetScrolledFrame();
575 0 : if (!scrolledFrame) {
576 0 : return nullptr;
577 : }
578 : // |scrolledFrame| should at this point be the root content frame of the
579 : // nearest ancestor scrollable frame. The element corresponding to this
580 : // frame should be the one with the displayport set on it, so find that
581 : // element and return it.
582 0 : nsIContent* content = scrolledFrame->GetContent();
583 0 : MOZ_ASSERT(content->IsElement()); // roc says this must be true
584 0 : return content->AsElement();
585 : }
586 :
587 :
588 : static dom::Element*
589 0 : GetRootDocumentElementFor(nsIWidget* aWidget)
590 : {
591 : // This returns the root element that ChromeProcessController sets the
592 : // displayport on during initialization.
593 0 : if (nsView* view = nsView::GetViewFor(aWidget)) {
594 0 : if (nsIPresShell* shell = view->GetPresShell()) {
595 0 : MOZ_ASSERT(shell->GetDocument());
596 0 : return shell->GetDocument()->GetDocumentElement();
597 : }
598 : }
599 0 : return nullptr;
600 : }
601 :
602 : static nsIFrame*
603 0 : UpdateRootFrameForTouchTargetDocument(nsIFrame* aRootFrame)
604 : {
605 : #if defined(MOZ_WIDGET_ANDROID)
606 : // Re-target so that the hit test is performed relative to the frame for the
607 : // Root Content Document instead of the Root Document which are different in
608 : // Android. See bug 1229752 comment 16 for an explanation of why this is necessary.
609 : if (nsIDocument* doc = aRootFrame->PresContext()->PresShell()->GetPrimaryContentDocument()) {
610 : if (nsIPresShell* shell = doc->GetShell()) {
611 : if (nsIFrame* frame = shell->GetRootFrame()) {
612 : return frame;
613 : }
614 : }
615 : }
616 : #endif
617 0 : return aRootFrame;
618 : }
619 :
620 : // Determine the scrollable target frame for the given point and add it to
621 : // the target list. If the frame doesn't have a displayport, set one.
622 : // Return whether or not a displayport was set.
623 : static bool
624 0 : PrepareForSetTargetAPZCNotification(nsIWidget* aWidget,
625 : const ScrollableLayerGuid& aGuid,
626 : nsIFrame* aRootFrame,
627 : const LayoutDeviceIntPoint& aRefPoint,
628 : nsTArray<ScrollableLayerGuid>* aTargets)
629 : {
630 0 : ScrollableLayerGuid guid(aGuid.mLayersId, 0, FrameMetrics::NULL_SCROLL_ID);
631 : nsPoint point =
632 0 : nsLayoutUtils::GetEventCoordinatesRelativeTo(aWidget, aRefPoint, aRootFrame);
633 0 : uint32_t flags = 0;
634 : #ifdef MOZ_WIDGET_ANDROID
635 : // On Android, we need IGNORE_ROOT_SCROLL_FRAME for correct hit testing
636 : // when zoomed out. On desktop, don't use it because it interferes with
637 : // hit testing for some purposes such as scrollbar dragging.
638 : flags = nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME;
639 : #endif
640 : nsIFrame* target =
641 0 : nsLayoutUtils::GetFrameForPoint(aRootFrame, point, flags);
642 : nsIScrollableFrame* scrollAncestor = target
643 0 : ? nsLayoutUtils::GetAsyncScrollableAncestorFrame(target)
644 0 : : aRootFrame->PresContext()->PresShell()->GetRootScrollFrameAsScrollable();
645 :
646 : // Assuming that if there's no scrollAncestor, there's already a displayPort.
647 : nsCOMPtr<dom::Element> dpElement = scrollAncestor
648 : ? GetDisplayportElementFor(scrollAncestor)
649 0 : : GetRootDocumentElementFor(aWidget);
650 :
651 : #ifdef APZCCH_LOGGING
652 : nsAutoString dpElementDesc;
653 : if (dpElement) {
654 : dpElement->Describe(dpElementDesc);
655 : }
656 : APZCCH_LOG("For event at %s found scrollable element %p (%s)\n",
657 : Stringify(aRefPoint).c_str(), dpElement.get(),
658 : NS_LossyConvertUTF16toASCII(dpElementDesc).get());
659 : #endif
660 :
661 : bool guidIsValid = APZCCallbackHelper::GetOrCreateScrollIdentifiers(
662 0 : dpElement, &(guid.mPresShellId), &(guid.mScrollId));
663 0 : aTargets->AppendElement(guid);
664 :
665 0 : if (!guidIsValid || nsLayoutUtils::HasDisplayPort(dpElement)) {
666 0 : return false;
667 : }
668 :
669 0 : if (!scrollAncestor) {
670 0 : MOZ_ASSERT(false); // If you hit this, please file a bug with STR.
671 :
672 : // Attempt some sort of graceful handling based on a theory as to why we
673 : // reach this point...
674 : // If we get here, the document element is non-null, valid, but doesn't have
675 : // a displayport. It's possible that the init code in ChromeProcessController
676 : // failed for some reason, or the document element got swapped out at some
677 : // later time. In this case let's try to set a displayport on the document
678 : // element again and bail out on this operation.
679 : APZCCH_LOG("Widget %p's document element %p didn't have a displayport\n",
680 : aWidget, dpElement.get());
681 : APZCCallbackHelper::InitializeRootDisplayport(aRootFrame->PresContext()->PresShell());
682 : return false;
683 : }
684 :
685 : APZCCH_LOG("%p didn't have a displayport, so setting one...\n", dpElement.get());
686 : bool activated = nsLayoutUtils::CalculateAndSetDisplayPortMargins(
687 0 : scrollAncestor, nsLayoutUtils::RepaintMode::Repaint);
688 0 : if (!activated) {
689 0 : return false;
690 : }
691 :
692 0 : nsIFrame* frame = do_QueryFrame(scrollAncestor);
693 : nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(frame,
694 0 : nsLayoutUtils::RepaintMode::Repaint);
695 :
696 0 : return true;
697 : }
698 :
699 : static void
700 0 : SendLayersDependentApzcTargetConfirmation(nsIPresShell* aShell, uint64_t aInputBlockId,
701 : const nsTArray<ScrollableLayerGuid>& aTargets)
702 : {
703 0 : LayerManager* lm = aShell->GetLayerManager();
704 0 : if (!lm) {
705 0 : return;
706 : }
707 :
708 0 : if (WebRenderLayerManager* wrlm = lm->AsWebRenderLayerManager()) {
709 0 : if (WebRenderBridgeChild* wrbc = wrlm->WrBridge()) {
710 0 : wrbc->SendSetConfirmedTargetAPZC(aInputBlockId, aTargets);
711 : }
712 0 : return;
713 : }
714 :
715 0 : LayerTransactionChild* shadow = lm->AsShadowForwarder()->GetShadowManager();
716 0 : if (!shadow) {
717 0 : return;
718 : }
719 :
720 0 : shadow->SendSetConfirmedTargetAPZC(aInputBlockId, aTargets);
721 : }
722 :
723 : class DisplayportSetListener : public nsAPostRefreshObserver {
724 : public:
725 0 : DisplayportSetListener(nsIPresShell* aPresShell,
726 : const uint64_t& aInputBlockId,
727 : const nsTArray<ScrollableLayerGuid>& aTargets)
728 0 : : mPresShell(aPresShell)
729 0 : , mInputBlockId(aInputBlockId)
730 0 : , mTargets(aTargets)
731 : {
732 0 : }
733 :
734 0 : virtual ~DisplayportSetListener()
735 0 : {
736 0 : }
737 :
738 0 : void DidRefresh() override {
739 0 : if (!mPresShell) {
740 0 : MOZ_ASSERT_UNREACHABLE("Post-refresh observer fired again after failed attempt at unregistering it");
741 : return;
742 : }
743 :
744 : APZCCH_LOG("Got refresh, sending target APZCs for input block %" PRIu64 "\n", mInputBlockId);
745 0 : SendLayersDependentApzcTargetConfirmation(mPresShell, mInputBlockId, Move(mTargets));
746 :
747 0 : if (!mPresShell->RemovePostRefreshObserver(this)) {
748 0 : MOZ_ASSERT_UNREACHABLE("Unable to unregister post-refresh observer! Leaking it instead of leaving garbage registered");
749 : // Graceful handling, just in case...
750 : mPresShell = nullptr;
751 : return;
752 : }
753 :
754 0 : delete this;
755 : }
756 :
757 : private:
758 : RefPtr<nsIPresShell> mPresShell;
759 : uint64_t mInputBlockId;
760 : nsTArray<ScrollableLayerGuid> mTargets;
761 : };
762 :
763 : // Sends a SetTarget notification for APZC, given one or more previous
764 : // calls to PrepareForAPZCSetTargetNotification().
765 : static void
766 0 : SendSetTargetAPZCNotificationHelper(nsIWidget* aWidget,
767 : nsIPresShell* aShell,
768 : const uint64_t& aInputBlockId,
769 : const nsTArray<ScrollableLayerGuid>& aTargets,
770 : bool aWaitForRefresh)
771 : {
772 0 : bool waitForRefresh = aWaitForRefresh;
773 0 : if (waitForRefresh) {
774 : APZCCH_LOG("At least one target got a new displayport, need to wait for refresh\n");
775 0 : waitForRefresh = aShell->AddPostRefreshObserver(
776 0 : new DisplayportSetListener(aShell, aInputBlockId, Move(aTargets)));
777 : }
778 0 : if (!waitForRefresh) {
779 : APZCCH_LOG("Sending target APZCs for input block %" PRIu64 "\n", aInputBlockId);
780 0 : aWidget->SetConfirmedTargetAPZC(aInputBlockId, aTargets);
781 : } else {
782 : APZCCH_LOG("Successfully registered post-refresh observer\n");
783 : }
784 0 : }
785 :
786 : bool
787 0 : APZCCallbackHelper::SendSetTargetAPZCNotification(nsIWidget* aWidget,
788 : nsIDocument* aDocument,
789 : const WidgetGUIEvent& aEvent,
790 : const ScrollableLayerGuid& aGuid,
791 : uint64_t aInputBlockId)
792 : {
793 0 : if (!aWidget || !aDocument) {
794 0 : return false;
795 : }
796 0 : if (aInputBlockId == sLastTargetAPZCNotificationInputBlock) {
797 : // We have already confirmed the target APZC for a previous event of this
798 : // input block. If we activated a scroll frame for this input block,
799 : // sending another target APZC confirmation would be harmful, as it might
800 : // race the original confirmation (which needs to go through a layers
801 : // transaction).
802 : APZCCH_LOG("Not resending target APZC confirmation for input block %" PRIu64 "\n", aInputBlockId);
803 0 : return false;
804 : }
805 0 : sLastTargetAPZCNotificationInputBlock = aInputBlockId;
806 0 : if (nsIPresShell* shell = aDocument->GetShell()) {
807 0 : if (nsIFrame* rootFrame = shell->GetRootFrame()) {
808 0 : rootFrame = UpdateRootFrameForTouchTargetDocument(rootFrame);
809 :
810 0 : bool waitForRefresh = false;
811 0 : nsTArray<ScrollableLayerGuid> targets;
812 :
813 0 : if (const WidgetTouchEvent* touchEvent = aEvent.AsTouchEvent()) {
814 0 : for (size_t i = 0; i < touchEvent->mTouches.Length(); i++) {
815 0 : waitForRefresh |= PrepareForSetTargetAPZCNotification(aWidget, aGuid,
816 0 : rootFrame, touchEvent->mTouches[i]->mRefPoint, &targets);
817 : }
818 0 : } else if (const WidgetWheelEvent* wheelEvent = aEvent.AsWheelEvent()) {
819 0 : waitForRefresh = PrepareForSetTargetAPZCNotification(aWidget, aGuid,
820 0 : rootFrame, wheelEvent->mRefPoint, &targets);
821 0 : } else if (const WidgetMouseEvent* mouseEvent = aEvent.AsMouseEvent()) {
822 0 : waitForRefresh = PrepareForSetTargetAPZCNotification(aWidget, aGuid,
823 0 : rootFrame, mouseEvent->mRefPoint, &targets);
824 : }
825 : // TODO: Do other types of events need to be handled?
826 :
827 0 : if (!targets.IsEmpty()) {
828 0 : SendSetTargetAPZCNotificationHelper(
829 : aWidget,
830 : shell,
831 : aInputBlockId,
832 0 : Move(targets),
833 0 : waitForRefresh);
834 : }
835 :
836 0 : return waitForRefresh;
837 : }
838 : }
839 0 : return false;
840 : }
841 :
842 : void
843 0 : APZCCallbackHelper::SendSetAllowedTouchBehaviorNotification(
844 : nsIWidget* aWidget,
845 : nsIDocument* aDocument,
846 : const WidgetTouchEvent& aEvent,
847 : uint64_t aInputBlockId,
848 : const SetAllowedTouchBehaviorCallback& aCallback)
849 : {
850 0 : if (nsIPresShell* shell = aDocument->GetShell()) {
851 0 : if (nsIFrame* rootFrame = shell->GetRootFrame()) {
852 0 : rootFrame = UpdateRootFrameForTouchTargetDocument(rootFrame);
853 :
854 0 : nsTArray<TouchBehaviorFlags> flags;
855 0 : for (uint32_t i = 0; i < aEvent.mTouches.Length(); i++) {
856 : flags.AppendElement(
857 0 : TouchActionHelper::GetAllowedTouchBehavior(aWidget,
858 0 : rootFrame, aEvent.mTouches[i]->mRefPoint));
859 : }
860 0 : aCallback(aInputBlockId, Move(flags));
861 : }
862 : }
863 0 : }
864 :
865 : void
866 0 : APZCCallbackHelper::NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent)
867 : {
868 0 : nsCOMPtr<nsIContent> targetContent = nsLayoutUtils::FindContentFor(aScrollId);
869 0 : if (!targetContent) {
870 0 : return;
871 : }
872 0 : nsCOMPtr<nsIDocument> ownerDoc = targetContent->OwnerDoc();
873 0 : if (!ownerDoc) {
874 0 : return;
875 : }
876 :
877 0 : nsContentUtils::DispatchTrustedEvent(
878 : ownerDoc, targetContent,
879 : aEvent,
880 0 : true, true);
881 : }
882 :
883 : void
884 0 : APZCCallbackHelper::NotifyFlushComplete(nsIPresShell* aShell)
885 : {
886 0 : MOZ_ASSERT(NS_IsMainThread());
887 : // In some cases, flushing the APZ state to the main thread doesn't actually
888 : // trigger a flush and repaint (this is an intentional optimization - the stuff
889 : // visible to the user is still correct). However, reftests update their
890 : // snapshot based on invalidation events that are emitted during paints,
891 : // so we ensure that we kick off a paint when an APZ flush is done. Note that
892 : // only chrome/testing code can trigger this behaviour.
893 0 : if (aShell && aShell->GetRootFrame()) {
894 0 : aShell->GetRootFrame()->SchedulePaint();
895 : }
896 :
897 0 : nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
898 0 : MOZ_ASSERT(observerService);
899 0 : observerService->NotifyObservers(nullptr, "apz-repaints-flushed", nullptr);
900 0 : }
901 :
902 : static int32_t sActiveSuppressDisplayport = 0;
903 : static bool sDisplayPortSuppressionRespected = true;
904 :
905 : void
906 0 : APZCCallbackHelper::SuppressDisplayport(const bool& aEnabled,
907 : const nsCOMPtr<nsIPresShell>& aShell)
908 : {
909 0 : if (aEnabled) {
910 0 : sActiveSuppressDisplayport++;
911 : } else {
912 0 : bool isSuppressed = IsDisplayportSuppressed();
913 0 : sActiveSuppressDisplayport--;
914 0 : if (isSuppressed && !IsDisplayportSuppressed() &&
915 0 : aShell && aShell->GetRootFrame()) {
916 : // We unsuppressed the displayport, trigger a paint
917 0 : aShell->GetRootFrame()->SchedulePaint();
918 : }
919 : }
920 :
921 0 : MOZ_ASSERT(sActiveSuppressDisplayport >= 0);
922 0 : }
923 :
924 : void
925 0 : APZCCallbackHelper::RespectDisplayPortSuppression(bool aEnabled,
926 : const nsCOMPtr<nsIPresShell>& aShell)
927 : {
928 0 : bool isSuppressed = IsDisplayportSuppressed();
929 0 : sDisplayPortSuppressionRespected = aEnabled;
930 0 : if (isSuppressed && !IsDisplayportSuppressed() &&
931 0 : aShell && aShell->GetRootFrame()) {
932 : // We unsuppressed the displayport, trigger a paint
933 0 : aShell->GetRootFrame()->SchedulePaint();
934 : }
935 0 : }
936 :
937 : bool
938 78 : APZCCallbackHelper::IsDisplayportSuppressed()
939 : {
940 : return sDisplayPortSuppressionRespected
941 78 : && sActiveSuppressDisplayport > 0;
942 : }
943 :
944 : /* static */ bool
945 0 : APZCCallbackHelper::IsScrollInProgress(nsIScrollableFrame* aFrame)
946 : {
947 0 : return aFrame->IsProcessingAsyncScroll()
948 0 : || nsLayoutUtils::CanScrollOriginClobberApz(aFrame->LastScrollOrigin())
949 0 : || aFrame->LastSmoothScrollOrigin();
950 : }
951 :
952 : /* static */ void
953 0 : APZCCallbackHelper::NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId)
954 : {
955 0 : MOZ_ASSERT(NS_IsMainThread());
956 0 : if (nsIScrollableFrame* scrollFrame = nsLayoutUtils::FindScrollableFrameFor(aScrollId)) {
957 0 : scrollFrame->AsyncScrollbarDragRejected();
958 : }
959 0 : }
960 :
961 : /* static */ void
962 0 : APZCCallbackHelper::NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
963 : LayoutDeviceCoord aSpanChange,
964 : Modifiers aModifiers,
965 : nsIWidget* aWidget)
966 : {
967 : EventMessage msg;
968 0 : switch (aType) {
969 : case PinchGestureInput::PINCHGESTURE_START:
970 0 : msg = eMagnifyGestureStart;
971 0 : break;
972 : case PinchGestureInput::PINCHGESTURE_SCALE:
973 0 : msg = eMagnifyGestureUpdate;
974 0 : break;
975 : case PinchGestureInput::PINCHGESTURE_END:
976 0 : msg = eMagnifyGesture;
977 0 : break;
978 : }
979 :
980 0 : WidgetSimpleGestureEvent event(true, msg, aWidget);
981 0 : event.mDelta = aSpanChange;
982 0 : event.mModifiers = aModifiers;
983 0 : DispatchWidgetEvent(event);
984 0 : }
985 :
986 : } // namespace layers
987 : } // namespace mozilla
988 :
|