tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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