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