tor-browser

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

commit 66835a87f54407fb0f3e440121902291c4a8c214
parent c2b1b7cfc8891e6cc7a1716ffe523453ca395eed
Author: Jari Jalkanen <jjalkanen@mozilla.com>
Date:   Mon, 13 Oct 2025 12:32:51 +0000

Bug 1924210 - Implement basic support for implicit anchors. r=layout-anchor-positioning-reviewers,firefox-style-system-reviewers,layout-reviewers,dshin

This patch deals with anchor function and basic fully styleable pseudo element support.
Popovers, position-areas and place-selfs are not yet handled.

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

Diffstat:
Mlayout/base/AnchorPositioningUtils.cpp | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mlayout/base/AnchorPositioningUtils.h | 26++++++++++++++++++++++++++
Mlayout/base/PresShell.cpp | 42++++++++++++++++++++++++++++++++----------
Mlayout/style/ComputedStyle.cpp | 5+++++
Mlayout/style/GeckoBindings.cpp | 30+++++++++++++-----------------
Dtesting/web-platform/meta/css/css-anchor-position/anchor-function-pseudo-element-implicit-anchor.html.ini | 6------
Dtesting/web-platform/meta/css/css-anchor-position/position-area-pseudo-element-implicit-anchor-dynamic.html.ini | 2--
Dtesting/web-platform/meta/css/css-anchor-position/position-area-pseudo-element-implicit-anchor.html.ini | 2--
Mtesting/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-non-intervening-container.html.ini | 3++-
Dtesting/web-platform/meta/css/css-anchor-position/pseudo-element-implicit-anchor.html.ini | 2--
Mtesting/web-platform/meta/html/semantics/popovers/popover-anchor-nested-display.tentative.html.ini | 2+-
Dtesting/web-platform/meta/html/semantics/the-button-element/command-and-commandfor/on-popover-behavior.html.ini | 3---
Mxpcom/ds/StaticAtoms.py | 2++
13 files changed, 146 insertions(+), 48 deletions(-)

