Bug 2065180 - Deal with abspos ASRs correctly when building the top layer. r=tnikkel,layout-reviewers,layout-anchor-positioning-reviewers,dshin

Cleaned-up version of D324081.

The assertion in the bug is one symptom; the other is that such a frame
doesn't follow its anchor during an async scroll, which the reftest
covers.

Co-Authored-By: Dão Gottwald <dao@mozilla.com>

Differential Revision: https://phabricator.services.mozilla.com/D324111
This commit is contained in:
Emilio Cobos Álvarez
2026-09-09 08:48:26 +00:00
committed by ealvarez@mozilla.com
co-authored by Dão Gottwald
parent 12e70a0e18
commit 313ff79ac3
7 changed files with 143 additions and 31 deletions
+34
View File
@@ -1289,6 +1289,40 @@ const ActiveScrolledRoot* DisplayPortUtils::ActivateDisplayportOnASRAncestors(
return asr;
}
const ActiveScrolledRoot* DisplayPortUtils::GetASRForAbsPosFrame(
nsIFrame* aFrame, const ActiveScrolledRoot* aContainingBlockASR,
nsDisplayListBuilder* aBuilder) {
MOZ_ASSERT(aFrame->IsAbsolutelyPositioned());
if (!aBuilder->IsPaintingToWindow() ||
// If we are in view transition capture we get a null asr no matter
// what, so don't bother checking for async scrolling with a CSS anchor
// pos anchor.
aBuilder->IsInViewTransitionCapture() ||
// If there is an active view transition in this document it is tricky
// to determine what will be an active scroll frame outside of that
// frame's BuildDisplayList, so don't bother to async scroll with an
// anchor in that case. Bug 2001861 tracks removing this check.
aFrame->PresContext()->Document()->GetActiveViewTransition()) {
return aContainingBlockASR;
}
nsIFrame* scrollsWithAnchor =
AnchorPositioningUtils::GetAnchorThatFrameScrollsWith(aFrame, aBuilder);
if (!scrollsWithAnchor) {
return aContainingBlockASR;
}
if (aBuilder->IsRetainingDisplayList()) {
if (aBuilder->IsPartialUpdate()) {
aBuilder->SetPartialBuildFailed(true);
} else {
aBuilder->SetDisablePartialUpdates(true);
}
}
// TODO should we set the scroll parent id too?
// https://github.com/w3c/csswg-drafts/issues/12042
return ActivateDisplayportOnASRAncestors(
scrollsWithAnchor, aFrame->GetParent(), aContainingBlockASR, aBuilder);
}
static bool CheckAxes(ScrollContainerFrame* aScrollFrame, PhysicalAxes aAxes) {
if (aAxes == kPhysicalAxesBoth) {
return true;
+7
View File
@@ -406,6 +406,13 @@ class DisplayPortUtils {
const ActiveScrolledRoot* aASRofLimitAncestor,
nsDisplayListBuilder* aBuilder);
/**
* Returns the ASR that an abspos frame `aFrame` should scroll with.
*/
static const ActiveScrolledRoot* GetASRForAbsPosFrame(
nsIFrame* aFrame, const ActiveScrolledRoot* aContainingBlockASR,
nsDisplayListBuilder* aBuilder);
/**
* aFrame is an absolutely positioned frame that is anchor positioned and
* compensates for scroll in at least one axis.
+5 -1
View File
@@ -12,6 +12,7 @@
#include "MobileViewportManager.h"
#include "mozilla/AbsoluteContainingBlock.h"
#include "mozilla/ComputedStyleInlines.h"
#include "mozilla/DisplayPortUtils.h"
#include "mozilla/PresShell.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ReflowInput.h"
@@ -133,8 +134,11 @@ static void BuildDisplayListForTopLayerFrame(nsDisplayListBuilder* aBuilder,
// root scroll frame.
clipState.SetClipChainForContainingBlockDescendants(
savedOutOfFlowData->mCombinedClipChain);
asrSetter.SetCurrentActiveScrolledRoot(
savedOutOfFlowData->mContainingBlockActiveScrolledRoot);
DisplayPortUtils::GetASRForAbsPosFrame(
aFrame, savedOutOfFlowData->mContainingBlockActiveScrolledRoot,
aBuilder));
asrSetter.SetCurrentScrollParentId(savedOutOfFlowData->mScrollParentId);
}
}
+2 -30
View File
@@ -4607,30 +4607,6 @@ void nsIFrame::BuildDisplayListForChild(nsDisplayListBuilder* aBuilder,
if (savedOutOfFlowData) {
aBuilder->SetBuildingInvisibleItems(false);
nsIFrame* scrollsWithAnchor = nullptr;
if (aBuilder->IsPaintingToWindow() &&
// If we are in view transition capture we get a null asr no matter
// what, so don't bother checking for async scrolling with a CSS anchor
// pos anchor.
!aBuilder->IsInViewTransitionCapture() &&
child->IsAbsolutelyPositioned(disp) &&
// If there is an active view transition in this document it is tricky
// to determine what will be an active scroll frame outside of that
// frame's BuildDisplayList, so don't bother to async scroll with an
// anchor in that case. Bug 2001861 tracks removing this check.
!PresContext()->Document()->GetActiveViewTransition()) {
scrollsWithAnchor = AnchorPositioningUtils::GetAnchorThatFrameScrollsWith(
child, aBuilder);
if (scrollsWithAnchor && aBuilder->IsRetainingDisplayList()) {
if (aBuilder->IsPartialUpdate()) {
aBuilder->SetPartialBuildFailed(true);
} else {
aBuilder->SetDisablePartialUpdates(true);
}
}
}
const ActiveScrolledRoot* asr =
savedOutOfFlowData->mContainingBlockActiveScrolledRoot;
@@ -4669,12 +4645,8 @@ void nsIFrame::BuildDisplayListForChild(nsDisplayListBuilder* aBuilder,
}
#endif
if (scrollsWithAnchor) {
asr = DisplayPortUtils::ActivateDisplayportOnASRAncestors(
scrollsWithAnchor, child->GetParent(), asr, aBuilder);
// TODO should we set the scroll parent id too?
// https://github.com/w3c/csswg-drafts/issues/12042
if (child->IsAbsolutelyPositioned(disp)) {
asr = DisplayPortUtils::GetASRForAbsPosFrame(child, asr, aBuilder);
}
if (aBuilder->IsInViewTransitionCapture()) {
@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<style>
.anchor {
position: absolute;
left: 0;
top: 50px;
width: 50px;
height: 50px;
background: blue;
}
.anchored {
position: absolute;
left: 50px;
top: 50px;
width: 50px;
height: 50px;
background: yellow;
}
.child {
position: absolute;
left: 50px;
top: 50px;
width: 10px;
height: 10px;
background: green;
}
</style>
<div class="anchor"></div>
<div class="anchored"></div>
<div class="child"></div>
</html>
@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html reftest-async-scroll>
<style>
.scroller {
position: absolute;
left: 0;
top: 0;
overflow: auto;
scrollbar-width: none;
width: 150px;
height: 100px;
}
.spacer {
width: 1px;
height: 100px;
}
.largespacer {
width: 1px;
height: 500px;
}
.anchor {
width: 50px;
height: 50px;
background: blue;
anchor-name: --my-anchor;
}
#popover {
margin: 0;
border: none;
padding: 0;
overflow: visible;
position: absolute;
position-anchor: --my-anchor;
position-visibility: always;
left: anchor(right);
top: anchor(top);
width: 50px;
height: 50px;
background: yellow;
}
#popover > div {
position: absolute;
left: 0;
top: 0;
width: 10px;
height: 10px;
background: green;
}
</style>
<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="anchor"></div>
<div class="largespacer"></div>
</div>
<div id="popover" popover><div></div></div>
<script>
document.getElementById("popover").showPopover();
</script>
</html>
@@ -202,3 +202,4 @@ defaults
== anchor-pos-sticky-3.html anchor-pos-sticky-3-ref.html
== anchor-pos-sticky-4.html anchor-pos-sticky-4-ref.html
== anchor-pos-one-axis-1.html anchor-pos-one-axis-1-ref.html
== anchor-pos-top-layer-1.html anchor-pos-top-layer-1-ref.html