Skip to content

Commit

Permalink
[anchor] Better handling of OOF sizing on first layout.
Browse files Browse the repository at this point in the history
If position-area is used, an OOF may be tethered to both the IMCB
(inset-modified containing block) and the default anchor. For instance,
position-area:top will get its top inset from the containing block and
the bottom offset from its default anchor. When scrolling, the default
anchor will appear to move up or down (or sideways), so that there will
be less space available on one side, and more space available on the
other side, of the default anchor. Elements tethered to the default
anchor AND the containing block can now grow or shrink their IMCB
accordingly, based on this.

The scroll offset for this use will only be snapshotted at an "anchor
recalculation point" - i.e. when an element is laid out for the first
time, or when it changes its position fallback style.

For now we only consider first-layout as an "anchor recalculation
point". Will deal with changing position fallback styles later.

This behavior change is behind the CSSAnchorPositionAreaVisualPosition
runtime flag.

Bug: 373874012
Change-Id: I11f0d25421be6ad6b4612501940696f4c806867d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6048895
Reviewed-by: Rune Lillesveen <[email protected]>
Commit-Queue: Morten Stenshorne <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1389310}
  • Loading branch information
mstensho authored and chromium-wpt-export-bot committed Nov 28, 2024
1 parent 5d7e670 commit b36bca1
Show file tree
Hide file tree
Showing 2 changed files with 272 additions and 0 deletions.
122 changes: 122 additions & 0 deletions css/css-anchor-position/position-area-scrolling-001.tentative.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<!DOCTYPE html>
<title>position-area to include current scroll position at first layout</title>
<link rel="author" title="Morten Stenshorne" href="mailto:[email protected]">
<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>
.pos {
position: absolute;
box-sizing: border-box;
border: solid;
position-anchor: --anchor;
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;">
<div style="width:1000px; height:1000px;">
<div id="container">
<div style="height:200px;"></div>
<div style="anchor-name:--anchor; margin-left:200px; width:100px; height:100px; background:gray;"></div>
<div id="e1" class="pos" style="position-area:top left;"></div>
<div id="e2" class="pos" style="position-area:top center;"></div>
<div id="e3" class="pos" style="position-area:top right;"></div>
<div id="e4" class="pos" style="position-area:center left;"></div>
<div id="e5" class="pos" style="position-area:center center;"></div>
<div id="e6" class="pos" style="position-area:center right;"></div>
<div id="e7" class="pos" style="position-area:bottom left;"></div>
<div id="e8" class="pos" style="position-area:bottom center;"></div>
<div id="e9" class="pos" style="position-area:bottom right;"></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"));
}

function assert_at_initial() {
assert_rects_equal(e1, 100, 100, 100, 100);
assert_rects_equal(e2, 225, 100, 50, 100);
assert_rects_equal(e3, 300, 100, 100, 100);
assert_rects_equal(e4, 100, 225, 100, 50);
assert_rects_equal(e5, 225, 225, 50, 50);
assert_rects_equal(e6, 300, 225, 100, 50);
assert_rects_equal(e7, 100, 300, 100, 100);
assert_rects_equal(e8, 225, 300, 50, 100);
assert_rects_equal(e9, 300, 300, 100, 100);
}

function assert_at_40_60() {
assert_rects_equal(e1, 120, 130, 80, 70);
assert_rects_equal(e2, 225, 130, 50, 70);
assert_rects_equal(e3, 300, 130, 120, 70);
assert_rects_equal(e4, 120, 225, 80, 50);
assert_rects_equal(e5, 225, 225, 50, 50);
assert_rects_equal(e6, 300, 225, 120, 50);
assert_rects_equal(e7, 120, 300, 80, 130);
assert_rects_equal(e8, 225, 300, 50, 130);
assert_rects_equal(e9, 300, 300, 120, 130);
}

promise_test(async() => {
assert_at_initial();
}, "Initial scroll position");

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

promise_test(async() => {
// As long as we're within the same animation frame (and therefore haven't
// run ResizeObserver events), there will be no anchor recalculation point.
container.style.display = "flow-root";
assert_at_initial();
container.style.display = "none";
document.body.offsetTop;
container.style.display = "block";
assert_at_initial();
}, "Reattach at 40,60");

promise_test(async() => {
container.style.display = "none";
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
container.style.display = "block";
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
assert_at_40_60();
}, "Redisplay at 40,60");

promise_test(async() => {
scrollable.scrollTo(0, 0);
container.className = "thicker";
container.style.display = "block";
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
assert_at_40_60();
}, "Scroll to 0,0 and relayout");

