diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /testing/web-platform/tests/css/css-scroll-anchoring | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/css/css-scroll-anchoring')
68 files changed, 2898 insertions, 0 deletions
diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/META.yml b/testing/web-platform/tests/css/css-scroll-anchoring/META.yml new file mode 100644 index 0000000000..3d24f5cc9f --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/META.yml @@ -0,0 +1,3 @@ +spec: https://drafts.csswg.org/css-scroll-anchoring/ +suggested_reviewers: + - tabatkins diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/README.md b/testing/web-platform/tests/css/css-scroll-anchoring/README.md new file mode 100644 index 0000000000..78f1387bdf --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/README.md @@ -0,0 +1,8 @@ +## Scroll Anchoring Test Suite + +Scroll anchoring adjusts the scroll position to prevent visible jumps (or +"reflows") when content changes above the viewport. + +* [explainer](https://github.com/WICG/ScrollAnchoring/blob/master/explainer.md) +* [spec](https://drafts.csswg.org/css-scroll-anchoring/) +* [file bug / view open issues](https://github.com/w3c/csswg-drafts/issues) diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/abspos-containing-block-outside-scroller.html b/testing/web-platform/tests/css/css-scroll-anchoring/abspos-containing-block-outside-scroller.html new file mode 100644 index 0000000000..76a4952383 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/abspos-containing-block-outside-scroller.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +body { margin: 0; } +#scroller { overflow: scroll; width: 500px; height: 400px; } +#space { height: 1000px; } +#abs { + position: absolute; background-color: red; + width: 100px; height: 100px; + left: 25px; top: 25px; +} +#rel { + position: relative; background-color: green; + left: 50px; top: 100px; width: 100px; height: 75px; +} + +</style> +<div id="scroller"> + <div id="space"> + <div id="abs"></div> + <div id="before"></div> + <div id="rel"></div> + </div> +</div> +<script> + +// Tests that anchor node selection skips an absolute-positioned descendant of +// the scroller if and only if its containing block is outside the scroller. + +test(() => { + var scroller = document.querySelector("#scroller"); + var abs = document.querySelector("#abs"); + var before = document.querySelector("#before"); + var rel = document.querySelector("#rel"); + + // We should not anchor to #abs, because it does not move with the scroller. + scroller.scrollTop = 25; + before.style.height = "25px"; + assert_equals(scroller.scrollTop, 50); + + // Reset, make #scroller a containing block. + before.style.height = "0"; + scroller.scrollTop = 0; + scroller.style.position = "relative"; + + // This time we should anchor to #abs. + scroller.scrollTop = 25; + before.style.height = "25px"; + assert_equals(scroller.scrollTop, 25); + +}, "Abs-pos descendant with containing block outside the scroller."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/abspos-contributes-to-static-parent-bounds.html b/testing/web-platform/tests/css/css-scroll-anchoring/abspos-contributes-to-static-parent-bounds.html new file mode 100644 index 0000000000..5d8ff9a911 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/abspos-contributes-to-static-parent-bounds.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +body, html, #static { height: 0; } +#abs { + position: absolute; + left: 50px; + top: 50px; + height: 1200px; + padding: 50px; + border: 5px solid gray; +} +#anchor { + background-color: #afa; + width: 100px; + height: 100px; +} + +</style> +<div id="static"> + <div id="abs"> + <div id="changer"></div> + <div id="anchor"></div> + </div> +</div> +<script> + +// Tests that the "bounds" of an element, for the purpose of visibility in the +// anchor node selection algorithm, include any space occupied by the element's +// positioned descendants. + +test(() => { + document.scrollingElement.scrollTop = 120; + document.querySelector("#changer").style.height = "100px"; + assert_equals(document.scrollingElement.scrollTop, 220); +}, "Abs-pos with zero-height static parent."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/abspos-in-multicol-001.html b/testing/web-platform/tests/css/css-scroll-anchoring/abspos-in-multicol-001.html new file mode 100644 index 0000000000..ee11148747 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/abspos-in-multicol-001.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com" /> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/#candidate-examination"> +<meta name="assert" content="The candidate examination algorithm iterates abspos descendants of the containing block. Being inside a fragmentainer shouldn't break that."> +<style> +html { + column-count: 1; /* Fragmentainer */ +} + +body { + position: relative; /* Containing block */ +} + +div { + position: absolute; /* Abspos */ + font-size: 100px; + width: 200px; + height: 4000px; + line-height: 100px; +} +</style> +<div>abc <b id=b>def</b> ghi</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +// Tests anchoring to a text node that is moved by preceding text, +// everything in an absolutely positioned element whose containing block +// is inside a multi-column fragmentainer. + +test(() => { + var b = document.querySelector("#b"); + var preText = b.previousSibling; + document.scrollingElement.scrollTop = 150; + preText.nodeValue = "abcd efg "; + assert_equals(document.scrollingElement.scrollTop, 250); +}, "Anchoring with text wrapping changes."); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/abspos-in-multicol-002.html b/testing/web-platform/tests/css/css-scroll-anchoring/abspos-in-multicol-002.html new file mode 100644 index 0000000000..6c254d437a --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/abspos-in-multicol-002.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com" /> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/#candidate-examination"> +<meta name="assert" content="The candidate examination algorithm iterates abspos descendants of the containing block. Being inside a fully clipped element and inside a fragmentainer shouldn't break that."> +<style> +html { + column-count: 1; /* Fragmentainer */ +} + +body { + position: relative; /* Containing block */ + height: 4000px; +} + +main { + height: 0px; /* Fully clipped */ +} + +div { + position: absolute; /* Abspos */ + font-size: 100px; + width: 200px; + height: 100%; + line-height: 100px; +} +</style> +<main><div>abc <b id=b>def</b> ghi</div></main> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +// Tests anchoring to a text node that is moved by preceding text, +// where the text is inside an absolutely positioned element, +// there is a fully clippped element between the abspos and its contaning block, +// and the containing block is inside a multi-column fragmentainer. +test(() => { + var b = document.querySelector("#b"); + var preText = b.previousSibling; + document.scrollingElement.scrollTop = 150; + preText.nodeValue = "abcd efg "; + assert_equals(document.scrollingElement.scrollTop, 250); +}, "Anchoring with text wrapping changes."); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/abspos-in-multicol-003.html b/testing/web-platform/tests/css/css-scroll-anchoring/abspos-in-multicol-003.html new file mode 100644 index 0000000000..01caa895da --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/abspos-in-multicol-003.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com" /> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org" /> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/#candidate-examination"> +<meta name="assert" content="The candidate examination algorithm iterates abspos descendants of the containing block. Being inside a fragmentainer shouldn't break that."> +<style> +html { + column-count: 1; /* Fragmentainer */ +} + +main { + position: relative; /* Containing block */ +} + +div { + position: absolute; /* Abspos */ + font-size: 100px; + width: 200px; + height: 4000px; + line-height: 100px; +} +</style> +<main><div>abc <b id=b>def</b> ghi</div></main> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +// Tests anchoring to a text node that is moved by preceding text, +// everything in an absolutely positioned element whose containing block +// is inside a multi-column fragmentainer. + +test(() => { + var b = document.querySelector("#b"); + var preText = b.previousSibling; + document.scrollingElement.scrollTop = 150; + preText.nodeValue = "abcd efg "; + assert_equals(document.scrollingElement.scrollTop, 250); +}, "Anchoring with text wrapping changes."); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/adjustments-in-scroll-event-handler.tentative.html b/testing/web-platform/tests/css/css-scroll-anchoring/adjustments-in-scroll-event-handler.tentative.html new file mode 100644 index 0000000000..84fd79cbcb --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/adjustments-in-scroll-event-handler.tentative.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<meta name="viewport" content="width=device-width"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="author" title="Mozilla" href="https://mozilla.org"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/"> +<style> + body { margin: 0 } + .content { + height: 200px; + background: lightblue; + } + .spacer { + height: 300vh; + } +</style> +<div class="content"></div> +<div class="content" style="background: green"></div> +<div class="spacer"></div> +<script> +const anchor = document.querySelectorAll(".content")[1]; + +const t = async_test("Scroll adjustments happen even if it's triggered from scroll event listeners"); +window.addEventListener("scroll", t.step_func(function() { + // Forcibly flush layout, this will flush the pending the node insertion. + let scrollPosition = window.scrollY; + + requestAnimationFrame(t.step_func(function() { + requestAnimationFrame(t.step_func(function() { + assert_equals(window.scrollY, 400); + t.done(); + })); + })); +}), { once: true }); + +window.onload = t.step_func(function() { + requestAnimationFrame(t.step_func(function() { + // Scroll to the anchor node in a requestAnimationFrame callback so that + // it queues a scroll event which will be fired in the next event loop. + anchor.scrollIntoView({ behavior: "instant" }); + + // Then in a setTimeout callback insert an element just right before the + // anchor node, it will run before firing the scroll event. + t.step_timeout(function() { + const content = document.createElement("div"); + content.classList.add("content"); + content.style.background = "red"; + anchor.before(content); + }, 0); + })); +}); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/ancestor-change-heuristic.html b/testing/web-platform/tests/css/css-scroll-anchoring/ancestor-change-heuristic.html new file mode 100644 index 0000000000..21adfbb6b7 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/ancestor-change-heuristic.html @@ -0,0 +1,81 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +#space { height: 4000px; } +#ancestor { position: relative; } +#before, #anchor { height: 100px; } +#anchor { background-color: green; } + +.layout1 { padding-top: 20px; } +.layout2 { margin-right: 20px; } +.layout3 { max-width: 100px; } +.layout4 { min-height: 400px; } +.layout5 { position: static !important; } +.layout6 { left: 20px; } +.layout7 { transform: matrix(1, 0, 0, 1, 50, 50); } +.nonLayout1 { color: red; } +.nonLayout2 { font-size: 200%; } +.nonLayout3 { box-shadow: 10px 10px 10px gray; } +.nonLayout4 { opacity: 0.5; } +.nonLayout5 { z-index: -1; } + +.scroller { + overflow: scroll; + width: 600px; + height: 600px; +} + +</style> +<div id="maybeScroller"> + <div id="space"> + <div id="ancestor"> + <div id="before"></div> + <div id="anchor"></div> + </div> + </div> +</div> +<script> + +// Tests that scroll anchoring is suppressed when one of the "layout-affecting" +// properties is modified on an ancestor of the anchor node. + +var scroller; +var ancestor = document.querySelector("#ancestor"); +var before = document.querySelector("#before"); + +function runCase(classToApply, expectSuppression) { + // Reset. + scroller.scrollTop = 0; + ancestor.className = ""; + before.style.height = ""; + scroller.scrollTop = 150; + + ancestor.className = classToApply; + before.style.height = "150px"; + + var expectedTop = expectSuppression ? 150 : 200; + assert_equals(scroller.scrollTop, expectedTop); +} + +function runAll() { + for (var i = 1; i <= 7; i++) + runCase("layout" + i, true); + for (var i = 1; i <= 5; i++) + runCase("nonLayout" + i, false); +} + +test(() => { + document.querySelector("#maybeScroller").className = ""; + scroller = document.scrollingElement; + runAll(); +}, "Ancestor changes in document scroller."); + +test(() => { + scroller = document.querySelector("#maybeScroller"); + scroller.className = "scroller"; + runAll(); +}, "Ancestor changes in scrollable <div>."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/anchor-inside-iframe.html b/testing/web-platform/tests/css/css-scroll-anchoring/anchor-inside-iframe.html new file mode 100644 index 0000000000..ea1ce4b13d --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/anchor-inside-iframe.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring-1/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe width="700" height="500" srcdoc=" + <!DOCTYPE html> + <style> body { height: 1000px } div { height: 100px } </style> + <div id='block1'>abc</div> + <div id='block2'>def</div> +"></iframe> +<script> + async_test((t) => { + var iframeWindow = document.querySelector("iframe").contentWindow; + iframeWindow.addEventListener("load", () => { + var block1 = iframeWindow.document.querySelector("#block1"); + iframeWindow.scrollTo(0, 150); + + requestAnimationFrame(() => { + step_timeout(() => { + block1.style.height = "200px"; + assert_equals(iframeWindow.scrollY, 250); + t.done(); + }, 0); + }); + }); + }, "Scroll anchoring in an iframe."); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/anchor-updates-after-explicit-scroll.html b/testing/web-platform/tests/css/css-scroll-anchoring/anchor-updates-after-explicit-scroll.html new file mode 100644 index 0000000000..7f0c54d1dc --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/anchor-updates-after-explicit-scroll.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +#scroller { + height: 200px; + width: 200px; + overflow: scroll; +} +#a1, #space1, #a2, #space2 { + height: 200px; +} +#a1, #a2 { + background-color: #8f8; +} + +</style> +<div id="scroller"> + <div id="space0"></div> + <div id="a1"></div> + <div id="space1"></div> + <div id="a2"></div> + <div id="space2"></div> +</div> +<script> + +// Tests that the anchor node is recomputed after an explicit change to the +// scroll position. + +test(() => { + var scroller = document.querySelector("#scroller"); + scroller.scrollTop = 500; + + // We should now be anchored to #a2. + document.querySelector("#space1").style.height = "300px"; + assert_equals(scroller.scrollTop, 600); + + scroller.scrollTop = 100; + + // We should now be anchored to #a1. Make sure there is no adjustment from + // moving #a2. + document.querySelector("#space1").style.height = "400px"; + assert_equals(scroller.scrollTop, 100); + + // Moving #a1 should produce an adjustment. + document.querySelector("#space0").style.height = "100px"; + assert_equals(scroller.scrollTop, 200); +}, "Anchor node recomputed after an explicit scroll occurs."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/anchoring-with-bounds-clamping-div.html b/testing/web-platform/tests/css/css-scroll-anchoring/anchoring-with-bounds-clamping-div.html new file mode 100644 index 0000000000..3de725e683 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/anchoring-with-bounds-clamping-div.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +#scroller { + height: 500px; + width: 200px; + overflow: scroll; +} +#changer { height: 1500px; } +#anchor { + width: 150px; + height: 1000px; + overflow: scroll; +} + +</style> +<div id="scroller"> + <div id="changer"></div> + <div id="anchor"></div> +</div> +<script> + +// Test that scroll anchoring interacts correctly with scroll bounds clamping +// inside a scrollable <div> element. +// +// There should be no visible jump even if the content shrinks such that the +// new max scroll position is less than the previous scroll position. + +test(() => { + var scroller = document.querySelector("#scroller"); + scroller.scrollTop = 1600; + document.querySelector("#changer").style.height = "0"; + assert_equals(scroller.scrollTop, 100); +}, "Anchoring combined with scroll bounds clamping in a <div>."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/anchoring-with-bounds-clamping.html b/testing/web-platform/tests/css/css-scroll-anchoring/anchoring-with-bounds-clamping.html new file mode 100644 index 0000000000..e9d06579d5 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/anchoring-with-bounds-clamping.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +#changer { height: 1500px; } +#anchor { + width: 150px; + height: 4000px; + background-color: pink; +} + +</style> +<div id="changer"></div> +<div id="anchor"></div> +<script> + +// Test that scroll anchoring interacts correctly with scroll bounds clamping: +// There should be no visible jump even if the content shrinks such that the +// new max scroll position is less than the previous scroll position. + +test(() => { + document.scrollingElement.scrollTop = 1600; + document.querySelector("#changer").style.height = "0"; + assert_equals(document.scrollingElement.scrollTop, 100); +}, "Anchoring combined with scroll bounds clamping in the document."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/anonymous-block-box.html b/testing/web-platform/tests/css/css-scroll-anchoring/anonymous-block-box.html new file mode 100644 index 0000000000..97542e2613 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/anonymous-block-box.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +body { height: 2000px; margin: 0 10px; } +#before, #after { height: 100px; } +#before { margin-bottom: 100px; } +#container { line-height: 100px; vertical-align: top; } + +</style> +<div id="container"> + <div id="before">before</div> + <span id="inline">inline</span> + <div id="after">after</div> +</div> +<script> + +// Tests anchoring inside an anonymous block box. The anchor selection algorithm +// should descend into the children of the anonymous box even though it is fully +// contained in the viewport. + +test(() => { + document.scrollingElement.scrollTop = 150; + + var span = document.querySelector("#inline"); + var newSpan = document.createElement("span"); + newSpan.innerHTML = "inserted<br>"; + span.parentNode.insertBefore(newSpan, span); + + assert_equals(document.scrollingElement.scrollTop, 250); +}, "Anchor selection descent into anonymous block boxes."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/basic.html b/testing/web-platform/tests/css/css-scroll-anchoring/basic.html new file mode 100644 index 0000000000..99625ba7da --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/basic.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +body { height: 4000px; } +div { height: 100px; } + +</style> +<div id="block1">abc</div> +<div id="block2">def</div> +<script> + +// Tests that growing an element above the viewport produces a scroll +// anchoring adjustment equal to the amount by which it grew. + +test(() => { + document.scrollingElement.scrollTop = 150; + document.querySelector("#block1").style.height = "200px"; + assert_equals(document.scrollingElement.scrollTop, 250); +}, "Minimal scroll anchoring example."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/clamp-negative-overflow.html b/testing/web-platform/tests/css/css-scroll-anchoring/clamp-negative-overflow.html new file mode 100644 index 0000000000..4f0b5fe9a2 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/clamp-negative-overflow.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<html> +<meta charset="utf-8"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring-1/"> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style type="text/css"> + #scroller { + overflow: scroll; + width: 500px; + height: 500px; + } + #anchor { + position: relative; + width: 100px; + height: 100px; + margin-top: 100px; + margin-bottom: 1000px; + background-color: blue; + } + #positioned { + position: absolute; + width: 10px; + height: 10px; + top: -200px; + background-color: yellow; + } + </style> +</head> +<body> + <div id="scroller"> + <div id="anchor"> + <div id="positioned"> + </div> + </div> + </div> + <script type="text/javascript"> + test(() => { + let scroller = document.querySelector('#scroller'); + let positioned = document.querySelector('#positioned'); + + // Scroll down to select #anchor as an anchor node + scroller.scrollTop = 20; + + // Move #positioned downwards, which will move the unclamped scrollable + // overflow rect of #anchor downards as well + positioned.style.top = '-180px'; + // To trigger the bug that this regression tests in Gecko, we need + // to not take Gecko's relative positioning fast path. To do + // this, change the 'left' of #positioned from 'auto' to '0px'. + positioned.style.left = '0px'; + + // The implementation should clamp the scrollable overflow rect + // before the start-edge of the anchor node, and not apply an + // adjustment + assert_equals(scroller.scrollTop, 20); + }, 'scrollable overflow before the start-edge of the anchor node should be clamped'); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/clipped-scrollers-skipped.html b/testing/web-platform/tests/css/css-scroll-anchoring/clipped-scrollers-skipped.html new file mode 100644 index 0000000000..594cd604f4 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/clipped-scrollers-skipped.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +body { height: 2000px; } +#scroller { overflow: scroll; width: 500px; height: 300px; } +.anchor { + position: relative; height: 100px; width: 150px; + background-color: #afa; border: 1px solid gray; +} +#forceScrolling { height: 500px; background-color: #fcc; } + +</style> +<div id="scroller"> + <div id="innerChanger"></div> + <div id="innerAnchor" class="anchor"></div> + <div id="forceScrolling"></div> +</div> +<div id="outerChanger"></div> +<div id="outerAnchor" class="anchor"></div> +<script> + +// Test that we ignore the clipped content when computing visibility otherwise +// we may end up with an anchor that we think is in the viewport but is not. + +test(() => { + document.querySelector("#scroller").scrollTop = 100; + document.scrollingElement.scrollTop = 350; + + document.querySelector("#innerChanger").style.height = "200px"; + document.querySelector("#outerChanger").style.height = "150px"; + + assert_equals(document.querySelector("#scroller").scrollTop, 300); + assert_equals(document.scrollingElement.scrollTop, 500); +}, "Anchor selection with nested scrollers."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/contain-paint-offscreen-container.html b/testing/web-platform/tests/css/css-scroll-anchoring/contain-paint-offscreen-container.html new file mode 100644 index 0000000000..58f41cc748 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/contain-paint-offscreen-container.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<meta charset="utf8"> +<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/"> +<meta name="assert" content="ensures that scroll anchoring does not recurse into contained offscreen elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<style> +body { height: 10000px; } +#container { contain: paint; } +#overflow { + position: relative; + top: 300px; + height: 10px; +} +#anchor { + width: 10px; + height: 50px; +} +</style> + +<div style="height: 800px"></div> +<div id="container" style="height: 40px"> + <div id=overflow></div> +</div> +<div id="changer" style="height: 150px"></div> +<div id=anchor></div> + +<script> +test(() => { + // Ensure #anchor is the only thing on screen. + // Note that #overflow would be on screen if container + // did not have layout and paint containment. + document.scrollingElement.scrollTop = 1000; + + // Ensure anchor doesn't move if #changer shrinks. + const offset = anchor.getBoundingClientRect().y; + document.querySelector("#changer").style.height = "50px"; + assert_equals(anchor.getBoundingClientRect().y, offset); +}, "Contain: style paint container offscreen."); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/descend-into-container-with-float.html b/testing/web-platform/tests/css/css-scroll-anchoring/descend-into-container-with-float.html new file mode 100644 index 0000000000..ff39608ff0 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/descend-into-container-with-float.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +body { height: 4000px; } +#outer { width: 300px; } +#outer:after { content: " "; clear:both; display: table; } +#float { + float: left; background-color: #ccc; + height: 500px; width: 100%; +} +#inner { height: 100px; background-color: green; } + +</style> +<div id="outer"> + <div id="zeroheight"> + <div id="float"> + <div id="changer"></div> + <div id="inner"></div> + </div> + </div> +</div> +<div>after</div> +<script> + +// Tests that we descend into zero-height containers that have floating content. + +test(() => { + document.scrollingElement.scrollTop = 50; + assert_equals(document.querySelector("#zeroheight").offsetHeight, 0); + document.querySelector("#changer").style.height = "50px"; + assert_equals(document.scrollingElement.scrollTop, 100); +}, "Zero-height container with float."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/descend-into-container-with-overflow.html b/testing/web-platform/tests/css/css-scroll-anchoring/descend-into-container-with-overflow.html new file mode 100644 index 0000000000..654f34a051 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/descend-into-container-with-overflow.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +body { height: 4000px; } +#outer { width: 300px; } +#zeroheight { height: 0px; } +#changer { height: 100px; background-color: red; } +#bottom { margin-top: 600px; } + +</style> +<div id="outer"> + <div id="zeroheight"> + <div id="changer"></div> + <div id="bottom">bottom</div> + </div> +</div> +<script> + +// Tests that the anchor selection algorithm descends into zero-height +// containers that have overflowing content. + +test(() => { + document.scrollingElement.scrollTop = 200; + document.querySelector("#changer").style.height = "200px"; + assert_equals(document.scrollingElement.scrollTop, 300); +}, "Zero-height container with visible overflow."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/device-pixel-adjustment.html b/testing/web-platform/tests/css/css-scroll-anchoring/device-pixel-adjustment.html new file mode 100644 index 0000000000..4a135939fd --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/device-pixel-adjustment.html @@ -0,0 +1,77 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring-1/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +body { + height: 200vh; +} +#anchor { + width: 100px; + height: 100px; + background-color: blue; +} + +</style> +<div id="expander"></div> +<div id="anchor"></div> +<script> + +// This tests that scroll anchor adjustments can happen by quantities smaller +// than a device pixel. +// +// Unfortunately, we can't test this by simply reading 'scrollTop', because +// 'scrollTop' may be rounded to the nearest CSS pixel. So, to test that +// subpixel adjustments can in fact happen, we repeatedly trigger a scroll +// adjustment in a way that would produce a different final .scrollTop value, +// depending on whether or not we rounded each adjustment as we apply it. + +test(() => { + let scroller = document.scrollingElement; + let expander = document.querySelector("#expander"); + let anchor = document.querySelector("#anchor"); + const initialTop = 10; + + // Scroll 10px to activate scroll anchoring + scroller.scrollTop = initialTop; + + // Helper to insert a div with specified height before the anchor node + function addChild(height) { + let child = document.createElement("div"); + child.style.height = `${height}px`; + anchor.before(child); + } + + // Calculate what fraction of a CSS pixel corresponds to one device pixel + let devicePixel = 1.0 / window.devicePixelRatio; + assert_true(devicePixel <= 1.0, "there should be more device pixels than CSS pixels"); + + // The 0.5 is an arbitrary scale when creating the subpixel delta + let delta = 0.5 * devicePixel; + + // To help us check for for premature rounding of adjustments, we'll + // trigger "count" subpixel adjustments of size "delta", where "count" is + // the first positive integer such that: + // round(count * delta) != count * round(delta) + // As round(X) and count are integers, this happens when: + // count * delta = count * round(delta) +/- 1 + // Solving for count: + // count = 1 / abs(delta - round(delta)) + // Note that we don't need to worry about the denominator being zero, as: + // 0 < devicePixel <= 1 + // And so halving devicePixel should never yield a whole number. + let count = 1 / Math.abs(delta - Math.round(delta)); + + for (let i = 0; i < count; i++) { + addChild(delta); + // Trigger an anchor adjustment by forcing a layout flush + scroller.scrollTop; + } + + let destination = Math.round(initialTop + delta * count); + assert_equals(scroller.scrollTop, destination, + `adjusting by ${delta}px, ${count} times, should be the same as adjusting by ${delta * count}px, once.`); +}, "Test that scroll anchor adjustments can happen by a sub device-pixel amount."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/dirty-contents-reselect-anchor.tentative.html b/testing/web-platform/tests/css/css-scroll-anchoring/dirty-contents-reselect-anchor.tentative.html new file mode 100644 index 0000000000..41adf53a0f --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/dirty-contents-reselect-anchor.tentative.html @@ -0,0 +1,54 @@ +<!doctype html> +<meta charset=utf-8> +<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1738781"> +<link rel=help href="https://github.com/w3c/csswg-drafts/issues/6787"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + .padding { + background: grey; + border: 1px dashed black; + margin: 5px; + height: 200vh; + } +</style> +<div id="content"></div> +<script> + const content = document.getElementById("content"); + + const t = async_test("Scroll anchor is re-selected after adjustment if there are dirty descendants at selection time"); + function replaceAllContent() { + content.innerHTML = ` + <div class="padding"></div> + <button id="target">Scroll target</button> + <div class="padding"></div> + `; + } + + function insertContent() { + let inserted = document.createElement("div"); + inserted.className = "padding inserted"; + content.insertBefore(inserted, content.firstChild); + } + + // Set the content, and scroll #target into view. + replaceAllContent(); + document.getElementById("target").scrollIntoView(); + + t.step(function() { + assert_not_equals(window.scrollY, 0, "Should've scrolled"); + }); + + // Save the target scroll position, which shouldn't change. + const oldTargetTop = document.getElementById("target").getBoundingClientRect().top; + + // Replace all the content, then insert content at the top afterwards. + replaceAllContent(); + + requestAnimationFrame(() => requestAnimationFrame(t.step_func_done(function() { + insertContent(); + const newTargetTop = document.getElementById("target").getBoundingClientRect().top; + assert_equals(oldTargetTop, newTargetTop, "Scroll position should've been preserved"); + }))); +</script> +<style> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/exclude-fixed-position.html b/testing/web-platform/tests/css/css-scroll-anchoring/exclude-fixed-position.html new file mode 100644 index 0000000000..d48d3f7ced --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/exclude-fixed-position.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +body { height: 4000px; margin: 0; } +#fixed, #content { width: 200px; height: 100px; } +#fixed { position: fixed; left: 100px; top: 50px; } +#before { height: 50px; } +#content { margin-top: 100px; } + +</style> +<div id="fixed">fixed</div> +<div id="before"></div> +<div id="content">content</div> +<script> + +// Tests that the anchor selection algorithm skips fixed-positioned elements. + +test(() => { + document.scrollingElement.scrollTop = 100; + document.querySelector("#before").style.height = "100px"; + assert_equals(document.scrollingElement.scrollTop, 150); +}, "Fixed-position header."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/exclude-inline.html b/testing/web-platform/tests/css/css-scroll-anchoring/exclude-inline.html new file mode 100644 index 0000000000..cea6b61dfe --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/exclude-inline.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +#expander { + margin-bottom: 50px; +} +#no { + overflow-anchor: none; +} +#spacing { + margin-bottom: 300vh; +} + +</style> +<span>out of view</span> +<div id="expander"></div> +<span id="no">excluded subtree <span>[nested inline]</span></span> +<div id="spacing"></div> +<script> + +// Tests that an inline element can be an excluded subtree. + +test(() => { + scrollTo(0, 50); + document.querySelector('#expander').style = "margin-bottom: 100px"; + assert_equals(document.scrollingElement.scrollTop, 50, + "Scroll anchoring should not anchor within the span."); + scrollTo(0, 0); +}); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/exclude-sticky.html b/testing/web-platform/tests/css/css-scroll-anchoring/exclude-sticky.html new file mode 100644 index 0000000000..2158d39802 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/exclude-sticky.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring-1/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +body { height: 400vh; margin: 0; } +#sticky, #content { width: 200px; height: 100px; } +#sticky { position: sticky; left: 100px; top: 50px; } +#before { height: 50px; } +#content { margin-top: 100px; } + +</style> +<div id="sticky">sticky</div> +<div id="before"></div> +<div id="content">content</div> +<script> + +// Tests that the anchor selection algorithm skips sticky-positioned elements. + +test(() => { + document.scrollingElement.scrollTop = 150; + document.querySelector("#before").style.height = "100px"; + assert_equals(document.scrollingElement.scrollTop, 200); +}, "Sticky-positioned headers shouldn't be chosen as scroll anchors (we should use 'content' instead)"); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/focus-prioritized.html b/testing/web-platform/tests/css/css-scroll-anchoring/focus-prioritized.html new file mode 100644 index 0000000000..32d90badca --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/focus-prioritized.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<meta charset="utf8"> +<title>CSS Scroll Anchoring: prioritize focused element</title> +<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/#anchor-node-selection"> +<meta name="assert" content="anchor selection prioritized focused element"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<style> +body { height: 4000px } +.spacer { height: 100px } +#growing { height: 100px } +#focused { height: 10px } +</style> + +<div class=spacer></div> +<div class=spacer></div> +<div class=spacer></div> +<div class=spacer></div> +<div id=growing></div> +<div class=spacer></div> +<div id=focused contenteditable></div> +<div class=spacer></div> +<div class=spacer></div> + +<script> +async_test((t) => { + document.scrollingElement.scrollTop = 150; + focused.focus(); + + const target_rect = focused.getBoundingClientRect(); + growing.style.height = "3000px"; + + requestAnimationFrame(() => { + t.step(() => { + const new_rect = focused.getBoundingClientRect(); + assert_equals(new_rect.x, target_rect.x, "x coordinate"); + assert_equals(new_rect.y, target_rect.y, "y coordinate"); + assert_not_equals(document.scrollingElement.scrollTop, 150, "scroll adjusted"); + }); + t.done(); + }); +}, "Anchor selection prioritized focused element."); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/fragment-scrolling-anchors.html b/testing/web-platform/tests/css/css-scroll-anchoring/fragment-scrolling-anchors.html new file mode 100644 index 0000000000..99c679acaa --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/fragment-scrolling-anchors.html @@ -0,0 +1,60 @@ +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + body { + margin: 0px; + height: 2000px; + width: 2000px; + } + + #first { + height: 1000px; + background-color: #FFA5D2; + } + + #anchor { + position: absolute; + background-color: #84BE6A; + height: 600px; + width: 100%; + } + + #fragment { + position: relative; + background-color: orange; + height: 200px; + width: 200px; + margin: 10px; + } +</style> + +<div id="first"></div> +<div id="changer"></div> +<div id="anchor"> + <div id="fragment" name="fragment"></div> +</div> + +<script> + promise_test(async function(t) { + // Note that this test passes even without scroll anchoring because of + // fragment anchoring. + window.location.hash = 'fragment'; + // Height of first + fragment margin-top. + assert_equals(window.scrollY, 1010); + + // Change height of content above fragment. + var ch = document.getElementById('changer'); + ch.style.height = 100; + + await new Promise(resolve => addEventListener('scroll', resolve, {once: true})); + + // Height of first + height changer + fragment margin-top. + assert_equals(window.scrollY, 1110); + + first.remove(); + changer.remove(); + anchor.remove(); + }, 'Verify scroll anchoring interaction with fragment scrolls'); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/fullscreen-crash.html b/testing/web-platform/tests/css/css-scroll-anchoring/fullscreen-crash.html new file mode 100644 index 0000000000..545f2919b5 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/fullscreen-crash.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html class="test-wait"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/"> +<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=823150"> + +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<style> + +#a { height: 700px; } +#b { border: 4px solid #ccc; } + +</style> +<div id="a"><div id="b"></div></div> +<script> + +onload = () => { + test_driver.bless("requestFullscreen", step2); +}; +step2 = () => { + b.requestFullscreen(); + b.addEventListener('fullscreenchange', step3); +}; +step3 = () => { + document.designMode = "on"; + document.execCommand("selectAll"); + document.execCommand("formatBlock", false, "p"); + document.documentElement.classList.remove('test-wait'); +}; + +</script> +</html> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/heuristic-with-offset-update-from-scroll-event-listener.html b/testing/web-platform/tests/css/css-scroll-anchoring/heuristic-with-offset-update-from-scroll-event-listener.html new file mode 100644 index 0000000000..b3964dfcc7 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/heuristic-with-offset-update-from-scroll-event-listener.html @@ -0,0 +1,59 @@ +<!doctype html> +<meta charset="utf-8"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring-1/"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1586909"> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<link rel="author" title="Mozilla" href="https://mozilla.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + #scroller { + overflow: scroll; + height: 500px; + height: 500px; + } + #before { + height: 200px; + } + #anchor { + position: relative; + width: 200px; + height: 200px; + margin-bottom: 500px; + background-color: blue; + top: 0px; + } +</style> +<div id="scroller"> + <div id="before"> + </div> + <div id="anchor"> + </div> +</div> +<script> +async_test(t => { + let scroller = document.querySelector('#scroller'); + let before = document.querySelector('#before'); + let anchor = document.querySelector('#anchor'); + + scroller.onscroll = t.step_func(function() { + // Adjust the 'top' of #anchor, which should trigger a suppression + anchor.style.top = '10px'; + + // Expand #before and make sure we don't apply an adjustment + before.style.height = '300px'; + + assert_equals(scroller.scrollTop, 200); + + t.step_timeout(t.step_func_done(function() { + // Expand #before again and make sure we don't keep #anchor as + // an anchor from the last time. + before.style.height = '600px'; + assert_equals(scroller.scrollTop, 200); + }), 0); + }); + + // Scroll down to select #anchor as a scroll anchor + scroller.scrollTop = 200; +}, 'Positioned ancestors with dynamic changes to offsets trigger scroll suppressions.'); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/heuristic-with-offset-update.html b/testing/web-platform/tests/css/css-scroll-anchoring/heuristic-with-offset-update.html new file mode 100644 index 0000000000..7fcbd983ed --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/heuristic-with-offset-update.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring-1/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<html> +<head> + <style type="text/css"> + #scroller { + overflow: scroll; + height: 500px; + height: 500px; + } + #before { + height: 200px; + } + #anchor { + position: relative; + width: 200px; + height: 200px; + margin-bottom: 500px; + background-color: blue; + /* + * To trigger the Gecko bug that's being regression-tested here, we + * need 'top' to start out at a non-'auto' value, so that the + * dynamic change can trigger Gecko's "RecomputePosition" fast path + */ + top: 0px; + } + </style> +</head> +<body> + <div id="scroller"> + <div id="before"> + </div> + <div id="anchor"> + </div> + </div> + + <script type="text/javascript"> + test(() => { + let scroller = document.querySelector('#scroller'); + let before = document.querySelector('#before'); + let anchor = document.querySelector('#anchor'); + + // Scroll down to select #anchor as a scroll anchor + scroller.scrollTop = 200; + + // Adjust the 'top' of #anchor, which should trigger a suppression + anchor.style.top = '10px'; + + // Expand #before and make sure we don't apply an adjustment + before.style.height = '300px'; + assert_equals(scroller.scrollTop, 200); + }, 'Positioned ancestors with dynamic changes to offsets trigger scroll suppressions.'); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/history-restore-anchors.html b/testing/web-platform/tests/css/css-scroll-anchoring/history-restore-anchors.html new file mode 100644 index 0000000000..ecd7806bc9 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/history-restore-anchors.html @@ -0,0 +1,36 @@ +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> + var win; + var messageCount = 0; + // Navigation steps: + // 1- page gets loaded and anchor element gets scrolled into view. + // 2- loaded page refreshed. + async_test(function(t) { + window.onmessage = function() { + if (++messageCount == 1) { + t.step(() => { + var anchor = win.document.getElementById('anchor'); + anchor.scrollIntoView(); + assert_equals(win.scrollY, 1000); + win.location.reload(); + }); + } else { + t.step(() => { + assert_equals(win.scrollY, 1000); + // Change height of content above anchor. + var ch = win.document.getElementById('changer'); + ch.style.height = 100; + // Height of first + height changer. + assert_equals(win.scrollY, 1100) + t.done(); + }); + win.close(); + } + }; + win = window.open('support/history-restore-anchors-new-window.html'); + }, 'Verify scroll anchoring interaction with history restoration'); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/image-001.html b/testing/web-platform/tests/css/css-scroll-anchoring/image-001.html new file mode 100644 index 0000000000..475c9170b8 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/image-001.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Scroll Anchoring: Anchor node can be an image</title> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<link rel="author" title="Mozilla" href="https://mozilla.org"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/#anchor-node-selection"> +<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/4247"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1540203"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> +body { height: 4000px } +#spacer { height: 100px } +img { + width: 500px; + height: 500px; + background: green; +} +</style> +<div id=spacer></div> +<br><br> +<img> +<script> +test(() => { + document.scrollingElement.scrollTop = 150; + document.querySelector("#spacer").style.height = "150px"; + assert_equals(document.scrollingElement.scrollTop, 200); +}, "Anchor selection can select images"); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/infinite-scroll-event.tentative.html b/testing/web-platform/tests/css/css-scroll-anchoring/infinite-scroll-event.tentative.html new file mode 100644 index 0000000000..e2a2998c52 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/infinite-scroll-event.tentative.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<link rel="author" title="Mozilla" href="https://mozilla.org"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1561450"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers"> +<style> + body { margin: 0 } + .content { + height: 45vh; + background: lightblue; + } +</style> +<div class="content"></div> +<div id="hidden" style="display: none; height: 200px"></div> +<div class="content"></div> +<div class="content"></div> +<div class="content"></div> +<script> +let count = 0; +const t = async_test("Scroll adjustments don't keep happening with 0-length adjustments triggered by a single scroll operation"); +onscroll = t.step_func(function() { + ++count; + hidden.style.display = "block"; + hidden.offsetTop; + hidden.style.display = "none"; + let currentCount = count; + requestAnimationFrame(t.step_func(function() { + requestAnimationFrame(t.step_func(function() { + if (currentCount == count) { + t.done(); + } + })); + })); +}); + +window.onload = t.step_func(function() { + window.scrollTo(0, document.documentElement.scrollHeight); + window.scrollBy(0, -200); +}); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/inheritance.html b/testing/web-platform/tests/css/css-scroll-anchoring/inheritance.html new file mode 100644 index 0000000000..035d4ffd2e --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/inheritance.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>Inheritance of CSS Scroll Anchoring properties</title> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/#property-index"> +<meta name="assert" content="overflow-anchor does not inherit."> +<meta name="assert" content="overflow-anchor has initial value 'none'."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/css/support/inheritance-testcommon.js"></script> +</head> +<body> +<div id="container"> + <div id="target"></div> +</div> +<script> +assert_not_inherited('overflow-anchor', 'auto', 'none'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/inline-block-002.html b/testing/web-platform/tests/css/css-scroll-anchoring/inline-block-002.html new file mode 100644 index 0000000000..9163d34b3b --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/inline-block-002.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Scroll Anchoring: Anchor node can be an empty inline-block</title> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<link rel="author" title="Mozilla" href="https://mozilla.org"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/#anchor-node-selection"> +<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/4247"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1540203"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> +body { height: 4000px } +#ib1, #ib2 { display: inline-block; height: 100px; width: 100% } +</style> +<span id=ib1></span> +<br><br> +<span id=ib2></span> +<script> + +// Tests anchoring to an empty inline-block. + +test(() => { + document.scrollingElement.scrollTop = 150; + document.querySelector("#ib1").style.height = "150px"; + assert_equals(document.scrollingElement.scrollTop, 200); +}, "Anchor selection can select empty inline-blocks"); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/inline-block.html b/testing/web-platform/tests/css/css-scroll-anchoring/inline-block.html new file mode 100644 index 0000000000..881ff97de9 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/inline-block.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +body { height: 4000px } +#outer { line-height: 100px } +#ib1, #ib2 { display: inline-block } + +</style> +<span id=outer> + <span id=ib1>abc</span> + <br><br> + <span id=ib2>def</span> +</span> +<script> + +// Tests anchoring to an inline block inside a <span>. + +test(() => { + document.scrollingElement.scrollTop = 150; + document.querySelector("#ib1").style.lineHeight = "150px"; + assert_equals(document.scrollingElement.scrollTop, 200); +}, "Anchor selection descent into inline blocks."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/multicol-fragmented-anchor.html b/testing/web-platform/tests/css/css-scroll-anchoring/multicol-fragmented-anchor.html new file mode 100644 index 0000000000..931a88ccbf --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/multicol-fragmented-anchor.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring-1/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +body { margin: 0; } +#scroller { + overflow: scroll; + height: 100px; +} +#multicol { + margin-top: 20px; + height: 200px; + columns: 2; + column-fill: auto; +} +#before { + margin-top: 100px; + height: 100px; +} +#content { + height: 10px; +} +</style> +<div id="scroller"> + <div id="multicol"> + <div id="fragmented"> + <div id="before"></div> + <div id="content">content</div> + </div> + </div> +</div> +<script> + +// Tests a scroll anchor inside of a div fragmented across multicol + +test(() => { + let scroller = document.querySelector("#scroller"); + let before = document.querySelector("#before"); + let content = document.querySelector("#content"); + + // Scroll down so that we select a scroll anchor. We should select #content + // and not #before, as #before is positioned offscreen in the first column + scroller.scrollTop = 10; + + // Increase the height of #before so that it fragments into the second + // column and pushes #content down. + before.style.height = "110px"; + + // We should have anchored to #content and have done an adjustment of 10px + assert_equals(scroller.scrollTop, 20); +}, "An element in a fragmented div should be able to be selected as an anchor node."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/negative-layout-overflow.html b/testing/web-platform/tests/css/css-scroll-anchoring/negative-layout-overflow.html new file mode 100644 index 0000000000..e1ce331f1a --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/negative-layout-overflow.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +body { + height: 1200px; +} +#header { + position: relative; + height: 100px; +} +#evil { + position: relative; + top: -900px; + height: 1000px; + width: 100px; +} +#changer { + height: 100px; +} +#anchor { + height: 100px; + background-color: green; +} + +</style> +<div id="header"> + <div id="evil"></div> +</div> +<div id="changer"></div> +<div id="anchor"></div> +<script> + +// Tests that the anchor selection algorithm correctly accounts for negative +// positioning when computing bounds for visibility. + +test(() => { + document.scrollingElement.scrollTop = 250; + document.querySelector("#changer").style.height = "200px"; + assert_equals(document.scrollingElement.scrollTop, 350); +}, "Anchor selection accounts for negative positioning."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/nested-overflow-subtree-layout-ref.html b/testing/web-platform/tests/css/css-scroll-anchoring/nested-overflow-subtree-layout-ref.html new file mode 100644 index 0000000000..003cb9b68a --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/nested-overflow-subtree-layout-ref.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <link rel="author" title="Chris Harrelson" href="mailto:chrishtr@chromium.org"> + <link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/"> + <script src="/common/reftest-wait.js"></script> +</head> +<style> +#outer { + overflow: hidden; + width: 500px; + height: 500px; +} +#inner { + overflow: auto; + position: relative; + width: 500px; + height: 2000px; +} +p { + + font: 48pt monospace; +} +</style> +</head> +<body> +<div id="outer"> + <div id="inner"> + <p>Anchor</p> + </div> +</div> +<script> +const outer = document.querySelector("#outer"); +const inner = document.querySelector("#inner"); + +onload = () => { + requestAnimationFrame(() => { + requestAnimationFrame(() => { + outer.scrollTo(0, 70); + takeScreenshot(); + }); + }); +}; +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/nested-overflow-subtree-layout-vertical-ref.html b/testing/web-platform/tests/css/css-scroll-anchoring/nested-overflow-subtree-layout-vertical-ref.html new file mode 100644 index 0000000000..0026f2f888 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/nested-overflow-subtree-layout-vertical-ref.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <link rel="author" title="Chris Harrelson" href="mailto:chrishtr@chromium.org"> + <link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com"> + <link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/"> + <script src="/common/reftest-wait.js"></script> +</head> +<style> +#outer { + overflow: hidden; + width: 500px; + height: 500px; + writing-mode: vertical-rl; +} +#inner { + overflow: auto; + position: relative; + width: 2000px; + height: 500px; +} +p { + font: 48pt monospace; +} +</style> +</head> +<body> +<div id="outer"> + <div id="inner"> + <p>Anchor</p> + </div> +</div> +<script> +const outer = document.querySelector("#outer"); +const inner = document.querySelector("#inner"); + +onload = () => { + requestAnimationFrame(() => { + requestAnimationFrame(() => { + outer.scrollTo(-70, 0); + takeScreenshot(); + }); + }); +}; +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/nested-overflow-subtree-layout-vertical.html b/testing/web-platform/tests/css/css-scroll-anchoring/nested-overflow-subtree-layout-vertical.html new file mode 100644 index 0000000000..5b176a2042 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/nested-overflow-subtree-layout-vertical.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <title>Test that subtree layout with nested overflow preserves scroll anchoring in vertical mode.</title> + <link rel="author" title="Chris Harrelson" href="mailto:chrishtr@chromium.org"> + <link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com"> + <link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/"> + <link rel="match" href="nested-overflow-subtree-layout-vertical-ref.html"> + <script src="/common/reftest-wait.js"></script> +</head> +<style> +#outer { + overflow: hidden; + width: 500px; + height: 500px; + writing-mode: vertical-rl; +} +#inner { + overflow: auto; + position: relative; + width: 2000px; + height: 500px; +} +p { + font: 48pt monospace; +} +</style> +</head> +<body> +<div id="outer"> + <div id="inner"> + <p>Anchor</p> + </div> +</div> +<script> +const outer = document.querySelector("#outer"); +const inner = document.querySelector("#inner"); + +onload = () => { + requestAnimationFrame(() => { + requestAnimationFrame(() => { + outer.scrollTo(-70, 0); + requestAnimationFrame(() => { + const elem = document.createElement("p"); + elem.textContent = "FAIL"; + inner.insertBefore(elem, inner.firstChild); + takeScreenshot(); + }); + }); + }); +}; +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/nested-overflow-subtree-layout.html b/testing/web-platform/tests/css/css-scroll-anchoring/nested-overflow-subtree-layout.html new file mode 100644 index 0000000000..e7696016bb --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/nested-overflow-subtree-layout.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <title>Test that subtree layout with nested overflow preserves scroll anchoring.</title> + <link rel="author" title="Chris Harrelson" href="mailto:chrishtr@chromium.org"> + <link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/"> + <link rel="match" href="nested-overflow-subtree-layout-ref.html"> + <script src="/common/reftest-wait.js"></script> +</head> +<style> +#outer { + overflow: hidden; + width: 500px; + height: 500px; +} +#inner { + overflow: auto; + position: relative; + width: 500px; + height: 2000px; +} +p { + + font: 48pt monospace; +} +</style> +</head> +<body> +<div id="outer"> + <div id="inner"> + <p>Anchor</p> + </div> +</div> +<script> +const outer = document.querySelector("#outer"); +const inner = document.querySelector("#inner"); + +onload = () => { + requestAnimationFrame(() => { + requestAnimationFrame(() => { + outer.scrollTo(0, 70); + requestAnimationFrame(() => { + const elem = document.createElement("p"); + elem.textContent = "FAIL"; + inner.insertBefore(elem, inner.firstChild); + takeScreenshot(); + }); + }); + }); +}; +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/opt-out-dynamic-scroller.html b/testing/web-platform/tests/css/css-scroll-anchoring/opt-out-dynamic-scroller.html new file mode 100644 index 0000000000..6ccbc4f2fd --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/opt-out-dynamic-scroller.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring-1/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +#scroller { + overflow: scroll; + width: 300px; + height: 300px; +} +#before { height: 50px; } +#content { margin-top: 100px; margin-bottom: 600px; } +.no { overflow-anchor: none; } + +</style> +<div id="scroller"> + <div id="before"></div> + <div id="content">content</div> +</div> +<script> + +// Tests that dynamic styling 'overflow-anchor' on a scrolling element has the +// same effect as initial styling + +test(() => { + let scroller = document.querySelector("#scroller"); + let before = document.querySelector("#before"); + + // Scroll down so that #content is the first element in the viewport + scroller.scrollTop = 100; + + // Change the height of #before to trigger a scroll adjustment. This ensures + // that #content was selected as a scroll anchor + before.style.height = "100px"; + assert_equals(scroller.scrollTop, 150); + + // Now set 'overflow-anchor: none' on #scroller. This should invalidate the + // scroll anchor, and #scroller shouldn't be able to select an anchor anymore + scroller.className = 'no'; + + // Change the height of #before and make sure we don't adjust. This ensures + // that #content is not a scroll anchor + before.style.height = "150px"; + assert_equals(scroller.scrollTop, 150); +}, "Dynamically styling 'overflow-anchor: none' on the scroller element should prevent scroll anchoring"); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/opt-out-dynamic.html b/testing/web-platform/tests/css/css-scroll-anchoring/opt-out-dynamic.html new file mode 100644 index 0000000000..ec548dc3d6 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/opt-out-dynamic.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring-1/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +#scroller { + overflow: scroll; + width: 300px; + height: 300px; +} +#before { height: 50px; } +#content { margin-top: 100px; margin-bottom: 600px; } +.no { overflow-anchor: none; } + +</style> +<div id="scroller"> + <div id="before"></div> + <div id="content">content</div> +</div> +<script> + +// Tests that dynamic styling 'overflow-anchor' on an anchor node has the +// same effect as initial styling + +test(() => { + let scroller = document.querySelector("#scroller"); + let before = document.querySelector("#before"); + let content = document.querySelector("#content"); + + // Scroll down so that #content is the first element in the viewport + scroller.scrollTop = 100; + + // Change the height of #before to trigger a scroll adjustment. This ensures + // that #content was selected as a scroll anchor + before.style.height = "100px"; + assert_equals(scroller.scrollTop, 150); + + // Now set 'overflow-anchor: none' on #content. This should invalidate the + // scroll anchor, and #scroller should recalculate its anchor. There are no + // other valid anchors in the viewport, so there should be no anchor. + content.className = 'no'; + + // Change the height of #before and make sure we don't adjust. This ensures + // that #content was not selected as a scroll anchor + before.style.height = "150px"; + assert_equals(scroller.scrollTop, 150); +}, "Dynamically styling 'overflow-anchor: none' on the anchor node should prevent scroll anchoring"); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/opt-out-inner-table.html b/testing/web-platform/tests/css/css-scroll-anchoring/opt-out-inner-table.html new file mode 100644 index 0000000000..d5ec073821 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/opt-out-inner-table.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring-1/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +#scroller { + height: 200px; + overflow: scroll; +} +#before { height: 50px; } +#table-row { + display: table-row; + overflow-anchor: none; + width: 100px; + height: 100px; +} +#after { margin-bottom: 500px; } + +</style> +<div id="scroller"> + <div id="before"></div> + <div id="table-row">content</div> + <div id="after"></div> +</div> +<script> + +// Tests that the anchor exclusion API works with table parts that generate +// anonymous table box wrappers + +test(() => { + let scroller = document.querySelector('#scroller'); + let before = document.querySelector('#before'); + + // Scroll down so that #table-row is the only element in view + scroller.scrollTop = 50; + + // Expand #before so that we might perform a scroll adjustment + before.style.height = "100px"; + + // We shouldn't have selected #table-row as an anchor as it is + // 'overflow-anchor: none' + assert_equals(scroller.scrollTop, 50); +}, "A table with anonymous wrappers and 'overflow-anchor: none' shouldn't generate any scroll anchor candidates."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/opt-out-table.html b/testing/web-platform/tests/css/css-scroll-anchoring/opt-out-table.html new file mode 100644 index 0000000000..83cfef9797 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/opt-out-table.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring-1/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +#scroller { + height: 200px; + overflow: scroll; +} +#before { height: 50px; } +#table { + display: table; + overflow-anchor: none; + width: 100px; + height: 100px; + margin-bottom: 500px; +} + +</style> +<div id="scroller"> + <div id="before"></div> + <div id="table">content</div> +</div> +<script> + +// Tests that the anchor exclusion API works with tables + +test(() => { + let scroller = document.querySelector('#scroller'); + let before = document.querySelector('#before'); + + // Scroll down so that #table is the only element in view + scroller.scrollTop = 50; + + // Expand #before so that we might perform a scroll adjustment + before.style.height = "100px"; + + // We shouldn't have selected #table as an anchor as it is + // 'overflow-anchor: none' + assert_equals(scroller.scrollTop, 50); +}, "A table with 'overflow-anchor: none' shouldn't generate any scroll anchor candidates."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/opt-out.html b/testing/web-platform/tests/css/css-scroll-anchoring/opt-out.html new file mode 100644 index 0000000000..12d46c13f9 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/opt-out.html @@ -0,0 +1,74 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +body { height: 2000px; overflow-anchor: none; } +#scroller { overflow: scroll; width: 500px; height: 300px; } +.anchor { + position:relative; height: 100px; width: 150px; + background-color: #afa; border: 1px solid gray; +} +#forceScrolling { height: 500px; background-color: #fcc; } + +</style> +<div id="outerChanger"></div> +<div id="outerAnchor" class="anchor"></div> +<div id="scroller"> + <div id="innerChanger"></div> + <div id="innerAnchor" class="anchor"></div> + <div id="forceScrolling"></div> +</div> +<script> + +// Tests that scroll anchoring can be disabled per-scroller with the +// overflow-anchor property. + +var divScroller = document.querySelector("#scroller"); +var docScroller = document.scrollingElement; +var innerChanger = document.querySelector("#innerChanger"); +var outerChanger = document.querySelector("#outerChanger"); + +function setup() { + divScroller.scrollTop = 100; + docScroller.scrollTop = 100; + innerChanger.style.height = "200px"; + outerChanger.style.height = "150px"; +} + +function reset() { + document.body.style.overflowAnchor = ""; + divScroller.style.overflowAnchor = ""; + divScroller.scrollTop = 0; + docScroller.scrollTop = 0; + innerChanger.style.height = ""; + outerChanger.style.height = ""; +} + +test(() => { + setup(); + + assert_equals(divScroller.scrollTop, 300, + "Scroll anchoring should apply to #scroller."); + + assert_equals(docScroller.scrollTop, 100, + "Scroll anchoring should not apply to the document scroller."); + + reset(); +}, "Disabled on document, enabled on div."); + +test(() => { + document.body.style.overflowAnchor = "auto"; + divScroller.style.overflowAnchor = "none"; + setup(); + + assert_equals(divScroller.scrollTop, 100, + "Scroll anchoring should not apply to #scroller."); + + assert_equals(docScroller.scrollTop, 250, + "Scroll anchoring should apply to the document scroller."); + + reset(); +}, "Enabled on document, disabled on div."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/parsing/overflow-anchor-computed.html b/testing/web-platform/tests/css/css-scroll-anchoring/parsing/overflow-anchor-computed.html new file mode 100644 index 0000000000..16b41edb86 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/parsing/overflow-anchor-computed.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Scroll Anchoring: getComputedStyle().overflowAnchor</title> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/#propdef-overflow-anchor"> +<meta name="assert" content="overflow-anchor computed value is as specified."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/css/support/computed-testcommon.js"></script> +</head> +<body> +<div id="target"></div> +<script> +test_computed_value("overflow-anchor", "auto"); +test_computed_value("overflow-anchor", "none"); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/parsing/overflow-anchor-invalid.html b/testing/web-platform/tests/css/css-scroll-anchoring/parsing/overflow-anchor-invalid.html new file mode 100644 index 0000000000..af66cd6e8a --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/parsing/overflow-anchor-invalid.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Scroll Anchoring: parsing overflow-anchor with invalid values</title> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/#propdef-overflow-anchor"> +<meta name="assert" content="overflow-anchor supports only the grammar 'auto | none'."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/css/support/parsing-testcommon.js"></script> +</head> +<body> +<script> +test_invalid_value("overflow-anchor", "all"); +test_invalid_value("overflow-anchor", "auto none"); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/parsing/overflow-anchor-valid.html b/testing/web-platform/tests/css/css-scroll-anchoring/parsing/overflow-anchor-valid.html new file mode 100644 index 0000000000..62b9761552 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/parsing/overflow-anchor-valid.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Scroll Anchoring: parsing overflow-anchor with valid values</title> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/#propdef-overflow-anchor"> +<meta name="assert" content="overflow-anchor supports the full grammar 'auto | none'."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/css/support/parsing-testcommon.js"></script> +</head> +<body> +<script> +test_valid_value("overflow-anchor", "auto"); +test_valid_value("overflow-anchor", "none"); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/position-change-heuristic-display-none-change.html b/testing/web-platform/tests/css/css-scroll-anchoring/position-change-heuristic-display-none-change.html new file mode 100644 index 0000000000..6e00238382 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/position-change-heuristic-display-none-change.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<link rel="author" title="Mozilla" href="https://mozilla.org"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1543599"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers"> +<style> +#hidden { + display: none; + background: red; +} +#spacer { + height: calc(100vh + 200px); /* At least 200px of scroll range */ +} +</style> +<table> + <thead> + <tr> + <th>1 + <th>1 + <th>1 + <th>1 + </tr> + </thead> + <thead id="hidden"> + <tr> + <th>1 + <th>1 + <th>1 + <th>1 + </tr> + </thead> + <tbody> + <tr><td>0 <td>0 <td>0 <td>0 </tr> + <tr><td>0 <td>0 <td>0 <td>0 </tr> + <tr><td>0 <td>0 <td>0 <td>0 </tr> + <tr><td>0 <td>0 <td>0 <td>0 </tr> + <tr><td>0 <td>0 <td>0 <td>0 </tr> + <tr><td>0 <td>0 <td>0 <td>0 </tr> + <tr><td>0 <td>0 <td>0 <td>0 </tr> + </tbody> +</table> +<div id="spacer"></div> +<script> +let firstEvent = true; +const targetScrollPosition = 100; +const hidden = document.querySelector("#hidden"); +const t = async_test("Scroll offset doesn't change when an element goes back and forth to display: none"); +window.onscroll = t.step_func(function() { + hidden.style.display = "block"; + hidden.style.position = "absolute"; + hidden.style.visibility = "hidden"; + window.unused = hidden.offsetHeight; + hidden.style.display = ""; + hidden.style.position = ""; + hidden.style.visibility = ""; + + if (firstEvent) { + firstEvent = false; + requestAnimationFrame(t.step_func(function() { + requestAnimationFrame(t.step_func_done(function() { + assert_equals(document.scrollingElement.scrollTop, targetScrollPosition); + })); + })); + } +}); + +window.onload = t.step_func(function() { + window.scrollTo(0, targetScrollPosition); +}); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/position-change-heuristic-display-none-to-abspos-change.html b/testing/web-platform/tests/css/css-scroll-anchoring/position-change-heuristic-display-none-to-abspos-change.html new file mode 100644 index 0000000000..5cf27d9e6b --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/position-change-heuristic-display-none-to-abspos-change.html @@ -0,0 +1,71 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<link rel="author" title="Mozilla" href="https://mozilla.org"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1568778"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers"> +<style> +#hidden { + display: none; + background: red; +} +#spacer { + height: calc(100vh + 200px); /* At least 200px of scroll range */ +} +</style> +<table> + <thead> + <tr> + <th>1 + <th>1 + <th>1 + <th>1 + </tr> + </thead> + <thead id="hidden"> + <tr> + <th>1 + <th>1 + <th>1 + <th>1 + </tr> + </thead> + <tbody> + <tr><td>0 <td>0 <td>0 <td>0 </tr> + <tr><td>0 <td>0 <td>0 <td>0 </tr> + <tr><td>0 <td>0 <td>0 <td>0 </tr> + <tr><td>0 <td>0 <td>0 <td>0 </tr> + <tr><td>0 <td>0 <td>0 <td>0 </tr> + <tr><td>0 <td>0 <td>0 <td>0 </tr> + <tr><td>0 <td>0 <td>0 <td>0 </tr> + </tbody> +</table> +<div id="spacer"></div> +<script> +let isFirstEvent = true; +const targetScrollPosition = 100; +const hidden = document.querySelector("#hidden"); +const t = async_test("Scroll offset doesn't get stuck in infinite scroll events when an element goes back and forth to display: none while changing abspos style"); +window.onscroll = t.step_func(function() { + hidden.style.display = "block"; + hidden.style.position = "absolute"; + hidden.style.visibility = "hidden"; + window.unused = hidden.offsetHeight; + hidden.style.display = ""; + hidden.style.position = ""; + hidden.style.visibility = ""; + + assert_true(isFirstEvent, "Shouldn't get more than one scroll event"); + isFirstEvent = false; + requestAnimationFrame(t.step_func(function() { + requestAnimationFrame(t.step_func_done(function() { + assert_equals(document.scrollingElement.scrollTop, targetScrollPosition); + })); + })); +}); + +window.onload = t.step_func(function() { + window.scrollTo(0, targetScrollPosition); +}); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/position-change-heuristic-ib-split.html b/testing/web-platform/tests/css/css-scroll-anchoring/position-change-heuristic-ib-split.html new file mode 100644 index 0000000000..e903325112 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/position-change-heuristic-ib-split.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<link rel="author" title="Mozilla" href="https://mozilla.org"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1559627"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers"> +<style> + body, h1 { + margin: 0 + } + .sticky { + width: 100%; + top: 0; + left: 0; + } + .sticky > div { + width: 100%; + height: 50px; + background: darkblue; + } + header { + background: lightblue; + } + main { + background: lightgrey; + height: 200vh; + } +</style> +<header> + <h1>Some title</h1> + <span class="sticky"><div>Sticky header</div></span> +</header> +<main> + Some actual content. +</main> +<script> +const sticky = document.querySelector(".sticky"); +const nonStickyOffset = sticky.firstElementChild.offsetTop; +const t = async_test("Scroll offset adjustments are correctly suppressed when changing the position of an inline"); +let firstEvent = true; +window.onscroll = t.step_func(function() { + sticky.style.position = + document.documentElement.scrollTop > nonStickyOffset ? "fixed" : "static"; + if (firstEvent) { + firstEvent = false; + requestAnimationFrame(t.step_func(function() { + requestAnimationFrame(t.step_func_done(function() { + assert_equals(sticky.style.position, "fixed", "Element should become and remain fixed") + })); + })); + } +}); +window.onload = t.step_func(function() { + window.scrollTo(0, nonStickyOffset + 1); +}); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/position-change-heuristic-in-nested-scroll-box.html b/testing/web-platform/tests/css/css-scroll-anchoring/position-change-heuristic-in-nested-scroll-box.html new file mode 100644 index 0000000000..58c88001d5 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/position-change-heuristic-in-nested-scroll-box.html @@ -0,0 +1,85 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers"> +<style> +#space { + height: 4000px; + overflow: hidden; +} +#header { + background-color: #F5B335; + height: 50px; + width: 100%; +} +#content { + background-color: #D3D3D3; + height: 400px; +} +.scroller { + overflow: scroll; + position: relative; + width: 600px; + height: 600px; +} +body { + overflow: hidden; +} +</style> +<div id="maybeScroller"> + <div id="space"> + <div id="header"></div> + <div id="before"></div> + <div id="content"></div> + </div> +</div> +<script> + +// Tests that scroll anchoring is suppressed when an element in the scroller +// changes its in-flow state. + +var scroller; + +function runCase(oldPos, newPos, expectSuppression, skipInverse) { + var header = document.querySelector("#header"); + var before = document.querySelector("#before"); + + header.style.position = oldPos; + before.style.height = "0"; + scroller.scrollTop = 200; + + header.style.position = newPos; + before.style.height = "25px"; + + var expectedTop = expectSuppression ? 200 : 225; + assert_equals(scroller.scrollTop, expectedTop); + + if (!skipInverse) + runCase(newPos, oldPos, expectSuppression, true); +} + +test(() => { + scroller = document.scrollingElement; + document.querySelector("#maybeScroller").className = ""; + + runCase("static", "fixed", true); + runCase("static", "absolute", true); + runCase("static", "relative", false); + runCase("fixed", "absolute", false); + runCase("fixed", "relative", true); + runCase("absolute", "relative", true); +}, "Position changes in document scroller."); + +test(() => { + scroller = document.querySelector("#maybeScroller"); + scroller.className = "scroller"; + + runCase("static", "fixed", true); + runCase("static", "absolute", true); + runCase("static", "relative", false); + runCase("fixed", "absolute", false); + runCase("fixed", "relative", true); + runCase("absolute", "relative", true); +}, "Position changes in scrollable <div>."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/position-change-heuristic.html b/testing/web-platform/tests/css/css-scroll-anchoring/position-change-heuristic.html new file mode 100644 index 0000000000..b36b211f58 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/position-change-heuristic.html @@ -0,0 +1,82 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +#space { + height: 4000px; +} +#header { + background-color: #F5B335; + height: 50px; + width: 100%; +} +#content { + background-color: #D3D3D3; + height: 400px; +} +.scroller { + overflow: scroll; + position: relative; + width: 600px; + height: 600px; +} + +</style> +<div id="maybeScroller"> + <div id="space"> + <div id="header"></div> + <div id="before"></div> + <div id="content"></div> + </div> +</div> +<script> + +// Tests that scroll anchoring is suppressed when an element in the scroller +// changes its in-flow state. + +var scroller; + +function runCase(oldPos, newPos, expectSuppression, skipInverse) { + var header = document.querySelector("#header"); + var before = document.querySelector("#before"); + + header.style.position = oldPos; + before.style.height = "0"; + scroller.scrollTop = 200; + + header.style.position = newPos; + before.style.height = "25px"; + + var expectedTop = expectSuppression ? 200 : 225; + assert_equals(scroller.scrollTop, expectedTop); + + if (!skipInverse) + runCase(newPos, oldPos, expectSuppression, true); +} + +test(() => { + scroller = document.scrollingElement; + document.querySelector("#maybeScroller").className = ""; + + runCase("static", "fixed", true); + runCase("static", "absolute", true); + runCase("static", "relative", false); + runCase("fixed", "absolute", false); + runCase("fixed", "relative", true); + runCase("absolute", "relative", true); +}, "Position changes in document scroller."); + +test(() => { + scroller = document.querySelector("#maybeScroller"); + scroller.className = "scroller"; + + runCase("static", "fixed", true); + runCase("static", "absolute", true); + runCase("static", "relative", false); + runCase("fixed", "absolute", false); + runCase("fixed", "relative", true); + runCase("absolute", "relative", true); +}, "Position changes in scrollable <div>."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/reading-scroll-forces-anchoring.html b/testing/web-platform/tests/css/css-scroll-anchoring/reading-scroll-forces-anchoring.html new file mode 100644 index 0000000000..39b8e36398 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/reading-scroll-forces-anchoring.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring-1/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> +body { height: 1000px } +div { height: 100px } +</style> +<div id="block1">abc</div> +<div id="block2">def</div> +<script> + // This test verifies that reading window.scrollY forces any pending scroll + // anchoring adjustment to occur before computing the return value. + async_test((t) => { + scrollTo(0, 150); + requestAnimationFrame(() => { + step_timeout(() => { + // Queue scroll anchoring adjustment. + document.querySelector("#block1").style.height = "200px"; + + // Reading scrollY should force both the layout and the adjustment to + // occur synchronously. + var y = scrollY; + + assert_equals(y, 250); + t.done(); + }, 0); + }); + }, 'Reading scroll position forces scroll anchoring adjustment.'); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/scroll-padding-affects-anchoring.html b/testing/web-platform/tests/css/css-scroll-anchoring/scroll-padding-affects-anchoring.html new file mode 100644 index 0000000000..e2c8741d88 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/scroll-padding-affects-anchoring.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>scroll anchoring accounts for scroll-padding</title> +<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez"> +<link rel="author" href="https://mozilla.org" title="Mozilla"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/#anchor-node-selection"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-snap-1/#optimal-viewing-region"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + #scroller { + overflow: auto; + height: 500px; + scroll-padding-top: 200px; + } + #changer { + height: 100px; + } + #content { + height: 1000px; + } +</style> +<div id="scroller"> + <div id="changer"></div> + <div id="content"></div> +</div> +<script> + test(() => { + scroller.scrollTop = 50; + changer.style.height = "200px"; + assert_equals(scroller.scrollTop, 150, "Shouldn't anchor to #changer, since it's covered by scroll-padding"); + }); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/start-edge-in-block-layout-direction.html b/testing/web-platform/tests/css/css-scroll-anchoring/start-edge-in-block-layout-direction.html new file mode 100644 index 0000000000..043844d056 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/start-edge-in-block-layout-direction.html @@ -0,0 +1,141 @@ +<!DOCTYPE html> +<meta name="viewport" content="user-scalable=no"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +body { margin: 0; } +html { + line-height: 0; + width: 200vw; + height: 200vh; +} + +html.ltr { direction: ltr; } +html.rtl { direction: rtl; } + +html.horz { writing-mode: horizontal-tb; } +html.vlr { writing-mode: vertical-lr; } +html.vrl { writing-mode: vertical-rl; } + +.horz.ltr .cx2, .vlr .cx2 { left: 100vw; } +.horz.rtl .cx2, .vrl .cx2 { right: 100vw; } +.horz .cy2, .ltr .cy2 { top: 100vh; } +.vlr.rtl .cy2, .vrl.rtl .cy2 { bottom: 100vh; } + +#block_pusher, #inline_pusher { + display: inline-block; + width: 100px; + height: 100px; +} +#block_pusher { background-color: #e88; } +#inline_pusher { background-color: #88e; } +.vpush { height: 80px !important; } +.hpush { width: 70px !important; } + +#anchor-container { + display: inline-block; +} +#anchor { + position: relative; + background-color: #8e8; + min-width: 100px; + min-height: 100px; +} + +#grower { width: 0; height: 0; } +.grow { + width: 180px !important; + height: 160px !important; +} + +</style> +<div id="container"> + <div id="block_pusher"></div><br> + <div id="inline_pusher"></div><div id="anchor-container"> + <div id="anchor"> + <div id="grower"></div> + </div> + </div> +</div> +<script> + +// Tests that anchoring adjustments are only on the block layout axis and that +// their magnitude is based on the movement of the block start edge of the +// anchor node, for all 6 combinations of text direction and writing mode, +// regardless of which corner of the viewport the anchor node overlaps. + +var CORNERS = ["cx1 cy1", "cx2 cy1", "cx1 cy2", "cx2 cy2"]; +var docEl = document.documentElement; +var scroller = document.scrollingElement; +var blockPusher = document.querySelector("#block_pusher"); +var inlinePusher = document.querySelector("#inline_pusher"); +var grower = document.querySelector("#grower"); +var anchor = document.querySelector("#anchor"); + +function reset() { + scroller.scrollLeft = 0; + scroller.scrollTop = 0; + blockPusher.className = ""; + inlinePusher.className = ""; + grower.className = ""; +} + +function runCase(docClass, xDir, yDir, vert, expectXAdj, expectYAdj, corner) { + docEl.className = docClass; + anchor.className = corner; + + var initX = 150 * xDir; + var initY = 150 * yDir; + + scroller.scrollLeft = initX; + scroller.scrollTop = initY; + + // Each corner moves a different distance. + block_pusher.className = vert ? "hpush" : "vpush"; + inline_pusher.className = vert ? "vpush" : "hpush"; + grower.className = "grow"; + + assert_equals(scroller.scrollLeft, initX + expectXAdj); + assert_equals(scroller.scrollTop, initY + expectYAdj); + + reset(); +} + +test(() => { + CORNERS.forEach((corner) => { + runCase("horz ltr", 1, 1, false, 0, -20, corner); + }); +}, "Horizontal LTR."); + +test(() => { + CORNERS.forEach((corner) => { + runCase("horz rtl", -1, 1, false, 0, -20, corner); + }); +}, "Horizontal RTL."); + +test(() => { + CORNERS.forEach((corner) => { + runCase("vlr ltr", 1, 1, true, -30, 0, corner); + }); +}, "Vertical-LR LTR."); + +test(() => { + CORNERS.forEach((corner) => { + runCase("vlr rtl", 1, -1, true, -30, 0, corner); + }); +}, "Vertical-LR RTL."); + +test(() => { + CORNERS.forEach((corner) => { + runCase("vrl ltr", -1, 1, true, 30, 0, corner); + }); +}, "Vertical-RL LTR."); + +test(() => { + CORNERS.forEach((corner) => { + runCase("vrl rtl", -1, -1, true, 30, 0, corner); + }); +}, "Vertical-RL RTL."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/subtree-exclusion.html b/testing/web-platform/tests/css/css-scroll-anchoring/subtree-exclusion.html new file mode 100644 index 0000000000..25961b3664 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/subtree-exclusion.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +body { height: 4000px } +#A, #B { width: 100px; background-color: #afa; } +#B { height: 100px; } +#inner { width: 100px; height: 100px; background-color: pink; } +#A { overflow-anchor: none; } + +</style> +<div id="changer1"></div> +<div id="A"> + <div id="inner"></div> + <div id="changer2"></div> +</div> +<div id="B"></div> +<script> + +// Tests that an element with overflow-anchor: none is excluded, along with its +// DOM descendants, from the anchor selection algorithm. + +test(() => { + var changer1 = document.querySelector("#changer1"); + var changer2 = document.querySelector("#changer2"); + + document.scrollingElement.scrollTop = 50; + changer1.style.height = "100px"; + changer2.style.height = "50px"; + + // We should anchor to #B, not #A or #inner, despite them being visible. + assert_equals(document.scrollingElement.scrollTop, 200); + + document.querySelector("#A").style.overflowAnchor = "auto"; + document.scrollingElement.scrollTop = 150; + + changer1.style.height = "200px"; + changer2.style.height = "100px"; + + // We should now anchor to #inner, which is moved only by #changer1. + assert_equals(document.scrollingElement.scrollTop, 250); +}, "Subtree exclusion with overflow-anchor."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/support/flexbox-scrolling-vertical-rl.html b/testing/web-platform/tests/css/css-scroll-anchoring/support/flexbox-scrolling-vertical-rl.html new file mode 100644 index 0000000000..1a2d02d5c7 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/support/flexbox-scrolling-vertical-rl.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org"> +<style> + html { + writing-mode: vertical-rl; + overflow: hidden; + background: red; + } + body { + margin: 0; + } +</style> +<div style="display:flex; flex-direction:column-reverse;"> + <div style="block-size:1000px;"></div> + <div style="block-size:100px; background:green;"></div> + <div style="block-size:100px;"></div> +</div> +<script> + window.scrollTo(-100, 0); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/support/history-restore-anchors-new-window.html b/testing/web-platform/tests/css/css-scroll-anchoring/support/history-restore-anchors-new-window.html new file mode 100644 index 0000000000..bd8290e793 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/support/history-restore-anchors-new-window.html @@ -0,0 +1,29 @@ +<style> + body { + margin: 0px; + height: 2000px; + width: 2000px; + } + + #first { + height: 1000px; + background-color: #FFA5D2; + } + + #anchor { + position: absolute; + background-color: #84BE6A; + height: 600px; + width: 100%; + } +</style> + +<div id="first"></div> +<div id="changer"></div> +<div id="anchor"></div> + +<script> +onload = function() { + opener.postMessage("loaded", "*"); +} +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/support/scrolling-vertical-rl.html b/testing/web-platform/tests/css/css-scroll-anchoring/support/scrolling-vertical-rl.html new file mode 100644 index 0000000000..1273469dab --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/support/scrolling-vertical-rl.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org"> +<style> + html { + writing-mode: vertical-rl; + overflow: hidden; + background: red; + } + body { + margin: 0; + } +</style> +<div style="block-size:100px;"></div> +<div style="block-size:100px; background:green;"></div> +<div style="block-size:1000px;"></div> +<script> + window.scrollTo(-100, 0); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/table-collapsed-borders-crash.html b/testing/web-platform/tests/css/css-scroll-anchoring/table-collapsed-borders-crash.html new file mode 100644 index 0000000000..aa699317e2 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/table-collapsed-borders-crash.html @@ -0,0 +1,25 @@ +<!doctype html> +<html class="test-wait"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/"> +<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=746570"> +<meta name="assert" content="No crash when a table with dirty internal layout is the scroll anchor."/> +<style> +body { + height:200vh; +} +table { + height: 200px; + width: 200px; + background-color: lime; + border-collapse: collapse; /* triggers problematic border calculation */ +} +</style> + +<table id=table1></table> + +<script> + window.scrollBy(0, 10); + table1.innerHTML = "<tr><td style='background-color:lightblue'></td></tr>"; + document.documentElement.classList.remove('test-wait'); +</script> +</html> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/text-anchor-in-vertical-rl.html b/testing/web-platform/tests/css/css-scroll-anchoring/text-anchor-in-vertical-rl.html new file mode 100644 index 0000000000..0edf950936 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/text-anchor-in-vertical-rl.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/"> + +<p>There should be no red below.</p> +<div id="container" style="writing-mode:vertical-rl; overflow:auto; width:300px; height:300px;"> + <div style="width:300px; background:red;"></div> + <div style="width:400px; font-size:16px; line-height:25px;"> + <span id="displayMe" style="color:red; display:none;"> + FAIL<br>FAIL<br>FAIL<br>FAIL<br> + </span> + line<br> + </div> + <div id="displayMeToo" style="display:none; width:300px; background:red;"></div> +</div> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + test(()=> { + var container = document.getElementById("container"); + var displayMe = document.getElementById("displayMe"); + var displayMeToo = document.getElementById("displayMeToo"); + // Scroll the text container into view. + container.scrollLeft = -300; + displayMe.style.display = "inline"; + displayMeToo.style.display = "block"; + assert_equals(container.scrollLeft, -400); + }, "Line at edge of scrollport shouldn't jump visually when content is inserted before"); +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/vertical-rl-viewport-size-change-000.html b/testing/web-platform/tests/css/css-scroll-anchoring/vertical-rl-viewport-size-change-000.html new file mode 100644 index 0000000000..ee367b1c97 --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/vertical-rl-viewport-size-change-000.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/"> +<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1124195"> +<link rel="match" href="../reference/ref-filled-green-100px-square.xht"> +<style> + #iframe { + display: block; + border: none; + width: 300px; + height: 100px; + } +</style> +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<iframe id="iframe" src="support/scrolling-vertical-rl.html"></iframe> +<script> + onload = function() { + document.body.offsetTop; + iframe.style.width = "100px"; + } +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/vertical-rl-viewport-size-change-001.html b/testing/web-platform/tests/css/css-scroll-anchoring/vertical-rl-viewport-size-change-001.html new file mode 100644 index 0000000000..0e58a1e63b --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/vertical-rl-viewport-size-change-001.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/"> +<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1124195"> +<link rel="match" href="../reference/ref-filled-green-100px-square.xht"> +<style> + #iframe { + display: block; + border: none; + width: 300px; + height: 100px; + } +</style> +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<iframe id="iframe" src="support/flexbox-scrolling-vertical-rl.html"></iframe> +<script> + onload = function() { + document.body.offsetTop; + iframe.style.width = "100px"; + } +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/wrapped-text.html b/testing/web-platform/tests/css/css-scroll-anchoring/wrapped-text.html new file mode 100644 index 0000000000..60f11fbcfe --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/wrapped-text.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +body { + position: absolute; + font-size: 100px; + width: 200px; + height: 4000px; + line-height: 100px; +} + +</style> +abc <b id=b>def</b> ghi +<script> + +// Tests anchoring to a text node that is moved by preceding text. + +test(() => { + var b = document.querySelector("#b"); + var preText = b.previousSibling; + document.scrollingElement.scrollTop = 150; + preText.nodeValue = "abcd efg "; + assert_equals(document.scrollingElement.scrollTop, 250); +}, "Anchoring with text wrapping changes."); + +</script> diff --git a/testing/web-platform/tests/css/css-scroll-anchoring/zero-scroll-offset.html b/testing/web-platform/tests/css/css-scroll-anchoring/zero-scroll-offset.html new file mode 100644 index 0000000000..b8f5aa2ccc --- /dev/null +++ b/testing/web-platform/tests/css/css-scroll-anchoring/zero-scroll-offset.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<head> + <title>Test that scroll anchoring is suppressed when scroll offset is zero.</title> + <link rel="author" title="Nick Burris" href="mailto:nburris@chromium.org"> + <link rel="help" href="https://drafts.csswg.org/css-scroll-anchoring/"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> +<style> +#header { + height: 100px; + border: 1px solid black; + overflow-anchor: none; +} +#content { + height: 200vh; +} +</style> +<div id="header"></div> +<div id="content">abc</div> +<script> +window.addEventListener("scroll", function() { + if (document.scrollingElement.scrollTop > 0) { + // On the first scroll event, shrink the header. Scroll anchoring anchors to + // content, but the header shrinks by more than the scroll offset so the + // resulting scroll position is zero. + step_timeout(function() { + document.querySelector("#header").style.height = "50px"; + }, 0); + } else { + // On the second scroll event, grow the header. Since the scroll offset is + // zero, scroll anchoring should be suppressed. Otherwise, scroll anchoring + // would anchor to content and the resulting scroll position would be 50px. + step_timeout(function() { + document.querySelector("#header").style.height = "100px"; + }, 0); + } +}); + +async_test(function(t) { + // Scroll down a bit to trigger the scroll event listener. + window.scrollTo(0, 10); + + window.requestAnimationFrame(function() { + window.requestAnimationFrame(function() { + window.requestAnimationFrame(t.step_func_done(() => { + assert_equals(document.scrollingElement.scrollTop, 0); + })); + }); + }); + +}, "Scroll anchoring suppressed when scroll offset is zero."); +</script> |