1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "APZCCallbackHelper.h"
8 
9 #include "TouchActionHelper.h"
10 #include "gfxPlatform.h"  // For gfxPlatform::UseTiling
11 
12 #include "mozilla/EventForwards.h"
13 #include "mozilla/dom/Element.h"
14 #include "mozilla/dom/MouseEventBinding.h"
15 #include "mozilla/dom/BrowserParent.h"
16 #include "mozilla/IntegerPrintfMacros.h"
17 #include "mozilla/layers/LayerTransactionChild.h"
18 #include "mozilla/layers/RepaintRequest.h"
19 #include "mozilla/layers/ShadowLayers.h"
20 #include "mozilla/layers/WebRenderLayerManager.h"
21 #include "mozilla/layers/WebRenderBridgeChild.h"
22 #include "mozilla/DisplayPortUtils.h"
23 #include "mozilla/PresShell.h"
24 #include "mozilla/TouchEvents.h"
25 #include "mozilla/ToString.h"
26 #include "mozilla/ViewportUtils.h"
27 #include "nsContainerFrame.h"
28 #include "nsContentUtils.h"
29 #include "nsIContent.h"
30 #include "nsIDOMWindowUtils.h"
31 #include "mozilla/dom/Document.h"
32 #include "nsIInterfaceRequestorUtils.h"
33 #include "nsIScrollableFrame.h"
34 #include "nsLayoutUtils.h"
35 #include "nsPrintfCString.h"
36 #include "nsRefreshDriver.h"
37 #include "nsString.h"
38 #include "nsView.h"
39 #include "Layers.h"
40 
41 static mozilla::LazyLogModule sApzHlpLog("apz.helper");
42 #define APZCCH_LOG(...) MOZ_LOG(sApzHlpLog, LogLevel::Debug, (__VA_ARGS__))
43 static mozilla::LazyLogModule sDisplayportLog("apz.displayport");
44 
45 namespace mozilla {
46 namespace layers {
47 
48 using dom::BrowserParent;
49 
50 uint64_t APZCCallbackHelper::sLastTargetAPZCNotificationInputBlock =
51     uint64_t(-1);
52 
RecenterDisplayPort(const ScreenMargin & aDisplayPort)53 static ScreenMargin RecenterDisplayPort(const ScreenMargin& aDisplayPort) {
54   ScreenMargin margins = aDisplayPort;
55   margins.right = margins.left = margins.LeftRight() / 2;
56   margins.top = margins.bottom = margins.TopBottom() / 2;
57   return margins;
58 }
59 
GetPresShell(const nsIContent * aContent)60 static PresShell* GetPresShell(const nsIContent* aContent) {
61   if (dom::Document* doc = aContent->GetComposedDoc()) {
62     return doc->GetPresShell();
63   }
64   return nullptr;
65 }
66 
ScrollFrameTo(nsIScrollableFrame * aFrame,const RepaintRequest & aRequest,bool & aSuccessOut)67 static CSSPoint ScrollFrameTo(nsIScrollableFrame* aFrame,
68                               const RepaintRequest& aRequest,
69                               bool& aSuccessOut) {
70   aSuccessOut = false;
71   CSSPoint targetScrollPosition = aRequest.GetLayoutScrollOffset();
72 
73   if (!aFrame) {
74     return targetScrollPosition;
75   }
76 
77   CSSPoint geckoScrollPosition =
78       CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
79 
80   // If the repaint request was triggered due to a previous main-thread scroll
81   // offset update sent to the APZ, then we don't need to do another scroll here
82   // and we can just return.
83   if (!aRequest.GetScrollOffsetUpdated()) {
84     return geckoScrollPosition;
85   }
86 
87   // If this frame is overflow:hidden, then the expectation is that it was
88   // sized in a way that respects its scrollable boundaries. For the root
89   // frame, this means that it cannot be scrolled in such a way that it moves
90   // the layout viewport. For a non-root frame, this means that it cannot be
91   // scrolled at all.
92   //
93   // In either case, |targetScrollPosition| should be the same as
94   // |geckoScrollPosition| here.
95   //
96   // However, this is slightly racy. We query the overflow property of the
97   // scroll frame at the time the repaint request arrives at the main thread
98   // (i.e., right now), but APZ made the decision of whether or not to allow
99   // scrolling based on the information it had at the time it processed the
100   // scroll event. The overflow property could have changed at some time
101   // between the two events and so APZ may have computed a scrollable region
102   // that is larger than what is actually allowed.
103   //
104   // Currently, we allow the scroll position to change even though the frame is
105   // overflow:hidden (that is, we take |targetScrollPosition|). If this turns
106   // out to be problematic, an alternative solution would be to ignore the
107   // scroll position change (that is, use |geckoScrollPosition|).
108   if (aFrame->GetScrollStyles().mVertical == StyleOverflow::Hidden &&
109       targetScrollPosition.y != geckoScrollPosition.y) {
110     NS_WARNING(
111         nsPrintfCString(
112             "APZCCH: targetScrollPosition.y (%f) != geckoScrollPosition.y (%f)",
113             targetScrollPosition.y, geckoScrollPosition.y)
114             .get());
115   }
116   if (aFrame->GetScrollStyles().mHorizontal == StyleOverflow::Hidden &&
117       targetScrollPosition.x != geckoScrollPosition.x) {
118     NS_WARNING(
119         nsPrintfCString(
120             "APZCCH: targetScrollPosition.x (%f) != geckoScrollPosition.x (%f)",
121             targetScrollPosition.x, geckoScrollPosition.x)
122             .get());
123   }
124 
125   // If the scrollable frame is currently in the middle of an async or smooth
126   // scroll then we don't want to interrupt it (see bug 961280).
127   // Also if the scrollable frame got a scroll request from a higher priority
128   // origin since the last layers update, then we don't want to push our scroll
129   // request because we'll clobber that one, which is bad.
130   bool scrollInProgress = APZCCallbackHelper::IsScrollInProgress(aFrame);
131   if (!scrollInProgress) {
132     aFrame->ScrollToCSSPixelsApproximate(targetScrollPosition,
133                                          ScrollOrigin::Apz);
134     geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
135     aSuccessOut = true;
136   }
137   // Return the final scroll position after setting it so that anything that
138   // relies on it can have an accurate value. Note that even if we set it above
139   // re-querying it is a good idea because it may have gotten clamped or
140   // rounded.
141   return geckoScrollPosition;
142 }
143 
144 /**
145  * Scroll the scroll frame associated with |aContent| to the scroll position
146  * requested in |aRequest|.
147  *
148  * Any difference between the requested and actual scroll positions is used to
149  * update the callback-transform stored on the content, and return a new
150  * display port.
151  */
ScrollFrame(nsIContent * aContent,const RepaintRequest & aRequest)152 static DisplayPortMargins ScrollFrame(nsIContent* aContent,
153                                       const RepaintRequest& aRequest) {
154   // Scroll the window to the desired spot
155   nsIScrollableFrame* sf =
156       nsLayoutUtils::FindScrollableFrameFor(aRequest.GetScrollId());
157   if (sf) {
158     sf->ResetScrollInfoIfNeeded(aRequest.GetScrollGeneration(),
159                                 aRequest.IsAnimationInProgress());
160     sf->SetScrollableByAPZ(!aRequest.IsScrollInfoLayer());
161     if (sf->IsRootScrollFrameOfDocument()) {
162       if (!APZCCallbackHelper::IsScrollInProgress(sf)) {
163         APZCCH_LOG("Setting VV offset to %s\n",
164                    ToString(aRequest.GetVisualScrollOffset()).c_str());
165         if (sf->SetVisualViewportOffset(
166                 CSSPoint::ToAppUnits(aRequest.GetVisualScrollOffset()),
167                 /* aRepaint = */ false)) {
168           // sf can't be destroyed if SetVisualViewportOffset returned true.
169           sf->MarkEverScrolled();
170         }
171       }
172     }
173   }
174   // sf might have been destroyed by the call to SetVisualViewportOffset, so
175   // re-get it.
176   sf = nsLayoutUtils::FindScrollableFrameFor(aRequest.GetScrollId());
177   bool scrollUpdated = false;
178   auto displayPortMargins = DisplayPortMargins::ForScrollFrame(
179       sf, aRequest.GetDisplayPortMargins(),
180       Some(aRequest.DisplayportPixelsPerCSSPixel()));
181   CSSPoint apzScrollOffset = aRequest.GetVisualScrollOffset();
182   CSSPoint actualScrollOffset = ScrollFrameTo(sf, aRequest, scrollUpdated);
183   CSSPoint scrollDelta = apzScrollOffset - actualScrollOffset;
184 
185   if (scrollUpdated) {
186     if (aRequest.IsScrollInfoLayer()) {
187       // In cases where the APZ scroll offset is different from the content
188       // scroll offset, we want to interpret the margins as relative to the APZ
189       // scroll offset except when the frame is not scrollable by APZ.
190       // Therefore, if the layer is a scroll info layer, we leave the margins
191       // as-is and they will be interpreted as relative to the content scroll
192       // offset.
193       if (nsIFrame* frame = aContent->GetPrimaryFrame()) {
194         frame->SchedulePaint();
195       }
196     } else {
197       // Correct the display port due to the difference between the requested
198       // and actual scroll offsets.
199       displayPortMargins = DisplayPortMargins::FromAPZ(
200           aRequest.GetDisplayPortMargins(), apzScrollOffset, actualScrollOffset,
201           aRequest.DisplayportPixelsPerCSSPixel());
202     }
203   } else if (aRequest.IsRootContent() &&
204              apzScrollOffset != aRequest.GetLayoutScrollOffset()) {
205     // APZ uses the visual viewport's offset to calculate where to place the
206     // display port, so the display port is misplaced when a pinch zoom occurs.
207     //
208     // We need to force a display port adjustment in the following paint to
209     // account for a difference between the requested and actual scroll
210     // offsets in repaints requested by
211     // AsyncPanZoomController::NotifyLayersUpdated.
212     displayPortMargins = DisplayPortMargins::FromAPZ(
213         aRequest.GetDisplayPortMargins(), apzScrollOffset, actualScrollOffset,
214         aRequest.DisplayportPixelsPerCSSPixel());
215   } else {
216     // For whatever reason we couldn't update the scroll offset on the scroll
217     // frame, which means the data APZ used for its displayport calculation is
218     // stale. Fall back to a sane default behaviour. Note that we don't
219     // tile-align the recentered displayport because tile-alignment depends on
220     // the scroll position, and the scroll position here is out of our control.
221     // See bug 966507 comment 21 for a more detailed explanation.
222     displayPortMargins = DisplayPortMargins::ForScrollFrame(
223         sf, RecenterDisplayPort(aRequest.GetDisplayPortMargins()),
224         Some(aRequest.DisplayportPixelsPerCSSPixel()));
225   }
226 
227   // APZ transforms inputs assuming we applied the exact scroll offset it
228   // requested (|apzScrollOffset|). Since we may not have, record the difference
229   // between what APZ asked for and what we actually applied, and apply it to
230   // input events to compensate.
231   // Note that if the main-thread had a change in its scroll position, we don't
232   // want to record that difference here, because it can be large and throw off
233   // input events by a large amount. It is also going to be transient, because
234   // any main-thread scroll position change will be synced to APZ and we will
235   // get another repaint request when APZ confirms. In the interval while this
236   // is happening we can just leave the callback transform as it was.
237   bool mainThreadScrollChanged =
238       sf && sf->CurrentScrollGeneration() != aRequest.GetScrollGeneration() &&
239       nsLayoutUtils::CanScrollOriginClobberApz(sf->LastScrollOrigin());
240   if (aContent && !mainThreadScrollChanged) {
241     aContent->SetProperty(nsGkAtoms::apzCallbackTransform,
242                           new CSSPoint(scrollDelta),
243                           nsINode::DeleteProperty<CSSPoint>);
244   }
245 
246   return displayPortMargins;
247 }
248 
SetDisplayPortMargins(PresShell * aPresShell,nsIContent * aContent,const DisplayPortMargins & aDisplayPortMargins,CSSSize aDisplayPortBase)249 static void SetDisplayPortMargins(PresShell* aPresShell, nsIContent* aContent,
250                                   const DisplayPortMargins& aDisplayPortMargins,
251                                   CSSSize aDisplayPortBase) {
252   if (!aContent) {
253     return;
254   }
255 
256   bool hadDisplayPort = DisplayPortUtils::HasDisplayPort(aContent);
257   if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Debug)) {
258     if (!hadDisplayPort) {
259       mozilla::layers::ScrollableLayerGuid::ViewID viewID =
260           mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID;
261       nsLayoutUtils::FindIDFor(aContent, &viewID);
262       MOZ_LOG(
263           sDisplayportLog, LogLevel::Debug,
264           ("APZCCH installing displayport margins %s on scrollId=%" PRIu64 "\n",
265            ToString(aDisplayPortMargins).c_str(), viewID));
266     }
267   }
268   DisplayPortUtils::SetDisplayPortMargins(
269       aContent, aPresShell, aDisplayPortMargins,
270       hadDisplayPort ? DisplayPortUtils::ClearMinimalDisplayPortProperty::No
271                      : DisplayPortUtils::ClearMinimalDisplayPortProperty::Yes,
272       0);
273   if (!hadDisplayPort) {
274     DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
275         aContent->GetPrimaryFrame());
276   }
277 
278   nsRect base(0, 0, aDisplayPortBase.width * AppUnitsPerCSSPixel(),
279               aDisplayPortBase.height * AppUnitsPerCSSPixel());
280   DisplayPortUtils::SetDisplayPortBaseIfNotSet(aContent, base);
281 }
282 
SetPaintRequestTime(nsIContent * aContent,const TimeStamp & aPaintRequestTime)283 static void SetPaintRequestTime(nsIContent* aContent,
284                                 const TimeStamp& aPaintRequestTime) {
285   aContent->SetProperty(nsGkAtoms::paintRequestTime,
286                         new TimeStamp(aPaintRequestTime),
287                         nsINode::DeleteProperty<TimeStamp>);
288 }
289 
NotifyLayerTransforms(const nsTArray<MatrixMessage> & aTransforms)290 void APZCCallbackHelper::NotifyLayerTransforms(
291     const nsTArray<MatrixMessage>& aTransforms) {
292   MOZ_ASSERT(NS_IsMainThread());
293   for (const MatrixMessage& msg : aTransforms) {
294     BrowserParent* parent =
295         BrowserParent::GetBrowserParentFromLayersId(msg.GetLayersId());
296     if (parent) {
297       parent->SetChildToParentConversionMatrix(
298           ViewAs<LayoutDeviceToLayoutDeviceMatrix4x4>(
299               msg.GetMatrix(),
300               PixelCastJustification::ContentProcessIsLayerInUiProcess),
301           msg.GetTopLevelViewportVisibleRectInBrowserCoords());
302     }
303   }
304 }
305 
UpdateRootFrame(const RepaintRequest & aRequest)306 void APZCCallbackHelper::UpdateRootFrame(const RepaintRequest& aRequest) {
307   if (aRequest.GetScrollId() == ScrollableLayerGuid::NULL_SCROLL_ID) {
308     return;
309   }
310   RefPtr<nsIContent> content =
311       nsLayoutUtils::FindContentFor(aRequest.GetScrollId());
312   if (!content) {
313     return;
314   }
315 
316   RefPtr<PresShell> presShell = GetPresShell(content);
317   if (!presShell || aRequest.GetPresShellId() != presShell->GetPresShellId()) {
318     return;
319   }
320 
321   APZCCH_LOG("Handling request %s\n", ToString(aRequest).c_str());
322   if (nsLayoutUtils::AllowZoomingForDocument(presShell->GetDocument()) &&
323       aRequest.GetAsyncZoom().scale != 1.0) {
324     // If zooming is disabled then we don't really want to let APZ fiddle
325     // with these things. In theory setting the resolution here should be a
326     // no-op, but setting the visual viewport size is bad because it can cause a
327     // stale value to be returned by window.innerWidth/innerHeight (see bug
328     // 1187792).
329 
330     float presShellResolution = presShell->GetResolution();
331 
332     // If the pres shell resolution has changed on the content side side
333     // the time this repaint request was fired, consider this request out of
334     // date and drop it; setting a zoom based on the out-of-date resolution can
335     // have the effect of getting us stuck with the stale resolution.
336     // One might think that if the last ResolutionChangeOrigin was apz then the
337     // pres shell resolutions should match but
338     // that is not the case. We can get multiple repaint requests that has the
339     // same pres shell resolution (because apz didn't receive a content layers
340     // update inbetween) if the first has async zoom we apply that and chance
341     // the content pres shell resolution and thus when handling the second
342     // repaint request the pres shell resolution won't match. So that's why we
343     // also check if the last resolution change origin was apz (aka 'us').
344     if (!FuzzyEqualsMultiplicative(presShellResolution,
345                                    aRequest.GetPresShellResolution()) &&
346         presShell->GetLastResolutionChangeOrigin() !=
347             ResolutionChangeOrigin::Apz) {
348       return;
349     }
350 
351     // The pres shell resolution is updated by the the async zoom since the
352     // last paint.
353     // We want to calculate the new presshell resolution as
354     // |aRequest.GetPresShellResolution() * aRequest.GetAsyncZoom()| but that
355     // calculation can lead to small inaccuracies due to limited floating point
356     // precision. Specifically,
357     // clang-format off
358     // asyncZoom = zoom / layerPixelsPerCSSPixel
359     //           = zoom / (devPixelsPerCSSPixel * cumulativeResolution)
360     // clang-format on
361     // Since this is a root frame we generally do not allow css transforms to
362     // scale it, so it is very likely that cumulativeResolution ==
363     // presShellResoluion. So
364     // clang-format off
365     // newPresShellResoluion = presShellResoluion * asyncZoom
366     // = presShellResoluion * zoom / (devPixelsPerCSSPixel * presShellResoluion)
367     // = zoom / devPixelsPerCSSPixel
368     // clang-format on
369     // However, we want to keep the calculation general and so we do not assume
370     // presShellResoluion == cumulativeResolution, but rather factor those
371     // values out so they cancel and the floating point division has a very high
372     // probability of being exactly 1.
373     presShellResolution =
374         (aRequest.GetPresShellResolution() /
375          aRequest.GetCumulativeResolution().ToScaleFactor().scale) *
376         (aRequest.GetZoom().ToScaleFactor() /
377          aRequest.GetDevPixelsPerCSSPixel())
378             .scale;
379     presShell->SetResolutionAndScaleTo(presShellResolution,
380                                        ResolutionChangeOrigin::Apz);
381 
382     // Changing the resolution will trigger a reflow which will cause the
383     // main-thread scroll position to be realigned in layer pixels. This
384     // (subpixel) scroll mutation can trigger a scroll update to APZ which
385     // is undesirable. Instead of having that happen as part of the post-reflow
386     // code, we force it to happen here with ScrollOrigin::Apz so that it
387     // doesn't trigger a scroll update to APZ.
388     nsIScrollableFrame* sf =
389         nsLayoutUtils::FindScrollableFrameFor(aRequest.GetScrollId());
390     CSSPoint currentScrollPosition =
391         CSSPoint::FromAppUnits(sf->GetScrollPosition());
392     sf->ScrollToCSSPixelsApproximate(currentScrollPosition, ScrollOrigin::Apz);
393   }
394 
395   // Do this as late as possible since scrolling can flush layout. It also
396   // adjusts the display port margins, so do it before we set those.
397   DisplayPortMargins displayPortMargins = ScrollFrame(content, aRequest);
398 
399   SetDisplayPortMargins(presShell, content, displayPortMargins,
400                         aRequest.CalculateCompositedSizeInCssPixels());
401   SetPaintRequestTime(content, aRequest.GetPaintRequestTime());
402 }
403 
UpdateSubFrame(const RepaintRequest & aRequest)404 void APZCCallbackHelper::UpdateSubFrame(const RepaintRequest& aRequest) {
405   if (aRequest.GetScrollId() == ScrollableLayerGuid::NULL_SCROLL_ID) {
406     return;
407   }
408   RefPtr<nsIContent> content =
409       nsLayoutUtils::FindContentFor(aRequest.GetScrollId());
410   if (!content) {
411     return;
412   }
413 
414   // We don't currently support zooming for subframes, so nothing extra
415   // needs to be done beyond the tasks common to this and UpdateRootFrame.
416   DisplayPortMargins displayPortMargins = ScrollFrame(content, aRequest);
417   if (RefPtr<PresShell> presShell = GetPresShell(content)) {
418     SetDisplayPortMargins(presShell, content, displayPortMargins,
419                           aRequest.CalculateCompositedSizeInCssPixels());
420   }
421   SetPaintRequestTime(content, aRequest.GetPaintRequestTime());
422 }
423 
GetOrCreateScrollIdentifiers(nsIContent * aContent,uint32_t * aPresShellIdOut,ScrollableLayerGuid::ViewID * aViewIdOut)424 bool APZCCallbackHelper::GetOrCreateScrollIdentifiers(
425     nsIContent* aContent, uint32_t* aPresShellIdOut,
426     ScrollableLayerGuid::ViewID* aViewIdOut) {
427   if (!aContent) {
428     return false;
429   }
430   *aViewIdOut = nsLayoutUtils::FindOrCreateIDFor(aContent);
431   if (PresShell* presShell = GetPresShell(aContent)) {
432     *aPresShellIdOut = presShell->GetPresShellId();
433     return true;
434   }
435   return false;
436 }
437 
InitializeRootDisplayport(PresShell * aPresShell)438 void APZCCallbackHelper::InitializeRootDisplayport(PresShell* aPresShell) {
439   // Create a view-id and set a zero-margin displayport for the root element
440   // of the root document in the chrome process. This ensures that the scroll
441   // frame for this element gets an APZC, which in turn ensures that all content
442   // in the chrome processes is covered by an APZC.
443   // The displayport is zero-margin because this element is generally not
444   // actually scrollable (if it is, APZC will set proper margins when it's
445   // scrolled).
446   if (!aPresShell) {
447     return;
448   }
449 
450   MOZ_ASSERT(aPresShell->GetDocument());
451   nsIContent* content = aPresShell->GetDocument()->GetDocumentElement();
452   if (!content) {
453     return;
454   }
455 
456   uint32_t presShellId;
457   ScrollableLayerGuid::ViewID viewId;
458   if (APZCCallbackHelper::GetOrCreateScrollIdentifiers(content, &presShellId,
459                                                        &viewId)) {
460     MOZ_LOG(
461         sDisplayportLog, LogLevel::Debug,
462         ("Initializing root displayport on scrollId=%" PRIu64 "\n", viewId));
463     Maybe<nsRect> baseRect =
464         DisplayPortUtils::GetRootDisplayportBase(aPresShell);
465     if (baseRect) {
466       DisplayPortUtils::SetDisplayPortBaseIfNotSet(content, *baseRect);
467     }
468 
469     // Note that we also set the base rect that goes with these margins in
470     // nsRootBoxFrame::BuildDisplayList.
471     DisplayPortUtils::SetDisplayPortMargins(
472         content, aPresShell, DisplayPortMargins::Empty(content),
473         DisplayPortUtils::ClearMinimalDisplayPortProperty::Yes, 0);
474     DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
475         content->GetPrimaryFrame());
476   }
477 }
478 
GetPresContextForContent(nsIContent * aContent)479 nsPresContext* APZCCallbackHelper::GetPresContextForContent(
480     nsIContent* aContent) {
481   dom::Document* doc = aContent->GetComposedDoc();
482   if (!doc) {
483     return nullptr;
484   }
485   PresShell* presShell = doc->GetPresShell();
486   if (!presShell) {
487     return nullptr;
488   }
489   return presShell->GetPresContext();
490 }
491 
GetRootContentDocumentPresShellForContent(nsIContent * aContent)492 PresShell* APZCCallbackHelper::GetRootContentDocumentPresShellForContent(
493     nsIContent* aContent) {
494   nsPresContext* context = GetPresContextForContent(aContent);
495   if (!context) {
496     return nullptr;
497   }
498   context = context->GetInProcessRootContentDocumentPresContext();
499   if (!context) {
500     return nullptr;
501   }
502   return context->PresShell();
503 }
504 
DispatchWidgetEvent(WidgetGUIEvent & aEvent)505 nsEventStatus APZCCallbackHelper::DispatchWidgetEvent(WidgetGUIEvent& aEvent) {
506   nsEventStatus status = nsEventStatus_eConsumeNoDefault;
507   if (aEvent.mWidget) {
508     aEvent.mWidget->DispatchEvent(&aEvent, status);
509   }
510   return status;
511 }
512 
DispatchSynthesizedMouseEvent(EventMessage aMsg,uint64_t aTime,const LayoutDevicePoint & aRefPoint,Modifiers aModifiers,int32_t aClickCount,nsIWidget * aWidget)513 nsEventStatus APZCCallbackHelper::DispatchSynthesizedMouseEvent(
514     EventMessage aMsg, uint64_t aTime, const LayoutDevicePoint& aRefPoint,
515     Modifiers aModifiers, int32_t aClickCount, nsIWidget* aWidget) {
516   MOZ_ASSERT(aMsg == eMouseMove || aMsg == eMouseDown || aMsg == eMouseUp ||
517              aMsg == eMouseLongTap);
518 
519   WidgetMouseEvent event(true, aMsg, aWidget, WidgetMouseEvent::eReal,
520                          WidgetMouseEvent::eNormal);
521   event.mRefPoint = LayoutDeviceIntPoint::Truncate(aRefPoint.x, aRefPoint.y);
522   event.mTime = aTime;
523   event.mButton = MouseButton::ePrimary;
524   event.mButtons |= MouseButtonsFlag::ePrimaryFlag;
525   event.mInputSource = dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH;
526   if (aMsg == eMouseLongTap) {
527     event.mFlags.mOnlyChromeDispatch = true;
528   }
529   if (aMsg != eMouseMove) {
530     event.mClickCount = aClickCount;
531   }
532   event.mModifiers = aModifiers;
533   // Real touch events will generate corresponding pointer events. We set
534   // convertToPointer to false to prevent the synthesized mouse events generate
535   // pointer events again.
536   event.convertToPointer = false;
537   return DispatchWidgetEvent(event);
538 }
539 
DispatchMouseEvent(PresShell * aPresShell,const nsString & aType,const CSSPoint & aPoint,int32_t aButton,int32_t aClickCount,int32_t aModifiers,unsigned short aInputSourceArg,uint32_t aPointerId)540 PreventDefaultResult APZCCallbackHelper::DispatchMouseEvent(
541     PresShell* aPresShell, const nsString& aType, const CSSPoint& aPoint,
542     int32_t aButton, int32_t aClickCount, int32_t aModifiers,
543     unsigned short aInputSourceArg, uint32_t aPointerId) {
544   NS_ENSURE_TRUE(aPresShell, PreventDefaultResult::ByContent);
545 
546   PreventDefaultResult preventDefaultResult;
547   nsContentUtils::SendMouseEvent(
548       aPresShell, aType, aPoint.x, aPoint.y, aButton,
549       nsIDOMWindowUtils::MOUSE_BUTTONS_NOT_SPECIFIED, aClickCount, aModifiers,
550       /* aIgnoreRootScrollFrame = */ false, 0, aInputSourceArg, aPointerId,
551       false, &preventDefaultResult, false,
552       /* aIsWidgetEventSynthesized = */ false);
553   return preventDefaultResult;
554 }
555 
FireSingleTapEvent(const LayoutDevicePoint & aPoint,Modifiers aModifiers,int32_t aClickCount,nsIWidget * aWidget)556 void APZCCallbackHelper::FireSingleTapEvent(const LayoutDevicePoint& aPoint,
557                                             Modifiers aModifiers,
558                                             int32_t aClickCount,
559                                             nsIWidget* aWidget) {
560   if (aWidget->Destroyed()) {
561     return;
562   }
563   APZCCH_LOG("Dispatching single-tap component events to %s\n",
564              ToString(aPoint).c_str());
565   int time = 0;
566   DispatchSynthesizedMouseEvent(eMouseMove, time, aPoint, aModifiers,
567                                 aClickCount, aWidget);
568   DispatchSynthesizedMouseEvent(eMouseDown, time, aPoint, aModifiers,
569                                 aClickCount, aWidget);
570   DispatchSynthesizedMouseEvent(eMouseUp, time, aPoint, aModifiers, aClickCount,
571                                 aWidget);
572 }
573 
GetDisplayportElementFor(nsIScrollableFrame * aScrollableFrame)574 static dom::Element* GetDisplayportElementFor(
575     nsIScrollableFrame* aScrollableFrame) {
576   if (!aScrollableFrame) {
577     return nullptr;
578   }
579   nsIFrame* scrolledFrame = aScrollableFrame->GetScrolledFrame();
580   if (!scrolledFrame) {
581     return nullptr;
582   }
583   // |scrolledFrame| should at this point be the root content frame of the
584   // nearest ancestor scrollable frame. The element corresponding to this
585   // frame should be the one with the displayport set on it, so find that
586   // element and return it.
587   nsIContent* content = scrolledFrame->GetContent();
588   MOZ_ASSERT(content->IsElement());  // roc says this must be true
589   return content->AsElement();
590 }
591 
GetRootDocumentElementFor(nsIWidget * aWidget)592 static dom::Element* GetRootDocumentElementFor(nsIWidget* aWidget) {
593   // This returns the root element that ChromeProcessController sets the
594   // displayport on during initialization.
595   if (nsView* view = nsView::GetViewFor(aWidget)) {
596     if (PresShell* presShell = view->GetPresShell()) {
597       MOZ_ASSERT(presShell->GetDocument());
598       return presShell->GetDocument()->GetDocumentElement();
599     }
600   }
601   return nullptr;
602 }
603 
604 namespace {
605 
606 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
607 
608 // Determine the scrollable target frame for the given point and add it to
609 // the target list. If the frame doesn't have a displayport, set one.
610 // Return whether or not the frame had a displayport that has already been
611 // painted (in this case, the caller can send the SetTargetAPZC notification
612 // right away, rather than waiting for a transaction to propagate the
613 // displayport to APZ first).
PrepareForSetTargetAPZCNotification(nsIWidget * aWidget,const LayersId & aLayersId,nsIFrame * aRootFrame,const LayoutDeviceIntPoint & aRefPoint,nsTArray<ScrollableLayerGuid> * aTargets)614 static bool PrepareForSetTargetAPZCNotification(
615     nsIWidget* aWidget, const LayersId& aLayersId, nsIFrame* aRootFrame,
616     const LayoutDeviceIntPoint& aRefPoint,
617     nsTArray<ScrollableLayerGuid>* aTargets) {
618   ScrollableLayerGuid guid(aLayersId, 0, ScrollableLayerGuid::NULL_SCROLL_ID);
619   RelativeTo relativeTo{aRootFrame, ViewportType::Visual};
620   nsPoint point = nsLayoutUtils::GetEventCoordinatesRelativeTo(
621       aWidget, aRefPoint, relativeTo);
622   nsIFrame* target = nsLayoutUtils::GetFrameForPoint(relativeTo, point);
623   nsIScrollableFrame* scrollAncestor =
624       target ? nsLayoutUtils::GetAsyncScrollableAncestorFrame(target)
625              : aRootFrame->PresShell()->GetRootScrollFrameAsScrollable();
626 
627   // Assuming that if there's no scrollAncestor, there's already a displayPort.
628   nsCOMPtr<dom::Element> dpElement =
629       scrollAncestor ? GetDisplayportElementFor(scrollAncestor)
630                      : GetRootDocumentElementFor(aWidget);
631 
632   if (MOZ_LOG_TEST(sApzHlpLog, LogLevel::Debug)) {
633     nsAutoString dpElementDesc;
634     if (dpElement) {
635       dpElement->Describe(dpElementDesc);
636     }
637     APZCCH_LOG("For event at %s found scrollable element %p (%s)\n",
638                ToString(aRefPoint).c_str(), dpElement.get(),
639                NS_LossyConvertUTF16toASCII(dpElementDesc).get());
640   }
641 
642   bool guidIsValid = APZCCallbackHelper::GetOrCreateScrollIdentifiers(
643       dpElement, &(guid.mPresShellId), &(guid.mScrollId));
644   aTargets->AppendElement(guid);
645 
646   if (!guidIsValid) {
647     return false;
648   }
649   if (DisplayPortUtils::HasNonMinimalNonZeroDisplayPort(dpElement)) {
650     // If the element has a displayport but it hasn't been painted yet,
651     // we want the caller to wait for the paint to happen, but we don't
652     // need to set the displayport here since it's already been set.
653     return !DisplayPortUtils::HasPaintedDisplayPort(dpElement);
654   }
655 
656   if (!scrollAncestor) {
657     // This can happen if the document element gets swapped out after
658     // ChromeProcessController runs InitializeRootDisplayport. In this case
659     // let's try to set a displayport again and bail out on this operation.
660     APZCCH_LOG("Widget %p's document element %p didn't have a displayport\n",
661                aWidget, dpElement.get());
662     APZCCallbackHelper::InitializeRootDisplayport(aRootFrame->PresShell());
663     return false;
664   }
665 
666   APZCCH_LOG("%p didn't have a displayport, so setting one...\n",
667              dpElement.get());
668   MOZ_LOG(sDisplayportLog, LogLevel::Debug,
669           ("Activating displayport on scrollId=%" PRIu64 " for SetTargetAPZC\n",
670            guid.mScrollId));
671   bool activated = DisplayPortUtils::CalculateAndSetDisplayPortMargins(
672       scrollAncestor, DisplayPortUtils::RepaintMode::Repaint);
673   if (!activated) {
674     return false;
675   }
676 
677   nsIFrame* frame = do_QueryFrame(scrollAncestor);
678   DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(frame);
679 
680   return !DisplayPortUtils::HasPaintedDisplayPort(dpElement);
681 }
682 
SendLayersDependentApzcTargetConfirmation(nsPresContext * aPresContext,uint64_t aInputBlockId,nsTArray<ScrollableLayerGuid> && aTargets)683 static void SendLayersDependentApzcTargetConfirmation(
684     nsPresContext* aPresContext, uint64_t aInputBlockId,
685     nsTArray<ScrollableLayerGuid>&& aTargets) {
686   PresShell* ps = aPresContext->GetPresShell();
687   if (!ps) {
688     return;
689   }
690 
691   LayerManager* lm = ps->GetLayerManager();
692   if (!lm) {
693     return;
694   }
695 
696   if (WebRenderLayerManager* wrlm = lm->AsWebRenderLayerManager()) {
697     if (WebRenderBridgeChild* wrbc = wrlm->WrBridge()) {
698       wrbc->SendSetConfirmedTargetAPZC(aInputBlockId, aTargets);
699     }
700     return;
701   }
702 
703   ShadowLayerForwarder* lf = lm->AsShadowForwarder();
704   if (!lf) {
705     return;
706   }
707 
708   LayerTransactionChild* shadow = lf->GetShadowManager();
709   if (!shadow) {
710     return;
711   }
712 
713   shadow->SendSetConfirmedTargetAPZC(aInputBlockId, aTargets);
714 }
715 
716 }  // namespace
717 
DisplayportSetListener(nsIWidget * aWidget,nsPresContext * aPresContext,const uint64_t & aInputBlockId,nsTArray<ScrollableLayerGuid> && aTargets)718 DisplayportSetListener::DisplayportSetListener(
719     nsIWidget* aWidget, nsPresContext* aPresContext,
720     const uint64_t& aInputBlockId, nsTArray<ScrollableLayerGuid>&& aTargets)
721     : ManagedPostRefreshObserver(aPresContext),
722       mWidget(aWidget),
723       mInputBlockId(aInputBlockId),
724       mTargets(std::move(aTargets)) {
725   MOZ_ASSERT(!mAction, "Setting Action twice");
726   mAction = [instance = MOZ_KnownLive(this)](bool aWasCanceled) {
727     instance->OnPostRefresh();
728     return Unregister::Yes;
729   };
730 }
731 
732 DisplayportSetListener::~DisplayportSetListener() = default;
733 
Register()734 void DisplayportSetListener::Register() {
735   APZCCH_LOG("DisplayportSetListener::Register\n");
736   mPresContext->RegisterManagedPostRefreshObserver(this);
737 }
738 
OnPostRefresh()739 void DisplayportSetListener::OnPostRefresh() {
740   APZCCH_LOG("Got refresh, sending target APZCs for input block %" PRIu64 "\n",
741              mInputBlockId);
742   SendLayersDependentApzcTargetConfirmation(mPresContext, mInputBlockId,
743                                             std::move(mTargets));
744 }
745 
746 already_AddRefed<DisplayportSetListener>
SendSetTargetAPZCNotification(nsIWidget * aWidget,dom::Document * aDocument,const WidgetGUIEvent & aEvent,const LayersId & aLayersId,uint64_t aInputBlockId)747 APZCCallbackHelper::SendSetTargetAPZCNotification(nsIWidget* aWidget,
748                                                   dom::Document* aDocument,
749                                                   const WidgetGUIEvent& aEvent,
750                                                   const LayersId& aLayersId,
751                                                   uint64_t aInputBlockId) {
752   if (!aWidget || !aDocument) {
753     return nullptr;
754   }
755   if (aInputBlockId == sLastTargetAPZCNotificationInputBlock) {
756     // We have already confirmed the target APZC for a previous event of this
757     // input block. If we activated a scroll frame for this input block,
758     // sending another target APZC confirmation would be harmful, as it might
759     // race the original confirmation (which needs to go through a layers
760     // transaction).
761     APZCCH_LOG("Not resending target APZC confirmation for input block %" PRIu64
762                "\n",
763                aInputBlockId);
764     return nullptr;
765   }
766   sLastTargetAPZCNotificationInputBlock = aInputBlockId;
767   if (PresShell* presShell = aDocument->GetPresShell()) {
768     if (nsIFrame* rootFrame = presShell->GetRootFrame()) {
769       bool waitForRefresh = false;
770       nsTArray<ScrollableLayerGuid> targets;
771 
772       if (const WidgetTouchEvent* touchEvent = aEvent.AsTouchEvent()) {
773         for (size_t i = 0; i < touchEvent->mTouches.Length(); i++) {
774           waitForRefresh |= PrepareForSetTargetAPZCNotification(
775               aWidget, aLayersId, rootFrame, touchEvent->mTouches[i]->mRefPoint,
776               &targets);
777         }
778       } else if (const WidgetWheelEvent* wheelEvent = aEvent.AsWheelEvent()) {
779         waitForRefresh = PrepareForSetTargetAPZCNotification(
780             aWidget, aLayersId, rootFrame, wheelEvent->mRefPoint, &targets);
781       } else if (const WidgetMouseEvent* mouseEvent = aEvent.AsMouseEvent()) {
782         waitForRefresh = PrepareForSetTargetAPZCNotification(
783             aWidget, aLayersId, rootFrame, mouseEvent->mRefPoint, &targets);
784       }
785       // TODO: Do other types of events need to be handled?
786 
787       if (!targets.IsEmpty()) {
788         if (waitForRefresh) {
789           APZCCH_LOG(
790               "At least one target got a new displayport, need to wait for "
791               "refresh\n");
792           return MakeAndAddRef<DisplayportSetListener>(
793               aWidget, presShell->GetPresContext(), aInputBlockId,
794               std::move(targets));
795         }
796         APZCCH_LOG("Sending target APZCs for input block %" PRIu64 "\n",
797                    aInputBlockId);
798         aWidget->SetConfirmedTargetAPZC(aInputBlockId, targets);
799       }
800     }
801   }
802   return nullptr;
803 }
804 
805 nsTArray<TouchBehaviorFlags>
SendSetAllowedTouchBehaviorNotification(nsIWidget * aWidget,dom::Document * aDocument,const WidgetTouchEvent & aEvent,uint64_t aInputBlockId,const SetAllowedTouchBehaviorCallback & aCallback)806 APZCCallbackHelper::SendSetAllowedTouchBehaviorNotification(
807     nsIWidget* aWidget, dom::Document* aDocument,
808     const WidgetTouchEvent& aEvent, uint64_t aInputBlockId,
809     const SetAllowedTouchBehaviorCallback& aCallback) {
810   nsTArray<TouchBehaviorFlags> flags;
811   if (!aWidget || !aDocument) {
812     return flags;
813   }
814   if (PresShell* presShell = aDocument->GetPresShell()) {
815     if (nsIFrame* rootFrame = presShell->GetRootFrame()) {
816       for (uint32_t i = 0; i < aEvent.mTouches.Length(); i++) {
817         flags.AppendElement(TouchActionHelper::GetAllowedTouchBehavior(
818             aWidget, RelativeTo{rootFrame, ViewportType::Visual},
819             aEvent.mTouches[i]->mRefPoint));
820       }
821       aCallback(aInputBlockId, flags);
822     }
823   }
824   return flags;
825 }
826 
NotifyMozMouseScrollEvent(const ScrollableLayerGuid::ViewID & aScrollId,const nsString & aEvent)827 void APZCCallbackHelper::NotifyMozMouseScrollEvent(
828     const ScrollableLayerGuid::ViewID& aScrollId, const nsString& aEvent) {
829   nsCOMPtr<nsIContent> targetContent = nsLayoutUtils::FindContentFor(aScrollId);
830   if (!targetContent) {
831     return;
832   }
833   RefPtr<dom::Document> ownerDoc = targetContent->OwnerDoc();
834   if (!ownerDoc) {
835     return;
836   }
837 
838   nsContentUtils::DispatchEventOnlyToChrome(ownerDoc, targetContent, aEvent,
839                                             CanBubble::eYes, Cancelable::eYes);
840 }
841 
NotifyFlushComplete(PresShell * aPresShell)842 void APZCCallbackHelper::NotifyFlushComplete(PresShell* aPresShell) {
843   MOZ_ASSERT(NS_IsMainThread());
844   // In some cases, flushing the APZ state to the main thread doesn't actually
845   // trigger a flush and repaint (this is an intentional optimization - the
846   // stuff visible to the user is still correct). However, reftests update their
847   // snapshot based on invalidation events that are emitted during paints,
848   // so we ensure that we kick off a paint when an APZ flush is done. Note that
849   // only chrome/testing code can trigger this behaviour.
850   if (aPresShell && aPresShell->GetRootFrame()) {
851     aPresShell->GetRootFrame()->SchedulePaint(nsIFrame::PAINT_DEFAULT, false);
852   }
853 
854   nsCOMPtr<nsIObserverService> observerService =
855       mozilla::services::GetObserverService();
856   MOZ_ASSERT(observerService);
857   observerService->NotifyObservers(nullptr, "apz-repaints-flushed", nullptr);
858 }
859 
860 /* static */
IsScrollInProgress(nsIScrollableFrame * aFrame)861 bool APZCCallbackHelper::IsScrollInProgress(nsIScrollableFrame* aFrame) {
862   using IncludeApzAnimation = nsIScrollableFrame::IncludeApzAnimation;
863 
864   return aFrame->IsScrollAnimating(IncludeApzAnimation::No) ||
865          nsLayoutUtils::CanScrollOriginClobberApz(aFrame->LastScrollOrigin());
866 }
867 
868 /* static */
NotifyAsyncScrollbarDragInitiated(uint64_t aDragBlockId,const ScrollableLayerGuid::ViewID & aScrollId,ScrollDirection aDirection)869 void APZCCallbackHelper::NotifyAsyncScrollbarDragInitiated(
870     uint64_t aDragBlockId, const ScrollableLayerGuid::ViewID& aScrollId,
871     ScrollDirection aDirection) {
872   MOZ_ASSERT(NS_IsMainThread());
873   if (nsIScrollableFrame* scrollFrame =
874           nsLayoutUtils::FindScrollableFrameFor(aScrollId)) {
875     scrollFrame->AsyncScrollbarDragInitiated(aDragBlockId, aDirection);
876   }
877 }
878 
879 /* static */
NotifyAsyncScrollbarDragRejected(const ScrollableLayerGuid::ViewID & aScrollId)880 void APZCCallbackHelper::NotifyAsyncScrollbarDragRejected(
881     const ScrollableLayerGuid::ViewID& aScrollId) {
882   MOZ_ASSERT(NS_IsMainThread());
883   if (nsIScrollableFrame* scrollFrame =
884           nsLayoutUtils::FindScrollableFrameFor(aScrollId)) {
885     scrollFrame->AsyncScrollbarDragRejected();
886   }
887 }
888 
889 /* static */
NotifyAsyncAutoscrollRejected(const ScrollableLayerGuid::ViewID & aScrollId)890 void APZCCallbackHelper::NotifyAsyncAutoscrollRejected(
891     const ScrollableLayerGuid::ViewID& aScrollId) {
892   MOZ_ASSERT(NS_IsMainThread());
893   nsCOMPtr<nsIObserverService> observerService =
894       mozilla::services::GetObserverService();
895   MOZ_ASSERT(observerService);
896 
897   nsAutoString data;
898   data.AppendInt(aScrollId);
899   observerService->NotifyObservers(nullptr, "autoscroll-rejected-by-apz",
900                                    data.get());
901 }
902 
903 /* static */
CancelAutoscroll(const ScrollableLayerGuid::ViewID & aScrollId)904 void APZCCallbackHelper::CancelAutoscroll(
905     const ScrollableLayerGuid::ViewID& aScrollId) {
906   MOZ_ASSERT(NS_IsMainThread());
907   nsCOMPtr<nsIObserverService> observerService =
908       mozilla::services::GetObserverService();
909   MOZ_ASSERT(observerService);
910 
911   nsAutoString data;
912   data.AppendInt(aScrollId);
913   observerService->NotifyObservers(nullptr, "apz:cancel-autoscroll",
914                                    data.get());
915 }
916 
917 /* static */
NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,const LayoutDevicePoint & aFocusPoint,LayoutDeviceCoord aSpanChange,Modifiers aModifiers,const nsCOMPtr<nsIWidget> & aWidget)918 void APZCCallbackHelper::NotifyPinchGesture(
919     PinchGestureInput::PinchGestureType aType,
920     const LayoutDevicePoint& aFocusPoint, LayoutDeviceCoord aSpanChange,
921     Modifiers aModifiers, const nsCOMPtr<nsIWidget>& aWidget) {
922   APZCCH_LOG("APZCCallbackHelper dispatching pinch gesture\n");
923   EventMessage msg;
924   switch (aType) {
925     case PinchGestureInput::PINCHGESTURE_START:
926       msg = eMagnifyGestureStart;
927       break;
928     case PinchGestureInput::PINCHGESTURE_SCALE:
929       msg = eMagnifyGestureUpdate;
930       break;
931     case PinchGestureInput::PINCHGESTURE_FINGERLIFTED:
932     case PinchGestureInput::PINCHGESTURE_END:
933       msg = eMagnifyGesture;
934       break;
935   }
936 
937   WidgetSimpleGestureEvent event(true, msg, aWidget.get());
938   // XXX mDelta for the eMagnifyGesture event is supposed to be the
939   // cumulative magnification over the entire gesture (per docs in
940   // SimpleGestureEvent.webidl) but currently APZ just sends us a zero
941   // aSpanChange for that event, so the mDelta is wrong. Nothing relies
942   // on that currently, but we might want to fix it at some point.
943   event.mDelta = aSpanChange;
944   event.mModifiers = aModifiers;
945   event.mRefPoint = RoundedToInt(aFocusPoint);
946 
947   DispatchWidgetEvent(event);
948 }
949 
950 }  // namespace layers
951 }  // namespace mozilla
952