promise_test(async() => {
container.style.display = "none";
await waitUntilNextAnimationFrame();
container.style.display = "block";
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
assert_at_initial();
}, "Redisplay at 0,0");
</script>
150 changes: 150 additions & 0 deletions css/css-anchor-position/position-area-scrolling-002.tentative.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<!DOCTYPE html>
<title>position-area to include current scroll position at first layout</title>
<link rel="author" title="Morten Stenshorne" href="mailto:[email protected]">
<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>
.pos {
position: absolute;
box-sizing: border-box;
border: solid;
position-anchor: --anchor;
width: 50%;
height: 50%;
background: cyan;
}
#container.thicker > .pos {
border-width: thick;
}
</style>
<div style="position:relative; width:500px; height:500px;">
<div id="scrollable" style="overflow:hidden; width:500px; height:500px;">
<div style="width:1000px; height:1000px;">
<div style="height:200px;"></div>
<div style="anchor-name:--anchor; margin-left:200px; width:100px; height:100px; background:gray;"></div>
</div>
</div>

<div id="container">
<div id="e1" class="pos" style="position-area:top left;"></div>
<div id="e2" class="pos" style="position-area:top center;"></div>
<div id="e3" class="pos" style="position-area:top right;"></div>
<div id="e4" class="pos" style="position-area:center left;"></div>
<div id="e5" class="pos" style="position-area:center center;"></div>
<div id="e6" class="pos" style="position-area:center right;"></div>
<div id="e7" class="pos" style="position-area:bottom left;"></div>
<div id="e8" class="pos" style="position-area:bottom center;"></div>
<div id="e9" class="pos" style="position-area:bottom right;"></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"));
}

function assert_scroll_and_layout_at_initial() {
assert_rects_equal(e1, 100, 100, 100, 100);
assert_rects_equal(e2, 225, 100, 50, 100);
assert_rects_equal(e3, 300, 100, 100, 100);
assert_rects_equal(e4, 100, 225, 100, 50);
assert_rects_equal(e5, 225, 225, 50, 50);
assert_rects_equal(e6, 300, 225, 100, 50);
assert_rects_equal(e7, 100, 300, 100, 100);
assert_rects_equal(e8, 225, 300, 50, 100);
assert_rects_equal(e9, 300, 300, 100, 100);
}

function assert_scroll_at_40_60_layout_at_initial() {
assert_rects_equal(e1, 60, 40, 100, 100);
assert_rects_equal(e2, 185, 40, 50, 100);
assert_rects_equal(e3, 260, 40, 100, 100);
assert_rects_equal(e4, 60, 165, 100, 50);
assert_rects_equal(e5, 185, 165, 50, 50);
assert_rects_equal(e6, 260, 165, 100, 50);
assert_rects_equal(e7, 60, 240, 100, 100);
assert_rects_equal(e8, 185, 240, 50, 100);
assert_rects_equal(e9, 260, 240, 100, 100);
}

function assert_scroll_and_layout_at_40_60() {
assert_rects_equal(e1, 80, 70, 80, 70);
assert_rects_equal(e2, 185, 70, 50, 70);
assert_rects_equal(e3, 260, 70, 120, 70);
assert_rects_equal(e4, 80, 165, 80, 50);
assert_rects_equal(e5, 185, 165, 50, 50);
assert_rects_equal(e6, 260, 165, 120, 50);
assert_rects_equal(e7, 80, 240, 80, 130);
assert_rects_equal(e8, 185, 240, 50, 130);
assert_rects_equal(e9, 260, 240, 120, 130);
}

function assert_scroll_at_initial_layout_at_40_60() {
assert_rects_equal(e1, 120, 130, 80, 70);
assert_rects_equal(e2, 225, 130, 50, 70);
assert_rects_equal(e3, 300, 130, 120, 70);
assert_rects_equal(e4, 120, 225, 80, 50);
assert_rects_equal(e5, 225, 225, 50, 50);
assert_rects_equal(e6, 300, 225, 120, 50);
assert_rects_equal(e7, 120, 300, 80, 130);
assert_rects_equal(e8, 225, 300, 50, 130);
assert_rects_equal(e9, 300, 300, 120, 130);
}

promise_test(async() => {
assert_scroll_and_layout_at_initial();
}, "Initial scroll position");

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

promise_test(async() => {
// As long as we're within the same animation frame (and therefore haven't
// run ResizeObserver events), there will be no anchor recalculation point.
container.style.display = "flow-root";
assert_scroll_and_layout_at_initial();
container.style.display = "none";
document.body.offsetTop;
container.style.display = "block";
assert_scroll_and_layout_at_initial();
}, "Reattach at 40,60");

promise_test(async() => {
container.style.display = "none";
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
container.style.display = "block";
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
assert_scroll_and_layout_at_40_60();
}, "Redisplay at 40,60");

promise_test(async() => {
scrollable.scrollTo(0, 0);
container.className = "thicker";
container.style.display = "block";
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
assert_scroll_at_initial_layout_at_40_60();
}, "Scroll to 0,0 and relayout");

promise_test(async() => {
container.style.display = "none";
await waitUntilNextAnimationFrame();
container.style.display = "block";
await waitUntilNextAnimationFrame();
await waitUntilNextAnimationFrame();
assert_scroll_and_layout_at_initial();
}, "Redisplay at 0,0");
</script>

0 comments on commit b36bca1

Please sign in to comment.