tor-browser

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

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

Bug 1988030. Create nsLayoutUtils::GetASRAncestorFrame and OneStepInASRChain that walks only scroll frames that are currently async scrollable in the ASR order. r=botond,hiro,layout-reviewers,layout-anchor-positioning-reviewers,emilio

In subsequent patches we need to match the ASR tree which is different from the "async scrollable ancestor chain", so we need these functions.

WantAsyncScroll and mWillBuildScrollableLayer are completely independent of each other, ie it's possible to have all four combinations of them being true/false.

ASR's don't walked from fixed pos to root scroll frame, that behaviour is for other reasons that don't apply to ASRs.

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

Diffstat:
Mlayout/base/DisplayPortUtils.cpp | 17+++++++++++++++++
Mlayout/base/DisplayPortUtils.h | 10++++++++++
Mlayout/base/nsLayoutUtils.cpp | 52+++++++++++++++++++++++++++++++++++++++++++++++++---
Mlayout/base/nsLayoutUtils.h | 39++++++++++++++++++++++++++++++++++++++-
4 files changed, 114 insertions(+), 4 deletions(-)

diff --git a/layout/base/DisplayPortUtils.cpp b/layout/base/DisplayPortUtils.cpp @@ -10,6 +10,7 @@ #include "FrameMetrics.h" #include "RetainedDisplayListBuilder.h" +#include "StickyScrollContainer.h" #include "WindowRenderer.h" #include "mozilla/PresShell.h" #include "mozilla/ScrollContainerFrame.h" @@ -855,6 +856,22 @@ nsIFrame* DisplayPortUtils::OneStepInAsyncScrollableAncestorChain( return nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame); } +nsIFrame* DisplayPortUtils::OneStepInASRChain(nsIFrame* aFrame) { + // This mirrors one iteration of GetNearestScrollableOrOverflowClipFrame in + // nsLayoutUtils.cpp as called by nsLayoutUtils::GetASRAncestorFrame. They + // should be kept in sync. See that function for comments about the structure + // of this code. + if (aFrame->IsMenuPopupFrame()) { + return nullptr; + } + nsIFrame* anchor = nullptr; + while ((anchor = + AnchorPositioningUtils::GetAnchorThatFrameScrollsWith(aFrame))) { + aFrame = anchor; + } + return nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame); +} + void DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors( nsIFrame* aFrame) { nsIFrame* frame = aFrame; diff --git a/layout/base/DisplayPortUtils.h b/layout/base/DisplayPortUtils.h @@ -289,6 +289,16 @@ class DisplayPortUtils { static nsIFrame* OneStepInAsyncScrollableAncestorChain(nsIFrame* aFrame); /** + * Step up one frame in the ASR chain, to be used in conjunction with + * GetASRAncestorFrame to walk the ASR chain. Note this doesn't go from one + * ASR frame to the next. Rather this walks all frame types, taking only one + * ancestor step per call. Note that a frame returned from this function could + * generate two ASRs: an inner one corresponding to an activated scroll frame, + * and an outer one corresponding to sticky pos. + */ + static nsIFrame* OneStepInASRChain(nsIFrame* aFrame); + + /** * Sets a zero margin display port on all proper ancestors of aFrame that * are async scrollable. */ diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp @@ -1318,6 +1318,11 @@ static nsIFrame* GetNearestScrollableOrOverflowClipFrame( MOZ_ASSERT( aFrame, "GetNearestScrollableOrOverflowClipFrame expects a non-null frame"); + // Only one of these two flags can be set at a time. + MOZ_ASSERT_IF(aFlags & nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE, + !(aFlags & nsLayoutUtils::SCROLLABLE_ONLY_ASRS)); + MOZ_ASSERT_IF(aFlags & nsLayoutUtils::SCROLLABLE_ONLY_ASRS, + !(aFlags & nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE)); auto GetNextFrame = [aFlags](const nsIFrame* aFrame) -> nsIFrame* { return (aFlags & nsLayoutUtils::SCROLLABLE_SAME_DOC) @@ -1326,7 +1331,8 @@ static nsIFrame* GetNearestScrollableOrOverflowClipFrame( }; // This should be kept in sync with - // DisplayPortUtils::OneStepInAsyncScrollableAncestorChain. + // DisplayPortUtils::OneStepInAsyncScrollableAncestorChain and + // DisplayPortUtils::OneStepInASRChain. for (nsIFrame* f = aFrame; f; f = GetNextFrame(f)) { if (aClipFrameCheck && aClipFrameCheck(f)) { return f; @@ -1338,7 +1344,8 @@ static nsIFrame* GetNearestScrollableOrOverflowClipFrame( // TODO: We should also stop at popup frames other than // SCROLLABLE_ONLY_ASYNC_SCROLLABLE cases. - if ((aFlags & nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE) && + if ((aFlags & (nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE | + nsLayoutUtils::SCROLLABLE_ONLY_ASRS)) && f->IsMenuPopupFrame()) { break; } @@ -1348,6 +1355,10 @@ static nsIFrame* GetNearestScrollableOrOverflowClipFrame( if (scrollContainerFrame->WantAsyncScroll()) { return f; } + } else if (aFlags & nsLayoutUtils::SCROLLABLE_ONLY_ASRS) { + if (scrollContainerFrame->IsMaybeAsynchronouslyScrolled()) { + return f; + } } else { ScrollStyles ss = scrollContainerFrame->GetScrollStyles(); if ((aFlags & nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN) || @@ -1381,13 +1392,29 @@ static nsIFrame* GetNearestScrollableOrOverflowClipFrame( // (because of that same reason), and that moving up one will happen either // via the special fixed pos behaviour below or the next iteration of the // outer for loop. - if (aFlags & nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE) { + if (aFlags & (nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE | + nsLayoutUtils::SCROLLABLE_ONLY_ASRS)) { while ( (anchor = AnchorPositioningUtils::GetAnchorThatFrameScrollsWith(f))) { f = anchor; } } + // Note that the order of checking for anchors and sticky pos is significant + // even though a frame can't be both sticky pos and anchored (because + // anchoring requires abs pos). However, if we follow an anchor, the anchor + // could be an active sticky pos, so that would generate an ASR and we want + // to return that rather than do another iteration of the outer for loop + // which moves on to the (crossdoc) parent frame. + if (aFlags & nsLayoutUtils::SCROLLABLE_ONLY_ASRS) { + if (f->StyleDisplay()->mPosition == StylePositionProperty::Sticky) { + auto* ssc = StickyScrollContainer::GetOrCreateForFrame(f); + if (ssc && ssc->ScrollContainer()->IsMaybeAsynchronouslyScrolled()) { + return f; + } + } + } + if ((aFlags & nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT) && f->StyleDisplay()->mPosition == StylePositionProperty::Fixed && nsLayoutUtils::IsReallyFixedPos(f)) { @@ -1402,6 +1429,10 @@ static nsIFrame* GetNearestScrollableOrOverflowClipFrame( // static ScrollContainerFrame* nsLayoutUtils::GetNearestScrollContainerFrame( nsIFrame* aFrame, uint32_t aFlags) { + // Not suitable to use SCROLLABLE_ONLY_ASRS here because it needs to return + // non-scroll frames. + MOZ_ASSERT(!(aFlags & SCROLLABLE_ONLY_ASRS), + "can't use SCROLLABLE_ONLY_ASRS flag"); nsIFrame* found = GetNearestScrollableOrOverflowClipFrame(aFrame, aFlags); if (!found) { return nullptr; @@ -2630,6 +2661,21 @@ ScrollContainerFrame* nsLayoutUtils::GetAsyncScrollableAncestorFrame( return nsLayoutUtils::GetNearestScrollContainerFrame(aTarget, flags); } +nsIFrame* nsLayoutUtils::GetASRAncestorFrame(nsIFrame* aTarget, + nsDisplayListBuilder* aBuilder) { + MOZ_ASSERT(aBuilder->IsPaintingToWindow()); + // We use different flags from GetAsyncScrollableAncestorFrame above because + // the ASR tree is different from the "async scrollable ancestor chain". We + // don't want SCROLLABLE_ALWAYS_MATCH_ROOT because we only want to match the + // root if it generates an ASR. We don't want SCROLLABLE_FIXEDPOS_FINDS_ROOT + // because the ASR tree does not jump from fixed pos to root (that behaviour + // exists so that fixed pos in the root document in the process can find some + // apzc, ASRs have no such need and that would be incorrect). + // This should be kept in sync with DisplayPortUtils::OneStepInASRChain. + uint32_t flags = nsLayoutUtils::SCROLLABLE_ONLY_ASRS; + return GetNearestScrollableOrOverflowClipFrame(aTarget, flags); +} + void nsLayoutUtils::AddExtraBackgroundItems(nsDisplayListBuilder* aBuilder, nsDisplayList* aList, nsIFrame* aFrame, diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h @@ -603,12 +603,22 @@ class nsLayoutUtils { */ SCROLLABLE_FIXEDPOS_FINDS_ROOT = 0x10, /** + * If the SCROLLABLE_ONLY_ASRS flag is set, then we only want to return + * frames that will generate an ASR. This means that they are either + * scrollable frames for which IsMaybeAsynchronouslyScrolled() returns true + * (aka mWillBuildScrollableLayer == true) or they are sticky position + * frames for which their corresponding scroll frame will generate an ASR. + * This is an internal only flag, you cannot pass it to + * GetNearestScrollContainerFrame since that can only return scroll frames. + */ + SCROLLABLE_ONLY_ASRS = 0x20, + /** * If the SCROLLABLE_STOP_AT_PAGE flag is set, then we stop searching * for scrollable ancestors when seeing a nsPageFrame. This can be used * to avoid finding the viewport scroll frame in Print Preview (which * would be undesirable as a 'position:sticky' container for content). */ - SCROLLABLE_STOP_AT_PAGE = 0x20, + SCROLLABLE_STOP_AT_PAGE = 0x40, }; /** * GetNearestScrollContainerFrame locates the first ancestor of aFrame @@ -2890,8 +2900,35 @@ class nsLayoutUtils { static FrameMetrics CalculateBasicFrameMetrics( mozilla::ScrollContainerFrame* aScrollContainerFrame); + /** + * Follows the async scrollable ancestor chain of scroll frames to find + * scroll frames that WantAsyncScroll(). This is used when activating scroll + * frames and finding APZCs. + */ static mozilla::ScrollContainerFrame* GetAsyncScrollableAncestorFrame( nsIFrame* aTarget); + /** + * Follows the ASR (ActiveScrolledRoot) chain of frames, so that if + * f is the frame of an ASR A, then calling this function on + * OneStepInASRChain(f) will return the frame of parent ASR of A. Frames that + * generate an ASR are scroll frames for which IsMaybeAsynchronouslyScrolled() + * returns true (aka mWillBuildScrollableLayer == true) or they are sticky + * position frames for which their corresponding scroll frame will generate an + * ASR. This function is different from GetAsyncScrollableAncestorFrame above + * because GetAsyncScrollableAncestorFrame looks only for scroll frames that + * WantAsyncScroll that that function walks from fixed pos to the root scroll + * frame. Because that status (ie mWillBuildScrollableLayer) can change this + * should only be called during a paint to the window after BuildDisplayList + * has been called on aTarget so that mWillBuildScrollableLayer will have been + * updated for this paint already for any frame we need to consult. Or for + * some other reason you know that mWillBuildScrollableLayer is up to date for + * this paint for any frame that might need to be consulted, ie you just + * updated them yourself. Note that a frame returned from this function could + * generate two ASRs: an inner one corresponding to an activated scroll frame, + * and an outer one corresponding to sticky pos. + */ + static nsIFrame* GetASRAncestorFrame(nsIFrame* aTarget, + nsDisplayListBuilder* aBuilder); static void SetBSizeFromFontMetrics( const nsIFrame* aFrame, mozilla::ReflowOutput& aMetrics,