commit ed0af44645631521c59aa5b5bbe2c8f469c97bff
parent 4f13eaf338db535866eac0c1032ccc53cb88b5eb
Author: Timothy Nikkel <tnikkel@gmail.com>
Date: Tue, 9 Dec 2025 08:38:55 +0000
Bug 2004600. Change how iteration is done by OneStepInASRChain/GetASRAncestorFrame to fix a bug where sticky frames can be skipped over. r=botond,hiro,layout-reviewers
The bug we are trying to fix is that OneStepInASRChain will always walk to the parent after it's while loop walking the anchor chain, potentially missing a sticky asr at the end of the anchor chain. But we can't just return the end of the anchor chain because we want to only consider it for sticky ASRs, not scroll ASRs. So we need to change these functions so that we can express that with the return value. So we change OneStepInASRChain and GetASRAncestorFrame to operate on FrameAndASRKind.
Differential Revision: https://phabricator.services.mozilla.com/D275376
Diffstat:
12 files changed, 310 insertions(+), 69 deletions(-)
diff --git a/layout/base/DisplayPortUtils.cpp b/layout/base/DisplayPortUtils.cpp
@@ -1037,8 +1037,8 @@ nsIFrame* DisplayPortUtils::OneStepInAsyncScrollableAncestorChain(
return nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame);
}
-nsIFrame* DisplayPortUtils::GetASRAncestorFrame(
- nsIFrame* aFrame, nsDisplayListBuilder* aBuilder) {
+FrameAndASRKind DisplayPortUtils::GetASRAncestorFrame(
+ FrameAndASRKind aFrameAndASRKind, nsDisplayListBuilder* aBuilder) {
MOZ_ASSERT(aBuilder->IsPaintingToWindow());
// This has different behaviour from
// nsLayoutUtils::GetAsyncScrollableAncestorFrame because the ASR tree is
@@ -1053,7 +1053,7 @@ nsIFrame* DisplayPortUtils::GetASRAncestorFrame(
// OneStepInAsyncScrollableAncestorChain, OneStepInASRChain,
// nsLayoutUtils::GetAsyncScrollableAncestorFrame.
- for (nsIFrame* f = aFrame; f;
+ for (nsIFrame* f = aFrameAndASRKind.mFrame; f;
f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
if (f->IsMenuPopupFrame()) {
break;
@@ -1063,9 +1063,12 @@ nsIFrame* DisplayPortUtils::GetASRAncestorFrame(
// IsMaybeAsynchronouslyScrolled, anchors, and sticky pos is significant.
// The potential ASR of the scroll container frame is the "inner" one, the
// potenial ASR of the sticky is the "outer" one.
- if (ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(f)) {
- if (scrollContainerFrame->IsMaybeAsynchronouslyScrolled()) {
- return f;
+ if (f != aFrameAndASRKind.mFrame ||
+ aFrameAndASRKind.mASRKind == ActiveScrolledRoot::ASRKind::Scroll) {
+ if (ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(f)) {
+ if (scrollContainerFrame->IsMaybeAsynchronouslyScrolled()) {
+ return {f, ActiveScrolledRoot::ASRKind::Scroll};
+ }
}
}
@@ -1091,37 +1094,44 @@ nsIFrame* DisplayPortUtils::GetASRAncestorFrame(
if (f->StyleDisplay()->mPosition == StylePositionProperty::Sticky) {
auto* ssc = StickyScrollContainer::GetOrCreateForFrame(f);
if (ssc && ssc->ScrollContainer()->IsMaybeAsynchronouslyScrolled()) {
- return f->FirstContinuation();
+ return {f->FirstContinuation(), ActiveScrolledRoot::ASRKind::Sticky};
}
}
}
- return nullptr;
+ return FrameAndASRKind::default_value();
}
-nsIFrame* DisplayPortUtils::OneStepInASRChain(
- nsIFrame* aFrame, nsIFrame* aLimitAncestor /* = nullptr */) {
+FrameAndASRKind DisplayPortUtils::OneStepInASRChain(
+ FrameAndASRKind aFrameAndASRKind,
+ nsIFrame* aLimitAncestor /* = nullptr */) {
// This has the same basic structure as GetASRAncestorFrame since they are
// meant to be used together. So this should be kept in sync with
// GetASRAncestorFrame. See that function for more comments about the
// structure of this code.
- if (aFrame->IsMenuPopupFrame()) {
- return nullptr;
+ if (aFrameAndASRKind.mFrame->IsMenuPopupFrame()) {
+ return FrameAndASRKind::default_value();
}
- nsIFrame* anchor = nullptr;
- while ((anchor =
- AnchorPositioningUtils::GetAnchorThatFrameScrollsWith(aFrame))) {
- MOZ_ASSERT_IF(aLimitAncestor,
- nsLayoutUtils::IsProperAncestorFrameConsideringContinuations(
- aLimitAncestor, anchor));
- aFrame = anchor;
+ if (aFrameAndASRKind.mASRKind == ActiveScrolledRoot::ASRKind::Scroll) {
+ nsIFrame* frame = aFrameAndASRKind.mFrame;
+ nsIFrame* anchor = nullptr;
+ while ((anchor =
+ AnchorPositioningUtils::GetAnchorThatFrameScrollsWith(frame))) {
+ MOZ_ASSERT_IF(
+ aLimitAncestor,
+ nsLayoutUtils::IsProperAncestorFrameConsideringContinuations(
+ aLimitAncestor, anchor));
+ frame = anchor;
+ }
+ return {frame, ActiveScrolledRoot::ASRKind::Sticky};
}
- nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame);
+ nsIFrame* parent =
+ nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrameAndASRKind.mFrame);
if (aLimitAncestor && parent &&
(parent == aLimitAncestor ||
parent->FirstContinuation() == aLimitAncestor->FirstContinuation())) {
- return nullptr;
+ return FrameAndASRKind::default_value();
}
- return parent;
+ return {parent, ActiveScrolledRoot::ASRKind::Scroll};
}
// This first checks if aFrame is a scroll frame, if so it then tries to
@@ -1153,11 +1163,6 @@ static bool ActivatePotentialStickyASR(nsIFrame* aFrame,
aBuilder);
}
-struct FrameAndASRKind {
- nsIFrame* mFrame;
- ActiveScrolledRoot::ASRKind mASRKind;
-};
-
const ActiveScrolledRoot* DisplayPortUtils::ActivateDisplayportOnASRAncestors(
nsIFrame* aAnchor, nsIFrame* aLimitAncestor,
const ActiveScrolledRoot* aASRofLimitAncestor,
@@ -1165,25 +1170,34 @@ const ActiveScrolledRoot* DisplayPortUtils::ActivateDisplayportOnASRAncestors(
MOZ_ASSERT(ScrollContainerFrame::ShouldActivateAllScrollFrames(
aBuilder, aLimitAncestor));
- MOZ_ASSERT((aASRofLimitAncestor ? aASRofLimitAncestor->mFrame : nullptr) ==
- GetASRAncestorFrame(aLimitAncestor, aBuilder));
+ MOZ_ASSERT(
+ (aASRofLimitAncestor ? FrameAndASRKind{aASRofLimitAncestor->mFrame,
+ aASRofLimitAncestor->mKind}
+ : FrameAndASRKind::default_value()) ==
+ GetASRAncestorFrame({aLimitAncestor, ActiveScrolledRoot::ASRKind::Scroll},
+ aBuilder));
MOZ_ASSERT(nsLayoutUtils::IsProperAncestorFrameConsideringContinuations(
aLimitAncestor, aAnchor));
AutoTArray<FrameAndASRKind, 4> ASRframes;
- nsIFrame* frame = aAnchor;
// The passed in frame is the anchor, if it is a scroll frame we do not scroll
// with that scroll frame (we are "outside" of it) but if it is sticky pos
- // then we do move with the sticky ASR.
- if (ActivatePotentialStickyASR(frame, aBuilder)) {
- ASRframes.EmplaceBack(frame, ActiveScrolledRoot::ASRKind::Sticky);
- }
- frame = OneStepInASRChain(frame, aLimitAncestor);
- while (frame && frame != aLimitAncestor &&
- (!aLimitAncestor ||
- frame->FirstContinuation() != aLimitAncestor->FirstContinuation())) {
+ // then we do move with the sticky ASR, so we init our iterator at
+ // ASRKind::Scroll indicating we have completed ASRKind::Scroll for aAnchor.
+ // We call OneStepInASRChain once before the loop, this moves us to the end of
+ // the anchor chain if aAnchor is also anchored, and flips the ASRKind to
+ // sticky to give us our first FrameAndASRKind to consider. (Note that if the
+ // original anchored frame was passed in to this function then calling
+ // OneStepInASRChain on the (first) anchor would be equivalent to calling
+ // OneStepInASRChain on the anchored frame, but this saves
+ // GetAnchorThatFrameScrollsWith call that we've already done.)
+ FrameAndASRKind frameAndASRKind{aAnchor, ActiveScrolledRoot::ASRKind::Scroll};
+ frameAndASRKind = OneStepInASRChain(frameAndASRKind, aLimitAncestor);
+ while (frameAndASRKind.mFrame && frameAndASRKind.mFrame != aLimitAncestor &&
+ (!aLimitAncestor || frameAndASRKind.mFrame->FirstContinuation() !=
+ aLimitAncestor->FirstContinuation())) {
// We check if each frame encountered generates an ASR. It can either
// generate a scroll asr or a sticky asr, or both! If it generates both then
// the sticky asr is the outer (parent) asr. So we check for scroll ASRs
@@ -1206,16 +1220,22 @@ const ActiveScrolledRoot* DisplayPortUtils::ActivateDisplayportOnASRAncestors(
// every scroll frame that WantAsyncScroll will have a displayport), and
// hence it's also different from what GetAsyncScrollableAncestorFrame will
// return.
- if (ActivatePotentialScrollASR(frame, aBuilder)) {
- ASRframes.EmplaceBack(frame, ActiveScrolledRoot::ASRKind::Scroll);
- }
- // Then check if it's a sticky ASR.
- if (ActivatePotentialStickyASR(frame, aBuilder)) {
- ASRframes.EmplaceBack(frame, ActiveScrolledRoot::ASRKind::Sticky);
+ switch (frameAndASRKind.mASRKind) {
+ case ActiveScrolledRoot::ASRKind::Scroll:
+ if (ActivatePotentialScrollASR(frameAndASRKind.mFrame, aBuilder)) {
+ ASRframes.EmplaceBack(frameAndASRKind);
+ }
+ break;
+
+ case ActiveScrolledRoot::ASRKind::Sticky:
+ if (ActivatePotentialStickyASR(frameAndASRKind.mFrame, aBuilder)) {
+ ASRframes.EmplaceBack(frameAndASRKind);
+ }
+ break;
}
- frame = OneStepInASRChain(frame, aLimitAncestor);
+ frameAndASRKind = OneStepInASRChain(frameAndASRKind, aLimitAncestor);
}
const ActiveScrolledRoot* asr = aASRofLimitAncestor;
@@ -1226,16 +1246,9 @@ const ActiveScrolledRoot* DisplayPortUtils::ActivateDisplayportOnASRAncestors(
MOZ_ASSERT(nsLayoutUtils::IsProperAncestorFrameConsideringContinuations(
aLimitAncestor, asrFrame.mFrame));
-#ifdef DEBUG
- if (asr && (asr->mFrame == asrFrame.mFrame)) {
- MOZ_ASSERT(asr->mKind == ActiveScrolledRoot::ASRKind::Sticky);
- MOZ_ASSERT(asrFrame.mASRKind == ActiveScrolledRoot::ASRKind::Scroll);
- } else {
- MOZ_ASSERT(
- (asr ? asr->mFrame : nullptr) ==
- GetASRAncestorFrame(OneStepInASRChain(asrFrame.mFrame), aBuilder));
- }
-#endif
+ MOZ_ASSERT((asr ? FrameAndASRKind{asr->mFrame, asr->mKind}
+ : FrameAndASRKind::default_value()) ==
+ GetASRAncestorFrame(OneStepInASRChain(asrFrame), aBuilder));
asr = (asrFrame.mASRKind == ActiveScrolledRoot::ASRKind::Scroll)
? aBuilder->GetOrCreateActiveScrolledRoot(
diff --git a/layout/base/DisplayPortUtils.h b/layout/base/DisplayPortUtils.h
@@ -124,6 +124,15 @@ struct DisplayPortMarginsPropertyData {
bool mPainted;
};
+struct FrameAndASRKind {
+ nsIFrame* mFrame;
+ ActiveScrolledRoot::ASRKind mASRKind;
+ bool operator==(const FrameAndASRKind&) const = default;
+ static FrameAndASRKind default_value() {
+ return {nullptr, ActiveScrolledRoot::ASRKind::Scroll};
+ }
+};
+
class DisplayPortUtils {
public:
/**
@@ -328,6 +337,24 @@ class DisplayPortUtils {
static nsIFrame* OneStepInAsyncScrollableAncestorChain(nsIFrame* aFrame);
/**
+ * The next two functions (GetASRAncestorFrame and OneStepInASRChain) use
+ * FrameAndASRKind (a pair of a nsIFrame pointer an an ASRKind enum) as a
+ * cursor iterating up the frame tree. Each frame can potential generate two
+ * ASRs: an inner one corresponding to scrolling with the contents of the
+ * frame if it is a scroll frame, and an outer one correspnding to scrolling
+ * with the frame itself if it is a sticky position frame. Its meaning is
+ * different for each of the two functions but is natural when considering
+ * what each function does. When passed into GetASRAncestorFrame it specifies
+ * the first frame and type for the function to check. When returned from
+ * GetASRAncestorFrame it specifies the frame and type of the ASR (because
+ * GetASRAncestorFrame only returns ASRs). When passed into OneStepInASRChain
+ * it specifies the last spot that was checked, and OneStepInASRChain's job is
+ * to move one iteration from that, so it returns the next frame and ASR kind
+ * to be checked (which may not generate an ASR, just that it needs to be
+ * checked because it could generate an ASR).
+ */
+
+ /**
* Follows the ASR (ActiveScrolledRoot) chain of frames, so that if
* f is the frame of an ASR A, then calling this function on
* OneStepInASRChain(f) will return the frame of parent ASR of A. Frames that
@@ -348,8 +375,8 @@ class DisplayPortUtils {
* generate two ASRs: an inner one corresponding to an activated scroll frame,
* and an outer one corresponding to sticky pos.
*/
- static nsIFrame* GetASRAncestorFrame(nsIFrame* aFrame,
- nsDisplayListBuilder* aBuilder);
+ static FrameAndASRKind GetASRAncestorFrame(FrameAndASRKind aFrameAndASRKind,
+ nsDisplayListBuilder* aBuilder);
/**
* Step up one frame in the ASR chain, to be used in conjunction with
@@ -360,8 +387,8 @@ class DisplayPortUtils {
* and an outer one corresponding to sticky pos. Returns null if we hit
* aLimitAncestor.
*/
- static nsIFrame* OneStepInASRChain(nsIFrame* aFrame,
- nsIFrame* aLimitAncestor = nullptr);
+ static FrameAndASRKind OneStepInASRChain(FrameAndASRKind aFrameAndASRKind,
+ nsIFrame* aLimitAncestor = nullptr);
/**
* Calls DecideScrollableLayerEnsureDisplayport on all proper ancestors of
diff --git a/layout/generic/nsIFrame.cpp b/layout/generic/nsIFrame.cpp
@@ -4453,9 +4453,11 @@ void nsIFrame::BuildDisplayListForChild(nsDisplayListBuilder* aBuilder,
if (savedOutOfFlowData->mContainingBlockInViewTransitionCapture) {
MOZ_ASSERT(asr == nullptr);
MOZ_ASSERT(aBuilder->IsInViewTransitionCapture());
- } else if ((asr ? asr->mFrame : nullptr) !=
- DisplayPortUtils::GetASRAncestorFrame(child->GetParent(),
- aBuilder)) {
+ } else if ((asr ? FrameAndASRKind{asr->mFrame, asr->mKind}
+ : FrameAndASRKind::default_value()) !=
+ DisplayPortUtils::GetASRAncestorFrame(
+ {child->GetParent(), ActiveScrolledRoot::ASRKind::Scroll},
+ aBuilder)) {
// A weird case for native anonymous content in the custom content
// container when the root is captured by a view transition. This
// content is built outside of the view transition capture but the
diff --git a/layout/reftests/async-scrolling/anchor-pos-1.html b/layout/reftests/async-scrolling/anchor-pos-1.html
@@ -40,7 +40,7 @@
reftest-displayport-w="100" reftest-displayport-h="500"
reftest-async-scroll-y="50">
<div class="anchor"></div>
- <div class="spacer"></div>
+ <div class="spacer"></div>
</div>
<div class="anchored"></div>
</div>
diff --git a/layout/reftests/async-scrolling/anchor-pos-2.html b/layout/reftests/async-scrolling/anchor-pos-2.html
@@ -50,14 +50,14 @@
reftest-displayport-x="0" reftest-displayport-y="0"
reftest-displayport-w="150" reftest-displayport-h="500">
<div class="scroller"
- reftest-displayport-x="0" reftest-displayport-y="0"
- reftest-displayport-w="150" reftest-displayport-h="500"
- reftest-async-scroll-y="50">
+ reftest-displayport-x="0" reftest-displayport-y="0"
+ reftest-displayport-w="150" reftest-displayport-h="500"
+ reftest-async-scroll-y="50">
<div class="anchor2"></div>
<div class="spacer"></div>
</div>
<div class="anchor1"></div>
- <div class="spacer"></div>
+ <div class="spacer"></div>
</div>
<div class="anchored"></div>
</div>
diff --git a/layout/reftests/async-scrolling/anchor-pos-sticky-1.html b/layout/reftests/async-scrolling/anchor-pos-sticky-1.html
@@ -46,7 +46,7 @@
reftest-async-scroll-y="50">
<div class="spacer"></div>
<div class="anchor"></div>
- <div class="largespacer"></div>
+ <div class="largespacer"></div>
</div>
<div class="anchored"></div>
</html>
diff --git a/layout/reftests/async-scrolling/anchor-pos-sticky-2.html b/layout/reftests/async-scrolling/anchor-pos-sticky-2.html
@@ -46,7 +46,7 @@
reftest-async-scroll-y="250">
<div class="spacer"></div>
<div class="anchor"></div>
- <div class="largespacer"></div>
+ <div class="largespacer"></div>
</div>
<div class="anchored"></div>
</html>
diff --git a/layout/reftests/async-scrolling/anchor-pos-sticky-3-ref.html b/layout/reftests/async-scrolling/anchor-pos-sticky-3-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<style>
+.anchor2 {
+ position: absolute;
+ width: 50px;
+ height: 50px;
+ background: pink;
+ left: 0;
+ top: 50px;
+}
+.anchored {
+ position: absolute;
+ width: 50px;
+ height: 50px;
+ background: yellow;
+ left: 100px;
+ top: 150px;
+}
+</style>
+<div class="anchor2"></div>
+<div class="anchored"></div>
+</html>
diff --git a/layout/reftests/async-scrolling/anchor-pos-sticky-3.html b/layout/reftests/async-scrolling/anchor-pos-sticky-3.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html reftest-async-scroll>
+<style>
+.container {
+ position: absolute;
+ left: 0;
+ top: 0;
+}
+.scroller {
+ position: absolute;
+ overflow: auto;
+ scrollbar-width: none;
+ width: 150px;
+ height: 100px;
+}
+.spacer {
+ width: 1px;
+ height: 100px;
+}
+.largespacer {
+ width: 1px;
+ height: 500px;
+}
+.anchor1 {
+ width: 50px;
+ height: 50px;
+ background: blue;
+ anchor-name: --my-anchor1;
+ position: absolute;
+ position-anchor: --my-anchor2;
+ position-visibility: always;
+ left: anchor(right);
+ top: anchor(bottom);
+}
+.anchor2 {
+ position: sticky;
+ top: 0;
+ width: 50px;
+ height: 50px;
+ background: pink;
+ anchor-name: --my-anchor2;
+}
+.anchored {
+ position: absolute;
+ position-anchor: --my-anchor1;
+ position-visibility: always;
+ width: 50px;
+ height: 50px;
+ background: yellow;
+ left: anchor(right);
+ top: anchor(bottom);
+}
+</style>
+<div class="container">
+ <div class="scroller"
+ reftest-displayport-x="0" reftest-displayport-y="0"
+ reftest-displayport-w="150" reftest-displayport-h="500">
+ <div class="scroller"
+ reftest-displayport-x="0" reftest-displayport-y="0"
+ reftest-displayport-w="150" reftest-displayport-h="500"
+ reftest-async-scroll-y="50">
+ <div class="spacer"></div>
+ <div class="anchor2"></div>
+ <div class="largespacer"></div>
+ </div>
+ <div class="anchor1"></div>
+ <div class="largespacer"></div>
+ </div>
+ <div class="anchored"></div>
+</div>
+</html>
diff --git a/layout/reftests/async-scrolling/anchor-pos-sticky-4-ref.html b/layout/reftests/async-scrolling/anchor-pos-sticky-4-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<style>
+.anchor2 {
+ position: absolute;
+ width: 50px;
+ height: 50px;
+ background: pink;
+ left: 0;
+ top: 0;
+}
+.anchor1 {
+ width: 50px;
+ height: 50px;
+ background: blue;
+ position: absolute;
+ left: 50px;
+ top: 50px;
+}
+.anchored {
+ position: absolute;
+ width: 50px;
+ height: 50px;
+ background: yellow;
+ left: 100px;
+ top: 100px;
+}
+</style>
+<div class="anchor2"></div>
+<div class="anchor1"></div>
+<div class="anchored"></div>
+</html>
diff --git a/layout/reftests/async-scrolling/anchor-pos-sticky-4.html b/layout/reftests/async-scrolling/anchor-pos-sticky-4.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html reftest-async-scroll>
+<style>
+.container {
+ position: absolute;
+ left: 0;
+ top: 0;
+}
+.scroller {
+ position: absolute;
+ overflow: auto;
+ scrollbar-width: none;
+ width: 150px;
+ height: 100px;
+}
+.spacer {
+ width: 1px;
+ height: 100px;
+}
+.largespacer {
+ width: 1px;
+ height: 500px;
+}
+.anchor1 {
+ width: 50px;
+ height: 50px;
+ background: blue;
+ anchor-name: --my-anchor1;
+ position: absolute;
+ position-anchor: --my-anchor2;
+ position-visibility: always;
+ left: anchor(right);
+ top: anchor(bottom);
+}
+.anchor2 {
+ position: sticky;
+ top: 0;
+ width: 50px;
+ height: 50px;
+ background: pink;
+ anchor-name: --my-anchor2;
+}
+.anchored {
+ position: absolute;
+ position-anchor: --my-anchor1;
+ position-visibility: always;
+ width: 50px;
+ height: 50px;
+ background: yellow;
+ left: anchor(right);
+ top: anchor(bottom);
+}
+</style>
+<div class="container">
+ <div class="scroller"
+ reftest-displayport-x="0" reftest-displayport-y="0"
+ reftest-displayport-w="150" reftest-displayport-h="500">
+ <div class="scroller"
+ reftest-displayport-x="0" reftest-displayport-y="0"
+ reftest-displayport-w="150" reftest-displayport-h="500"
+ reftest-async-scroll-y="250">
+ <div class="spacer"></div>
+ <div class="anchor2"></div>
+ <div class="largespacer"></div>
+ </div>
+ <div class="anchor1"></div>
+ <div class="largespacer"></div>
+ </div>
+ <div class="anchored"></div>
+</div>
+</html>
diff --git a/layout/reftests/async-scrolling/reftest.list b/layout/reftests/async-scrolling/reftest.list
@@ -194,3 +194,5 @@ pref(layout.css.anchor-positioning.enabled,true) == anchor-pos-1.html anchor-pos
pref(layout.css.anchor-positioning.enabled,true) == anchor-pos-2.html anchor-pos-2-ref.html
pref(layout.css.anchor-positioning.enabled,true) == anchor-pos-sticky-1.html anchor-pos-sticky-1-ref.html
pref(layout.css.anchor-positioning.enabled,true) == anchor-pos-sticky-2.html anchor-pos-sticky-2-ref.html
+pref(layout.css.anchor-positioning.enabled,true) == anchor-pos-sticky-3.html anchor-pos-sticky-3-ref.html
+pref(layout.css.anchor-positioning.enabled,true) == anchor-pos-sticky-4.html anchor-pos-sticky-4-ref.html