commit 282a5bc016f34bf23cd617f198eb7ecf7c814990
parent c2271ec0ede2fcb68ac7329fffc5833a341f85a9
Author: Emilio Cobos Álvarez <emilio@crisal.io>
Date: Tue, 2 Dec 2025 22:57:39 +0000
Bug 2003593 - Invalidate for anchor changes correctly. r=dshin,layout-anchor-positioning-reviewers,firefox-style-system-reviewers,layout-reviewers
We need to start the reflow from our parent, even if we happen to be a
reflow root.
Differential Revision: https://phabricator.services.mozilla.com/D274805
Diffstat:
7 files changed, 77 insertions(+), 13 deletions(-)
diff --git a/layout/base/AnchorPositioningUtils.cpp b/layout/base/AnchorPositioningUtils.cpp
@@ -951,8 +951,7 @@ static bool TriggerFallbackReflow(PresShell* aPresShell, nsIFrame* aPositioned,
// 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->FrameNeedsReflow(aPositioned, mozilla::IntrinsicDirty::None,
- NS_FRAME_IS_DIRTY);
+ aPresShell->MarkPositionedFrameForReflow(aPositioned);
return true;
}
diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp
@@ -11575,9 +11575,7 @@ PresShell::AnchorPosUpdateResult PresShell::UpdateAnchorPosLayout() {
}
if (shouldReflow) {
result = AnchorPosUpdateResult::NeedReflow;
- // Abspos frames should not affect ancestor intrinsics.
- FrameNeedsReflow(positioned, IntrinsicDirty::None,
- NS_FRAME_HAS_DIRTY_CHILDREN);
+ MarkPositionedFrameForReflow(positioned);
}
}
return result;
@@ -11817,8 +11815,7 @@ void PresShell::UpdateAnchorPosForScroll(
accService->NotifyAnchorPositionedScrollUpdate(this, positioned);
}
#endif
- FrameNeedsReflow(positioned, IntrinsicDirty::None,
- NS_FRAME_HAS_DIRTY_CHILDREN);
+ MarkPositionedFrameForReflow(positioned);
}
}
}
@@ -12146,13 +12143,19 @@ size_t PresShell::SizeOfTextRuns(MallocSizeOf aMallocSizeOf) const {
/* clear = */ false);
}
-void PresShell::MarkFixedFramesForReflow(IntrinsicDirty aIntrinsicDirty) {
+void PresShell::MarkPositionedFrameForReflow(nsIFrame* aFrame) {
+ // Abspos frames don't affect intrinsic sizes of ancestors.
+ FrameNeedsReflow(aFrame, IntrinsicDirty::None, NS_FRAME_HAS_DIRTY_CHILDREN,
+ ReflowRootHandling::PositionOrSizeChange);
+}
+
+void PresShell::MarkFixedFramesForReflow() {
nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
if (rootFrame) {
const nsFrameList& childList =
rootFrame->GetChildList(FrameChildListID::Fixed);
for (nsIFrame* childFrame : childList) {
- FrameNeedsReflow(childFrame, aIntrinsicDirty, NS_FRAME_IS_DIRTY);
+ MarkPositionedFrameForReflow(childFrame);
}
}
}
@@ -12230,7 +12233,7 @@ void PresShell::CompleteChangeToVisualViewportSize() {
if (ScrollContainerFrame* sf = GetRootScrollContainerFrame()) {
sf->MarkScrollbarsDirtyForReflow();
}
- MarkFixedFramesForReflow(IntrinsicDirty::None);
+ MarkFixedFramesForReflow();
}
MaybeReflowForInflationScreenSizeChange();
diff --git a/layout/base/PresShell.h b/layout/base/PresShell.h
@@ -1544,7 +1544,10 @@ class PresShell final : public nsStubDocumentObserver,
/**
* Calls FrameNeedsReflow on all fixed position children of the root frame.
*/
- void MarkFixedFramesForReflow(IntrinsicDirty aIntrinsicDirty);
+ void MarkFixedFramesForReflow();
+ // Marks a positioned frame for reflow, assuming that size or position of the
+ // frame might change.
+ void MarkPositionedFrameForReflow(nsIFrame*);
/**
* Similar to above MarkFixedFramesForReflow, but for sticky position children
diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp
@@ -2948,7 +2948,7 @@ void nsPresContext::UpdateDynamicToolbarOffset(ScreenIntCoord aOffset) {
// position:fixed or position:sticky element is painted at the correct
// position on the main-thread.
if (mDynamicToolbarHeight == 0 || aOffset == -mDynamicToolbarMaxHeight) {
- mPresShell->MarkFixedFramesForReflow(IntrinsicDirty::None);
+ mPresShell->MarkFixedFramesForReflow();
mPresShell->MarkStickyFramesForReflow();
mPresShell->ScheduleResizeEventIfNeeded(
PresShell::ResizeEventKind::Regular);
diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp
@@ -1257,7 +1257,7 @@ nsChangeHint nsStylePosition::CalcDifference(
mPositionArea != aNewData.mPositionArea) {
// We need to reflow in order to update the `AnchorPosReferences`
// property at minimum.
- hint |= nsChangeHint_NeedReflow;
+ hint |= nsChangeHint_NeedReflow | nsChangeHint_ReflowChangesSizeOrPosition;
}
if (mAspectRatio != aNewData.mAspectRatio) {
diff --git a/testing/web-platform/tests/css/css-anchor-position/anchor-name-dynamic-reflow-root-ref.html b/testing/web-platform/tests/css/css-anchor-position/anchor-name-dynamic-reflow-root-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+body { margin: 0 }
+.green {
+ position: absolute;
+ top: 20px;
+ left: 100px;
+ width: 100px;
+ height: 100px;
+ background-color: green;
+}
+</style>
+<div class="green"></div>
diff --git a/testing/web-platform/tests/css/css-anchor-position/anchor-name-dynamic-reflow-root.html b/testing/web-platform/tests/css/css-anchor-position/anchor-name-dynamic-reflow-root.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>Dynamic changes to anchor-name and reflow roots</title>
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#anchor-name">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=2003593">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mozilla" href="https://mozilla.com">
+<link rel="match" href="anchor-name-dynamic-reflow-root-ref.html">
+<style>
+body { margin: 0 }
+.tabs {
+ display: flex;
+}
+.tab {
+ height: 20px;
+ width: 100px;
+}
+.cur {
+ anchor-name: --current-tab;
+}
+.highlight {
+ position: absolute;
+ width: anchor-size(width);
+ left: anchor(left);
+ right: anchor(right);
+ height: 100px;
+ background: green;
+ top: anchor(bottom);
+ position-anchor: --current-tab;
+ transform: scaleY(1); /* This is important to test the bug */
+}
+</style>
+<div class=tabs>
+ <div id=t1 class="tab cur"></div>
+ <div id=t2 class="tab"></div>
+ <div class=highlight></div>
+</div>
+<script>
+document.addEventListener("TestRendered", function() {
+ t1.classList.remove('cur');
+ t2.classList.add('cur');
+ document.documentElement.className = "";
+});
+</script>