tor-browser

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

commit 9b3130e442de52c28e9016dffc7bcb278dbb58b3
parent 878edfaa957881203ae1f443eefd2de914232590
Author: David Shin <dshin@mozilla.com>
Date:   Thu, 30 Oct 2025 15:44:26 +0000

Bug 1987963: Find and reflow affected positioned frames when a frame becomes/stops being a scroll container. r=layout-anchor-positioning-reviewers,firefox-style-system-reviewers,layout-reviewers,emilio

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

Diffstat:
Mlayout/base/AnchorPositioningUtils.cpp | 45+++++++++++++++++++++++++++------------------
Mlayout/base/AnchorPositioningUtils.h | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mlayout/base/PresShell.cpp | 112++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mlayout/base/PresShell.h | 3+--
Mlayout/generic/AbsoluteContainingBlock.cpp | 6++++++
Atesting/web-platform/tests/css/css-anchor-position/anchor-scroll-update-008.html | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/css/css-anchor-position/anchor-scroll-update-009.html | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/css/css-anchor-position/anchor-scroll-update-010.html | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/css/css-anchor-position/reference/anchor-scroll-update-008-ref.html | 33+++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/css/css-anchor-position/reference/anchor-scroll-update-009-ref.html | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/css/css-anchor-position/reference/anchor-scroll-update-010-ref.html | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 548 insertions(+), 46 deletions(-)

