Skip to content

Commit

Permalink
[anchor] Anchor recalculation point when switching fallbacks.
Browse files Browse the repository at this point in the history
The spec says that an anchor recalculation point occurs when switching
to a different anchor positioning option, and not just when the element
gets displayed for the first time.

In order to support this, we need to stay at the same position option as
long as it fits, rather than considering all fallback alternatives every
time an anchor-positioned box is being laid out. Only do that when the
current option doesn't fit.

See https://drafts.csswg.org/css-anchor-position/#fallback-apply

This will introduce some statefulness, in that the current scroll offset
alone doesn't determine which position option is used: It depends on how
we got to that scroll offset, i.e. where we came from. Add a test to
demonstrate this, and fix some existing tests to pass with the new
behavior.

Behavior behind runtime flag CSSAnchorPositionAreaVisualPosition.

Bug: 391907168, 373874012
Change-Id: I7b7f81d5d7a9009508eda03234f760bdfb7cd6f9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6198263
Reviewed-by: Rune Lillesveen <futhark@chromium.org>
Commit-Queue: Morten Stenshorne <mstensho@chromium.org>
Reviewed-by: David Grogan <dgrogan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1424129}
  • Loading branch information
mstensho authored and chromium-wpt-export-bot committed Feb 24, 2025
1 parent 48a0138 commit 62e3659
Show file tree
Hide file tree
Showing 13 changed files with 422 additions and 29 deletions.
16 changes: 12 additions & 4 deletions css/css-anchor-position/anchor-scroll-position-try-001.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<style>
#cb {
width: 400px;
height: 400px;
height: 399px;
margin: 100px;
transform: scale(1);
outline: 1px solid black;
Expand Down Expand Up @@ -86,15 +86,23 @@
}, 'Scroll further down, making the first fallback position overflow by 1px');

promise_test(async () => {
scroller.scrollTop = 200;
scroller.scrollTop = 1;
await waitUntilNextAnimationFrame();
// The first option now fits again, but the current option also still fits, so
// stay there.
assert_fallback_position(anchored, anchor, 'left');
}, 'Scroll back up so that both the first and second options fit.');

promise_test(async () => {
scroller.scrollTop = 0;
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'top');
}, 'Scroll back up to reuse the first fallback position');
}, 'Scroll further up, where the second option no longer fits');

promise_test(async () => {
scroller.scrollTop = 249;
scroller.scrollLeft = 51;
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'right');
}, 'Scroll bottom-right to make the first three fallback positions overflow');
}, 'Scroll bottom-right to make the first two options overflow');
</script>
33 changes: 28 additions & 5 deletions css/css-anchor-position/anchor-scroll-position-try-006.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,34 @@
<div id="anchored"></div>

<script>
let vw = window.innerWidth;
let vh = window.innerHeight;

promise_test(async () => {
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'top');
assert_fallback_position(anchored, anchor, 'left');
}, 'Should use the last fallback position initially');
}, 'Should use the last (fourth) position option initially');

promise_test(async () => {
// Scroll down to have enough space below the anchor, but not enough right.
await waitUntilNextAnimationFrame();
document.documentElement.scrollTop = 250;
document.documentElement.scrollLeft = 150;
await waitUntilNextAnimationFrame();
// Since the current option also still fits, we'll stay there, though.
assert_fallback_position(anchored, anchor, 'top');
assert_fallback_position(anchored, anchor, 'left');
}, 'Should still use the last position option as long as it fits.');

promise_test(async () => {
// Scroll down to have enough space below the anchor, but not enough
// right. And not enough above.
document.documentElement.scrollTop = vh - 99;
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'bottom');
assert_fallback_position(anchored, anchor, 'left');
}, 'Should use the third fallback position with enough space below');
}, 'Should use the third position option with enough space below, and not enough above');

promise_test(async () => {
// Scroll right to have enough space right to the anchor, but not enough below.
Expand All @@ -77,14 +91,23 @@
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'top');
assert_fallback_position(anchored, anchor, 'right');
}, 'Should use the second fallback position with enough space right');
}, 'Should use the second position option with enough space right');

