[css-anchor-position] Support basic position-visibility: anchors-visible
This patch implements basic support for position-visibility:
anchors-visible with a single anchor. There is active discussion about
whether to track the visibility of multiple anchors at:
https://github.com/w3c/csswg-drafts/issues/7758#issuecomment-2026137829.
The high-level approach in this patch is to use a post-layout
intersection observer for the tracked anchor. On visibility changes, the
anchored element's paint layer is updated via
`PaintLayer::SetInvisibleForPositionVisibility`.
Spec:
https://github.com/w3c/csswg-drafts/issues/7758#issuecomment-1965540529
Bug: 329703412
Change-Id: Icedcb43510a0c6a491cf463e7dc8a114ab7abf5f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5410539
Commit-Queue: Xianzhu Wang <wangxianzhu@chromium.org>
Auto-Submit: Philip Rogers <pdr@chromium.org>
Reviewed-by: Xianzhu Wang <wangxianzhu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1281911}
diff --git a/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in-ref.html b/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in-ref.html
new file mode 100644
index 0000000..10f74d4
--- /dev/null
+++ b/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+ #scroll-container {
+ overflow: hidden scroll;
+ width: 300px;
+ height: 100px;
+ }
+
+ #anchor {
+ width: 100px;
+ height: 100px;
+ background: orange;
+ margin-bottom: 100px;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ background: green;
+ }
+</style>
+
+<div id="scroll-container">
+ <div id="anchor">anchor</div>
+</div>
+<div id="target">target</div>
+
+<script>
+ const scroller = document.getElementById('scroll-container');
+ scroller.scrollTop = 0;
+</script>
diff --git a/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in.tentative.html b/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in.tentative.html
new file mode 100644
index 0000000..cea439c
--- /dev/null
+++ b/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-in.tentative.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<meta charset="utf-8">
+<meta name="assert" content="Scrolling an anchor in to view should cause a position-visibility: anchors-visible element to appear." />
+<title>CSS Anchor Positioning Test: position-visibility: anchors-visible</title>
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="match" href="position-visibility-anchors-visible-after-scroll-in-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/rendering-utils.js"></script>
+<style>
+ #scroll-container {
+ overflow: hidden scroll;
+ width: 300px;
+ height: 100px;
+ }
+
+ #anchor {
+ anchor-name: --a1;
+ width: 100px;
+ height: 100px;
+ background: orange;
+ }
+
+ #spacer {
+ height: 100px;
+ }
+
+ #target {
+ position-anchor: --a1;
+ position-visibility: anchors-visible;
+ inset-area: block-end;
+ width: 100px;
+ height: 100px;
+ background: green;
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+</style>
+
+<div id="scroll-container">
+ <div id="anchor">anchor</div>
+ <div id="spacer"></div>
+ <div id="target">target</div>
+</div>
+
+<script>
+ // #target should be initially visible because it is anchored to #anchor,
+ // which is visible.
+ waitForAtLeastOneFrame().then(() => {
+ // Scroll #anchor out of view.
+ const scroller = document.getElementById('scroll-container');
+ scroller.scrollTop = 100;
+ // #target should now be invisible.
+
+ waitForAtLeastOneFrame().then(() => {
+ // Scroll #anchor back into view.
+ scroller.scrollTop = 0;
+
+ // #target should now be visible again.
+ takeScreenshot();
+ });
+ });
+</script>
diff --git a/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-out-ref.html b/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-out-ref.html
new file mode 100644
index 0000000..bd4fe1f
--- /dev/null
+++ b/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-out-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+ #scroll-container {
+ overflow: hidden scroll;
+ width: 300px;
+ height: 100px;
+ }
+
+ #spacer {
+ height: 200px;
+ }
+</style>
+
+<div id="scroll-container">
+ <div id="spacer"><div>
+</div>
+
+<script>
+ const scroller = document.getElementById('scroll-container');
+ scroller.scrollTop = 100;
+</script>
diff --git a/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-out.tentative.html b/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-out.tentative.html
new file mode 100644
index 0000000..b2e3643
--- /dev/null
+++ b/css/css-anchor-position/position-visibility-anchors-visible-after-scroll-out.tentative.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<meta charset="utf-8">
+<meta name="assert" content="Scrolling an anchor out of view should cause a position-visibility: anchors-visible element to disappear." />
+<title>CSS Anchor Positioning Test: position-visibility: anchors-visible</title>
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="match" href="position-visibility-anchors-visible-after-scroll-out-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/rendering-utils.js"></script>
+<style>
+ #scroll-container {
+ overflow: hidden scroll;
+ width: 300px;
+ height: 100px;
+ }
+
+ #anchor {
+ anchor-name: --a1;
+ width: 100px;
+ height: 100px;
+ background: orange;
+ }
+
+ #spacer {
+ height: 100px;
+ }
+
+ #target {
+ position-anchor: --a1;
+ position-visibility: anchors-visible;
+ inset-area: bottom;
+ width: 100px;
+ height: 100px;
+ background: red;
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+</style>
+
+<div id="scroll-container">
+ <div id="anchor">anchor</div>
+ <div id="spacer"></div>
+ <div id="target">target</div>
+</div>
+
+<script>
+ // #target should be initially visible because it is anchored to #anchor,
+ // which is visible.
+
+ waitForAtLeastOneFrame().then(() => {
+ // Scroll #anchor so that it is out of view.
+ const scroller = document.getElementById('scroll-container');
+ scroller.scrollTop = 100;
+
+ // #target should now be invisible.
+ takeScreenshot();
+ });
+</script>
diff --git a/css/css-anchor-position/position-visibility-anchors-visible-change-anchor-ref.html b/css/css-anchor-position/position-visibility-anchors-visible-change-anchor-ref.html
new file mode 100644
index 0000000..cc35e4c
--- /dev/null
+++ b/css/css-anchor-position/position-visibility-anchors-visible-change-anchor-ref.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+ #scroll-container {
+ overflow: hidden scroll;
+ width: 300px;
+ height: 100px;
+ }
+ #anchor {
+ width: 100px;
+ height: 200px;
+ background: orange;
+ }
+ #target {
+ width: 100px;
+ height: 100px;
+ background: green;
+ }
+</style>
+
+<div id="scroll-container">
+ <div id="anchor"></div>
+</div>
+<div id="target">target</div>
+
+<script>
+ const scroller = document.getElementById('scroll-container');
+ scroller.scrollTop = 100;
+</script>
diff --git a/css/css-anchor-position/position-visibility-anchors-visible-change-anchor.tentative.html b/css/css-anchor-position/position-visibility-anchors-visible-change-anchor.tentative.html
new file mode 100644
index 0000000..f8b1cc6
--- /dev/null
+++ b/css/css-anchor-position/position-visibility-anchors-visible-change-anchor.tentative.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<meta charset="utf-8">
+<meta name="assert" content="Position-visibility should not be affected by the visibility of a previous anchor." />
+<title>CSS Anchor Positioning Test: position-visibility: anchors-visible</title>
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="match" href="position-visibility-anchors-visible-change-anchor-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/rendering-utils.js"></script>
+<style>
+ #scroll-container {
+ overflow: hidden scroll;
+ width: 300px;
+ height: 100px;
+ }
+
+ .anchor {
+ width: 100px;
+ height: 100px;
+ background: orange;
+ display: inline-block;
+ }
+
+ #anchor1 {
+ height: 200px;
+ anchor-name: --a1;
+ }
+
+ #anchor2 {
+ anchor-name: --a2;
+ }
+
+ #target {
+ position-anchor: --a2;
+ position-visibility: anchors-visible;
+ inset-area: bottom;
+ width: 100px;
+ height: 100px;
+ background: green;
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+</style>
+
+<div id="scroll-container">
+ <div id="anchor1" class="anchor">anchor1</div>
+ <div id="anchor2" class="anchor">anchor2</div>
+ <div id="target">target</div>
+</div>
+
+<script>
+ // #target should be initially visible because it is anchored to #anchor2,
+ // which is visible.
+ waitForAtLeastOneFrame().then(() => {
+ // Change #target to be anchored to #anchor1.
+ target.style.positionAnchor = '--a1';
+ // #target should be still be visible because #anchor1 is also visible.
+ waitForAtLeastOneFrame().then(() => {
+ // Scroll #anchor2 out of view, with #anchor1 still in view.
+ const scroller = document.getElementById('scroll-container');
+ scroller.scrollTop = 100;
+ // #target should still be visible because it is anchored to #anchor1,
+ // which is still visible.
+ takeScreenshot();
+ });
+ });
+</script>
diff --git a/css/css-anchor-position/position-visibility-anchors-visible-non-intervening-container-ref.html b/css/css-anchor-position/position-visibility-anchors-visible-non-intervening-container-ref.html
new file mode 100644
index 0000000..3b6532e
--- /dev/null
+++ b/css/css-anchor-position/position-visibility-anchors-visible-non-intervening-container-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+ #target {
+ width: 100px;
+ height: 100px;
+ background: green;
+ }
+</style>
+<div id="target">target</div>
diff --git a/css/css-anchor-position/position-visibility-anchors-visible-non-intervening-container.tentative.html b/css/css-anchor-position/position-visibility-anchors-visible-non-intervening-container.tentative.html
new file mode 100644
index 0000000..7b84976
--- /dev/null
+++ b/css/css-anchor-position/position-visibility-anchors-visible-non-intervening-container.tentative.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="assert" content="position-visibility: anchors-visible should consider the visibility of the anchor relative the containing scroller, ignoring visibility in other scrollers." />
+<title>CSS Anchor Positioning Test: position-visibility: anchors-visible</title>
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="match" href="position-visibility-anchors-visible-non-intervening-container-ref.html">
+<style>
+ #non-intervening-scroll-container {
+ overflow: hidden;
+ width: 200px;
+ height: 200px;
+ position: relative;
+ }
+
+ #position-container {
+ position: relative;
+ }
+
+ #scroll-container {
+ overflow: hidden scroll;
+ width: 400px;
+ height: 100px;
+ }
+
+ #anchor {
+ anchor-name: --a1;
+ width: 100px;
+ height: 100px;
+ background: orange;
+ }
+
+ #spacer {
+ height: 100px;
+ }
+
+ #target {
+ position-anchor: --a1;
+ position-visibility: anchors-visible;
+ inset-area: right;
+ width: 100px;
+ height: 100px;
+ background: green;
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+</style>
+
+<div id="non-intervening-scroll-container">
+ <div id="position-container">
+ <div id="scroll-container">
+ <!-- The anchor is not visible to the screen, but it is visible in the -->
+ <!-- containing block of anchor1 and target1, so the target should not -->
+ <!-- be hidden due to position-visibility: anchors-visible. -->
+ <div id="anchor">anchor</div>
+ <div id="spacer"></div>
+ <div id="target">target</div>
+ </div>
+ </div>
+</div>
+
+<script>
+ const non_intervening_scroller = document.getElementById('non-intervening-scroll-container');
+ non_intervening_scroller.scrollLeft = 100;
+</script>
diff --git a/css/css-anchor-position/position-visibility-anchors-visible-ref.html b/css/css-anchor-position/position-visibility-anchors-visible-ref.html
index 6f8d3cb..1779817 100644
--- a/css/css-anchor-position/position-visibility-anchors-visible-ref.html
+++ b/css/css-anchor-position/position-visibility-anchors-visible-ref.html
@@ -3,30 +3,17 @@
<style>
#scroll-container {
overflow: hidden scroll;
- width: 400px;
+ width: 300px;
height: 100px;
}
- #contents-container {
- height: 400px;
- }
-
- .anchor {
- width: 100px;
- height: 100px;
- background: orange;
- display: inline-block;
+ #spacer {
+ height: 200px;
}
</style>
<div id="scroll-container">
- <div id="contents-container">
- <div class="anchor">anchor1</div>
-
- <div class="anchor" style="height: 150px;">anchor2</div>
-
- <div class="anchor" style="height: 150px;">anchor3</div>
- </div>
+ <div id="spacer"></div>
</div>
<script>
diff --git a/css/css-anchor-position/position-visibility-anchors-visible-with-position.tentative.html b/css/css-anchor-position/position-visibility-anchors-visible-with-position.tentative.html
new file mode 100644
index 0000000..82eed0b
--- /dev/null
+++ b/css/css-anchor-position/position-visibility-anchors-visible-with-position.tentative.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="assert" content="Position-visibility: anchors-visible should hide an element with an out-of-view anchor and a relpos scroller." />
+<title>CSS Anchor Positioning Test: position-visibility: anchors-visible</title>
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="match" href="position-visibility-anchors-visible-ref.html">
+<style>
+ #scroll-container {
+ overflow: hidden scroll;
+ width: 300px;
+ height: 100px;
+ /* Same as position-visibility-anchors-visible.html, but with relpos here */
+ position: relative;
+ }
+
+ #anchor {
+ anchor-name: --a1;
+ width: 100px;
+ height: 100px;
+ background: orange;
+ }
+
+ #spacer {
+ height: 100px;
+ }
+
+ #target {
+ position-anchor: --a1;
+ position-visibility: anchors-visible;
+ inset-area: bottom right;
+ width: 100px;
+ height: 100px;
+ background: red;
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+</style>
+
+<div id="scroll-container">
+ <div id="anchor">anchor</div>
+ <div id="spacer"></div>
+ <div id="target">target</div>
+</div>
+
+<script>
+ const scroller = document.getElementById('scroll-container');
+ scroller.scrollTop = 100;
+ // #target should not be visible because #anchor is scrolled out of view.
+</script>
diff --git a/css/css-anchor-position/position-visibility-anchors-visible.tentative.html b/css/css-anchor-position/position-visibility-anchors-visible.tentative.html
index 6605bbc..85b8d89 100644
--- a/css/css-anchor-position/position-visibility-anchors-visible.tentative.html
+++ b/css/css-anchor-position/position-visibility-anchors-visible.tentative.html
@@ -1,55 +1,48 @@
<!DOCTYPE html>
<meta charset="utf-8">
+<meta name="assert" content="Position-visibility: anchors-visible should hide an element with an out-of-view anchor." />
<title>CSS Anchor Positioning Test: position-visibility: anchors-visible</title>
<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
<link rel="match" href="position-visibility-anchors-visible-ref.html">
<style>
#scroll-container {
overflow: hidden scroll;
- width: 400px;
+ width: 300px;
height: 100px;
}
- #contents-container {
- height: 400px;
- }
-
- .anchor {
+ #anchor {
+ anchor-name: --a1;
width: 100px;
height: 100px;
background: orange;
- display: inline-block;
}
- .target {
- position: absolute;
+ #spacer {
+ height: 100px;
+ }
+
+ #target {
+ position-anchor: --a1;
position-visibility: anchors-visible;
- inset-area: block-end;
+ inset-area: bottom right;
width: 100px;
height: 100px;
background: red;
+ position: absolute;
top: 0;
left: 0;
}
</style>
<div id="scroll-container">
- <div id="contents-container">
- <!-- #target1 should not be visible because anchor is scrolled to not be visible. -->
- <div class="anchor" style="anchor-name: --a1;">anchor1</div>
- <div id="target1" class="target" style="position-anchor: --a1;">target1</div>
-
- <!-- #target2 should not be visible because referenced name in anchor() is not visible. -->
- <div class="anchor" style="anchor-name: --a2; height: 150px;">anchor2</div>
- <div id="target2" class="target" style="position-anchor: --a2; top: anchor(--a1 bottom);">target2</div>
-
- <!-- #target3 should not be visible because referenced name in anchor-size() is not visible. -->
- <div class="anchor" style="anchor-name: --a3; height: 150px;">anchor3</div>
- <div id="target3" class="target" style="position-anchor: --a3; min-width: anchor-width(--a1 width);">target3</div>
- </div>
+ <div id="anchor">anchor</div>
+ <div id="spacer"></div>
+ <div id="target">target</div>
</div>
<script>
const scroller = document.getElementById('scroll-container');
scroller.scrollTop = 100;
+ // #target should not be visible because #anchor is scrolled out of view.
</script>
diff --git a/css/css-anchor-position/position-visibility-remove-anchors-visible-ref.html b/css/css-anchor-position/position-visibility-remove-anchors-visible-ref.html
new file mode 100644
index 0000000..135763b
--- /dev/null
+++ b/css/css-anchor-position/position-visibility-remove-anchors-visible-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+ #scroll-container {
+ overflow: hidden scroll;
+ width: 300px;
+ height: 100px;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ margin-top: 100px;
+ background: green;
+ }
+</style>
+
+<div id="scroll-container">
+ <div id="target">target</div>
+</div>
+
+<script>
+ const scroller = document.getElementById('scroll-container');
+ scroller.scrollTop = 100;
+</script>
diff --git a/css/css-anchor-position/position-visibility-remove-anchors-visible.tentative.html b/css/css-anchor-position/position-visibility-remove-anchors-visible.tentative.html
new file mode 100644
index 0000000..c6649e5
--- /dev/null
+++ b/css/css-anchor-position/position-visibility-remove-anchors-visible.tentative.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<meta charset="utf-8">
+<meta name="assert" content="Removing position-visibility: anchors-visible from an invisible anchored element should cause it to become visible." />
+<title>CSS Anchor Positioning Test: position-visibility: anchors-visible</title>
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7758">
+<link rel="match" href="position-visibility-remove-anchors-visible-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/rendering-utils.js"></script>
+<style>
+ #scroll-container {
+ overflow: hidden scroll;
+ width: 300px;
+ height: 100px;
+ }
+
+ #anchor {
+ anchor-name: --a1;
+ width: 100px;
+ height: 100px;
+ background: orange;
+ }
+
+ #spacer {
+ height: 100px;
+ }
+
+ #target {
+ position-anchor: --a1;
+ position-visibility: anchors-visible;
+ inset-area: bottom;
+ width: 100px;
+ height: 100px;
+ background: green;
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+</style>
+
+<div id="scroll-container">
+ <div id="anchor">anchor</div>
+ <div id="spacer"></div>
+ <div id="target">target</div>
+</div>
+
+<script>
+ // #target should be initially visible because it is anchored to #anchor,
+ // which is visible.
+
+ // Scroll #anchor so that it is no longer visible.
+ const scroller = document.getElementById('scroll-container');
+ scroller.scrollTop = 100;
+
+ waitForAtLeastOneFrame().then(() => {
+ // Remove position-visibility: anchors-visible. #target should become
+ // visible again.
+ target.style.positionVisibility = 'initial';
+ takeScreenshot();
+ });
+</script>