tor-browser

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

commit 21742c0ed633238f6fed943adfbc2e827bd65eb9
parent 94f504f8109e6432d71ba52fc73de934cdbe6205
Author: Tiaan Louw <tlouw@mozilla.com>
Date:   Wed, 26 Nov 2025 21:31:02 +0000

Bug 1987965 - Trigger layout in ResizeObserver loop on scroll-based overflow r=dshin,layout-reviewers

Check whether positioned frames still fits their abs-cb and have any
fallbacks remaining, then trigger a layout.

Co-authored-by: David Shin <dshin@mozilla.com>
Co-authored-by: Emilio Cobos Álvarez <emilio@crisal.io>

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

Diffstat:
Mdom/base/Document.cpp | 13+++++++++++++
Mlayout/base/AnchorPositioningUtils.cpp | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Mlayout/base/AnchorPositioningUtils.h | 10++++++++++
Mlayout/generic/AbsoluteContainingBlock.cpp | 16++++++++--------
Mlayout/generic/nsIFrame.h | 7+++++--
Mtesting/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-006.html.ini | 4----
Mtesting/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-007.html.ini | 10+---------
Mtesting/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-008.html.ini | 13+------------
Mtesting/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-009.html.ini | 7-------
Mtesting/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-010.html.ini | 7+------
Dtesting/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-011.html.ini | 11-----------
Dtesting/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-012.html.ini | 4----
Dtesting/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-013.html.ini | 3---
Dtesting/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-014.html.ini | 3---
Mtesting/web-platform/meta/css/css-anchor-position/last-successful-animation.html.ini | 3---
Mtesting/web-platform/meta/css/css-anchor-position/last-successful-basic.html.ini | 3---
Dtesting/web-platform/meta/css/css-anchor-position/last-successful-fallback-to-base-style.html.ini | 6------
Mtesting/web-platform/meta/css/css-anchor-position/last-successful-iframe.html.ini | 3---
Mtesting/web-platform/meta/css/css-anchor-position/last-successful-pseudo-element-basic.html.ini | 2+-
Atesting/web-platform/meta/css/css-anchor-position/last-successful-pseudo-element-fallbacks.html.ini | 3+++
Mtesting/web-platform/meta/css/css-anchor-position/position-area-scrolling-006.html.ini | 3---
Mtesting/web-platform/meta/css/css-anchor-position/position-try-fallbacks-003.html.ini | 9+--------
22 files changed, 94 insertions(+), 96 deletions(-)

diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp @@ -19,6 +19,7 @@ #include <cstdint> #include <limits> +#include "AnchorPositioningUtils.h" #include "Attr.h" #include "ErrorList.h" #include "ExpandedPrincipal.h" @@ -18619,6 +18620,8 @@ void Document::DetermineProximityToViewportAndNotifyResizeObservers() { interruptible ? FlushType::InterruptibleLayout : FlushType::Layout, /* aFlushAnimations = */ false, /* aUpdateRelevancy = */ false); + bool initialAnchorOverflowDone = false; + // 2. While true: while (true) { // 2.1. Recalculate styles and update layout for doc. @@ -18647,6 +18650,16 @@ void Document::DetermineProximityToViewportAndNotifyResizeObservers() { // https://github.com/whatwg/html/issues/11210 for the timing of this. UpdateLastRememberedSizes(); + const bool evaluateAllFallbacksIfNeeded = !initialAnchorOverflowDone; + initialAnchorOverflowDone = true; + if (AnchorPositioningUtils::TriggerLayoutOnOverflow( + ps, evaluateAllFallbacksIfNeeded)) { + // If any of the anchor positioned items overflow its cb, then we trigger + // a layout for them. If we triggered for any item, we have to restart the + // loop to flush all layouts. + continue; + } + // 2.2. Let hadInitialVisibleContentVisibilityDetermination be false. // (this is part of "result"). // 2.3. For each element element with 'auto' used value of diff --git a/layout/base/AnchorPositioningUtils.cpp b/layout/base/AnchorPositioningUtils.cpp @@ -766,6 +766,10 @@ void DeleteAnchorPosReferenceData(AnchorPosReferenceData* aData) { delete aData; } +void DeleteLastSuccessfulPositionData(LastSuccessfulPositionData* aData) { + delete aData; +} + const nsAtom* AnchorPositioningUtils::GetUsedAnchorName( const nsIFrame* aPositioned, const nsAtom* aAnchorName) { if (aAnchorName && !aAnchorName->IsEmpty()) { @@ -904,4 +908,50 @@ nsIFrame* AnchorPositioningUtils::GetAnchorThatFrameScrollsWith( return anchor; } +bool AnchorPositioningUtils::TriggerLayoutOnOverflow( + PresShell* aPresShell, bool aEvaluateAllFallbacksIfNeeded) { + bool didLayoutPositionedItems = false; + + for (auto* positioned : aPresShell->GetAnchorPosPositioned()) { + AnchorPosReferenceData* referencedAnchors = + positioned->GetProperty(nsIFrame::AnchorPosReferences()); + if (NS_WARN_IF(!referencedAnchors)) { + continue; + } + + auto totalFallbacks = + positioned->StylePosition()->mPositionTryFallbacks._0.Length(); + if (!totalFallbacks) { + // No fallbacks specified. + continue; + } + + const bool positionedFitsInCB = + AnchorPositioningUtils::FitsInContainingBlock( + AnchorPositioningUtils::ContainingBlockInfo::UseCBFrameSize( + positioned), + positioned, referencedAnchors); + if (positionedFitsInCB) { + continue; + } + + // TODO(bug 1987964): Try to only do this when the scroll offset changes? + auto* lastSuccessfulPosition = + positioned->GetProperty(nsIFrame::LastSuccessfulPositionFallback()); + const bool needsRetry = + aEvaluateAllFallbacksIfNeeded || + (lastSuccessfulPosition && !lastSuccessfulPosition->mTriedAllFallbacks); + if (needsRetry) { + // We want to retry from the first position; remove the last position + // property so all potential positions are re-evaluated. + positioned->RemoveProperty(nsIFrame::LastSuccessfulPositionFallback()); + aPresShell->FrameNeedsReflow(positioned, mozilla::IntrinsicDirty::None, + NS_FRAME_IS_DIRTY); + didLayoutPositionedItems = true; + } + } + + return didLayoutPositionedItems; +} + } // namespace mozilla diff --git a/layout/base/AnchorPositioningUtils.h b/layout/base/AnchorPositioningUtils.h @@ -161,6 +161,11 @@ class AnchorPosReferenceData { mozilla::PhysicalAxes mCompensatingForScroll; }; +struct LastSuccessfulPositionData { + uint32_t mIndex = 0; + bool mTriedAllFallbacks = false; +}; + struct StylePositionArea; class WritingMode; @@ -315,6 +320,11 @@ struct AnchorPositioningUtils { * its anchor this function returns the anchor. Otherwise null. */ static nsIFrame* GetAnchorThatFrameScrollsWith(nsIFrame* aFrame); + + // Trigger a layout for positioned items that are currently overflowing their + // abs-cb and that have available fallbacks to try. + static bool TriggerLayoutOnOverflow(PresShell* aPresShell, + bool aEvaluateAllFallbacksIfNeeded); }; } // namespace mozilla diff --git a/layout/generic/AbsoluteContainingBlock.cpp b/layout/generic/AbsoluteContainingBlock.cpp @@ -1182,14 +1182,13 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( // TODO(emilio): Right now fallback only applies to position-area, which only // makes a difference with a default anchor... Generalize it? if (aAnchorPosResolutionCache) { - bool found = false; - uint32_t index = aKidFrame->GetProperty( - nsIFrame::LastSuccessfulPositionFallback(), &found); - if (found) { - if (!SeekFallbackTo(index)) { + const auto* lastSuccessfulPosition = + aKidFrame->GetProperty(nsIFrame::LastSuccessfulPositionFallback()); + if (lastSuccessfulPosition) { + if (!SeekFallbackTo(lastSuccessfulPosition->mIndex)) { aKidFrame->RemoveProperty(nsIFrame::LastSuccessfulPositionFallback()); } else { - firstTryIndex = Some(index); + firstTryIndex = Some(lastSuccessfulPosition->mIndex); } } } @@ -1614,8 +1613,9 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( StylePositionVisibility::NO_OVERFLOW); if (currentFallbackIndex) { - aKidFrame->SetProperty(nsIFrame::LastSuccessfulPositionFallback(), - *currentFallbackIndex); + aKidFrame->SetOrUpdateDeletableProperty( + nsIFrame::LastSuccessfulPositionFallback(), *currentFallbackIndex, + isOverflowingCB); } #ifdef DEBUG diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h @@ -140,6 +140,7 @@ enum class TableSelectionMode : uint32_t; class AbsoluteContainingBlock; class AnchorPosReferenceData; +struct LastSuccessfulPositionData; class EffectSet; class LazyLogModule; class nsDisplayItem; @@ -153,6 +154,7 @@ class WidgetGUIEvent; class WidgetMouseEvent; void DeleteAnchorPosReferenceData(AnchorPosReferenceData*); +void DeleteLastSuccessfulPositionData(LastSuccessfulPositionData*); struct PeekOffsetStruct; @@ -1446,8 +1448,9 @@ class nsIFrame : public nsQueryFrame { mozilla::DeleteAnchorPosReferenceData); // The last successful position-try-fallbacks index, if present. - NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(LastSuccessfulPositionFallback, - uint32_t); + NS_DECLARE_FRAME_PROPERTY_WITH_DTOR( + LastSuccessfulPositionFallback, mozilla::LastSuccessfulPositionData, + mozilla::DeleteLastSuccessfulPositionData); mozilla::PhysicalAxes GetAnchorPosCompensatingForScroll() const; diff --git a/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-006.html.ini b/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-006.html.ini @@ -1,7 +1,4 @@ [anchor-scroll-position-try-006.html] - [Should use the third position option with enough space below, and not enough above] - expected: FAIL - [Should use the second position option with enough space right] expected: FAIL @@ -11,4 +8,3 @@ [Should use the first position option with enough space below and right] expected: if (os == "mac") or (os == "android"): [PASS, FAIL] - FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-007.html.ini b/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-007.html.ini @@ -2,15 +2,7 @@ [Should use the third position option with enough space left] expected: FAIL - [Should use the second position option with enough space below (and not enough above)] - expected: FAIL - - [Should use the first position option with enough space left and below] - expected: - if (os == "mac") and not debug: [PASS, FAIL] - FAIL - [Should use the last position option initially] expected: - if (os == "android"): [PASS, FAIL] + if os == "android": [PASS, FAIL] FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-008.html.ini b/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-008.html.ini @@ -1,16 +1,5 @@ [anchor-scroll-position-try-008.html] - [Should use the third fallback position with enough space left] - expected: FAIL - - [Should use the second fallback position with enough space above] - expected: FAIL - - [Should use the first fallback position with enough space left and above] - expected: - if (os == "mac") and not debug: [PASS, FAIL] - FAIL - [Should use the last fallback position initially] expected: - if (os == "android"): [PASS, FAIL] + if os == "android": [PASS, FAIL] FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-009.html.ini b/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-009.html.ini @@ -1,15 +1,8 @@ [anchor-scroll-position-try-009.html] - [Should use the third fallback position with enough space right] - expected: FAIL - - [Should use the second fallback position with enough space below] - expected: FAIL - [Should use the first fallback position with enough space right and below] expected: if (os == "mac") and not debug: [PASS, FAIL] if os == "android": [PASS, FAIL] - FAIL [Should use the last fallback position initially] expected: diff --git a/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-010.html.ini b/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-010.html.ini @@ -2,17 +2,12 @@ [Should use the third fallback position with enough space right] expected: if os == "android": [PASS, FAIL] - FAIL - - [Should use the second fallback position with enough space above] - expected: FAIL [Should use the first fallback position with enough space right and above] expected: if (os == "mac") or (os == "android"): [PASS, FAIL] - FAIL [Should use the last fallback position initially] expected: - if (os == "android"): PASS + if os == "android": PASS FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-011.html.ini b/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-011.html.ini @@ -1,11 +0,0 @@ -[anchor-scroll-position-try-011.html] - [Should use the third fallback position with enough space above] - expected: FAIL - - [Should use the second fallback position with enough space right] - expected: FAIL - - [Should use the first fallback position with enough space above and right] - expected: - if (os == "mac") and not debug: [PASS, FAIL] - FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-012.html.ini b/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-012.html.ini @@ -1,4 +0,0 @@ -[anchor-scroll-position-try-012.html] - expected: - if (os == "win") and debug and not swgl: [FAIL, PASS] - FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-013.html.ini b/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-013.html.ini @@ -1,3 +0,0 @@ -[anchor-scroll-position-try-013.html] - [anchor-scroll-position-try-013 1] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-014.html.ini b/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-014.html.ini @@ -1,3 +0,0 @@ -[anchor-scroll-position-try-014.html] - [anchor-scroll-position-try-014 1] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/last-successful-animation.html.ini b/testing/web-platform/meta/css/css-anchor-position/last-successful-animation.html.ini @@ -1,6 +1,3 @@ [last-successful-animation.html] [No successful position, keep flip-block] expected: FAIL - - [Base position without fallback now successful] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/last-successful-basic.html.ini b/testing/web-platform/meta/css/css-anchor-position/last-successful-basic.html.ini @@ -1,6 +1,3 @@ [last-successful-basic.html] [No successful position, keep flip-block] expected: FAIL - - [Base position without fallback now successful] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/last-successful-fallback-to-base-style.html.ini b/testing/web-platform/meta/css/css-anchor-position/last-successful-fallback-to-base-style.html.ini @@ -1,6 +0,0 @@ -[last-successful-fallback-to-base-style.html] - [Base position without fallback now successful] - expected: FAIL - - [Both base position and flip-inline works, keep base position since it's the last successful option] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/last-successful-iframe.html.ini b/testing/web-platform/meta/css/css-anchor-position/last-successful-iframe.html.ini @@ -1,6 +1,3 @@ [last-successful-iframe.html] [No successful position, keep flip-block] expected: FAIL - - [Base position without fallback now successful] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/last-successful-pseudo-element-basic.html.ini b/testing/web-platform/meta/css/css-anchor-position/last-successful-pseudo-element-basic.html.ini @@ -1,3 +1,3 @@ [last-successful-pseudo-element-basic.html] - [Base position without fallback now successful] + [No successful position, keep flip-block] expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/last-successful-pseudo-element-fallbacks.html.ini b/testing/web-platform/meta/css/css-anchor-position/last-successful-pseudo-element-fallbacks.html.ini @@ -0,0 +1,3 @@ +[last-successful-pseudo-element-fallbacks.html] + [No successful position, keep flip-block] + expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-area-scrolling-006.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-area-scrolling-006.html.ini @@ -4,6 +4,3 @@ [Scroll to 150] expected: FAIL - - [Scroll to 100] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-try-fallbacks-003.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-try-fallbacks-003.html.ini @@ -1,6 +1,4 @@ [position-try-fallbacks-003.html] - expected: - if (os == "linux") and debug and fission: [OK, TIMEOUT] [initial position] expected: if os == "android": [FAIL, PASS] @@ -8,30 +6,25 @@ [scroll to 100] expected: - if (os == "linux") and debug and fission: [FAIL, TIMEOUT] if os == "android": [FAIL, PASS] FAIL [scroll to 101] expected: - if (os == "linux") and debug and fission: [FAIL, NOTRUN] FAIL [scroll back to 100] expected: - if (os == "linux") and debug and fission: [FAIL, NOTRUN] FAIL [redisplay at 100] expected: - if (os == "linux") and debug and fission: [FAIL, PASS, NOTRUN] FAIL [scroll to 300] expected: - if (os == "linux") and debug and fission: [PASS, FAIL, NOTRUN] + FAIL [scroll back to 0] expected: - if (os == "linux") and debug and fission: [FAIL, NOTRUN] FAIL