Skip to content

Commit

Permalink
[anchor] Support anchor-scope property
Browse files Browse the repository at this point in the history
The behavior of anchor-scope is similar to the previous behavior
we had for contain:style, and therefore this CL is based on that
code from futhark@. (Added in CL:5237173, and removed again
in CL:5378414).

We have to remove the optimizations that try to traverse/propagate
only the last item seen in tree order for a given name, since
anchor-scope effectively allows reaching past that last seen item.

For invalidation, we appear to already "aggressively" layout
out-of-flow elements whenever an element is marked for layout,
at least when anchor references are involved. Hence, we just need
to mark anchor-scope as invalidate:layout, and dependencies should
invalidate from that (covered by anchor-scope-dynamic.html).

Bug: 40281992
Change-Id: Ib8007cae39cc2e2481dc819b2608469d1f474350
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5526614
Auto-Submit: Anders Hartvoll Ruud <andruud@chromium.org>
Commit-Queue: Anders Hartvoll Ruud <andruud@chromium.org>
Reviewed-by: Ian Kilpatrick <ikilpatrick@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1303666}
  • Loading branch information
andruud authored and chromium-wpt-export-bot committed May 21, 2024
1 parent 699a024 commit 4593ea7
Show file tree
Hide file tree
Showing 6 changed files with 512 additions and 0 deletions.
254 changes: 254 additions & 0 deletions css/css-anchor-position/anchor-scope-basic.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
<!DOCTYPE html>
<title>CSS Anchor Positioning: Basic anchor-scope behavior</title>
<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#anchor-scope">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
.scope-all { anchor-scope: all; }
.scope-a { anchor-scope: --a; }
.scope-b { anchor-scope: --b; }
.scope-ab { anchor-scope: --a, --b; }

.anchor-a { anchor-name: --a; }
.anchor-b { anchor-name: --b; }
.anchor-a, .anchor-b {
background: skyblue;
height: 10px;
}

.anchored-a { position-anchor: --a; }
.anchored-b { position-anchor: --b; }
.anchored-a, .anchored-b {
position: absolute;
top: anchor(bottom);
left: anchor(left);
width: 5px;
height: 5px;
background: coral;
}

/* Containing block */
main {
position: relative;
width: 100px;
height: 100px;
border: 1px solid black;
}
</style>
<script>
function inflate(t, template_element) {
if (!template_element.hasAttribute('debug')) {
t.add_cleanup(() => main.replaceChildren());
}
main.append(template_element.content.cloneNode(true));
}
</script>

<main id=main>
</main>

<template id=test_scope_all_common_ancestor>
<div class=scope-all>
<div class=anchor-a></div>
<div class=anchor-a></div>
<div class=anchor-a></div>
<div class=anchor-a></div><!--A-->
<div class=anchored-a></div>
</div>
</template>
<script>
test((t) => {
inflate(t, test_scope_all_common_ancestor);
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '40px');
}, 'anchor-scope:all on common ancestor');
</script>

<template id=test_scope_named_common_ancestor>
<div class=scope-a>
<div class=anchor-a></div>
<div class=anchor-a></div>
<div class=anchor-a></div>
<div class=anchor-a></div><!--A-->
<div class=anchored-a></div>
</div>
</template>
<script>
test((t) => {
inflate(t, test_scope_named_common_ancestor);
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '40px');
}, 'anchor-scope:--a on common ancestor');
</script>

<template id=test_scope_all_sibling>
<div class=anchor-a></div>
<div class=anchor-a></div><!--A-->
<div class=scope-all>
<div class=anchor-a></div>
<div class=anchor-a></div>
</div>
<div class=anchored-a></div>
</template>
<script>
test((t) => {
inflate(t, test_scope_all_sibling);
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '20px');
}, 'anchor-scope:all on sibling');
</script>

<template id=test_scope_all_multiple>
<div class=anchor-b></div><!--B-->
<div class=anchor-a></div><!--A-->
<div class=scope-all>
<div class=anchor-b></div>
<div class=anchor-a></div>
</div>
<div class=anchored-a></div>
<div class=anchored-b></div>
</template>
<script>
test((t) => {
inflate(t, test_scope_all_multiple);
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '20px');
assert_equals(getComputedStyle(main.querySelector('.anchored-b')).top, '10px');
}, 'anchor-scope:all scopes multiple names');
</script>

