tor-browser

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

commit 1816a3d49caa602d49af77c9a99b2c0325c31521
parent 7cd59a61c6a01fdd88254a2d880191ebcd3865ab
Author: Timothy Nikkel <tnikkel@gmail.com>
Date:   Wed, 26 Nov 2025 13:04:52 +0000

Bug 1988030. Create a function that activates and creates ASRs for scrollframes of the anchor of anchored content. r=hiro,layout-reviewers,layout-anchor-positioning-reviewers,botond,emilio

This is the most complicated and intricate patch in this series.

We want to assign the ASR of the anchor to the anchored content. But there is no painting relationship between them. Meaning that BuildDisplayList for the anchor might be called before or after BuildDisplayList for the anchored content.

If BuildDisplayList is called for the anchor before the anchored content we want to make sure that any scroll frames containing the anchor are activated. This is easy on desktop as all scroll frames that want it are activated. But on android we don't do that yet. That's where bug 1995759 comes in to activate all scroll frames when anchor pos is used.

If BuildDisplayList is called for the anchor after the anchored content then we want to activate any scroll frames containing the anchor during the BuildDisplayList call for the anchored content. That's one of the main things that this patch does. We create a new function DecideScrollableLayerEnsureDisplayport that always set a minimal display port to do that for us (it's a simplified version of DecideScrollableLayer).

Then we use our knowledge that the anchor is in the containing block of the anchored content to walk the scroll frame ancestors of the anchor until we reach the anchored content's containing block. Once we've collected all the scroll frames we can create the ASR structs.

Differential Revision: https://phabricator.services.mozilla.com/D271806

Diffstat:
Mlayout/base/DisplayPortUtils.cpp | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlayout/base/DisplayPortUtils.h | 23+++++++++++++++++++++++
Mlayout/generic/ScrollContainerFrame.cpp | 31+++++++++++++++++++------------
Mlayout/generic/ScrollContainerFrame.h | 10++++++++++
4 files changed, 191 insertions(+), 12 deletions(-)