diff --git a/layout/base/AnchorPositioningUtils.cpp b/layout/base/AnchorPositioningUtils.cpp @@ -386,12 +386,6 @@ const AnchorPosReferenceData::Value* AnchorPosReferenceData::Lookup( } AnchorPosDefaultAnchorCache::AnchorPosDefaultAnchorCache( - const nsIFrame* aAnchor) - : mAnchor{aAnchor}, - mScrollContainer{AnchorPositioningUtils::GetNearestScrollFrame(aAnchor)} { -} - -AnchorPosDefaultAnchorCache::AnchorPosDefaultAnchorCache( const nsIFrame* aAnchor, const nsIFrame* aScrollContainer) : mAnchor{aAnchor}, mScrollContainer{aScrollContainer} { MOZ_ASSERT_IF( @@ -542,6 +536,7 @@ Maybe<AnchorPosInfo> AnchorPositioningUtils::ResolveAnchorPosRect( GetAnchorPosRect(aAbsoluteContainingBlock, anchor, aCBRectIsvalid); return result.map([&](const nsRect& aRect) { bool compensatesForScroll = false; + DistanceToNearestScrollContainer distanceToNearestScrollContainer; if (aResolutionCache) { MOZ_ASSERT(entry); // Update the cache. @@ -550,15 +545,25 @@ Maybe<AnchorPosInfo> AnchorPositioningUtils::ResolveAnchorPosRect( if (!aAnchorName) { // Explicitly resolved default anchor for the first time - populate // the cache. - defaultAnchorCache = AnchorPosDefaultAnchorCache{anchor}; + defaultAnchorCache.mAnchor = anchor; + const auto [scrollContainer, distance] = + AnchorPositioningUtils::GetNearestScrollFrame(anchor); + distanceToNearestScrollContainer = distance; + defaultAnchorCache.mScrollContainer = scrollContainer; + aResolutionCache->mReferenceData->mDistanceToDefaultScrollContainer = + distance; + aResolutionCache->mReferenceData->mDefaultAnchorName = anchorName; + // This is the default anchor, so scroll compensated by definition. + return true; } if (defaultAnchorCache.mAnchor == anchor) { // This is referring to the default anchor, so scroll compensated by // definition. return true; } - const auto* scrollContainer = + const auto [scrollContainer, distance] = AnchorPositioningUtils::GetNearestScrollFrame(anchor); + distanceToNearestScrollContainer = distance; return scrollContainer == aResolutionCache->mDefaultAnchorCache.mScrollContainer; }(); @@ -567,7 +572,8 @@ Maybe<AnchorPosInfo> AnchorPositioningUtils::ResolveAnchorPosRect( MOZ_ASSERT_IF(*entry, entry->ref().mSize == aRect.Size()); *entry = Some(AnchorPosResolutionData{ aRect.Size(), - Some(AnchorPosOffsetData{aRect.TopLeft(), compensatesForScroll}), + Some(AnchorPosOffsetData{aRect.TopLeft(), compensatesForScroll, + distanceToNearestScrollContainer}), }); } return AnchorPosInfo{aRect, compensatesForScroll}; @@ -698,18 +704,21 @@ nsRect AnchorPositioningUtils::AdjustAbsoluteContainingBlockRectForPositionArea( return res; } -const nsIFrame* AnchorPositioningUtils::GetNearestScrollFrame( - const nsIFrame* aFrame) { +AnchorPositioningUtils::NearestScrollFrameInfo +AnchorPositioningUtils::GetNearestScrollFrame(const nsIFrame* aFrame) { if (!aFrame) { - return nullptr; + return {nullptr, {}}; } + uint32_t distance = 1; // `GetNearestScrollContainerFrame` will return the incoming frame if it's a // scroll frame, so nudge to parent. - const nsIFrame* parent = aFrame->GetParent(); - return nsLayoutUtils::GetNearestScrollContainerFrame( - const_cast<nsIFrame*>(parent), - nsLayoutUtils::SCROLLABLE_SAME_DOC | - nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN); + for (const nsIFrame* f = aFrame->GetParent(); f; f = f->GetParent()) { + if (f->IsScrollContainerOrSubclass()) { + return {f, DistanceToNearestScrollContainer{distance}}; + } + distance++; + } + return {nullptr, {}}; } nsPoint AnchorPositioningUtils::GetScrollOffsetFor( @@ -726,7 +735,7 @@ nsPoint AnchorPositioningUtils::GetScrollOffsetFor( // elements are in different continuation frames of the absolute containing // block. const auto* absoluteContainingBlock = aPositioned->GetParent(); - if (GetNearestScrollFrame(aPositioned) == + if (GetNearestScrollFrame(aPositioned).mScrollContainer == aDefaultAnchorCache.mScrollContainer) { // Would scroll together anyway, skip. return nsPoint{}; diff --git a/layout/base/AnchorPositioningUtils.h b/layout/base/AnchorPositioningUtils.h @@ -32,6 +32,29 @@ struct AnchorPosInfo { bool mCompensatesForScroll; }; +class DistanceToNearestScrollContainer { + public: + DistanceToNearestScrollContainer() : mDistance{kInvalid} {} + explicit DistanceToNearestScrollContainer(uint32_t aDistance) + : mDistance{aDistance} {} + bool Valid() const { return mDistance != kInvalid; } + bool operator==(const DistanceToNearestScrollContainer& aOther) const { + return mDistance == aOther.mDistance; + } + bool operator!=(const DistanceToNearestScrollContainer& aOther) const { + return !(*this == aOther); + } + + private: + // 0 is invalid - a frame itself cannot be its own nearest scroll container. + static constexpr uint32_t kInvalid = 0; + // Ancestor hops to the nearest scroll container. Note that scroll containers + // between abspos/fixedpos frames and their containing blocks are irrelevant, + // so the distance should be measured from the out-of-flow frame, not the + // placeholder frame. + uint32_t mDistance; +}; + struct AnchorPosOffsetData { // Origin of the referenced anchor, w.r.t. containing block at the time of // resolution. @@ -39,6 +62,8 @@ struct AnchorPosOffsetData { // Does this anchor's offset compensate for scroll? // https://drafts.csswg.org/css-anchor-position-1/#compensate-for-scroll bool mCompensatesForScroll = false; + // Distance to this anchor's nearest scroll container. + DistanceToNearestScrollContainer mDistanceToNearestScrollContainer; }; // Resolved anchor positioning data. @@ -66,7 +91,13 @@ class AnchorPosReferenceData { public: // Backup data for attempting a different `@position-try` style, when // the default anchor remains the same. - using PositionTryBackup = std::pair<mozilla::PhysicalAxes, nsPoint>; + // These entries correspond 1:1 to that of `AnchorPosReferenceData`. + struct PositionTryBackup { + mozilla::PhysicalAxes mCompensatingForScroll; + nsPoint mDefaultScrollShift; + nsRect mContainingBlockRect; + bool mUseScrollableContainingBlock = false; + }; using Value = mozilla::Maybe<AnchorPosResolutionData>; AnchorPosReferenceData() = default; @@ -98,20 +129,31 @@ class AnchorPosReferenceData { } PositionTryBackup TryPositionWithSameDefaultAnchor() { - mozilla::PhysicalAxes compensatingForScroll = - std::exchange(mCompensatingForScroll, {}); - nsPoint defaultScrollShift = std::exchange(mDefaultScrollShift, {}); - return std::make_pair(compensatingForScroll, defaultScrollShift); + auto compensatingForScroll = std::exchange(mCompensatingForScroll, {}); + auto defaultScrollShift = std::exchange(mDefaultScrollShift, {}); + auto insetModifiedContainingBlock = std::exchange(mContainingBlockRect, {}); + return {compensatingForScroll, defaultScrollShift, + insetModifiedContainingBlock}; } void UndoTryPositionWithSameDefaultAnchor(PositionTryBackup&& aBackup) { - std::tie(mCompensatingForScroll, mDefaultScrollShift) = aBackup; + mCompensatingForScroll = aBackup.mCompensatingForScroll; + mDefaultScrollShift = aBackup.mDefaultScrollShift; + mContainingBlockRect = aBackup.mContainingBlockRect; } + // 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; // 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 + // style, because of fallbacks. + RefPtr<const nsAtom> mDefaultAnchorName; private: ResolutionMap mMap; @@ -130,7 +172,6 @@ struct AnchorPosDefaultAnchorCache { const nsIFrame* mScrollContainer = nullptr; AnchorPosDefaultAnchorCache() = default; - explicit AnchorPosDefaultAnchorCache(const nsIFrame* aAnchor); AnchorPosDefaultAnchorCache(const nsIFrame* aAnchor, const nsIFrame* aScrollContainer); }; @@ -240,7 +281,11 @@ struct AnchorPositioningUtils { */ static const nsIFrame* GetAnchorPosImplicitAnchor(const nsIFrame* aFrame); - static const nsIFrame* GetNearestScrollFrame(const nsIFrame* aFrame); + struct NearestScrollFrameInfo { + const nsIFrame* mScrollContainer = nullptr; + DistanceToNearestScrollContainer mDistance; + }; + static NearestScrollFrameInfo GetNearestScrollFrame(const nsIFrame* aFrame); static nsPoint GetScrollOffsetFor( PhysicalAxes aAxes, const nsIFrame* aPositioned, diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp @@ -11687,6 +11687,15 @@ static bool NeedReflowForAnchorPos( // Didn't resolve offsets, no need to reflow based on it. return false; } + + const auto nearestScrollFrameInfo = + AnchorPositioningUtils::GetNearestScrollFrame(aAnchor); + if (anchorReference.mOffsetData->mDistanceToNearestScrollContainer != + nearestScrollFrameInfo.mDistance) { + // Scroll container relationship changed, need to reflow. + return true; + } + const auto posInfo = AnchorPositioningUtils::GetAnchorPosRect( aPositioned->GetParent(), aAnchor, true); MOZ_ASSERT(posInfo, "Can't resolve anchor rect?"); @@ -11696,6 +11705,12 @@ static bool NeedReflowForAnchorPos( return newOrigin != prevOrigin; } +struct DefaultAnchorInfo { + const nsAtom* mName; + const nsIFrame* mAnchor; + DistanceToNearestScrollContainer mDistanceToNearestScrollContainer; +}; + PresShell::AnchorPosUpdateResult PresShell::UpdateAnchorPosLayout() { if (mAnchorPosPositioned.IsEmpty()) { return AnchorPosUpdateResult::NotApplicable; @@ -11722,17 +11737,56 @@ PresShell::AnchorPosUpdateResult PresShell::UpdateAnchorPosLayout() { // Already marked for reflow. continue; } - for (const auto& kv : *anchorPosReferenceData) { - const auto& data = kv.GetData(); - const auto& anchorName = kv.GetKey(); + const auto defaultAnchorInfo = [&]() -> Maybe<DefaultAnchorInfo> { + const auto* anchorName = + AnchorPositioningUtils::GetUsedAnchorName(positioned, nullptr); + if (!anchorName) { + return Nothing{}; + } const auto* anchor = GetAnchorPosAnchor(anchorName, positioned); - if (NeedReflowForAnchorPos(anchor, positioned, data)) { - result = AnchorPosUpdateResult::NeedReflow; - // Abspos frames should not affect ancestor intrinsics. - FrameNeedsReflow(positioned, IntrinsicDirty::None, - NS_FRAME_HAS_DIRTY_CHILDREN); + if (!anchor) { + return Nothing{}; + } + const auto nearestScrollFrame = + AnchorPositioningUtils::GetNearestScrollFrame(anchor); + return Some( + DefaultAnchorInfo{anchorName, anchor, nearestScrollFrame.mDistance}); + }(); + bool shouldReflow = false; + if (defaultAnchorInfo && + defaultAnchorInfo->mDistanceToNearestScrollContainer != + anchorPosReferenceData->mDistanceToDefaultScrollContainer) { + // Default anchor's nearest scroller changed, reflow. + shouldReflow = true; + } else { + const auto GetAnchor = + [&](const nsAtom* aName, + const nsIFrame* aPositioned) -> const nsIFrame* { + if (!defaultAnchorInfo) { + return GetAnchorPosAnchor(aName, aPositioned); + } + const auto* defaultAnchorName = defaultAnchorInfo->mName; + if (aName != defaultAnchorName) { + return GetAnchorPosAnchor(aName, aPositioned); + } + return defaultAnchorInfo->mAnchor; + }; + for (const auto& kv : *anchorPosReferenceData) { + const auto& data = kv.GetData(); + const auto& anchorName = kv.GetKey(); + const auto* anchor = GetAnchor(anchorName, positioned); + if (NeedReflowForAnchorPos(anchor, positioned, data)) { + shouldReflow = true; + break; + } } } + if (shouldReflow) { + result = AnchorPosUpdateResult::NeedReflow; + // Abspos frames should not affect ancestor intrinsics. + FrameNeedsReflow(positioned, IntrinsicDirty::None, + NS_FRAME_HAS_DIRTY_CHILDREN); + } } return result; } @@ -11763,7 +11817,7 @@ static nsTArray<AffectedAnchorGroup> FindAnchorsAffectedByScroll( Maybe<nsTArray<AffectedAnchor>> affected; for (const auto& frame : anchorFrames) { const auto* scrollContainer = - AnchorPositioningUtils::GetNearestScrollFrame(frame); + AnchorPositioningUtils::GetNearestScrollFrame(frame).mScrollContainer; if (!scrollContainer) { // Fixed-pos anchor, likely continue; @@ -11799,18 +11853,16 @@ static Maybe<FindScrollCompensatedAnchorResult> FindScrollCompensatedAnchor( const nsIFrame* aPositioned) { MOZ_ASSERT(aPositioned->IsAbsolutelyPositioned(), "Anchor positioned frame is not absolutely positioned?"); - const auto* defaultAnchorName = - AnchorPositioningUtils::GetUsedAnchorName(aPositioned, nullptr); - if (!defaultAnchorName) { - return Nothing{}; - } - auto* referencedAnchors = aPositioned->GetProperty(nsIFrame::AnchorPosReferences()); if (!referencedAnchors || referencedAnchors->IsEmpty()) { return Nothing{}; } + if (!referencedAnchors->mDefaultAnchorName) { + return Nothing{}; + } + const auto compensatingForScroll = referencedAnchors->CompensatingForScrollAxes(); if (compensatingForScroll.isEmpty()) { @@ -11824,6 +11876,8 @@ static Maybe<FindScrollCompensatedAnchorResult> FindScrollCompensatedAnchor( }; // Find the relevant default anchor. + const auto* defaultAnchorName = + AnchorPositioningUtils::GetUsedAnchorName(aPositioned, nullptr); nsIFrame const* defaultAnchor = nullptr; for (const auto& group : aAffectedAnchors) { if (defaultAnchorName && @@ -11856,9 +11910,28 @@ static Maybe<FindScrollCompensatedAnchorResult> FindScrollCompensatedAnchor( return Nothing{}; } +static bool CheckOverflow(nsIFrame* aPositioned, const nsPoint& aOffset, + const AnchorPosReferenceData& aData) { + const auto* stylePos = aPositioned->StylePosition(); + const auto hasFallbacks = !stylePos->mPositionTryFallbacks._0.IsEmpty(); + const auto visibilityDependsOnOverflow = + stylePos->mPositionVisibility == StylePositionVisibility::NO_OVERFLOW; + if (!hasFallbacks && !visibilityDependsOnOverflow) { + return false; + } + const auto* cb = aPositioned->GetParent(); + MOZ_ASSERT(cb); + const auto overflows = !AnchorPositioningUtils::FitsInContainingBlock( + aData.mContainingBlockRect - aOffset, cb->GetPaddingRect(), + aPositioned->GetRect()); + aPositioned->AddOrRemoveStateBits(NS_FRAME_POSITION_VISIBILITY_HIDDEN, + visibilityDependsOnOverflow && overflows); + return hasFallbacks && overflows; +} + // https://drafts.csswg.org/css-anchor-position-1/#default-scroll-shift void PresShell::UpdateAnchorPosForScroll( - const ScrollContainerFrame* aScrollContainer) const { + const ScrollContainerFrame* aScrollContainer) { if (mAnchorPosAnchors.IsEmpty()) { return; } @@ -11885,8 +11958,7 @@ void PresShell::UpdateAnchorPosForScroll( const auto& info = scrollDependency->mAnchorInfo; auto& referenceData = scrollDependency->mReferenceData; const auto offset = AnchorPositioningUtils::GetScrollOffsetFor( - scrollDependency->mReferenceData.CompensatingForScrollAxes(), - positioned, info); + referenceData.CompensatingForScrollAxes(), positioned, info); if (referenceData.mDefaultScrollShift != offset) { positioned->SetPosition(positioned->GetNormalPosition() - offset); // Update positioned frame's overflow, then the absolute containing @@ -11895,6 +11967,10 @@ void PresShell::UpdateAnchorPosForScroll( nsContainerFrame::PlaceFrameView(positioned); positioned->GetParent()->UpdateOverflow(); referenceData.mDefaultScrollShift = offset; + if (CheckOverflow(positioned, offset, referenceData)) { + FrameNeedsReflow(positioned, IntrinsicDirty::None, + NS_FRAME_HAS_DIRTY_CHILDREN); + } } } } diff --git a/layout/base/PresShell.h b/layout/base/PresShell.h @@ -760,8 +760,7 @@ class PresShell final : public nsStubDocumentObserver, NeedReflow, }; AnchorPosUpdateResult UpdateAnchorPosLayout(); - void UpdateAnchorPosForScroll( - const ScrollContainerFrame* aScrollContainer) const; + void UpdateAnchorPosForScroll(const ScrollContainerFrame* aScrollContainer); inline void AddAnchorPosPositioned(nsIFrame* aFrame) { if (!mAnchorPosPositioned.Contains(aFrame)) { diff --git a/layout/generic/AbsoluteContainingBlock.cpp b/layout/generic/AbsoluteContainingBlock.cpp @@ -1172,6 +1172,10 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( } return ContainingBlockRect{aOriginalContainingBlockRect}; }(); + if (aAnchorPosResolutionCache) { + aAnchorPosResolutionCache->mReferenceData->mContainingBlockRect = + cb.mRect; + } const WritingMode outerWM = aReflowInput.GetWritingMode(); const WritingMode wm = aKidFrame->GetWritingMode(); const LogicalSize cbSize(outerWM, cb.mRect.Size()); @@ -1432,6 +1436,8 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( overflowCheckRect = GrowOverflowCheckRect(overflowCheckRect, aKidFrame->GetNormalRect(), cb.mAnchorShiftInfo->mResolvedArea); + aAnchorPosResolutionCache->mReferenceData->mContainingBlockRect = + overflowCheckRect; const auto originalContainingBlockRect = aOriginalContainingBlockRect + paddingEdgeShift; return AnchorPositioningUtils::FitsInContainingBlock( diff --git a/testing/web-platform/tests/css/css-anchor-position/anchor-scroll-update-008.html b/testing/web-platform/tests/css/css-anchor-position/anchor-scroll-update-008.html @@ -0,0 +1,70 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<title>Anchored element should update when anchor's div under `contain: layout size` becomes a non scroll container.</title> +<link rel="author" href="mailto:dshin@mozilla.com"> +<link rel="help" href="https://drafts.csswg.org/css-anchor-1/"> +<link rel="match" href="reference/anchor-scroll-update-008-ref.html"> +<style> +.abspos-cb { + position: relative; + width: 200px; + height: 200px; + border: 1px solid; +} + +.positioned { + width: 15px; + height: 15px; + background: purple; + + position: absolute; + position-anchor: --a; + left: anchor(right); + top: anchor(top); +} + +.scroller { + overflow-y: scroll; + height: 100%; +} + +.filler { + width: 1px; + height: 500px; +} + +.anchor { + width: 15px; + height: 15px; + background: magenta; + anchor-name: --a; +} + +.contain { + contain: layout size; + width: 200px; + height: 200px; +} +</style> +<div class=abspos-cb> + <div class=positioned></div> + <div class=contain><div id=dut class=scroller> + <div class=anchor></div> + <div class=filler></div> + </div></div> +</div> +<script> +dut.scrollTop = 300; +dut.classList.toggle('scroller'); + +function raf() { + return new Promise(resolve => requestAnimationFrame(resolve)); +} + +async function runTest() { + await raf(); + await raf(); + document.documentElement.classList.remove('reftest-wait'); +} +runTest(); +</script> diff --git a/testing/web-platform/tests/css/css-anchor-position/anchor-scroll-update-009.html b/testing/web-platform/tests/css/css-anchor-position/anchor-scroll-update-009.html @@ -0,0 +1,70 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<title>Anchored element should update when anchor's div under `contain: layout size` becomes a scroll container.</title> +<link rel="author" href="mailto:dshin@mozilla.com"> +<link rel="help" href="https://drafts.csswg.org/css-anchor-1/"> +<link rel="match" href="reference/anchor-scroll-update-009-ref.html"> +<style> +.abspos-cb { + position: relative; + width: 200px; + height: 200px; + border: 1px solid; +} + +.positioned { + width: 15px; + height: 15px; + background: purple; + + position: absolute; + position-anchor: --a; + left: anchor(right); + top: anchor(top); +} + +.scroller { + overflow-y: scroll; + height: 100%; +} + +.filler { + width: 1px; + height: 500px; +} + +.anchor { + width: 15px; + height: 15px; + background: magenta; + anchor-name: --a; +} + +.contain { + contain: layout size; + width: 200px; + height: 200px; +} +</style> +<div class=abspos-cb> + <div class=positioned></div> + <div class=contain><div id=dut> + <div class=filler></div> + <div class=anchor></div> + </div></div> +</div> +<script> +dut.classList.toggle('scroller'); +dut.scrollTop = 315; + +function raf() { + return new Promise(resolve => requestAnimationFrame(resolve)); +} + +async function runTest() { + await raf(); + await raf(); + document.documentElement.classList.remove('reftest-wait'); +} +runTest(); +</script> diff --git a/testing/web-platform/tests/css/css-anchor-position/anchor-scroll-update-010.html b/testing/web-platform/tests/css/css-anchor-position/anchor-scroll-update-010.html @@ -0,0 +1,84 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<title>Anchored element should update when the nearest scroll container under `contain: layout size` element changes.</title> +<link rel="author" href="mailto:dshin@mozilla.com"> +<link rel="help" href="https://drafts.csswg.org/css-anchor-1/"> +<link rel="match" href="reference/anchor-scroll-update-010-ref.html"> +<style> +.anchor { + width: 20px; + height: 20px; + background: magenta; +} + +.positioned { + position-anchor: --a; + position: absolute; + background: purple; + width: 20px; + height: 20px; + /* Initially not part of --a's nearest scroll container */ + left: anchor(--b right); + top: anchor(--b top); +} + +.abs-cb { + position: relative; + width: 200px; + height: 200px; + border: 1px solid; +} + +.scroll { + overflow: scroll; +} + +.outer { + width: 200px; + height: 200px; +} + +.inner { + width: 150px; + height: 150px; +} + +.filler { + width: 1px; + height: 200px; +} + +.contain { + contain: layout size; + width: 200px; + height: 200px; +} +</style> +<div class=abs-cb> + <div class=contain> + <div id=outer class="scroll outer"> + <div class=filler></div> + <div class=anchor style="anchor-name: --b"></div> + <div id=inner class="scroll inner"> + <div class=anchor style="anchor-name: --a"></div> + </div> + </div> + </div> + <div class=positioned></div> +</div> +<script> +inner.classList.toggle('scroll'); +outer.scrollTop = 100; + +function raf() { + return new Promise(resolve => requestAnimationFrame(resolve)); +} + +async function runTest() { + await raf(); + await raf(); + document.documentElement.classList.remove('reftest-wait'); +} +runTest(); +</script> +</html> diff --git a/testing/web-platform/tests/css/css-anchor-position/reference/anchor-scroll-update-008-ref.html b/testing/web-platform/tests/css/css-anchor-position/reference/anchor-scroll-update-008-ref.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>Anchored element should update when anchor's div under `contain: layout size` becomes a scroll container.</title> +<link rel="author" href="mailto:dshin@mozilla.com"> +<link rel="help" href="https://drafts.csswg.org/css-anchor-1/"> +<style> +.abspos-cb { + position: relative; + width: 200px; + height: 200px; + border: 1px solid; +} + +.flex { + display: flex; +} + +.positioned { + width: 15px; + height: 15px; + background: purple; +} + +.anchor { + width: 15px; + height: 15px; + background: magenta; +} +</style> +<div class=abspos-cb> + <div class=flex> + <div class=anchor></div><div class=positioned></div> + </div> +</div> diff --git a/testing/web-platform/tests/css/css-anchor-position/reference/anchor-scroll-update-009-ref.html b/testing/web-platform/tests/css/css-anchor-position/reference/anchor-scroll-update-009-ref.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<title>Anchored element should update when anchor's div under `contain: layout size` becomes a scroll container.</title> +<link rel="author" href="mailto:dshin@mozilla.com"> +<link rel="help" href="https://drafts.csswg.org/css-anchor-1/"> +<style> +.abspos-cb { + position: relative; + width: 200px; + height: 200px; + border: 1px solid; +} + +.flex { + display: flex; +} + +.positioned { + width: 15px; + height: 15px; + background: purple; +} + +.scroller { + overflow-y: scroll; + height: 100%; +} + +.anchor { + width: 15px; + height: 15px; + background: magenta; +} + +.filler { + width: 1px; + height: 500px; +} +</style> +<div class=abspos-cb> + <div id=dut class=scroller> + <div class=filler></div> + <div class=flex> + <div class=anchor></div> + <div class=positioned></div> + </div> + </div> +</div> +<script> +dut.scrollTop = 315; +</script> diff --git a/testing/web-platform/tests/css/css-anchor-position/reference/anchor-scroll-update-010-ref.html b/testing/web-platform/tests/css/css-anchor-position/reference/anchor-scroll-update-010-ref.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<title>Anchored element should update when the nearest scroll container under `contain: layout size` element changes.</title> +<style> +.anchor { + width: 20px; + height: 20px; + background: magenta; +} + +.positioned { + background: purple; + width: 20px; + height: 20px; +} + +.flex { + display: flex; +} + +.abs-cb { + position: relative; + width: 200px; + height: 200px; + border: 1px solid; +} + +.scroll { + overflow: scroll; +} + +.outer { + width: 200px; + height: 200px; +} + +.inner { + width: 150px; + height: 150px; +} + +.filler { + width: 1px; + height: 200px; +} +</style> +<div class=abs-cb> + <div id=outer class="scroll outer"> + <div class=filler></div> + <div class=flex> + <div class=anchor></div> + <div class=positioned></div> + </div> + <div class=inner> + <div class=anchor></div> + </div> + </div> +</div> +<script> +outer.scrollTop = 100; +</script>