commit 89b86fad2fafb90dcd329d06999393dab29af58a
parent 0aaf10f74f5189678fbfdd64e37888d7fe86c5e1
Author: Jonathan Kew <jkew@mozilla.com>
Date: Sat, 27 Dec 2025 15:37:19 +0000
Bug 2006373 - Rework handling of last-successful-fallback. r=layout-anchor-positioning-reviewers,layout-reviewers,emilio
Author: Jonathan Kew <jkew@mozilla.com>
Date: Tue Dec 23 13:47:40 2025 +0000
This brings our last-successful-fallback handling into line with the spec.
We get a couple of new test passes. There would be additional passing tests,
I believe (e.g. last-successful-basic.html) except that it's also affected
by bug 1924792.
Differential Revision: https://phabricator.services.mozilla.com/D277555
Diffstat:
4 files changed, 45 insertions(+), 24 deletions(-)
diff --git a/layout/base/AnchorPositioningUtils.cpp b/layout/base/AnchorPositioningUtils.cpp
@@ -986,9 +986,6 @@ static bool TriggerFallbackReflow(PresShell* aPresShell, nsIFrame* aPositioned,
if (!needsRetry) {
return false;
}
- // We want to retry from the first position; remove the last position
- // property so all potential positions are re-evaluated.
- aPositioned->RemoveProperty(nsIFrame::LastSuccessfulPositionFallback());
aPresShell->MarkPositionedFrameForReflow(aPositioned);
return true;
}
diff --git a/layout/generic/AbsoluteContainingBlock.cpp b/layout/generic/AbsoluteContainingBlock.cpp
@@ -1183,15 +1183,27 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame(
Maybe<uint32_t> currentFallbackIndex;
const StylePositionTryFallbacksItem* currentFallback = nullptr;
RefPtr<ComputedStyle> currentFallbackStyle;
+ RefPtr<ComputedStyle> firstTryStyle;
+ Maybe<uint32_t> firstTryIndex;
- auto SeekFallbackTo = [&](uint32_t aIndex) -> bool {
- if (aIndex >= fallbacks.Length()) {
+ // Set the current fallback to the given index, or reset to the base position
+ // if Nothing() is passed.
+ auto SeekFallbackTo = [&](Maybe<uint32_t> aIndex) -> bool {
+ if (!aIndex) {
+ currentFallbackIndex = Nothing();
+ currentFallback = nullptr;
+ currentFallbackStyle = nullptr;
+ return true;
+ }
+ uint32_t index = *aIndex;
+ if (index >= fallbacks.Length()) {
return false;
}
+
const StylePositionTryFallbacksItem* nextFallback;
RefPtr<ComputedStyle> nextFallbackStyle;
while (true) {
- nextFallback = &fallbacks[aIndex];
+ nextFallback = &fallbacks[index];
nextFallbackStyle = aPresContext->StyleSet()->ResolvePositionTry(
*aKidFrame->GetContent()->AsElement(), *aKidFrame->Style(),
*nextFallback);
@@ -1200,35 +1212,47 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame(
}
// No @position-try rule for this name was found, per spec we should
// skip it.
- aIndex++;
- if (aIndex >= fallbacks.Length()) {
+ index++;
+ if (index >= fallbacks.Length()) {
return false;
}
}
- currentFallbackIndex = Some(aIndex);
+ currentFallbackIndex = Some(index);
currentFallback = nextFallback;
currentFallbackStyle = std::move(nextFallbackStyle);
return true;
};
+ // Advance to the next fallback to be tried. Normally this is simply the next
+ // index in the position-try-fallbacks list, but we have some special cases:
+ // - if we're currently at the last-successful fallback (recorded as
+ // firstTryIndex), we "advance" to the base position
+ // - we skip the last-successful fallback when we reach its position again
auto TryAdvanceFallback = [&]() -> bool {
if (fallbacks.IsEmpty()) {
return false;
}
+ if (firstTryIndex && currentFallbackIndex == firstTryIndex) {
+ return SeekFallbackTo(Nothing());
+ }
uint32_t nextFallbackIndex =
currentFallbackIndex ? *currentFallbackIndex + 1 : 0;
- return SeekFallbackTo(nextFallbackIndex);
+ if (firstTryIndex && nextFallbackIndex == *firstTryIndex) {
+ ++nextFallbackIndex;
+ }
+ return SeekFallbackTo(Some(nextFallbackIndex));
};
- Maybe<uint32_t> firstTryIndex;
Maybe<nsPoint> firstTryNormalPosition;
- const auto* lastSuccessfulPosition =
- aKidFrame->GetProperty(nsIFrame::LastSuccessfulPositionFallback());
- if (lastSuccessfulPosition) {
- if (!SeekFallbackTo(lastSuccessfulPosition->mIndex)) {
- aKidFrame->RemoveProperty(nsIFrame::LastSuccessfulPositionFallback());
- } else {
+ if (auto* lastSuccessfulPosition =
+ aKidFrame->GetProperty(nsIFrame::LastSuccessfulPositionFallback())) {
+ if (SeekFallbackTo(Some(lastSuccessfulPosition->mIndex))) {
+ // Remember which fallback we're trying first; also record its style,
+ // in case we need to restore it later.
firstTryIndex = Some(lastSuccessfulPosition->mIndex);
+ firstTryStyle = currentFallbackStyle;
+ } else {
+ aKidFrame->RemoveProperty(nsIFrame::LastSuccessfulPositionFallback());
}
}
@@ -1639,6 +1663,9 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame(
// didn't have any to try in the first place.
isOverflowingCB = !fits;
fallback.CommitCurrentFallback();
+ if (currentFallbackIndex == Nothing()) {
+ aKidFrame->RemoveProperty(nsIFrame::LastSuccessfulPositionFallback());
+ }
break;
}
@@ -1657,8 +1684,11 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame(
!firstTryNormalPosition) {
return;
}
- // We gave up applying fallbacks. Recover previous values, if changed.
+ // We gave up applying fallbacks. Recover previous values, if changed, and
+ // reset currentFallbackIndex/Style to match.
// Because we rolled back to first try data, our cache should be up-to-date.
+ currentFallbackIndex = firstTryIndex;
+ currentFallbackStyle = firstTryStyle;
const auto normalPosition = *firstTryNormalPosition;
const auto oldNormalPosition = aKidFrame->GetNormalPosition();
if (normalPosition != oldNormalPosition) {
diff --git a/testing/web-platform/meta/css/css-anchor-position/last-successful-pseudo-element-basic.html.ini b/testing/web-platform/meta/css/css-anchor-position/last-successful-pseudo-element-basic.html.ini
@@ -1,3 +0,0 @@
-[last-successful-pseudo-element-basic.html]
- [No successful position, keep flip-block]
- expected: 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
@@ -1,3 +0,0 @@
-[last-successful-pseudo-element-fallbacks.html]
- [No successful position, keep flip-block]
- expected: FAIL