tor-browser

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

commit ee48e485917c6f256be21b2820d32ff91a5955e6
parent 3b30c384711efe66e18ad027c0312b22baba1c62
Author: David Shin <dshin@mozilla.com>
Date:   Thu, 11 Dec 2025 00:07:52 +0000

Bug 2003591: Resolve anchor() against modified CB. r=layout-anchor-positioning-reviewers,firefox-style-system-reviewers,layout-reviewers,emilio

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

Diffstat:
Mlayout/base/AnchorPositioningUtils.cpp | 2+-
Mlayout/base/AnchorPositioningUtils.h | 14++++++++++----
Mlayout/generic/AbsoluteContainingBlock.cpp | 11+++++++++--
Mlayout/style/GeckoBindings.cpp | 37++++++++++++++++++++++++++++---------
Dtesting/web-platform/meta/css/css-anchor-position/position-area-anchor-001.html.ini | 2--
Atesting/web-platform/tests/css/css-anchor-position/position-area-anchor-getComputedStyle.html | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 141 insertions(+), 18 deletions(-)

diff --git a/layout/base/AnchorPositioningUtils.cpp b/layout/base/AnchorPositioningUtils.cpp @@ -872,7 +872,7 @@ bool AnchorPositioningUtils::FitsInContainingBlock( const nsIFrame* aPositioned, const AnchorPosReferenceData& aReferenceData) { MOZ_ASSERT(aPositioned->GetProperty(nsIFrame::AnchorPosReferences()) == &aReferenceData); - return aReferenceData.mContainingBlockRect.Contains( + return aReferenceData.mOriginalContainingBlockRect.Contains( aPositioned->GetMarginRect()); } diff --git a/layout/base/AnchorPositioningUtils.h b/layout/base/AnchorPositioningUtils.h @@ -92,6 +92,7 @@ class AnchorPosReferenceData { struct PositionTryBackup { mozilla::PhysicalAxes mCompensatingForScroll; nsPoint mDefaultScrollShift; + nsRect mAdjustedContainingBlock; }; using Value = mozilla::Maybe<AnchorPosResolutionData>; @@ -126,21 +127,26 @@ class AnchorPosReferenceData { PositionTryBackup TryPositionWithSameDefaultAnchor() { auto compensatingForScroll = std::exchange(mCompensatingForScroll, {}); auto defaultScrollShift = std::exchange(mDefaultScrollShift, {}); - return {compensatingForScroll, defaultScrollShift}; + auto adjustedContainingBlock = std::exchange(mAdjustedContainingBlock, {}); + return {compensatingForScroll, defaultScrollShift, adjustedContainingBlock}; } void UndoTryPositionWithSameDefaultAnchor(PositionTryBackup&& aBackup) { mCompensatingForScroll = aBackup.mCompensatingForScroll; mDefaultScrollShift = aBackup.mDefaultScrollShift; + mAdjustedContainingBlock = aBackup.mAdjustedContainingBlock; } // Distance from the default anchor to the nearest scroll container. DistanceToNearestScrollContainer mDistanceToDefaultScrollContainer; // https://drafts.csswg.org/css-anchor-position-1/#default-scroll-shift nsPoint mDefaultScrollShift; - // Rect of containing block before being inset-modified, at the time of - // resolution. - nsRect mContainingBlockRect; + // Rect of the original containg block. + nsRect mOriginalContainingBlockRect; + // Adjusted containing block, by position-area or grid, as per + // https://drafts.csswg.org/css-position/#original-cb + // TODO(dshin, bug 2004596): "or" should be "and/or." + nsRect mAdjustedContainingBlock; // TODO(dshin, bug 1987962): Remembered scroll offset // https://drafts.csswg.org/css-anchor-position-1/#remembered-scroll-offset // Name of the default used anchor. Not necessarily positioned frame's diff --git a/layout/generic/AbsoluteContainingBlock.cpp b/layout/generic/AbsoluteContainingBlock.cpp @@ -1279,8 +1279,15 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( return ContainingBlockRect{aOriginalContainingBlockRect}; }(); if (aAnchorPosResolutionCache) { - aAnchorPosResolutionCache->mReferenceData->mContainingBlockRect = - cb.mMaybeScrollableRect; + const auto& originalCb = cb.mMaybeScrollableRect; + aAnchorPosResolutionCache->mReferenceData->mOriginalContainingBlockRect = + originalCb; + // Stash the adjusted containing block as well, since the insets need to + // resolve against the adjusted CB, e.g. With `position-area: bottom + // right;`, + `left: anchor(right);` + // resolves to 0. + aAnchorPosResolutionCache->mReferenceData->mAdjustedContainingBlock = + cb.mFinalRect; } const WritingMode outerWM = aReflowInput.GetWritingMode(); const WritingMode wm = aKidFrame->GetWritingMode(); diff --git a/layout/style/GeckoBindings.cpp b/layout/style/GeckoBindings.cpp @@ -1897,13 +1897,13 @@ bool Gecko_GetAnchorPosOffset(const AnchorPosOffsetResolutionParams* aParams, } const auto* positioned = aParams->mBaseParams.mFrame; const auto* containingBlock = positioned->GetParent(); + auto* cache = aParams->mBaseParams.mCache; const auto info = AnchorPositioningUtils::ResolveAnchorPosRect( - positioned, containingBlock, aAnchorName, !aParams->mCBSize, - aParams->mBaseParams.mCache); + positioned, containingBlock, aAnchorName, !aParams->mCBSize, cache); if (!info) { return false; } - if (info->mCompensatesForScroll && aParams->mBaseParams.mCache) { + if (info->mCompensatesForScroll && cache) { // Without cache (Containing information on default anchor) being available, // we woudln't be able to determine scroll compensation status. const auto axis = [aPropSide]() { @@ -1919,19 +1919,38 @@ bool Gecko_GetAnchorPosOffset(const AnchorPosOffsetResolutionParams* aParams, } return PhysicalAxis::Vertical; }(); - aParams->mBaseParams.mCache->mReferenceData->AdjustCompensatingForScroll( - axis); + cache->mReferenceData->AdjustCompensatingForScroll(axis); } // Compute the offset here in C++, where translating between physical/logical // coordinates is easier. - const auto& rect = info->mRect; + const auto usesCBWM = AnchorSideUsesCBWM(aAnchorSideKeyword); const auto cbwm = containingBlock->GetWritingMode(); const auto wm = usesCBWM ? aParams->mBaseParams.mFrame->GetWritingMode() : cbwm; - const auto logicalCBSize = aParams->mCBSize - ? aParams->mCBSize->ConvertTo(wm, cbwm) - : containingBlock->PaddingSize(wm); + const auto [rect, logicalCBSize] = [&] { + // We need `AnchorPosReferenceData` to compute the anchor offset against + // the adjusted CB, so make the best attempt to retrieve it. + // TODO(dshin, bug 2005207): We really need to unify containing block + // lookups and clean up cache lookups here. + const auto* referenceData = + cache ? cache->mReferenceData + : positioned->GetProperty(nsIFrame::AnchorPosReferences()); + if (!referenceData) { + return std::make_pair( + info->mRect, aParams->mCBSize ? aParams->mCBSize->ConvertTo(wm, cbwm) + : containingBlock->PaddingSize(wm)); + } + // Offset happens from padding rect. + const auto offset = referenceData->mAdjustedContainingBlock.TopLeft() - + referenceData->mOriginalContainingBlockRect.TopLeft(); + return std::make_pair( + info->mRect - offset, + aParams->mCBSize + ? aParams->mCBSize->ConvertTo(wm, cbwm) + : LogicalSize{cbwm, referenceData->mAdjustedContainingBlock.Size()} + .ConvertTo(wm, cbwm)); + }(); const LogicalRect logicalAnchorRect{wm, rect, logicalCBSize.GetPhysicalSize(wm)}; const auto logicalPropSide = wm.LogicalSideForPhysicalSide(ToSide(aPropSide)); diff --git a/testing/web-platform/meta/css/css-anchor-position/position-area-anchor-001.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-area-anchor-001.html.ini @@ -1,2 +0,0 @@ -[position-area-anchor-001.html] - expected: FAIL diff --git a/testing/web-platform/tests/css/css-anchor-position/position-area-anchor-getComputedStyle.html b/testing/web-platform/tests/css/css-anchor-position/position-area-anchor-getComputedStyle.html @@ -0,0 +1,93 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Tests that getComputedStyle() resolves anchor() functions w.r.t. position-area containing block</title> +<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-area"> +<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#anchor-pos"> +<link rel="author" name="David Shin" href="mailto:dshin@mozilla.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> +.abs-cb { + width: 200px; + height: 200px; + border: 5px solid; + position: relative; +} + +.anchor { + anchor-name: --a; + width: 50px; + height: 50px; + margin-left: 75px; + background: pink; +} + +.positioned { + position: absolute; + position-anchor: --a; + width: 25px; + height: 25px; + background: purple; +} + +.top { + bottom: anchor(top); +} + +.bottom { + top: anchor(bottom); +} + +.left { + right: anchor(left); +} + +.right { + left: anchor(right); +} + +.bottom.right { + position-area: bottom right; +} + +.bottom.left { + position-area: bottom left; +} + +.top.right { + position-area: top right; +} + +.top.left { + position-area: top left; +} +</style> +<div id=cb class=abs-cb> + <div style="height: 75px;"></div> + <div class=anchor></div> + <div id="top-right" class="positioned top right"></div> + <div id="bottom-right" class="positioned bottom right"></div> + <div id="bottom-left" class="positioned bottom left"></div> + <div id="top-left" class="positioned top left"></div> +</div> +<script> +const opposite = { + 'top': 'bottom', + 'bottom': 'top', + 'left': 'right', + 'right': 'left', +}; +function test_positioned(y, x) { + test(() => { + const d = document.getElementById(`${y}-${x}`); + const s = getComputedStyle(d); + assert_equals(s.getPropertyValue(opposite[y]), '0px'); + assert_equals(s.getPropertyValue(opposite[x]), '0px'); + }, `Offsets for ${y} ${x} positioned`); +} + +test_positioned('top', 'right'); +test_positioned('bottom', 'right'); +test_positioned('bottom', 'left'); +test_positioned('top', 'left'); +</script>