summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/css/css-scroll-snap-2
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/css/css-scroll-snap-2
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/css/css-scroll-snap-2')
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-computed.html140
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-invalid.html48
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-shorthand.html63
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-target-computed.html86
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-target-invalid.html36
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-target-shorthand.html31
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-target-valid.html34
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-valid.html81
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/resources/common.js106
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/resources/user-scroll-common.js71
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-aligns-with-snap-align.tentative.html74
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-display-toggled.tentative.html125
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-nested-container.tentative.html230
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-root.tentative.html63
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-rtl.tentative.html63
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-anchor-navigation-inner-frame.html42
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-anchor-navigation.tentative.html60
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-hash-fragment-navigation-inner-frame.html48
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-hash-fragment-navigation.tentative.html34
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-scroll-snap.tentative.html79
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-scroll-start-root.tentative.html66
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-scroll-start.tentative.html75
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-text-fragment-navigation-target.html83
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-text-fragment-navigation.tentative.html49
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-user-programmatic-scroll.tentative.html125
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target.tentative.html98
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/stash.py27
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-display-toggled.tentative.html78
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-fieldset.tentative.html149
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-overflow-toggled.tentative.html67
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-root.tentative.html33
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-vertical-lr.tentative.html133
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-anchor-navigation-inner-frame.html38
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-anchor-navigation.tentative.html56
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-fragment-navigation-inner-frame.html36
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-fragment-navigation.tentative.html30
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-programmatic-scroll.tentative.html82
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-scroll-snap.tentative.html70
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-text-fragment-navigation-target.html74
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-text-fragment-navigation.tentative.html49
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-user-scroll.tentative.html91
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start.tentative.html122
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/stash.py27
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-after-layout-change.tentative.html145
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-ensures-dom-order.html95
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-interrupted-scroll.tentative.html68
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-programmatic-root-scroll.tentative.html122
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-programmatic-scroll.tentative.html128
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-user-root-scroll.tentative.html166
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-user-scroll.tentative.html170
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-same-targets-after-layout-changed.html106
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-with-proximity-strictness.tentative.html80
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-after-layout-change.tentative.html118
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-on-programmatic-root-scroll.tentative.html105
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-on-programmatic-scroll.tentative.html113
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-on-user-root-scroll.tentative.html198
-rw-r--r--testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-on-user-scroll.tentative.html188
57 files changed, 4974 insertions, 0 deletions
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-computed.html b/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-computed.html
new file mode 100644
index 0000000000..dd8a5e280f
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-computed.html
@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-* computed values</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+ <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("scroll-start", "start");
+ test_computed_value("scroll-start", "start start", "start");
+ test_computed_value("scroll-start", "100px");
+ test_computed_value("scroll-start", "50%");
+ test_computed_value("scroll-start", "center");
+ test_computed_value("scroll-start", "100px 200px");
+ test_computed_value("scroll-start", "50% 100px");
+ test_computed_value("scroll-start", "start 50%");
+ test_computed_value("scroll-start", "center start", "center");
+ test_computed_value("scroll-start", "end center");
+ test_computed_value("scroll-start", "top end");
+ test_computed_value("scroll-start", "bottom top");
+ test_computed_value("scroll-start", "left bottom");
+ test_computed_value("scroll-start", "right left");
+ test_computed_value("scroll-start", "auto right");
+ test_computed_value("scroll-start", "calc(1px)", "1px")
+ test_computed_value("scroll-start", "calc(1px) start", "1px")
+
+ test_computed_value("scroll-start-block", "100px");
+ test_computed_value("scroll-start-block", "50%");
+ test_computed_value("scroll-start-block", "start");
+ test_computed_value("scroll-start-block", "center");
+ test_computed_value("scroll-start-block", "end");
+ test_computed_value("scroll-start-block", "top");
+ test_computed_value("scroll-start-block", "bottom");
+ test_computed_value("scroll-start-block", "left");
+ test_computed_value("scroll-start-block", "right");
+ test_computed_value("scroll-start-block", "auto");
+ test_computed_value("scroll-start-block", "calc(-1px)", "0px");
+
+ test_computed_value("scroll-start-inline", "100px");
+ test_computed_value("scroll-start-inline", "50%");
+ test_computed_value("scroll-start-inline", "start");
+ test_computed_value("scroll-start-inline", "center");
+ test_computed_value("scroll-start-inline", "end");
+ test_computed_value("scroll-start-inline", "top");
+ test_computed_value("scroll-start-inline", "bottom");
+ test_computed_value("scroll-start-inline", "left");
+ test_computed_value("scroll-start-inline", "right");
+ test_computed_value("scroll-start-inline", "auto");
+ test_computed_value("scroll-start-inline", "calc(-1px)", "0px");
+
+ test_computed_value("scroll-start-x", "100px");
+ test_computed_value("scroll-start-x", "50%");
+ test_computed_value("scroll-start-x", "start");
+ test_computed_value("scroll-start-x", "center");
+ test_computed_value("scroll-start-x", "end");
+ test_computed_value("scroll-start-x", "top");
+ test_computed_value("scroll-start-x", "bottom");
+ test_computed_value("scroll-start-x", "left");
+ test_computed_value("scroll-start-x", "right");
+ test_computed_value("scroll-start-x", "auto");
+ test_computed_value("scroll-start-x", "calc(-1px)", "0px");
+
+ test_computed_value("scroll-start-y", "100px");
+ test_computed_value("scroll-start-y", "50%");
+ test_computed_value("scroll-start-y", "start");
+ test_computed_value("scroll-start-y", "center");
+ test_computed_value("scroll-start-y", "end");
+ test_computed_value("scroll-start-y", "top");
+ test_computed_value("scroll-start-y", "bottom");
+ test_computed_value("scroll-start-y", "left");
+ test_computed_value("scroll-start-y", "right");
+ test_computed_value("scroll-start-y", "auto");
+ test_computed_value("scroll-start-y", "calc(-1px)", "0px");
+
+ target.style = "";
+
+ // Test logical-physical mapping.
+ test((t) => {
+ t.add_cleanup(() => { target.style = ""; });
+ target.style.scrollStartBlock = "100px";
+ assert_equals(getComputedStyle(target).scrollStartX, "auto");
+ assert_equals(getComputedStyle(target).scrollStartY, "100px");
+ }, "scroll-start-block maps to scroll-start-y in horizontal writing mode.");
+ test((t) => {
+ t.add_cleanup(() => { target.style = ""; });
+ target.style.scrollStartInline = "200px";
+ assert_equals(getComputedStyle(target).scrollStartX, "200px");
+ assert_equals(getComputedStyle(target).scrollStartY, "auto");
+ }, "scroll-start-inline maps to scroll-start-x in horizontal writing mode.");
+ test((t) => {
+ t.add_cleanup(() => { target.style = ""; });
+ target.style.scrollStartX = "100px";
+ assert_equals(getComputedStyle(target).scrollStartBlock, "auto");
+ assert_equals(getComputedStyle(target).scrollStartInline, "100px");
+ }, "scroll-start-x maps to scroll-start-inline in horizontal writing mode.");
+ test((t) => {
+ t.add_cleanup(() => { target.style = ""; });
+ target.style.scrollStartY = "200px";
+ assert_equals(getComputedStyle(target).scrollStartBlock, "200px");
+ assert_equals(getComputedStyle(target).scrollStartInline, "auto");
+ }, "scroll-start-y maps to scroll-start-block in horizontal writing mode.");
+ test((t) => {
+ t.add_cleanup(() => { target.style = ""; });
+ target.style.scrollStartBlock = "100px";
+ target.style.writingMode = "vertical-lr";
+ assert_equals(getComputedStyle(target).scrollStartX, "100px");
+ assert_equals(getComputedStyle(target).scrollStartY, "auto");
+ }, "scroll-start-block maps to scroll-start-x in vertical writing mode.");
+ test((t) => {
+ t.add_cleanup(() => { target.style = ""; });
+ target.style.scrollStartInline = "200px";
+ target.style.writingMode = "vertical-lr";
+ assert_equals(getComputedStyle(target).scrollStartX, "auto");
+ assert_equals(getComputedStyle(target).scrollStartY, "200px");
+ }, "scroll-start-inline maps to scroll-start-y in vertical writing mode.");
+ test((t) => {
+ t.add_cleanup(() => { target.style = ""; });
+ target.style.scrollStartX = "100px";
+ target.style.writingMode = "vertical-lr";
+ assert_equals(getComputedStyle(target).scrollStartBlock, "100px");
+ assert_equals(getComputedStyle(target).scrollStartInline, "auto");
+ }, "scroll-start-x maps to scroll-start-block in vertical writing mode.");
+ test((t) => {
+ t.add_cleanup(() => { target.style = ""; });
+ target.style.scrollStartY = "200px";
+ target.style.writingMode = "vertical-lr";
+ assert_equals(getComputedStyle(target).scrollStartBlock, "auto");
+ assert_equals(getComputedStyle(target).scrollStartInline, "200px");
+ }, "scroll-start-y maps to scroll-start-inline in vertical writing mode.");
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-invalid.html b/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-invalid.html
new file mode 100644
index 0000000000..01dbbc4858
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-invalid.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-block with invalid values</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+ <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("scroll-start", "-100px");
+ test_invalid_value("scroll-start", "-50%");
+ test_invalid_value("scroll-start", "invalid_keyword");
+ test_invalid_value("scroll-start", "-100px -50%");
+ test_invalid_value("scroll-start", "-50% invalid_keyword");
+ test_invalid_value("scroll-start", "invalid_keyword invalid_keyword");
+ test_invalid_value("scroll-start", "-100px - 100px");
+ test_invalid_value("scroll-start", "-50% -100px");
+ test_invalid_value("scroll-start", "invalid_keyword -50%");
+ test_invalid_value("scroll-start", "-100px invalid_keyword");
+ test_invalid_value("scroll-start", "-50% -50%");
+ test_invalid_value("scroll-start", "invalid_keyword -100px");
+ test_invalid_value("scroll-start", "100px 200px 300px");
+
+ test_invalid_value("scroll-start-block", "-100px");
+ test_invalid_value("scroll-start-block", "-50%");
+ test_invalid_value("scroll-start-block", "invalid_keyword");
+ test_invalid_value("scroll-start-block", "100px 200px");
+
+ test_invalid_value("scroll-start-inline", "-100px");
+ test_invalid_value("scroll-start-inline", "-50%");
+ test_invalid_value("scroll-start-inline", "invalid_keyword");
+ test_invalid_value("scroll-start-inline", "100px 200px");
+
+ test_invalid_value("scroll-start-x", "-100px");
+ test_invalid_value("scroll-start-x", "-50%");
+ test_invalid_value("scroll-start-x", "invalid_keyword");
+ test_invalid_value("scroll-start-x", "100px 200px");
+
+ test_invalid_value("scroll-start-y", "-100px");
+ test_invalid_value("scroll-start-y", "-50%");
+ test_invalid_value("scroll-start-y", "invalid_keyword");
+ test_invalid_value("scroll-start-y", "100px 200px");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-shorthand.html b/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-shorthand.html
new file mode 100644
index 0000000000..22e206ec62
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-shorthand.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Scroll Snap 2 Test: scroll-start sets longhands</title>
+<link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/shorthand-testcommon.js"></script>
+</head>
+
+<body>
+ <script>
+ test_shorthand_value('scroll-start', '100px', {
+ 'scroll-start-block': '100px',
+ 'scroll-start-inline': 'start',
+ });
+ test_shorthand_value('scroll-start', '50%', {
+ 'scroll-start-block': '50%',
+ 'scroll-start-inline': 'start',
+ });
+ test_shorthand_value('scroll-start', 'center', {
+ 'scroll-start-block': 'center',
+ 'scroll-start-inline': 'start',
+ });
+ test_shorthand_value('scroll-start', '100px 200px', {
+ 'scroll-start-block': '100px',
+ 'scroll-start-inline': '200px',
+ });
+ test_shorthand_value('scroll-start', '100px 50%', {
+ 'scroll-start-block': '100px',
+ 'scroll-start-inline': '50%',
+ });
+ test_shorthand_value('scroll-start', '100px center', {
+ 'scroll-start-block': '100px',
+ 'scroll-start-inline': 'center',
+ });
+ test_shorthand_value('scroll-start', '50% 200px', {
+ 'scroll-start-block': '50%',
+ 'scroll-start-inline': '200px',
+ });
+ test_shorthand_value('scroll-start', '50% 25%', {
+ 'scroll-start-block': '50%',
+ 'scroll-start-inline': '25%',
+ });
+ test_shorthand_value('scroll-start', '50% center', {
+ 'scroll-start-block': '50%',
+ 'scroll-start-inline': 'center',
+ });
+ test_shorthand_value('scroll-start', 'center 200px', {
+ 'scroll-start-block': 'center',
+ 'scroll-start-inline': '200px',
+ });
+ test_shorthand_value('scroll-start', 'center 25%', {
+ 'scroll-start-block': 'center',
+ 'scroll-start-inline': '25%',
+ });
+ test_shorthand_value('scroll-start', 'center end', {
+ 'scroll-start-block': 'center',
+ 'scroll-start-inline': 'end',
+ });
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-target-computed.html b/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-target-computed.html
new file mode 100644
index 0000000000..bb31f2b244
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-target-computed.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-target-* computed values</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+ <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("scroll-start-target-block", "auto");
+ test_computed_value("scroll-start-target-block", "none");
+
+ test_computed_value("scroll-start-target-inline", "auto");
+ test_computed_value("scroll-start-target-inline", "none");
+
+ test_computed_value("scroll-start-target-x", "auto");
+ test_computed_value("scroll-start-target-x", "none");
+
+ test_computed_value("scroll-start-target-y", "auto");
+ test_computed_value("scroll-start-target-y", "none");
+
+ target.style = "";
+
+ // Test logical-physical mapping.
+ test((t) => {
+ t.add_cleanup(() => { target.style = ""; });
+ target.style.scrollStartTargetBlock = "auto";
+ assert_equals(getComputedStyle(target).scrollStartTargetX, "none");
+ assert_equals(getComputedStyle(target).scrollStartTargetY, "auto");
+ }, "scroll-start-block maps to scroll-start-y in horizontal writing mode.");
+ test((t) => {
+ t.add_cleanup(() => { target.style = ""; });
+ target.style.scrollStartTargetInline = "auto";
+ assert_equals(getComputedStyle(target).scrollStartTargetX, "auto");
+ assert_equals(getComputedStyle(target).scrollStartTargetY, "none");
+ }, "scroll-start-inline maps to scroll-start-x in horizontal writing mode.");
+ test((t) => {
+ t.add_cleanup(() => { target.style = ""; });
+ target.style.scrollStartTargetX = "auto";
+ assert_equals(getComputedStyle(target).scrollStartTargetBlock, "none");
+ assert_equals(getComputedStyle(target).scrollStartTargetInline, "auto");
+ }, "scroll-start-x maps to scroll-start-inline in horizontal writing mode.");
+ test((t) => {
+ t.add_cleanup(() => { target.style = ""; });
+ target.style.scrollStartTargetY = "auto";
+ assert_equals(getComputedStyle(target).scrollStartTargetBlock, "auto");
+ assert_equals(getComputedStyle(target).scrollStartTargetInline, "none");
+ }, "scroll-start-y maps to scroll-start-block in horizontal writing mode.");
+ test((t) => {
+ t.add_cleanup(() => { target.style = ""; });
+ target.style.scrollStartTargetBlock = "auto";
+ target.style.writingMode = "vertical-lr";
+ assert_equals(getComputedStyle(target).scrollStartTargetX, "auto");
+ assert_equals(getComputedStyle(target).scrollStartTargetY, "none");
+ }, "scroll-start-block maps to scroll-start-x in vertical writing mode.");
+ test((t) => {
+ t.add_cleanup(() => { target.style = ""; });
+ target.style.scrollStartTargetInline = "auto";
+ target.style.writingMode = "vertical-lr";
+ assert_equals(getComputedStyle(target).scrollStartTargetX, "none");
+ assert_equals(getComputedStyle(target).scrollStartTargetY, "auto");
+ }, "scroll-start-inline maps to scroll-start-y in vertical writing mode.");
+ test((t) => {
+ t.add_cleanup(() => { target.style = ""; });
+ target.style.scrollStartTargetX = "auto";
+ target.style.writingMode = "vertical-lr";
+ assert_equals(getComputedStyle(target).scrollStartTargetBlock, "auto");
+ assert_equals(getComputedStyle(target).scrollStartTargetInline, "none");
+ }, "scroll-start-x maps to scroll-start-block in vertical writing mode.");
+ test((t) => {
+ t.add_cleanup(() => { target.style = ""; });
+ target.style.scrollStartTargetY = "auto";
+ target.style.writingMode = "vertical-lr";
+ assert_equals(getComputedStyle(target).scrollStartTargetBlock, "none");
+ assert_equals(getComputedStyle(target).scrollStartTargetInline, "auto");
+ }, "scroll-start-y maps to scroll-start-inline in vertical writing mode.");
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-target-invalid.html b/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-target-invalid.html
new file mode 100644
index 0000000000..03ca718629
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-target-invalid.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-target-* with invalid values</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+ <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("scroll-start-target", "invalid_keyword");
+ test_invalid_value("scroll-start-target", "100px");
+ test_invalid_value("scroll-start-target", "none none none");
+ test_invalid_value("scroll-start-target", "invalid_keyword1 invalid_keyword2");
+ test_invalid_value("scroll-start-target", "100px 100px");
+
+ test_invalid_value("scroll-start-target-block", "invalid_keyword");
+ test_invalid_value("scroll-start-target-block", "100px");
+ test_invalid_value("scroll-start-target-block", "none none");
+
+ test_invalid_value("scroll-start-target-inline", "invalid_keyword");
+ test_invalid_value("scroll-start-target-inline", "100px");
+ test_invalid_value("scroll-start-target-inline", "none none");
+
+ test_invalid_value("scroll-start-target-x", "invalid_keyword");
+ test_invalid_value("scroll-start-target-x", "100px");
+ test_invalid_value("scroll-start-target-x", "none none");
+
+ test_invalid_value("scroll-start-target-y", "invalid_keyword");
+ test_invalid_value("scroll-start-target-y", "100px");
+ test_invalid_value("scroll-start-target-y", "none none");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-target-shorthand.html b/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-target-shorthand.html
new file mode 100644
index 0000000000..6017157842
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-target-shorthand.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Scroll Snap 2 Test: scroll-none-target sets longhands</title>
+<link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start-target">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/shorthand-testcommon.js"></script>
+</head>
+
+<body>
+ <script>
+ test_shorthand_value("scroll-start-target", "none", {
+ "scroll-start-target-block": "none",
+ "scroll-start-target-inline": "none",
+ });
+ test_shorthand_value("scroll-start-target", "auto", {
+ "scroll-start-target-block": "auto",
+ "scroll-start-target-inline": "none",
+ });
+ test_shorthand_value("scroll-start-target", "none auto", {
+ "scroll-start-target-block": "none",
+ "scroll-start-target-inline": "auto",
+ });
+ test_shorthand_value("scroll-start-target", "auto none", {
+ "scroll-start-target-block": "auto",
+ "scroll-start-target-inline": "none",
+ });
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-target-valid.html b/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-target-valid.html
new file mode 100644
index 0000000000..aed964bdff
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-target-valid.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-target-* with valid values</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+ <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("scroll-start-target", "none");
+ test_valid_value("scroll-start-target", "auto");
+ test_valid_value("scroll-start-target", "none auto");
+ test_valid_value("scroll-start-target", "auto none", "auto");
+
+ test_valid_value("scroll-start-target-block", "none");
+ test_valid_value("scroll-start-target-block", "auto");
+
+ test_valid_value("scroll-start-target-inline", "none");
+ test_valid_value("scroll-start-target-inline", "auto");
+
+ test_valid_value("scroll-start-target-x", "none");
+ test_valid_value("scroll-start-target-x", "auto");
+
+ test_valid_value("scroll-start-target-y", "none");
+ test_valid_value("scroll-start-target-y", "auto");
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-valid.html b/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-valid.html
new file mode 100644
index 0000000000..c472979543
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/parsing/scroll-start-valid.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-block with valid values</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+ <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("scroll-start", "start");
+ test_valid_value("scroll-start", "start start", "start");
+ test_valid_value("scroll-start", "50%");
+ test_valid_value("scroll-start", "center");
+ test_valid_value("scroll-start", "100px 200px");
+ test_valid_value("scroll-start", "50% 100px");
+ test_valid_value("scroll-start", "start 50%");
+ test_valid_value("scroll-start", "center start", "center");
+ test_valid_value("scroll-start", "end center");
+ test_valid_value("scroll-start", "top end");
+ test_valid_value("scroll-start", "bottom top");
+ test_valid_value("scroll-start", "left bottom");
+ test_valid_value("scroll-start", "right left");
+ test_valid_value("scroll-start", "auto right");
+ test_valid_value("scroll-start", "calc(1px) auto");
+
+ test_valid_value("scroll-start-block", "100px");
+ test_valid_value("scroll-start-block", "50%");
+ test_valid_value("scroll-start-block", "start");
+ test_valid_value("scroll-start-block", "center");
+ test_valid_value("scroll-start-block", "end");
+ test_valid_value("scroll-start-block", "top");
+ test_valid_value("scroll-start-block", "bottom");
+ test_valid_value("scroll-start-block", "left");
+ test_valid_value("scroll-start-block", "right");
+ test_valid_value("scroll-start-block", "auto");
+ test_valid_value("scroll-start-block", "calc(-1px)");
+
+ test_valid_value("scroll-start-inline", "100px");
+ test_valid_value("scroll-start-inline", "50%");
+ test_valid_value("scroll-start-inline", "start");
+ test_valid_value("scroll-start-inline", "center");
+ test_valid_value("scroll-start-inline", "end");
+ test_valid_value("scroll-start-inline", "top");
+ test_valid_value("scroll-start-inline", "bottom");
+ test_valid_value("scroll-start-inline", "left");
+ test_valid_value("scroll-start-inline", "right");
+ test_valid_value("scroll-start-inline", "auto");
+ test_valid_value("scroll-start-inline", "calc(-1px)");
+
+ test_valid_value("scroll-start-x", "100px");
+ test_valid_value("scroll-start-x", "50%");
+ test_valid_value("scroll-start-x", "start");
+ test_valid_value("scroll-start-x", "center");
+ test_valid_value("scroll-start-x", "end");
+ test_valid_value("scroll-start-x", "top");
+ test_valid_value("scroll-start-x", "bottom");
+ test_valid_value("scroll-start-x", "left");
+ test_valid_value("scroll-start-x", "right");
+ test_valid_value("scroll-start-x", "auto");
+ test_valid_value("scroll-start-x", "calc(-1px)");
+
+ test_valid_value("scroll-start-y", "100px");
+ test_valid_value("scroll-start-y", "50%");
+ test_valid_value("scroll-start-y", "start");
+ test_valid_value("scroll-start-y", "center");
+ test_valid_value("scroll-start-y", "end");
+ test_valid_value("scroll-start-y", "top");
+ test_valid_value("scroll-start-y", "bottom");
+ test_valid_value("scroll-start-y", "left");
+ test_valid_value("scroll-start-y", "right");
+ test_valid_value("scroll-start-y", "auto");
+ test_valid_value("scroll-start-y", "calc(-1px)");
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/resources/common.js b/testing/web-platform/tests/css/css-scroll-snap-2/resources/common.js
new file mode 100644
index 0000000000..1a2edab90b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/resources/common.js
@@ -0,0 +1,106 @@
+function checkSnapEventSupport(event_type) {
+ if (event_type == "snapchanged") {
+ assert_true(window.onsnapchanged !== undefined, "snapchanged not supported");
+ } else if (event_type == "snapchanging") {
+ assert_true(window.onsnapchanging !== undefined, "snapchanging not supported");
+ } else {
+ assert_unreached(`Unknown snap event type selected: ${event_type}`);
+ }
+}
+
+function assertSnapEvent(evt, expected_ids) {
+ assert_equals(evt.bubbles, false, "snap events don't bubble");
+ assert_false(evt.cancelable, "snap events are not cancelable.");
+ const actual = Array.from(evt.snapTargets, el => el.id).join(",");
+ const expected = expected_ids.join(",");
+ assert_equals(actual, expected, "snap event supplied expected targets");
+}
+
+// This function holds logic intended to be used by tests for scroll snap
+// events.
+// |test_data| should contain:
+// - |scroller|: the snap container being scrolled (or
+// document.scrollingElement)
+// - |scrolling_function|: this function should trigger the desired snap event
+// when executed.
+// - |expected_snap_targets|: a list of element ids which the triggered snap
+// event should supply in SnapEvent.snapTargets.
+// - |expected_scroll_offsets|: the scroll offsets at which the snap container
+// should be after scrolling function has been
+// executed.
+// |event_type|: should be "snapchanged" or "snapchanging".
+async function test_snap_event(test, test_data, event_type) {
+ checkSnapEventSupport(event_type);
+ await waitForScrollReset(test, test_data.scroller);
+
+ let listener = test_data.scroller ==
+ document.scrollingElement ? document : test_data.scroller;
+
+ const event_promise = waitForSnapEvent(listener, event_type);
+ await test_data.scrolling_function();
+ let evt = await event_promise;
+
+ assertSnapEvent(evt, test_data.expected_snap_targets);
+ assert_approx_equals(test_data.scroller.scrollTop,
+ test_data.expected_scroll_offsets.y, 1,
+ "vertical scroll offset mismatch.");
+ assert_approx_equals(test_data.scroller.scrollLeft,
+ test_data.expected_scroll_offsets.x, 1,
+ "horizontal scroll offset mismatch.");
+}
+
+async function test_snapchanged(test, test_data) {
+ await test_snap_event(test, test_data, "snapchanged");
+}
+
+function waitForEventUntil(event_target, event_type, wait_until) {
+ return new Promise(resolve => {
+ let result = null;
+ const listener = (evt) => {
+ result = evt;
+ };
+ event_target.addEventListener(event_type, listener);
+ wait_until.then(() => {
+ event_target.removeEventListener(event_type, listener);
+ resolve(result);
+ });
+ });
+}
+
+function waitForEventsUntil(event_target, event_type, wait_until) {
+ return new Promise(resolve => {
+ let result = [];
+ const listener = (evt) => {
+ result.push(evt);
+ };
+ event_target.addEventListener(event_type, listener);
+ wait_until.then(() => {
+ event_target.removeEventListener(event_type, listener);
+ resolve(result);
+ });
+ });
+}
+
+// Proxy a wait for a snap event. We want to avoid having a test
+// timeout in the event of an expected snap event not firing in a particular
+// test case as that would cause the entire file to fail.
+// Snap events should fire before scrollend, so if a scroll should happen, wait
+// for a scrollend event. Otherwise, just do a rAF-based wait.
+function waitForSnapEvent(event_target, event_type, scroll_happens = true) {
+ return scroll_happens ? waitForEventUntil(event_target, event_type,
+ waitForScrollendEventNoTimeout(event_target))
+ : waitForEventUntil(event_target, event_type,
+ waitForAnimationFrames(2));
+}
+
+function waitForSnapChangedEvent(event_target, scroll_happens = true) {
+ return waitForSnapEvent(event_target, "snapchanged", scroll_happens);
+}
+
+function getScrollbarToScrollerRatio(scroller) {
+ // Ideally we'd subtract the length of the scrollbar thumb from
+ // the dividend but there isn't currently a way to get the
+ // scrollbar thumb length.
+ return scroller.clientHeight /
+ (scroller.scrollHeight - scroller.clientHeight);
+}
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/resources/user-scroll-common.js b/testing/web-platform/tests/css/css-scroll-snap-2/resources/user-scroll-common.js
new file mode 100644
index 0000000000..6587aebd92
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/resources/user-scroll-common.js
@@ -0,0 +1,71 @@
+// Helper functions for snapchanged-on-user-* tests.
+
+// This performs a touch scroll on |scroller| using the coordinates provided
+// in |start_pos| and |end_pos|.
+// It is meant for use in snapchanged & snapchanging tests for triggering snap
+// events when touch scrolling from |start_pos| to |end_pos|.
+function snap_event_touch_scroll_helper(start_pos, end_pos) {
+ return new test_driver.Actions()
+ .addPointer("TestPointer", "touch")
+ .pointerMove(Math.round(start_pos.x), Math.round(start_pos.y))
+ .pointerDown()
+ .addTick()
+ .pause(200)
+ .pointerMove(Math.round(end_pos.x), Math.round(end_pos.y))
+ .addTick()
+ .pointerUp()
+ .send();
+}
+
+// This drags the provided |scroller|'s scrollbar vertically by |drag_amt|.
+// Snap event tests should provide a |drag_amt| that would result in a
+// the desired snap event being triggered.
+const vertical_offset_into_scrollbar = 30;
+function snap_event_scrollbar_drag_helper(scroller, scrollbar_width, drag_amt) {
+ let x, y, bounds;
+ if (scroller == document.scrollingElement) {
+ bounds = document.documentElement.getBoundingClientRect();
+ x = Math.round(window.innerWidth - scrollbar_width / 2);
+ } else {
+ bounds = scroller.getBoundingClientRect();
+ x = Math.round(bounds.right - Math.round(scrollbar_width / 2));
+ }
+ y = Math.round(bounds.top + vertical_offset_into_scrollbar);
+ return new test_driver.Actions()
+ .addPointer('TestPointer', 'mouse')
+ .pointerMove(x, y)
+ .pointerDown()
+ .pointerMove(x, Math.round(y + drag_amt))
+ .addTick()
+ .pointerUp()
+ .send();
+}
+
+// This tests that snap event of type |event_type| don't fire for a user (wheel)
+// scroll that snaps back to the same element. Snap events tests should provide
+// a |delta| small enough that no change in |scroller|'s snap targets occurs at
+// the end of the scroll.
+async function test_no_snap_event(test, scroller, delta, event_type) {
+ const listening_element = scroller == document.scrollingElement
+ ? document : scroller;
+ checkSnapEventSupport(event_type);
+ await waitForScrollReset(test, scroller);
+ await waitForCompositorCommit();
+ let snap_event_promise = waitForSnapEvent(listening_element, event_type);
+ // Set the scroll destination to just a little off (0, 0) top so we snap
+ // back to the top box.
+ await new test_driver.Actions().scroll(0, 0, delta, delta,
+ { origin: scroller }).send();
+ let evt = await snap_event_promise;
+ assert_equals(evt, null, "no snap event since scroller is back to top");
+ assert_equals(scroller.scrollTop, 0, "scroller snaps back to the top");
+ assert_equals(scroller.scrollLeft, 0, "scroller snaps back to the left");
+}
+
+async function test_no_snapchanged(t, scroller, delta) {
+ await test_no_snap_event(t, scroller, delta, "snapchanged");
+}
+
+async function test_no_snapchanging(t, scroller, delta) {
+ await test_no_snap_event(t, scroller, delta, "snapchanging");
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-aligns-with-snap-align.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-aligns-with-snap-align.tentative.html
new file mode 100644
index 0000000000..6b133dea7d
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-aligns-with-snap-align.tentative.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-target*</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start-target">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+ </head>
+ <body>
+ <style>
+ #space {
+ width: 1000px;
+ height: 1000px;
+ border: solid 1px red;
+ }
+ #scroller {
+ width: 400px;
+ height: 400px;
+ overflow: hidden;
+ border: solid 1px blue;
+ position: absolute;
+ }
+ #target {
+ width: 100px;
+ height: 100px;
+ background-color: pink;
+ scroll-start-target: auto auto;
+ position: absolute;
+ top: 400px;
+ left: 400px;
+ }
+ </style>
+ <div id="scroller">
+ <div id="space"></div>
+ <div id="target"></div>
+ </div>
+ <script>
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+
+ assert_equals(scroller.scrollTop, 400,
+ "scroller is vertically scrolled to target");
+ assert_equals(scroller.scrollLeft, 400,
+ "scroller is horizontally scrolled to target");
+
+ target.style.scrollSnapAlign = "center";
+ await waitForCompositorCommit();
+
+ assert_equals(scroller.scrollTop, 250,
+ "scroller is vertically aligned to target's center");
+ assert_equals(scroller.scrollLeft, 250,
+ "scroller is horizontally aligned to target's center");
+
+ target.style.scrollSnapAlign = "end";
+ await waitForCompositorCommit();
+
+ assert_equals(scroller.scrollTop, 100,
+ "scroller is vertically aligned to target's bottom");
+ assert_equals(scroller.scrollLeft, 100,
+ "scroller is horizontally aligned to target's right");
+
+ target.style.scrollSnapAlign = "start";
+ await waitForCompositorCommit();
+
+ assert_equals(scroller.scrollTop, 400,
+ "scroller is vertically aligned to target's top");
+ assert_equals(scroller.scrollLeft, 400,
+ "scroller is horizontally aligned to target's left");
+ }, "scroll-start-target aligns with scroll-snap-align");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-display-toggled.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-display-toggled.tentative.html
new file mode 100644
index 0000000000..527d750267
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-display-toggled.tentative.html
@@ -0,0 +1,125 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-target*</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start-target">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <style>
+ #space-filler {
+ width: 500px;
+ height: 500px;
+ border: solid 1px red;
+ }
+ #outer-container {
+ width: 400px;
+ height: 400px;
+ overflow: scroll;
+ border: solid 2px blue;
+ }
+ #inner-container {
+ top: 20px;
+ left: 20px;
+ width: 300px;
+ height: 300px;
+ overflow: scroll;
+ position: relative;
+ border: solid 2px black;
+ }
+ #target {
+ width: 100px;
+ height: 100px;
+ background-color: pink;
+ scroll-start-target: auto auto;
+ }
+ </style>
+ <div id="outer-container">
+ <div id="inner-container">
+ <div id="space-filler"></div>
+ <div id="target">
+ </div>
+ </div>
+ </div>
+ <script>
+ let outer_scroller = document.getElementById("outer-container");
+ let inner_scroller = document.getElementById("inner-container");
+ let space_filler = document.getElementById("space-filler");
+ let target = document.getElementById("target");
+
+ const target_height = target.getBoundingClientRect().height;
+ const space_filler_height = space_filler.getBoundingClientRect().height;
+ const total_content_height = target_height + space_filler_height;
+
+ async function resetDisplay() {
+ return new Promise((resolve) => {
+ if (getComputedStyle(outer_scroller).display == "block" &&
+ getComputedStyle(inner_scroller).display == "block" &&
+ getComputedStyle(target).display == "block") {
+ resolve();
+ } else {
+ outer_scroller.style.display = "block";
+ inner_scroller.style.display = "block";
+ target.style.display = "block";
+ requestAnimationFrame(async () => {
+ await resetDisplay();
+ resolve();
+ });
+ }
+ });
+ }
+
+ async function waitForDisplay(element, display) {
+ return new Promise((resolve) => {
+ if (getComputedStyle(element).display == display) {
+ resolve();
+ } else {
+ requestAnimationFrame(async () => {
+ await waitForDisplay(element, display);
+ resolve();
+ })
+ }
+ });
+ }
+
+ promise_test(async (t) => {
+ await resetDisplay();
+ let initial_expected_scroll_top =
+ total_content_height - inner_scroller.clientHeight;
+ assert_equals(inner_scroller.scrollTop, initial_expected_scroll_top,
+ "inner-scroller is scrolled to scroll-start-target");
+
+ let display_promise = waitForDisplay(target, "none");
+ target.style.display = "none";
+ await display_promise;
+
+ let final_expected_scroll_top = initial_expected_scroll_top - target_height;
+ assert_equals(inner_scroller.scrollTop, final_expected_scroll_top,
+ "inner scroller is clamped to updated scroll range");
+ }, "display:block scroll-start-target becomes display: none");
+
+ promise_test(async (t) => {
+ await resetDisplay();
+ let initial_expected_scroll_top =
+ total_content_height - inner_scroller.clientHeight;
+ assert_equals(inner_scroller.scrollTop, initial_expected_scroll_top,
+ "inner-scroller is scrolled to scroll-start-target");
+
+ let display_promise = waitForDisplay(target, "none");
+ target.style.display = "none";
+ await display_promise;
+ assert_equals(inner_scroller.scrollTop,
+ initial_expected_scroll_top - target_height,
+ "inner scroller is clamped to updated scroll range");
+
+ display_promise = waitForDisplay(target, "block");
+ target.style.display = "block";
+ await display_promise;
+ assert_equals(inner_scroller.scrollTop, initial_expected_scroll_top,
+ "inner scroller is updated as scroll-start-target reappears");
+ }, "display:none scroll-start-target becomes display: block");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-nested-container.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-nested-container.tentative.html
new file mode 100644
index 0000000000..b84803c941
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-nested-container.tentative.html
@@ -0,0 +1,230 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-target*</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start-target">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-actions.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+ </head>
+ <body>
+ <style>
+ #space-filler {
+ width: 500px;
+ height: 500px;
+ background-color: green;
+ }
+ #outer-container {
+ width: 400px;
+ height: 400px;
+ overflow: scroll;
+ background-color: yellow;
+ }
+ #inner-container {
+ top: 20px;
+ left: 20px;
+ width: 300px;
+ height: 300px;
+ overflow: visible;
+ position: relative;
+ background-color: blue;
+ }
+ #target {
+ width: 100px;
+ height: 100px;
+ background-color: pink;
+ scroll-start-target: auto auto;
+ }
+ </style>
+ <div id="outer-container">
+ <div id="space-filler"></div>
+ <div id="inner-container">
+ <div id="space-filler"></div>
+ <div id="target">
+ </div>
+ </div>
+ </div>
+ <script>
+ let outer_container = document.getElementById("outer-container");
+ let inner_container = document.getElementById("inner-container");
+ let space_filler = document.getElementById("space-filler");
+ let target = document.getElementById("target");
+
+ const inner_scroller_top_offset = 20;
+ const target_height = target.getBoundingClientRect().height;
+ const space_filler_height = space_filler.getBoundingClientRect().height;
+ const inner_content_height = target_height + space_filler_height;
+ const inner_container_height = inner_container.getBoundingClientRect().height;
+
+ async function resetDisplay() {
+ return new Promise((resolve) => {
+ if (getComputedStyle(outer_container).display == "block" &&
+ getComputedStyle(inner_container).display == "block" &&
+ getComputedStyle(target).display == "block") {
+ resolve();
+ } else {
+ outer_container.style.display = "block";
+ inner_container.style.display = "block";
+ target.style.display = "block";
+ requestAnimationFrame(async () => {
+ await resetDisplay();
+ resolve();
+ });
+ }
+ });
+ }
+
+ async function waitForCSSProperty(element, property, value) {
+ return new Promise((resolve) => {
+ if (getComputedStyle(element)[property] == value) {
+ resolve();
+ } else {
+ requestAnimationFrame(async () => {
+ await waitForCSSProperty(element, property, value);
+ resolve();
+ })
+ }
+ });
+ }
+
+ async function waitForDisplay(element, value) {
+ return waitForCSSProperty(element, "display", value);
+ }
+
+ async function waitForOverflow(element, value) {
+ return waitForCSSProperty(element, "overflow", value);
+ }
+
+ let initial_expected_scroll_top = space_filler_height +
+ inner_scroller_top_offset + inner_content_height -
+ outer_container.clientHeight;
+ promise_test(async (t) => {
+ await resetDisplay();
+ assert_equals(outer_container.scrollTop, initial_expected_scroll_top,
+ "outer-container is scrolled to scroll-start-target");
+
+ inner_container.style.display = "none";
+ await waitForDisplay(inner_container, "none");
+
+ assert_equals(outer_container.scrollTop,
+ space_filler_height - outer_container.clientHeight,
+ "outer-container has no content to scroll");
+
+ inner_container.style.display = "block";
+ await waitForDisplay(inner_container, "block");
+
+ assert_equals(outer_container.scrollTop, initial_expected_scroll_top,
+ "outer-scroller is updated as scroll-start-target reappears");
+ }, "display:none scroll-start-target becomes display:block");
+
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ await resetDisplay();
+ assert_equals(outer_container.scrollTop, initial_expected_scroll_top,
+ "outer-container is scrolled to scroll-start-target");
+
+ inner_container.style.overflow = "scroll";
+ await waitForOverflow(inner_container, "scroll");
+
+ // inner-container has become a scroller and should be scrolled to
+ // scroll-start-target.
+ assert_equals(inner_container.scrollTop,
+ inner_content_height - inner_container.clientHeight,
+ "inner-container is fully scrolled to target");
+ // outer-container should be adjusted to its new max scroll offset.
+ const scrollbar_width = outer_container.offsetHeight -
+ outer_container.clientHeight;
+ assert_equals(outer_container.scrollTop,
+ space_filler_height + inner_scroller_top_offset +
+ inner_container_height - outer_container.clientHeight,
+ "outer-container's overflowing content is only its direct " +
+ "children");
+
+ inner_container.style.overflow = "visible";
+ await waitForOverflow(inner_container, "visible");
+
+ assert_equals(inner_container.scrollTop, 0,
+ "inner-container is no longer a scroll container");
+ assert_equals(outer_container.scrollTop, initial_expected_scroll_top,
+ "outer-scroller is the scroll container for target once again");
+ }, "intermediate overflow:visible container becomes overflow:scroll");
+
+ promise_test(async (t) => {
+ // This test verifies that:
+ // 1. when both the child and grandchild are scroll-start-targets, the
+ // grandchild wins/is scrolled to.
+ // 2. if/when the grandchild stops being a scroll-start-target, the
+ // child (inner container) is scrolled to.
+ await waitForCompositorCommit();
+ await resetDisplay();
+ t.add_cleanup(async () => {
+ target.style.scrollStartTarget = "auto auto";
+ await waitForCSSProperty(target, "scroll-start-target", "auto");
+ });
+
+ assert_equals(outer_container.scrollTop, initial_expected_scroll_top,
+ "outer-container is scrolled to scroll-start-target");
+ // Make the inner container a scroll-start-target.
+ inner_container.style.scrollStartTarget = "auto auto";
+ await waitForCSSProperty(inner_container, "scroll-start-target", "auto");
+
+ // The inner container has overflow: visible, so it's not the scroll
+ // container of target.
+ assert_equals(outer_container.scrollTop, initial_expected_scroll_top,
+ "outer-container is still scrolled to inner scroll-start-target");
+
+ // Make target no longer a scroll-start-target. The outer container's
+ // scroll-start-target should now be the inner container.
+ target.style.scrollStartTarget = "none none";
+ await waitForCSSProperty(target, "scroll-start-target", "none");
+ assert_equals(outer_container.scrollTop,
+ space_filler_height + inner_scroller_top_offset,
+ "outer-container is scrolled to inner-container");
+ }, "inner scroll-start-target takes precedence over outer");
+
+ promise_test(async (t) => {
+ // This test verifies that a child which is a scroller, is a
+ // scroll-start-target, and has a scroll-start-target is scrolled to by
+ // its scrolling container, and also scrolls to its own
+ // scroll-start-target.
+ await waitForCompositorCommit();
+ await resetDisplay();
+ t.add_cleanup(async () => {
+ inner_container.style.overflow = "visible";
+ inner_container.style.scrollStartTarget = "none none";
+ await waitForCSSProperty(inner_container, "overflow",
+ "visible");
+ await waitForCSSProperty(inner_container, "scroll-start-target",
+ "none");
+ });
+
+ assert_equals(outer_container.scrollTop, initial_expected_scroll_top,
+ "outer-container is scrolled to scroll-start-target");
+
+ // Make the inner container a scroll-start-target.
+ inner_container.style.scrollStartTarget = "auto auto";
+ await waitForCSSProperty(inner_container, "scroll-start-target", "auto");
+
+ assert_equals(outer_container.scrollTop, initial_expected_scroll_top,
+ "outer-container is still scrolled to inner scroll-start-target");
+
+ // Make the inner container a scroller.
+ inner_container.style.overflow = "scroll";
+ await waitForOverflow(inner_container, "scroll");
+
+ assert_equals(outer_container.scrollTop,
+ space_filler_height + inner_scroller_top_offset +
+ inner_container.offsetHeight - outer_container.clientHeight,
+ "outer-container is scrolled to the inner container");
+ assert_equals(inner_container.scrollTop,
+ space_filler_height + target.offsetHeight -
+ inner_container.clientHeight,
+ "inner-container is scrolled to target");
+ }, "scroll containers can also be scroll-start-targets");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-root.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-root.tentative.html
new file mode 100644
index 0000000000..f2af38bbab
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-root.tentative.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-target*</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start-target">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <style>
+ .spacer {
+ width: 200vw;
+ height: 200vh;
+ }
+
+ .box {
+ position: absolute;
+ width: 60vw;
+ height: 60vh;
+ }
+
+ .top_left {
+ top: 0px;
+ left: 0px;
+ background-color: red;
+ }
+
+ .center {
+ top: 60vh;
+ left: 60vw;
+ background-color: purple;
+ scroll-start-target: auto auto;
+ }
+
+ .bottom_right {
+ top: 120vh;
+ left: 120vw;
+ background-color: yellow;
+ }
+ </style>
+ <div class="spacer"></div>
+ <div class="top_left box" id="top_left_box"></div>
+ <div class="center box" id="centerbox"></div>
+ <div class="bottom_right box"></div>
+ <script>
+ test((t) => {
+ let scroller = document.scrollingElement;
+ let top_left_box = document.getElementById("top_left_box");
+
+ const expected_scroll_top = top_left_box.getBoundingClientRect().height;
+ const expected_scroll_left = top_left_box.getBoundingClientRect().width;
+
+ assert_approx_equals(scroller.scrollTop, expected_scroll_top, 1,
+ "scroll-start-target sets initial vertical scroll position");
+ assert_approx_equals(scroller.scrollLeft, expected_scroll_left, 1,
+ "scroll-start-target sets initial horizontal scroll position");
+ });
+ </script>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-rtl.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-rtl.tentative.html
new file mode 100644
index 0000000000..5a2fa0a93c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-rtl.tentative.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-target*</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start-target">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <style>
+ #scroller {
+ height: 500px;
+ width: 500px;
+ display: block;
+ overflow: scroll;
+ writing-mode: vertical-rl;
+ }
+
+ .box {
+ position: relative;
+ width: 60%;
+ height: 60%;
+ }
+
+ .top_right {
+ top: 0px;
+ left: 0px;
+ background-color: red;
+ }
+
+ .center {
+ top: 60%;
+ background-color: purple;
+ scroll-start-target: auto auto;
+ }
+
+ .bottom_left {
+ top: 120%;
+ background-color: yellow;
+ }
+ </style>
+ <div id="scroller">
+ <div class="top_right box" id="box1"></div>
+ <div class="center box" id="box2"></div>
+ <div class="bottom_left box" id="box3"></div>
+ </div>
+ <script>
+ let initial_expected_scroll_top = box1.getBoundingClientRect().height;
+ let initial_expected_scroll_left = -box1.getBoundingClientRect().width;
+
+ test((t) => {
+ assert_equals(scroller.scrollTop, initial_expected_scroll_top,
+ "scroller is vertically scrolled to scroll-start-target");
+ assert_equals(scroller.scrollLeft, initial_expected_scroll_left,
+ "scroller is horizontally scrolled to scroll-start-target");
+ }, "scroll-start-target reflects vertical rtl writing mode.");
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-anchor-navigation-inner-frame.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-anchor-navigation-inner-frame.html
new file mode 100644
index 0000000000..bea0525ecd
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-anchor-navigation-inner-frame.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+</head>
+
+<body>
+ <style>
+ :root, body {
+ margin: 0px;
+ }
+
+ #spacer {
+ height: 100vh;
+ width: 100px;
+ }
+
+ #top_box {
+ width: 100px;
+ height: 60vh;
+ background-color: red;
+ }
+ #middle_box {
+ width: 100px;
+ height: 60vh;
+ scroll-start-target: auto auto;
+ background-color: purple;
+ }
+ #bottom_box {
+ width: 100px;
+ height: 60vh;
+ background-color: yellow;
+ }
+ </style>
+ <div id="top_box"><a id="anchor_target_link" href="#anchor_target">Anchor Link</a></div>
+ <div id="middle_box"></div>
+ <div id="bottom_box"></div>
+ <div id="spacer"></div>
+ <div id="anchor_target">Anchor Target</div>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-anchor-navigation.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-anchor-navigation.tentative.html
new file mode 100644
index 0000000000..bc5b75f75f
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-anchor-navigation.tentative.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-target interaction with anchor navigation</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start-target">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-actions.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/html/browsers/browsing-the-web/resources/helpers.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+</head>
+
+<body>
+ <iframe id="frame" src="scroll-start-target-with-anchor-navigation-inner-frame.html" onload="runTest()"></iframe>
+ <script>
+ function runTest() {
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ let scroller = frame.contentDocument.scrollingElement;
+ // anchor_target is at the bottom of the frame so the frame should be
+ // fully scrolled down to bring it into view.
+ let anchor_target_scrolltop = scroller.scrollHeight - scroller.clientHeight;
+ let anchor_target_link = frame.contentDocument.getElementById("anchor_target_link");
+
+ // Expect scroll offset of 100px per scroll-start.
+ const scroll_start_target_top = 0.6 * frame.contentWindow.innerHeight;
+ assert_equals(scroller.scrollTop, scroll_start_target_top,
+ "scroll-start-target sets initial scroll offset");
+
+ // Scroll away from start position.
+ scroller.scrollTop = 200;
+ assert_equals(scroller.scrollTop, 200,
+ "scrolled away from scroll-start-target");
+
+ anchor_target_link.click();
+ await waitForHashchange(frame.contentWindow);
+ assert_equals(frame.contentWindow.location.hash, "#anchor_target",
+ "clicking anchor link navigates to target");
+
+ // Expect page to be fully scrolled as anchor_target is at the bottom of
+ // the document.
+ assert_equals(scroller.scrollTop, anchor_target_scrolltop,
+ "anchor navigation sets scroll offset");
+
+ frame.contentWindow.history.back();
+ await waitForHashchange(frame.contentWindow);
+ assert_equals(frame.contentWindow.location.hash, "");
+
+ assert_equals(scroller.scrollTop, 200,
+ "scroller returns to previous scroll position, not " +
+ "scroll-start-target");
+ }, "scroll-start-target does not override anchor navigation.");
+ }
+ </script>
+</body>
+
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-hash-fragment-navigation-inner-frame.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-hash-fragment-navigation-inner-frame.html
new file mode 100644
index 0000000000..9bf77363d3
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-hash-fragment-navigation-inner-frame.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+</head>
+
+<body>
+ <style>
+ :root {
+ margin: 0px;
+ }
+
+ #spacer {
+ height: 100vh;
+ width: 100px;
+ }
+
+ #top_box {
+ width: 100px;
+ height: 60vh;
+ background-color: blue;
+ }
+ #middle_box {
+ width: 100px;
+ height: 60vh;
+ scroll-start-target: auto auto;
+ background-color: purple;
+ }
+ #bottom_box {
+ width: 100px;
+ height: 60vh;
+ background-color: yellow;
+ }
+
+ #fragment_target {
+ width: 100px;
+ height: 100px;
+ background-color: red;
+ }
+ </style>
+ <div id="top_box"></div>
+ <div id="middle_box"></div>
+ <div id="bottom_box"></div>
+ <div id="spacer"></div>
+ <div id="fragment_target">Fragment Target</div>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-hash-fragment-navigation.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-hash-fragment-navigation.tentative.html
new file mode 100644
index 0000000000..2d291c2ef9
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-hash-fragment-navigation.tentative.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-target interaction with fragment-navigation</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start-target">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <iframe id="frame" src="scroll-start-target-with-fragment-navigation-inner-frame.html#fragment_target"
+ onload="runTest()"></iframe>
+ <script>
+ function runTest() {
+ test((t) => {
+ let scroller = frame.contentDocument.scrollingElement;
+ // fragment_target is at the bottom of the frame so the frame should be
+ // fully scrolled down to bring it into view (despite middle_box being
+ // the scroll-start-target).
+ let expected_scroll_top = scroller.scrollHeight - scroller.clientHeight;
+ // The scroll-start-target is just below top_box which has a height of
+ // 60vh.
+ const scroll_start_target_top = 0.6 * frame.contentWindow.innerHeight;
+
+ assert_equals(frame.contentWindow.location.hash, "#fragment_target");
+ assert_not_equals(scroll_start_target_top, expected_scroll_top);
+ assert_equals(frame.contentDocument.scrollingElement.scrollTop,
+ expected_scroll_top);
+ }, "scroll-start-target does not override hash fragment navigation");
+ }
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-scroll-snap.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-scroll-snap.tentative.html
new file mode 100644
index 0000000000..9cb66c01fc
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-scroll-snap.tentative.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-target*</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start-target">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <style>
+ .spacer {
+ width: 1000px;
+ height: 1000px;
+ }
+
+ .scroller {
+ width: 300px;
+ height: 300px;
+ border: solid 1px black;
+ overflow: scroll;
+ margin: 0px;
+ position: absolute;
+ scroll-snap-type: y mandatory;
+ }
+
+ .box {
+ position: absolute;
+ width: 200px;
+ height: 200px;
+ }
+
+ .top_left {
+ top: 0px;
+ left: 0px;
+ background-color: red;
+ }
+
+ .center {
+ top: 200px;
+ left: 200px;
+ background-color: purple;
+ scroll-start-target: auto auto;
+ }
+
+ .bottom_right {
+ top: 400px;
+ left: 400px;
+ background-color: yellow;
+ /* Expect scroller to snap to the top and left border of the bottom right div. */
+ scroll-snap-align: start start;
+ }
+ </style>
+ <div class="scroller" id="scroller">
+ <div class="spacer"></div>
+ <div class="top_left box" id="top_left_box"></div>
+ <div class="center box" id="centerbox"></div>
+ <div class="bottom_right box"></div>
+ </div>
+ <script>
+ test((t) => {
+ let scroller = document.getElementById("scroller");
+ let top_left_box = document.getElementById("top_left_box");
+ let center_box = document.getElementById("center_box");
+
+ const expected_scroll_top = top_left_box.getBoundingClientRect().height +
+ centerbox.getBoundingClientRect().height;
+ const expected_scroll_left = top_left_box.getBoundingClientRect().width;
+ centerbox.getBoundingClientRect().width;
+
+ assert_approx_equals(scroller.scrollTop, expected_scroll_top, 1,
+ "scroll-start-target sets initial vertical scroll position");
+ assert_approx_equals(scroller.scrollLeft, expected_scroll_left, 1,
+ "scroll-start-target sets initial horizontal scroll position");
+ });
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-scroll-start-root.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-scroll-start-root.tentative.html
new file mode 100644
index 0000000000..af99595f25
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-scroll-start-root.tentative.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-target*</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start-target">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <style>
+ :root {
+ scroll-start: end end;
+ }
+
+ .spacer {
+ width: 200vw;
+ height: 200vh;
+ }
+
+ .box {
+ position: absolute;
+ width: 60vw;
+ height: 60vh;
+ }
+
+ .top_left {
+ top: 0px;
+ left: 0px;
+ background-color: red;
+ }
+
+ .center {
+ top: 60vh;
+ left: 60vw;
+ background-color: purple;
+ scroll-start-target: auto auto;
+ }
+
+ .bottom_right {
+ top: 120vh;
+ left: 120vw;
+ background-color: yellow;
+ }
+ </style>
+ <div class="spacer"></div>
+ <div class="top_left box" id="top_left_box"></div>
+ <div class="center box" id="centerbox"></div>
+ <div class="bottom_right box"></div>
+ <script>
+ test((t) => {
+ let scroller = document.scrollingElement;
+ let top_left_box = document.getElementById("top_left_box");
+
+ const expected_scroll_top = top_left_box.getBoundingClientRect().height;
+ const expected_scroll_left = top_left_box.getBoundingClientRect().width;
+
+ assert_approx_equals(scroller.scrollTop, expected_scroll_top, 1,
+ "scroll-start-target sets initial vertical scroll position");
+ assert_approx_equals(scroller.scrollLeft, expected_scroll_left, 1,
+ "scroll-start-target sets initial horizontal scroll position");
+ });
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-scroll-start.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-scroll-start.tentative.html
new file mode 100644
index 0000000000..a37c831288
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-scroll-start.tentative.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-target*</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start-target">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <style>
+ .spacer {
+ width: 1000px;
+ height: 1000px;
+ }
+
+ .scroller {
+ width: 300px;
+ height: 300px;
+ border: solid 1px black;
+ overflow: scroll;
+ margin: 0px;
+ position: absolute;
+ scroll-start: end end;
+ /* This should be overriden by scroll-start-target. */
+ }
+
+ .box {
+ position: absolute;
+ width: 200px;
+ height: 200px;
+ }
+
+ .top_left {
+ top: 0px;
+ left: 0px;
+ background-color: red;
+ }
+
+ .center {
+ top: 200px;
+ left: 200px;
+ background-color: purple;
+ scroll-start-target: auto auto;
+ }
+
+ .bottom_right {
+ top: 400px;
+ left: 400px;
+ background-color: yellow;
+ }
+ </style>
+ <div class="scroller" id="scroller">
+ <div class="spacer"></div>
+ <div class="top_left box" id="top_left_box"></div>
+ <div class="center box" id="centerbox"></div>
+ <div class="bottom_right box"></div>
+ </div>
+ <script>
+ test((t) => {
+ let scroller = document.getElementById("scroller");
+ let top_left_box = document.getElementById("top_left_box");
+
+ const expected_scroll_top = top_left_box.getBoundingClientRect().height;
+ const expected_scroll_left = top_left_box.getBoundingClientRect().width;
+
+ assert_approx_equals(scroller.scrollTop, expected_scroll_top, 1,
+ "scroll-start-target sets initial vertical scroll position");
+ assert_approx_equals(scroller.scrollLeft, expected_scroll_left, 1,
+ "scroll-start-target sets initial horizontal scroll position");
+ });
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-text-fragment-navigation-target.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-text-fragment-navigation-target.html
new file mode 100644
index 0000000000..da53e7a566
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-text-fragment-navigation-target.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+
+<body>
+ <style>
+ :root {
+ margin: 0px;
+ }
+
+ #spacer {
+ height: 100vh;
+ width: 100px;
+ }
+
+ #top_box {
+ width: 100px;
+ height: 60vh;
+ background-color: blue;
+ }
+ #middle_box {
+ width: 100px;
+ height: 60vh;
+ scroll-start-target: auto auto;
+ background-color: purple;
+ }
+ #bottom_box {
+ width: 100px;
+ height: 60vh;
+ background-color: yellow;
+ }
+
+ #fragment_target {
+ width: 100px;
+ height: 100px;
+ background-color: red;
+ }
+ </style>
+ <div id="top_box"></div>
+ <div id="middle_box"></div>
+ <div id="bottom_box"></div>
+ <div id="spacer"></div>
+ <div id="fragment_target">Target</div>
+ <script>
+ function stashResult(key, results) {
+ fetch(`/css/css-scroll-snap-2/scroll-start-target/stash.py?key=${key}`, {
+ method: "POST",
+ body: JSON.stringify(results)
+ }).then(() => {
+ window.close();
+ });
+ }
+ function record() {
+ let scroll_position = "UNKNOWN";
+ // Expect page is scrolled all the way down as the text is at the bottom of
+ // the page.
+ const expected_scroll_top = document.scrollingElement.scrollHeight -
+ document.scrollingElement.clientHeight;
+
+ const scroll_start_target_top = top_box.getBoundingClientRect().height;
+
+ if (document.scrollingElement.scrollTop == scroll_start_target_top) {
+ scroll_position = "AT_SCROLL_START_TARGET";
+ } else if (document.scrollingElement.scrollTop == expected_scroll_top) {
+ scroll_position = "AT_TEXT_FRAGMENT";
+ }
+
+ const result = {
+ scroll_position: scroll_position
+ };
+
+ let key = (new URL(document.location)).searchParams.get("key");
+ stashResult(key, result);
+ }
+
+ window.onload = () => {
+ window.requestAnimationFrame(function () {
+ window.requestAnimationFrame(record);
+ })
+ }
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-text-fragment-navigation.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-text-fragment-navigation.tentative.html
new file mode 100644
index 0000000000..f83ea1a036
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-text-fragment-navigation.tentative.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-target interaction with text-fragment navigation</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start-target">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-actions.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/common/utils.js"></script>
+</head>
+
+<body onload="runTest()">
+ <script>
+ function fetchResult(key, resolve, reject) {
+ fetch(`/css/css-scroll-snap-2/scroll-start-target/stash.py?key=${key}`).then(response => {
+ return response.text();
+ }).then(text => {
+ if (text) {
+ try {
+ let result = JSON.parse(text);
+ resolve(result);
+ } catch (e) {
+ reject();
+ }
+ } else {
+ fetchResult(key, resolve, reject);
+ }
+ });
+ }
+
+ function runTest() {
+ promise_test(t => new Promise(async (resolve, reject) => {
+ let key = token();
+
+ test_driver.bless("Open a URL with a text fragment directive", () => {
+ window.open(`scroll-start-target-with-text-fragment-navigation-target.html?key=${key}#:~:text=Target`, "_blank", "noopener");
+ });
+
+ fetchResult(key, resolve, reject);
+ }).then(result => {
+ assert_equals(result.scroll_position, "AT_TEXT_FRAGMENT");
+ }), "scroll-start doesn't override text fragment navigation");
+ }
+ </script>
+</body>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-user-programmatic-scroll.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-user-programmatic-scroll.tentative.html
new file mode 100644
index 0000000000..2d487e9b85
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target-with-user-programmatic-scroll.tentative.html
@@ -0,0 +1,125 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-target*</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start-target">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-actions.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+</head>
+
+<body>
+ <style>
+ .spacer {
+ width: 1000px;
+ height: 1000px;
+ }
+
+ .scroller {
+ width: 300px;
+ height: 300px;
+ border: solid 1px black;
+ overflow: scroll;
+ margin: 0px;
+ position: absolute;
+ }
+
+ .box {
+ position: absolute;
+ width: 200px;
+ height: 200px;
+ }
+
+ .top_left {
+ top: 0px;
+ left: 0px;
+ background-color: red;
+ }
+
+ .center {
+ top: 200px;
+ left: 200px;
+ background-color: purple;
+ scroll-start-target: auto auto;
+ }
+
+ .bottom_right {
+ top: 400px;
+ left: 400px;
+ background-color: yellow;
+ }
+ </style>
+ <div class="scroller" id="user_scroller">
+ <div class="spacer"></div>
+ <div class="top_left box" id="user_top_left_box"></div>
+ <div class="center box"></div>
+ <div class="bottom_right box"></div>
+ </div>
+ <div class="scroller" id="programmatic_scroller" style="left: 500px">
+ <div class="spacer"></div>
+ <div class="top_left box" id="programmatic_top_left_box"></div>
+ <div class="center box"></div>
+ <div class="bottom_right box"></div>
+ </div>
+ <script>
+ async function user_scroll(scroller, current_offset, target_offset) {
+ return new test_driver.Actions().scroll(0, 0,
+ target_offset.x - current_offset.x,
+ target_offset.y - current_offset.y, { origin: scroller })
+ .send();
+ }
+
+ function programmatic_scroll(scroller, current_offset, target_offset) {
+ scroller.scrollTo(target_offset.x, target_offset.y);
+ }
+
+ async function test_scroll_start_target(test, scroller, msg, scrolling_function) {
+ await waitForCompositorCommit();
+ let top_left_box = document.getElementById("user_top_left_box");
+
+ let expected_scroll_top = top_left_box.getBoundingClientRect().height;
+ let expected_scroll_left = top_left_box.getBoundingClientRect().width;
+
+ assert_approx_equals(scroller.scrollTop, expected_scroll_top, 1,
+ "scroll-start-target sets initial vertical scroll position");
+ assert_approx_equals(scroller.scrollLeft, expected_scroll_left, 1,
+ "scroll-start-target sets initial horizontal scroll position");
+
+ let scrollend_promise = new Promise((resolve) => {
+ scroller.addEventListener("scrollend", resolve);
+ });
+ const current_offset = { x: scroller.scrollLeft, y: scroller.scrollTop };
+ const target_offset = {
+ x: current_offset.x + 100,
+ y: current_offset.y + 100
+ };
+ await scrolling_function(scroller, current_offset, target_offset);
+
+ // Only wait for scrollend if it is supported.
+ if (window.onscrollend == null || window.onscrollend != undefined) {
+ await scrollend_promise;
+ }
+ assert_approx_equals(scroller.scrollTop, target_offset.y, 1,
+ `${msg} (vertical)`);
+ assert_approx_equals(scroller.scrollLeft, target_offset.x, 1,
+ `${msg} (horizontal)`);
+ }
+
+ promise_test(async (t) => {
+ let scroller = document.getElementById("user_scroller");
+ const msg = "user scroll is not overriden in by scroll-start-target";
+ await test_scroll_start_target(t, scroller, msg, user_scroll);
+ }, "scroll-start-target does not override user scroll");
+
+ promise_test(async (t) => {
+ let scroller = document.getElementById("programmatic_scroller");
+ const msg = "programmatic scroll is not overriden in by scroll-start-target";
+ await test_scroll_start_target(t, scroller, msg, programmatic_scroll);
+ }, "scroll-start-target does not override programmatic scroll");
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target.tentative.html
new file mode 100644
index 0000000000..2e679c3739
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/scroll-start-target.tentative.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-target*</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start-target">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <style>
+ .spacer {
+ width: 1000px;
+ height: 1000px;
+ }
+
+ .scroller {
+ width: 300px;
+ height: 300px;
+ border: solid 1px black;
+ overflow: scroll;
+ margin: 0px;
+ position: absolute;
+ }
+
+ .box {
+ position: absolute;
+ width: 200px;
+ height: 200px;
+ }
+
+ .top_left {
+ top: 0px;
+ left: 0px;
+ background-color: red;
+ }
+
+ .target_for_x_and_y {
+ scroll-start-target: auto auto;
+ }
+
+ .target_for_x {
+ scroll-start-target: none auto;
+ }
+
+ .center {
+ top: 200px;
+ left: 200px;
+ background-color: purple;
+ }
+
+ .bottom_right {
+ top: 400px;
+ left: 400px;
+ background-color: yellow;
+ }
+ </style>
+ <div class="scroller" id="scroller1">
+ <div class="spacer"></div>
+ <div class="top_left box" id="top_left_box1"></div>
+ <div class="center box target_for_x_and_y" id="centerbox"></div>
+ <div class="bottom_right box"></div>
+ </div>
+ <div class="scroller" id="scroller2">
+ <div class="spacer"></div>
+ <div class="top_left box" id="top_left_box2"></div>
+ <div class="center box target_for_x" id="centerbox2"></div>
+ <div class="bottom_right box"></div>
+ </div>
+ <script>
+ test((t) => {
+ let scroller = document.getElementById("scroller1");
+ let top_left_box = document.getElementById("top_left_box1");
+
+ const expected_scroll_top = top_left_box.getBoundingClientRect().height;
+ const expected_scroll_left = top_left_box.getBoundingClientRect().width;
+
+ assert_approx_equals(scroller.scrollTop, expected_scroll_top, 1,
+ "scroll-start-target sets initial vertical scroll position");
+ assert_approx_equals(scroller.scrollLeft, expected_scroll_left, 1,
+ "scroll-start-target sets initial horizontal scroll position");
+ });
+ test((t) => {
+ let scroller = document.getElementById("scroller2");
+ let top_left_box = document.getElementById("top_left_box2");
+
+ const expected_scroll_top = 0;
+ const expected_scroll_left = top_left_box.getBoundingClientRect().width;
+
+ assert_approx_equals(scroller.scrollTop, expected_scroll_top, 1,
+ "scroll-start-target sets initial vertical scroll position");
+ assert_approx_equals(scroller.scrollLeft, expected_scroll_left, 1,
+ "scroll-start-target sets initial horizontal scroll position");
+ });
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/stash.py b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/stash.py
new file mode 100644
index 0000000000..3c65e2b59b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start-target/stash.py
@@ -0,0 +1,27 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""
+This file allows the different windows created by
+css/css-scroll-snap-2/scroll-start-target-with-text-fragment-navigation.html
+to store and retrieve data.
+
+scroll-start-target-with-text-fragment-navigation.html (test file) opens a window to
+scroll-start-target-with-text-fragment-navigation-target.html which writes some data
+which the test file will eventually read. This file handles the requests from
+both windows.
+"""
+
+import time
+
+def main(request, response):
+ key = request.GET.first(b"key")
+
+ if request.method == u"POST":
+ # Received result data from target page
+ request.server.stash.put(key, request.body, u'/css/css-scroll-snap-2/scroll-start-target/')
+ return u"ok"
+ else:
+ # Request for result data from test page
+ value = request.server.stash.take(key, u'/css/css-scroll-snap-2/scroll-start-target/')
+ return value
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-display-toggled.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-display-toggled.tentative.html
new file mode 100644
index 0000000000..088c14128e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-display-toggled.tentative.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-*</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <style>
+ #scroller {
+ height: 100px;
+ width: 100px;
+ scroll-start: 100px 200px;
+ border: solid 1px black;
+ overflow: scroll;
+ }
+
+ .spacer {
+ width: 200vw;
+ height: 200vh;
+ border: solid 1px green;
+ }
+ </style>
+ <div id="scroller">
+ <div class="spacer"></div>
+ </div>
+ <script>
+ async function assertScrollPositionResetOnDisplayNone(scroller) {
+ return new Promise((resolve) => {
+ if (getComputedStyle(scroller)["display"] == "none") {
+ assert_equals(scroller.scrollTop, 0, "scrollTop is reset");
+ assert_equals(scroller.scrollLeft, 0, "scrollLeft is reset");
+ resolve();
+ } else {
+ requestAnimationFrame(async () => {
+ await assertScrollPositionResetOnDisplayNone(scroller);
+ resove();
+ });
+ }
+ });
+ }
+ promise_test(async (t) => {
+ // This tests that toggling the display of a scroller from none to block
+ // scroll-start does not reset the scroller's scroll position.
+ assert_equals(scroller.scrollTop, 100,
+ "scroll-start: <length> sets initial vertical scroll position");
+ assert_equals(scroller.scrollLeft, 200,
+ "scroll-start: <length> sets initial horizontal scroll position");
+
+ // Scroll to somewhere other than scroll-start position.
+ scroller.scrollTop = 200;
+ scroller.scrollLeft = 100;
+ assert_equals(scroller.scrollTop, 200,
+ "vertical scroll position is programmatically adjusted");
+ assert_equals(scroller.scrollLeft, 100,
+ "horizontal scroll position is programmatically adjusted");
+
+ scroller.style.display = "none";
+ assert_equals(getComputedStyle(scroller)["display"], "none");
+
+ await assertScrollPositionResetOnDisplayNone(scroller);
+
+ scroller.style.display = "block";
+ assert_equals(getComputedStyle(scroller)["display"], "block");
+
+ // Verify that we are again scrolled to the position specified by scroll-start.
+ assert_equals(scroller.scrollTop, 200,
+ "scroll-start is not applied vertically after display toggle");
+ assert_equals(scroller.scrollLeft, 100,
+ "scroll-start is not applied horizontally after display toggle");
+ }, "scroll-start does not interfer with recovering saved scroll position " +
+ "after display toggle");
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-fieldset.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-fieldset.tentative.html
new file mode 100644
index 0000000000..9a0190506e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-fieldset.tentative.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-*</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <style>
+ .spacer {
+ width: 500px;
+ height: 500px;
+ border: solid 1px green;
+ }
+
+ legend {
+ background-color: #000;
+ color: #fff;
+ padding: 0px 0px;
+ }
+
+ input {
+ margin: 0rem;
+ }
+
+ .scroller {
+ width: 100px;
+ height: 100px;
+ border: solid 1px black;
+ overflow: scroll;
+ padding-block-start: 0px;
+ padding-block-end: 0px;
+ }
+ </style>
+ <fieldset id="scroller" class="scroller">
+ <div class="spacer"></div>
+ </fieldset>
+ <script>
+ let scroller = document.getElementById("scroller");
+ // fieldsets' clientHeight and scrollHeight can be affected by the presence of
+ // a scrollbar which has been anecdotally measured to be 15 on several
+ // platforms.
+ const scrollbar_width = 15;
+ const max_vertical_scroll_offset = scroller.scrollHeight -
+ scroller.clientHeight;
+ // The fieldset's width is set based on the size of its contents:
+ // https://html.spec.whatwg.org/multipage/form-elements.html#the-fieldset-element
+ // "For the purpose of calculating the min-content inline size, use the
+ // greater of the min-content inline size of the rendered legend and the
+ // min-content inline size of the anonymous fieldset content box."
+ // So only bother checking vertical scrolling as the adjusted width might
+ // not permit horizontal scrolling.
+ let test_cases = [
+ {
+ scroll_start: "100px 200px",
+ expectation: {
+ scrollTop: 100,
+ msg: "scroll-start: <length> sets initial scroll position",
+ }
+ },
+ {
+ scroll_start: "25% 75%",
+ expectation: {
+ scrollTop: 0.25 * max_vertical_scroll_offset,
+ msg: "scroll-start: <percent> sets initial scroll position",
+ }
+ },
+ {
+ scroll_start: "calc(50px) calc(75px)",
+ expectation: {
+ scrollTop: 50,
+ msg: "scroll-start: <calc> sets initial scroll position",
+ }
+ },
+ {
+ scroll_start: "start",
+ expectation: {
+ scrollTop: 0,
+ msg: "scroll-start: start sets initial scroll position",
+ }
+ },
+ {
+ scroll_start: "center center",
+ expectation: {
+ scrollTop: 0.5 * max_vertical_scroll_offset,
+ msg: "scroll-start: center sets initial scroll position",
+ }
+ },
+ {
+ scroll_start: "end end",
+ expectation: {
+ scrollTop: max_vertical_scroll_offset,
+ msg: "scroll-start: end sets initial scroll position",
+ }
+ },
+ {
+ scroll_start: "top top",
+ expectation: {
+ scrollTop: 0,
+ msg: "scroll-start: top sets initial scroll position",
+ }
+ },
+ {
+ scroll_start: "bottom bottom",
+ expectation: {
+ scrollTop: max_vertical_scroll_offset,
+ msg: "scroll-start: bottom sets initial scroll position",
+ }
+ },
+ {
+ scroll_start: "1000px 2000px",
+ expectation: {
+ scrollTop: max_vertical_scroll_offset,
+ msg: "scroll-start is clamped",
+ }
+ }
+ ];
+
+ async function resetScroller(scroll_start) {
+ scroller.style.display = "none";
+ assert_equals(getComputedStyle(scroller)["display"], "none");
+
+ scroller.style["scroll-start"] = scroll_start;
+
+ scroller.style.display = "block";
+ assert_equals(getComputedStyle(scroller)["display"], "block");
+ assert_equals(scroller.style.scrollStart, scroll_start);
+ }
+
+ async function test_scroll_start(scroll_start, expectation) {
+ await resetScroller(scroll_start);
+
+ assert_approx_equals(scroller.scrollTop, expectation.scrollTop,
+ scrollbar_width, expectation.msg);
+ }
+
+
+ promise_test(async () => {
+ for (let test_case of test_cases) {
+ await test_scroll_start(test_case.scroll_start,
+ test_case.expectation);
+ }
+ }, "scroll-start sets default position of fieldset element");
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-overflow-toggled.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-overflow-toggled.tentative.html
new file mode 100644
index 0000000000..8829519024
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-overflow-toggled.tentative.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-*</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <style>
+ #scroller {
+ height: 100px;
+ width: 100px;
+ scroll-start: 100px 200px;
+ border: solid 1px black;
+ overflow: scroll;
+ }
+
+ .spacer {
+ width: 200vw;
+ height: 200vh;
+ border: solid 1px green;
+ }
+ </style>
+ <div id="scroller">
+ <div class="spacer"></div>
+ </div>
+ <script>
+ promise_test(async (t) => {
+ // This tests that toggling the overflow of a scroller from visible to
+ // scroll doesn't change the scroll position to scroll-start (since
+ // overflow:visible to overflow:scroll doesn't cause the scroller to be laid
+ // out again.)
+ assert_equals(scroller.scrollTop, 100,
+ "scroll-start: <length> sets initial vertical scroll position");
+ assert_equals(scroller.scrollLeft, 200,
+ "scroll-start: <length> sets initial horizontal scroll position");
+
+ // Scroll to somewhere other than scroll-start position.
+ scroller.scrollTop = 200;
+ scroller.scrollLeft = 100;
+ // Allow for an animation frame that might be needed for the update to take
+ // place.
+ await requestAnimationFrame(() => { });
+ assert_equals(scroller.scrollTop, 200,
+ "vertical scroll position is programmatically adjusted");
+ assert_equals(scroller.scrollLeft, 100,
+ "horizontal scroll position is programmatically adjusted");
+
+ scroller.style.overflow = "visible";
+ assert_equals(getComputedStyle(scroller)["overflow"], "visible");
+ scroller.style.overflow = "scroll";
+ assert_equals(getComputedStyle(scroller)["overflow"], "scroll");
+
+ // Verify that the scroll position is not changed.
+ assert_equals(scroller.scrollTop, 200,
+ "scroll-start does not reset vertical scroll position on overflow " +
+ "toggle.");
+ assert_equals(scroller.scrollLeft, 100,
+ "scroll-start does not reset vertical scroll position on overflow " +
+ "toggle.");
+ }, "scroll-start sets scroller position if overflow is not visible");
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-root.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-root.tentative.html
new file mode 100644
index 0000000000..a74a1131e3
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-root.tentative.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-*</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <style>
+ :root {
+ scroll-start: 10vh 200px;
+ }
+
+ .spacer {
+ width: 200vw;
+ height: 200vh;
+ border: solid 1px green;
+ }
+ </style>
+ <div class="spacer"></div>
+ <script>
+ promise_test(async (t) => {
+ assert_equals(window.scrollX, 200,
+ "scroll-start: <length> sets initial scroll position");
+ assert_equals(window.scrollY, 0.1 * window.innerHeight,
+ "scroll-start: <length> sets initial scroll position");
+ }, "scroll-start sets the initial scroll position of the document");
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-vertical-lr.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-vertical-lr.tentative.html
new file mode 100644
index 0000000000..7ed152fd9a
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-vertical-lr.tentative.html
@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-*</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <style>
+ .spacer {
+ width: 500px;
+ height: 500px;
+ border: solid 1px green;
+ }
+
+ .scroller {
+ width: 100px;
+ height: 100px;
+ border: solid 1px black;
+ overflow: scroll;
+ writing-mode: vertical-lr;
+ }
+ </style>
+ <div id="lengthscroller" class="scroller" style="scroll-start: 100px">
+ <div class="spacer"></div>
+ </div>
+ <div id="percentscroller" class="scroller" style="scroll-start: 25%">
+ <div class="spacer"></div>
+ </div>
+ <div id="calcscroller" class="scroller" style="scroll-start: calc(50px)">
+ <div class="spacer"></div>
+ </div>
+ <div id="startscroller" class="scroller" style="scroll-start: start">
+ <div class="spacer"></div>
+ </div>
+ <div id="centerscroller" class="scroller" style="scroll-start: center">
+ <div class="spacer"></div>
+ </div>
+ <div id="endscroller" class="scroller" style="scroll-start: end">
+ <div class="spacer"></div>
+ </div>
+ <div id="topscroller" class="scroller" style="scroll-start: 100px top">
+ <div class="spacer"></div>
+ </div>
+ <div id="bottomscroller" class="scroller" style="scroll-start: 100px bottom">
+ <div class="spacer"></div>
+ </div>
+ <div id="leftscroller" class="scroller" style="scroll-start: left 100px">
+ <div class="spacer"></div>
+ </div>
+ <div id="rightscroller" class="scroller" style="scroll-start: right 100px">
+ <div class="spacer"></div>
+ </div>
+ <div id="clampedscroller" class="scroller" style="scroll-start: 1000px 1000px">
+ <div class="spacer"></div>
+ </div>
+ <script>
+ promise_test(async (t) => {
+ let length_scroller = document.getElementById("lengthscroller");
+ assert_equals(length_scroller.scrollLeft, 100,
+ "scroll-start: <length> sets initial scroll position");
+
+ let percent_scroller = document.getElementById("percentscroller");
+ const percent_scroll_left = 0.25 * (percent_scroller.scrollWidth -
+ percent_scroller.clientWidth);
+ assert_approx_equals(percent_scroller.scrollLeft, percent_scroll_left, 1,
+ "scroll-start: <percent> sets initial scroll position");
+
+ let calc_scroller = document.getElementById("calcscroller");
+ assert_equals(calc_scroller.scrollLeft, 50,
+ "scroll-start: <calc> sets initial scroll position");
+
+ let start_scroller = document.getElementById("startscroller");
+ assert_equals(start_scroller.scrollLeft, 0,
+ "scroll-start: start sets initial scroll position");
+
+ let center_scroller = document.getElementById("centerscroller");
+ const center_scroll_top = 0.5 * (center_scroller.scrollHeight -
+ center_scroller.clientHeight);
+ assert_approx_equals(center_scroller.scrollLeft, center_scroll_top, 1,
+ "scroll-start: center sets initial scroll position");
+
+ let end_scroller = document.getElementById("endscroller");
+ const end_scroll_top = end_scroller.scrollWidth -
+ end_scroller.clientWidth;
+ assert_equals(end_scroller.scrollLeft, end_scroll_top,
+ "scroll-start: end sets initial scroll position");
+
+ let top_scroller = document.getElementById("topscroller");
+ assert_equals(top_scroller.scrollLeft, 100,
+ "scroll-start: top sets initial scroll position");
+ assert_equals(top_scroller.scrollTop, 0,
+ "scroll-start: top sets initial scroll position");
+
+ let bottom_scroller = document.getElementById("bottomscroller");
+ const bottom_scroll_top = bottom_scroller.scrollHeight -
+ bottom_scroller.clientHeight;
+ assert_equals(bottom_scroller.scrollLeft, 100,
+ "scroll-start: bottom sets initial scroll position");
+ assert_equals(bottom_scroller.scrollTop, bottom_scroll_top,
+ "scroll-start: top sets initial scroll position");
+
+ let left_scroller = document.getElementById("leftscroller");
+ assert_equals(left_scroller.scrollTop, 100,
+ "scroll-start: left sets initial scroll position");
+ assert_equals(left_scroller.scrollLeft, 0,
+ "scroll-start: left sets initial scroll position");
+
+ let right_scroller = document.getElementById("rightscroller");
+ const right_scroll_top = right_scroller.scrollWidth -
+ right_scroller.clientWidth;
+ assert_equals(right_scroller.scrollTop, 100,
+ "scroll-start: right sets initial scroll position");
+ assert_equals(right_scroller.scrollLeft, right_scroll_top,
+ "scroll-start: right sets initial scroll position");
+
+ let clamped_scroller = document.getElementById("clampedscroller");
+ const clamped_scroll_top = clamped_scroller.scrollWidth -
+ clamped_scroller.clientWidth;
+ const clamped_scroll_left = clamped_scroller.scrollHeight -
+ clamped_scroller.clientHeight;
+ assert_equals(clamped_scroller.scrollTop, clamped_scroll_top,
+ "scroll-start is clamped to max vertical scroll offset");
+ assert_equals(clamped_scroller.scrollLeft, clamped_scroll_left,
+ "scroll-start is clamped to max horizontal scroll offset");
+ }, "scroll-start sets initial scroll offset correctly in vertical " +
+ "writing modes");
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-anchor-navigation-inner-frame.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-anchor-navigation-inner-frame.html
new file mode 100644
index 0000000000..c32bac913d
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-anchor-navigation-inner-frame.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+</head>
+
+<body>
+ <style>
+ :root {
+ margin: 0px;
+ scroll-start: 100px;
+ }
+
+ #spacer {
+ height: 100vh;
+ width: 100vw;
+ }
+
+ #scroll_start_target {
+ width: 100px;
+ height: 100px;
+ background-color: blue;
+ }
+
+ #anchor_target {
+ width: 100px;
+ height: 100px;
+ background-color: red;
+ }
+ </style>
+ <a id="anchor_target_link" href="#anchor_target"></a>
+ <div id="spacer"></div>
+ <div id="scroll_start_target"></div>
+ <div id="anchor_target">
+ </div>
+</body>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-anchor-navigation.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-anchor-navigation.tentative.html
new file mode 100644
index 0000000000..ff5c979391
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-anchor-navigation.tentative.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start interaction with anchor navigation</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/html/browsers/browsing-the-web/resources/helpers.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+</head>
+
+<body>
+ <iframe id="frame" src="scroll-start-with-anchor-navigation-inner-frame.html" onload="runTest()"></iframe>
+ <script>
+ function runTest() {
+ promise_test(async (t) => {
+ let scroller = frame.contentDocument.scrollingElement;
+ // anchor_target is at the bottom of the frame so the frame should be
+ // fully scrolled down to bring it into view.
+ let anchor_target_scrolltop = scroller.scrollHeight - scroller.clientHeight;
+ let anchor_target_link = frame.contentDocument.getElementById("anchor_target_link");
+
+ // Expect scroll offset of 100px per scroll-start.
+ assert_equals(scroller.scrollTop, 100,
+ "scroll-start sets initial scroll offset");
+
+ // Scroll away from start position.
+ scroller.scrollTop = 200;
+ await waitForCompositorCommit();
+ assert_equals(scroller.scrollTop, 200,
+ "scroll-start sets initial scroll offset");
+
+ anchor_target_link.click();
+ await waitForHashchange(frame.contentWindow);
+ assert_equals(frame.contentWindow.location.hash, "#anchor_target",
+ "clicking anchor link navigates to target");
+
+ // Expect page to be fully scrolled as anchor_target is at the bottom of
+ // the document.
+ assert_equals(scroller.scrollTop, anchor_target_scrolltop,
+ "anchor navigation sets scroll offset");
+
+ frame.contentWindow.history.back();
+ await waitForHashchange(frame.contentWindow);
+ assert_equals(frame.contentWindow.location.hash, "");
+
+ scroller = frame.contentDocument.scrollingElement;
+ assert_equals(scroller.scrollTop, 200,
+ "scroller returns to previous scroll position, not " +
+ "scroll-start");
+ }, "scroll-start does not override anchor navigation.");
+ }
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-fragment-navigation-inner-frame.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-fragment-navigation-inner-frame.html
new file mode 100644
index 0000000000..736a26a5f0
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-fragment-navigation-inner-frame.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+</head>
+
+<body>
+ <style>
+ :root {
+ margin: 0px;
+ scroll-start: 100px;
+ }
+
+ #spacer {
+ height: 100vh;
+ width: 100vw;
+ }
+
+ #box {
+ width: 100px;
+ height: 100px;
+ background-color: blue;
+ }
+
+ #fragment_target {
+ width: 100px;
+ height: 100px;
+ background-color: red;
+ }
+ </style>
+ <div id="spacer"></div>
+ <div id="box"></div>
+ <div id="fragment_target"></div>
+</body>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-fragment-navigation.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-fragment-navigation.tentative.html
new file mode 100644
index 0000000000..6e7730b0dc
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-fragment-navigation.tentative.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start interaction with fragment-navigation</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <iframe id="frame" src="scroll-start-with-fragment-navigation-inner-frame.html#fragment_target"
+ onload="runTest()"></iframe>
+ <script>
+ function runTest() {
+ test((t) => {
+ let scroller = frame.contentDocument.scrollingElement;
+ // fragment_target is at the bottom of the frame so the frame should be
+ // fully scrolled down to bring it into view.
+ let expected_scroll_top = scroller.scrollHeight - scroller.clientHeight;
+
+ assert_equals(frame.contentWindow.location.hash, "#fragment_target");
+ assert_not_equals(100, expected_scroll_top);
+ assert_equals(frame.contentDocument.scrollingElement.scrollTop,
+ expected_scroll_top);
+ }, "scroll-start does not override hash fragment navigation");
+ }
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-programmatic-scroll.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-programmatic-scroll.tentative.html
new file mode 100644
index 0000000000..c10746f854
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-programmatic-scroll.tentative.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-*</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body onload="runTest()">
+ <style>
+ #scroller {
+ height: 100px;
+ width: 100px;
+ overflow: scroll;
+ scroll-start: 10vh 200px;
+ }
+
+ .spacer {
+ width: 200vw;
+ height: 200vh;
+ border: solid 1px green;
+ }
+ </style>
+ <div id="scroller">
+ <div class="spacer" id="spacer"></div>
+ </div>
+ <script>
+ function runTest() {
+ // scroll position declared by scroll-start.
+ const scroll_start_top = 0.1 * window.innerHeight;
+ const scroll_start_left = 200;
+
+ // target position of the programmatic scroll.
+ const target_scroll_top = 100;
+ const target_scroll_left = 100;
+
+ promise_test(async (t) => {
+ // verify that we are starting from the offsets indicated by scroll start.
+ assert_equals(scroller.scrollTop, scroll_start_top,
+ "scroll-start: <length> sets initial scroll position vertically");
+ assert_equals(scroller.scrollLeft, scroll_start_left,
+ "scroll-start: <length> sets initial scroll position horizontally");
+
+ // verify that the programmatic scroll should result in an actual scroll.
+ assert_not_equals(target_scroll_top, scroll_start_top,
+ "programmatic scroll should not be a nop vertically");
+ assert_not_equals(target_scroll_left, scroll_start_left,
+ "programmatic scroll should not be a nop horizontally");
+
+ scroller.scrollTop = target_scroll_top;
+ scroller.scrollLeft = target_scroll_left;
+ // verify that programmtic scroll succeeded.
+ assert_equals(scroller.scrollTop, target_scroll_top,
+ "programmatic scroll succeeds vertically");
+ assert_equals(scroller.scrollLeft, target_scroll_left,
+ "programmatic scroll succeeds horizontally");
+
+ // Trigger a layout change.
+ scroller.style.height = "200px";
+ scroller.style.width = "200px";
+ let spacer = document.getElementById("spacer");
+ spacer.style.height = "300vh";
+ spacer.style.width = "300vw";
+ assert_equals(getComputedStyle(spacer)["height"],
+ `${3 * window.innerHeight}px`);
+ assert_equals(getComputedStyle(spacer)["width"],
+ `${3 * window.innerWidth}px`);
+
+ // Verify that the layout change did not affect the scroll position.
+ assert_equals(scroller.scrollTop, target_scroll_top,
+ "layout change after programmatic scroll doesn't apply scroll-start " +
+ "vertically");
+ assert_equals(scroller.scrollLeft, target_scroll_left,
+ "layout change after programmatic scroll doesn't apply scroll-start " +
+ "horizontally");
+ }, "scroll-start is not applied after a programmatic scroll");
+ }
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-scroll-snap.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-scroll-snap.tentative.html
new file mode 100644
index 0000000000..8bdac300ba
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-scroll-snap.tentative.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start interaction with scroll-snap</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+
+<body>
+ <style>
+ body {
+ margin: 0px;
+ }
+
+ .spacer {
+ height: 200px;
+ width: 100px;
+ }
+
+ .scroller {
+ height: 220px;
+ width: 100px;
+ overflow: scroll;
+ scroll-snap-type: both mandatory;
+ }
+
+ #single_snap_scroller {
+ scroll-start: 100%;
+ }
+ #multi_snap_scroller {
+ scroll-start: 350px;
+ }
+
+ .snap_point {
+ width: 100px;
+ height: 200px;
+ scroll-snap-align: start;
+ }
+ </style>
+ <div id="single_snap_scroller" class="scroller">
+ <div id="top_spacer" class="spacer"></div>
+ <div id="lone_snap_point" class="snap_point"></div>
+ <div id="bottom_spacer" class="spacer"></div>
+ </div>
+ <div id="multi_snap_scroller" class="scroller">
+ <div id="snap_point_1" class="snap_point"></div>
+ <div id="snap_point_2" class="snap_point"></div>
+ <div id="snap_point_3" class="snap_point"></div>
+ </div>
+ <script>
+ test((t) => {
+ assert_equals(single_snap_scroller.scrollTop,
+ top_spacer.getBoundingClientRect().height,
+ "scroller snaps to top of snap point");
+ }, "snap overrides scroll-start position");
+
+ test((t) => {
+ // scroll-start sets the initial scroll offset to 350px which is closer to
+ // the third snap point than the second, so the scroller should snap to
+ // the third snap_point.
+ assert_equals(multi_snap_scroller.scrollTop,
+ multi_snap_scroller.scrollHeight - multi_snap_scroller.clientHeight,
+ "scroller snaps to snap point closer to start position.");
+ }, "scroller snaps based on scroll-start position");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-text-fragment-navigation-target.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-text-fragment-navigation-target.html
new file mode 100644
index 0000000000..5537d47fb5
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-text-fragment-navigation-target.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+</head>
+
+<body>
+ <div id="spacer"></div>
+ <div id="box"></div>
+ <div id="text_fragment_target">
+ <p>Target</p>
+ </div>
+ <style>
+ :root {
+ margin: 0px;
+ scroll-start: 100px;
+ }
+
+ #spacer {
+ height: 100vh;
+ width: 100vw;
+ }
+
+ #box {
+ width: 100px;
+ height: 100px;
+ background-color: blue;
+ }
+
+ #fragment_target {
+ width: 100px;
+ height: 100px;
+ background-color: red;
+ }
+ </style>
+ <script>
+ function stashResult(key, results) {
+ fetch(`/css/css-scroll-snap-2/scroll-start/stash.py?key=${key}`, {
+ method: "POST",
+ body: JSON.stringify(results)
+ }).then(() => {
+ window.close();
+ });
+ }
+ function record() {
+ let scroll_position = "UNKNOWN";
+ // Expect page is scrolled all the way down as the text is at the bottom of
+ // the page.
+ const expected_scroll_top = document.scrollingElement.scrollHeight -
+ document.scrollingElement.clientHeight;
+
+ if (document.scrollingElement.scrollTop == 100) {
+ scroll_position = "AT_SCROLL_START";
+ } else if (document.scrollingElement.scrollTop == expected_scroll_top) {
+ scroll_position = "AT_TEXT_FRAGMENT";
+ }
+
+ const result = {
+ scroll_position: scroll_position
+ };
+
+ let key = (new URL(document.location)).searchParams.get("key");
+ stashResult(key, result);
+ }
+
+ window.onload = () => {
+ window.requestAnimationFrame(function () {
+ window.requestAnimationFrame(record);
+ })
+ }
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-text-fragment-navigation.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-text-fragment-navigation.tentative.html
new file mode 100644
index 0000000000..7348c39501
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-text-fragment-navigation.tentative.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start interaction with text-fragment navigation</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-actions.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/common/utils.js"></script>
+</head>
+
+<body onload="runTest()">
+ <script>
+ function fetchResult(key, resolve, reject) {
+ fetch(`/css/css-scroll-snap-2/scroll-start/stash.py?key=${key}`).then(response => {
+ return response.text();
+ }).then(text => {
+ if (text) {
+ try {
+ let result = JSON.parse(text);
+ resolve(result);
+ } catch (e) {
+ reject();
+ }
+ } else {
+ fetchResult(key, resolve, reject);
+ }
+ });
+ }
+
+ function runTest() {
+ promise_test(t => new Promise(async (resolve, reject) => {
+ let key = token();
+
+ test_driver.bless("Open a URL with a text fragment directive", () => {
+ window.open(`scroll-start-with-text-fragment-navigation-target.html?key=${key}#:~:text=Target`, "_blank", "noopener");
+ });
+
+ fetchResult(key, resolve, reject);
+ }).then(result => {
+ assert_equals(result.scroll_position, "AT_TEXT_FRAGMENT");
+ }), "scroll-start doesn't override text fragment navigation");
+ }
+ </script>
+</body>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-user-scroll.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-user-scroll.tentative.html
new file mode 100644
index 0000000000..c122a6ef09
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start-with-user-scroll.tentative.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-*</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-actions.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+</head>
+
+<body onload="runTest()">
+ <style>
+ #scroller {
+ height: 100px;
+ width: 100px;
+ overflow: scroll;
+ scroll-start: 10vh 200px;
+ }
+
+ .spacer {
+ width: 200vw;
+ height: 200vh;
+ border: solid 1px green;
+ }
+ </style>
+ <script>
+ function runTest() {
+ // scroll position declared by scroll-start.
+ const scroll_start_top = 0.1 * window.innerHeight;
+ const scroll_start_left = 200;
+
+ // target position of the user scroll.
+ const target_scroll_delta = 100;
+ const target_scroll_top = scroll_start_top + target_scroll_delta;
+ const target_scroll_left = scroll_start_left + target_scroll_delta;
+
+ promise_test(async (t) => {
+ // verify that we are starting from the offsets indicated by scroll start.
+ assert_equals(scroller.scrollTop, scroll_start_top,
+ "scroll-start: <length> sets initial scroll position vertically");
+ assert_equals(scroller.scrollLeft, scroll_start_left,
+ "scroll-start: <length> sets initial scroll position horizontally");
+
+ // verify that the user scroll should result in an actual scroll.
+ assert_not_equals(target_scroll_top, scroll_start_top,
+ "user scroll should not be nop vertically");
+ assert_not_equals(target_scroll_left, scroll_start_left,
+ "user scroll should not be nop horizontally");
+
+ let scrollend_promise = new Promise((resolve) => {
+ scroller.onscrollend = () => { resolve(); }
+ });
+ await new test_driver.Actions().scroll(0, 0,
+ target_scroll_delta,
+ target_scroll_delta,
+ { origin: scroller }).send();
+
+ await scrollend_promise;
+ assert_equals(scroller.scrollTop, target_scroll_top,
+ "user scroll succeeds vertically");
+ assert_equals(scroller.scrollLeft, target_scroll_left,
+ "user scroll succeeds horizontally");
+
+ // Trigger a layout change.
+ scroller.style.height = "200px";
+ scroller.style.width = "200px";
+ let spacer = document.getElementById("spacer");
+ spacer.style.height = "300vh";
+ spacer.style.width = "300vw";
+ assert_equals(getComputedStyle(spacer)["height"],
+ `${3 * window.innerHeight}px`);
+ assert_equals(getComputedStyle(spacer)["width"],
+ `${3 * window.innerWidth}px`);
+ // Verify that the layout change did not affect the scroll position.
+ assert_equals(scroller.scrollTop, target_scroll_top,
+ "layout change after user scroll does not apply scroll-start " +
+ "vertically");
+ assert_equals(scroller.scrollLeft, target_scroll_left,
+ "layout change after user scroll does not apply scroll-start " +
+ "horizontally");
+ }, "scroll-start is not applied after user a scroll");
+ }
+ </script>
+ <div id="scroller">
+ <div class="spacer" id="spacer"></div>
+ </div>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start.tentative.html
new file mode 100644
index 0000000000..a35c612d7f
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/scroll-start.tentative.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: scroll-start-*</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#scroll-start">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <style>
+ .spacer {
+ width: 500px;
+ height: 500px;
+ border: solid 1px green;
+ }
+
+ .scroller {
+ width: 100px;
+ height: 100px;
+ border: solid 1px black;
+ overflow: scroll;
+ }
+ </style>
+ <div id="lengthscroller" class="scroller" style="scroll-start: 100px">
+ <div class="spacer"></div>
+ </div>
+ <div id="percentscroller" class="scroller" style="scroll-start: 25%">
+ <div class="spacer"></div>
+ </div>
+ <div id="calcscroller" class="scroller" style="scroll-start: calc(50px)">
+ <div class="spacer"></div>
+ </div>
+ <div id="startscroller" class="scroller" style="scroll-start: start">
+ <div class="spacer"></div>
+ </div>
+ <div id="centerscroller" class="scroller" style="scroll-start: center">
+ <div class="spacer"></div>
+ </div>
+ <div id="endscroller" class="scroller" style="scroll-start: end">
+ <div class="spacer"></div>
+ </div>
+ <div id="topscroller" class="scroller" style="scroll-start: top">
+ <div class="spacer"></div>
+ </div>
+ <div id="bottomscroller" class="scroller" style="scroll-start: bottom">
+ <div class="spacer"></div>
+ </div>
+ <div id="leftscroller" class="scroller" style="scroll-start: auto left">
+ <div class="spacer"></div>
+ </div>
+ <div id="rightscroller" class="scroller" style="scroll-start: auto right">
+ <div class="spacer"></div>
+ </div>
+ <div id="clampedscroller" class="scroller" style="scroll-start: 1000px 1000px">
+ <div class="spacer"></div>
+ </div>
+ <script>promise_test(async (t) => {
+ let length_scroller = document.getElementById("lengthscroller");
+ assert_equals(length_scroller.scrollTop, 100,
+ "scroll-start: <length> sets initial scroll position");
+
+ let percent_scroller = document.getElementById("percentscroller");
+ const percent_scroll_top = 0.25 * (percent_scroller.scrollHeight -
+ percent_scroller.clientHeight);
+ assert_approx_equals(percent_scroller.scrollTop, percent_scroll_top, 1,
+ "scroll-start: <percent> sets initial scroll position");
+
+ let calc_scroller = document.getElementById("calcscroller");
+ assert_equals(calc_scroller.scrollTop, 50,
+ "scroll-start: <calc> sets initial scroll position");
+
+ let start_scroller = document.getElementById("startscroller");
+ assert_equals(start_scroller.scrollTop, 0,
+ "scroll-start: start sets initial scroll position");
+
+ let center_scroller = document.getElementById("centerscroller");
+ const center_scroll_top = 0.5 * (center_scroller.scrollHeight -
+ center_scroller.clientHeight);
+ assert_approx_equals(center_scroller.scrollTop, center_scroll_top, 1,
+ "scroll-start: center sets initial scroll position");
+
+ let end_scroller = document.getElementById("endscroller");
+ const end_scroll_top = end_scroller.scrollHeight -
+ end_scroller.clientHeight;
+ assert_equals(end_scroller.scrollTop, end_scroll_top,
+ "scroll-start: end sets initial scroll position");
+
+ let top_scroller = document.getElementById("topscroller");
+ assert_equals(top_scroller.scrollTop, 0,
+ "scroll-start: top sets initial scroll position");
+
+ let bottom_scroller = document.getElementById("bottomscroller");
+ const bottom_scroll_top = bottom_scroller.scrollHeight -
+ bottom_scroller.clientHeight;
+ assert_equals(bottom_scroller.scrollTop, bottom_scroll_top,
+ "scroll-start: bottom sets initial scroll position");
+
+ let left_scroller = document.getElementById("leftscroller");
+ assert_equals(left_scroller.scrollLeft, 0,
+ "scroll-start: left sets initial scroll position");
+
+ let right_scroller = document.getElementById("rightscroller");
+ const right_scroll_top = right_scroller.scrollWidth -
+ right_scroller.clientWidth;
+ assert_equals(right_scroller.scrollLeft, right_scroll_top,
+ "scroll-start: right sets initial scroll position");
+
+ let clamped_scroller = document.getElementById("clampedscroller");
+ const clamped_scroll_top = clamped_scroller.scrollHeight -
+ clamped_scroller.clientHeight;
+ const clamped_scroll_left = clamped_scroller.scrollWidth -
+ clamped_scroller.clientWidth;
+ assert_equals(clamped_scroller.scrollTop, clamped_scroll_top,
+ "scroll-start is clamped to max vertical scroll offset");
+ assert_equals(clamped_scroller.scrollLeft, clamped_scroll_left,
+ "scroll-start is clamped to max horizontal scroll offset");
+ });
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/stash.py b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/stash.py
new file mode 100644
index 0000000000..13bb0e91ba
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/scroll-start/stash.py
@@ -0,0 +1,27 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""
+This file allows the different windows created by
+css/css-scroll-snap-2/scroll-start/scroll-start-with-text-fragment-navigation.html
+to store and retrieve data.
+
+scroll-start-with-text-fragment-navigation.html (test file) opens a window to
+scroll-start-with-text-fragment-navigation-target.html which writes some data
+which the test file will eventually read. This file handles the requests from
+both windows.
+"""
+
+import time
+
+def main(request, response):
+ key = request.GET.first(b"key")
+
+ if request.method == u"POST":
+ # Received result data from target page
+ request.server.stash.put(key, request.body, u'/css/css-scroll-snap-2/scroll-start')
+ return u"ok"
+ else:
+ # Request for result data from test page
+ value = request.server.stash.take(key, u'/css/css-scroll-snap-2/scroll-start')
+ return value
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-after-layout-change.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-after-layout-change.tentative.html
new file mode 100644
index 0000000000..293400edda
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-after-layout-change.tentative.html
@@ -0,0 +1,145 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#snap-events" />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+ <script src="/css/css-scroll-snap-2/resources/common.js"></script>
+ <script src="/web-animations/testcommon.js"></script>
+</head>
+
+<body>
+ <style>
+ #scroller {
+ overflow: scroll;
+ scroll-snap-type: y mandatory;
+ height: 200px;
+ width: 200px;
+ border: solid 1px;
+ position: absolute;
+ }
+
+ .snap_area {
+ position: absolute;
+ width: 100px;
+ left: calc(50% - 50px);
+ }
+
+ #outer_snap_area {
+ scroll-snap-align: start none;
+ height: 1000px;
+ background-color: blue;
+ }
+
+ #inner_snap_area {
+ scroll-snap-align: start none;
+ height: 100px;
+ top: 100px;
+ background-color: yellow;
+ }
+ </style>
+ <div id="scroller">
+ <div id="outer_snap_area" class="snap_area"></div>
+ <div id="inner_snap_area" class="snap_area"></div>
+ </div>
+ <script>
+ let scroller = document.getElementById("scroller");
+
+ async function reset(t) {
+ inner_snap_area.style.height = "100px";
+ inner_snap_area.style.scrollSnapAlign = "start none";
+ outer_snap_area.style.scrollSnapAlign = "start none";
+ scroller.style.scrollSnapType = "y mandatory";
+ await resetTargetScrollState(t, scroller);
+ }
+
+ async function setup(t) {
+ checkSnapEventSupport("snapchanged");
+ await reset(t);
+ await waitForCompositorCommit();
+ assert_equals(scroller.scrollTop, 0, "test precondition: scroller " +
+ "is not scrolled.");
+ }
+
+ promise_test(async (t) => {
+ await setup(t);
+ let target_snap_position = inner_snap_area.offsetTop +
+ inner_snap_area.offsetHeight;
+ // Scroll to an offset close to the bottom of the inner snap area and
+ // expect to snap to an offset just below this snap area.
+ let scrollend_promise = waitForScrollendEventNoTimeout(scroller);
+ scroller.scrollTo(0, target_snap_position - 10);
+ await scrollend_promise;
+ assert_equals(scroller.scrollTop, target_snap_position,
+ "scroller snaps to just below the inner snap area.");
+ // We are just below the inner snap area. Increase its height so that it
+ // is larger than the snapport and straddled by the snapport. Verify
+ // that we snap to its bottom.
+ let snapchanged_promise = waitForSnapChangedEvent(scroller);
+ inner_snap_area.style.height =
+ `${scroller.clientHeight + inner_snap_area.clientHeight - 10}px`;
+ const evt = await snapchanged_promise;
+ assertSnapEvent(evt, [outer_snap_area.id, inner_snap_area.id]);
+ target_snap_position = inner_snap_area.offsetTop +
+ inner_snap_area.offsetHeight - scroller.clientHeight;
+ assert_equals(scroller.scrollTop, target_snap_position,
+ "scroller snaps to the bottom of the smaller snap area (which is " +
+ "now covering).");
+ }, "snapchanged fires after snap area is snapped to upon layout change.");
+
+ promise_test(async (t) => {
+ await setup(t);
+ let target_snap_position = inner_snap_area.offsetTop +
+ inner_snap_area.offsetHeight;
+ // Scroll to an offset close to the bottom of the inner snap area and
+ // expect to snap to an offset just below this snap area.
+ let scrollend_promise = waitForScrollendEventNoTimeout(scroller);
+ scroller.scrollTo(0, target_snap_position - 10);
+ await scrollend_promise;
+ assert_equals(scroller.scrollTop, target_snap_position,
+ "scroller snaps to just below the inner snap area.");
+ // We are just below the inner snap area. Increase its height so that it
+ // is larger than the snapport making the current scroll position
+ // a valid covering position within the inner snap area.
+ let snapchanged_promise = waitForSnapChangedEvent(scroller, false);
+ inner_snap_area.style.height =
+ `${scroller.clientHeight + inner_snap_area.clientHeight + 10}px`;
+ const evt = await snapchanged_promise;
+ assertSnapEvent(evt, [outer_snap_area.id, inner_snap_area.id]);
+ assert_equals(scroller.scrollTop, target_snap_position,
+ "scroller maintains offset which is now covering within inner area");
+ }, "snapchanged fires after snap area is snapped to upon layout change " +
+ "without scroll.");
+
+ promise_test(async(t) => {
+ await setup(t);
+ await waitForCompositorCommit();
+ let snapchanged_promise = waitForSnapChangedEvent(scroller, false);
+ scroller.style.scrollSnapType = "none";
+ let evt = await snapchanged_promise;
+ assertSnapEvent(evt, []);
+ snapchanged_promise = waitForSnapChangedEvent(scroller, false);
+ scroller.style.scrollSnapType = "y mandatory";
+ evt = await snapchanged_promise;
+ assertSnapEvent(evt, [outer_snap_area.id]);
+ }, "snapchanged fires when container stops snapping");
+
+ promise_test(async(t) => {
+ await setup(t);
+ await waitForCompositorCommit();
+ let snapchanged_promise = waitForSnapChangedEvent(scroller, false);
+ inner_snap_area.style.scrollSnapAlign = "none";
+ outer_snap_area.style.scrollSnapAlign = "none";
+ let evt = await snapchanged_promise;
+ assertSnapEvent(evt, []);
+ snapchanged_promise = waitForSnapChangedEvent(scroller, false);
+ inner_snap_area.style.scrollSnapAlign = "start";
+ outer_snap_area.style.scrollSnapAlign = "start";
+ evt = await snapchanged_promise;
+ assertSnapEvent(evt, [outer_snap_area.id]);
+ }, "snapchanged fires when snap container no longer has snap areas");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-ensures-dom-order.html b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-ensures-dom-order.html
new file mode 100644
index 0000000000..10bc73b622
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-ensures-dom-order.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#snap-events" />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+ <script src="/css/css-scroll-snap-2/resources/common.js"></script>
+ <script src="/web-animations/testcommon.js"></script>
+ <style>
+ #scroller {
+ overflow-y: scroll;
+ scroll-snap-type: y mandatory;
+ width: 500px;
+ height: 500px;
+ background-color: white;
+ position: relative;
+ }
+ .space_filler {
+ display: inline-block;
+ width: 40%;
+ height: 30%;
+ background-color: green;
+ }
+ .snap_area {
+ scroll-snap-align: start;
+ background-color: yellow;
+ position: absolute;
+ width: 40%;
+ height: 30%;
+ }
+
+ #snap_point_1 {
+ left: 1px;
+ }
+ #snap_point_2 {
+ left: 80%;
+ }
+ #snap_point_3 {
+ left: 40%;
+ scroll-snap-align: start;
+ background-color: yellow;
+ position: absolute;
+ width: 40%;
+ height: 30%;
+ }
+ </style>
+</head>
+<body>
+ <div id="scroller">
+ <div class="space_filler"></div>
+ <div class="space_filler"></div>
+ <div class="space_filler"></div>
+ <div class="space_filler"></div>
+ <div class="space_filler"></div>
+ <div class="space_filler"></div>
+ <div class="space_filler"></div>
+ <div class="space_filler"></div>
+ <div id="snap_point_1" class="snap_area"><h1>1</h1></div>
+ <div id="snap_point_2" class="snap_area"><h1>2</h1></div>
+ </div>
+ <script>
+ promise_test(async (t) => {
+ checkSnapEventSupport("snapchanged");
+ await waitForCompositorCommit();
+ const snapchanged_promise = waitForSnapChangedEvent(scroller, false);
+ const snap_point_3 = document.createElement("div");
+ snap_point_3.id = "snap_point_3";
+ t.add_cleanup(() => {
+ snap_point_3.remove();
+ });
+ scroller.insertBefore(snap_point_3, snap_point_2);
+ const evt_seen = await snapchanged_promise;
+ assertSnapEvent(evt_seen,
+ [snap_point_1.id, snap_point_3.id, snap_point_2.id]);
+ }, "snapchanged lists snapTargets in DOM order.");
+
+ promise_test(async (t) => {
+ checkSnapEventSupport("snapchanged");
+ await waitForCompositorCommit();
+ const unreached_func = t.unreached_func("snapchanged shouldn't fire " +
+ "since the scroller is snapped to the same elements despite the " +
+ "dom order change.");
+ t.add_cleanup(() => {
+ scroller.removeEventListener("snapchanged", unreached_func);
+ })
+ scroller.addEventListener("snapchanged", unreached_func);
+ scroller.insertBefore(snap_point_2, snap_point_1);
+ await waitForCompositorCommit();
+ }, "DOM order change doesn't trigger snapchanged if snapped targets " +
+ "don't change.");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-interrupted-scroll.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-interrupted-scroll.tentative.html
new file mode 100644
index 0000000000..a1d5259451
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-interrupted-scroll.tentative.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: snapchanged events</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#snap-events">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+ </head>
+ <body>
+ <style>
+ #container {
+ overflow-y: scroll;
+ height: 500px;
+ width: 300px;
+ border: solid 1px black;
+ position: absolute;
+ scroll-snap-type: y mandatory;
+ }
+ .snap_area {
+ scroll-snap-align: start;
+ height: 400px;
+ width: 200px;
+ left: 50px;
+ position: absolute;
+ }
+ #area1 {
+ background-color: blue;
+ }
+ #area2 {
+ top: 400px;
+ background-color: yellow;
+ }
+ #area3 {
+ top: 800px;
+ background-color: green;
+ }
+ </style>
+ <div id="container">
+ <div id="area1" class="snap_area"></div>
+ <div id="area2" class="snap_area"></div>
+ <div id="area3" class="snap_area"></div>
+ </div>
+ <script>
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+
+ container.addEventListener("snapchanged",
+ t.unreached_func("snapchanged should not fire"));
+ let reset = () => {
+ container.scrollTo({ top: 0, behavior: "smooth"});
+ container.removeEventListener("scroll", reset);
+ };
+ container.addEventListener("scroll", reset);
+
+ let scrollend_promise = waitForScrollendEventNoTimeout(container);
+ container.scrollTo({ top: 250, behavior: "smooth"});
+ await scrollend_promise;
+ assert_equals(container.scrollTop, 0, "scroll position is reset");
+
+ // snapchanged should not fire since the scroll ended on the same snap
+ // target as the one it started on.
+ await waitForCompositorCommit();
+ }, "snapchanged doesn't fire if interrupting scroll cancels snap");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-programmatic-root-scroll.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-programmatic-root-scroll.tentative.html
new file mode 100644
index 0000000000..2e33c3c970
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-programmatic-root-scroll.tentative.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: snapchanged event on the root/document</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#snap-events">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/css/css-scroll-snap-2/resources/common.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+ <script src="/web-animations/testcommon.js"></script>
+</head>
+
+<body>
+ <style>
+ :root {
+ margin: 0;
+ padding: 0;
+ scroll-snap-type: both mandatory;
+ }
+
+ div {
+ position: absolute;
+ margin: 0px;
+ }
+
+ #spacer {
+ width: 200vw;
+ height: 200vh;
+ }
+
+ .snap_point {
+ width: 40vw;
+ height: 40vh;
+ scroll-snap-align: start;
+ }
+
+ #snap_point_1 {
+ left: 0px;
+ top: 0px;
+ background-color: red;
+ }
+
+ #snap_point_2 {
+ top: 40vh;
+ left: 40vw;
+ background-color: orange;
+ }
+
+ #snap_point_3 {
+ left: 80vw;
+ top: 80vh;
+ background-color: blue;
+ }
+ </style>
+ <div id="spacer"></div>
+ <div id="snap_point_1" class="snap_point"></div>
+ <div id="snap_point_2" class="snap_point"></div>
+ <div id="snap_point_3" class="snap_point"></div>
+
+ <script>
+ let scroller = document.scrollingElement;
+
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: () => {
+ scroller.scrollTo(snap_point_2.offsetLeft, snap_point_2.offsetTop);
+ },
+ expected_snap_targets: [snap_point_2.id],
+ expected_scroll_offsets: {
+ x: snap_point_2.offsetLeft,
+ y: snap_point_2.offsetTop,
+ }
+ };
+ await test_snapchanged(t, test_data);
+ }, "snapchanged event fires after snap target changes via scrollTo");
+
+ promise_test(async (t) => {
+ checkSnapEventSupport("snapchanged");
+ await waitForScrollReset(t, scroller);
+ await waitForCompositorCommit();
+ assert_equals(scroller.scrollTop, 0,
+ "scroller is initially not scrolled vertically");
+ assert_equals(scroller.scrollLeft, 0,
+ "scroller is initially not scrolled horizontally");
+
+ let snapchanged_promise = waitForSnapChangedEvent(document, false);
+ // Set the scroll destination to just a little off (0, 0) so we snap
+ // back to the top box.
+ let scroll_top_target = 10;
+ let scroll_left_target = 10;
+
+ scroller.scrollTo(scroll_left_target, scroll_top_target);
+ let evt = await snapchanged_promise;
+ assert_equals(evt, null, "no snapchanges since scroller is back to top");
+ // scroller should snap back to (0,0) with no snapchanged event.
+ assert_equals(scroller.scrollTop, 0,
+ "scroller snaps back to the top");
+ assert_equals(scroller.scrollLeft, 0,
+ "scroller snaps back to the left");
+
+ snapchanged_promise = waitForSnapChangedEvent(document);
+ scroll_top_target = snap_point_2.offsetTop + 10;
+ scroll_left_target = snap_point_2.offsetLeft + 10;
+ // This scroll should snap to snap_point_2, so snapchanged should be
+ // fired.
+ scroller.scrollTo(scroll_left_target, scroll_top_target);
+
+ evt = await snapchanged_promise;
+ assertSnapEvent(evt, [snap_point_2.id]);
+ assert_approx_equals(scroller.scrollTop, snap_point_2.offsetTop, 1,
+ "scroller snaps to the top of snap_point_2");
+ assert_approx_equals(scroller.scrollLeft, snap_point_2.offsetLeft, 1,
+ "scroller snaps to the left of snap_point_2");
+ }, "snapchanged is not fired if snap target doesn't change on " +
+ "programmatic scroll");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-programmatic-scroll.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-programmatic-scroll.tentative.html
new file mode 100644
index 0000000000..6082e09013
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-programmatic-scroll.tentative.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: snapchanged events</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#snap-events">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/css/css-scroll-snap-2/resources/common.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+ <script src="/web-animations/testcommon.js"></script>
+</head>
+
+<body onload="runTests()">
+ <style>
+ body {
+ margin: 0px;
+ }
+
+ div {
+ position: absolute;
+ margin: 0px;
+ }
+
+ #spacer {
+ width: 2000px;
+ height: 2000px;
+ }
+
+ .scroller {
+ height: 400px;
+ width: 400px;
+ overflow: scroll;
+ scroll-snap-type: both mandatory;
+ }
+
+ .snap_point {
+ width: 300px;
+ height: 300px;
+ scroll-snap-align: start;
+ }
+
+ #snap_point_1 {
+ left: 0px;
+ top: 0px;
+ background-color: red;
+ }
+
+ #snap_point_2 {
+ top: 300px;
+ left: 300px;
+ background-color: orange;
+ }
+
+ #snap_point_3 {
+ left: 600px;
+ top: 600px;
+ background-color: blue;
+ }
+ </style>
+ <div id="scroller" class="scroller">
+ <div id="spacer"></div>
+ <div id="snap_point_1" class="snap_point"></div>
+ <div id="snap_point_2" class="snap_point"></div>
+ <div id="snap_point_3" class="snap_point"></div>
+ </div>
+ <script>
+ function runTests () {
+ let scroller = document.getElementById("scroller");
+
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: () => {
+ scroller.scrollTo(snap_point_2.offsetLeft, snap_point_2.offsetTop);
+ },
+ expected_snap_targets: [snap_point_2.id],
+ expected_scroll_offsets: {
+ x: snap_point_2.offsetLeft,
+ y: snap_point_2.offsetTop,
+ }
+ };
+ await test_snapchanged(t, test_data);
+ }, "snapchanged event fires after snap target changes via scrollTo");
+
+ promise_test(async (t) => {
+ checkSnapEventSupport("snapchanged");
+ await waitForScrollReset(t, scroller);
+ await waitForCompositorCommit();
+ assert_equals(scroller.scrollTop, 0,
+ "scroller is initially not scrolled vertically");
+ assert_equals(scroller.scrollLeft, 0,
+ "scroller is initially not scrolled horizontally");
+
+ let snapchanged_promise = waitForSnapChangedEvent(scroller, false);
+ // Set the scroll destination to just a little off (0, 0) so we snap
+ // back to the top box.
+ let scroll_top_target = 10;
+ let scroll_left_target = 10;
+
+ scroller.scrollTo(scroll_left_target, scroll_top_target);
+ let evt = await snapchanged_promise;
+ assert_equals(evt, null, "no snapchanges since scroller is back to top");
+ // scroller should snap back to (0,0) with no snapchanged event.
+ assert_equals(scroller.scrollTop, 0, "scroller snaps back to the top");
+ assert_equals(scroller.scrollLeft, 0, "scroller snaps back to the left");
+
+ snapchanged_promise = waitForSnapChangedEvent(scroller);
+ scroll_top_target = snap_point_2.offsetTop + 10;
+ scroll_left_target = snap_point_2.offsetLeft + 10;
+ // This scroll should snap to snap_point_2, so snapchanged should be
+ // fired.
+ scroller.scrollTo(scroll_left_target, scroll_top_target);
+
+ evt = await snapchanged_promise;
+ assertSnapEvent(evt, [snap_point_2.id]);
+ assert_approx_equals(scroller.scrollTop, snap_point_2.offsetTop, 1,
+ "scroller snaps to the top of snap_point_2");
+ assert_approx_equals(scroller.scrollLeft, snap_point_2.offsetLeft, 1,
+ "scroller snaps to the left of snap_point_2");
+ }, "snapchanged is not fired if snap target doesn't change on " +
+ "programmatic scroll");
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-user-root-scroll.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-user-root-scroll.tentative.html
new file mode 100644
index 0000000000..5405d778bf
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-user-root-scroll.tentative.html
@@ -0,0 +1,166 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: snapchanged events</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#snap-events">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-actions.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/css/css-scroll-snap-2/resources/common.js"></script>
+ <script src="/css/css-scroll-snap-2/resources/user-scroll-common.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+ <script src="/web-animations/testcommon.js"></script>
+</head>
+
+<body>
+ <style type='text/css'>
+ :root {
+ margin: 0px;
+ padding: 0px;
+ scroll-snap-type: both mandatory;
+ }
+
+ div {
+ position: absolute;
+ margin: 0px;
+ }
+
+ #spacer {
+ width: 200vw;
+ height: 200vh;
+ }
+
+ .snap_point {
+ width: 40vw;
+ height: 40vh;
+ scroll-snap-align: start;
+ }
+
+ #snap_point_1 {
+ left: 0px;
+ top: 0px;
+ background-color: red;
+ }
+
+ #snap_point_2 {
+ top: 35vh;
+ left: 35vw;
+ background-color: orange;
+ }
+
+ #snap_point_3 {
+ left: 70vw;
+ top: 70vh;
+ background-color: blue;
+ }
+ </style>
+ <div id="spacer"></div>
+ <div id="snap_point_1" class="snap_point"></div>
+ <div id="snap_point_2" class="snap_point"></div>
+ <div id="snap_point_3" class="snap_point"></div>
+ <script>
+ const scroller = document.scrollingElement;
+ const offset_to_snap_point_2 = {
+ x: snap_point_2.offsetLeft,
+ y: snap_point_2.offsetTop
+ };
+
+ // Touch scroll test.
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ const start_pos = {
+ x: scroller.clientWidth / 2,
+ y: scroller.clientHeight / 2,
+ };
+ const end_pos = { x: 0, y: 0 };
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: async () => {
+ await snap_event_touch_scroll_helper(start_pos, end_pos);
+ },
+ expected_snap_targets: [snap_point_2.id],
+ expected_scroll_offsets: {
+ x: offset_to_snap_point_2.x,
+ y: offset_to_snap_point_2.y,
+ }
+ };
+ await test_snapchanged(t, test_data);
+ }, "snapchanged event fires after snap target changes on touch scroll");
+
+ // Wheel scroll test.
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: async () => {
+ await new test_driver.Actions().scroll(0, 0,
+ offset_to_snap_point_2.x,
+ offset_to_snap_point_2.y,
+ { origin: scroller }).send();
+ },
+ expected_snap_targets: [snap_point_2.id],
+ expected_scroll_offsets: {
+ x: offset_to_snap_point_2.x,
+ y: offset_to_snap_point_2.y,
+ }
+ };
+ await test_snapchanged(t, test_data);
+ }, "snapchanged event fires after snap target changes on wheel scroll");
+
+ // Scrollbar drag test.
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ // Skip test on platforms that do not have a visible scrollbar (e.g.
+ // overlay scrollbar).
+ const scrollbar_width = window.innerWidth -
+ document.documentElement.clientWidth;
+ if (scrollbar_width == 0) {
+ return;
+ }
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: async () => {
+ const scrollbar_to_scroller_ratio =
+ getScrollbarToScrollerRatio(document.documentElement);
+ // Scroll by just over half of the top box's height.
+ const drag_amt = (offset_to_snap_point_2.y / 2 + 1) *
+ scrollbar_to_scroller_ratio;
+ await snap_event_scrollbar_drag_helper(scroller, scrollbar_width,
+ drag_amt);
+ },
+ expected_snap_targets: [snap_point_1.id, snap_point_2.id],
+ expected_scroll_offsets: {
+ x: 0,
+ y: offset_to_snap_point_2.y,
+ }
+ };
+ await test_snapchanged(t, test_data);
+ }, "snapchanged event fires after snap target changes on scrollbar drag");
+
+ // Keyboard test.
+ promise_test(async (t) => {
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: async () => {
+ scroller.focus();
+ window.test_driver.send_keys(document.documentElement,
+ '\ue015'/*ArrowDown*/);
+ },
+ expected_snap_targets: [snap_point_1.id, snap_point_2.id],
+ expected_scroll_offsets: {
+ x: 0,
+ y: offset_to_snap_point_2.y,
+ }
+ };
+ await test_snapchanged(t, test_data);
+ }, "snapchanged event fires after snap target changes on keydown press");
+
+ promise_test(async (t) => {
+ await test_no_snapchanged(t, scroller, /*delta*/10);
+ }, "snapchanged is not fired if snap target doesn't change on user scroll");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-user-scroll.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-user-scroll.tentative.html
new file mode 100644
index 0000000000..4f36200722
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-on-user-scroll.tentative.html
@@ -0,0 +1,170 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title> CSS Scroll Snap 2 Test: snapchanged events</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#snap-events">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-actions.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/css/css-scroll-snap-2/resources/common.js"></script>
+ <script src="/css/css-scroll-snap-2/resources/user-scroll-common.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+ <script src="/web-animations/testcommon.js"></script>
+</head>
+
+<body>
+ <style>
+ body {
+ margin: 0px;
+ }
+
+ div {
+ position: absolute;
+ margin: 0px;
+ }
+
+ #spacer {
+ width: 200vw;
+ height: 200vh;
+ }
+
+ .scroller {
+ height: 400px;
+ width: 400px;
+ overflow: scroll;
+ scroll-snap-type: both mandatory;
+ }
+
+ .snap_point {
+ width: 40%;
+ height: 40%;
+ scroll-snap-align: start;
+ }
+
+ #snap_point_1 {
+ left: 0px;
+ top: 0px;
+ background-color: red;
+ }
+
+ #snap_point_2 {
+ top: 35%;
+ left: 35%;
+ background-color: orange;
+ }
+
+ #snap_point_3 {
+ top: 70%;
+ left: 70%;
+ background-color: blue;
+ }
+ </style>
+ <div id="scroller" class="scroller">
+ <div id="spacer"></div>
+ <div id="snap_point_1" class="snap_point"></div>
+ <div id="snap_point_2" class="snap_point"></div>
+ <div id="snap_point_3" class="snap_point"></div>
+ </div>
+ <script>
+ const scroller = document.getElementById("scroller");
+ const offset_to_snap_point_2 = {
+ x: snap_point_2.offsetLeft,
+ y: snap_point_2.offsetTop
+ };
+
+ // Touch scroll test.
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ const start_pos = {
+ x: scroller.clientWidth / 2,
+ y: scroller.clientHeight / 2,
+ };
+ const end_pos = { x: 0, y: 0 };
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: async () => {
+ await snap_event_touch_scroll_helper(start_pos, end_pos);
+ },
+ expected_snap_targets: [snap_point_2.id],
+ expected_scroll_offsets: {
+ x: offset_to_snap_point_2.x,
+ y: offset_to_snap_point_2.y,
+ }
+ };
+ await test_snapchanged(t, test_data);
+ }, "snapchanged event fires after snap target changes on touch scroll");
+
+ // Wheel scroll test.
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: async () => {
+ await new test_driver.Actions().scroll(0, 0,
+ offset_to_snap_point_2.x,
+ offset_to_snap_point_2.y,
+ { origin: scroller }).send();
+ },
+ expected_snap_targets: [snap_point_2.id],
+ expected_scroll_offsets: {
+ x: offset_to_snap_point_2.x,
+ y: offset_to_snap_point_2.y,
+ }
+ };
+ await test_snapchanged(t, test_data);
+ }, "snapchanged event fires after snap target changes on wheel scroll");
+
+ // Scrollbar drag test.
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ // Skip test on platforms that do not have a visible scrollbar (e.g.
+ // overlay scrollbar).
+ const scrollbar_width = scroller.offsetWidth - scroller.clientWidth;
+ if (scrollbar_width == 0)
+ return;
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: async () => {
+ const scrollbar_to_scroller_ratio =
+ getScrollbarToScrollerRatio(scroller);
+ // Scroll by just over half of the top box's height.
+ const drag_amt = (offset_to_snap_point_2.y / 2 + 1) *
+ scrollbar_to_scroller_ratio;
+ await snap_event_scrollbar_drag_helper(scroller, scrollbar_width, drag_amt);
+ },
+ expected_snap_targets: [snap_point_1.id, snap_point_2.id],
+ expected_scroll_offsets: {
+ x: 0,
+ y: offset_to_snap_point_2.y,
+ }
+ };
+ await test_snapchanged(t, test_data);
+ }, "snapchanged event fires after snap target changes on scrollbar drag");
+
+ // Keyboard test.
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: async () => {
+ scroller.focus();
+ window.test_driver.send_keys(scroller, '\ue015'/*ArrowDown*/);
+ },
+ expected_snap_targets: [snap_point_1.id, snap_point_2.id],
+ expected_scroll_offsets: {
+ x: 0,
+ y: offset_to_snap_point_2.y,
+ }
+ };
+ await test_snapchanged(t, test_data);
+ }, "snapchanged event fires after snap target changes on keydown press");
+
+ promise_test(async (t) => {
+ await test_no_snapchanged(t, scroller, /*delta*/10);
+ }, "snapchanged is not fired if snap target doesn't change on user scroll");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-same-targets-after-layout-changed.html b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-same-targets-after-layout-changed.html
new file mode 100644
index 0000000000..4d16bd80a3
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-same-targets-after-layout-changed.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#snap-events" />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+ <script src="/css/css-scroll-snap-2/resources/common.js"></script>
+ <style>
+ #scroller {
+ overflow-y: scroll;
+ scroll-snap-type: y mandatory;
+ width: 500px;
+ height: 500px;
+ background-color: white;
+ position: relative;
+ }
+ .large_space {
+ position: absolute;
+ height: 100vh;
+ width: 100vw;
+ }
+ .space_filler {
+ display: inline-block;
+ width: 40%;
+ height: 30%;
+ background-color: green;
+ }
+ .snap_area {
+ scroll-snap-align: start;
+ background-color: yellow;
+ position: absolute;
+ width: 40%;
+ height: 30%;
+ top: 500px;
+ }
+
+ .left {
+ left: 1px;
+ }
+ .right {
+ left: 41%;
+ }
+ </style>
+</head>
+<body>
+ <div id="scroller">
+ <div class="large_space"></div>
+ <div class="space_filler"></div>
+ <div class="space_filler"></div>
+ <div class="space_filler"></div>
+ <div class="space_filler"></div>
+ <div class="space_filler"></div>
+ <div class="space_filler"></div>
+ <div class="space_filler"></div>
+ <div class="space_filler"></div>
+ <div id="left" class="snap_area left"><h1>1</h1></div>
+ <div id="right" class="snap_area right"><h1>2</h1></div>
+ </div>
+ <script>
+ let unreached_func = null;
+ promise_test(async (t) => {
+ checkSnapEventSupport("snapchanged");
+ await waitForCompositorCommit();
+ unreached_func = t.unreached_func("snapchanged shouldn't fire " +
+ "since the scroller is snapped to the same elements.");
+ scroller.addEventListener("snapchanged", unreached_func);
+ t.add_cleanup(() => {
+ scroller.removeEventListener("snapchanged", unreached_func);
+ });
+ assert_greater_than(right.offsetLeft, left.offsetLeft,
+ "boxes have switched positions.");
+ // Switch the boxes' horizontal positions.
+ right.style.left = "1px";
+ left.style.left = "41%";
+ await waitForCompositorCommit();
+ assert_less_than(right.offsetLeft, left.offsetLeft,
+ "boxes have switched positions.");
+ await waitForCompositorCommit();
+ }, "snapchanged doesn't fire after layout change if snapped to the same " +
+ "elements");
+
+ promise_test(async (t) => {
+ checkSnapEventSupport("snapchanged");
+ await waitForCompositorCommit();
+ unreached_func = t.unreached_func("snapchanged shouldn't fire " +
+ "since the scroller is snapped to the same elements.");
+ scroller.addEventListener("snapchanged", unreached_func);
+ t.add_cleanup(() => {
+ scroller.removeEventListener("snapchanged", unreached_func);
+ });
+ const scrollend_promise = waitForScrollendEventNoTimeout(scroller);
+ // Move the boxes to the same vertical level. Both boxes should still be
+ // considered snapped to so snapchanged should not be triggerred.
+ right.style.top = `0px`;
+ left.style.top = `0px`;
+ await scrollend_promise;
+ assert_equals(scroller.scrollTop, 0);
+ await waitForCompositorCommit();
+ }, "snapchanged doesn't fire after snap to the same targets after scroll. " +
+ "elements");
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-with-proximity-strictness.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-with-proximity-strictness.tentative.html
new file mode 100644
index 0000000000..cb55054e6f
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanged/snapchanged-with-proximity-strictness.tentative.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>
+ <title> CSS Scroll Snap 2 Test: snapchanged events on proximity strictness container</title>
+ <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#snap-events"/>
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/dom/events/scrolling/scroll_support.js"></script>
+<script src="/css/css-scroll-snap-2/resources/common.js"></script>
+<style>
+ div {
+ position: absolute;
+ margin: 0;
+ }
+
+ #scroller {
+ height: 600px;
+ width: 600px;
+ overflow: hidden;
+ scroll-snap-type: y proximity;
+ }
+
+ .snap {
+ width: 300px;
+ height: 300px;
+ background-color: green;
+ scroll-snap-align: start;
+ }
+
+ .area {
+ width: 2000px;
+ height: 2000px;
+ }
+ </style>
+</head>
+<body>
+<div id="scroller">
+ <div class="area"></div>
+ <div id="target" class="snap" style="top: 0px;"></div>
+</div>
+
+<script>
+ const target = document.getElementById("target");
+ let resolve_func = null;
+
+ promise_test(async (test) => {
+ checkSnapEventSupport("snapchanged");
+ await waitForCompositorCommit();
+ // The initial snap position is at (0, 0).
+ assert_equals(scroller.scrollTop, 0);
+ assert_equals(scroller.scrollLeft, 0);
+
+ let snapchanged_promise = waitForSnapChangedEvent(scroller);
+ // Scroll to a position where it's outside of the scroll-snap proximity
+ // threshold, so that it won't trigger snapping.
+ scroller.scrollTo(0, 250);
+
+ // snapchanged should fire as we've moved from within the proximity range
+ // to outside the proximity range and are no longer snapped.
+ let evt = await snapchanged_promise;
+ assert_equals(scroller.scrollTop, 250);
+ assertSnapEvent(evt, []);
+ evt = null;
+
+ snapchanged_promise = waitForSnapChangedEvent(scroller);
+ // Scroll to a position within the scroll-snap proximity
+ // threshold, so that it triggers snapping.
+ scroller.scrollTo(0, 190);
+
+ evt = await snapchanged_promise;
+ assert_equals(scroller.scrollTop, 0);
+ // snapchanged should fire as we've moved from outside the proximity range
+ // to inside the proximity range and are once again snapped.
+ assertSnapEvent(evt, [target.id]);
+ }, "Snapchanged fires when scrolling outside proximity range.");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-after-layout-change.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-after-layout-change.tentative.html
new file mode 100644
index 0000000000..5474b7ddce
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-after-layout-change.tentative.html
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-actions.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+ <script src="/css/css-scroll-snap-2/resources/common.js"></script>
+ <script src="/css/css-scroll-snap-2/resources/user-scroll-common.js"></script>
+ <script src="/web-animations/testcommon.js"></script>
+ <style>
+ body {
+ margin: 0px;
+ }
+ #space {
+ height: 200vh;
+ width: 200vw;
+ }
+ .scroller {
+ scroll-snap-type: x mandatory;
+ overflow-x: auto;
+ overflow-y: hidden;
+ position: relative;
+ height: 500px;
+ width: 500px;
+ }
+
+ .box {
+ scroll-snap-align: start;
+ height: 100px;
+ width: 100px;
+ position: absolute;
+ top: 200px;
+ }
+
+ #box1 {
+ background-color: red;
+ }
+
+ #box2 {
+ background-color: yellow;
+ left: 200px;
+ }
+
+ #box3 {
+ background-color: blue;
+ left: 400px;
+ }
+ </style>
+</head>
+<body>
+ <div id="scroller" class="scroller">
+ <div id="space"></div>
+ <div id="box1" class="box"><h1>1</h1></div>
+ <div id="box2" class="box"><h1>2</h1></div>
+ <div id="box3" class="box"><h1>3</h1></div>
+ </div>
+ <script>
+ const scroller = document.getElementById("scroller");
+ promise_test(async (t) => {
+ // This tests snapchanging firing after a layout change in the middle of a
+ // touch scroll. We start a touch scroll far enough that snapchanging
+ // fires and then, with the pointer still down, we change the layout so
+ // that snapchanging should fire with a different target.
+ await waitForScrollReset(t, scroller);
+ await waitForCompositorCommit();
+
+ const start_pos_x = Math.round(box2.offsetLeft);
+ // Drag by enough to ensure box2 is the preferred snap target.
+ const drag_amt = Math.round(box2.offsetLeft / 2) + 50;
+ const end_pos_x = start_pos_x - drag_amt;
+ const pos_y = Math.round(scroller.clientHeight / 2);
+ let evt_promise;
+ let snap_evt;
+
+ const save_snapchanging_evt = (evt) => { snap_evt = evt; }
+ evt_promise = scroller.addEventListener("snapchanging",
+ save_snapchanging_evt);
+ // We wait to reach the expected scroll position rather than waiting for a
+ // snapchanging event to avoid timing out if the snapchanging event does
+ // not fire.
+ const scroll_promise = new Promise((resolve) => {
+ scroller.addEventListener("scroll", async () => {
+ if (scroller.scrollLeft >= (box2.offsetLeft / 2)) {
+ await waitForAnimationFrames(2);
+ resolve();
+ }
+ });
+ });
+
+ await new test_driver.Actions()
+ .addPointer("TestPointer", "touch")
+ .pointerMove(start_pos_x, pos_y)
+ .pointerDown()
+ .addTick()
+ .pause(200)
+ // Drag closer to box2, which should trigger a snapchanging event.
+ .pointerMove(start_pos_x - drag_amt, pos_y)
+ .send();
+
+ // assert snapchanging that should have already happened.
+ await scroll_promise;
+ assertSnapEvent(snap_evt, [box2.id]);
+
+ evt_promise = waitForSnapEvent(scroller, "snapchanging", false);
+ // Change layout while pointer is still down.
+ let box2_prev_left = getComputedStyle(box2).getPropertyValue("left");
+ let box3_prev_left = getComputedStyle(box3).getPropertyValue("left");
+ box2.style.left = box3_prev_left;
+ box3.style.left = box2_prev_left;
+ snap_evt = await evt_promise;
+ assertSnapEvent(snap_evt, [box3.id]);
+ }, "snapchanging fires after layout change");
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-on-programmatic-root-scroll.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-on-programmatic-root-scroll.tentative.html
new file mode 100644
index 0000000000..d031811c17
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-on-programmatic-root-scroll.tentative.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-actions.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+ <script src="/css/css-scroll-snap-2/resources/common.js"></script>
+ß <script src="/web-animations/testcommon.js"></script>
+</head>
+
+<body>
+ <style>
+ :root {
+ scroll-snap-type: y mandatory;
+ }
+
+ .box {
+ position: absolute;
+ left: 150px;
+ height: 80vh;
+ width: 100px;
+ border: solid 1px white;
+ }
+
+ .snap {
+ scroll-snap-align: start;
+ }
+
+ .blue {
+ background-color: blue;
+ }
+
+ .green {
+ background-color: green;
+ }
+
+ .yellow {
+ background-color: yellow;
+ }
+
+ #snap_area_1 {
+ top: 0px;
+ }
+
+ #snap_area_2 {
+ top: calc(80vh + 2px);
+ }
+
+ #snap_area_3 {
+ top: calc(160vh + 4px);
+ }
+
+ .large_space {
+ height: 400vh;
+ width: 400vw;
+ position: absolute;
+ }
+ </style>
+ <div class="large_space"></div>
+ <div id="snap_area_1" class="blue snap box"></div>
+ <div id="snap_area_2" class="green snap box"></div>
+ <div id="snap_area_3" class="yellow snap box"></div>
+ <script>
+ const scroller = document.scrollingElement;
+
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: async () => {
+ scroller.scrollTo(0, snap_area_2.offsetTop);
+ },
+ expected_snap_targets: [snap_area_2.id],
+ expected_scroll_offsets: {
+ x: 0,
+ y: snap_area_2.offsetTop
+ }
+ };
+ await test_snap_event(t, test_data, "snapchanging");
+ }, "snapchanging fires on programmatic scrolls that changes a scroller's" +
+ " snap targets.");
+
+ promise_test(async (t) => {
+ checkSnapEventSupport("snapchanging");
+ await waitForScrollReset(t, scroller);
+ await waitForCompositorCommit();
+ let snap_event_promise = waitForSnapEvent(document, "snapchanging", false);
+ // The snap areas are far apart enough that 10px is not enough to trigger
+ // a change in snap targets.
+ const small_scroll_offset = 10;
+ scroller.scrollTo(0, small_scroll_offset);
+ let evt = await snap_event_promise;
+ assert_equals(evt, null, "no snap event since scroller is back to top");
+ assert_equals(scroller.scrollTop, 0, "scroller snaps back to the top");
+ assert_equals(scroller.scrollLeft, 0, "scroller snaps back to the left");
+ }, "snapchanging does not fire on programmatic scrolls that don't " +
+ "trigger a change in snap targets.");
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-on-programmatic-scroll.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-on-programmatic-scroll.tentative.html
new file mode 100644
index 0000000000..5a0de1deb3
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-on-programmatic-scroll.tentative.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-actions.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+ <script src="/css/css-scroll-snap-2/resources/common.js"></script>
+ <script src="/web-animations/testcommon.js"></script>
+</head>
+
+<body>
+ <style>
+ #scroller {
+ height: 400px;
+ width: 400px;
+ position: relative;
+ overflow: scroll;
+ scroll-snap-type: y mandatory;
+ border: solid 1px black;
+ }
+
+ .box {
+ position: absolute;
+ left: 150px;
+ height: 350px;
+ width: 100px;
+ border: solid 1px white;
+ }
+
+ .snap {
+ scroll-snap-align: start;
+ }
+
+ .blue {
+ background-color: blue;
+ }
+
+ .green {
+ background-color: green;
+ }
+
+ .yellow {
+ background-color: yellow;
+ }
+
+ #snap_area_1 {
+ top: 0px;
+ }
+
+ #snap_area_2 {
+ top: 352px;
+ }
+
+ #snap_area_3 {
+ top: 704px;
+ }
+
+ .large_space {
+ height: 400vh;
+ width: 400vw;
+ position: absolute;
+ }
+ </style>
+ <div id="scroller">
+ <div class="large_space"></div>
+ <div id="snap_area_1" class="blue snap box"></div>
+ <div id="snap_area_2" class="green snap box"></div>
+ <div id="snap_area_3" class="yellow snap box"></div>
+ </div>
+ <script>
+ const scroller = document.getElementById("scroller");
+
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: async () => {
+ scroller.scrollTo(0, snap_area_2.offsetTop);
+ },
+ expected_snap_targets: [snap_area_2.id],
+ expected_scroll_offsets: {
+ x: 0,
+ y: snap_area_2.offsetTop
+ }
+ };
+ await test_snap_event(t, test_data, "snapchanging");
+ }, "snapchanging fires on programmatic scrolls that changes a scroller's" +
+ " snap targets.");
+
+ promise_test(async (t) => {
+ checkSnapEventSupport("snapchanging");
+ await waitForScrollReset(t, scroller);
+ await waitForCompositorCommit();
+ let snap_event_promise = waitForSnapEvent(scroller, "snapchanging", false);
+ // The snap areas are far apart enough that 10px is not enough to trigger
+ // a change in snap targets.
+ const small_scroll_offset = 10;
+ // Set the scroll destination to just a little off (0, 0) top so we snap
+ // back to the top box.
+ scroller.scrollTo(0, small_scroll_offset);
+ let evt = await snap_event_promise;
+ assert_equals(evt, null, "no snap event since scroller is back to top");
+ assert_equals(scroller.scrollTop, 0, "scroller snaps back to the top");
+ assert_equals(scroller.scrollLeft, 0, "scroller snaps back to the left");
+ });
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-on-user-root-scroll.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-on-user-root-scroll.tentative.html
new file mode 100644
index 0000000000..29d0239e2d
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-on-user-root-scroll.tentative.html
@@ -0,0 +1,198 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-actions.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+ <script src="/css/css-scroll-snap-2/resources/common.js"></script>
+ <script src="/css/css-scroll-snap-2/resources/user-scroll-common.js"></script>
+</head>
+
+<body>
+ <style>
+ :root {
+ scroll-snap-type: y mandatory;
+ }
+ #scroller {
+ height: 400px;
+ width: 400px;
+ position: relative;
+ overflow: scroll;
+ scroll-snap-type: y mandatory;
+ border: solid 1px black;
+ }
+
+ .box {
+ position: absolute;
+ left: 150px;
+ height: 80vh;
+ width: 100px;
+ border: solid 1px white;
+ }
+
+ .snap {
+ scroll-snap-align: start;
+ }
+
+ .blue {
+ background-color: blue;
+ }
+
+ .green {
+ background-color: green;
+ }
+
+ .yellow {
+ background-color: yellow;
+ }
+
+ #snap_area_1 {
+ top: 0px;
+ }
+
+ #snap_area_2 {
+ top: calc(80vh + 2px); /* height of snap_area_1 + its borders. */
+ }
+
+ #snap_area_3 {
+ top: calc(160vh + 4px); /* heights of snap areas 1 & 2 + their borders */
+ }
+
+ .large_space {
+ height: 400vh;
+ width: 400vw;
+ position: absolute;
+ }
+ </style>
+ <div class="large_space"></div>
+ <div id="snap_area_1" class="blue snap box"></div>
+ <div id="snap_area_2" class="green snap box"></div>
+ <div id="snap_area_3" class="yellow snap box"></div>
+ <script>
+ const scroller = document.scrollingElement;
+
+ // Touch scroll test.
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ const scroller_middle = Math.round(scroller.clientWidth / 2);
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: async () => {
+ const start_pos = { x: scroller_middle, y: snap_area_2.offsetTop };
+ const end_pos = { x: scroller_middle, y: 0 };
+ await snap_event_touch_scroll_helper(start_pos, end_pos);
+ },
+ expected_snap_targets: [snap_area_2.id],
+ expected_scroll_offsets: {
+ x: 0,
+ y: snap_area_2.offsetTop
+ }
+ };
+ await test_snap_event(t, test_data, "snapchanging");
+ }, "touch scrolling fires snapchanging.");
+
+ // Wheel scroll test.
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: async () => {
+ await new test_driver.Actions().scroll(0, 0, 0,
+ Math.round(snap_area_2.offsetTop / 2) + 1).send();
+ },
+ expected_snap_targets: [snap_area_2.id],
+ expected_scroll_offsets: {
+ x: 0,
+ y: snap_area_2.offsetTop
+ }
+ };
+ await test_snap_event(t, test_data, "snapchanging");
+ }, "mouse wheel scroll triggers snapchanging.");
+
+ // Scrollbar drag test.
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ // Skip test on platforms that do not have a visible scrollbar (e.g.
+ // overlay scrollbar).
+ const scrollbar_width = window.innerWidth -
+ document.documentElement.clientWidth;
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: async () => {
+ const scrollbar_to_scroller_ratio =
+ getScrollbarToScrollerRatio(scroller);
+ // Scroll by just over half of the top box's height.
+ const drag_amt = (snap_area_2.offsetTop / 2 + 1) *
+ scrollbar_to_scroller_ratio;
+ await snap_event_scrollbar_drag_helper(scroller, scrollbar_width, drag_amt);
+ },
+ expected_snap_targets: [snap_area_2.id],
+ expected_scroll_offsets: {
+ x: 0,
+ y: snap_area_2.offsetTop
+ }
+ };
+ await test_snap_event(t, test_data, "snapchanging");
+ }, "scrollbar dragging fires snapchanging.");
+
+ // Keyboard test.
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: async () => {
+ scroller.focus();
+ window.test_driver.send_keys(document.documentElement, '\ue015'/*ArrowDown*/);
+ },
+ expected_snap_targets: [snap_area_2.id],
+ expected_scroll_offsets: {
+ x: 0,
+ y: snap_area_2.offsetTop
+ }
+ };
+ await test_snap_event(t, test_data, "snapchanging");
+ }, "keyboard scroll triggers snapchanging.");
+
+ // Touch scroll test: peek at snap_area_2 and then drag back to
+ // snap_area_1.
+ promise_test(async (t) => {
+ await waitForScrollReset(t, scroller);
+ await waitForCompositorCommit();
+ const pos_x = Math.round(scroller.clientWidth / 2);
+ const start_pos_y = Math.round(snap_area_2.offsetTop);
+ let evts_promise = waitForEventsUntil(document, "snapchanging",
+ waitForScrollendEventNoTimeout(document));
+ await new test_driver.Actions()
+ .addPointer("TestPointer", "touch")
+ .pointerMove(pos_x, start_pos_y)
+ .pointerDown()
+ .addTick()
+ .pause(200)
+ // Drag up to y=0, which should trigger a snapchanging event.
+ .pointerMove(pos_x, 0)
+ .addTick()
+ .pause(200)
+ // Drag down again to start position, which should trigger a
+ // snapchanging event.
+ .pointerMove(pos_x, start_pos_y)
+ .pointerUp()
+ .send();
+ let evts = await evts_promise;
+ assert_equals(evts.length, 2, "2 snapchanging events are seens");
+ assertSnapEvent(evts[0], [snap_area_2.id]);
+ assertSnapEvent(evts[1], [snap_area_1.id]);
+ }, "snapchanging fires as scroll moves through different snap targets.");
+
+ // snapchanging doesn't fire test.
+ promise_test(async (t) => {
+ test_no_snapchanging(t, scroller, 10);
+ }, "snapchanging doesn't fire if scroll doesn't reach different snap " +
+ "targets.");
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-on-user-scroll.tentative.html b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-on-user-scroll.tentative.html
new file mode 100644
index 0000000000..2c1f9742b6
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap-2/snapchanging/snapchanging-on-user-scroll.tentative.html
@@ -0,0 +1,188 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-actions.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+ <script src="/css/css-scroll-snap-2/resources/common.js"></script>
+ <script src="/css/css-scroll-snap-2/resources/user-scroll-common.js"></script>
+</head>
+
+<body>
+ <style>
+ #scroller {
+ height: 400px;
+ width: 400px;
+ position: relative;
+ overflow: scroll;
+ scroll-snap-type: y mandatory;
+ border: solid 1px black;
+ }
+ .box {
+ position: absolute;
+ left: 150px;
+ height: 350px;
+ width: 100px;
+ border: solid 1px white;
+ }
+ .snap {
+ scroll-snap-align: start;
+ }
+ .blue {
+ background-color: blue;
+ }
+ .green {
+ background-color: green;
+ }
+ .yellow {
+ background-color: yellow;
+ }
+ #snap_area_1 {
+ top: 0px;
+ }
+ #snap_area_2 {
+ top: 352px;
+ }
+ #snap_area_3 {
+ top: 704px;
+ }
+ .large_space {
+ height: 400vh;
+ width: 400vw;
+ position: absolute;
+ }
+ </style>
+ <div id="scroller">
+ <div class="large_space"></div>
+ <div id="snap_area_1" class="blue snap box"></div>
+ <div id="snap_area_2" class="green snap box"></div>
+ <div id="snap_area_3" class="yellow snap box"></div>
+ </div>
+ <script>
+ const scroller = document.getElementById("scroller");
+
+ // Touch scroll test.
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ const scroller_middle = Math.round(scroller.clientWidth / 2);
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: async () => {
+ const start_pos = { x: scroller_middle, y: snap_area_2.offsetTop };
+ const end_pos = { x: scroller_middle, y: 0 };
+ await snap_event_touch_scroll_helper(start_pos, end_pos);
+ },
+ expected_snap_targets: [snap_area_2.id],
+ expected_scroll_offsets: {
+ x: 0,
+ y: snap_area_2.offsetTop
+ }
+ };
+ await test_snap_event(t, test_data, "snapchanging");
+ }, "touch scrolling fires snapchanging.");
+
+ // Wheel scroll test.
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: async () => {
+ await new test_driver.Actions().scroll(0, 0, 0,
+ Math.round(snap_area_2.offsetTop / 2) + 1,
+ { origin: scroller }).send();
+ },
+ expected_snap_targets: [snap_area_2.id],
+ expected_scroll_offsets: {
+ x: 0,
+ y: snap_area_2.offsetTop
+ }
+ };
+ await test_snap_event(t, test_data, "snapchanging");
+ }, "mouse wheel scroll triggers snapchanging.");
+
+ // Scrollbar drag test.
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ // Skip test on platforms that do not have a visible scrollbar (e.g.
+ // overlay scrollbar).
+ const scrollbar_width = scroller.offsetWidth - scroller.clientWidth;
+ if (scrollbar_width == 0)
+ return;
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: async () => {
+ const scrollbar_to_scroller_ratio =
+ getScrollbarToScrollerRatio(scroller);
+ // Scroll by just over half of the top box's height.
+ const drag_amt = (snap_area_2.offsetTop / 2 + 1) *
+ scrollbar_to_scroller_ratio;
+ await snap_event_scrollbar_drag_helper(scroller, scrollbar_width, drag_amt);
+ },
+ expected_snap_targets: [snap_area_2.id],
+ expected_scroll_offsets: {
+ x: 0,
+ y: snap_area_2.offsetTop
+ }
+ };
+ await test_snap_event(t, test_data, "snapchanging");
+ }, "scrollbar dragging fires snapchanging.");
+
+ // Keyboard test.
+ promise_test(async (t) => {
+ await waitForCompositorCommit();
+ const test_data = {
+ scroller: scroller,
+ scrolling_function: async () => {
+ scroller.focus();
+ window.test_driver.send_keys(scroller, '\ue015'/*ArrowDown*/);
+ },
+ expected_snap_targets: [snap_area_2.id],
+ expected_scroll_offsets: {
+ x: 0,
+ y: snap_area_2.offsetTop
+ }
+ };
+ await test_snap_event(t, test_data, "snapchanging");
+ }, "keyboard scroll triggers snapchanging.");
+
+ // Touch scroll test: peek at snap_area_2 and then drag back to
+ // snap_area_1.
+ promise_test(async (t) => {
+ await waitForScrollReset(t, scroller);
+ await waitForCompositorCommit();
+ const pos_x = Math.round(scroller.clientWidth / 2);
+ const start_pos_y = Math.round(snap_area_2.offsetTop);
+ let evts_promise = waitForEventsUntil(scroller, "snapchanging",
+ waitForScrollendEventNoTimeout(scroller));
+ await new test_driver.Actions()
+ .addPointer("TestPointer", "touch")
+ .pointerMove(pos_x, start_pos_y)
+ .pointerDown()
+ .addTick()
+ .pause(200)
+ // Drag up to y=0, which should trigger a snapchanging event.
+ .pointerMove(pos_x, 0)
+ .addTick()
+ .pause(200)
+ // Drag down again to start position, which should trigger a
+ // snapchanging event.
+ .pointerMove(pos_x, start_pos_y)
+ .pointerUp()
+ .send();
+ let evts = await evts_promise;
+ assert_equals(evts.length, 2, "2 snapchanging events are seens");
+ assertSnapEvent(evts[0], [snap_area_2.id]);
+ assertSnapEvent(evts[1], [snap_area_1.id]);
+ }, "snapchanging fires as scroll moves through different snap targets.");
+
+ // snapchanging doesn't fire test.
+ promise_test(async (t) => {
+ test_no_snapchanging(t, scroller, 10);
+ }, "snapchanging doesn't fire if scroll doesn't reach different snap " +
+ "targets.");
+ </script>
+ </body>
+</html>