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