<template id=test_scope_ab>
<div class=anchor-b></div><!--B-->
<div class=anchor-a></div><!--A-->
<div class=scope-ab>
<div class=anchor-b></div>
<div class=anchor-a></div>
</div>
<div class=anchored-a></div>
<div class=anchored-b></div>
</template>
<script>
test((t) => {
inflate(t, test_scope_ab);
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '20px');
assert_equals(getComputedStyle(main.querySelector('.anchored-b')).top, '10px');
}, 'anchor-scope:--a,--b scopes --a and --b');
</script>

<template id=test_scope_a>
<div class=anchor-b></div>
<div class=anchor-a></div><!--A-->
<div class=scope-a>
<div class=anchor-b></div><!--B-->
<div class=anchor-a></div>
</div>
<div class=anchored-a></div>
<div class=anchored-b></div>
</template>
<script>
test((t) => {
inflate(t, test_scope_a);
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '20px');
assert_equals(getComputedStyle(main.querySelector('.anchored-b')).top, '30px');
}, 'anchor-scope:--a scopes only --a');
</script>

<template id=test_scope_b>
<div class=anchor-b></div><!--B-->
<div class=anchor-a></div>
<div class=scope-b>
<div class=anchor-b></div>
<div class=anchor-a></div><!--A-->
</div>
<div class=anchored-a></div>
<div class=anchored-b></div>
</template>
<script>
test((t) => {
inflate(t, test_scope_b);
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '40px');
assert_equals(getComputedStyle(main.querySelector('.anchored-b')).top, '10px');
}, 'anchor-scope:--b scopes only --b');
</script>

<template id=test_out_of_flow_anchors>
<style>
.anchor-a, .anchor-b {
position: absolute;
width: 5px;
height: 5px;
}
</style>
<div class=anchor-b style='left:10px'></div>
<div class=anchor-a style='left:20px'></div><!--A-->
<div class=scope-a>
<div class=anchor-b style='left:30px'></div><!--B-->
<div class=anchor-a style='left:40px'></div>
</div>
<div class=anchored-a></div>
<div class=anchored-b></div>
</template>
<script>
test((t) => {
inflate(t, test_out_of_flow_anchors);
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '5px');
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).left, '20px');

assert_equals(getComputedStyle(main.querySelector('.anchored-b')).top, '5px');
assert_equals(getComputedStyle(main.querySelector('.anchored-b')).left, '30px');
}, 'anchor-scope:--a scopes only --a (out-of-flow anchors)');
</script>

<!-- Out-of-flow anchor within anchor-scope:--a -->
<template id=test_mixed_flow_anchors>
<style>
.abs {
position: absolute;
width: 5px;
height: 5px;
}
</style>
<div class=anchor-b></div>
<div class=anchor-a></div><!--A-->
<div class=scope-a>
<div class=anchor-b></div><!--B-->
<div class='anchor-a abs' style='top:50px'></div>
</div>
<div class=anchored-a></div>
<div class=anchored-b></div>
</template>
<script>
test((t) => {
inflate(t, test_mixed_flow_anchors);
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '20px');
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).left, '0px');

assert_equals(getComputedStyle(main.querySelector('.anchored-b')).top, '30px');
assert_equals(getComputedStyle(main.querySelector('.anchored-b')).left, '0px');
}, 'anchor-scope:--a scopes only --a (both out-of-flow and in-flow anchors)');
</script>

<!-- In-flow anchor within anchor-scope:--a -->
<template id=test_mixed_flow_anchors_reverse>
<style>
.abs {
position: absolute;
width: 5px;
height: 5px;
}
</style>
<div class=anchor-b></div>
<div class='anchor-a abs' style='top:50px'></div><!--A-->
<div class=scope-a>
<div class=anchor-b></div><!--B-->
<div class=anchor-a></div>
</div>
<div class=anchored-a></div>
<div class=anchored-b></div>
</template>
<script>
test((t) => {
inflate(t, test_mixed_flow_anchors_reverse);
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '55px');
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).left, '0px');

