diff --git a/layout/generic/ScrollAnchorContainer.cpp b/layout/generic/ScrollAnchorContainer.cpp index 159ee4fbc93e..ecc5c6b9ce6f 100644 --- a/layout/generic/ScrollAnchorContainer.cpp +++ b/layout/generic/ScrollAnchorContainer.cpp @@ -7,6 +7,7 @@ #include "ScrollAnchorContainer.h" #include "mozilla/dom/Text.h" +#include "mozilla/ScopeExit.h" #include "mozilla/PresShell.h" #include "mozilla/ProfilerLabels.h" #include "mozilla/StaticPrefs_layout.h" @@ -45,6 +46,7 @@ ScrollAnchorContainer::ScrollAnchorContainer(ScrollFrameHelper* aScrollFrame) mAnchorNode(nullptr), mLastAnchorOffset(0), mDisabled(false), + mAnchorMightBeSubOptimal(false), mAnchorNodeIsDirty(true), mApplyingAnchorAdjustment(false), mSuppressAnchorAdjustment(false) {} @@ -263,6 +265,8 @@ void ScrollAnchorContainer::SelectAnchor() { ANCHOR_LOG("Skipping selection, doesn't maintain a scroll anchor.\n"); mAnchorNode = nullptr; } + mAnchorMightBeSubOptimal = + mAnchorNode && mAnchorNode->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); // Update the anchor flags if needed if (oldAnchor != mAnchorNode) { @@ -400,6 +404,7 @@ void ScrollAnchorContainer::InvalidateAnchor(ScheduleSelection aSchedule) { FindFor(Frame())->InvalidateAnchor(); } mAnchorNode = nullptr; + mAnchorMightBeSubOptimal = false; mAnchorNodeIsDirty = true; mLastAnchorOffset = 0; @@ -445,6 +450,16 @@ void ScrollAnchorContainer::ApplyAdjustments() { ANCHOR_LOG("Anchor has moved from %d to %d.\n", mLastAnchorOffset, current); + auto maybeInvalidate = MakeScopeExit([&] { + if (mAnchorMightBeSubOptimal && + StaticPrefs::layout_css_scroll_anchoring_reselect_if_suboptimal()) { + ANCHOR_LOG( + "Anchor might be suboptimal, invalidating to try finding a better " + "one\n"); + InvalidateAnchor(); + } + }); + if (logicalAdjustment == 0) { ANCHOR_LOG("Ignoring zero delta anchor adjustment for %p.\n", this); mSuppressAnchorAdjustment = false; diff --git a/layout/generic/ScrollAnchorContainer.h b/layout/generic/ScrollAnchorContainer.h index 08f2a76a74ea..56b2a20241dc 100644 --- a/layout/generic/ScrollAnchorContainer.h +++ b/layout/generic/ScrollAnchorContainer.h @@ -167,6 +167,9 @@ class ScrollAnchorContainer final { // layout.css.scroll-anchoring.min-adjustment-threshold. bool mDisabled : 1; + // True if when we selected the current scroll anchor, there were unlaid out + // children that could be better anchor nodes after layout. + bool mAnchorMightBeSubOptimal : 1; // True if we should recalculate our anchor node at the next chance bool mAnchorNodeIsDirty : 1; // True if we are applying a scroll anchor adjustment diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index aee083d6cef5..02c0e79c68f2 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -7596,6 +7596,14 @@ value: false mirror: always +# Pref to control whether we reselect scroll anchors if sub-optimal +# +# See https://github.com/w3c/csswg-drafts/issues/6787 +- name: layout.css.scroll-anchoring.reselect-if-suboptimal + type: bool + value: true + mirror: always + # Are shared memory User Agent style sheets enabled? - name: layout.css.shared-memory-ua-sheets.enabled type: bool diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/dirty-contents-reselect-anchor.tentative.html b/testing/web-platform/tests/css/css-scroll-anchoring/dirty-contents-reselect-anchor.tentative.html new file mode 100644 index 000000000000..41adf53a0f92 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/dirty-contents-reselect-anchor.tentative.html @@ -0,0 +1,54 @@ + + + + + + + +
+ +