promise_test(async () => {
// Scroll down and right to have enough space on both axes.
// Scroll down and right to have enough space on both axes. Since the second
// option still fits, stay there, though.
document.documentElement.scrollTop = 250;
document.documentElement.scrollLeft = 250;
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'top');
assert_fallback_position(anchored, anchor, 'right');
}, 'Should still use the second position option as long as it fits');

promise_test(async () => {
// Scroll down and right to have enough space on both axes.
document.documentElement.scrollTop = vh - 99;
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'bottom');
assert_fallback_position(anchored, anchor, 'right');
}, 'Should use the first fallback position with enough space below and right');
}, 'Should use the first position option with enough space below and right');
</script>
35 changes: 24 additions & 11 deletions css/css-anchor-position/anchor-scroll-position-try-007.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,36 +59,49 @@
<div id="anchored"></div>

<script>
let vw = window.innerWidth;
let vh = window.innerHeight;

promise_test(async () => {
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'top');
assert_fallback_position(anchored, anchor, 'right');
}, 'Should use the last fallback position initially');
}, 'Should use the last position option initially');

promise_test(async () => {
// Scroll left to have enough space left to the anchor, but not enough below.
document.documentElement.scrollLeft = -250;
document.documentElement.scrollTop = 150;
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'left');
assert_fallback_position(anchored, anchor, 'top');
}, 'Should use the third fallback position with enough space left');
assert_fallback_position(anchored, anchor, 'right');
// The anchored element stays where it is, though, since the current option still fits.
}, 'Should stay at initial position, since it still fits');

promise_test(async () => {
// Scroll down to have enough space below the anchor, but not enough left.
document.documentElement.scrollLeft = -150;
document.documentElement.scrollTop = 250;
// Scroll left to have enough space left to the anchor, but not enough below.
document.documentElement.scrollLeft = -vw + 99;
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'right');
assert_fallback_position(anchored, anchor, 'bottom');
}, 'Should use the second fallback position with enough space below');
assert_fallback_position(anchored, anchor, 'left');
assert_fallback_position(anchored, anchor, 'top');
}, 'Should use the third position option with enough space left');

promise_test(async () => {
// Scroll down and left to have enough space on both axes.
document.documentElement.scrollLeft = -250;
document.documentElement.scrollTop = 250;
document.documentElement.scrollTop = vh + 100;
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'left');
assert_fallback_position(anchored, anchor, 'bottom');
}, 'Should use the first fallback position with enough space left and below');
}, 'Should use the first position option with enough space left and below');

promise_test(async () => {
// Scroll down to have enough space below the anchor, but not enough left.
document.documentElement.scrollLeft = -150;
document.documentElement.scrollTop = vh - 99;
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'right');
assert_fallback_position(anchored, anchor, 'bottom');
}, 'Should use the second position option with enough space below (and not enough above)');
</script>
4 changes: 3 additions & 1 deletion css/css-anchor-position/anchor-scroll-position-try-008.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
<div id="anchored"></div>

<script>
let vw = window.innerWidth;

