/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/layers/ClipManager.h" #include "DisplayItemClipChain.h" #include "FrameMetrics.h" #include "mozilla/ScrollContainerFrame.h" #include "mozilla/dom/Document.h" #include "mozilla/layers/StackingContextHelper.h" #include "mozilla/layers/WebRenderLayerManager.h" #include "mozilla/StaticPrefs_apz.h" #include "mozilla/webrender/WebRenderAPI.h" #include "nsDisplayList.h" #include "nsLayoutUtils.h" #include "nsRefreshDriver.h" #include "nsStyleStructInlines.h" #include "UnitTransforms.h" static mozilla::LazyLogModule sClipLog("wr.clip"); #define CLIP_LOG(...) MOZ_LOG(sClipLog, LogLevel::Debug, (__VA_ARGS__)) namespace mozilla { namespace layers { ClipManager::ClipManager() : mManager(nullptr), mBuilder(nullptr) {} void ClipManager::BeginBuild(WebRenderLayerManager* aManager, wr::DisplayListBuilder& aBuilder) { MOZ_ASSERT(!mManager); mManager = aManager; MOZ_ASSERT(!mBuilder); mBuilder = &aBuilder; MOZ_ASSERT(mCacheStack.empty()); mCacheStack.emplace(); MOZ_ASSERT(mASROverride.empty()); MOZ_ASSERT(mItemClipStack.empty()); } void ClipManager::EndBuild() { mBuilder = nullptr; mManager = nullptr; mCacheStack.pop(); MOZ_ASSERT(mCacheStack.empty()); MOZ_ASSERT(mASROverride.empty()); MOZ_ASSERT(mItemClipStack.empty()); } void ClipManager::BeginList(const StackingContextHelper& aStackingContext) { CLIP_LOG("begin list %p affects = %d, ref-frame = %d\n", &aStackingContext, aStackingContext.AffectsClipPositioning(), aStackingContext.ReferenceFrameId().isSome()); ItemClips clips(nullptr, nullptr, 0, false); if (!mItemClipStack.empty()) { clips = mItemClipStack.top(); } if (aStackingContext.AffectsClipPositioning()) { if (auto referenceFrameId = aStackingContext.ReferenceFrameId()) { PushOverrideForASR(clips.mASR, *referenceFrameId); clips.mScrollId = *referenceFrameId; } else { // Start a new cache mCacheStack.emplace(); } if (clips.mChain) { clips.mClipChainId = DefineClipChain(clips.mChain, clips.mAppUnitsPerDevPixel); } } CLIP_LOG(" push: clip: %p, asr: %p, scroll =%" PRIuPTR ", clip =%" PRIu64 "\n", clips.mChain, clips.mASR, clips.mScrollId.id, clips.mClipChainId.valueOr(wr::WrClipChainId{0}).id); mItemClipStack.push(clips); } void ClipManager::EndList(const StackingContextHelper& aStackingContext) { MOZ_ASSERT(!mItemClipStack.empty()); CLIP_LOG("end list %p\n", &aStackingContext); mBuilder->SetClipChainLeaf(Nothing()); mItemClipStack.pop(); if (aStackingContext.AffectsClipPositioning()) { if (aStackingContext.ReferenceFrameId()) { PopOverrideForASR(mItemClipStack.empty() ? nullptr : mItemClipStack.top().mASR); } else { MOZ_ASSERT(!mCacheStack.empty()); mCacheStack.pop(); } } } void ClipManager::PushOverrideForASR(const ActiveScrolledRoot* aASR, const wr::WrSpatialId& aSpatialId) { wr::WrSpatialId space = GetSpatialId(aASR); CLIP_LOG("Pushing %p override %zu -> %zu\n", aASR, space.id, aSpatialId.id); auto it = mASROverride.insert({space, std::stack()}); it.first->second.push(aSpatialId); // Start a new cache mCacheStack.emplace(); // Fix up our cached item clip if needed. if (!mItemClipStack.empty()) { auto& top = mItemClipStack.top(); if (top.mASR == aASR) { top.mScrollId = aSpatialId; if (top.mChain) { top.mClipChainId = DefineClipChain(top.mChain, top.mAppUnitsPerDevPixel); } } } } void ClipManager::PopOverrideForASR(const ActiveScrolledRoot* aASR) { MOZ_ASSERT(!mCacheStack.empty()); mCacheStack.pop(); wr::WrSpatialId space = GetSpatialId(aASR); auto it = mASROverride.find(space); if (it == mASROverride.end()) { MOZ_ASSERT_UNREACHABLE("Push/PopOverrideForASR should be balanced"); } else { CLIP_LOG("Popping %p override %zu -> %zu\n", aASR, space.id, it->second.top().id); it->second.pop(); } if (!mItemClipStack.empty()) { auto& top = mItemClipStack.top(); if (top.mASR == aASR) { top.mScrollId = (it == mASROverride.end() || it->second.empty()) ? space : it->second.top(); if (top.mChain) { top.mClipChainId = DefineClipChain(top.mChain, top.mAppUnitsPerDevPixel); } } } if (it != mASROverride.end() && it->second.empty()) { mASROverride.erase(it); } } wr::WrSpatialId ClipManager::SpatialIdAfterOverride( const wr::WrSpatialId& aSpatialId) { auto it = mASROverride.find(aSpatialId); if (it == mASROverride.end()) { return aSpatialId; } MOZ_ASSERT(!it->second.empty()); CLIP_LOG("Overriding %zu with %zu\n", aSpatialId.id, it->second.top().id); return it->second.top(); } wr::WrSpaceAndClipChain ClipManager::SwitchItem(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem) { const DisplayItemClipChain* clip = aItem->GetClipChain(); const DisplayItemClipChain* inheritedClipChain = mBuilder->GetInheritedClipChain(); if (inheritedClipChain && inheritedClipChain != clip) { if (!clip) { clip = mBuilder->GetInheritedClipChain(); } else { clip = aBuilder->CreateClipChainIntersection( mBuilder->GetInheritedClipChain(), clip); } } const ActiveScrolledRoot* asr = aItem->GetActiveScrolledRoot(); DisplayItemType type = aItem->GetType(); const ActiveScrolledRoot* stickyAsr = nullptr; if (type == DisplayItemType::TYPE_STICKY_POSITION) { // For sticky position items, the ASR is computed differently depending on // whether the item has a fixed descendant or not. But for WebRender // purposes we always want to use the ASR that would have been used if it // didn't have fixed descendants, which is stored as the "container ASR" on // the sticky item. auto* sticky = static_cast(aItem); asr = sticky->GetContainerASR(); stickyAsr = ActiveScrolledRoot::GetStickyASRFromFrame(sticky->Frame()); MOZ_ASSERT(stickyAsr); } CLIP_LOG("processing item %p (%s) asr %p clip %p, inherited = %p\n", aItem, DisplayItemTypeName(aItem->GetType()), asr, clip, inheritedClipChain); // In most cases we can combine the leaf of the clip chain with the clip rect // of the display item. This reduces the number of clip items, which avoids // some overhead further down the pipeline. bool separateLeaf = false; if (clip && clip->mASR == asr && clip->mClip.GetRoundedRectCount() == 0) { // Container display items are not currently supported because the clip // rect of a stacking context is not handled the same as normal display // items. separateLeaf = !aItem->GetChildren(); } // Zoom display items report their bounds etc using the parent document's // APD because zoom items act as a conversion layer between the two different // APDs. const int32_t auPerDevPixel = [&] { if (type == DisplayItemType::TYPE_ZOOM) { return static_cast(aItem)->GetParentAppUnitsPerDevPixel(); } return aItem->Frame()->PresContext()->AppUnitsPerDevPixel(); }(); ItemClips clips(asr, clip, auPerDevPixel, separateLeaf); MOZ_ASSERT(!mItemClipStack.empty()); if (clips.HasSameInputs(mItemClipStack.top())) { // Early-exit because if the clips are the same as aItem's previous sibling, // then we don't need to do do the work of popping the old stuff and then // pushing it right back on for the new item. Note that if aItem doesn't // have a previous sibling, that means BeginList would have been called // just before this, which will have pushed a ItemClips(nullptr, nullptr) // onto mItemClipStack, so the HasSameInputs check should return false. CLIP_LOG("\tearly-exit for %p\n", aItem); return mItemClipStack.top().GetSpaceAndClipChain(); } // Pop aItem's previous sibling's stuff from mBuilder in preparation for // pushing aItem's stuff. mItemClipStack.pop(); // If the leaf of the clip chain is going to be merged with the display item's // clip rect, then we should create a clip chain id from the leaf's parent. if (separateLeaf) { CLIP_LOG("\tseparate leaf detected, ignoring the last clip\n"); clip = clip->mParent; } // There are up to three ASR chains here that we need to be fully defined: // 1. The ASR chain pointed to by |asr| // 2. The ASR chain pointed to by clip->mASR // 3. For a sticky item, the ASR chain pointed to by the item's sticky ASR. // This one is needed so that when we create WebRender commands for the // sticky item, we can give WebRender accurate information about the // spatial node we're in. // These chains will often be the same, or one will include the other, // but we can't rely on that always being the case, so we make a // DefineSpatialNodes call on all three. These calls will recursively // define all the ASRs that we care about for this item, but will not // actually push anything onto the WR stack. (void)DefineSpatialNodes(aBuilder, asr, aItem); if (clip && clip->mASR != asr) { (void)DefineSpatialNodes(aBuilder, clip->mASR, aItem); } if (stickyAsr && stickyAsr != asr) { (void)DefineSpatialNodes(aBuilder, stickyAsr, aItem); } // Define all the clips in the item's clip chain, and obtain a clip chain id // for it. clips.mClipChainId = DefineClipChain(clip, auPerDevPixel); wr::WrSpatialId space = GetSpatialId(asr); clips.mScrollId = SpatialIdAfterOverride(space); CLIP_LOG("\tassigning %d -> %d\n", (int)space.id, (int)clips.mScrollId.id); // Now that we have the scroll id and a clip id for the item, push it onto // the WR stack. clips.UpdateSeparateLeaf(*mBuilder, auPerDevPixel); auto spaceAndClipChain = clips.GetSpaceAndClipChain(); CLIP_LOG(" push: clip: %p, asr: %p, scroll = %" PRIuPTR ", clip = %" PRIu64 "\n", clips.mChain, clips.mASR, clips.mScrollId.id, clips.mClipChainId.valueOr(wr::WrClipChainId{0}).id); mItemClipStack.push(clips); CLIP_LOG("done setup for %p\n", aItem); return spaceAndClipChain; } wr::WrSpatialId ClipManager::GetSpatialId(const ActiveScrolledRoot* aASR) { for (const ActiveScrolledRoot* asr = aASR; asr; asr = asr->mParent) { Maybe space = Nothing(); if (asr->mKind == ActiveScrolledRoot::ASRKind::Sticky) { space = mBuilder->GetSpatialIdForDefinedStickyLayer(asr); } else { space = mBuilder->GetScrollIdForDefinedScrollLayer(asr->GetViewId()); } if (space) { return *space; } // If this ASR doesn't have a scroll ID, then we should check its ancestor. // There may not be one defined because the ASR may not be scrollable or we // failed to get the scroll metadata. } Maybe space = mBuilder->GetScrollIdForDefinedScrollLayer( ScrollableLayerGuid::NULL_SCROLL_ID); MOZ_ASSERT(space.isSome()); return *space; } StickyScrollContainer* ClipManager::GetStickyScrollContainer( const ActiveScrolledRoot* aASR) { MOZ_ASSERT(aASR->mKind == ActiveScrolledRoot::ASRKind::Sticky); StickyScrollContainer* stickyScrollContainer = StickyScrollContainer::GetOrCreateForFrame(aASR->mFrame); if (stickyScrollContainer) { // If there's no ASR for the scrollframe that this sticky item is attached // to, then don't create a WR sticky item for it either. Trying to do so // will end in sadness because WR will interpret some coordinates as // relative to the nearest enclosing scrollframe, which will correspond // to the nearest ancestor ASR on the gecko side. That ASR will not be the // same as the scrollframe this sticky item is actually supposed to be // attached to, thus the sadness. // Not sending WR the sticky item is ok, because the enclosing scrollframe // will never be asynchronously scrolled. Instead we will always position // the sticky items correctly on the gecko side and WR will never need to // adjust their position itself. if (!stickyScrollContainer->ScrollContainer() ->IsMaybeAsynchronouslyScrolled()) { stickyScrollContainer = nullptr; } } return stickyScrollContainer; } // Returns the smallest distance from "0" to the range [min, max] where // min <= max. Despite the name, the return value is actually a 1-D vector, // and so may be negative if max < 0. static nscoord DistanceToRange(nscoord min, nscoord max) { MOZ_ASSERT(min <= max); if (max < 0) { return max; } if (min > 0) { return min; } MOZ_ASSERT(min <= 0 && max >= 0); return 0; } // Returns the magnitude of the part of the range [min, max] that is greater // than zero. The return value is always non-negative. static nscoord PositivePart(nscoord min, nscoord max) { MOZ_ASSERT(min <= max); if (min >= 0) { return max - min; } if (max > 0) { return max; } return 0; } // Returns the magnitude of the part of the range [min, max] that is less // than zero. The return value is always non-negative. static nscoord NegativePart(nscoord min, nscoord max) { MOZ_ASSERT(min <= max); if (max <= 0) { return max - min; } if (min < 0) { return 0 - min; } return 0; } Maybe ClipManager::DefineStickyNode( nsDisplayListBuilder* aBuilder, Maybe aParentSpatialId, const ActiveScrolledRoot* aASR, nsDisplayItem* aItem) { nsIFrame* stickyFrame = aASR->mFrame; if (Maybe space = mBuilder->GetSpatialIdForDefinedStickyLayer(aASR)) { return space; } StickyScrollContainer* stickyScrollContainer = GetStickyScrollContainer(aASR); if (!stickyScrollContainer) { // This may indicated a sticky item that does not need a webrender spatial // node. See the comment in GetStickyScrollContainer for details. return Nothing(); } // Do not create a spatial node for sticky items with mShouldFlatten=true. // These are inside inactive scroll frames and so cannot move asynchronously. if (stickyScrollContainer->ShouldFlattenAway()) { return Nothing(); } float auPerDevPixel = stickyFrame->PresContext()->AppUnitsPerDevPixel(); nsRect itemBounds; // Make both itemBounds and scrollPort be relative to the reference frame // of the sticky frame's parent. nsRect scrollPort = stickyScrollContainer->ScrollContainer()->GetScrollPortRect(); const nsIFrame* referenceFrame = aBuilder->FindReferenceFrameFor(stickyFrame->GetParent()); nsRect transformedBounds = stickyFrame->GetRectRelativeToSelf(); DebugOnly transformResult = nsLayoutUtils::TransformRect( stickyFrame, referenceFrame, transformedBounds); MOZ_ASSERT(transformResult == nsLayoutUtils::TRANSFORM_SUCCEEDED); itemBounds = transformedBounds; scrollPort = scrollPort + stickyScrollContainer->ScrollContainer()->GetOffsetToCrossDoc( referenceFrame); Maybe topMargin; Maybe rightMargin; Maybe bottomMargin; Maybe leftMargin; wr::StickyOffsetBounds vBounds = {0.0, 0.0}; wr::StickyOffsetBounds hBounds = {0.0, 0.0}; nsPoint appliedOffset; nsRectAbsolute outer; nsRectAbsolute inner; stickyScrollContainer->GetScrollRanges(stickyFrame, &outer, &inner); // The following computations make more sense upon understanding the // semantics of "inner" and "outer", which is explained in the comment on // SetStickyPositionData in Layers.h. if (outer.YMost() != inner.YMost()) { // Question: How far will itemBounds.y be from the top of the scrollport // when we have scrolled from the current scroll position of "0" to // reach the range [inner.YMost(), outer.YMost()] where the item gets // stuck? // Answer: the current distance is "itemBounds.y - scrollPort.y". That // needs to be adjusted by the distance to the range, less any other // sticky ranges that fall between 0 and the range. If the distance is // negative (i.e. inner.YMost() <= outer.YMost() < 0) then we would be // scrolling upwards (decreasing scroll offset) to reach that range, // which would increase itemBounds.y and make it farther away from the // top of the scrollport. So in that case the adjustment is -distance. // If the distance is positive (0 < inner.YMost() <= outer.YMost()) then // we would be scrolling downwards, itemBounds.y would decrease, and we // again need to adjust by -distance. If we are already in the range // then no adjustment is needed and distance is 0 so again using // -distance works. If the distance is positive, and the item has both // top and bottom sticky ranges, then the bottom sticky range may fall // (entirely[1] or partly[2]) between the current scroll position. // [1]: 0 <= outer.Y() <= inner.Y() < inner.YMost() <= outer.YMost() // [2]: outer.Y() < 0 <= inner.Y() < inner.YMost() <= outer.YMost() // In these cases, the item doesn't actually move for that part of the // distance, so we need to subtract out that bit, which can be computed // as the positive portion of the range [outer.Y(), inner.Y()]. nscoord distance = DistanceToRange(inner.YMost(), outer.YMost()); if (distance > 0) { distance -= PositivePart(outer.Y(), inner.Y()); } topMargin = Some(NSAppUnitsToFloatPixels( itemBounds.y - scrollPort.y - distance, auPerDevPixel)); // Question: What is the maximum positive ("downward") offset that WR // will have to apply to this item in order to prevent the item from // visually moving? // Answer: Since the item is "sticky" in the range [inner.YMost(), // outer.YMost()], the maximum offset will be the size of the range, which // is outer.YMost() - inner.YMost(). vBounds.max = NSAppUnitsToFloatPixels(outer.YMost() - inner.YMost(), auPerDevPixel); // Question: how much of an offset has layout already applied to the item? // Answer: if we are // (a) inside the sticky range (inner.YMost() < 0 <= outer.YMost()), or // (b) past the sticky range (inner.YMost() < outer.YMost() < 0) // then layout has already applied some offset to the position of the // item. The amount of the adjustment is |0 - inner.YMost()| in case (a) // and |outer.YMost() - inner.YMost()| in case (b). if (inner.YMost() < 0) { appliedOffset.y = std::min(0, outer.YMost()) - inner.YMost(); MOZ_ASSERT(appliedOffset.y > 0); } } if (outer.Y() != inner.Y()) { // Similar logic as in the previous section, but this time we care about // the distance from itemBounds.YMost() to scrollPort.YMost(). nscoord distance = DistanceToRange(outer.Y(), inner.Y()); if (distance < 0) { distance += NegativePart(inner.YMost(), outer.YMost()); } bottomMargin = Some(NSAppUnitsToFloatPixels( scrollPort.YMost() - itemBounds.YMost() + distance, auPerDevPixel)); // And here WR will be moving the item upwards rather than downwards so // again things are inverted from the previous block. vBounds.min = NSAppUnitsToFloatPixels(outer.Y() - inner.Y(), auPerDevPixel); // We can't have appliedOffset be both positive and negative, and the top // adjustment takes priority. So here we only update appliedOffset.y if // it wasn't set by the top-sticky case above. if (appliedOffset.y == 0 && inner.Y() > 0) { appliedOffset.y = std::max(0, outer.Y()) - inner.Y(); MOZ_ASSERT(appliedOffset.y < 0); } } // Same as above, but for the x-axis if (outer.XMost() != inner.XMost()) { nscoord distance = DistanceToRange(inner.XMost(), outer.XMost()); if (distance > 0) { distance -= PositivePart(outer.X(), inner.X()); } leftMargin = Some(NSAppUnitsToFloatPixels( itemBounds.x - scrollPort.x - distance, auPerDevPixel)); hBounds.max = NSAppUnitsToFloatPixels(outer.XMost() - inner.XMost(), auPerDevPixel); if (inner.XMost() < 0) { appliedOffset.x = std::min(0, outer.XMost()) - inner.XMost(); MOZ_ASSERT(appliedOffset.x > 0); } } if (outer.X() != inner.X()) { nscoord distance = DistanceToRange(outer.X(), inner.X()); if (distance < 0) { distance += NegativePart(inner.XMost(), outer.XMost()); } rightMargin = Some(NSAppUnitsToFloatPixels( scrollPort.XMost() - itemBounds.XMost() + distance, auPerDevPixel)); hBounds.min = NSAppUnitsToFloatPixels(outer.X() - inner.X(), auPerDevPixel); if (appliedOffset.x == 0 && inner.X() > 0) { appliedOffset.x = std::max(0, outer.X()) - inner.X(); MOZ_ASSERT(appliedOffset.x < 0); } } LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(itemBounds, auPerDevPixel); wr::LayoutVector2D applied = { NSAppUnitsToFloatPixels(appliedOffset.x, auPerDevPixel), NSAppUnitsToFloatPixels(appliedOffset.y, auPerDevPixel)}; bool needsProp = nsDisplayStickyPosition::ShouldGetStickyAnimationId(stickyFrame); Maybe prop; auto displayItemKey = nsDisplayItem::GetPerFrameKey( 0, 0, DisplayItemType::TYPE_STICKY_POSITION); auto spatialKey = wr::SpatialKey(uint64_t(stickyFrame), displayItemKey, wr::SpatialKeyKind::Sticky); if (needsProp) { RefPtr animationData = mManager->CommandBuilder() .CreateOrRecycleWebRenderUserData( displayItemKey, stickyFrame); uint64_t animationId = animationData->GetAnimationId(); prop.emplace(); prop->id = animationId; prop->key = spatialKey; prop->effect_type = wr::WrAnimationType::Transform; } wr::WrSpatialId spatialId = mBuilder->DefineStickyFrame( aASR, aParentSpatialId, wr::ToLayoutRect(bounds), topMargin.ptrOr(nullptr), rightMargin.ptrOr(nullptr), bottomMargin.ptrOr(nullptr), leftMargin.ptrOr(nullptr), vBounds, hBounds, applied, spatialKey, prop.ptrOr(nullptr)); return Some(spatialId); } Maybe ClipManager::DefineSpatialNodes( nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR, nsDisplayItem* aItem) { if (!aASR) { // Recursion base case return Nothing(); } ScrollableLayerGuid::ViewID viewId = ScrollableLayerGuid::NULL_SCROLL_ID; if (aASR->mKind == ActiveScrolledRoot::ASRKind::Scroll) { viewId = aASR->GetViewId(); Maybe space = mBuilder->GetScrollIdForDefinedScrollLayer(viewId); if (space) { // If we've already defined this scroll layer before, we can early-exit return space; } } // Recurse to define the ancestors Maybe ancestorSpace = DefineSpatialNodes(aBuilder, aASR->mParent, aItem); if (aASR->mKind == ActiveScrolledRoot::ASRKind::Sticky) { Maybe parent = ancestorSpace.map( [this](wr::WrSpatialId& aId) { return SpatialIdAfterOverride(aId); }); return ClipManager::DefineStickyNode(aBuilder, parent, aASR, aItem); } MOZ_ASSERT(viewId != ScrollableLayerGuid::NULL_SCROLL_ID); ScrollContainerFrame* scrollContainerFrame = aASR->ScrollFrame(); Maybe metadata = scrollContainerFrame->ComputeScrollMetadata( mManager, aItem->Frame(), aItem->ToReferenceFrame()); if (!metadata) { MOZ_ASSERT_UNREACHABLE("Expected scroll metadata to be available!"); return ancestorSpace; } FrameMetrics& metrics = metadata->GetMetrics(); if (!metrics.IsScrollable()) { // This item is a scrolling no-op, skip over it in the ASR chain. return ancestorSpace; } nsPoint offset = scrollContainerFrame->GetOffsetToCrossDoc(aItem->Frame()) + aItem->ToReferenceFrame(); int32_t auPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel(); nsRect scrollPort = scrollContainerFrame->GetScrollPortRect() + offset; LayoutDeviceRect clipBounds = LayoutDeviceRect::FromAppUnits(scrollPort, auPerDevPixel); // The content rect that we hand to PushScrollLayer should be relative to // the same origin as the clipBounds that we hand to PushScrollLayer - // that is, both of them should be relative to the stacking context `aSc`. // However, when we get the scrollable rect from the FrameMetrics, the // origin has nothing to do with the position of the frame but instead // represents the minimum allowed scroll offset of the scrollable content. // While APZ uses this to clamp the scroll position, we don't need to send // this to WebRender at all. Instead, we take the position from the // composition bounds. LayoutDeviceRect contentRect = metrics.GetExpandedScrollableRect() * metrics.GetDevPixelsPerCSSPixel(); contentRect.MoveTo(clipBounds.TopLeft()); Maybe parent = ancestorSpace; if (parent) { *parent = SpatialIdAfterOverride(*parent); } // The external scroll offset is accumulated into the local space positions of // display items inside WR, so that the elements hash (intern) to the same // content ID for quick comparisons. To avoid invalidations when the // auPerDevPixel is not a round value, round here directly from app units. // This guarantees we won't introduce any inaccuracy in the external scroll // offset passed to WR. const bool useRoundedOffset = StaticPrefs::apz_rounded_external_scroll_offset(); LayoutDevicePoint scrollOffset = useRoundedOffset ? LayoutDevicePoint::FromAppUnitsRounded( scrollContainerFrame->GetScrollPosition(), auPerDevPixel) : LayoutDevicePoint::FromAppUnits( scrollContainerFrame->GetScrollPosition(), auPerDevPixel); // Currently we track scroll-linked effects at the granularity of documents, // not scroll frames, so we consider a scroll frame to have a scroll-linked // effect whenever its containing document does. nsPresContext* presContext = aItem->Frame()->PresContext(); const bool hasScrollLinkedEffect = !StaticPrefs::apz_disable_for_scroll_linked_effects() && presContext->Document()->HasScrollLinkedEffect(); return Some(mBuilder->DefineScrollLayer( viewId, parent, wr::ToLayoutRect(contentRect), wr::ToLayoutRect(clipBounds), wr::ToLayoutVector2D(scrollOffset), wr::ToWrAPZScrollGeneration( scrollContainerFrame->ScrollGenerationOnApz()), wr::ToWrHasScrollLinkedEffect(hasScrollLinkedEffect), wr::SpatialKey(uint64_t(scrollContainerFrame), 0, wr::SpatialKeyKind::Scroll))); } Maybe ClipManager::DefineClipChain( const DisplayItemClipChain* aChain, int32_t aAppUnitsPerDevPixel) { MOZ_ASSERT(!mCacheStack.empty()); AutoTArray allClipIds; ClipIdMap& cache = mCacheStack.top(); // Iterate through the clips in the current item's clip chain, define them // in WR, and put their IDs into |clipIds|. for (const DisplayItemClipChain* chain = aChain; chain; chain = chain->mParent) { MOZ_DIAGNOSTIC_ASSERT(chain->mOnStack || !chain->mASR || chain->mASR->mFrame); if (!chain->mClip.HasClip()) { // This item in the chain is a no-op, skip over it continue; } auto emplaceResult = cache.try_emplace(chain); auto& chainClipIds = emplaceResult.first->second; if (!emplaceResult.second) { // Found it in the currently-active cache, so just use the id we have for // it. CLIP_LOG("cache[%p] => hit\n", chain); allClipIds.AppendElements(chainClipIds); continue; } LayoutDeviceRect clip = LayoutDeviceRect::FromAppUnits( chain->mClip.GetClipRect(), aAppUnitsPerDevPixel); AutoTArray wrRoundedRects; chain->mClip.ToComplexClipRegions(aAppUnitsPerDevPixel, wrRoundedRects); wr::WrSpatialId space = GetSpatialId(chain->mASR); // Define the clip space = SpatialIdAfterOverride(space); auto rectClipId = mBuilder->DefineRectClip(Some(space), wr::ToLayoutRect(clip)); CLIP_LOG("cache[%p] <= %zu\n", chain, rectClipId.id); chainClipIds.AppendElement(rectClipId); for (const auto& complexClip : wrRoundedRects) { auto complexClipId = mBuilder->DefineRoundedRectClip(Some(space), complexClip); CLIP_LOG("cache[%p] <= %zu\n", chain, complexClipId.id); chainClipIds.AppendElement(complexClipId); } allClipIds.AppendElements(chainClipIds); } if (allClipIds.IsEmpty()) { return Nothing(); } return Some(mBuilder->DefineClipChain(allClipIds)); } ClipManager::~ClipManager() { MOZ_ASSERT(!mBuilder); MOZ_ASSERT(mCacheStack.empty()); MOZ_ASSERT(mItemClipStack.empty()); } ClipManager::ItemClips::ItemClips(const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aChain, int32_t aAppUnitsPerDevPixel, bool aSeparateLeaf) : mASR(aASR), mChain(aChain), mAppUnitsPerDevPixel(aAppUnitsPerDevPixel), mSeparateLeaf(aSeparateLeaf) { mScrollId = wr::wr_root_scroll_node_id(); } void ClipManager::ItemClips::UpdateSeparateLeaf( wr::DisplayListBuilder& aBuilder, int32_t aAppUnitsPerDevPixel) { Maybe clipLeaf; if (mSeparateLeaf) { MOZ_ASSERT(mChain); clipLeaf.emplace(wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits( mChain->mClip.GetClipRect(), aAppUnitsPerDevPixel))); } aBuilder.SetClipChainLeaf(clipLeaf); } bool ClipManager::ItemClips::HasSameInputs(const ItemClips& aOther) { if (mASR != aOther.mASR || mChain != aOther.mChain || mSeparateLeaf != aOther.mSeparateLeaf) { return false; } // AUPDP only matters if we have a clip chain, since it's only used to compute // the device space clip rect. if (mChain && mAppUnitsPerDevPixel != aOther.mAppUnitsPerDevPixel) { return false; } return true; } wr::WrSpaceAndClipChain ClipManager::ItemClips::GetSpaceAndClipChain() const { auto spaceAndClipChain = wr::RootScrollNodeWithChain(); spaceAndClipChain.space = mScrollId; if (mClipChainId) { spaceAndClipChain.clip_chain = mClipChainId->id; } return spaceAndClipChain; } } // namespace layers } // namespace mozilla