DisplayPortUtils.cpp (56817B)
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 "DisplayPortUtils.h" 8 9 #include <ostream> 10 11 #include "AnchorPositioningUtils.h" 12 #include "FrameMetrics.h" 13 #include "RetainedDisplayListBuilder.h" 14 #include "StickyScrollContainer.h" 15 #include "WindowRenderer.h" 16 #include "mozilla/PresShell.h" 17 #include "mozilla/ScrollContainerFrame.h" 18 #include "mozilla/StaticPrefs_layers.h" 19 #include "mozilla/StaticPrefs_layout.h" 20 #include "mozilla/dom/BrowserChild.h" 21 #include "mozilla/dom/Document.h" 22 #include "mozilla/gfx/Point.h" 23 #include "mozilla/layers/APZPublicUtils.h" 24 #include "mozilla/layers/CompositorBridgeChild.h" 25 #include "mozilla/layers/LayersMessageUtils.h" 26 #include "mozilla/layers/PAPZ.h" 27 #include "nsIFrameInlines.h" 28 #include "nsLayoutUtils.h" 29 #include "nsPlaceholderFrame.h" 30 #include "nsRefreshDriver.h" 31 #include "nsSubDocumentFrame.h" 32 33 namespace mozilla { 34 35 using gfx::IntSize; 36 37 using layers::FrameMetrics; 38 using layers::ScrollableLayerGuid; 39 40 typedef ScrollableLayerGuid::ViewID ViewID; 41 42 static LazyLogModule sDisplayportLog("apz.displayport"); 43 44 /* static */ 45 DisplayPortMargins DisplayPortMargins::FromAPZ(const ScreenMargin& aMargins, 46 const CSSPoint& aVisualOffset, 47 const CSSPoint& aLayoutOffset) { 48 return DisplayPortMargins{aMargins, aVisualOffset, aLayoutOffset}; 49 } 50 51 /* static */ 52 DisplayPortMargins DisplayPortMargins::ForScrollContainerFrame( 53 ScrollContainerFrame* aScrollContainerFrame, const ScreenMargin& aMargins) { 54 CSSPoint visualOffset; 55 CSSPoint layoutOffset; 56 if (aScrollContainerFrame) { 57 PresShell* presShell = aScrollContainerFrame->PresShell(); 58 layoutOffset = 59 CSSPoint::FromAppUnits(aScrollContainerFrame->GetScrollPosition()); 60 if (aScrollContainerFrame->IsRootScrollFrameOfDocument()) { 61 visualOffset = 62 CSSPoint::FromAppUnits(presShell->GetVisualViewportOffset()); 63 64 } else { 65 visualOffset = layoutOffset; 66 } 67 } 68 return DisplayPortMargins{aMargins, visualOffset, layoutOffset}; 69 } 70 71 /* static */ 72 DisplayPortMargins DisplayPortMargins::ForContent( 73 nsIContent* aContent, const ScreenMargin& aMargins) { 74 return ForScrollContainerFrame( 75 aContent ? nsLayoutUtils::FindScrollContainerFrameFor(aContent) : nullptr, 76 aMargins); 77 } 78 79 ScreenMargin DisplayPortMargins::GetRelativeToLayoutViewport( 80 ContentGeometryType aGeometryType, 81 ScrollContainerFrame* aScrollContainerFrame, 82 const CSSToScreenScale2D& aDisplayportScale) const { 83 // APZ wants |mMargins| applied relative to the visual viewport. 84 // The main-thread painting code applies margins relative to 85 // the layout viewport. To get the main thread to paint the 86 // area APZ wants, apply a translation between the two. The 87 // magnitude of the translation depends on whether we are 88 // applying the displayport to scrolled or fixed content. 89 CSSPoint scrollDeltaCss = 90 ComputeAsyncTranslation(aGeometryType, aScrollContainerFrame); 91 ScreenPoint scrollDelta = scrollDeltaCss * aDisplayportScale; 92 ScreenMargin margins = mMargins; 93 margins.left -= scrollDelta.x; 94 margins.right += scrollDelta.x; 95 margins.top -= scrollDelta.y; 96 margins.bottom += scrollDelta.y; 97 return margins; 98 } 99 100 std::ostream& operator<<(std::ostream& aOs, 101 const DisplayPortMargins& aMargins) { 102 if (aMargins.mVisualOffset == CSSPoint() && 103 aMargins.mLayoutOffset == CSSPoint()) { 104 aOs << aMargins.mMargins; 105 } else { 106 aOs << "{" << aMargins.mMargins << "," << aMargins.mVisualOffset << "," 107 << aMargins.mLayoutOffset << "}"; 108 } 109 return aOs; 110 } 111 112 CSSPoint DisplayPortMargins::ComputeAsyncTranslation( 113 ContentGeometryType aGeometryType, 114 ScrollContainerFrame* aScrollContainerFrame) const { 115 // If we are applying the displayport to scrolled content, the 116 // translation is the entire difference between the visual and 117 // layout offsets. 118 if (aGeometryType == ContentGeometryType::Scrolled) { 119 return mVisualOffset - mLayoutOffset; 120 } 121 122 // If we are applying the displayport to fixed content, only 123 // part of the difference between the visual and layout offsets 124 // should be applied. This is because fixed content remains fixed 125 // to the layout viewport, and some of the async delta between 126 // the visual and layout offsets can drag the layout viewport 127 // with it. We want only the remaining delta, i.e. the offset of 128 // the visual viewport relative to the (async-scrolled) layout 129 // viewport. 130 if (!aScrollContainerFrame) { 131 // Displayport on a non-scrolling frame for some reason. 132 // There will be no divergence between the two viewports. 133 return CSSPoint(); 134 } 135 // Fixed content is always fixed to an RSF. 136 MOZ_ASSERT(aScrollContainerFrame->IsRootScrollFrameOfDocument()); 137 if (!aScrollContainerFrame->PresShell()->IsVisualViewportSizeSet()) { 138 // Zooming is disabled, so the layout viewport tracks the 139 // visual viewport completely. 140 return CSSPoint(); 141 } 142 // Use KeepLayoutViewportEnclosingViewportVisual() to compute 143 // an async layout viewport the way APZ would. 144 const CSSRect visualViewport{ 145 mVisualOffset, 146 // TODO: There are probably some edge cases here around async zooming 147 // that are not currently being handled properly. For proper handling, 148 // we'd likely need to save APZ's async zoom when populating 149 // mVisualOffset, and using it to adjust the visual viewport size here. 150 // Note that any incorrectness caused by this will only occur transiently 151 // during async zooming. 152 CSSSize::FromAppUnits( 153 aScrollContainerFrame->PresShell()->GetVisualViewportSize())}; 154 const CSSRect scrollableRect = CSSRect::FromAppUnits( 155 nsLayoutUtils::CalculateExpandedScrollableRect(aScrollContainerFrame)); 156 CSSRect asyncLayoutViewport{ 157 mLayoutOffset, 158 CSSSize::FromAppUnits(aScrollContainerFrame->GetScrollPortRect().Size())}; 159 FrameMetrics::KeepLayoutViewportEnclosingVisualViewport( 160 visualViewport, scrollableRect, /* out */ asyncLayoutViewport); 161 return mVisualOffset - asyncLayoutViewport.TopLeft(); 162 } 163 164 static nsRect GetDisplayPortFromRectData(nsIContent* aContent, 165 DisplayPortPropertyData* aRectData) { 166 // In the case where the displayport is set as a rect, we assume it is 167 // already aligned and clamped as necessary. The burden to do that is 168 // on the setter of the displayport. In practice very few places set the 169 // displayport directly as a rect (mostly tests). 170 return aRectData->mRect; 171 } 172 173 static nsRect GetDisplayPortFromMarginsData( 174 nsIContent* aContent, DisplayPortMarginsPropertyData* aMarginsData, 175 const DisplayPortOptions& aOptions) { 176 // In the case where the displayport is set via margins, we apply the margins 177 // to a base rect. Then we align the expanded rect based on the alignment 178 // requested, and finally, clamp it to the size of the scrollable rect. 179 180 nsRect base; 181 if (nsRect* baseData = static_cast<nsRect*>( 182 aContent->GetProperty(nsGkAtoms::DisplayPortBase))) { 183 base = *baseData; 184 } else { 185 // In theory we shouldn't get here, but we do sometimes (see bug 1212136). 186 // Fall through for graceful handling. 187 } 188 189 nsIFrame* frame = nsLayoutUtils::GetScrollContainerFrameFromContent(aContent); 190 if (!frame) { 191 // Turns out we can't really compute it. Oops. We still should return 192 // something sane. 193 NS_WARNING( 194 "Attempting to get a displayport from a content with no primary " 195 "frame!"); 196 return base; 197 } 198 199 bool isRoot = false; 200 if (aContent->OwnerDoc()->GetRootElement() == aContent) { 201 isRoot = true; 202 } 203 204 ScrollContainerFrame* scrollContainerFrame = frame->GetScrollTargetFrame(); 205 nsPoint scrollPos; 206 if (scrollContainerFrame) { 207 scrollPos = scrollContainerFrame->GetScrollPosition(); 208 } 209 210 nsPresContext* presContext = frame->PresContext(); 211 int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel(); 212 213 LayoutDeviceToScreenScale2D res = 214 LayoutDeviceToParentLayerScale( 215 presContext->PresShell()->GetCumulativeResolution()) * 216 nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics( 217 frame); 218 219 // Calculate the expanded scrollable rect, which we'll be clamping the 220 // displayport to. 221 nsRect expandedScrollableRect = 222 nsLayoutUtils::CalculateExpandedScrollableRect(frame); 223 224 // GetTransformToAncestorScale() can return 0. In this case, just return the 225 // base rect (clamped to the expanded scrollable rect), as other calculations 226 // would run into divisions by zero. 227 if (res == LayoutDeviceToScreenScale2D(0, 0)) { 228 // Make sure the displayport remains within the scrollable rect. 229 return base.MoveInsideAndClamp(expandedScrollableRect - scrollPos); 230 } 231 232 // First convert the base rect to screen pixels 233 LayoutDeviceToScreenScale2D parentRes = res; 234 if (isRoot) { 235 // the base rect for root scroll frames is specified in the parent document 236 // coordinate space, so it doesn't include the local resolution. 237 float localRes = presContext->PresShell()->GetResolution(); 238 parentRes.xScale /= localRes; 239 parentRes.yScale /= localRes; 240 } 241 ScreenRect screenRect = 242 LayoutDeviceRect::FromAppUnits(base, auPerDevPixel) * parentRes; 243 244 // Note on the correctness of applying the alignment in Screen space: 245 // The correct space to apply the alignment in would be Layer space, but 246 // we don't necessarily know the scale to convert to Layer space at this 247 // point because Layout may not yet have chosen the resolution at which to 248 // render (it chooses that in FrameLayerBuilder, but this can be called 249 // during display list building). Therefore, we perform the alignment in 250 // Screen space, which basically assumes that Layout chose to render at 251 // screen resolution; since this is what Layout does most of the time, 252 // this is a good approximation. A proper solution would involve moving 253 // the choosing of the resolution to display-list building time. 254 ScreenSize alignment; 255 256 PresShell* presShell = presContext->PresShell(); 257 MOZ_ASSERT(presShell); 258 259 ScreenMargin margins = aMarginsData->mMargins.GetRelativeToLayoutViewport( 260 aOptions.mGeometryType, scrollContainerFrame, 261 presContext->CSSToDevPixelScale() * res); 262 263 if (presShell->IsDisplayportSuppressed() || 264 aContent->GetProperty(nsGkAtoms::MinimalDisplayPort)) { 265 alignment = ScreenSize(1, 1); 266 } else { 267 // Moving the displayport is relatively expensive with WR so we use a larger 268 // alignment that causes the displayport to move less frequently. The 269 // alignment scales up with the size of the base rect so larger scrollframes 270 // use a larger alignment, but we clamp the alignment to a power of two 271 // between 128 and 1024 (inclusive). 272 // This naturally also increases the size of the displayport compared to 273 // always using a 128 alignment, so the displayport multipliers are also 274 // correspondingly smaller when WR is enabled to prevent the displayport 275 // from becoming too big. 276 gfx::Size multiplier = 277 layers::apz::GetDisplayportAlignmentMultiplier(screenRect.Size()); 278 alignment = ScreenSize(128 * multiplier.width, 128 * multiplier.height); 279 } 280 281 // Avoid division by zero. 282 if (alignment.width == 0) { 283 alignment.width = 128; 284 } 285 if (alignment.height == 0) { 286 alignment.height = 128; 287 } 288 289 // Expand the rect by the margins 290 screenRect.Inflate(margins); 291 292 ScreenPoint scrollPosScreen = 293 LayoutDevicePoint::FromAppUnits(scrollPos, auPerDevPixel) * res; 294 295 // Align the display port. 296 screenRect += scrollPosScreen; 297 float x = alignment.width * floor(screenRect.x / alignment.width); 298 float y = alignment.height * floor(screenRect.y / alignment.height); 299 float w = alignment.width * ceil(screenRect.width / alignment.width + 1); 300 float h = alignment.height * ceil(screenRect.height / alignment.height + 1); 301 screenRect = ScreenRect(x, y, w, h); 302 screenRect -= scrollPosScreen; 303 304 // Convert the aligned rect back into app units. 305 nsRect result = LayoutDeviceRect::ToAppUnits(screenRect / res, auPerDevPixel); 306 307 // Make sure the displayport remains within the scrollable rect. 308 result = result.MoveInsideAndClamp(expandedScrollableRect - scrollPos); 309 310 return result; 311 } 312 313 static bool GetDisplayPortData( 314 nsIContent* aContent, DisplayPortPropertyData** aOutRectData, 315 DisplayPortMarginsPropertyData** aOutMarginsData) { 316 MOZ_ASSERT(aOutRectData && aOutMarginsData); 317 318 *aOutRectData = static_cast<DisplayPortPropertyData*>( 319 aContent->GetProperty(nsGkAtoms::DisplayPort)); 320 *aOutMarginsData = static_cast<DisplayPortMarginsPropertyData*>( 321 aContent->GetProperty(nsGkAtoms::DisplayPortMargins)); 322 323 if (!*aOutRectData && !*aOutMarginsData) { 324 // This content element has no displayport data at all 325 return false; 326 } 327 328 if (*aOutRectData && *aOutMarginsData) { 329 // choose margins if equal priority 330 if ((*aOutRectData)->mPriority > (*aOutMarginsData)->mPriority) { 331 *aOutMarginsData = nullptr; 332 } else { 333 *aOutRectData = nullptr; 334 } 335 } 336 337 NS_ASSERTION((*aOutRectData == nullptr) != (*aOutMarginsData == nullptr), 338 "Only one of aOutRectData or aOutMarginsData should be set!"); 339 340 return true; 341 } 342 343 static bool GetWasDisplayPortPainted(nsIContent* aContent) { 344 DisplayPortPropertyData* rectData = nullptr; 345 DisplayPortMarginsPropertyData* marginsData = nullptr; 346 347 if (!GetDisplayPortData(aContent, &rectData, &marginsData)) { 348 return false; 349 } 350 351 return rectData ? rectData->mPainted : marginsData->mPainted; 352 } 353 354 bool DisplayPortUtils::IsMissingDisplayPortBaseRect(nsIContent* aContent) { 355 DisplayPortPropertyData* rectData = nullptr; 356 DisplayPortMarginsPropertyData* marginsData = nullptr; 357 358 if (GetDisplayPortData(aContent, &rectData, &marginsData) && marginsData) { 359 return !aContent->GetProperty(nsGkAtoms::DisplayPortBase); 360 } 361 362 return false; 363 } 364 365 static void TranslateFromScrollPortToScrollContainerFrame(nsIContent* aContent, 366 nsRect* aRect) { 367 MOZ_ASSERT(aRect); 368 if (ScrollContainerFrame* scrollContainerFrame = 369 nsLayoutUtils::FindScrollContainerFrameFor(aContent)) { 370 *aRect += scrollContainerFrame->GetScrollPortRect().TopLeft(); 371 } 372 } 373 374 static bool GetDisplayPortImpl(nsIContent* aContent, nsRect* aResult, 375 const DisplayPortOptions& aOptions) { 376 DisplayPortPropertyData* rectData = nullptr; 377 DisplayPortMarginsPropertyData* marginsData = nullptr; 378 379 if (!GetDisplayPortData(aContent, &rectData, &marginsData)) { 380 return false; 381 } 382 383 nsIFrame* frame = aContent->GetPrimaryFrame(); 384 if (frame && !frame->PresShell()->AsyncPanZoomEnabled()) { 385 return false; 386 } 387 388 if (!aResult) { 389 // We have displayport data, but the caller doesn't want the actual 390 // rect, so we don't need to actually compute it. 391 return true; 392 } 393 394 bool isDisplayportSuppressed = false; 395 396 if (frame) { 397 nsPresContext* presContext = frame->PresContext(); 398 MOZ_ASSERT(presContext); 399 PresShell* presShell = presContext->PresShell(); 400 MOZ_ASSERT(presShell); 401 isDisplayportSuppressed = presShell->IsDisplayportSuppressed(); 402 } 403 404 nsRect result; 405 if (rectData) { 406 result = GetDisplayPortFromRectData(aContent, rectData); 407 } else if (isDisplayportSuppressed || 408 nsLayoutUtils::ShouldDisableApzForElement(aContent) || 409 aContent->GetProperty(nsGkAtoms::MinimalDisplayPort)) { 410 // Note: the above conditions should be in sync with the conditions in 411 // WillUseEmptyDisplayPortMargins. 412 413 // Make a copy of the margins data but set the margins to empty. 414 // Do not create a new DisplayPortMargins object with 415 // DisplayPortMargins::Empty(), because that will record the visual 416 // and layout scroll offsets in place right now on the DisplayPortMargins, 417 // and those are only meant to be recorded when the margins are stored. 418 DisplayPortMarginsPropertyData noMargins = *marginsData; 419 noMargins.mMargins.mMargins = ScreenMargin(); 420 result = GetDisplayPortFromMarginsData(aContent, &noMargins, aOptions); 421 } else { 422 result = GetDisplayPortFromMarginsData(aContent, marginsData, aOptions); 423 } 424 425 if (aOptions.mRelativeTo == DisplayportRelativeTo::ScrollFrame) { 426 TranslateFromScrollPortToScrollContainerFrame(aContent, &result); 427 } 428 429 *aResult = result; 430 return true; 431 } 432 433 bool DisplayPortUtils::GetDisplayPort(nsIContent* aContent, nsRect* aResult, 434 const DisplayPortOptions& aOptions) { 435 return GetDisplayPortImpl(aContent, aResult, aOptions); 436 } 437 438 bool DisplayPortUtils::HasDisplayPort(nsIContent* aContent) { 439 return GetDisplayPort(aContent, nullptr); 440 } 441 442 bool DisplayPortUtils::HasPaintedDisplayPort(nsIContent* aContent) { 443 DisplayPortPropertyData* rectData = nullptr; 444 DisplayPortMarginsPropertyData* marginsData = nullptr; 445 GetDisplayPortData(aContent, &rectData, &marginsData); 446 if (rectData) { 447 return rectData->mPainted; 448 } 449 if (marginsData) { 450 return marginsData->mPainted; 451 } 452 return false; 453 } 454 455 void DisplayPortUtils::MarkDisplayPortAsPainted(nsIContent* aContent) { 456 DisplayPortPropertyData* rectData = nullptr; 457 DisplayPortMarginsPropertyData* marginsData = nullptr; 458 GetDisplayPortData(aContent, &rectData, &marginsData); 459 MOZ_ASSERT(rectData || marginsData, 460 "MarkDisplayPortAsPainted should only be called for an element " 461 "with a displayport"); 462 if (rectData) { 463 rectData->mPainted = true; 464 } 465 if (marginsData) { 466 marginsData->mPainted = true; 467 } 468 } 469 470 bool DisplayPortUtils::HasNonMinimalDisplayPort(nsIContent* aContent) { 471 return !aContent->GetProperty(nsGkAtoms::MinimalDisplayPort) && 472 HasDisplayPort(aContent); 473 } 474 475 bool DisplayPortUtils::HasNonMinimalNonZeroDisplayPort(nsIContent* aContent) { 476 if (aContent->GetProperty(nsGkAtoms::MinimalDisplayPort)) { 477 return false; 478 } 479 480 DisplayPortPropertyData* rectData = nullptr; 481 DisplayPortMarginsPropertyData* marginsData = nullptr; 482 if (!GetDisplayPortData(aContent, &rectData, &marginsData)) { 483 return false; 484 } 485 486 if (!marginsData) { 487 // We have a display port, so if we don't have margin data we must have rect 488 // data. We consider such as non zero and non minimal, it's probably not too 489 // important as display port rects are only used in tests. 490 return true; 491 } 492 493 if (marginsData->mMargins.mMargins != ScreenMargin()) { 494 return true; 495 } 496 497 return false; 498 } 499 500 /* static */ 501 bool DisplayPortUtils::GetDisplayPortForVisibilityTesting(nsIContent* aContent, 502 nsRect* aResult) { 503 MOZ_ASSERT(aResult); 504 return GetDisplayPortImpl( 505 aContent, aResult, 506 DisplayPortOptions().With(DisplayportRelativeTo::ScrollFrame)); 507 } 508 509 void DisplayPortUtils::InvalidateForDisplayPortChange( 510 nsIContent* aContent, bool aHadDisplayPort, const nsRect& aOldDisplayPort, 511 const nsRect& aNewDisplayPort, RepaintMode aRepaintMode) { 512 if (aRepaintMode != RepaintMode::Repaint) { 513 return; 514 } 515 516 bool changed = 517 !aHadDisplayPort || !aOldDisplayPort.IsEqualEdges(aNewDisplayPort); 518 519 nsIFrame* frame = nsLayoutUtils::FindScrollContainerFrameFor(aContent); 520 if (changed && frame) { 521 // It is important to call SchedulePaint on the same frame that we set the 522 // dirty rect properties on so we can find the frame later to remove the 523 // properties. 524 frame->SchedulePaint(); 525 526 if (!nsLayoutUtils::AreRetainedDisplayListsEnabled()) { 527 return; 528 } 529 530 if (StaticPrefs::layout_display_list_retain_sc()) { 531 // DisplayListBuildingDisplayPortRect property is not used when retain sc 532 // mode is enabled. 533 return; 534 } 535 536 auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(frame); 537 if (!builder) { 538 return; 539 } 540 541 bool found; 542 nsRect* rect = frame->GetProperty( 543 nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), &found); 544 545 if (!found) { 546 rect = new nsRect(); 547 frame->AddProperty( 548 nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect); 549 frame->SetHasOverrideDirtyRegion(true); 550 551 DL_LOGV("Adding display port building rect for frame %p\n", frame); 552 RetainedDisplayListData* data = builder->Data(); 553 data->Flags(frame) += RetainedDisplayListData::FrameFlag::HasProps; 554 } else { 555 MOZ_ASSERT(rect, "this property should only store non-null values"); 556 } 557 558 if (aHadDisplayPort) { 559 // We only need to build a display list for any new areas added 560 nsRegion newRegion(aNewDisplayPort); 561 newRegion.SubOut(aOldDisplayPort); 562 rect->UnionRect(*rect, newRegion.GetBounds()); 563 } else { 564 rect->UnionRect(*rect, aNewDisplayPort); 565 } 566 } 567 } 568 569 bool DisplayPortUtils::SetDisplayPortMargins( 570 nsIContent* aContent, PresShell* aPresShell, 571 const DisplayPortMargins& aMargins, 572 ClearMinimalDisplayPortProperty aClearMinimalDisplayPortProperty, 573 uint32_t aPriority, RepaintMode aRepaintMode) { 574 MOZ_ASSERT(aContent); 575 MOZ_ASSERT(aContent->GetComposedDoc() == aPresShell->GetDocument()); 576 577 DisplayPortMarginsPropertyData* currentData = 578 static_cast<DisplayPortMarginsPropertyData*>( 579 aContent->GetProperty(nsGkAtoms::DisplayPortMargins)); 580 if (currentData && currentData->mPriority > aPriority) { 581 return false; 582 } 583 584 if (currentData && currentData->mMargins.mVisualOffset != CSSPoint() && 585 aMargins.mVisualOffset == CSSPoint()) { 586 // If we hit this, then it's possible that we're setting a displayport 587 // that is wrong because the old one had a layout/visual adjustment and 588 // the new one does not. 589 MOZ_LOG(sDisplayportLog, LogLevel::Warning, 590 ("Dropping visual offset %s", 591 ToString(currentData->mMargins.mVisualOffset).c_str())); 592 } 593 594 nsIFrame* scrollFrame = 595 nsLayoutUtils::GetScrollContainerFrameFromContent(aContent); 596 597 nsRect oldDisplayPort; 598 bool hadDisplayPort = false; 599 bool wasPainted = GetWasDisplayPortPainted(aContent); 600 if (scrollFrame) { 601 // We only use the two return values from this function to call 602 // InvalidateForDisplayPortChange. InvalidateForDisplayPortChange does 603 // nothing if aContent does not have a frame. So getting the displayport is 604 // useless if the content has no frame, so we avoid calling this to avoid 605 // triggering a warning about not having a frame. 606 hadDisplayPort = GetDisplayPort(aContent, &oldDisplayPort); 607 } 608 609 aContent->SetProperty( 610 nsGkAtoms::DisplayPortMargins, 611 new DisplayPortMarginsPropertyData(aMargins, aPriority, wasPainted), 612 nsINode::DeleteProperty<DisplayPortMarginsPropertyData>); 613 614 if (aClearMinimalDisplayPortProperty == 615 ClearMinimalDisplayPortProperty::Yes) { 616 if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Debug) && 617 aContent->GetProperty(nsGkAtoms::MinimalDisplayPort)) { 618 mozilla::layers::ScrollableLayerGuid::ViewID viewID = 619 mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID; 620 nsLayoutUtils::FindIDFor(aContent, &viewID); 621 MOZ_LOG(sDisplayportLog, LogLevel::Debug, 622 ("SetDisplayPortMargins removing MinimalDisplayPort prop on " 623 "scrollId=%" PRIu64 "\n", 624 viewID)); 625 } 626 aContent->RemoveProperty(nsGkAtoms::MinimalDisplayPort); 627 } 628 629 ScrollContainerFrame* scrollContainerFrame = 630 scrollFrame ? scrollFrame->GetScrollTargetFrame() : nullptr; 631 if (!scrollContainerFrame) { 632 return true; 633 } 634 635 nsRect newDisplayPort; 636 DebugOnly<bool> hasDisplayPort = GetDisplayPort(aContent, &newDisplayPort); 637 MOZ_ASSERT(hasDisplayPort); 638 639 if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Debug)) { 640 mozilla::layers::ScrollableLayerGuid::ViewID viewID = 641 mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID; 642 nsLayoutUtils::FindIDFor(aContent, &viewID); 643 if (!hadDisplayPort) { 644 MOZ_LOG(sDisplayportLog, LogLevel::Debug, 645 ("SetDisplayPortMargins %s on scrollId=%" PRIu64 ", newDp=%s\n", 646 ToString(aMargins).c_str(), viewID, 647 ToString(newDisplayPort).c_str())); 648 } else { 649 // Use verbose level logging for when an existing displayport got its 650 // margins updated. 651 MOZ_LOG(sDisplayportLog, LogLevel::Verbose, 652 ("SetDisplayPortMargins %s on scrollId=%" PRIu64 ", newDp=%s\n", 653 ToString(aMargins).c_str(), viewID, 654 ToString(newDisplayPort).c_str())); 655 } 656 } 657 658 InvalidateForDisplayPortChange(aContent, hadDisplayPort, oldDisplayPort, 659 newDisplayPort, aRepaintMode); 660 661 scrollContainerFrame->TriggerDisplayPortExpiration(); 662 663 // Display port margins changing means that the set of visible frames may 664 // have drastically changed. Check if we should schedule an update. 665 hadDisplayPort = scrollContainerFrame 666 ->GetDisplayPortAtLastApproximateFrameVisibilityUpdate( 667 &oldDisplayPort); 668 669 bool needVisibilityUpdate = !hadDisplayPort; 670 // Check if the total size has changed by a large factor. 671 if (!needVisibilityUpdate) { 672 if ((newDisplayPort.width > 2 * oldDisplayPort.width) || 673 (oldDisplayPort.width > 2 * newDisplayPort.width) || 674 (newDisplayPort.height > 2 * oldDisplayPort.height) || 675 (oldDisplayPort.height > 2 * newDisplayPort.height)) { 676 needVisibilityUpdate = true; 677 } 678 } 679 // Check if it's moved by a significant amount. 680 if (!needVisibilityUpdate) { 681 if (nsRect* baseData = static_cast<nsRect*>( 682 aContent->GetProperty(nsGkAtoms::DisplayPortBase))) { 683 nsRect base = *baseData; 684 if ((std::abs(newDisplayPort.X() - oldDisplayPort.X()) > base.width) || 685 (std::abs(newDisplayPort.XMost() - oldDisplayPort.XMost()) > 686 base.width) || 687 (std::abs(newDisplayPort.Y() - oldDisplayPort.Y()) > base.height) || 688 (std::abs(newDisplayPort.YMost() - oldDisplayPort.YMost()) > 689 base.height)) { 690 needVisibilityUpdate = true; 691 } 692 } 693 } 694 if (needVisibilityUpdate) { 695 aPresShell->ScheduleApproximateFrameVisibilityUpdateNow(); 696 } 697 698 return true; 699 } 700 701 void DisplayPortUtils::SetDisplayPortBase(nsIContent* aContent, 702 const nsRect& aBase) { 703 if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Verbose)) { 704 ViewID viewId = nsLayoutUtils::FindOrCreateIDFor(aContent); 705 MOZ_LOG(sDisplayportLog, LogLevel::Verbose, 706 ("Setting base rect %s for scrollId=%" PRIu64 "\n", 707 ToString(aBase).c_str(), viewId)); 708 } 709 if (nsRect* baseData = static_cast<nsRect*>( 710 aContent->GetProperty(nsGkAtoms::DisplayPortBase))) { 711 *baseData = aBase; 712 return; 713 } 714 715 aContent->SetProperty(nsGkAtoms::DisplayPortBase, new nsRect(aBase), 716 nsINode::DeleteProperty<nsRect>); 717 } 718 719 void DisplayPortUtils::SetDisplayPortBaseIfNotSet(nsIContent* aContent, 720 const nsRect& aBase) { 721 if (aContent->GetProperty(nsGkAtoms::DisplayPortBase)) { 722 return; 723 } 724 if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Verbose)) { 725 ViewID viewId = nsLayoutUtils::FindOrCreateIDFor(aContent); 726 MOZ_LOG(sDisplayportLog, LogLevel::Verbose, 727 ("Setting base rect %s for scrollId=%" PRIu64 "\n", 728 ToString(aBase).c_str(), viewId)); 729 } 730 731 aContent->SetProperty(nsGkAtoms::DisplayPortBase, new nsRect(aBase), 732 nsINode::DeleteProperty<nsRect>); 733 } 734 735 void DisplayPortUtils::RemoveDisplayPort(nsIContent* aContent) { 736 aContent->RemoveProperty(nsGkAtoms::DisplayPort); 737 aContent->RemoveProperty(nsGkAtoms::DisplayPortMargins); 738 } 739 740 void DisplayPortUtils::SetMinimalDisplayPortDuringPainting( 741 nsIContent* aContent, PresShell* aPresShell) { 742 // SetDisplayPortMargins calls TriggerDisplayPortExpiration which starts a 743 // display port expiry timer for display ports that do expire. However 744 // minimal display ports do not expire, so the display port has to be 745 // marked before the SetDisplayPortMargins call so the expiry timer 746 // doesn't get started. 747 aContent->SetProperty(nsGkAtoms::MinimalDisplayPort, 748 reinterpret_cast<void*>(true)); 749 750 DisplayPortUtils::SetDisplayPortMargins( 751 aContent, aPresShell, DisplayPortMargins::Empty(aContent), 752 DisplayPortUtils::ClearMinimalDisplayPortProperty::No, 0, 753 DisplayPortUtils::RepaintMode::DoNotRepaint); 754 } 755 756 bool DisplayPortUtils::ViewportHasDisplayPort(nsPresContext* aPresContext) { 757 nsIFrame* rootScrollContainerFrame = 758 aPresContext->PresShell()->GetRootScrollContainerFrame(); 759 return rootScrollContainerFrame && 760 HasDisplayPort(rootScrollContainerFrame->GetContent()); 761 } 762 763 bool DisplayPortUtils::IsFixedPosFrameInDisplayPort(const nsIFrame* aFrame) { 764 // Fixed-pos frames are parented by the viewport frame or the page content 765 // frame. We'll assume that printing/print preview don't have displayports for 766 // their pages! 767 nsIFrame* parent = aFrame->GetParent(); 768 if (!parent || parent->GetParent() || 769 aFrame->StyleDisplay()->mPosition != StylePositionProperty::Fixed) { 770 return false; 771 } 772 return ViewportHasDisplayPort(aFrame->PresContext()); 773 } 774 775 // We want to this return true for the scroll frame, but not the 776 // scrolled frame (which has the same content). 777 bool DisplayPortUtils::FrameHasDisplayPort(nsIFrame* aFrame, 778 const nsIFrame* aScrolledFrame) { 779 if (!aFrame->GetContent() || !HasDisplayPort(aFrame->GetContent())) { 780 return false; 781 } 782 ScrollContainerFrame* sf = do_QueryFrame(aFrame); 783 if (sf) { 784 if (aScrolledFrame && aScrolledFrame != sf->GetScrolledFrame()) { 785 return false; 786 } 787 return true; 788 } 789 return false; 790 } 791 792 bool DisplayPortUtils::CalculateAndSetDisplayPortMargins( 793 ScrollContainerFrame* aScrollContainerFrame, RepaintMode aRepaintMode) { 794 nsIContent* content = aScrollContainerFrame->GetContent(); 795 MOZ_ASSERT(content); 796 797 FrameMetrics metrics = 798 nsLayoutUtils::CalculateBasicFrameMetrics(aScrollContainerFrame); 799 ScreenMargin displayportMargins = layers::apz::CalculatePendingDisplayPort( 800 metrics, ParentLayerPoint(0.0f, 0.0f)); 801 PresShell* presShell = aScrollContainerFrame->PresShell(); 802 803 DisplayPortMargins margins = DisplayPortMargins::ForScrollContainerFrame( 804 aScrollContainerFrame, displayportMargins); 805 806 return SetDisplayPortMargins(content, presShell, margins, 807 ClearMinimalDisplayPortProperty::Yes, 0, 808 aRepaintMode); 809 } 810 811 bool DisplayPortUtils::MaybeCreateDisplayPort( 812 nsDisplayListBuilder* aBuilder, ScrollContainerFrame* aScrollContainerFrame, 813 RepaintMode aRepaintMode) { 814 MOZ_ASSERT(aBuilder->IsPaintingToWindow()); 815 816 nsIContent* content = aScrollContainerFrame->GetContent(); 817 if (!content) { 818 return false; 819 } 820 821 // We perform an optimization where we ensure that at least one 822 // async-scrollable frame (i.e. one that WantsAsyncScroll()) has a 823 // displayport. If that's not the case yet, and we are async-scrollable, we 824 // will get a displayport. 825 MOZ_ASSERT(nsLayoutUtils::AsyncPanZoomEnabled(aScrollContainerFrame)); 826 if (!aBuilder->HaveScrollableDisplayPort() && 827 aScrollContainerFrame->WantAsyncScroll()) { 828 bool haveDisplayPort = HasNonMinimalNonZeroDisplayPort(content); 829 // If we don't already have a displayport, calculate and set one. 830 if (!haveDisplayPort) { 831 // We only use the viewId for logging purposes, but create it 832 // unconditionally to minimize impact of enabling logging. If we don't 833 // assign a viewId here it will get assigned later anyway so functionally 834 // there should be no difference. 835 ViewID viewId = nsLayoutUtils::FindOrCreateIDFor(content); 836 MOZ_LOG( 837 sDisplayportLog, LogLevel::Debug, 838 ("Setting DP on first-encountered scrollId=%" PRIu64 "\n", viewId)); 839 840 CalculateAndSetDisplayPortMargins(aScrollContainerFrame, aRepaintMode); 841 #ifdef DEBUG 842 haveDisplayPort = HasNonMinimalDisplayPort(content); 843 MOZ_ASSERT(haveDisplayPort, 844 "should have a displayport after having just set it"); 845 #endif 846 } 847 848 // Record that the we now have a scrollable display port. 849 aBuilder->SetHaveScrollableDisplayPort(); 850 return true; 851 } 852 return false; 853 } 854 855 void DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors( 856 nsIFrame* aFrame) { 857 nsIFrame* frame = aFrame; 858 while (frame) { 859 frame = OneStepInAsyncScrollableAncestorChain(frame); 860 if (!frame) { 861 break; 862 } 863 ScrollContainerFrame* scrollAncestor = 864 nsLayoutUtils::GetAsyncScrollableAncestorFrame(frame); 865 if (!scrollAncestor) { 866 break; 867 } 868 frame = scrollAncestor; 869 MOZ_ASSERT(scrollAncestor->WantAsyncScroll() || 870 frame->PresShell()->GetRootScrollContainerFrame() == frame); 871 if (nsLayoutUtils::AsyncPanZoomEnabled(frame) && 872 !HasDisplayPort(frame->GetContent())) { 873 SetDisplayPortMargins(frame->GetContent(), frame->PresShell(), 874 DisplayPortMargins::Empty(frame->GetContent()), 875 ClearMinimalDisplayPortProperty::No, 0, 876 RepaintMode::Repaint); 877 } 878 } 879 } 880 881 bool DisplayPortUtils::MaybeCreateDisplayPortInFirstScrollFrameEncountered( 882 nsIFrame* aFrame, nsDisplayListBuilder* aBuilder) { 883 // Don't descend into the tab bar in chrome, it can be very large and does not 884 // contain any async scrollable elements. 885 if (XRE_IsParentProcess() && aFrame->GetContent() && 886 aFrame->GetContent()->GetID() == nsGkAtoms::tabbrowser_arrowscrollbox) { 887 return false; 888 } 889 if (aFrame->IsScrollContainerOrSubclass()) { 890 auto* sf = static_cast<ScrollContainerFrame*>(aFrame); 891 if (MaybeCreateDisplayPort(aBuilder, sf, RepaintMode::Repaint)) { 892 // If this was the first displayport found in the first scroll container 893 // frame encountered, mark the scroll container frame with the current 894 // paint sequence number. This is used later to ensure the displayport 895 // created is never expired. When there is a scrollable frame with a first 896 // scrollable sequence number found that does not match the current paint 897 // sequence number (may occur if the dom was mutated in some way), the 898 // value will be reset. 899 sf->SetIsFirstScrollableFrameSequenceNumber( 900 Some(nsDisplayListBuilder::GetPaintSequenceNumber())); 901 return true; 902 } 903 } else if (aFrame->IsPlaceholderFrame()) { 904 nsPlaceholderFrame* placeholder = static_cast<nsPlaceholderFrame*>(aFrame); 905 nsIFrame* oof = placeholder->GetOutOfFlowFrame(); 906 if (oof && !nsLayoutUtils::IsPopup(oof) && 907 MaybeCreateDisplayPortInFirstScrollFrameEncountered(oof, aBuilder)) { 908 return true; 909 } 910 } else if (aFrame->IsSubDocumentFrame()) { 911 PresShell* presShell = static_cast<nsSubDocumentFrame*>(aFrame) 912 ->GetSubdocumentPresShellForPainting(0); 913 if (nsIFrame* root = presShell ? presShell->GetRootFrame() : nullptr) { 914 if (MaybeCreateDisplayPortInFirstScrollFrameEncountered(root, aBuilder)) { 915 return true; 916 } 917 } 918 } 919 // Checking mMozSubtreeHiddenOnlyVisually is relatively slow because it 920 // involves loading more memory. It's only allowed in chrome sheets so let's 921 // only support it in the parent process so we can mostly optimize this out in 922 // content processes. 923 if (XRE_IsParentProcess() && 924 aFrame->StyleUIReset()->mMozSubtreeHiddenOnlyVisually) { 925 // Only descend the visible card of deck / tabpanels 926 return false; 927 } 928 for (nsIFrame* child : aFrame->PrincipalChildList()) { 929 if (MaybeCreateDisplayPortInFirstScrollFrameEncountered(child, aBuilder)) { 930 return true; 931 } 932 } 933 return false; 934 } 935 936 void DisplayPortUtils::ExpireDisplayPortOnAsyncScrollableAncestor( 937 nsIFrame* aFrame) { 938 nsIFrame* frame = aFrame; 939 while (frame) { 940 frame = OneStepInAsyncScrollableAncestorChain(frame); 941 if (!frame) { 942 break; 943 } 944 ScrollContainerFrame* scrollAncestor = 945 nsLayoutUtils::GetAsyncScrollableAncestorFrame(frame); 946 if (!scrollAncestor) { 947 break; 948 } 949 frame = scrollAncestor; 950 MOZ_ASSERT(frame); 951 if (!frame) { 952 break; 953 } 954 MOZ_ASSERT(scrollAncestor->WantAsyncScroll() || 955 frame->PresShell()->GetRootScrollContainerFrame() == frame); 956 if (HasDisplayPort(frame->GetContent())) { 957 scrollAncestor->TriggerDisplayPortExpiration(); 958 // Stop after the first trigger. If it failed, there's no point in 959 // continuing because all the rest of the frames we encounter are going 960 // to be ancestors of |scrollAncestor| which will keep its displayport. 961 // If the trigger succeeded, we stop because when the trigger executes 962 // it will call this function again to trigger the next ancestor up the 963 // chain. 964 break; 965 } 966 } 967 } 968 969 Maybe<nsRect> DisplayPortUtils::GetRootDisplayportBase(PresShell* aPresShell) { 970 DebugOnly<nsPresContext*> pc = aPresShell->GetPresContext(); 971 MOZ_ASSERT(pc, "this function should be called after PresShell::Init"); 972 MOZ_ASSERT(pc->IsRootContentDocumentCrossProcess() || 973 !pc->GetParentPresContext()); 974 975 dom::BrowserChild* browserChild = dom::BrowserChild::GetFrom(aPresShell); 976 if (browserChild && !browserChild->IsTopLevel()) { 977 // If this is an in-process root in on OOP iframe, use the visible rect if 978 // it's been set. 979 return browserChild->GetVisibleRect(); 980 } 981 982 nsIFrame* frame = aPresShell->GetRootScrollContainerFrame(); 983 if (!frame) { 984 frame = aPresShell->GetRootFrame(); 985 } 986 987 nsRect baseRect; 988 if (frame) { 989 baseRect = GetDisplayportBase(frame); 990 } else { 991 baseRect = nsRect(nsPoint(0, 0), 992 aPresShell->GetPresContext()->GetVisibleArea().Size()); 993 } 994 995 return Some(baseRect); 996 } 997 998 nsRect DisplayPortUtils::GetDisplayportBase(nsIFrame* aFrame) { 999 MOZ_ASSERT(aFrame); 1000 1001 return nsRect(nsPoint(), 1002 nsLayoutUtils::CalculateCompositionSizeForFrame(aFrame)); 1003 } 1004 1005 bool DisplayPortUtils::WillUseEmptyDisplayPortMargins(nsIContent* aContent) { 1006 MOZ_ASSERT(HasDisplayPort(aContent)); 1007 nsIFrame* frame = aContent->GetPrimaryFrame(); 1008 if (!frame) { 1009 return false; 1010 } 1011 1012 // Note these conditions should be in sync with the conditions where we use 1013 // empty margins to calculate display port in GetDisplayPortImpl 1014 return aContent->GetProperty(nsGkAtoms::MinimalDisplayPort) || 1015 frame->PresShell()->IsDisplayportSuppressed() || 1016 nsLayoutUtils::ShouldDisableApzForElement(aContent); 1017 } 1018 1019 nsIFrame* DisplayPortUtils::OneStepInAsyncScrollableAncestorChain( 1020 nsIFrame* aFrame) { 1021 // This mirrors one iteration of GetNearestScrollableOrOverflowClipFrame in 1022 // nsLayoutUtils.cpp as called by 1023 // nsLayoutUtils::GetAsyncScrollableAncestorFrame. They should be kept in 1024 // sync. See that function for comments about the structure of this code. 1025 if (aFrame->IsMenuPopupFrame()) { 1026 return nullptr; 1027 } 1028 nsIFrame* anchor = nullptr; 1029 while ((anchor = AnchorPositioningUtils::GetAnchorThatFrameScrollsWith( 1030 aFrame, /* aBuilder */ nullptr))) { 1031 aFrame = anchor; 1032 } 1033 if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Fixed && 1034 nsLayoutUtils::IsReallyFixedPos(aFrame)) { 1035 if (nsIFrame* root = aFrame->PresShell()->GetRootScrollContainerFrame()) { 1036 return root; 1037 } 1038 } 1039 return nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame); 1040 } 1041 1042 FrameAndASRKind DisplayPortUtils::GetASRAncestorFrame( 1043 FrameAndASRKind aFrameAndASRKind, nsDisplayListBuilder* aBuilder) { 1044 MOZ_ASSERT(aBuilder->IsPaintingToWindow()); 1045 // This has different behaviour from 1046 // nsLayoutUtils::GetAsyncScrollableAncestorFrame because the ASR tree is 1047 // different from the "async scrollable ancestor chain" which is mainly used 1048 // for activating display ports. We don't want the 1049 // SCROLLABLE_ALWAYS_MATCH_ROOT behaviour because we only want to match the 1050 // root if it generates an ASR. We don't want the 1051 // SCROLLABLE_FIXEDPOS_FINDS_ROOT behaviour because the ASR tree does not jump 1052 // from fixed pos to root (that behaviour exists so that fixed pos in the root 1053 // document in the process can find some apzc, ASRs have no such need and that 1054 // would be incorrect). This should be kept in sync with 1055 // OneStepInAsyncScrollableAncestorChain, OneStepInASRChain, 1056 // ShouldAsyncScrollWithAnchorNotCached, 1057 // nsLayoutUtils::GetAsyncScrollableAncestorFrame. 1058 1059 for (nsIFrame* f = aFrameAndASRKind.mFrame; f; 1060 f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) { 1061 if (f->IsMenuPopupFrame()) { 1062 break; 1063 } 1064 1065 // Note that the order of checking for a scroll container frame with 1066 // IsMaybeAsynchronouslyScrolled, anchors, and sticky pos is significant. 1067 // The potential ASR of the scroll container frame is the "inner" one, the 1068 // potenial ASR of the sticky is the "outer" one. 1069 if (f != aFrameAndASRKind.mFrame || 1070 aFrameAndASRKind.mASRKind == ActiveScrolledRoot::ASRKind::Scroll) { 1071 if (ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(f)) { 1072 if (scrollContainerFrame->IsMaybeAsynchronouslyScrolled()) { 1073 return {f, ActiveScrolledRoot::ASRKind::Scroll}; 1074 } 1075 } 1076 } 1077 1078 nsIFrame* anchor = nullptr; 1079 // This needs to be a while loop because anchors can chain, and we don't 1080 // want to consider each frame in this loop separately (as a potential 1081 // scrollable ancestor) because they are all equivalent in the scrollable 1082 // ancestor chain: they all scroll together. We are not walking up the async 1083 // scrollable ancestor chain, but rather we are moving sideways. And when 1084 // we exit this loop the last frame might be a sticky asr, after that we 1085 // move up (the next iteration of the outer for loop). 1086 while ((anchor = AnchorPositioningUtils::GetAnchorThatFrameScrollsWith( 1087 f, aBuilder))) { 1088 f = anchor; 1089 } 1090 1091 // The ordering of this sticky check and the above anchor loop is 1092 // significant, even though a frame can't be both sticky pos and anchored 1093 // (because anchoring requires abs pos): if we follow an anchor, the anchor 1094 // could be an active sticky pos, so that would generate an ASR and we want 1095 // to return that rather than do another iteration of the outer for loop 1096 // which moves on to the (crossdoc) parent frame. 1097 if (f->StyleDisplay()->mPosition == StylePositionProperty::Sticky) { 1098 auto* ssc = StickyScrollContainer::GetOrCreateForFrame(f); 1099 if (ssc && ssc->ScrollContainer()->IsMaybeAsynchronouslyScrolled()) { 1100 return {f->FirstContinuation(), ActiveScrolledRoot::ASRKind::Sticky}; 1101 } 1102 } 1103 } 1104 return FrameAndASRKind::default_value(); 1105 } 1106 1107 FrameAndASRKind DisplayPortUtils::OneStepInASRChain( 1108 FrameAndASRKind aFrameAndASRKind, nsDisplayListBuilder* aBuilder, 1109 nsIFrame* aLimitAncestor /* = nullptr */) { 1110 MOZ_ASSERT(aBuilder->IsPaintingToWindow()); 1111 // This has the same basic structure as GetASRAncestorFrame since they are 1112 // meant to be used together. As well as ShouldAsyncScrollWithAnchor, so this 1113 // should be kept in sync with GetASRAncestorFrame and 1114 // ShouldAsyncScrollWithAnchor. See that function for more comments about the 1115 // structure of this code. 1116 if (aFrameAndASRKind.mFrame->IsMenuPopupFrame()) { 1117 return FrameAndASRKind::default_value(); 1118 } 1119 if (aFrameAndASRKind.mASRKind == ActiveScrolledRoot::ASRKind::Scroll) { 1120 nsIFrame* frame = aFrameAndASRKind.mFrame; 1121 nsIFrame* anchor = nullptr; 1122 while ((anchor = AnchorPositioningUtils::GetAnchorThatFrameScrollsWith( 1123 frame, aBuilder))) { 1124 MOZ_ASSERT_IF( 1125 aLimitAncestor, 1126 nsLayoutUtils::IsProperAncestorFrameConsideringContinuations( 1127 aLimitAncestor, anchor)); 1128 frame = anchor; 1129 } 1130 return {frame, ActiveScrolledRoot::ASRKind::Sticky}; 1131 } 1132 nsIFrame* parent = 1133 nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrameAndASRKind.mFrame); 1134 if (aLimitAncestor && parent && 1135 (parent == aLimitAncestor || 1136 parent->FirstContinuation() == aLimitAncestor->FirstContinuation())) { 1137 return FrameAndASRKind::default_value(); 1138 } 1139 return {parent, ActiveScrolledRoot::ASRKind::Scroll}; 1140 } 1141 1142 // This first checks if aFrame is a scroll frame, if so it then tries to 1143 // activate it. Then this function returns true if aFrame generates a scroll ASR 1144 // (ie its an active scroll frame). 1145 static bool ActivatePotentialScrollASR(nsIFrame* aFrame, 1146 nsDisplayListBuilder* aBuilder) { 1147 ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(aFrame); 1148 if (!scrollContainerFrame) { 1149 return false; 1150 } 1151 return scrollContainerFrame->DecideScrollableLayerEnsureDisplayport(aBuilder); 1152 } 1153 1154 // This first checks if aFrame is sticky pos, if so it then tries to activate 1155 // the associate scroll frame. Then this function returns true if aFrame 1156 // generates a sticky ASR (ie its sticky pos and its associated scroll frame is 1157 // active). 1158 static bool ActivatePotentialStickyASR(nsIFrame* aFrame, 1159 nsDisplayListBuilder* aBuilder) { 1160 if (aFrame->StyleDisplay()->mPosition != StylePositionProperty::Sticky) { 1161 return false; 1162 } 1163 auto* ssc = StickyScrollContainer::GetOrCreateForFrame(aFrame); 1164 if (!ssc) { 1165 return false; 1166 } 1167 return ssc->ScrollContainer()->DecideScrollableLayerEnsureDisplayport( 1168 aBuilder); 1169 } 1170 1171 const ActiveScrolledRoot* DisplayPortUtils::ActivateDisplayportOnASRAncestors( 1172 nsIFrame* aAnchor, nsIFrame* aLimitAncestor, 1173 const ActiveScrolledRoot* aASRofLimitAncestor, 1174 nsDisplayListBuilder* aBuilder) { 1175 MOZ_ASSERT(ScrollContainerFrame::ShouldActivateAllScrollFrames( 1176 aBuilder, aLimitAncestor)); 1177 1178 MOZ_ASSERT( 1179 (aASRofLimitAncestor ? FrameAndASRKind{aASRofLimitAncestor->mFrame, 1180 aASRofLimitAncestor->mKind} 1181 : FrameAndASRKind::default_value()) == 1182 GetASRAncestorFrame({aLimitAncestor, ActiveScrolledRoot::ASRKind::Scroll}, 1183 aBuilder)); 1184 1185 MOZ_ASSERT(nsLayoutUtils::IsProperAncestorFrameConsideringContinuations( 1186 aLimitAncestor, aAnchor)); 1187 1188 AutoTArray<FrameAndASRKind, 4> ASRframes; 1189 1190 // The passed in frame is the anchor, if it is a scroll frame we do not scroll 1191 // with that scroll frame (we are "outside" of it) but if it is sticky pos 1192 // then we do move with the sticky ASR, so we init our iterator at 1193 // ASRKind::Scroll indicating we have completed ASRKind::Scroll for aAnchor. 1194 // We call OneStepInASRChain once before the loop, this moves us to the end of 1195 // the anchor chain if aAnchor is also anchored, and flips the ASRKind to 1196 // sticky to give us our first FrameAndASRKind to consider. (Note that if the 1197 // original anchored frame was passed in to this function then calling 1198 // OneStepInASRChain on the (first) anchor would be equivalent to calling 1199 // OneStepInASRChain on the anchored frame, but this saves 1200 // GetAnchorThatFrameScrollsWith call that we've already done.) 1201 FrameAndASRKind frameAndASRKind{aAnchor, ActiveScrolledRoot::ASRKind::Scroll}; 1202 frameAndASRKind = 1203 OneStepInASRChain(frameAndASRKind, aBuilder, aLimitAncestor); 1204 while (frameAndASRKind.mFrame && frameAndASRKind.mFrame != aLimitAncestor && 1205 (!aLimitAncestor || frameAndASRKind.mFrame->FirstContinuation() != 1206 aLimitAncestor->FirstContinuation())) { 1207 // We check if each frame encountered generates an ASR. It can either 1208 // generate a scroll asr or a sticky asr, or both! If it generates both then 1209 // the sticky asr is the outer (parent) asr. So we check for scroll ASRs 1210 // first. 1211 1212 // We intentionally call this on all scroll frames encountered, not just the 1213 // ones that WantAsyncScroll. This is because scroll frames with 1214 // WantAsyncScroll == false can have a display port (say if they had 1215 // non-zero scroll range and had a display port but then their scroll range 1216 // shrank to zero then the displayport would still stick around), hence 1217 // mWillBuildScrollableLayer would be true on them and we need to make sure 1218 // mWillBuildScrollableLayer is up to date (if the scroll frame was 1219 // temporarily inside a view transition mWillBuildScrollableLayer would 1220 // temporarily get set to false). 1221 1222 // In this loop we are looking for any scroll frame that will generate an 1223 // ASR. This corresponds to scroll frames with mWillBuildScrollableLayer == 1224 // true. This is different from scroll frames that return true from 1225 // WantAsyncScroll (both because of what was explained above and because not 1226 // every scroll frame that WantAsyncScroll will have a displayport), and 1227 // hence it's also different from what GetAsyncScrollableAncestorFrame will 1228 // return. 1229 1230 switch (frameAndASRKind.mASRKind) { 1231 case ActiveScrolledRoot::ASRKind::Scroll: 1232 if (ActivatePotentialScrollASR(frameAndASRKind.mFrame, aBuilder)) { 1233 ASRframes.EmplaceBack(frameAndASRKind); 1234 } 1235 break; 1236 1237 case ActiveScrolledRoot::ASRKind::Sticky: 1238 if (ActivatePotentialStickyASR(frameAndASRKind.mFrame, aBuilder)) { 1239 ASRframes.EmplaceBack(frameAndASRKind); 1240 } 1241 break; 1242 } 1243 1244 frameAndASRKind = 1245 OneStepInASRChain(frameAndASRKind, aBuilder, aLimitAncestor); 1246 } 1247 1248 const ActiveScrolledRoot* asr = aASRofLimitAncestor; 1249 1250 // Iterate array in reverse order (top down in the frame/asr tree) creating 1251 // the asr structs. 1252 for (auto asrFrame : Reversed(ASRframes)) { 1253 MOZ_ASSERT(nsLayoutUtils::IsProperAncestorFrameConsideringContinuations( 1254 aLimitAncestor, asrFrame.mFrame)); 1255 1256 MOZ_ASSERT( 1257 (asr ? FrameAndASRKind{asr->mFrame, asr->mKind} 1258 : FrameAndASRKind::default_value()) == 1259 GetASRAncestorFrame(OneStepInASRChain(asrFrame, aBuilder), aBuilder)); 1260 1261 asr = (asrFrame.mASRKind == ActiveScrolledRoot::ASRKind::Scroll) 1262 ? aBuilder->GetOrCreateActiveScrolledRoot( 1263 asr, static_cast<ScrollContainerFrame*>( 1264 do_QueryFrame(asrFrame.mFrame))) 1265 : aBuilder->GetOrCreateActiveScrolledRootForSticky( 1266 asr, asrFrame.mFrame); 1267 } 1268 return asr; 1269 } 1270 1271 static bool CheckAxes(ScrollContainerFrame* aScrollFrame, PhysicalAxes aAxes) { 1272 if (aAxes == kPhysicalAxesBoth) { 1273 return true; 1274 } 1275 nsRect range = aScrollFrame->GetScrollRangeForUserInputEvents(); 1276 if (aAxes.contains(PhysicalAxis::Vertical)) { 1277 MOZ_ASSERT(!aAxes.contains(PhysicalAxis::Horizontal)); 1278 if (range.width > 0) { 1279 // compensating in vertical axis only, but scroll frame can scroll horz 1280 return false; 1281 } 1282 } 1283 if (aAxes.contains(PhysicalAxis::Horizontal)) { 1284 MOZ_ASSERT(!aAxes.contains(PhysicalAxis::Vertical)); 1285 if (range.height > 0) { 1286 // compensating in horizontal axis only, but scroll frame can scroll vert 1287 return false; 1288 } 1289 } 1290 return true; 1291 } 1292 1293 static bool CheckForScrollFrameAndAxes(nsIFrame* aFrame, PhysicalAxes aAxes, 1294 bool* aOutSawPotentialASR) { 1295 ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(aFrame); 1296 if (!scrollContainerFrame) { 1297 return true; 1298 } 1299 *aOutSawPotentialASR = true; 1300 return CheckAxes(scrollContainerFrame, aAxes); 1301 } 1302 1303 // true is good 1304 static bool CheckForStickyAndAxes(nsIFrame* aFrame, PhysicalAxes aAxes, 1305 bool* aOutSawPotentialASR) { 1306 if (aFrame->StyleDisplay()->mPosition != StylePositionProperty::Sticky) { 1307 return true; 1308 } 1309 auto* ssc = StickyScrollContainer::GetOrCreateForFrame(aFrame); 1310 if (!ssc) { 1311 return true; 1312 } 1313 *aOutSawPotentialASR = true; 1314 return CheckAxes(ssc->ScrollContainer(), aAxes); 1315 } 1316 1317 static bool ShouldAsyncScrollWithAnchorNotCached(nsIFrame* aFrame, 1318 nsIFrame* aAnchor, 1319 nsDisplayListBuilder* aBuilder, 1320 PhysicalAxes aAxes, 1321 bool* aReportToDoc) { 1322 // This has the same basic structure as GetASRAncestorFrame and 1323 // OneStepInASRChain. They should all be kept in sync. 1324 if (aFrame->IsMenuPopupFrame()) { 1325 *aReportToDoc = false; 1326 return false; 1327 } 1328 *aReportToDoc = true; 1329 nsIFrame* limitAncestor = aFrame->GetParent(); 1330 MOZ_ASSERT(limitAncestor); 1331 // Start from aAnchor (not aFrame) so we don't infinite loop. 1332 nsIFrame* frame = aAnchor; 1333 bool firstIteration = true; 1334 // We want to detect if we would assign an ASR to the anchored frame that is 1335 // subject to a transform that the anchored frame is not actually under 1336 // because doing so would give it the same spatial node and webrender would 1337 // incorrectly render it with that transform. So we track when we first see a 1338 // potential ASR and then start checking for transforms. 1339 bool sawPotentialASR = false; 1340 while (frame && !frame->IsMenuPopupFrame() && frame != limitAncestor && 1341 (frame->FirstContinuation() != limitAncestor->FirstContinuation())) { 1342 // Note that we purposely check all scroll frames in this loop because we 1343 // might not have activated scroll frames yet. 1344 1345 // On the first iteration we don't check the scroll frame because we don't 1346 // scroll with the contents of aAnchor. 1347 if (!firstIteration && 1348 !CheckForScrollFrameAndAxes(frame, aAxes, &sawPotentialASR)) { 1349 return false; 1350 } 1351 1352 // On the first iteration it's okay if the anchor is transformed, we won't 1353 // get rendered as transformed if we take it's ASR (even if it's sticky 1354 // pos). 1355 if (sawPotentialASR && !firstIteration && frame->IsTransformed()) { 1356 return false; 1357 } 1358 1359 nsIFrame* anchor = nullptr; 1360 while ((anchor = AnchorPositioningUtils::GetAnchorThatFrameScrollsWith( 1361 frame, aBuilder))) { 1362 MOZ_ASSERT(nsLayoutUtils::IsProperAncestorFrameConsideringContinuations( 1363 limitAncestor, anchor)); 1364 frame = anchor; 1365 // Any of these anchor chain frames are okay if they are transformed, they 1366 // won't affect our ASR/spatial node (even the last one, even if it's 1367 // sticky). 1368 } 1369 1370 if (!CheckForStickyAndAxes(frame, aAxes, &sawPotentialASR)) { 1371 return false; 1372 } 1373 // If sawPotentialASR flipped from false to true in the 1374 // CheckForStickyAndAxes call we don't want to check if frame is transformed 1375 // because its transform will not be applied to items with an ASR equal to 1376 // {frame, sticky} because the transform is inside the sticky. 1377 1378 frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame); 1379 firstIteration = false; 1380 } 1381 return true; 1382 } 1383 1384 bool DisplayPortUtils::ShouldAsyncScrollWithAnchor( 1385 nsIFrame* aFrame, nsIFrame* aAnchor, nsDisplayListBuilder* aBuilder, 1386 PhysicalAxes aAxes) { 1387 // Note that this does not recurse because we are passing aBuilder = nullptr, 1388 // but we have to skip the asserts related to aBuilder. 1389 MOZ_ASSERT(aAnchor == 1390 AnchorPositioningUtils::GetAnchorThatFrameScrollsWith( 1391 aFrame, /* aBuilder */ nullptr, /* aSkipAsserts */ true)); 1392 MOZ_ASSERT(aFrame->IsAbsolutelyPositioned()); 1393 MOZ_ASSERT(aBuilder->IsPaintingToWindow()); 1394 MOZ_ASSERT(!aAxes.isEmpty()); 1395 1396 // ShouldAsyncScrollWithAnchorNotCached can call recursively and modify 1397 // AsyncScrollsWithAnchorHashmap, so we have to be careful to not hold an 1398 // entry in the hashtable during a call to 1399 // ShouldAsyncScrollWithAnchorNotCached. Unfortunately this means that we have 1400 // to do two hashtable lookups if the frame is not present in the hashtable. 1401 if (auto entry = aBuilder->AsyncScrollsWithAnchorHashmap().Lookup(aFrame)) { 1402 return *entry; 1403 } 1404 bool reportToDoc = false; 1405 bool shouldAsyncScrollWithAnchor = ShouldAsyncScrollWithAnchorNotCached( 1406 aFrame, aAnchor, aBuilder, aAxes, &reportToDoc); 1407 { 1408 bool& entry = 1409 aBuilder->AsyncScrollsWithAnchorHashmap().LookupOrInsert(aFrame); 1410 entry = shouldAsyncScrollWithAnchor; 1411 } 1412 if (reportToDoc) { 1413 auto* pc = aFrame->PresContext(); 1414 pc->Document()->ReportHasScrollLinkedEffect( 1415 pc->RefreshDriver()->MostRecentRefresh(), 1416 dom::Document::ReportToConsole::No); 1417 } 1418 1419 return shouldAsyncScrollWithAnchor; 1420 } 1421 1422 } // namespace mozilla