tor-browser

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

ClipManager.cpp (30796B)


      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 "mozilla/layers/ClipManager.h"
      8 
      9 #include "DisplayItemClipChain.h"
     10 #include "FrameMetrics.h"
     11 #include "mozilla/ScrollContainerFrame.h"
     12 #include "mozilla/dom/Document.h"
     13 #include "mozilla/layers/StackingContextHelper.h"
     14 #include "mozilla/layers/WebRenderLayerManager.h"
     15 #include "mozilla/StaticPrefs_apz.h"
     16 #include "mozilla/webrender/WebRenderAPI.h"
     17 #include "nsDisplayList.h"
     18 #include "nsLayoutUtils.h"
     19 #include "nsRefreshDriver.h"
     20 #include "nsStyleStructInlines.h"
     21 #include "UnitTransforms.h"
     22 
     23 static mozilla::LazyLogModule sClipLog("wr.clip");
     24 #define CLIP_LOG(...) MOZ_LOG(sClipLog, LogLevel::Debug, (__VA_ARGS__))
     25 
     26 namespace mozilla::layers {
     27 
     28 ClipManager::ClipManager() : mManager(nullptr), mBuilder(nullptr) {}
     29 
     30 void ClipManager::BeginBuild(WebRenderLayerManager* aManager,
     31                             wr::DisplayListBuilder& aBuilder) {
     32  MOZ_ASSERT(!mManager);
     33  mManager = aManager;
     34  MOZ_ASSERT(!mBuilder);
     35  mBuilder = &aBuilder;
     36  MOZ_ASSERT(mCacheStack.empty());
     37  mCacheStack.emplace();
     38  MOZ_ASSERT(mASROverride.empty());
     39  MOZ_ASSERT(mItemClipStack.empty());
     40 }
     41 
     42 void ClipManager::EndBuild() {
     43  mBuilder = nullptr;
     44  mManager = nullptr;
     45  mCacheStack.pop();
     46  MOZ_ASSERT(mCacheStack.empty());
     47  MOZ_ASSERT(mASROverride.empty());
     48  MOZ_ASSERT(mItemClipStack.empty());
     49 }
     50 
     51 void ClipManager::BeginList(const StackingContextHelper& aStackingContext) {
     52  CLIP_LOG("begin list %p affects = %d, ref-frame = %d\n", &aStackingContext,
     53           aStackingContext.AffectsClipPositioning(),
     54           aStackingContext.ReferenceFrameId().isSome());
     55 
     56  ItemClips clips(nullptr, nullptr, 0, false);
     57  if (!mItemClipStack.empty()) {
     58    clips = mItemClipStack.top();
     59  }
     60 
     61  if (aStackingContext.AffectsClipPositioning()) {
     62    if (auto referenceFrameId = aStackingContext.ReferenceFrameId()) {
     63      PushOverrideForASR(clips.mASR, *referenceFrameId);
     64      clips.mScrollId = *referenceFrameId;
     65    } else {
     66      // Start a new cache
     67      mCacheStack.emplace();
     68    }
     69    if (clips.mChain) {
     70      clips.mClipChainId =
     71          DefineClipChain(clips.mChain, clips.mAppUnitsPerDevPixel);
     72    }
     73  }
     74 
     75  CLIP_LOG("  push: clip: %p, asr: %p, scroll =%" PRIuPTR ", clip =%" PRIu64
     76           "\n",
     77           clips.mChain, clips.mASR, clips.mScrollId.id,
     78           clips.mClipChainId.valueOr(wr::WrClipChainId{0}).id);
     79 
     80  mItemClipStack.push(clips);
     81 }
     82 
     83 void ClipManager::EndList(const StackingContextHelper& aStackingContext) {
     84  MOZ_ASSERT(!mItemClipStack.empty());
     85 
     86  CLIP_LOG("end list %p\n", &aStackingContext);
     87 
     88  mBuilder->SetClipChainLeaf(Nothing());
     89  mItemClipStack.pop();
     90 
     91  if (aStackingContext.AffectsClipPositioning()) {
     92    if (aStackingContext.ReferenceFrameId()) {
     93      PopOverrideForASR(mItemClipStack.empty() ? nullptr
     94                                               : mItemClipStack.top().mASR);
     95    } else {
     96      MOZ_ASSERT(!mCacheStack.empty());
     97      mCacheStack.pop();
     98    }
     99  }
    100 }
    101 
    102 void ClipManager::PushOverrideForASR(const ActiveScrolledRoot* aASR,
    103                                     const wr::WrSpatialId& aSpatialId) {
    104  wr::WrSpatialId space = GetSpatialId(aASR);
    105 
    106  CLIP_LOG("Pushing %p override %zu -> %zu\n", aASR, space.id, aSpatialId.id);
    107  auto it = mASROverride.insert({space, std::stack<wr::WrSpatialId>()});
    108  it.first->second.push(aSpatialId);
    109 
    110  // Start a new cache
    111  mCacheStack.emplace();
    112 
    113  // Fix up our cached item clip if needed.
    114  if (!mItemClipStack.empty()) {
    115    auto& top = mItemClipStack.top();
    116    if (top.mASR == aASR) {
    117      top.mScrollId = aSpatialId;
    118      if (top.mChain) {
    119        top.mClipChainId =
    120            DefineClipChain(top.mChain, top.mAppUnitsPerDevPixel);
    121      }
    122    }
    123  }
    124 }
    125 
    126 void ClipManager::PopOverrideForASR(const ActiveScrolledRoot* aASR) {
    127  MOZ_ASSERT(!mCacheStack.empty());
    128  mCacheStack.pop();
    129 
    130  wr::WrSpatialId space = GetSpatialId(aASR);
    131  auto it = mASROverride.find(space);
    132  if (it == mASROverride.end()) {
    133    MOZ_ASSERT_UNREACHABLE("Push/PopOverrideForASR should be balanced");
    134  } else {
    135    CLIP_LOG("Popping %p override %zu -> %zu\n", aASR, space.id,
    136             it->second.top().id);
    137    it->second.pop();
    138  }
    139 
    140  if (!mItemClipStack.empty()) {
    141    auto& top = mItemClipStack.top();
    142    if (top.mASR == aASR) {
    143      top.mScrollId = (it == mASROverride.end() || it->second.empty())
    144                          ? space
    145                          : it->second.top();
    146      if (top.mChain) {
    147        top.mClipChainId =
    148            DefineClipChain(top.mChain, top.mAppUnitsPerDevPixel);
    149      }
    150    }
    151  }
    152 
    153  if (it != mASROverride.end() && it->second.empty()) {
    154    mASROverride.erase(it);
    155  }
    156 }
    157 
    158 wr::WrSpatialId ClipManager::SpatialIdAfterOverride(
    159    const wr::WrSpatialId& aSpatialId) {
    160  auto it = mASROverride.find(aSpatialId);
    161  if (it == mASROverride.end()) {
    162    return aSpatialId;
    163  }
    164  MOZ_ASSERT(!it->second.empty());
    165  CLIP_LOG("Overriding %zu with %zu\n", aSpatialId.id, it->second.top().id);
    166  return it->second.top();
    167 }
    168 
    169 wr::WrSpaceAndClipChain ClipManager::SwitchItem(nsDisplayListBuilder* aBuilder,
    170                                                nsDisplayItem* aItem) {
    171  const DisplayItemClipChain* clip = aItem->GetClipChain();
    172  const DisplayItemClipChain* inheritedClipChain =
    173      mBuilder->GetInheritedClipChain();
    174  if (inheritedClipChain && inheritedClipChain != clip) {
    175    if (!clip) {
    176      clip = mBuilder->GetInheritedClipChain();
    177    } else {
    178      clip = aBuilder->CreateClipChainIntersection(
    179          mBuilder->GetInheritedClipChain(), clip);
    180    }
    181  }
    182  const ActiveScrolledRoot* asr = aItem->GetActiveScrolledRoot();
    183  DisplayItemType type = aItem->GetType();
    184  const ActiveScrolledRoot* stickyAsr = nullptr;
    185  if (type == DisplayItemType::TYPE_STICKY_POSITION) {
    186    // For sticky position items, the ASR is computed differently depending on
    187    // whether the item has a fixed descendant or not. But for WebRender
    188    // purposes we always want to use the ASR that would have been used if it
    189    // didn't have fixed descendants, which is stored as the "container ASR" on
    190    // the sticky item.
    191    auto* sticky = static_cast<nsDisplayStickyPosition*>(aItem);
    192    asr = sticky->GetContainerASR();
    193    stickyAsr = ActiveScrolledRoot::GetStickyASRFromFrame(sticky->Frame());
    194    MOZ_ASSERT(stickyAsr);
    195  }
    196 
    197  CLIP_LOG("processing item %p (%s) asr %p clip %p, inherited = %p\n", aItem,
    198           DisplayItemTypeName(aItem->GetType()), asr, clip,
    199           inheritedClipChain);
    200 
    201  // In most cases we can combine the leaf of the clip chain with the clip rect
    202  // of the display item. This reduces the number of clip items, which avoids
    203  // some overhead further down the pipeline.
    204  // Container display items are not currently supported because the clip
    205  // rect of a stacking context is not handled the same as normal display
    206  // items.
    207  const bool separateLeaf = clip && clip->mASR == asr &&
    208                            clip->mClip.GetRoundedRectCount() == 0 &&
    209                            !aItem->GetChildren();
    210 
    211  // Zoom display items report their bounds etc using the parent document's
    212  // APD because zoom items act as a conversion layer between the two different
    213  // APDs.
    214  const int32_t auPerDevPixel = [&] {
    215    if (type == DisplayItemType::TYPE_ZOOM) {
    216      return static_cast<nsDisplayZoom*>(aItem)->GetParentAppUnitsPerDevPixel();
    217    }
    218    return aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
    219  }();
    220 
    221  ItemClips clips(asr, clip, auPerDevPixel, separateLeaf);
    222  MOZ_ASSERT(!mItemClipStack.empty());
    223  if (clips.HasSameInputs(mItemClipStack.top())) {
    224    // Early-exit because if the clips are the same as aItem's previous sibling,
    225    // then we don't need to do do the work of popping the old stuff and then
    226    // pushing it right back on for the new item. Note that if aItem doesn't
    227    // have a previous sibling, that means BeginList would have been called
    228    // just before this, which will have pushed a ItemClips(nullptr, nullptr)
    229    // onto mItemClipStack, so the HasSameInputs check should return false.
    230    CLIP_LOG("\tearly-exit for %p\n", aItem);
    231    return mItemClipStack.top().GetSpaceAndClipChain();
    232  }
    233 
    234  // Pop aItem's previous sibling's stuff from mBuilder in preparation for
    235  // pushing aItem's stuff.
    236  mItemClipStack.pop();
    237 
    238  // If the leaf of the clip chain is going to be merged with the display item's
    239  // clip rect, then we should create a clip chain id from the leaf's parent.
    240  if (separateLeaf) {
    241    CLIP_LOG("\tseparate leaf detected, ignoring the last clip\n");
    242    clip = clip->mParent;
    243  }
    244 
    245  // There are up to three ASR chains here that we need to be fully defined:
    246  //  1. The ASR chain pointed to by |asr|
    247  //  2. The ASR chain pointed to by clip->mASR
    248  //  3. For a sticky item, the ASR chain pointed to by the item's sticky ASR.
    249  //     This one is needed so that when we create WebRender commands for the
    250  //     sticky item, we can give WebRender accurate information about the
    251  //     spatial node we're in.
    252  // These chains will often be the same, or one will include the other,
    253  // but we can't rely on that always being the case, so we make a
    254  // DefineSpatialNodes call on all three. These calls will recursively
    255  // define all the ASRs that we care about for this item, but will not
    256  // actually push anything onto the WR stack.
    257  (void)DefineSpatialNodes(aBuilder, asr, aItem);
    258  if (clip && clip->mASR != asr) {
    259    (void)DefineSpatialNodes(aBuilder, clip->mASR, aItem);
    260  }
    261  if (stickyAsr && stickyAsr != asr) {
    262    (void)DefineSpatialNodes(aBuilder, stickyAsr, aItem);
    263  }
    264 
    265  // Define all the clips in the item's clip chain, and obtain a clip chain id
    266  // for it.
    267  clips.mClipChainId = DefineClipChain(clip, auPerDevPixel);
    268 
    269  wr::WrSpatialId space = GetSpatialId(asr);
    270  clips.mScrollId = SpatialIdAfterOverride(space);
    271  CLIP_LOG("\tassigning %d -> %d\n", (int)space.id, (int)clips.mScrollId.id);
    272 
    273  // Now that we have the scroll id and a clip id for the item, push it onto
    274  // the WR stack.
    275  clips.UpdateSeparateLeaf(*mBuilder, auPerDevPixel);
    276  auto spaceAndClipChain = clips.GetSpaceAndClipChain();
    277 
    278  CLIP_LOG("  push: clip: %p, asr: %p, scroll = %" PRIuPTR ", clip = %" PRIu64
    279           "\n",
    280           clips.mChain, clips.mASR, clips.mScrollId.id,
    281           clips.mClipChainId.valueOr(wr::WrClipChainId{0}).id);
    282 
    283  mItemClipStack.push(clips);
    284 
    285  CLIP_LOG("done setup for %p\n", aItem);
    286  return spaceAndClipChain;
    287 }
    288 
    289 wr::WrSpatialId ClipManager::GetSpatialId(const ActiveScrolledRoot* aASR) {
    290  for (const ActiveScrolledRoot* asr = aASR; asr; asr = asr->mParent) {
    291    Maybe<wr::WrSpatialId> space = Nothing();
    292    if (asr->mKind == ActiveScrolledRoot::ASRKind::Sticky) {
    293      space = mBuilder->GetSpatialIdForDefinedStickyLayer(asr);
    294    } else {
    295      space = mBuilder->GetScrollIdForDefinedScrollLayer(asr->GetViewId());
    296    }
    297    if (space) {
    298      return *space;
    299    }
    300 
    301    // If this ASR doesn't have a scroll ID, then we should check its ancestor.
    302    // There may not be one defined because the ASR may not be scrollable or we
    303    // failed to get the scroll metadata.
    304  }
    305 
    306  Maybe<wr::WrSpatialId> space = mBuilder->GetScrollIdForDefinedScrollLayer(
    307      ScrollableLayerGuid::NULL_SCROLL_ID);
    308  MOZ_ASSERT(space.isSome());
    309  return *space;
    310 }
    311 
    312 StickyScrollContainer* ClipManager::GetStickyScrollContainer(
    313    const ActiveScrolledRoot* aASR) {
    314  MOZ_ASSERT(aASR->mKind == ActiveScrolledRoot::ASRKind::Sticky);
    315  StickyScrollContainer* stickyScrollContainer =
    316      StickyScrollContainer::GetOrCreateForFrame(aASR->mFrame);
    317  if (stickyScrollContainer) {
    318    // If there's no ASR for the scrollframe that this sticky item is attached
    319    // to, then don't create a WR sticky item for it either. Trying to do so
    320    // will end in sadness because WR will interpret some coordinates as
    321    // relative to the nearest enclosing scrollframe, which will correspond
    322    // to the nearest ancestor ASR on the gecko side. That ASR will not be the
    323    // same as the scrollframe this sticky item is actually supposed to be
    324    // attached to, thus the sadness.
    325    // Not sending WR the sticky item is ok, because the enclosing scrollframe
    326    // will never be asynchronously scrolled. Instead we will always position
    327    // the sticky items correctly on the gecko side and WR will never need to
    328    // adjust their position itself.
    329    if (!stickyScrollContainer->ScrollContainer()
    330             ->IsMaybeAsynchronouslyScrolled()) {
    331      stickyScrollContainer = nullptr;
    332    }
    333  }
    334  return stickyScrollContainer;
    335 }
    336 
    337 // Returns the smallest distance from "0" to the range [min, max] where
    338 // min <= max. Despite the name, the return value is actually a 1-D vector,
    339 // and so may be negative if max < 0.
    340 static nscoord DistanceToRange(nscoord min, nscoord max) {
    341  MOZ_ASSERT(min <= max);
    342  if (max < 0) {
    343    return max;
    344  }
    345  if (min > 0) {
    346    return min;
    347  }
    348  MOZ_ASSERT(min <= 0 && max >= 0);
    349  return 0;
    350 }
    351 
    352 // Returns the magnitude of the part of the range [min, max] that is greater
    353 // than zero. The return value is always non-negative.
    354 static nscoord PositivePart(nscoord min, nscoord max) {
    355  MOZ_ASSERT(min <= max);
    356  if (min >= 0) {
    357    return max - min;
    358  }
    359  if (max > 0) {
    360    return max;
    361  }
    362  return 0;
    363 }
    364 
    365 // Returns the magnitude of the part of the range [min, max] that is less
    366 // than zero. The return value is always non-negative.
    367 static nscoord NegativePart(nscoord min, nscoord max) {
    368  MOZ_ASSERT(min <= max);
    369  if (max <= 0) {
    370    return max - min;
    371  }
    372  if (min < 0) {
    373    return 0 - min;
    374  }
    375  return 0;
    376 }
    377 
    378 Maybe<wr::WrSpatialId> ClipManager::DefineStickyNode(
    379    nsDisplayListBuilder* aBuilder, Maybe<wr::WrSpatialId> aParentSpatialId,
    380    const ActiveScrolledRoot* aASR, nsDisplayItem* aItem) {
    381  nsIFrame* stickyFrame = aASR->mFrame;
    382 
    383  if (Maybe<wr::WrSpatialId> space =
    384          mBuilder->GetSpatialIdForDefinedStickyLayer(aASR)) {
    385    return space;
    386  }
    387 
    388  StickyScrollContainer* stickyScrollContainer = GetStickyScrollContainer(aASR);
    389  if (!stickyScrollContainer) {
    390    // This may indicated a sticky item that does not need a webrender spatial
    391    // node. See the comment in GetStickyScrollContainer for details.
    392    return Nothing();
    393  }
    394 
    395  // Do not create a spatial node for sticky items with mShouldFlatten=true.
    396  // These are inside inactive scroll frames and so cannot move asynchronously.
    397  if (stickyScrollContainer->ShouldFlattenAway()) {
    398    return Nothing();
    399  }
    400 
    401  float auPerDevPixel = stickyFrame->PresContext()->AppUnitsPerDevPixel();
    402 
    403  nsRect itemBounds;
    404 
    405  // Make both itemBounds and scrollPort be relative to the reference frame
    406  // of the sticky frame's parent.
    407  nsRect scrollPort =
    408      stickyScrollContainer->ScrollContainer()->GetScrollPortRect();
    409  const nsIFrame* referenceFrame =
    410      aBuilder->FindReferenceFrameFor(stickyFrame->GetParent());
    411  nsRect transformedBounds = stickyFrame->GetRectRelativeToSelf();
    412  DebugOnly transformResult = nsLayoutUtils::TransformRect(
    413      stickyFrame, referenceFrame, transformedBounds);
    414  MOZ_ASSERT(transformResult == nsLayoutUtils::TRANSFORM_SUCCEEDED);
    415  itemBounds = transformedBounds;
    416  scrollPort = scrollPort +
    417               stickyScrollContainer->ScrollContainer()->GetOffsetToCrossDoc(
    418                   referenceFrame);
    419 
    420  Maybe<float> topMargin;
    421  Maybe<float> rightMargin;
    422  Maybe<float> bottomMargin;
    423  Maybe<float> leftMargin;
    424  wr::StickyOffsetBounds vBounds = {0.0, 0.0};
    425  wr::StickyOffsetBounds hBounds = {0.0, 0.0};
    426  nsPoint appliedOffset;
    427 
    428  nsRectAbsolute outer;
    429  nsRectAbsolute inner;
    430  stickyScrollContainer->GetScrollRanges(stickyFrame, &outer, &inner);
    431 
    432  // The following computations make more sense upon understanding the
    433  // semantics of "inner" and "outer", which is explained in the comment on
    434  // SetStickyPositionData in Layers.h.
    435 
    436  if (outer.YMost() != inner.YMost()) {
    437    // Question: How far will itemBounds.y be from the top of the scrollport
    438    // when we have scrolled from the current scroll position of "0" to
    439    // reach the range [inner.YMost(), outer.YMost()] where the item gets
    440    // stuck?
    441    // Answer: the current distance is "itemBounds.y - scrollPort.y". That
    442    // needs to be adjusted by the distance to the range, less any other
    443    // sticky ranges that fall between 0 and the range. If the distance is
    444    // negative (i.e. inner.YMost() <= outer.YMost() < 0) then we would be
    445    // scrolling upwards (decreasing scroll offset) to reach that range,
    446    // which would increase itemBounds.y and make it farther away from the
    447    // top of the scrollport. So in that case the adjustment is -distance.
    448    // If the distance is positive (0 < inner.YMost() <= outer.YMost()) then
    449    // we would be scrolling downwards, itemBounds.y would decrease, and we
    450    // again need to adjust by -distance. If we are already in the range
    451    // then no adjustment is needed and distance is 0 so again using
    452    // -distance works. If the distance is positive, and the item has both
    453    // top and bottom sticky ranges, then the bottom sticky range may fall
    454    // (entirely[1] or partly[2]) between the current scroll position.
    455    // [1]: 0 <= outer.Y() <= inner.Y() < inner.YMost() <= outer.YMost()
    456    // [2]: outer.Y() < 0 <= inner.Y() < inner.YMost() <= outer.YMost()
    457    // In these cases, the item doesn't actually move for that part of the
    458    // distance, so we need to subtract out that bit, which can be computed
    459    // as the positive portion of the range [outer.Y(), inner.Y()].
    460    nscoord distance = DistanceToRange(inner.YMost(), outer.YMost());
    461    if (distance > 0) {
    462      distance -= PositivePart(outer.Y(), inner.Y());
    463    }
    464    topMargin = Some(NSAppUnitsToFloatPixels(
    465        itemBounds.y - scrollPort.y - distance, auPerDevPixel));
    466    // Question: What is the maximum positive ("downward") offset that WR
    467    // will have to apply to this item in order to prevent the item from
    468    // visually moving?
    469    // Answer: Since the item is "sticky" in the range [inner.YMost(),
    470    // outer.YMost()], the maximum offset will be the size of the range, which
    471    // is outer.YMost() - inner.YMost().
    472    vBounds.max =
    473        NSAppUnitsToFloatPixels(outer.YMost() - inner.YMost(), auPerDevPixel);
    474    // Question: how much of an offset has layout already applied to the item?
    475    // Answer: if we are
    476    // (a) inside the sticky range (inner.YMost() < 0 <= outer.YMost()), or
    477    // (b) past the sticky range (inner.YMost() < outer.YMost() < 0)
    478    // then layout has already applied some offset to the position of the
    479    // item. The amount of the adjustment is |0 - inner.YMost()| in case (a)
    480    // and |outer.YMost() - inner.YMost()| in case (b).
    481    if (inner.YMost() < 0) {
    482      appliedOffset.y = std::min(0, outer.YMost()) - inner.YMost();
    483      MOZ_ASSERT(appliedOffset.y > 0);
    484    }
    485  }
    486  if (outer.Y() != inner.Y()) {
    487    // Similar logic as in the previous section, but this time we care about
    488    // the distance from itemBounds.YMost() to scrollPort.YMost().
    489    nscoord distance = DistanceToRange(outer.Y(), inner.Y());
    490    if (distance < 0) {
    491      distance += NegativePart(inner.YMost(), outer.YMost());
    492    }
    493    bottomMargin = Some(NSAppUnitsToFloatPixels(
    494        scrollPort.YMost() - itemBounds.YMost() + distance, auPerDevPixel));
    495    // And here WR will be moving the item upwards rather than downwards so
    496    // again things are inverted from the previous block.
    497    vBounds.min = NSAppUnitsToFloatPixels(outer.Y() - inner.Y(), auPerDevPixel);
    498    // We can't have appliedOffset be both positive and negative, and the top
    499    // adjustment takes priority. So here we only update appliedOffset.y if
    500    // it wasn't set by the top-sticky case above.
    501    if (appliedOffset.y == 0 && inner.Y() > 0) {
    502      appliedOffset.y = std::max(0, outer.Y()) - inner.Y();
    503      MOZ_ASSERT(appliedOffset.y < 0);
    504    }
    505  }
    506  // Same as above, but for the x-axis
    507  if (outer.XMost() != inner.XMost()) {
    508    nscoord distance = DistanceToRange(inner.XMost(), outer.XMost());
    509    if (distance > 0) {
    510      distance -= PositivePart(outer.X(), inner.X());
    511    }
    512    leftMargin = Some(NSAppUnitsToFloatPixels(
    513        itemBounds.x - scrollPort.x - distance, auPerDevPixel));
    514    hBounds.max =
    515        NSAppUnitsToFloatPixels(outer.XMost() - inner.XMost(), auPerDevPixel);
    516    if (inner.XMost() < 0) {
    517      appliedOffset.x = std::min(0, outer.XMost()) - inner.XMost();
    518      MOZ_ASSERT(appliedOffset.x > 0);
    519    }
    520  }
    521  if (outer.X() != inner.X()) {
    522    nscoord distance = DistanceToRange(outer.X(), inner.X());
    523    if (distance < 0) {
    524      distance += NegativePart(inner.XMost(), outer.XMost());
    525    }
    526    rightMargin = Some(NSAppUnitsToFloatPixels(
    527        scrollPort.XMost() - itemBounds.XMost() + distance, auPerDevPixel));
    528    hBounds.min = NSAppUnitsToFloatPixels(outer.X() - inner.X(), auPerDevPixel);
    529    if (appliedOffset.x == 0 && inner.X() > 0) {
    530      appliedOffset.x = std::max(0, outer.X()) - inner.X();
    531      MOZ_ASSERT(appliedOffset.x < 0);
    532    }
    533  }
    534 
    535  LayoutDeviceRect bounds =
    536      LayoutDeviceRect::FromAppUnits(itemBounds, auPerDevPixel);
    537  wr::LayoutVector2D applied = {
    538      NSAppUnitsToFloatPixels(appliedOffset.x, auPerDevPixel),
    539      NSAppUnitsToFloatPixels(appliedOffset.y, auPerDevPixel)};
    540  bool needsProp =
    541      nsDisplayStickyPosition::ShouldGetStickyAnimationId(stickyFrame);
    542  Maybe<wr::WrAnimationProperty> prop;
    543  auto displayItemKey = nsDisplayItem::GetPerFrameKey(
    544      0, 0, DisplayItemType::TYPE_STICKY_POSITION);
    545  auto spatialKey = wr::SpatialKey(uint64_t(stickyFrame), displayItemKey,
    546                                   wr::SpatialKeyKind::Sticky);
    547  if (needsProp) {
    548    RefPtr<WebRenderAPZAnimationData> animationData =
    549        mManager->CommandBuilder()
    550            .CreateOrRecycleWebRenderUserData<WebRenderAPZAnimationData>(
    551                displayItemKey, stickyFrame);
    552    uint64_t animationId = animationData->GetAnimationId();
    553 
    554    prop.emplace();
    555    prop->id = animationId;
    556    prop->key = spatialKey;
    557    prop->effect_type = wr::WrAnimationType::Transform;
    558  }
    559  wr::WrSpatialId spatialId = mBuilder->DefineStickyFrame(
    560      aASR, aParentSpatialId, wr::ToLayoutRect(bounds),
    561      topMargin.ptrOr(nullptr), rightMargin.ptrOr(nullptr),
    562      bottomMargin.ptrOr(nullptr), leftMargin.ptrOr(nullptr), vBounds, hBounds,
    563      applied, spatialKey, prop.ptrOr(nullptr));
    564 
    565  return Some(spatialId);
    566 }
    567 
    568 Maybe<wr::WrSpatialId> ClipManager::DefineSpatialNodes(
    569    nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR,
    570    nsDisplayItem* aItem) {
    571  if (!aASR) {
    572    // Recursion base case
    573    return Nothing();
    574  }
    575 
    576  ScrollableLayerGuid::ViewID viewId = ScrollableLayerGuid::NULL_SCROLL_ID;
    577  if (aASR->mKind == ActiveScrolledRoot::ASRKind::Scroll) {
    578    viewId = aASR->GetViewId();
    579    Maybe<wr::WrSpatialId> space =
    580        mBuilder->GetScrollIdForDefinedScrollLayer(viewId);
    581    if (space) {
    582      // If we've already defined this scroll layer before, we can early-exit
    583      return space;
    584    }
    585  }
    586 
    587  // Recurse to define the ancestors
    588  Maybe<wr::WrSpatialId> ancestorSpace =
    589      DefineSpatialNodes(aBuilder, aASR->mParent, aItem);
    590 
    591  if (aASR->mKind == ActiveScrolledRoot::ASRKind::Sticky) {
    592    Maybe<wr::WrSpatialId> parent = ancestorSpace.map(
    593        [this](wr::WrSpatialId& aId) { return SpatialIdAfterOverride(aId); });
    594    return ClipManager::DefineStickyNode(aBuilder, parent, aASR, aItem);
    595  }
    596 
    597  MOZ_ASSERT(viewId != ScrollableLayerGuid::NULL_SCROLL_ID);
    598 
    599  ScrollContainerFrame* scrollContainerFrame = aASR->ScrollFrame();
    600  Maybe<ScrollMetadata> metadata = scrollContainerFrame->ComputeScrollMetadata(
    601      mManager, aItem->Frame(), aItem->ToReferenceFrame());
    602  if (!metadata) {
    603    MOZ_ASSERT_UNREACHABLE("Expected scroll metadata to be available!");
    604    return ancestorSpace;
    605  }
    606 
    607  FrameMetrics& metrics = metadata->GetMetrics();
    608  if (!metrics.IsScrollable()) {
    609    // This item is a scrolling no-op, skip over it in the ASR chain.
    610    return ancestorSpace;
    611  }
    612 
    613  nsPoint offset = scrollContainerFrame->GetOffsetToCrossDoc(aItem->Frame()) +
    614                   aItem->ToReferenceFrame();
    615  int32_t auPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
    616  nsRect scrollPort = scrollContainerFrame->GetScrollPortRect() + offset;
    617  LayoutDeviceRect clipBounds =
    618      LayoutDeviceRect::FromAppUnits(scrollPort, auPerDevPixel);
    619 
    620  // The content rect that we hand to PushScrollLayer should be relative to
    621  // the same origin as the clipBounds that we hand to PushScrollLayer -
    622  // that is, both of them should be relative to the stacking context `aSc`.
    623  // However, when we get the scrollable rect from the FrameMetrics, the
    624  // origin has nothing to do with the position of the frame but instead
    625  // represents the minimum allowed scroll offset of the scrollable content.
    626  // While APZ uses this to clamp the scroll position, we don't need to send
    627  // this to WebRender at all. Instead, we take the position from the
    628  // composition bounds.
    629  LayoutDeviceRect contentRect =
    630      metrics.GetExpandedScrollableRect() * metrics.GetDevPixelsPerCSSPixel();
    631  contentRect.MoveTo(clipBounds.TopLeft());
    632 
    633  Maybe<wr::WrSpatialId> parent = ancestorSpace;
    634  if (parent) {
    635    *parent = SpatialIdAfterOverride(*parent);
    636  }
    637  // The external scroll offset is accumulated into the local space positions of
    638  // display items inside WR, so that the elements hash (intern) to the same
    639  // content ID for quick comparisons. To avoid invalidations when the
    640  // auPerDevPixel is not a round value, round here directly from app units.
    641  // This guarantees we won't introduce any inaccuracy in the external scroll
    642  // offset passed to WR.
    643  const bool useRoundedOffset =
    644      StaticPrefs::apz_rounded_external_scroll_offset();
    645  LayoutDevicePoint scrollOffset =
    646      useRoundedOffset
    647          ? LayoutDevicePoint::FromAppUnitsRounded(
    648                scrollContainerFrame->GetScrollPosition(), auPerDevPixel)
    649          : LayoutDevicePoint::FromAppUnits(
    650                scrollContainerFrame->GetScrollPosition(), auPerDevPixel);
    651 
    652  // Currently we track scroll-linked effects at the granularity of documents,
    653  // not scroll frames, so we consider a scroll frame to have a scroll-linked
    654  // effect whenever its containing document does.
    655  nsPresContext* presContext = aItem->Frame()->PresContext();
    656  const bool hasScrollLinkedEffect =
    657      !StaticPrefs::apz_disable_for_scroll_linked_effects() &&
    658      presContext->Document()->HasScrollLinkedEffect();
    659 
    660  return Some(mBuilder->DefineScrollLayer(
    661      viewId, parent, wr::ToLayoutRect(contentRect),
    662      wr::ToLayoutRect(clipBounds), wr::ToLayoutVector2D(scrollOffset),
    663      wr::ToWrAPZScrollGeneration(
    664          scrollContainerFrame->ScrollGenerationOnApz()),
    665      wr::ToWrHasScrollLinkedEffect(hasScrollLinkedEffect),
    666      wr::SpatialKey(uint64_t(scrollContainerFrame), 0,
    667                     wr::SpatialKeyKind::Scroll)));
    668 }
    669 
    670 Maybe<wr::WrClipChainId> ClipManager::DefineClipChain(
    671    const DisplayItemClipChain* aChain, int32_t aAppUnitsPerDevPixel) {
    672  MOZ_ASSERT(!mCacheStack.empty());
    673  if (!aChain) {
    674    return Nothing();
    675  }
    676 
    677  ClipIdMap& cache = mCacheStack.top();
    678  MOZ_DIAGNOSTIC_ASSERT(aChain->mOnStack || !aChain->mASR ||
    679                        aChain->mASR->mFrame);
    680 
    681  if (auto iter = cache.find(aChain); iter != cache.end()) {
    682    // Found it in the currently-active cache, so just use the id we have for
    683    // it.
    684    CLIP_LOG("cache[%p] => hit\n", aChain);
    685    return iter->second.mWrChainID;
    686  }
    687 
    688  const auto parentChain =
    689      DefineClipChain(aChain->mParent, aAppUnitsPerDevPixel);
    690  if (!aChain->mClip.HasClip()) {
    691    cache[aChain] = {parentChain};
    692    // This item in the chain is a no-op, skip over it
    693    return parentChain;
    694  }
    695 
    696  auto clip = LayoutDeviceRect::FromAppUnits(aChain->mClip.GetClipRect(),
    697                                             aAppUnitsPerDevPixel);
    698  AutoTArray<wr::ComplexClipRegion, 6> wrRoundedRects;
    699  aChain->mClip.ToComplexClipRegions(aAppUnitsPerDevPixel, wrRoundedRects);
    700  wr::WrSpatialId space = GetSpatialId(aChain->mASR);
    701  // Define the clip
    702  space = SpatialIdAfterOverride(space);
    703  // Iterate through the clips in the current item's clip chain, define them
    704  // in WR, and put their IDs into |clipIds|.
    705  AutoTArray<wr::WrClipId, 6> clipChainClipIds;
    706  auto rectClipId =
    707      mBuilder->DefineRectClip(Some(space), wr::ToLayoutRect(clip));
    708  CLIP_LOG("cache[%p] <= %zu\n", aChain, rectClipId.id);
    709  clipChainClipIds.AppendElement(rectClipId);
    710 
    711  for (const auto& complexClip : wrRoundedRects) {
    712    auto complexClipId =
    713        mBuilder->DefineRoundedRectClip(Some(space), complexClip);
    714    CLIP_LOG("cache[%p] <= %zu\n", aChain, complexClipId.id);
    715    clipChainClipIds.AppendElement(complexClipId);
    716  }
    717  auto id = Some(mBuilder->DefineClipChain(clipChainClipIds, parentChain));
    718  cache[aChain] = {id};
    719  return id;
    720 }
    721 
    722 ClipManager::~ClipManager() {
    723  MOZ_ASSERT(!mBuilder);
    724  MOZ_ASSERT(mCacheStack.empty());
    725  MOZ_ASSERT(mItemClipStack.empty());
    726 }
    727 
    728 ClipManager::ItemClips::ItemClips(const ActiveScrolledRoot* aASR,
    729                                  const DisplayItemClipChain* aChain,
    730                                  int32_t aAppUnitsPerDevPixel,
    731                                  bool aSeparateLeaf)
    732    : mASR(aASR),
    733      mChain(aChain),
    734      mAppUnitsPerDevPixel(aAppUnitsPerDevPixel),
    735      mSeparateLeaf(aSeparateLeaf) {
    736  mScrollId = wr::wr_root_scroll_node_id();
    737 }
    738 
    739 void ClipManager::ItemClips::UpdateSeparateLeaf(
    740    wr::DisplayListBuilder& aBuilder, int32_t aAppUnitsPerDevPixel) {
    741  Maybe<wr::LayoutRect> clipLeaf;
    742  if (mSeparateLeaf) {
    743    MOZ_ASSERT(mChain);
    744    clipLeaf.emplace(wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits(
    745        mChain->mClip.GetClipRect(), aAppUnitsPerDevPixel)));
    746  }
    747 
    748  aBuilder.SetClipChainLeaf(clipLeaf);
    749 }
    750 
    751 bool ClipManager::ItemClips::HasSameInputs(const ItemClips& aOther) {
    752  if (mASR != aOther.mASR || mChain != aOther.mChain ||
    753      mSeparateLeaf != aOther.mSeparateLeaf) {
    754    return false;
    755  }
    756  // AUPDP only matters if we have a clip chain, since it's only used to compute
    757  // the device space clip rect.
    758  if (mChain && mAppUnitsPerDevPixel != aOther.mAppUnitsPerDevPixel) {
    759    return false;
    760  }
    761  return true;
    762 }
    763 
    764 wr::WrSpaceAndClipChain ClipManager::ItemClips::GetSpaceAndClipChain() const {
    765  auto spaceAndClipChain = wr::RootScrollNodeWithChain();
    766  spaceAndClipChain.space = mScrollId;
    767  if (mClipChainId) {
    768    spaceAndClipChain.clip_chain = mClipChainId->id;
    769  }
    770  return spaceAndClipChain;
    771 }
    772 
    773 }  // namespace mozilla::layers