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