-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[anchor] Support anchor-scope property
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
1 parent
699a024
commit 4593ea7
Showing
6 changed files
with
512 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
Oops, something went wrong.