diff --git a/layout/base/AnchorPositioningUtils.cpp b/layout/base/AnchorPositioningUtils.cpp @@ -787,14 +787,15 @@ nsRect AnchorPositioningUtils::AdjustAbsoluteContainingBlockRectForPositionArea( const StylePositionTryFallbacksTryTactic* aFallbackTactic) { // TODO: We need a single, unified way of getting the anchor, unifying // GetUsedAnchorName etc. - const auto& defaultAnchor = - aPositionedFrame->StylePosition()->mPositionAnchor; - if (!defaultAnchor.IsIdent()) { + const nsAtom* anchorName = + AnchorPositioningUtils::GetUsedAnchorName(aPositionedFrame, nullptr); + if (!anchorName) { return aCBRect; } - const nsAtom* anchorName = defaultAnchor.AsIdent().AsAtom(); nsRect anchorRect; + MOZ_ASSERT_IF(aPositionedFrame->HasAnchorPosReference(), + aAnchorPosReferenceData); const auto result = aAnchorPosReferenceData->InsertOrModify(anchorName, true); if (result.mAlreadyResolved) { MOZ_ASSERT(result.mEntry, "Entry exists but null?"); @@ -907,4 +908,64 @@ void DeleteAnchorPosReferenceData(AnchorPosReferenceData* aData) { delete aData; } +const nsAtom* AnchorPositioningUtils::GetUsedAnchorName( + const nsIFrame* aPositioned, const nsAtom* aAnchorName) { + if (aAnchorName && !aAnchorName->IsEmpty()) { + return aAnchorName; + } + + const auto defaultAnchor = aPositioned->StylePosition()->mPositionAnchor; + if (defaultAnchor.IsIdent()) { + return defaultAnchor.AsIdent().AsAtom(); + } + + if (aPositioned->Style()->IsPseudoElement()) { + return nsGkAtoms::AnchorPosImplicitAnchor; + } + + if (const nsIContent* content = aPositioned->GetContent()) { + if (const auto* element = content->AsElement()) { + if (element->GetPopoverData()) { + return nsGkAtoms::AnchorPosImplicitAnchor; + } + } + } + + return nullptr; +} + +const nsIFrame* AnchorPositioningUtils::GetAnchorPosImplicitAnchor( + const nsIFrame* aFrame) { + const auto* frameContent = aFrame->GetContent(); + const bool hasElement = frameContent && frameContent->IsElement(); + if (!aFrame->Style()->IsPseudoElement() && !hasElement) { + return nullptr; + } + + if (MOZ_LIKELY(hasElement)) { + const auto* element = frameContent->AsElement(); + MOZ_ASSERT(element); + const dom::PopoverData* popoverData = element->GetPopoverData(); + if (MOZ_UNLIKELY(popoverData)) { + if (const RefPtr<dom::Element>& invoker = popoverData->GetInvoker()) { + return invoker->GetPrimaryFrame(); + } + } + } + + const auto* pseudoRoot = aFrame->GetClosestNativeAnonymousSubtreeRoot(); + if (!pseudoRoot) { + return nullptr; + } + + const auto* pseudoRootFrame = pseudoRoot->GetPrimaryFrame(); + if (!pseudoRootFrame) { + return nullptr; + } + + return pseudoRootFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) + ? pseudoRootFrame->GetPlaceholderFrame()->GetParent() + : pseudoRootFrame->GetParent(); +} + } // namespace mozilla diff --git a/layout/base/AnchorPositioningUtils.h b/layout/base/AnchorPositioningUtils.h @@ -110,6 +110,32 @@ struct AnchorPositioningUtils { const nsRect& aCBRect, AnchorPosReferenceData* aAnchorPosReferenceData, const StylePositionArea& aPositionArea, const StylePositionTryFallbacksTryTactic* aFallbackTactic); + + /** + * Gets the used anchor name for an anchor positioned frame. + * + * @param aPositioned The anchor positioned frame. + * @param aAnchorName The anchor name specified in the anchor function, + * or nullptr if not specified. + * + * If `aAnchorName` is not specified, then this function will return the + * default anchor name, if the `position-anchor` property specified one. + * Otherwise it will return `nsGkAtoms::AnchorPosImplicitAnchor` if the + * element has an implicit anchor, or a nullptr. + */ + static const nsAtom* GetUsedAnchorName(const nsIFrame* aPositioned, + const nsAtom* aAnchorName); + + /** + * Get the implicit anchor of the frame. + * + * @param aFrame The anchor positioned frame. + * + * For pseudo-elements, this returns the parent frame of the originating + * element. For popovers, this returns the primary frame of the invoker. In + * all other cases, returns null. + */ + static const nsIFrame* GetAnchorPosImplicitAnchor(const nsIFrame* aFrame); }; } // namespace mozilla diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp @@ -12038,7 +12038,11 @@ nsIFrame* PresShell::GetAbsoluteContainingBlock(nsIFrame* aFrame) { const nsIFrame* PresShell::GetAnchorPosAnchor( const nsAtom* aName, const nsIFrame* aPositionedFrame) const { MOZ_ASSERT(aName); + MOZ_ASSERT(!aName->IsEmpty()); MOZ_ASSERT(mLazyAnchorPosAnchorChanges.IsEmpty()); + if (aName == nsGkAtoms::AnchorPosImplicitAnchor) { + return AnchorPositioningUtils::GetAnchorPosImplicitAnchor(aPositionedFrame); + } if (const auto& entry = mAnchorPosAnchors.Lookup(aName)) { return AnchorPositioningUtils::FindFirstAcceptableAnchor( aName, aPositionedFrame, entry.Data()); @@ -12311,13 +12315,6 @@ void PresShell::UpdateAnchorPosLayoutForScroll( continue; } - const auto* stylePos = positioned->StylePosition(); - if (!stylePos->mPositionAnchor.IsIdent()) { - // If it doesn't have a default anchor then it doesn't compensate for - // scroll. - continue; - } - const auto* anchorPosReferenceData = positioned->GetProperty(nsIFrame::AnchorPosReferences()); if (!anchorPosReferenceData || anchorPosReferenceData->IsEmpty()) { @@ -12327,11 +12324,14 @@ void PresShell::UpdateAnchorPosLayoutForScroll( } const nsAtom* defaultAnchorName = - stylePos->mPositionAnchor.AsIdent().AsAtom(); + AnchorPositioningUtils::GetUsedAnchorName(positioned, nullptr); + if (!defaultAnchorName) { + continue; + } // We might not need to do this GetAnchorPosAnchor call at all if none of // the affected anchors are referenced by positioned below. We could // improve this. - auto* defaultAnchorFrame = + const nsIFrame* defaultAnchorFrame = GetAnchorPosAnchor(defaultAnchorName, positioned); if (!defaultAnchorFrame) { continue; @@ -12349,6 +12349,28 @@ void PresShell::UpdateAnchorPosLayoutForScroll( continue; } + const auto* stylePos = positioned->StylePosition(); + if (!stylePos->mPositionAnchor.IsIdent()) { + auto* nearestScrollFrame = FindScrollContainerFrameOf(defaultAnchorFrame); + if (!nearestScrollFrame) { + // Fixed-pos anchor. + continue; + } + if (!UnderScrollContainer(nearestScrollFrame, aScrollContainer)) { + continue; + } + const auto* data = anchorPosReferenceData->Lookup(defaultAnchorName); + if (!data) { + continue; + } + if (NeedReflowForAnchorPos(defaultAnchorFrame, positioned, *data)) { + // Abspos frames should not affect ancestor intrinsics. + FrameNeedsReflow(positioned, IntrinsicDirty::None, + NS_FRAME_HAS_DIRTY_CHILDREN); + } + continue; + } + for (const auto& entry : affectedAnchors) { const auto* anchorName = entry.mAnchorName; const auto& anchors = entry.mFrames; @@ -12361,7 +12383,7 @@ void PresShell::UpdateAnchorPosLayoutForScroll( ? defaultAnchorFrame : GetAnchorPosAnchor(anchorName, positioned); const auto idx = anchors.IndexOf(anchorFrame, 0, Comparator{}); - if (idx == anchors.NoIndex) { + if (idx == nsTArray<AffectedAnchor>::NoIndex) { // Referring to an anchor of the same name but unaffected by scrolling - // skip. continue; diff --git a/layout/style/ComputedStyle.cpp b/layout/style/ComputedStyle.cpp @@ -440,6 +440,11 @@ bool ComputedStyle::HasAnchorPosReference() const { return true; } + if (!pos->mPositionArea.IsNone()) { + // Position area is relative to an anchor. + return true; + } + // Now check if any property that can use anchor() or anchor-size() // does use any. Note that it's valid to specify e.g. left: anchor(left); // but without specifying position-anchor, in which case the function diff --git a/layout/style/GeckoBindings.cpp b/layout/style/GeckoBindings.cpp @@ -1847,22 +1847,9 @@ static bool AnchorSideUsesCBWM( return false; } -static const nsAtom* GetUsedAnchorName(const nsIFrame* aPositioned, - const nsAtom* aAnchorName) { - if (aAnchorName && !aAnchorName->IsEmpty()) { - return aAnchorName; - } - const auto* stylePos = aPositioned->StylePosition(); - if (!stylePos->mPositionAnchor.IsIdent()) { - // No valid anchor specified, bail. - // TODO(dshin): Implicit anchor should be looked at here. - return nullptr; - } - return stylePos->mPositionAnchor.AsIdent().AsAtom(); -} - static const nsIFrame* GetAnchorOf(const nsIFrame* aPositioned, const nsAtom* aAnchorName) { + MOZ_ASSERT(aPositioned, "Must have a positioned frame"); const auto* presShell = aPositioned->PresShell(); MOZ_ASSERT(presShell, "No PresShell for frame?"); return presShell->GetAnchorPosAnchor(aAnchorName, aPositioned); @@ -1875,7 +1862,8 @@ static Maybe<AnchorPosInfo> GetAnchorPosRect( return Nothing{}; } - const auto* anchorName = GetUsedAnchorName(aPositioned, aAnchorName); + const auto* anchorName = + AnchorPositioningUtils::GetUsedAnchorName(aPositioned, aAnchorName); if (!anchorName) { return Nothing{}; } @@ -1885,6 +1873,7 @@ static Maybe<AnchorPosInfo> GetAnchorPosRect( const auto* containingBlock = aPositioned->GetParent(); Maybe<AnchorPosResolutionData>* entry = nullptr; + if (aReferenceData) { const auto result = aReferenceData->InsertOrModify(anchorName, true); if (result.mAlreadyResolved) { @@ -1918,8 +1907,14 @@ bool Gecko_GetAnchorPosOffset(const AnchorPosOffsetResolutionParams* aParams, if (!aParams || !aParams->mBaseParams.mFrame) { return false; } + + const auto* anchorName = AnchorPositioningUtils::GetUsedAnchorName( + aParams->mBaseParams.mFrame, aAnchorName); + + // Note: No exit on null anchorName: Instead, GetAnchorPosRect may return the + // containing block. const auto info = GetAnchorPosRect( - aParams->mBaseParams.mFrame, aAnchorName, !aParams->mCBSize, + aParams->mBaseParams.mFrame, anchorName, !aParams->mCBSize, aParams->mBaseParams.mAnchorPosReferenceData); if (info.isNothing()) { return false; @@ -2004,7 +1999,8 @@ bool Gecko_GetAnchorPosSize(const AnchorPosResolutionParams* aParams, } const auto* positioned = aParams->mFrame; - const auto* anchorName = GetUsedAnchorName(positioned, aAnchorName); + const auto* anchorName = + AnchorPositioningUtils::GetUsedAnchorName(positioned, aAnchorName); if (!anchorName) { return false; } diff --git a/testing/web-platform/meta/css/css-anchor-position/anchor-function-pseudo-element-implicit-anchor.html.ini b/testing/web-platform/meta/css/css-anchor-position/anchor-function-pseudo-element-implicit-anchor.html.ini @@ -1,6 +0,0 @@ -[anchor-function-pseudo-element-implicit-anchor.html] - [The implicit anchor element of a pseudo-element is its originating element] - expected: FAIL - - [Anchored position after moving] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-area-pseudo-element-implicit-anchor-dynamic.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-area-pseudo-element-implicit-anchor-dynamic.html.ini @@ -1,2 +0,0 @@ -[position-area-pseudo-element-implicit-anchor-dynamic.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-area-pseudo-element-implicit-anchor.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-area-pseudo-element-implicit-anchor.html.ini @@ -1,2 +0,0 @@ -[position-area-pseudo-element-implicit-anchor.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-non-intervening-container.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-visibility-anchors-visible-non-intervening-container.html.ini @@ -1,3 +1,4 @@ [position-visibility-anchors-visible-non-intervening-container.html] fuzzy: - if (os == "win"): maxDifference=92;totalPixels=0-1 + if os == "win": maxDifference=92;totalPixels=0-1 + expected: [FAIL, PASS] diff --git a/testing/web-platform/meta/css/css-anchor-position/pseudo-element-implicit-anchor.html.ini b/testing/web-platform/meta/css/css-anchor-position/pseudo-element-implicit-anchor.html.ini @@ -1,2 +0,0 @@ -[pseudo-element-implicit-anchor.html] - expected: FAIL diff --git a/testing/web-platform/meta/html/semantics/popovers/popover-anchor-nested-display.tentative.html.ini b/testing/web-platform/meta/html/semantics/popovers/popover-anchor-nested-display.tentative.html.ini @@ -1,2 +1,2 @@ [popover-anchor-nested-display.tentative.html] - expected: FAIL + expected: [FAIL, PASS] diff --git a/testing/web-platform/meta/html/semantics/the-button-element/command-and-commandfor/on-popover-behavior.html.ini b/testing/web-platform/meta/html/semantics/the-button-element/command-and-commandfor/on-popover-behavior.html.ini @@ -1,3 +0,0 @@ -[on-popover-behavior.html] - [invoking (as show-popover) should create an implicit anchor reference for anchor positioning.] - expected: FAIL diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py @@ -2517,6 +2517,8 @@ STATIC_ATOMS = [ Atom("_ua_view_transition_fade_out", "-ua-view-transition-fade-out"), Atom("_ua_view_transition_fade_in", "-ua-view-transition-fade-in"), Atom("_ua_mix_blend_mode_plus_lighter", "-ua-mix-blend-mode-plus-lighter"), + # CSS anchor positioning implicit anchor + Atom("AnchorPosImplicitAnchor", "AnchorPosImplicitAnchor"), # CSS pseudo-elements -- these must appear in the same order as # in nsCSSPseudoElementList.h PseudoElementAtom("PseudoElement_after", ":after"),