assert_equals(getComputedStyle(main.querySelector('.anchored-b')).top, '20px');
assert_equals(getComputedStyle(main.querySelector('.anchored-b')).left, '0px');
}, 'anchor-scope:--a scopes only --a (both out-of-flow and in-flow anchors, reverse)');
</script>
132 changes: 132 additions & 0 deletions css/css-anchor-position/anchor-scope-dynamic.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<!DOCTYPE html>
<title>CSS Anchor Positioning: anchor-scope appearing/disappearing dynamically</title>
<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#anchor-scope">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
.scope-all { anchor-scope: all; }
.scope-a { anchor-scope: --a; }
.scope-b { anchor-scope: --b; }
.scope-ab { anchor-scope: --a, --b; }

.anchor-a { anchor-name: --a; }
.anchor-b { anchor-name: --b; }
.anchor-a, .anchor-b {
background: skyblue;
height: 10px;
}

.anchored-a { position-anchor: --a; }
.anchored-b { position-anchor: --b; }
.anchored-a, .anchored-b {
position: absolute;
top: anchor(bottom);
left: anchor(left);
width: 5px;
height: 5px;
background: coral;
}

/* Containing block */
main {
position: relative;
width: 100px;
height: 100px;
border: 1px solid black;
}
</style>
<script>
function inflate(t, template_element) {
if (!template_element.hasAttribute('debug')) {
t.add_cleanup(() => main.replaceChildren());
}
main.append(template_element.content.cloneNode(true));
}
</script>

<main id=main>
</main>

<template id=test_scope_all_dynamic>
<div class=anchor-a></div>
<div class=anchor-a></div><!--A (after change)-->
<div id=dynamic>
<div class=anchor-a></div>
<div class=anchor-a></div><!--A (initially)-->
</div>
<div class=anchored-a></div>
</template>
<script>
test((t) => {
inflate(t, test_scope_all_dynamic);
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '40px');
dynamic.style.anchorScope = 'all';
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '20px');
dynamic.style.anchorScope = '';
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '40px');
}, 'anchor-scope:all appearing dynamically');
</script>

<template id=test_scope_named_dynamic>
<div class=anchor-a></div>
<div class=anchor-a></div><!--A (after change)-->
<div id=dynamic>
<div class=anchor-a></div>
<div class=anchor-a></div><!--A (initially)-->
</div>
<div class=anchored-a></div>
</template>
<script>
test((t) => {
inflate(t, test_scope_named_dynamic);
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '40px');
dynamic.style.anchorScope = '--a';
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '20px');
dynamic.style.anchorScope = '';
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '40px');
}, 'anchor-scope:--a appearing dynamically');
</script>

<template id=test_scope_named_unrelated_dynamic>
<div class=anchor-a></div>
<div class=anchor-a></div><!--A (after change)-->
<div id=dynamic>
<div class=anchor-a></div>
<div class=anchor-a></div><!--A (initially)-->
</div>
<div class=anchored-a></div>
</template>
<script>
test((t) => {
inflate(t, test_scope_named_unrelated_dynamic);
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '40px');
dynamic.style.anchorScope = '--b';
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '40px');
dynamic.style.anchorScope = '';
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '40px');
}, 'anchor-scope:--b appearing dynamically (--b never referenced)');
</script>

<template id=test_scope_a_dynamic>
<div class=anchor-b></div>
<div class=anchor-a></div><!--A (after change)-->
<div id=dynamic>
<div class=anchor-b></div><!--B-->
<div class=anchor-a></div><!--A (initially)-->
</div>
<div class=anchored-a></div>
<div class=anchored-b></div>
</template>
<script>
test((t) => {
inflate(t, test_scope_a_dynamic);
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '40px');
assert_equals(getComputedStyle(main.querySelector('.anchored-b')).top, '30px');
dynamic.style.anchorScope = '--a';
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '20px');
assert_equals(getComputedStyle(main.querySelector('.anchored-b')).top, '30px');
dynamic.style.anchorScope = '';
assert_equals(getComputedStyle(main.querySelector('.anchored-a')).top, '40px');
assert_equals(getComputedStyle(main.querySelector('.anchored-b')).top, '30px');
}, 'anchor-scope:--a appearing dynamically scopes only --a');
</script>
Loading

0 comments on commit 4593ea7

Please sign in to comment.