diff --git a/layout/base/DisplayPortUtils.cpp b/layout/base/DisplayPortUtils.cpp @@ -734,6 +734,22 @@ void DisplayPortUtils::RemoveDisplayPort(nsIContent* aContent) { aContent->RemoveProperty(nsGkAtoms::DisplayPortMargins); } +void DisplayPortUtils::SetMinimalDisplayPortDuringPainting( + nsIContent* aContent, PresShell* aPresShell) { + // SetDisplayPortMargins calls TriggerDisplayPortExpiration which starts a + // display port expiry timer for display ports that do expire. However + // minimal display ports do not expire, so the display port has to be + // marked before the SetDisplayPortMargins call so the expiry timer + // doesn't get started. + aContent->SetProperty(nsGkAtoms::MinimalDisplayPort, + reinterpret_cast<void*>(true)); + + DisplayPortUtils::SetDisplayPortMargins( + aContent, aPresShell, DisplayPortMargins::Empty(aContent), + DisplayPortUtils::ClearMinimalDisplayPortProperty::No, 0, + DisplayPortUtils::RepaintMode::DoNotRepaint); +} + bool DisplayPortUtils::ViewportHasDisplayPort(nsPresContext* aPresContext) { nsIFrame* rootScrollContainerFrame = aPresContext->PresShell()->GetRootScrollContainerFrame(); @@ -1046,4 +1062,127 @@ bool DisplayPortUtils::WillUseEmptyDisplayPortMargins(nsIContent* aContent) { nsLayoutUtils::ShouldDisableApzForElement(aContent); } +// This first checks if aFrame is a scroll frame, if so it then tries to +// activate it. Then this function returns true if aFrame generates a scroll ASR +// (ie its an active scroll frame). +static bool ActivatePotentialScrollASR(nsIFrame* aFrame, + nsDisplayListBuilder* aBuilder) { + ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(aFrame); + if (!scrollContainerFrame) { + return false; + } + return scrollContainerFrame->DecideScrollableLayerEnsureDisplayport(aBuilder); +} + +// This first checks if aFrame is sticky pos, if so it then tries to activate +// the associate scroll frame. Then this function returns true if aFrame +// generates a sticky ASR (ie its sticky pos and its associated scroll frame is +// active). +static bool ActivatePotentialStickyASR(nsIFrame* aFrame, + nsDisplayListBuilder* aBuilder) { + if (aFrame->StyleDisplay()->mPosition != StylePositionProperty::Sticky) { + return false; + } + auto* ssc = StickyScrollContainer::GetOrCreateForFrame(aFrame); + if (!ssc) { + return false; + } + return ssc->ScrollContainer()->DecideScrollableLayerEnsureDisplayport( + aBuilder); +} + +struct FrameAndASRKind { + nsIFrame* mFrame; + ActiveScrolledRoot::ASRKind mASRKind; +}; + +const ActiveScrolledRoot* DisplayPortUtils::ActivateDisplayportOnASRAncestors( + nsIFrame* aAnchor, nsIFrame* aLimitAncestor, + const ActiveScrolledRoot* aASRofLimitAncestor, + nsDisplayListBuilder* aBuilder) { + MOZ_ASSERT(ScrollContainerFrame::ShouldActivateAllScrollFrames( + aBuilder, aLimitAncestor)); + + MOZ_ASSERT((aASRofLimitAncestor ? aASRofLimitAncestor->mFrame : nullptr) == + nsLayoutUtils::GetASRAncestorFrame(aLimitAncestor, aBuilder)); + + MOZ_ASSERT(nsLayoutUtils::IsProperAncestorFrameConsideringContinuations( + aLimitAncestor, aAnchor)); + + AutoTArray<FrameAndASRKind, 4> ASRframes; + nsIFrame* frame = aAnchor; + + // The passed in frame is the anchor, if it is a scroll frame we do not scroll + // with that scroll frame (we are "outside" of it) but if it is sticky pos + // then we do move with the sticky ASR. + if (ActivatePotentialStickyASR(frame, aBuilder)) { + ASRframes.EmplaceBack(frame, ActiveScrolledRoot::ASRKind::Sticky); + } + frame = OneStepInASRChain(frame, aLimitAncestor); + while (frame && frame != aLimitAncestor && + (!aLimitAncestor || + frame->FirstContinuation() != aLimitAncestor->FirstContinuation())) { + // We check if each frame encountered generates an ASR. It can either + // generate a scroll asr or a sticky asr, or both! If it generates both then + // the sticky asr is the outer (parent) asr. So we check for scroll ASRs + // first. + + // We intentionally call this on all scroll frames encountered, not just the + // ones that WantAsyncScroll. This is because scroll frames with + // WantAsyncScroll == false can have a display port (say if they had + // non-zero scroll range and had a display port but then their scroll range + // shrank to zero then the displayport would still stick around), hence + // mWillBuildScrollableLayer would be true on them and we need to make sure + // mWillBuildScrollableLayer is up to date (if the scroll frame was + // temporarily inside a view transition mWillBuildScrollableLayer would + // temporarily get set to false). + + // In this loop we are looking for any scroll frame that will generate an + // ASR. This corresponds to scroll frames with mWillBuildScrollableLayer == + // true. This is different from scroll frames that return true from + // WantAsyncScroll (both because of what was explained above and because not + // every scroll frame that WantAsyncScroll will have a displayport), and + // hence it's also different from what GetAsyncScrollableAncestorFrame will + // return. + if (ActivatePotentialScrollASR(frame, aBuilder)) { + ASRframes.EmplaceBack(frame, ActiveScrolledRoot::ASRKind::Scroll); + } + + // Then check if it's a sticky ASR. + if (ActivatePotentialStickyASR(frame, aBuilder)) { + ASRframes.EmplaceBack(frame, ActiveScrolledRoot::ASRKind::Sticky); + } + + frame = OneStepInASRChain(frame, aLimitAncestor); + } + + const ActiveScrolledRoot* asr = aASRofLimitAncestor; + + // Iterate array in reverse order (top down in the frame/asr tree) creating + // the asr structs. + for (auto asrFrame : Reversed(ASRframes)) { + MOZ_ASSERT(nsLayoutUtils::IsProperAncestorFrameConsideringContinuations( + aLimitAncestor, asrFrame.mFrame)); + +#ifdef DEBUG + if (asr && (asr->mFrame == asrFrame.mFrame)) { + MOZ_ASSERT(asr->mKind == ActiveScrolledRoot::ASRKind::Sticky); + MOZ_ASSERT(asrFrame.mASRKind == ActiveScrolledRoot::ASRKind::Scroll); + } else { + MOZ_ASSERT((asr ? asr->mFrame : nullptr) == + nsLayoutUtils::GetASRAncestorFrame( + OneStepInASRChain(asrFrame.mFrame), aBuilder)); + } +#endif + + asr = (asrFrame.mASRKind == ActiveScrolledRoot::ASRKind::Scroll) + ? aBuilder->GetOrCreateActiveScrolledRoot( + asr, static_cast<ScrollContainerFrame*>( + do_QueryFrame(asrFrame.mFrame))) + : aBuilder->GetOrCreateActiveScrolledRootForSticky( + asr, asrFrame.mFrame); + } + return asr; +} + } // namespace mozilla diff --git a/layout/base/DisplayPortUtils.h b/layout/base/DisplayPortUtils.h @@ -238,6 +238,12 @@ class DisplayPortUtils { static void RemoveDisplayPort(nsIContent* aContent); /** + * Set minimal display port margins during painting. + */ + static void SetMinimalDisplayPortDuringPainting(nsIContent* aContent, + PresShell* aPresShell); + + /** * Return true if aPresContext's viewport has a displayport. */ static bool ViewportHasDisplayPort(nsPresContext* aPresContext); @@ -332,6 +338,23 @@ class DisplayPortUtils { * of displayports. */ static bool WillUseEmptyDisplayPortMargins(nsIContent* aContent); + + /** + * Calls DecideScrollableLayerEnsureDisplayport on all proper ancestors of + * aAnchor that are async scrollable up to but not including aLimitAncestor + * (this creates a minimal display port on all async scrollable ancestors if + * they don't have a display port) and makes sure that there is an ASR struct + * created for all such async scrollable ancestors. + * Returns the ASR of aAnchor. + * This is a very specific function for anchor positioning and likely not + * what you want. In that context, aAnchor is the anchor of an abspos frame f + * (not passed to this function because it is not needed) and aLimitAncestor + * is the parent/containing block of f. + */ + static const ActiveScrolledRoot* ActivateDisplayportOnASRAncestors( + nsIFrame* aAnchor, nsIFrame* aLimitAncestor, + const ActiveScrolledRoot* aASRofLimitAncestor, + nsDisplayListBuilder* aBuilder); }; } // namespace mozilla diff --git a/layout/generic/ScrollContainerFrame.cpp b/layout/generic/ScrollContainerFrame.cpp @@ -4343,6 +4343,24 @@ nsRect ScrollContainerFrame::RestrictToRootDisplayPort( return aFrame->PresShell()->GetRootPresShell()->HasSeenAnchorPos(); } +bool ScrollContainerFrame::DecideScrollableLayerEnsureDisplayport( + nsDisplayListBuilder* aBuilder) { + MOZ_ASSERT(ShouldActivateAllScrollFrames(aBuilder, this)); + nsIContent* content = GetContent(); + bool hasDisplayPort = DisplayPortUtils::HasDisplayPort(content); + + // Note this intentionally differs from DecideScrollableLayer below by not + // checking ShouldActivateAllScrollFrames. + if (!hasDisplayPort && aBuilder->IsPaintingToWindow() && + nsLayoutUtils::AsyncPanZoomEnabled(this) && WantAsyncScroll()) { + DisplayPortUtils::SetMinimalDisplayPortDuringPainting(content, PresShell()); + hasDisplayPort = true; + } + + mWillBuildScrollableLayer = hasDisplayPort || mZoomableByAPZ; + return mWillBuildScrollableLayer; +} + bool ScrollContainerFrame::DecideScrollableLayer( nsDisplayListBuilder* aBuilder, nsRect* aVisibleRect, nsRect* aDirtyRect, bool aSetBase, bool* aDirtyRectHasBeenOverriden) { @@ -4362,18 +4380,7 @@ bool ScrollContainerFrame::DecideScrollableLayer( if (aSetBase && !hasDisplayPort && aBuilder->IsPaintingToWindow() && ShouldActivateAllScrollFrames(aBuilder, this) && nsLayoutUtils::AsyncPanZoomEnabled(this) && WantAsyncScroll()) { - // SetDisplayPortMargins calls TriggerDisplayPortExpiration which starts a - // display port expiry timer for display ports that do expire. However - // minimal display ports do not expire, so the display port has to be - // marked before the SetDisplayPortMargins call so the expiry timer - // doesn't get started. - content->SetProperty(nsGkAtoms::MinimalDisplayPort, - reinterpret_cast<void*>(true)); - - DisplayPortUtils::SetDisplayPortMargins( - content, PresShell(), DisplayPortMargins::Empty(content), - DisplayPortUtils::ClearMinimalDisplayPortProperty::No, 0, - DisplayPortUtils::RepaintMode::DoNotRepaint); + DisplayPortUtils::SetMinimalDisplayPortDuringPainting(content, PresShell()); hasDisplayPort = true; } diff --git a/layout/generic/ScrollContainerFrame.h b/layout/generic/ScrollContainerFrame.h @@ -738,6 +738,16 @@ class ScrollContainerFrame : public nsContainerFrame, } /** + * Like DecideScrollableLayer but skips computing dirty/visible rects and + * setting the base rect. In addition, this always ensures a display port is + * set if this scroll frame could async scroll. DecideScrollableLayer only + * sets a display port in that situation if ShouldActivateAllScrollFrames + * returns true. However you should only call this function is + * ShouldActivateAllScrollFrames returns true anyways. + * */ + bool DecideScrollableLayerEnsureDisplayport(nsDisplayListBuilder* aBuilder); + + /** * Notify the scrollframe that the current scroll offset and origin have been * sent over in a layers transaction. *