commit ed458be3ba7c9cd01ee58e4deec4c52a8fdcc489 parent 1059f054014944000e39e9f05293a72f951ddb11 Author: agoloman <agoloman@mozilla.com> Date: Thu, 30 Oct 2025 19:43:38 +0200 Revert (Bug 1987963 Bug 1968745, Bug 1987961) : for causing wr failures @position-visibility-no-overflow-scroll.html. This reverts commit 63d8614128ed84675cbd4f351ea7b60f0d87232b. Revert "Bug 1987963: Ensure that we can roll back to previous normal position when we give up on finding fallbacks. r=layout-anchor-positioning-reviewers,layout-reviewers,emilio" This reverts commit 0685b925e3dfb05ee070c17406e9a7d67fb31ce6. Revert "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" This reverts commit 9b3130e442de52c28e9016dffc7bcb278dbb58b3. Revert "Bug 1987963: Apply default scroll offset on scroll. r=layout-reviewers,tnikkel,jwatt" This reverts commit 878edfaa957881203ae1f443eefd2de914232590. Revert "Bug 1987963: Compute and cache compensating for scroll state of anchor positioned elements. r=jwatt" This reverts commit e83f5939e36abcb07415d85c3b04f5c3db05589a. Revert "Bug 1968745: Keep track of default anchor and its nearest scrollers. r=layout-anchor-positioning-reviewers,layout-reviewers,jwatt,emilio" This reverts commit f47abb0c30dff9ad971771ccfc5368f373111d80. Revert "Bug 1968745: Pass short-lived cache into anchor resolution. r=jwatt" This reverts commit 045b5b1cb3b67f4fc035b2958a18ffbf77966d31. Revert "Bug 1987961: Stop taking scroll offset into account during anchor positioning offset resolution. r=jwatt" This reverts commit 11ea3d17d5db71036cfcdb3e25ae7cb75372a7b5. Diffstat:
37 files changed, 453 insertions(+), 1354 deletions(-)
diff --git a/accessible/base/nsCoreUtils.cpp b/accessible/base/nsCoreUtils.cpp @@ -730,7 +730,7 @@ nsIFrame* nsCoreUtils::GetPositionedFrameForAnchor( continue; } const auto* data = referencedAnchors->Lookup(name.AsAtom()); - if (data && *data && data->ref().mOffsetData) { + if (data && *data && data->ref().mOrigin) { if (aAnchorFrame == aPresShell->GetAnchorPosAnchor(name.AsAtom(), frame)) { if (positionedFrame) { diff --git a/layout/base/AnchorPositioningUtils.cpp b/layout/base/AnchorPositioningUtils.cpp @@ -6,7 +6,6 @@ #include "AnchorPositioningUtils.h" -#include "ScrollContainerFrame.h" #include "mozilla/Maybe.h" #include "mozilla/PresShell.h" #include "mozilla/dom/Document.h" @@ -377,7 +376,7 @@ AnchorPosReferenceData::Result AnchorPosReferenceData::InsertOrModify( // Previous resolution may have been for size only, in which case another // anchor resolution is still required. - return {result->ref().mOffsetData.isSome(), result}; + return {result->ref().mOrigin.isSome(), result}; } const AnchorPosReferenceData::Value* AnchorPosReferenceData::Lookup( @@ -385,17 +384,6 @@ const AnchorPosReferenceData::Value* AnchorPosReferenceData::Lookup( return mMap.Lookup(aAnchorName).DataPtrOrNull(); } -AnchorPosDefaultAnchorCache::AnchorPosDefaultAnchorCache( - const nsIFrame* aAnchor, const nsIFrame* aScrollContainer) - : mAnchor{aAnchor}, mScrollContainer{aScrollContainer} { - MOZ_ASSERT_IF( - aAnchor, - nsLayoutUtils::GetNearestScrollContainerFrame( - const_cast<nsContainerFrame*>(aAnchor->GetParent()), - nsLayoutUtils::SCROLLABLE_SAME_DOC | - nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN) == mScrollContainer); -} - nsIFrame* AnchorPositioningUtils::FindFirstAcceptableAnchor( const nsAtom* aName, const nsIFrame* aPositionedFrame, const nsTArray<nsIFrame*>& aPossibleAnchorFrames) { @@ -438,22 +426,15 @@ static const nsIFrame* TraverseUpToContainerChild(const nsIFrame* aContainer, } } -static const nsIFrame* GetAnchorOf(const nsIFrame* aPositioned, - const nsAtom* aAnchorName) { - const auto* presShell = aPositioned->PresShell(); - MOZ_ASSERT(presShell, "No PresShell for frame?"); - return presShell->GetAnchorPosAnchor(aAnchorName, aPositioned); -} - -Maybe<nsRect> AnchorPositioningUtils::GetAnchorPosRect( +Maybe<AnchorPosInfo> AnchorPositioningUtils::GetAnchorPosRect( const nsIFrame* aAbsoluteContainingBlock, const nsIFrame* aAnchor, - bool aCBRectIsvalid) { + bool aCBRectIsvalid, + Maybe<AnchorPosResolutionData>* aReferencedAnchorsEntry) { auto rect = [&]() -> Maybe<nsRect> { if (aCBRectIsvalid) { const nsRect result = nsLayoutUtils::GetCombinedFragmentRects(aAnchor, true); - const auto offset = - aAnchor->GetOffsetToIgnoringScrolling(aAbsoluteContainingBlock); + const auto offset = aAnchor->GetOffsetTo(aAbsoluteContainingBlock); // Easy, just use the existing function. return Some(result + offset); } @@ -477,7 +458,7 @@ Maybe<nsRect> AnchorPositioningUtils::GetAnchorPosRect( // going to do it again here, which feels a little wasteful. const nsRect rectToContainerChild = nsLayoutUtils::GetCombinedFragmentRects(aAnchor, true); - const auto offset = aAnchor->GetOffsetToIgnoringScrolling(containerChild); + const auto offset = aAnchor->GetOffsetTo(containerChild); return Some(rectToContainerChild + offset + containerChild->GetPosition()); }(); return rect.map([&](const nsRect& aRect) { @@ -488,128 +469,23 @@ Maybe<nsRect> AnchorPositioningUtils::GetAnchorPosRect( const auto border = aAbsoluteContainingBlock->GetUsedBorder(); const nsPoint borderTopLeft{border.left, border.top}; const auto rect = aRect - borderTopLeft; - return rect; - }); -} - -Maybe<AnchorPosInfo> AnchorPositioningUtils::ResolveAnchorPosRect( - const nsIFrame* aPositioned, const nsIFrame* aAbsoluteContainingBlock, - const nsAtom* aAnchorName, bool aCBRectIsvalid, - AnchorPosResolutionCache* aResolutionCache) { - MOZ_ASSERT(aPositioned->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)); - MOZ_ASSERT(aPositioned->GetParent() == aAbsoluteContainingBlock); - - if (!aPositioned) { - return Nothing{}; - } - - const auto* anchorName = GetUsedAnchorName(aPositioned, aAnchorName); - if (!anchorName) { - return Nothing{}; - } - - Maybe<AnchorPosResolutionData>* entry = nullptr; - if (aResolutionCache) { - const auto result = - aResolutionCache->mReferenceData->InsertOrModify(anchorName, true); - if (result.mAlreadyResolved) { - MOZ_ASSERT(result.mEntry, "Entry exists but null?"); - return result.mEntry->map([&](const AnchorPosResolutionData& aData) { - MOZ_ASSERT(aData.mOffsetData, "Missing anchor offset resolution."); - const auto& offsetData = aData.mOffsetData.ref(); - return AnchorPosInfo{nsRect{offsetData.mOrigin, aData.mSize}, - offsetData.mCompensatesForScroll}; - }); - } - entry = result.mEntry; - } - - const auto* anchor = GetAnchorOf(aPositioned, anchorName); - if (!anchor) { - // If we have a cached entry, just check that it resolved to nothing last - // time as well. - MOZ_ASSERT_IF(entry, entry->isNothing()); - return Nothing{}; - } - - const auto result = - GetAnchorPosRect(aAbsoluteContainingBlock, anchor, aCBRectIsvalid); - return result.map([&](const nsRect& aRect) { - bool compensatesForScroll = false; - DistanceToNearestScrollContainer distanceToNearestScrollContainer; - if (aResolutionCache) { - MOZ_ASSERT(entry); - // Update the cache. - compensatesForScroll = [&]() { - auto& defaultAnchorCache = aResolutionCache->mDefaultAnchorCache; - if (!aAnchorName) { - // Explicitly resolved default anchor for the first time - populate - // the cache. - 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, distance] = - AnchorPositioningUtils::GetNearestScrollFrame(anchor); - distanceToNearestScrollContainer = distance; - return scrollContainer == - aResolutionCache->mDefaultAnchorCache.mScrollContainer; - }(); + if (aReferencedAnchorsEntry) { // If a partially resolved entry exists, make sure that it matches what we // have now. - MOZ_ASSERT_IF(*entry, entry->ref().mSize == aRect.Size()); - *entry = Some(AnchorPosResolutionData{ - aRect.Size(), - Some(AnchorPosOffsetData{aRect.TopLeft(), compensatesForScroll, - distanceToNearestScrollContainer}), + MOZ_ASSERT_IF(*aReferencedAnchorsEntry, + aReferencedAnchorsEntry->ref().mSize == rect.Size()); + *aReferencedAnchorsEntry = Some(AnchorPosResolutionData{ + rect.Size(), + Some(rect.TopLeft()), }); } - return AnchorPosInfo{aRect, compensatesForScroll}; + return AnchorPosInfo{ + .mRect = rect, + .mContainingBlock = aAbsoluteContainingBlock, + }; }); } -Maybe<nsSize> AnchorPositioningUtils::ResolveAnchorPosSize( - const nsIFrame* aPositioned, const nsAtom* aAnchorName, - AnchorPosResolutionCache* aResolutionCache) { - const auto* anchorName = GetUsedAnchorName(aPositioned, aAnchorName); - if (!anchorName) { - return Nothing{}; - } - Maybe<AnchorPosResolutionData>* entry = nullptr; - auto* referencedAnchors = - aResolutionCache ? aResolutionCache->mReferenceData : nullptr; - if (referencedAnchors) { - const auto result = referencedAnchors->InsertOrModify(anchorName, false); - if (result.mAlreadyResolved) { - MOZ_ASSERT(result.mEntry, "Entry exists but null?"); - return result.mEntry->map( - [](const AnchorPosResolutionData& aData) { return aData.mSize; }); - } - entry = result.mEntry; - } - const auto* anchor = GetAnchorOf(aPositioned, anchorName); - if (!anchor) { - return Nothing{}; - } - const auto size = nsLayoutUtils::GetCombinedFragmentRects(anchor).Size(); - if (entry) { - *entry = Some(AnchorPosResolutionData{size, Nothing{}}); - } - return Some(size); -} - /** * Returns an equivalent StylePositionArea that contains: * [ @@ -704,59 +580,6 @@ nsRect AnchorPositioningUtils::AdjustAbsoluteContainingBlockRectForPositionArea( return res; } -AnchorPositioningUtils::NearestScrollFrameInfo -AnchorPositioningUtils::GetNearestScrollFrame(const nsIFrame* aFrame) { - if (!aFrame) { - return {nullptr, {}}; - } - uint32_t distance = 1; - // `GetNearestScrollContainerFrame` will return the incoming frame if it's a - // scroll frame, so nudge to parent. - for (const nsIFrame* f = aFrame->GetParent(); f; f = f->GetParent()) { - if (f->IsScrollContainerOrSubclass()) { - return {f, DistanceToNearestScrollContainer{distance}}; - } - distance++; - } - return {nullptr, {}}; -} - -nsPoint AnchorPositioningUtils::GetScrollOffsetFor( - PhysicalAxes aAxes, const nsIFrame* aPositioned, - const AnchorPosDefaultAnchorCache& aDefaultAnchorCache) { - MOZ_ASSERT(aPositioned); - if (!aDefaultAnchorCache.mAnchor || aAxes.isEmpty()) { - return nsPoint{}; - } - nsPoint offset; - const bool trackHorizontal = aAxes.contains(PhysicalAxis::Horizontal); - const bool trackVertical = aAxes.contains(PhysicalAxis::Vertical); - // TODO(dshin, bug 1991489): Traverse properly, in case anchor and positioned - // elements are in different continuation frames of the absolute containing - // block. - const auto* absoluteContainingBlock = aPositioned->GetParent(); - if (GetNearestScrollFrame(aPositioned).mScrollContainer == - aDefaultAnchorCache.mScrollContainer) { - // Would scroll together anyway, skip. - return nsPoint{}; - } - // Grab the accumulated offset up to, but not including, the abspos - // container. - for (const auto* f = aDefaultAnchorCache.mScrollContainer; - f && f != absoluteContainingBlock; f = f->GetParent()) { - if (const ScrollContainerFrame* scrollFrame = do_QueryFrame(f)) { - const auto o = scrollFrame->GetScrollPosition(); - if (trackHorizontal) { - offset.x += o.x; - } - if (trackVertical) { - offset.y += o.y; - } - } - } - return offset; -} - // Out of line to avoid having to include AnchorPosReferenceData from nsIFrame.h void DeleteAnchorPosReferenceData(AnchorPosReferenceData* aData) { delete aData; @@ -822,11 +645,43 @@ const nsIFrame* AnchorPositioningUtils::GetAnchorPosImplicitAnchor( : pseudoRootFrame->GetParent(); } -bool AnchorPositioningUtils::FitsInContainingBlock( - const nsRect& aOverflowCheckRect, - const nsRect& aOriginalContainingBlockSize, const nsRect& aRect) { - return aOverflowCheckRect.Intersect(aOriginalContainingBlockSize) - .Contains(aRect); +AnchorPositioningUtils::DefaultAnchorInfo +AnchorPositioningUtils::GetDefaultAnchor( + const nsIFrame* aPositioned, bool aCBRectIsValid, + AnchorPosReferenceData* aAnchorPosReferenceData) { + const auto* presShell = aPositioned->PresShell(); + const auto* name = GetUsedAnchorName(aPositioned, nullptr); + if (!name) { + return {}; + } + + Maybe<AnchorPosResolutionData>* entry = nullptr; + if (aAnchorPosReferenceData) { + auto result = aAnchorPosReferenceData->InsertOrModify(name, true); + entry = result.mEntry; + if (result.mAlreadyResolved) { + return DefaultAnchorInfo{ + name, entry->map([](const AnchorPosResolutionData& aValue) { + MOZ_ASSERT(aValue.mOrigin, "Already resolved but no origin?"); + return nsRect{*aValue.mOrigin, aValue.mSize}; + })}; + } + } + + const auto* anchor = presShell->GetAnchorPosAnchor(name, aPositioned); + if (!anchor) { + return {name, Nothing{}}; + } + + const auto info = + GetAnchorPosRect(aPositioned->GetParent(), anchor, aCBRectIsValid, entry); + if (!info) { + NS_WARNING("Can find anchor frame but not rect (In multicol?)"); + // Behave as if anchor is invalid. + return {name, Nothing{}}; + } + + return {name, Some(info->mRect)}; } } // namespace mozilla diff --git a/layout/base/AnchorPositioningUtils.h b/layout/base/AnchorPositioningUtils.h @@ -7,8 +7,6 @@ #ifndef AnchorPositioningUtils_h__ #define AnchorPositioningUtils_h__ -#include "WritingModes.h" -#include "mozilla/EnumSet.h" #include "mozilla/Maybe.h" #include "nsRect.h" #include "nsTHashMap.h" @@ -25,79 +23,37 @@ class CopyableTArray; namespace mozilla { struct AnchorPosInfo { - // Border-box of the anchor frame, offset against the positioned frame's - // absolute containing block's padding box. + // Border-box of the anchor frame, offset against `mContainingBlock`'s padding + // box. nsRect mRect; - // See `AnchorPosOffsetData::mCompensatesForScroll`. - 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. - nsPoint mOrigin; - // 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; + const nsIFrame* mContainingBlock; }; // Resolved anchor positioning data. struct AnchorPosResolutionData { // Size of the referenced anchor. nsSize mSize; - // Offset resolution data. Nothing if the anchor did not resolve, or if the - // anchor was only referred to by its size. - Maybe<AnchorPosOffsetData> mOffsetData; + // Origin of the referenced anchor, w.r.t. containing block at the time of + // resolution. Includes scroll offsets, for now. + // Nothing if the anchor did not resolve, or if the anchor was only referred + // to by its size. + mozilla::Maybe<nsPoint> mOrigin; }; // Data required for an anchor positioned frame, including: // * If valid anchors are found, // * Cached offset/size resolution, if resolution was valid, -// * Compensating for scroll [1] -// * Default scroll shift [2] +// * TODO(dshin, bug 1968745): Compensating for scroll [1] +// * TODO(dshin, bug 1987962): Default scroll shift [2] // // [1]: https://drafts.csswg.org/css-anchor-position-1/#compensate-for-scroll // [2]: https://drafts.csswg.org/css-anchor-position-1/#default-scroll-shift class AnchorPosReferenceData { private: - using ResolutionMap = + using Map = nsTHashMap<RefPtr<const nsAtom>, mozilla::Maybe<AnchorPosResolutionData>>; public: - // Backup data for attempting a different `@position-try` style, when - // the default anchor remains the same. - // 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; @@ -117,103 +73,16 @@ class AnchorPosReferenceData { bool IsEmpty() const { return mMap.IsEmpty(); } - ResolutionMap::const_iterator begin() const { return mMap.cbegin(); } - ResolutionMap::const_iterator end() const { return mMap.cend(); } - - void AdjustCompensatingForScroll(const mozilla::PhysicalAxes& aAxes) { - mCompensatingForScroll += aAxes; - } - - mozilla::PhysicalAxes CompensatingForScrollAxes() const { - return mCompensatingForScroll; - } - - PositionTryBackup TryPositionWithSameDefaultAnchor() { - auto compensatingForScroll = std::exchange(mCompensatingForScroll, {}); - auto defaultScrollShift = std::exchange(mDefaultScrollShift, {}); - auto insetModifiedContainingBlock = std::exchange(mContainingBlockRect, {}); - return {compensatingForScroll, defaultScrollShift, - insetModifiedContainingBlock}; - } - - void UndoTryPositionWithSameDefaultAnchor(PositionTryBackup&& 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; + Map::const_iterator begin() const { return mMap.cbegin(); } + Map::const_iterator end() const { return mMap.cend(); } private: - ResolutionMap mMap; - // Axes we need to compensate for scroll [1] in. - // [1]: https://drafts.csswg.org/css-anchor-position-1/#compensate-for-scroll - mozilla::PhysicalAxes mCompensatingForScroll; + Map mMap; }; struct StylePositionArea; class WritingMode; -struct AnchorPosDefaultAnchorCache { - // Default anchor element's corresponding frame. - const nsIFrame* mAnchor = nullptr; - // Scroll container for the default anchor. - const nsIFrame* mScrollContainer = nullptr; - - AnchorPosDefaultAnchorCache() = default; - AnchorPosDefaultAnchorCache(const nsIFrame* aAnchor, - const nsIFrame* aScrollContainer); -}; - -// Cache data used by anchor resolution. To be populated on abspos reflow, -// whenever the frame makes any anchor reference. -struct AnchorPosResolutionCache { - // Storage for referenced anchors. Designed to be long-lived (i.e. beyond - // a reflow cycle). - AnchorPosReferenceData* mReferenceData = nullptr; - // Cached data for default anchor resolution. Designed to be short-lived, - // so it can contain e.g. frame pointers. - AnchorPosDefaultAnchorCache mDefaultAnchorCache; - - // Backup data for attempting a different `@position-try` style, when - // the default anchor remains the same. - using PositionTryBackup = AnchorPosReferenceData::PositionTryBackup; - PositionTryBackup TryPositionWithSameDefaultAnchor() { - return mReferenceData->TryPositionWithSameDefaultAnchor(); - } - void UndoTryPositionWithSameDefaultAnchor(PositionTryBackup&& aBackup) { - mReferenceData->UndoTryPositionWithSameDefaultAnchor(std::move(aBackup)); - } - - // Backup data for attempting a different `@position-try` style, when - // the default anchor changes. - using PositionTryFullBackup = - std::pair<AnchorPosReferenceData, AnchorPosDefaultAnchorCache>; - PositionTryFullBackup TryPositionWithDifferentDefaultAnchor() { - auto referenceData = std::move(*mReferenceData); - *mReferenceData = {}; - return std::make_pair( - std::move(referenceData), - std::exchange(mDefaultAnchorCache, AnchorPosDefaultAnchorCache{})); - } - void UndoTryPositionWithDifferentDefaultAnchor( - PositionTryFullBackup&& aBackup) { - *mReferenceData = std::move(aBackup.first); - std::exchange(mDefaultAnchorCache, aBackup.second); - } -}; - enum class StylePositionTryFallbacksTryTacticKeyword : uint8_t; using StylePositionTryFallbacksTryTactic = CopyableTArray<StylePositionTryFallbacksTryTacticKeyword>; @@ -233,18 +102,10 @@ struct AnchorPositioningUtils { const nsAtom* aName, const nsIFrame* aPositionedFrame, const nsTArray<nsIFrame*>& aPossibleAnchorFrames); - static Maybe<nsRect> GetAnchorPosRect( + static Maybe<AnchorPosInfo> GetAnchorPosRect( const nsIFrame* aAbsoluteContainingBlock, const nsIFrame* aAnchor, - bool aCBRectIsvalid); - - static Maybe<AnchorPosInfo> ResolveAnchorPosRect( - const nsIFrame* aPositioned, const nsIFrame* aAbsoluteContainingBlock, - const nsAtom* aAnchorName, bool aCBRectIsvalid, - AnchorPosResolutionCache* aResolutionCache); - - static Maybe<nsSize> ResolveAnchorPosSize( - const nsIFrame* aPositioned, const nsAtom* aAnchorName, - AnchorPosResolutionCache* aResolutionCache); + bool aCBRectIsvalid, + Maybe<AnchorPosResolutionData>* aReferencedAnchorsEntry); /** * Adjust the containing block rect for the 'position-area' property. @@ -281,19 +142,25 @@ struct AnchorPositioningUtils { */ static const nsIFrame* GetAnchorPosImplicitAnchor(const nsIFrame* aFrame); - struct NearestScrollFrameInfo { - const nsIFrame* mScrollContainer = nullptr; - DistanceToNearestScrollContainer mDistance; + struct DefaultAnchorInfo { + const nsAtom* mName = nullptr; + Maybe<nsRect> mRect; }; - static NearestScrollFrameInfo GetNearestScrollFrame(const nsIFrame* aFrame); - static nsPoint GetScrollOffsetFor( - PhysicalAxes aAxes, const nsIFrame* aPositioned, - const AnchorPosDefaultAnchorCache& aDefaultAnchorCache); - - static bool FitsInContainingBlock(const nsRect& aOverflowCheckRect, - const nsRect& aOriginalContainingBlockSize, - const nsRect& aRect); + /** + * Resolve the default anchor's rect, if the default anchor exists and is + * valid. + * + * @param aFrame The anchor positioned frame. + * @param aCBRectIsValid Whether or not the containing block's `GetRect` + * returns a valid result. This will be not be the case during the abspos + * frame's reflow. + * @param aAnchorPosReferenceData Anchor pos references to attempt to reuse & + * cache lookups to. + */ + static DefaultAnchorInfo GetDefaultAnchor( + const nsIFrame* aPositioned, bool aCBRectIsValid, + AnchorPosReferenceData* aAnchorPosReferenceData); }; } // namespace mozilla diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp @@ -11683,34 +11683,19 @@ static bool NeedReflowForAnchorPos( // Size changed, needs reflow. return true; } - if (!anchorReference.mOffsetData) { + if (!anchorReference.mOrigin) { // 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); + aPositioned->GetParent(), aAnchor, true, nullptr); MOZ_ASSERT(posInfo, "Can't resolve anchor rect?"); - const auto newOrigin = posInfo.ref().TopLeft(); - const auto& prevOrigin = anchorReference.mOffsetData.ref().mOrigin; + const auto newOrigin = posInfo.ref().mRect.TopLeft(); + const auto& prevOrigin = anchorReference.mOrigin.ref(); // Did the offset change? return newOrigin != prevOrigin; } -struct DefaultAnchorInfo { - const nsAtom* mName; - const nsIFrame* mAnchor; - DistanceToNearestScrollContainer mDistanceToNearestScrollContainer; -}; - PresShell::AnchorPosUpdateResult PresShell::UpdateAnchorPosLayout() { if (mAnchorPosPositioned.IsEmpty()) { return AnchorPosUpdateResult::NotApplicable; @@ -11737,237 +11722,191 @@ PresShell::AnchorPosUpdateResult PresShell::UpdateAnchorPosLayout() { // Already marked for reflow. continue; } - const auto defaultAnchorInfo = [&]() -> Maybe<DefaultAnchorInfo> { - const auto* anchorName = - AnchorPositioningUtils::GetUsedAnchorName(positioned, nullptr); - if (!anchorName) { - return Nothing{}; - } + for (const auto& kv : *anchorPosReferenceData) { + const auto& data = kv.GetData(); + const auto& anchorName = kv.GetKey(); const auto* anchor = GetAnchorPosAnchor(anchorName, positioned); - 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 (NeedReflowForAnchorPos(anchor, positioned, data)) { + result = AnchorPosUpdateResult::NeedReflow; + // Abspos frames should not affect ancestor intrinsics. + FrameNeedsReflow(positioned, IntrinsicDirty::None, + NS_FRAME_HAS_DIRTY_CHILDREN); } } - if (shouldReflow) { - result = AnchorPosUpdateResult::NeedReflow; - // Abspos frames should not affect ancestor intrinsics. - FrameNeedsReflow(positioned, IntrinsicDirty::None, - NS_FRAME_HAS_DIRTY_CHILDREN); - } } return result; } -using AffectedAnchor = AnchorPosDefaultAnchorCache; -struct AffectedAnchorGroup { - const nsAtom* mAnchorName; - nsTArray<AffectedAnchor> mFrames; -}; +static ScrollContainerFrame* FindScrollContainerFrameOf( + const nsIFrame* aFrame) { + MOZ_ASSERT(aFrame, "NULL frame for FindScrollContainerFrameOf()"); + auto* parent = aFrame->GetParent(); + return nsLayoutUtils::GetNearestScrollContainerFrame( + parent, nsLayoutUtils::SCROLLABLE_SAME_DOC | + nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN); +} -static nsTArray<AffectedAnchorGroup> FindAnchorsAffectedByScroll( - const nsTHashMap<RefPtr<const nsAtom>, nsTArray<nsIFrame*>>& aAnchors, - const ScrollContainerFrame* aScrollContainer) { - const auto AffectedByScrollContainer = - [](const nsIFrame* aFrame, const ScrollContainerFrame* aScrollContainer) { - MOZ_ASSERT(aFrame); - MOZ_ASSERT(aScrollContainer); - return aFrame == aScrollContainer || - nsLayoutUtils::IsProperAncestorFrame(aScrollContainer, aFrame); - }; +static bool UnderScrollContainer(nsIFrame* aFrame, + const ScrollContainerFrame* aScrollContainer) { + MOZ_ASSERT(aFrame); + MOZ_ASSERT(aScrollContainer); + return aFrame == aScrollContainer || + nsLayoutUtils::IsProperAncestorFrame(aScrollContainer, aFrame); +} + +void PresShell::UpdateAnchorPosLayoutForScroll( + ScrollContainerFrame* aScrollContainer) { + if (mAnchorPosAnchors.IsEmpty()) { + return; + } + AUTO_PROFILER_MARKER_UNTYPED("UpdateAnchorPosLayoutForScroll", LAYOUT, {}); + // TODO(dshin, bug 1923401): What follows is a non-spec compliant + // implementation of scroll handling for anchor positioning. Specifically, it + // will compensate for any offset between anchor and all positioned elements, + // instead of compensating in axes anchoring to elements in the same scroller + // as the default anchor. As a result, positioned elements will resize to + // stay attached to all anchors. This needs to be adressed after we stop + // storing anchor offset that includes scroll offsets. + // TODO(dshin, bug 1987463): After bug 1923401, we still need this code path + // to update the scroll offset, so it's worth investigating further + // optimizations. + struct AffectedAnchor { + const nsIFrame* mFrame; + ScrollContainerFrame* mNearestScrollContainer; + }; + struct AffectedAnchorGroup { + const nsAtom* mAnchorName; + nsTArray<AffectedAnchor> mFrames; + }; + struct Comparator { + bool Equals(const AffectedAnchor& aEntry, const nsIFrame* aFrame) const { + return aEntry.mFrame == aFrame; + } + }; + + // First, find all anchors under this scroll container. Can look at positioned + // frames' anchor references first, but we want to avoid anchor lookups if we + // can. nsTArray<AffectedAnchorGroup> affectedAnchors; - // We keep only referenced anchors' name in positioned frames to avoid dealing - // with lifetime issues associated with it. Now we need to re-establish that - // association. - for (const auto& kv : aAnchors) { + for (const auto& kv : mAnchorPosAnchors) { const auto& anchorFrames = kv.GetData(); Maybe<nsTArray<AffectedAnchor>> affected; for (const auto& frame : anchorFrames) { - const auto* scrollContainer = - AnchorPositioningUtils::GetNearestScrollFrame(frame).mScrollContainer; - if (!scrollContainer) { - // Fixed-pos anchor, likely + auto* nearestScrollFrame = FindScrollContainerFrameOf(frame); + if (!nearestScrollFrame) { + // Fixed-pos anchor. continue; } - // Does this scroll container match a anchor's nearest scroll container, - // or contain it? - if (!AffectedByScrollContainer(scrollContainer, aScrollContainer)) { + if (!UnderScrollContainer(nearestScrollFrame, aScrollContainer)) { continue; } if (affected.isNothing()) { affected = Some(nsTArray<AffectedAnchor>{anchorFrames.Length()}); } - affected.ref().AppendElement(AffectedAnchor{frame, scrollContainer}); + affected.ref().AppendElement(AffectedAnchor{frame, nearestScrollFrame}); } if (affected.isSome()) { affectedAnchors.AppendElement( AffectedAnchorGroup{kv.GetKey(), std::move(*affected)}); } } - return affectedAnchors; -} -struct FindScrollCompensatedAnchorResult { - const AffectedAnchor& mAnchorInfo; - AnchorPosReferenceData& mReferenceData; -}; - -// Given a list of anchors affected by scrolling, find one that the given -// positioned frame need to compensate scroll for. -static Maybe<FindScrollCompensatedAnchorResult> FindScrollCompensatedAnchor( - const PresShell* aPresShell, - const nsTArray<AffectedAnchorGroup>& aAffectedAnchors, - const nsIFrame* aPositioned) { - MOZ_ASSERT(aPositioned->IsAbsolutelyPositioned(), - "Anchor positioned frame is not absolutely positioned?"); - 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()) { - return Nothing{}; + if (affectedAnchors.IsEmpty()) { + return; } - struct Comparator { - bool Equals(const AffectedAnchor& aEntry, const nsIFrame* aFrame) const { - return aEntry.mAnchor == aFrame; + // Now, find positioned frames that depend on anchors under the scroll frame. + for (auto* positioned : mAnchorPosPositioned) { + MOZ_ASSERT(positioned->IsAbsolutelyPositioned(), + "Anchor positioned frame is not absolutely positioned?"); + if (positioned->HasAnyStateBits(NS_FRAME_IS_DIRTY)) { + // Already dirty? Skip. + continue; } - }; - // Find the relevant default anchor. - const auto* defaultAnchorName = - AnchorPositioningUtils::GetUsedAnchorName(aPositioned, nullptr); - nsIFrame const* defaultAnchor = nullptr; - for (const auto& group : aAffectedAnchors) { - if (defaultAnchorName && - !group.mAnchorName->Equals(defaultAnchorName->GetUTF16String(), - defaultAnchorName->GetLength())) { - // Default anchor has a name, and it's different from this affected - // anchor group. + const auto* anchorPosReferenceData = + positioned->GetProperty(nsIFrame::AnchorPosReferences()); + if (!anchorPosReferenceData || anchorPosReferenceData->IsEmpty()) { + // If it doesn't reference any anchors then it doesn't compensate for + // scroll. continue; } - if (!defaultAnchor) { - defaultAnchor = - aPresShell->GetAnchorPosAnchor(defaultAnchorName, aPositioned); - if (!defaultAnchor) { - // Default anchor not valid for this positioned frame. - return Nothing{}; - } + + const nsAtom* defaultAnchorName = + AnchorPositioningUtils::GetUsedAnchorName(positioned, nullptr); + if (!defaultAnchorName) { + continue; } - const auto& anchors = group.mFrames; - // Find the affected anchor that not only matches in name, but in actual - // frame. - const auto idx = anchors.IndexOf(defaultAnchor, 0, Comparator{}); - if (idx == anchors.NoIndex) { - // We found the default anchor, but it wasn't correct. - break; + // 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. + const nsIFrame* defaultAnchorFrame = + GetAnchorPosAnchor(defaultAnchorName, positioned); + if (!defaultAnchorFrame) { + continue; } - const auto& info = anchors.ElementAt(idx); - return Some(FindScrollCompensatedAnchorResult{info, *referencedAnchors}); - } + auto* nearestScrollToDefaultAnchor = + FindScrollContainerFrameOf(defaultAnchorFrame); - 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) { - if (mAnchorPosAnchors.IsEmpty()) { - return; - } - - AUTO_PROFILER_MARKER_UNTYPED("UpdateAnchorPosForScroll", LAYOUT, {}); - - // First, find all anchors under this scroll container. Can look at positioned - // frames' anchor references first, but we want to avoid anchor lookups if we - // can. - nsTArray<AffectedAnchorGroup> affectedAnchors = - FindAnchorsAffectedByScroll(mAnchorPosAnchors, aScrollContainer); + auto* absoluteContainingBlock = positioned->GetParent(); - if (affectedAnchors.IsEmpty()) { - return; - } + if (UnderScrollContainer(absoluteContainingBlock, + nearestScrollToDefaultAnchor)) { + // If the positioned element's containing block is under the only possible + // anchor scroll container that it can scroll with, they'll scroll + // together without intervention, so skip the update. + continue; + } - // Now, update all affected positioned elements' scroll offsets. - for (auto* positioned : mAnchorPosPositioned) { - const auto scrollDependency = - FindScrollCompensatedAnchor(this, affectedAnchors, positioned); - if (!scrollDependency) { + 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; } - const auto& info = scrollDependency->mAnchorInfo; - auto& referenceData = scrollDependency->mReferenceData; - const auto offset = AnchorPositioningUtils::GetScrollOffsetFor( - referenceData.CompensatingForScrollAxes(), positioned, info); - if (referenceData.mDefaultScrollShift != offset) { - positioned->SetPosition(positioned->GetNormalPosition() - offset); - // Update positioned frame's overflow, then the absolute containing - // block's. - positioned->UpdateOverflow(); - nsContainerFrame::PlaceFrameView(positioned); - positioned->GetParent()->UpdateOverflow(); - referenceData.mDefaultScrollShift = offset; - if (CheckOverflow(positioned, offset, referenceData)) { + + for (const auto& entry : affectedAnchors) { + const auto* anchorName = entry.mAnchorName; + const auto& anchors = entry.mFrames; + const auto* data = anchorPosReferenceData->Lookup(anchorName); + if (!data) { + continue; + } + const auto* anchorFrame = + anchorName == defaultAnchorName + ? defaultAnchorFrame + : GetAnchorPosAnchor(anchorName, positioned); + const auto idx = anchors.IndexOf(anchorFrame, 0, Comparator{}); + if (idx == nsTArray<AffectedAnchor>::NoIndex) { + // Referring to an anchor of the same name but unaffected by scrolling - + // skip. + continue; + } + auto* anchorScrollContainer = + anchors.ElementAt(idx).mNearestScrollContainer; + if (anchorScrollContainer != nearestScrollToDefaultAnchor) { + // We do not compensate for scroll for this anchor + continue; + } + + if (NeedReflowForAnchorPos(anchorFrame, positioned, *data)) { + // Abspos frames should not affect ancestor intrinsics. FrameNeedsReflow(positioned, IntrinsicDirty::None, NS_FRAME_HAS_DIRTY_CHILDREN); } diff --git a/layout/base/PresShell.h b/layout/base/PresShell.h @@ -760,7 +760,7 @@ class PresShell final : public nsStubDocumentObserver, NeedReflow, }; AnchorPosUpdateResult UpdateAnchorPosLayout(); - void UpdateAnchorPosForScroll(const ScrollContainerFrame* aScrollContainer); + void UpdateAnchorPosLayoutForScroll(ScrollContainerFrame* aScrollContainer); inline void AddAnchorPosPositioned(nsIFrame* aFrame) { if (!mAnchorPosPositioned.Contains(aFrame)) { diff --git a/layout/generic/AbsoluteContainingBlock.cpp b/layout/generic/AbsoluteContainingBlock.cpp @@ -155,40 +155,6 @@ static bool IsSnapshotContainingBlock(const nsIFrame* aFrame) { PseudoStyleType::mozSnapshotContainingBlock; } -static PhysicalAxes CheckEarlyCompensatingForScroll(const nsIFrame* aKidFrame) { - // Three conditions to compensate for scroll, once a default anchor - // exists: - // * Used alignment property is `anchor-center`, - // * `position-area` is not `none`, or - // * `anchor()` function refers to default anchor, or an anchor that - // shares the same scroller with it. - // Second condition is checkable right now, so do that. - if (!aKidFrame->StylePosition()->mPositionArea.IsNone()) { - return PhysicalAxes{PhysicalAxis::Horizontal, PhysicalAxis::Vertical}; - } - return PhysicalAxes{}; -} - -static AnchorPosResolutionCache PopulateAnchorResolutionCache( - const nsIFrame* aKidFrame, AnchorPosReferenceData* aData) { - MOZ_ASSERT(aKidFrame->HasAnchorPosReference()); - // If the default anchor exists, it will likely be referenced (Except when - // authors then use `anchor()` without referring to anchors whose nearest - // scroller that of the default anchor, but that seems - // counter-productive). This is a prerequisite for scroll compensation. We - // also need to check for `anchor()` resolutions, so cache information for - // default anchor and its scrollers right now. - AnchorPosResolutionCache result{aData, {}}; - // Let this call populate the cache. - const auto defaultAnchorInfo = AnchorPositioningUtils::ResolveAnchorPosRect( - aKidFrame, aKidFrame->GetParent(), nullptr, false, &result); - if (defaultAnchorInfo) { - aData->AdjustCompensatingForScroll( - CheckEarlyCompensatingForScroll(aKidFrame)); - } - return result; -} - void AbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame, nsPresContext* aPresContext, const ReflowInput& aReflowInput, @@ -211,12 +177,10 @@ void AbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame, aFlags.contains(AbsPosReflowFlag::CBHeightChanged); nsOverflowContinuationTracker tracker(aDelegatingFrame, true); for (nsIFrame* kidFrame : mAbsoluteFrames) { - Maybe<AnchorPosResolutionCache> anchorPosResolutionCache; + AnchorPosReferenceData* anchorPosReferenceData = nullptr; if (kidFrame->HasAnchorPosReference()) { - auto* referenceData = kidFrame->SetOrUpdateDeletableProperty( + anchorPosReferenceData = kidFrame->SetOrUpdateDeletableProperty( nsIFrame::AnchorPosReferences()); - anchorPosResolutionCache = - Some(PopulateAnchorResolutionCache(kidFrame, referenceData)); } else { kidFrame->RemoveProperty(nsIFrame::AnchorPosReferences()); } @@ -224,7 +188,7 @@ void AbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame, bool kidNeedsReflow = reflowAll || kidFrame->IsSubtreeDirty() || FrameDependsOnContainer(kidFrame, cbWidthChanged, cbHeightChanged, - anchorPosResolutionCache.ptrOr(nullptr)); + anchorPosReferenceData); if (kidFrame->IsSubtreeDirty()) { MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos( kidFrame, aDelegatingFrame); @@ -265,8 +229,7 @@ void AbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame, nsReflowStatus kidStatus; ReflowAbsoluteFrame(aDelegatingFrame, aPresContext, aReflowInput, aContainingBlock, aFlags, kidFrame, kidStatus, - aOverflowAreas, - anchorPosResolutionCache.ptrOr(nullptr)); + aOverflowAreas, anchorPosReferenceData); MOZ_ASSERT(!kidStatus.IsInlineBreakBefore(), "ShouldAvoidBreakInside should prevent this from happening"); nsIFrame* nextFrame = kidFrame->GetNextInFlow(); @@ -342,7 +305,7 @@ static inline bool IsFixedOffset(const AnchorResolvedInset& aInset) { bool AbsoluteContainingBlock::FrameDependsOnContainer( nsIFrame* f, bool aCBWidthChanged, bool aCBHeightChanged, - AnchorPosResolutionCache* aAnchorPosResolutionCache) { + AnchorPosReferenceData* anchorPosReferenceData) { const nsStylePosition* pos = f->StylePosition(); // See if f's position might have changed because it depends on a // placeholder's position. @@ -357,7 +320,7 @@ bool AbsoluteContainingBlock::FrameDependsOnContainer( const nsStyleMargin* margin = f->StyleMargin(); WritingMode wm = f->GetWritingMode(); const auto anchorResolutionParams = - AnchorPosResolutionParams::From(f, aAnchorPosResolutionCache); + AnchorPosResolutionParams::From(f, anchorPosReferenceData); if (wm.IsVertical() ? aCBHeightChanged : aCBWidthChanged) { // See if f's inline-size might have changed. // If margin-inline-start/end, padding-inline-start/end, @@ -878,145 +841,25 @@ void AbsoluteContainingBlock::ResolveAutoMarginsAfterLayout( } } -struct None {}; -using OldCacheState = Variant<None, AnchorPosResolutionCache::PositionTryBackup, - AnchorPosResolutionCache::PositionTryFullBackup>; - struct MOZ_STACK_CLASS MOZ_RAII AutoFallbackStyleSetter { - AutoFallbackStyleSetter(nsIFrame* aFrame, ComputedStyle* aFallbackStyle, - AnchorPosResolutionCache* aCache, bool aIsFirstTry) - : mFrame(aFrame), mCache{aCache}, mOldCacheState{None{}} { + AutoFallbackStyleSetter(nsIFrame* aFrame, ComputedStyle* aFallbackStyle) + : mFrame(aFrame) { if (aFallbackStyle) { mOldStyle = aFrame->SetComputedStyleWithoutNotification(aFallbackStyle); } - // We need to be able to "go back" to the old, first try (Which is not - // necessarily base style) cache. - if (!aIsFirstTry && aCache) { - // New fallback could just be a flip keyword. - if (mOldStyle && mOldStyle->StylePosition()->mPositionAnchor != - aFrame->StylePosition()->mPositionAnchor) { - mOldCacheState = - OldCacheState{aCache->TryPositionWithDifferentDefaultAnchor()}; - *aCache = PopulateAnchorResolutionCache(aFrame, aCache->mReferenceData); - } else { - mOldCacheState = - OldCacheState{aCache->TryPositionWithSameDefaultAnchor()}; - if (aCache->mDefaultAnchorCache.mAnchor) { - aCache->mReferenceData->AdjustCompensatingForScroll( - CheckEarlyCompensatingForScroll(aFrame)); - } - } - } } ~AutoFallbackStyleSetter() { if (mOldStyle) { mFrame->SetComputedStyleWithoutNotification(std::move(mOldStyle)); } - std::move(mOldCacheState) - .match( - [](None&&) {}, - [&](AnchorPosResolutionCache::PositionTryBackup&& aBackup) { - mCache->UndoTryPositionWithSameDefaultAnchor(std::move(aBackup)); - }, - [&](AnchorPosResolutionCache::PositionTryFullBackup&& aBackup) { - mCache->UndoTryPositionWithDifferentDefaultAnchor( - std::move(aBackup)); - }); } - void CommitCurrentFallback() { mOldCacheState = OldCacheState{None{}}; } - private: nsIFrame* const mFrame; RefPtr<ComputedStyle> mOldStyle; - AnchorPosResolutionCache* const mCache; - OldCacheState mOldCacheState; }; -struct AnchorShiftInfo { - nsPoint mOffset; - StylePositionArea mResolvedArea; -}; - -struct ContainingBlockRect { - Maybe<AnchorShiftInfo> mAnchorShiftInfo = Nothing{}; - nsRect mRect; - - explicit ContainingBlockRect(const nsRect& aRect) : mRect{aRect} {} - ContainingBlockRect(const nsPoint& aOffset, - const StylePositionArea& aResolvedArea, - const nsRect& aRect) - : mAnchorShiftInfo{Some(AnchorShiftInfo{aOffset, aResolvedArea})}, - mRect{aRect} {} - - StylePositionArea ResolvedPositionArea() const { - return mAnchorShiftInfo - .map([](const AnchorShiftInfo& aInfo) { return aInfo.mResolvedArea; }) - .valueOr(StylePositionArea{}); - } -}; - -static nsRect GrowOverflowCheckRect(const nsRect& aOverflowCheckRect, - const nsRect& aKidRect, - const StylePositionArea& aPosArea) { - // The overflow check rect may end up being smaller than the positioned rect - - // imagine an absolute containing block & a scroller of the same size, and an - // anchor inside it. If position-area: bottom, and the anchor positioned such - // that the anchor is touching the lower edge of the containing block & the - // scroller, the grid height is 0, which the positioned frame will always - // overflow - until the scrollbar moves. To account for this, we will let this - // containing block grow in directions that aren't constrained by the anchor. - auto result = aOverflowCheckRect; - if (aPosArea.first == StylePositionAreaKeyword::Left || - aPosArea.first == StylePositionAreaKeyword::SpanLeft) { - // Allowed to grow left - if (aKidRect.x < result.x) { - result.SetLeftEdge(aKidRect.x); - } - } else if (aPosArea.first == StylePositionAreaKeyword::Center) { - // Not allowed to grow in this axis - } else if (aPosArea.first == StylePositionAreaKeyword::Right || - aPosArea.first == StylePositionAreaKeyword::SpanRight) { - // Allowed to grow right - if (aKidRect.XMost() > aOverflowCheckRect.XMost()) { - result.SetRightEdge(aKidRect.XMost()); - } - } else if (aPosArea.first == StylePositionAreaKeyword::SpanAll) { - // Allowed to grow in both directions - if (aKidRect.x < aOverflowCheckRect.x) { - result.SetLeftEdge(aKidRect.x); - } - if (aKidRect.XMost() > aOverflowCheckRect.XMost()) { - result.SetRightEdge(aKidRect.XMost()); - } - } - if (aPosArea.first == StylePositionAreaKeyword::Top || - aPosArea.first == StylePositionAreaKeyword::SpanTop) { - // Allowed to grow up - if (aKidRect.y < aOverflowCheckRect.y) { - result.SetTopEdge(aKidRect.y); - } - } else if (aPosArea.first == StylePositionAreaKeyword::Center) { - // Not allowed to grow in this axis - } else if (aPosArea.first == StylePositionAreaKeyword::Bottom || - aPosArea.first == StylePositionAreaKeyword::SpanBottom) { - // Allowed to grow down - if (aKidRect.YMost() > aOverflowCheckRect.YMost()) { - result.SetBottomEdge(aKidRect.YMost()); - } - } else if (aPosArea.first == StylePositionAreaKeyword::SpanAll) { - // Allowed to grow in both directions - if (aKidRect.y < aOverflowCheckRect.y) { - result.SetTopEdge(aKidRect.y); - } - if (aKidRect.YMost() > aOverflowCheckRect.YMost()) { - result.SetBottomEdge(aKidRect.YMost()); - } - } - return result; -} - // XXX Optimize the case where it's a resize reflow and the absolutely // positioned child has the exact same size and position and skip the // reflow... @@ -1025,7 +868,7 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( const ReflowInput& aReflowInput, const nsRect& aOriginalContainingBlockRect, AbsPosReflowFlags aFlags, nsIFrame* aKidFrame, nsReflowStatus& aStatus, OverflowAreas* aOverflowAreas, - AnchorPosResolutionCache* aAnchorPosResolutionCache) { + AnchorPosReferenceData* aAnchorPosReferenceData) { MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); #ifdef DEBUG @@ -1094,20 +937,14 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( return SeekFallbackTo(nextFallbackIndex); }; - Maybe<uint32_t> firstTryIndex; - Maybe<nsPoint> firstTryNormalPosition; // TODO(emilio): Right now fallback only applies to position-area, which only // makes a difference with a default anchor... Generalize it? - if (aAnchorPosResolutionCache) { + if (aAnchorPosReferenceData) { bool found = false; uint32_t index = aKidFrame->GetProperty( nsIFrame::LastSuccessfulPositionFallback(), &found); - if (found) { - if (!SeekFallbackTo(index)) { - aKidFrame->RemoveProperty(nsIFrame::LastSuccessfulPositionFallback()); - } else { - firstTryIndex = Some(index); - } + if (found && !SeekFallbackTo(index)) { + aKidFrame->RemoveProperty(nsIFrame::LastSuccessfulPositionFallback()); } } @@ -1116,70 +953,47 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( bool isOverflowingCB = true; do { - AutoFallbackStyleSetter fallback(aKidFrame, currentFallbackStyle, - aAnchorPosResolutionCache, - firstTryIndex == currentFallbackIndex); - const auto cb = [&]() { + AutoFallbackStyleSetter fallback(aKidFrame, currentFallbackStyle); + auto positionArea = aKidFrame->StylePosition()->mPositionArea; + StylePositionArea resolvedPositionArea; + const nsRect usedCb = [&] { if (isGrid) { // TODO(emilio): how does position-area interact with grid? - return ContainingBlockRect{nsGridContainerFrame::GridItemCB(aKidFrame)}; + return nsGridContainerFrame::GridItemCB(aKidFrame); } - auto positionArea = aKidFrame->StylePosition()->mPositionArea; if (currentFallback && currentFallback->IsPositionArea()) { MOZ_ASSERT(currentFallback->IsPositionArea()); positionArea = currentFallback->AsPositionArea(); } - if (!positionArea.IsNone() && aAnchorPosResolutionCache) { - const auto defaultAnchorInfo = - AnchorPositioningUtils::ResolveAnchorPosRect( - aKidFrame, aDelegatingFrame, nullptr, false, - aAnchorPosResolutionCache); - if (defaultAnchorInfo) { - // Offset should be up to, but not including the containing block's - // scroll offset. - const auto offset = AnchorPositioningUtils::GetScrollOffsetFor( - aAnchorPosResolutionCache->mReferenceData - ->CompensatingForScrollAxes(), - aKidFrame, aAnchorPosResolutionCache->mDefaultAnchorCache); - // Imagine an abspos container with a scroller in it, and then an - // anchor in it, where the anchor is visually in the middle of the - // scrollport. Then, when the scroller moves such that the anchor's - // left edge is on that of the scrollports, w.r.t. containing block, - // the anchor is zero left offset horizontally. The position-area grid - // needs to account for this. - const auto scrolledAnchorRect = defaultAnchorInfo->mRect - offset; - StylePositionArea resolvedPositionArea{}; - const auto scrolledAnchorCb = AnchorPositioningUtils:: + if (!positionArea.IsNone()) { + const auto defaultAnchorInfo = AnchorPositioningUtils::GetDefaultAnchor( + aKidFrame, false, aAnchorPosReferenceData); + if (defaultAnchorInfo.mRect) { + return AnchorPositioningUtils:: AdjustAbsoluteContainingBlockRectForPositionArea( - scrolledAnchorRect, aOriginalContainingBlockRect, + *defaultAnchorInfo.mRect, aOriginalContainingBlockRect, aKidFrame->GetWritingMode(), aDelegatingFrame->GetWritingMode(), positionArea, &resolvedPositionArea); - return ContainingBlockRect{offset, resolvedPositionArea, - scrolledAnchorCb}; } } if (ViewportFrame* viewport = do_QueryFrame(aDelegatingFrame)) { if (!IsSnapshotContainingBlock(aKidFrame)) { - return ContainingBlockRect{ - viewport->GetContainingBlockAdjustedForScrollbars(aReflowInput)}; + return viewport->GetContainingBlockAdjustedForScrollbars( + aReflowInput); } - return ContainingBlockRect{ - dom::ViewTransition::SnapshotContainingBlockRect( - viewport->PresContext())}; + return dom::ViewTransition::SnapshotContainingBlockRect( + viewport->PresContext()); } - return ContainingBlockRect{aOriginalContainingBlockRect}; + return 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()); + const LogicalSize cbSize(outerWM, usedCb.Size()); ReflowInput::InitFlags initFlags; const bool staticPosIsCBOrigin = [&] { @@ -1227,7 +1041,7 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( // Don't split things below the fold. (Ideally we shouldn't *have* // anything totally below the fold, but we can't position frames // across next-in-flow breaks yet. - (aKidFrame->GetLogicalRect(cb.mRect.Size()).BStart(wm) <= + (aKidFrame->GetLogicalRect(usedCb.Size()).BStart(wm) <= aReflowInput.AvailableBSize()); // Get the border values @@ -1242,7 +1056,7 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( ReflowInput kidReflowInput(aPresContext, aReflowInput, aKidFrame, availSize.ConvertTo(wm, outerWM), Some(cbSize.ConvertTo(wm, outerWM)), initFlags, - {}, {}, aAnchorPosResolutionCache); + {}, {}, aAnchorPosReferenceData); if (nscoord kidAvailBSize = kidReflowInput.AvailableBSize(); kidAvailBSize != NS_UNCONSTRAINEDSIZE) { @@ -1282,7 +1096,7 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( // If we're solving for start in either inline or block direction, // then compute it now that we know the dimensions. ResolveSizeDependentOffsets(kidReflowInput, cbSize, kidSize, margin, - cb.ResolvedPositionArea(), offsets); + resolvedPositionArea, offsets); if (kidReflowInput.mFlags.mDeferAutoMarginComputation) { ResolveAutoMarginsAfterLayout(kidReflowInput, cbSize, kidSize, margin, @@ -1297,7 +1111,7 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( const auto anchorResolutionParams = AnchorPosOffsetResolutionParams::ExplicitCBFrameSize( AnchorPosResolutionParams::From(aKidFrame, - aAnchorPosResolutionCache), + aAnchorPosReferenceData), &cbSize); const bool iInsetAuto = stylePos @@ -1335,7 +1149,7 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( offsets.IStart(outerWM), offsets.IEnd(outerWM), }), - cb.ResolvedPositionArea()); + resolvedPositionArea); offsets.IStart(outerWM) += alignOffset; offsets.IEnd(outerWM) = @@ -1353,7 +1167,7 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( offsets.BStart(outerWM), offsets.BEnd(outerWM), }), - cb.ResolvedPositionArea()); + resolvedPositionArea); offsets.BStart(outerWM) += alignOffset; offsets.BEnd(outerWM) = cbSize.BSize(outerWM) - @@ -1370,11 +1184,7 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( border.Size(outerWM).GetPhysicalSize(outerWM)); // Offset the frame rect by the given origin of the absolute CB. - r += cb.mRect.TopLeft(); - if (cb.mAnchorShiftInfo) { - // Push the frame out to where the anchor is. - r += cb.mAnchorShiftInfo->mOffset; - } + r += usedCb.TopLeft(); aKidFrame->SetRect(r); @@ -1391,70 +1201,13 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( aKidFrame->DidReflow(aPresContext, &kidReflowInput); - [&]() { - if (!aAnchorPosResolutionCache) { - return; - } - auto* referenceData = aAnchorPosResolutionCache->mReferenceData; - if (referenceData->CompensatingForScrollAxes().isEmpty()) { - return; - } - // Now that all the anchor-related values are resolved, completing the - // scroll compensation flag, compute the scroll offsets. - const auto offset = [&]() { - if (cb.mAnchorShiftInfo) { - // Already resolved. - return cb.mAnchorShiftInfo->mOffset; - } - return AnchorPositioningUtils::GetScrollOffsetFor( - referenceData->CompensatingForScrollAxes(), aKidFrame, - aAnchorPosResolutionCache->mDefaultAnchorCache); - }(); - // Apply the hypothetical scroll offset. - const auto position = aKidFrame->GetPosition(); - // Set initial scroll position. TODO(dshin, bug 1987962): Need - // additional work for remembered scroll offset here. - if (!firstTryNormalPosition) { - firstTryNormalPosition = Some(position); - } - aKidFrame->SetProperty(nsIFrame::NormalPositionProperty(), position); - if (offset != nsPoint{}) { - aKidFrame->SetPosition(position - offset); - // Ensure that the positioned frame's overflow is updated. Absolutely - // containing block's overflow will be updated shortly below. - aKidFrame->UpdateOverflow(); - nsContainerFrame::PlaceFrameView(aKidFrame); - } - aAnchorPosResolutionCache->mReferenceData->mDefaultScrollShift = offset; - }(); - - const auto fits = aStatus.IsComplete() && [&]() { - // TODO(dshin, bug 1996832): This should probably be done at call sites of - // `AbsoluteContainingBlock::Reflow`. - const auto paddingEdgeShift = [&]() { - const auto border = aDelegatingFrame->GetUsedBorder(); - return nsPoint{border.left, border.top}; - }(); - auto overflowCheckRect = cb.mRect + paddingEdgeShift; - if (aAnchorPosResolutionCache && cb.mAnchorShiftInfo) { - overflowCheckRect = - GrowOverflowCheckRect(overflowCheckRect, aKidFrame->GetNormalRect(), - cb.mAnchorShiftInfo->mResolvedArea); - aAnchorPosResolutionCache->mReferenceData->mContainingBlockRect = - overflowCheckRect; - const auto originalContainingBlockRect = - aOriginalContainingBlockRect + paddingEdgeShift; - return AnchorPositioningUtils::FitsInContainingBlock( - overflowCheckRect, originalContainingBlockRect, - aKidFrame->GetRect()); - } - return overflowCheckRect.Contains(aKidFrame->GetRect()); - }(); + const bool fits = + aStatus.IsComplete() && usedCb.Contains(aKidFrame->GetRect()); if (fallbacks.IsEmpty() || fits) { // We completed the reflow - Either we had a fallback that fit, or we // didn't have any to try in the first place. + // TODO(dshin, bug 1987963): Hypothetical scroll will be committed here. isOverflowingCB = !fits; - fallback.CommitCurrentFallback(); break; } @@ -1468,31 +1221,6 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( aStatus.Reset(); } while (true); - [&]() { - if (!isOverflowingCB || !aAnchorPosResolutionCache || - !firstTryNormalPosition) { - return; - } - // We gave up applying fallbacks. Recover previous values, if changed. - // Because we rolled back to first try data, our cache should be up-to-date. - const auto normalPosition = *firstTryNormalPosition; - const auto oldNormalPosition = aKidFrame->GetNormalPosition(); - if (normalPosition != oldNormalPosition) { - aKidFrame->SetProperty(nsIFrame::NormalPositionProperty(), - normalPosition); - } - const auto position = - normalPosition - - aAnchorPosResolutionCache->mReferenceData->mDefaultScrollShift; - const auto oldPosition = aKidFrame->GetPosition(); - if (position == oldPosition) { - return; - } - aKidFrame->SetPosition(position); - aKidFrame->UpdateOverflow(); - nsContainerFrame::PlaceFrameView(aKidFrame); - }(); - // If author asked for `position-visibility: no-overflow` and we overflow // `usedCB`, treat as "strongly hidden". aKidFrame->AddOrRemoveStateBits( diff --git a/layout/generic/AbsoluteContainingBlock.h b/layout/generic/AbsoluteContainingBlock.h @@ -110,7 +110,7 @@ class AbsoluteContainingBlock { */ bool FrameDependsOnContainer( nsIFrame* aFrame, bool aCBWidthChanged, bool aCBHeightChanged, - mozilla::AnchorPosResolutionCache* aAnchorPosResolutionCache = nullptr); + AnchorPosReferenceData* aAnchorPosReferenceData = nullptr); /** * After an abspos child's size is known, this method can be used to @@ -145,13 +145,14 @@ class AbsoluteContainingBlock { LogicalMargin& aMargin, LogicalMargin& aOffsets); - void ReflowAbsoluteFrame( - nsIFrame* aDelegatingFrame, nsPresContext* aPresContext, - const ReflowInput& aReflowInput, - const nsRect& aOriginalContainingBlockRect, AbsPosReflowFlags aFlags, - nsIFrame* aKidFrame, nsReflowStatus& aStatus, - OverflowAreas* aOverflowAreas, - mozilla::AnchorPosResolutionCache* aAnchorPosResolutionCache = nullptr); + void ReflowAbsoluteFrame(nsIFrame* aDelegatingFrame, + nsPresContext* aPresContext, + const ReflowInput& aReflowInput, + const nsRect& aOriginalContainingBlockRect, + AbsPosReflowFlags aFlags, nsIFrame* aKidFrame, + nsReflowStatus& aStatus, + OverflowAreas* aOverflowAreas, + AnchorPosReferenceData* aAnchorPosReferenceData); /** * Mark our absolute frames dirty. diff --git a/layout/generic/ReflowInput.cpp b/layout/generic/ReflowInput.cpp @@ -109,10 +109,10 @@ static nscoord FontSizeInflationListMarginAdjustment(const nsIFrame* aFrame) { SizeComputationInput::SizeComputationInput( nsIFrame* aFrame, gfxContext* aRenderingContext, - AnchorPosResolutionCache* aAnchorPosResolutionCache) + AnchorPosReferenceData* aAnchorPosReferenceData) : mFrame(aFrame), mRenderingContext(aRenderingContext), - mAnchorPosResolutionCache(aAnchorPosResolutionCache), + mAnchorPosReferenceData(aAnchorPosReferenceData), mWritingMode(aFrame->GetWritingMode()), mIsThemed(aFrame->IsThemed()), mComputedMargin(mWritingMode), @@ -180,9 +180,9 @@ ReflowInput::ReflowInput(nsPresContext* aPresContext, InitFlags aFlags, const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aComputeSizeFlags, - AnchorPosResolutionCache* aAnchorPosResolutionCache) + AnchorPosReferenceData* aAnchorPosReferenceData) : SizeComputationInput(aFrame, aParentReflowInput.mRenderingContext, - aAnchorPosResolutionCache), + aAnchorPosReferenceData), mParentReflowInput(&aParentReflowInput), mFloatManager(aParentReflowInput.mFloatManager), mLineLayout(mFrame->IsLineParticipant() ? aParentReflowInput.mLineLayout @@ -212,7 +212,7 @@ ReflowInput::ReflowInput(nsPresContext* aPresContext, bool* aFixed = nullptr) -> nscoord { nscoord limit = NS_UNCONSTRAINEDSIZE; const auto* pos = aFrame->StylePosition(); - // Don't add to anchor resolution cache, since this function is called for + // Don't add to referenced anchors, since this function is called for // other frames. const auto anchorResolutionParams = AnchorPosResolutionParams::From(aFrame); @@ -365,13 +365,13 @@ nscoord SizeComputationInput::ComputeISizeValue( contentEdgeToBoxSizing.ISize(wm); return mFrame - ->ComputeISizeValue(mRenderingContext, wm, aContainingBlockSize, - contentEdgeToBoxSizing, boxSizingToMarginEdgeISize, - aSize, - *mFrame->StylePosition()->BSize( - wm, AnchorPosResolutionParams::From( - mFrame, mAnchorPosResolutionCache)), - mFrame->GetAspectRatio()) + ->ComputeISizeValue( + mRenderingContext, wm, aContainingBlockSize, contentEdgeToBoxSizing, + boxSizingToMarginEdgeISize, aSize, + *mFrame->StylePosition()->BSize( + wm, + AnchorPosResolutionParams::From(mFrame, mAnchorPosReferenceData)), + mFrame->GetAspectRatio()) .mISize; } @@ -527,7 +527,7 @@ void ReflowInput::Init(nsPresContext* aPresContext, nsIFrame* containingBlk = mFrame; while (containingBlk) { const nsStylePosition* stylePos = containingBlk->StylePosition(); - // It's for containing block, so don't add to anchor resolution cache + // It's for containing block, so don't add to referenced anchors const auto containingBlkAnchorResolutionParams = AnchorPosResolutionParams::From(containingBlk); const auto bSizeCoord = @@ -2945,7 +2945,7 @@ bool SizeComputationInput::ComputeMargin(WritingMode aCBWM, } LogicalMargin m(aCBWM); const auto anchorResolutionParams = - AnchorPosResolutionParams::From(mFrame, mAnchorPosResolutionCache); + AnchorPosResolutionParams::From(mFrame, mAnchorPosReferenceData); for (const LogicalSide side : LogicalSides::All) { m.Side(side, aCBWM) = nsLayoutUtils::ComputeCBDependentValue( aPercentBasis, diff --git a/layout/generic/ReflowInput.h b/layout/generic/ReflowInput.h @@ -68,8 +68,8 @@ struct SizeComputationInput { // Rendering context to use for measurement. gfxContext* mRenderingContext; - // Cache for anchor resolution in this computation. - AnchorPosResolutionCache* mAnchorPosResolutionCache = nullptr; + // Cache of referenced anchors for this computation. + AnchorPosReferenceData* mAnchorPosReferenceData = nullptr; nsMargin ComputedPhysicalMargin() const { return mComputedMargin.GetPhysicalMargin(mWritingMode); @@ -132,7 +132,7 @@ struct SizeComputationInput { // Callers using this constructor must call InitOffsets on their own. SizeComputationInput( nsIFrame* aFrame, gfxContext* aRenderingContext, - AnchorPosResolutionCache* aAnchorPosResolutionCache = nullptr); + AnchorPosReferenceData* aAnchorPosReferenceData = nullptr); SizeComputationInput(nsIFrame* aFrame, gfxContext* aRenderingContext, WritingMode aContainingBlockWritingMode, @@ -626,9 +626,9 @@ struct ReflowInput : public SizeComputationInput { * call nsIFrame::ComputeSize() internally. * @param aComputeSizeFlags A set of flags used when we call * nsIFrame::ComputeSize() internally. - * @param aAnchorResolutionCache A cache of referenced anchors to be populated - * (If specified) for this reflowed frame. Should live for the lifetime - * of this ReflowInput. + * @param aAnchorPosReferenceData A cache of referenced anchors to be + * populated (If specified) for this reflowed frame. Should live for the + * lifetime of this ReflowInput. */ ReflowInput(nsPresContext* aPresContext, const ReflowInput& aParentReflowInput, nsIFrame* aFrame, @@ -637,7 +637,7 @@ struct ReflowInput : public SizeComputationInput { InitFlags aFlags = {}, const StyleSizeOverrides& aSizeOverrides = {}, ComputeSizeFlags aComputeSizeFlags = {}, - AnchorPosResolutionCache* aAnchorPosResolutionCache = nullptr); + AnchorPosReferenceData* aAnchorPosReferenceData = nullptr); /** * This method initializes various data members. It is automatically called by @@ -985,7 +985,7 @@ inline AnchorPosResolutionParams AnchorPosResolutionParams::From( aIgnorePositionArea ? mozilla::StylePositionArea{} : aRI->mStylePosition->mPositionArea; return {aRI->mFrame, aRI->mStyleDisplay->mPosition, posArea, - aRI->mAnchorPosResolutionCache}; + aRI->mAnchorPosReferenceData}; } #endif // mozilla_ReflowInput_h diff --git a/layout/generic/ScrollContainerFrame.cpp b/layout/generic/ScrollContainerFrame.cpp @@ -3270,7 +3270,7 @@ void ScrollContainerFrame::ScrollToImpl( return; } } - PresShell()->UpdateAnchorPosForScroll(this); + PresShell()->UpdateAnchorPosLayoutForScroll(this); presContext->RecordInteractionTime( nsPresContext::InteractionType::ScrollInteraction, TimeStamp::Now()); diff --git a/layout/generic/nsIFrame.cpp b/layout/generic/nsIFrame.cpp @@ -12,7 +12,6 @@ #include <algorithm> -#include "AnchorPositioningUtils.h" #include "LayoutLogging.h" #include "RubyUtils.h" #include "TextOverflow.h" @@ -796,7 +795,6 @@ void nsIFrame::HandlePrimaryFrameStyleChange(ComputedStyle* aOldStyle) { const bool isReferringToAnchor = HasAnchorPosReference(); if (wasReferringToAnchor && !isReferringToAnchor) { PresShell()->RemoveAnchorPosPositioned(this); - RemoveProperty(NormalPositionProperty()); } else if (!wasReferringToAnchor && isReferringToAnchor) { PresShell()->AddAnchorPosPositioned(this); } @@ -8405,6 +8403,8 @@ void nsIFrame::MovePositionBy(const nsPoint& aTranslation) { } nsRect nsIFrame::GetNormalRect() const { + // It might be faster to first check + // StyleDisplay()->IsRelativelyPositionedStyle(). bool hasProperty; nsPoint normalPosition = GetProperty(NormalPositionProperty(), &hasProperty); if (hasProperty) { @@ -8472,14 +8472,13 @@ OverflowAreas nsIFrame::GetOverflowAreasRelativeToParent() const { OverflowAreas nsIFrame::GetActualAndNormalOverflowAreasRelativeToParent() const { - const bool hasAnchorPosReference = HasAnchorPosReference(); - if (MOZ_LIKELY(!IsRelativelyOrStickyPositioned() && !hasAnchorPosReference)) { + if (MOZ_LIKELY(!IsRelativelyOrStickyPositioned())) { return GetOverflowAreasRelativeToParent(); } const OverflowAreas overflows = GetOverflowAreas(); OverflowAreas actualAndNormalOverflows = overflows + GetNormalPosition(); - if (IsRelativelyPositioned() || hasAnchorPosReference) { + if (IsRelativelyPositioned()) { actualAndNormalOverflows.UnionWith(overflows + GetPosition()); } else { // For sticky positioned elements, we only use the normal position for the @@ -12268,18 +12267,6 @@ bool nsIFrame::IsSuppressedScrollableBlockForPrint() const { return true; } -PhysicalAxes nsIFrame::GetAnchorPosCompensatingForScroll() const { - if (!HasAnchorPosReference()) { - return {}; - } - const auto* prop = GetProperty(AnchorPosReferences()); - if (!prop) { - return {}; - } - - return prop->CompensatingForScrollAxes(); -} - bool nsIFrame::HasUnreflowedContainerQueryAncestor() const { // If this frame has done the first reflow, its ancestors are guaranteed to // have as well. diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h @@ -1451,8 +1451,6 @@ class nsIFrame : public nsQueryFrame { NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(LastSuccessfulPositionFallback, uint32_t); - mozilla::PhysicalAxes GetAnchorPosCompensatingForScroll() const; - // This tracks the start and end page value for a frame. // // https://www.w3.org/TR/css-page-3/#using-named-pages @@ -5923,9 +5921,9 @@ inline nsIFrame* nsFrameList::BackwardFrameTraversal::Prev(nsIFrame* aFrame) { inline AnchorPosResolutionParams AnchorPosResolutionParams::From( const nsIFrame* aFrame, - mozilla::AnchorPosResolutionCache* aAnchorPosResolutionCache) { + mozilla::AnchorPosReferenceData* aAnchorPosReferenceData) { return {aFrame, aFrame->StyleDisplay()->mPosition, - aFrame->StylePosition()->mPositionArea, aAnchorPosResolutionCache}; + aFrame->StylePosition()->mPositionArea, aAnchorPosReferenceData}; } #endif /* nsIFrame_h___ */ diff --git a/layout/style/GeckoBindings.cpp b/layout/style/GeckoBindings.cpp @@ -1845,6 +1845,58 @@ static bool AnchorSideUsesCBWM( return false; } +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); +} + +static Maybe<AnchorPosInfo> GetAnchorPosRect( + const nsIFrame* aPositioned, const nsAtom* aAnchorName, bool aCBRectIsvalid, + AnchorPosReferenceData* aReferenceData) { + if (!aPositioned) { + return Nothing{}; + } + + const auto* anchorName = + AnchorPositioningUtils::GetUsedAnchorName(aPositioned, aAnchorName); + if (!anchorName) { + return Nothing{}; + } + + MOZ_ASSERT(aPositioned->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW), + "Calling GetAnchorPoseRect on non-abspos frame?"); + const auto* containingBlock = aPositioned->GetParent(); + + Maybe<AnchorPosResolutionData>* entry = nullptr; + + if (aReferenceData) { + const auto result = aReferenceData->InsertOrModify(anchorName, true); + if (result.mAlreadyResolved) { + MOZ_ASSERT(result.mEntry, "Entry exists but null?"); + return result.mEntry->map([&](const AnchorPosResolutionData& aData) { + MOZ_ASSERT(aData.mOrigin, "Missing anchor offset resolution."); + return AnchorPosInfo{nsRect{aData.mOrigin.ref(), aData.mSize}, + containingBlock}; + }); + } + entry = result.mEntry; + } + + const auto* anchor = GetAnchorOf(aPositioned, anchorName); + if (!anchor) { + // If we have a cached entry, just check that it resolved to nothing last + // time as well. + MOZ_ASSERT_IF(entry, entry->isNothing()); + return Nothing{}; + } + + return AnchorPositioningUtils::GetAnchorPosRect(containingBlock, anchor, + aCBRectIsvalid, entry); +} + bool Gecko_GetAnchorPosOffset(const AnchorPosOffsetResolutionParams* aParams, const nsAtom* aAnchorName, StylePhysicalSide aPropSide, @@ -1853,36 +1905,22 @@ bool Gecko_GetAnchorPosOffset(const AnchorPosOffsetResolutionParams* aParams, if (!aParams || !aParams->mBaseParams.mFrame) { return false; } - const auto* positioned = aParams->mBaseParams.mFrame; - const auto* containingBlock = positioned->GetParent(); - const auto info = AnchorPositioningUtils::ResolveAnchorPosRect( - positioned, containingBlock, aAnchorName, !aParams->mCBSize, - aParams->mBaseParams.mCache); - if (!info) { + + 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, anchorName, !aParams->mCBSize, + aParams->mBaseParams.mAnchorPosReferenceData); + if (info.isNothing()) { return false; } - if (info->mCompensatesForScroll && aParams->mBaseParams.mCache) { - // Without cache (Containing information on default anchor) being available, - // we woudln't be able to determine scroll compensation status. - const auto axis = [aPropSide]() { - switch (aPropSide) { - case StylePhysicalSide::Left: - case StylePhysicalSide::Right: - return PhysicalAxis::Horizontal; - case StylePhysicalSide::Top: - case StylePhysicalSide::Bottom: - break; - default: - MOZ_ASSERT_UNREACHABLE("Unhandled side?"); - } - return PhysicalAxis::Vertical; - }(); - aParams->mBaseParams.mCache->mReferenceData->AdjustCompensatingForScroll( - axis); - } // Compute the offset here in C++, where translating between physical/logical // coordinates is easier. - const auto& rect = info->mRect; + const auto& rect = info.ref().mRect; + const auto* containingBlock = info.ref().mContainingBlock; const auto usesCBWM = AnchorSideUsesCBWM(aAnchorSideKeyword); const auto cbwm = containingBlock->GetWritingMode(); const auto wm = @@ -1958,8 +1996,34 @@ bool Gecko_GetAnchorPosSize(const AnchorPosResolutionParams* aParams, return false; } const auto* positioned = aParams->mFrame; - const auto size = AnchorPositioningUtils::ResolveAnchorPosSize( - positioned, aAnchorName, aParams->mCache); + + const auto* anchorName = + AnchorPositioningUtils::GetUsedAnchorName(positioned, aAnchorName); + if (!anchorName) { + return false; + } + const auto size = [&]() -> Maybe<nsSize> { + Maybe<AnchorPosResolutionData>* entry = nullptr; + if (aParams->mAnchorPosReferenceData) { + const auto result = + aParams->mAnchorPosReferenceData->InsertOrModify(anchorName, false); + if (result.mAlreadyResolved) { + MOZ_ASSERT(result.mEntry, "Entry exists but null?"); + return result.mEntry->map( + [](const AnchorPosResolutionData& aData) { return aData.mSize; }); + } + entry = result.mEntry; + } + const auto* anchor = GetAnchorOf(positioned, anchorName); + if (!anchor) { + return Nothing{}; + } + const auto size = nsLayoutUtils::GetCombinedFragmentRects(anchor).Size(); + if (entry) { + *entry = Some(AnchorPosResolutionData{size, Nothing{}}); + } + return Some(size); + }(); if (!size) { return false; } diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h @@ -43,7 +43,7 @@ namespace mozilla { class ComputedStyle; struct IntrinsicSize; struct ReflowInput; -struct AnchorPosResolutionCache; +class AnchorPosReferenceData; } // namespace mozilla @@ -390,14 +390,15 @@ struct AnchorPosResolutionParams { mozilla::StylePositionProperty mPosition; // position-area property of the element in question. mozilla::StylePositionArea mPositionArea; - // Cache data used for anchor resolution. - mozilla::AnchorPosResolutionCache* const mCache; + // Storage for anchor reference data. To be populated on abspos reflow, + // whenever the frame makes any anchor reference. + mozilla::AnchorPosReferenceData* const mAnchorPosReferenceData = nullptr; // Helper functions for creating anchor resolution parameters. // Defined in corresponding header files. static inline AnchorPosResolutionParams From( const nsIFrame* aFrame, - mozilla::AnchorPosResolutionCache* aAnchorPosResolutionCache = nullptr); + mozilla::AnchorPosReferenceData* aAnchorPosReferenceData = nullptr); static inline AnchorPosResolutionParams From( const mozilla::ReflowInput* aRI, bool aIgnorePositionArea = false); static inline AnchorPosResolutionParams From( diff --git a/servo/ports/geckolib/glue.rs b/servo/ports/geckolib/glue.rs @@ -10710,7 +10710,7 @@ fn offset_params_from_base_params( mFrame: params.mFrame, mPosition: params.mPosition, mPositionArea: params.mPositionArea, - mCache: params.mCache, + mAnchorPosReferenceData: params.mAnchorPosReferenceData, }, } } diff --git a/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-chained-002.html.ini b/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-chained-002.html.ini @@ -1,3 +0,0 @@ -[anchor-scroll-chained-002.html] - # If scrollbars are always visible, we will fail this test. - expected: [PASS, FAIL] diff --git a/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-fixedpos-004.html.ini b/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-fixedpos-004.html.ini @@ -0,0 +1,2 @@ +[anchor-scroll-fixedpos-004.html] + expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-003.html.ini b/testing/web-platform/meta/css/css-anchor-position/anchor-scroll-position-try-003.html.ini @@ -0,0 +1,3 @@ +[anchor-scroll-position-try-003.html] + [Should use the first fallback position at the initial scroll offset] + expected: FAIL 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 @@ -14,7 +14,8 @@ FAIL [Should use the last (fourth) position option initially] - expected: FAIL + expected: + if os == "android": FAIL [Should still use the last position option as long as it fits.] expected: 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 @@ -9,6 +9,3 @@ expected: if (os == "mac") and not debug: [PASS, FAIL] FAIL - - [Should use the last position option initially] - expected: 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 @@ -9,6 +9,3 @@ expected: if (os == "mac") and not debug: [PASS, FAIL] FAIL - - [Should use the last fallback position initially] - expected: 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 @@ -14,4 +14,3 @@ [Should use the last fallback position initially] expected: if os == "android": [PASS, FAIL] - FAIL 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 @@ -1,7 +1,7 @@ [anchor-scroll-position-try-010.html] [Should use the third fallback position with enough space right] expected: - if os == "android": [PASS, FAIL] + if (os == "android"): [PASS, FAIL] FAIL [Should use the second fallback position with enough space above] @@ -13,4 +13,5 @@ FAIL [Should use the last fallback position initially] - expected: FAIL + expected: + if (os == "android"): 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, last successful invalidated by position-try-fallbacks change] + expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-area-in-position-try.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-area-in-position-try.html.ini @@ -0,0 +1,6 @@ +[position-area-in-position-try.html] + [Placement: --bottom, --right, --left, --top] + expected: FAIL + + [Placement: --bottom, --left, --top, --right] + expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-area-scrolling-002.tentative.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-area-scrolling-002.tentative.html.ini @@ -1,4 +1,7 @@ [position-area-scrolling-002.tentative.html] + [Scroll to 40,60] + expected: FAIL + [Reattach at 40,60] expected: FAIL diff --git a/testing/web-platform/meta/css/css-anchor-position/position-area-scrolling-005.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-area-scrolling-005.html.ini @@ -1,4 +1,7 @@ [position-area-scrolling-005.html] + [Scroll to 40,60] + expected: FAIL + [Scroll to 100,150] 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 @@ -22,6 +22,7 @@ [redisplay at 100] expected: if (os == "linux") and debug and fission: [FAIL, NOTRUN] + FAIL [scroll to 299] expected: 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 @@ -0,0 +1,4 @@ +[position-visibility-anchors-visible-non-intervening-container.html] + fuzzy: + if os == "win": maxDifference=92;totalPixels=0-1 + expected: [FAIL, PASS] diff --git a/testing/web-platform/meta/css/css-anchor-position/try-tactic-basic-anchor.html.ini b/testing/web-platform/meta/css/css-anchor-position/try-tactic-basic-anchor.html.ini @@ -0,0 +1,6 @@ +[try-tactic-basic-anchor.html] + [Uses flip-block] + expected: FAIL + + [Uses flip-inline] + expected: FAIL diff --git a/testing/web-platform/meta/css/css-highlight-api/target-text-text-decoration-001.html.ini b/testing/web-platform/meta/css/css-highlight-api/target-text-text-decoration-001.html.ini @@ -0,0 +1,2 @@ +[target-text-text-decoration-001.html] + fuzzy: maxDifference=0-92;totalPixels=0-1 +\ No newline at end of file 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 @@ -1,70 +0,0 @@ -<!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 @@ -1,70 +0,0 @@ -<!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 @@ -1,84 +0,0 @@ -<!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 @@ -1,33 +0,0 @@ -<!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 @@ -1,50 +0,0 @@ -<!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 @@ -1,60 +0,0 @@ -<!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>