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