promise_test(async () => {
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'bottom');
Expand All @@ -86,7 +88,7 @@

promise_test(async () => {
// Scroll up and left to have enough space on both axes.
document.documentElement.scrollLeft = -250;
document.documentElement.scrollLeft = -vw + 99;
document.documentElement.scrollTop = -250;
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'left');
Expand Down
4 changes: 3 additions & 1 deletion css/css-anchor-position/anchor-scroll-position-try-009.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
<div id="anchored"></div>

<script>
let vw = window.innerWidth;

promise_test(async () => {
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'top');
Expand All @@ -85,7 +87,7 @@

promise_test(async () => {
// Scroll down and right to have enough space on both axes.
document.documentElement.scrollLeft = 250;
document.documentElement.scrollLeft = vw - 99;
document.documentElement.scrollTop = 250;
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'right');
Expand Down
4 changes: 3 additions & 1 deletion css/css-anchor-position/anchor-scroll-position-try-010.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
<div id="anchored"></div>

<script>
let vw = window.innerWidth;

promise_test(async () => {
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'bottom');
Expand All @@ -86,7 +88,7 @@

promise_test(async () => {
// Scroll up and right to have enough space on both axes.
document.documentElement.scrollLeft = 250;
document.documentElement.scrollLeft = vw - 99;
document.documentElement.scrollTop = -250;
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'right');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@

promise_test(async () => {
// Scroll up and right to have enough space on both axes.
scroller.scrollTop = -250;
scroller.scrollTop = -350;
scroller.scrollLeft = 250;
await waitUntilNextAnimationFrame();
assert_fallback_position(anchored, anchor, 'top');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,5 @@
</div>

<script>
scroller.scrollTop = 150;
scroller.scrollTop = 250;
</script>

3 changes: 1 addition & 2 deletions css/css-anchor-position/anchor-scroll-position-try-012.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,9 @@
<script>
requestAnimationFrame(() => {
requestAnimationFrame(() => {
scroller.scrollTop = 150;
scroller.scrollTop = 250;
document.documentElement.classList.remove('reftest-wait');
});
});
</script>
</html>

127 changes: 127 additions & 0 deletions css/css-anchor-position/position-area-scrolling-005.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<!DOCTYPE html>
<title>position-area with fallback and scrolling</title>
<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#scroll">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/test-common.js"></script>
<style>
#anchored {
position: absolute;
box-sizing: border-box;
border: solid;
position-anchor: --anchor;
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
width: 50%;
height: 50%;
background: cyan;
}
#container.thicker > .pos {
border-width: thick;
}
</style>
<div id="scrollable" style="position:relative; overflow:hidden; width:500px; height:500px; background:yellow;">
<div style="width:2000px; height:2000px;">
<div id="container">
<div style="anchor-name:--anchor; margin:200px; width:50px; height:50px; background:gray;"></div>
<div id="anchored" style="position-area:top left;"></div>
</div>
</div>
</div>
<script>
function assert_rects_equal(elm, x, y, width, height) {
assert_equals(elm.offsetLeft, x, (elm.id + " x"));
assert_equals(elm.offsetTop, y, (elm.id + " y"));
assert_equals(elm.offsetWidth, width, (elm.id + " width"));
assert_equals(elm.offsetHeight, height, (elm.id + " height"));
}

async function redisplay(elm) {
elm.style.display = "none";
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
elm.style.display = "block";
}

promise_test(async() => {
// Start at top left.
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
assert_rects_equal(anchored, 100, 100, 100, 100);
}, "Initial scroll position");

promise_test(async() => {
scrollable.scrollTo(40, 60);
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
assert_rects_equal(anchored, 100, 100, 100, 100);
}, "Scroll to 40,60");

promise_test(async() => {
// Switch to bottom left.
scrollable.scrollTo(100, 150);
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
assert_rects_equal(anchored, 150, 250, 50, 200);
}, "Scroll to 100,150");

promise_test(async() => {
scrollable.scrollTo(0, 0);
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
assert_rects_equal(anchored, 150, 250, 50, 200);
}, "Scroll to 0,0");

promise_test(async() => {
// Switch back to top left.
await redisplay(anchored);
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
assert_rects_equal(anchored, 100, 100, 100, 100);
}, "Redisplay at 0,0");

promise_test(async() => {
// Switch to top right.
scrollable.scrollTo(750, 100);
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
assert_rects_equal(anchored, 250, 150, 500, 50);
}, "Scroll to 750,100");

promise_test(async() => {
// Switch to bottom right.
scrollable.scrollTo(750, 196);
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
assert_rects_equal(anchored, 250, 250, 500, 223);
}, "Scroll to 750,196");

promise_test(async() => {
scrollable.scrollTo(195, 196);
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
assert_rects_equal(anchored, 250, 250, 500, 223);
}, "Scroll to 195,196");

promise_test(async() => {
// Switch to top left.
scrollable.scrollTo(194, 196);
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
assert_rects_equal(anchored, 194, 250, 6, 223);
}, "Scroll to 194,195");

promise_test(async() => {
scrollable.scrollTo(194, 194);
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
assert_rects_equal(anchored, 194, 250, 6, 223);
}, "Scroll to 194,194");

promise_test(async() => {
await redisplay(anchored);
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
assert_rects_equal(anchored, 194, 194, 6, 6);
}, "Redisplay at 194,194");
</script>
Loading

0 comments on commit 62e3659

Please sign in to comment.