summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/css/css-cascade
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /testing/web-platform/tests/css/css-cascade
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/css/css-cascade')
-rw-r--r--testing/web-platform/tests/css/css-cascade/META.yml4
-rw-r--r--testing/web-platform/tests/css/css-cascade/all-prop-001-ref.html12
-rw-r--r--testing/web-platform/tests/css/css-cascade/all-prop-001.html48
-rw-r--r--testing/web-platform/tests/css/css-cascade/all-prop-002.html42
-rw-r--r--testing/web-platform/tests/css/css-cascade/all-prop-inherit-color.html23
-rw-r--r--testing/web-platform/tests/css/css-cascade/all-prop-initial-color.html23
-rw-r--r--testing/web-platform/tests/css/css-cascade/all-prop-initial-visited-ref.html3
-rw-r--r--testing/web-platform/tests/css/css-cascade/all-prop-initial-visited.html9
-rw-r--r--testing/web-platform/tests/css/css-cascade/all-prop-initial-xml.html34
-rw-r--r--testing/web-platform/tests/css/css-cascade/all-prop-revert-color.html23
-rw-r--r--testing/web-platform/tests/css/css-cascade/all-prop-revert-layer-noop.html70
-rw-r--r--testing/web-platform/tests/css/css-cascade/all-prop-revert-layer.html459
-rw-r--r--testing/web-platform/tests/css/css-cascade/all-prop-revert-noop.html68
-rw-r--r--testing/web-platform/tests/css/css-cascade/all-prop-revert-visited-ref.html3
-rw-r--r--testing/web-platform/tests/css/css-cascade/all-prop-revert-visited.html10
-rw-r--r--testing/web-platform/tests/css/css-cascade/all-prop-unset-color.html23
-rw-r--r--testing/web-platform/tests/css/css-cascade/all-prop-unset-visited-ref.html8
-rw-r--r--testing/web-platform/tests/css/css-cascade/all-prop-unset-visited.html10
-rw-r--r--testing/web-platform/tests/css/css-cascade/at-scope-parsing.html64
-rw-r--r--testing/web-platform/tests/css/css-cascade/idlharness.html36
-rw-r--r--testing/web-platform/tests/css/css-cascade/import-conditional-001.html32
-rw-r--r--testing/web-platform/tests/css/css-cascade/import-conditional-002.html30
-rw-r--r--testing/web-platform/tests/css/css-cascade/import-conditions.html132
-rw-r--r--testing/web-platform/tests/css/css-cascade/import-removal.html18
-rw-r--r--testing/web-platform/tests/css/css-cascade/important-prop-ref.html19
-rw-r--r--testing/web-platform/tests/css/css-cascade/important-prop.html39
-rw-r--r--testing/web-platform/tests/css/css-cascade/important-transition-manual.html25
-rw-r--r--testing/web-platform/tests/css/css-cascade/important-vs-inline-001.html40
-rw-r--r--testing/web-platform/tests/css/css-cascade/important-vs-inline-002.html42
-rw-r--r--testing/web-platform/tests/css/css-cascade/important-vs-inline-003.html27
-rw-r--r--testing/web-platform/tests/css/css-cascade/inherit-initial.html36
-rw-r--r--testing/web-platform/tests/css/css-cascade/initial-background-color.html42
-rw-r--r--testing/web-platform/tests/css/css-cascade/initial-color-background-001-ref.html20
-rw-r--r--testing/web-platform/tests/css/css-cascade/initial-color-background-001.html36
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-basic.html524
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-counter-style-override.html150
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-cssom-order-reverse-at-property.html94
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-cssom-order-reverse.html137
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-font-face-override.html141
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-import.html294
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-important.html107
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-keyframes-override.html138
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-media-query.html169
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-media-toggle.html30
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-property-override.html154
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-replaceSync-clears-stale.html56
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-rules-cssom.html113
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-slotted-rule.html33
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-statement-before-import.html157
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-statement-copy-crash.html10
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing-important.html18
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing-ref.html10
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing.html20
-rw-r--r--testing/web-platform/tests/css/css-cascade/layer-vs-inline-style.html60
-rw-r--r--testing/web-platform/tests/css/css-cascade/parsing/all-invalid.html24
-rw-r--r--testing/web-platform/tests/css/css-cascade/parsing/all-valid.html20
-rw-r--r--testing/web-platform/tests/css/css-cascade/parsing/layer-import-parsing.html79
-rw-r--r--testing/web-platform/tests/css/css-cascade/parsing/layer.html25
-rw-r--r--testing/web-platform/tests/css/css-cascade/parsing/supports-import-parsing.html78
-rw-r--r--testing/web-platform/tests/css/css-cascade/presentational-hints-cascade.html39
-rw-r--r--testing/web-platform/tests/css/css-cascade/presentational-hints-rollback.html125
-rw-r--r--testing/web-platform/tests/css/css-cascade/reference/all-green.html1
-rw-r--r--testing/web-platform/tests/css/css-cascade/reference/ref-filled-green-100px-square.xht19
-rw-r--r--testing/web-platform/tests/css/css-cascade/reference/ref-green-text.html9
-rw-r--r--testing/web-platform/tests/css/css-cascade/resources/scope.css4
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-layer-001.html26
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-layer-002.html24
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-layer-003.html30
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-layer-004.html28
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-layer-005.html34
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-layer-006.html34
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-layer-007.html40
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-layer-008.html43
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-layer-009.html16
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-layer-010.html22
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-layer-011.html29
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-layer-012.html16
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-layer-013.html28
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-layer-014.html29
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-layer-015-ref.html5
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-layer-015.html14
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-val-001.html41
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-val-002.html41
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-val-003.html29
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-val-004.html22
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-val-005.html40
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-val-006.html28
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-val-007.html34
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-val-008.html32
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-val-009.html30
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-val-010.html36
-rw-r--r--testing/web-platform/tests/css/css-cascade/revert-val-011.html35
-rw-r--r--testing/web-platform/tests/css/css-cascade/scope-deep.html52
-rw-r--r--testing/web-platform/tests/css/css-cascade/scope-evaluation.html513
-rw-r--r--testing/web-platform/tests/css/css-cascade/scope-implicit-external.html30
-rw-r--r--testing/web-platform/tests/css/css-cascade/scope-implicit.html173
-rw-r--r--testing/web-platform/tests/css/css-cascade/scope-invalidation.html170
-rw-r--r--testing/web-platform/tests/css/css-cascade/scope-nesting.html170
-rw-r--r--testing/web-platform/tests/css/css-cascade/scope-proximity.html123
-rw-r--r--testing/web-platform/tests/css/css-cascade/scope-shadow.html60
-rw-r--r--testing/web-platform/tests/css/css-cascade/scope-specificity.html38
-rw-r--r--testing/web-platform/tests/css/css-cascade/support/test-green.css4
-rw-r--r--testing/web-platform/tests/css/css-cascade/support/test-red.css4
-rw-r--r--testing/web-platform/tests/css/css-cascade/unset-val-001.html45
-rw-r--r--testing/web-platform/tests/css/css-cascade/unset-val-002.html35
-rw-r--r--testing/web-platform/tests/css/css-cascade/unset-value-storage.html24
106 files changed, 6575 insertions, 0 deletions
diff --git a/testing/web-platform/tests/css/css-cascade/META.yml b/testing/web-platform/tests/css/css-cascade/META.yml
new file mode 100644
index 0000000000..96467729fe
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/META.yml
@@ -0,0 +1,4 @@
+spec: https://drafts.csswg.org/css-cascade/
+suggested_reviewers:
+ - fantasai
+ - tabatkins
diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-001-ref.html b/testing/web-platform/tests/css/css-cascade/all-prop-001-ref.html
new file mode 100644
index 0000000000..e255585783
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/all-prop-001-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: all shorthand Reference File</title>
+</head>
+<body>
+ <p>Test passes if the digits are <strong>in order</strong> and there is <strong>no red</strong>.</p>
+
+ <div class="test">123 456 789</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-001.html b/testing/web-platform/tests/css/css-cascade/all-prop-001.html
new file mode 100644
index 0000000000..9c07dfe1b8
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/all-prop-001.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: "all" shorthand property excludes "direction" and "unicode-bidi"</title>
+ <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact">
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="http://www.w3.org/TR/css-cascade-3/#all-shorthand">
+ <link rel="help" href="http://www.w3.org/TR/css-cascade-4/#all-shorthand">
+ <link rel="match" href="all-prop-001-ref.html">
+ <meta name="assert" content="Test passes if 'all' resets properties other than 'direction' and 'unicode-bidi'.">
+ <style>
+.test {
+ /* these must not be overridden */
+ direction: rtl;
+ unicode-bidi: bidi-override;
+}
+
+.test, bdo {
+ /* all of these must be overridden */
+ border: solid red;
+ background: red;
+ color: red;
+ text-decoration: line-through;
+ font: bold italic small-caps 20px monospace;
+ outline: solid red;
+ float: left;
+ letter-spacing: 1em;
+ display: list-item;
+ text-align: center;
+ width: 0.5em;
+ margin: 10em;
+ overflow: scroll;
+}
+
+.test, bdo {
+ all: initial;
+ /* if incorrectly implemented, this causes direction: initial; unicode-bidi: initial;
+ which is the same as direction:ltr; unicode-bidi: normal */
+}
+ </style>
+</head>
+<body>
+ <p>Test passes if the digits are <strong>in order</strong> and there is <strong>no red</strong>.</p>
+
+ <div class="test"><bdo dir=rtl>987 <span>654</span></bdo> 321</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-002.html b/testing/web-platform/tests/css/css-cascade/all-prop-002.html
new file mode 100644
index 0000000000..1e5b450ce1
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/all-prop-002.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: all:inherit includes display:inherit</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="http://www.w3.org/TR/css-cascade-3/#all-shorthand">
+ <link rel="help" href="http://www.w3.org/TR/css-cascade-4/#all-shorthand">
+ <link rel="match" href="reference/ref-filled-green-100px-square.xht">
+ <meta name="assert" content="all:inherit should cause display:inherit so the red divs will display inline/horizontal (like their parent span) rather than block/vertical (like the default UA style for a div).">
+ <style>
+.inline {
+ all: inherit;/* inherit display:inline from span */
+}
+.half-red-sq {
+ /* half of a red square */
+ display: inline-block;
+ width: 50px;
+ height: 100px;
+ background-color: red;
+}
+#positioned {
+ position: relative;
+}
+#green-sq {
+ position: absolute;/* put higher on Z axis */
+ width: 100px;
+ height: 100px;
+ background-color: green;
+}
+ </style>
+</head>
+<body>
+ <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+ <div id="positioned">
+ <div id="green-sq"></div>
+ <span>
+ <div class="inline"><div class="half-red-sq"></div></div><div class="inline"><div class="half-red-sq"></div></div>
+ </span>
+ </div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-inherit-color.html b/testing/web-platform/tests/css/css-cascade/all-prop-inherit-color.html
new file mode 100644
index 0000000000..6cd8425cb9
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/all-prop-inherit-color.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: "color" property preceded by "all: initial"</title>
+ <link rel="help" href="https://www.w3.org/TR/css-cascade-4/#all-shorthand">
+ <link rel="match" href="reference/ref-green-text.html">
+ <meta name="assert" content="Own 'color', preceded by 'all: inherit', overrides inherited 'color'.">
+ <style>
+ .outer {
+ color: red;
+ }
+
+ .inner {
+ all: inherit;
+ color: green;
+ }
+ </style>
+</head>
+<body>
+ <p class="outer"><span class="inner">Test passes if this text is green.</span></p>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-initial-color.html b/testing/web-platform/tests/css/css-cascade/all-prop-initial-color.html
new file mode 100644
index 0000000000..83a78bd086
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/all-prop-initial-color.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: "color" property preceded by "all: initial"</title>
+ <link rel="help" href="https://www.w3.org/TR/css-cascade-4/#all-shorthand">
+ <link rel="match" href="reference/ref-green-text.html">
+ <meta name="assert" content="Own 'color', preceded by 'all: initial', overrides inherited 'color'.">
+ <style>
+ .outer {
+ color: red;
+ }
+
+ .inner {
+ all: initial;
+ color: green;
+ }
+ </style>
+</head>
+<body>
+ <p class="outer"><span class="inner">Test passes if this text is green.</span></p>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-initial-visited-ref.html b/testing/web-platform/tests/css/css-cascade/all-prop-initial-visited-ref.html
new file mode 100644
index 0000000000..f596b559b0
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/all-prop-initial-visited-ref.html
@@ -0,0 +1,3 @@
+<!doctype html>
+<title>CSS Test: Reference</title>
+<span style="color:green">This text must be green.</a>
diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-initial-visited.html b/testing/web-platform/tests/css/css-cascade/all-prop-initial-visited.html
new file mode 100644
index 0000000000..6fb7936652
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/all-prop-initial-visited.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<title>CSS Cascade: Apply all:initial to a visited link overriding with a color</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#initial">
+<link rel="match" href="all-prop-initial-visited-ref.html">
+<style>
+ a { all: initial }
+ a:visited { color: green }
+</style>
+<a href="">This text must be green.</a>
diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-initial-xml.html b/testing/web-platform/tests/css/css-cascade/all-prop-initial-xml.html
new file mode 100644
index 0000000000..a04956a52d
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/all-prop-initial-xml.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<title>all: initial on unknown XML tree</title>
+<link rel=help href=https://www.w3.org/TR/css-cascade-3/#all-shorthand>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+const iframe = document.createElement("iframe");
+const setup_test = async_test("setup");
+iframe.onload = setup_test.step_func_done(function() {
+ const root = iframe.contentDocument.documentElement;
+ // we need the empty stylesheet to avoid default XSLT views of the XML
+ const style = iframe.contentDocument.createElementNS("http://www.w3.org/1999/xhtml", "style");
+ root.appendChild(style);
+ const cs = iframe.contentWindow.getComputedStyle(root);
+ let actual_initial = Object.create(null);
+ for (let i = 0; i < cs.length; i++) {
+ let prop_name = cs[i];
+ actual_initial[prop_name] = cs[prop_name];
+ }
+ test(() => {
+ style.textContent = ":root { color: blue }";
+ assert_equals(cs["color"], "rgb(0, 0, 255)");
+ }, "stylesheet takes effect");
+ style.textContent = ":root { all: initial; direction: initial; unicode-bidi: initial; } style { display: none; }";
+ for (let prop_name in actual_initial) {
+ test(() => {
+ assert_equals(cs[prop_name], actual_initial[prop_name]);
+ }, prop_name);
+ }
+});
+iframe.src = URL.createObjectURL(new Blob(["<foo/>"], { type: "application/xml" }));
+document.body.appendChild(iframe);
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-revert-color.html b/testing/web-platform/tests/css/css-cascade/all-prop-revert-color.html
new file mode 100644
index 0000000000..786bd08109
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/all-prop-revert-color.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: "color" property preceded by "all: revert"</title>
+ <link rel="help" href="https://www.w3.org/TR/css-cascade-4/#all-shorthand">
+ <link rel="match" href="reference/ref-green-text.html">
+ <meta name="assert" content="Own 'color', preceded by 'all: revert', overrides inherited 'color'.">
+ <style>
+ .outer {
+ color: red;
+ }
+
+ .inner {
+ all: revert;
+ color: green;
+ }
+ </style>
+</head>
+<body>
+ <p class="outer"><span class="inner">Test passes if this text is green.</span></p>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-revert-layer-noop.html b/testing/web-platform/tests/css/css-cascade/all-prop-revert-layer-noop.html
new file mode 100644
index 0000000000..5c31c5803c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/all-prop-revert-layer-noop.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Cascade: "all: revert-layer"</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#revert-layer">
+<meta name="assert" content="Checks that adding 'all: revert-layer' inside @layer has no effect on elements with no other author rules.">
+<!-- Split into chunks to avoid timeouts. -->
+<meta name="variant" content="?include=0">
+<meta name="variant" content="?include=1">
+<meta name="variant" content="?include=2">
+<meta name="variant" content="?include=3">
+<meta name="variant" content="?include=4">
+<meta name="variant" content="?include=5">
+<meta name="variant" content="?include=6">
+<meta name="variant" content="?include=7">
+<script>
+ const CHUNKS = 8;
+</script>
+
+<style>
+@layer {
+ .revert-all {
+ all: revert-layer;
+ }
+}
+</style>
+
+<div id="log"></div>
+<div id="wrapper"></div>
+
+<script src="/common/subset-tests-by-key.js"></script>
+<script src="/html/resources/common.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+function cloneStyle(style) {
+ const clone = Object.create(null);
+ for (let property of style) {
+ clone[property] = style.getPropertyValue(property);
+ }
+ return clone;
+}
+
+function assertSameClones(clone1, clone2, callback) {
+ for (let property in clone1) {
+ const value1 = clone1[property];
+ const value2 = clone2[property];
+ // assert_equals is slow, so only call it if it's going to fail.
+ if (value1 !== value2) {
+ assert_equals(value1, value2, property);
+ }
+ }
+}
+
+const wrapper = document.getElementById("wrapper");
+const elementNames = [...HTML5_ELEMENTS, "math", "svg", "z-custom"].sort();
+for (let i = 0; i < elementNames.length; ++i) {
+ let elementName = elementNames[i];
+ let chunk = i % CHUNKS;
+ subsetTestByKey(chunk.toString(), test, function() {
+ const element = document.createElement(elementName);
+ wrapper.appendChild(element);
+ const style = getComputedStyle(element);
+ const clonedBaseStyle = cloneStyle(style);
+ element.classList.add("revert-all");
+ const clonedRevertedStyle = cloneStyle(style);
+ assertSameClones(clonedRevertedStyle, clonedBaseStyle);
+ }, elementName);
+}
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-revert-layer.html b/testing/web-platform/tests/css/css-cascade/all-prop-revert-layer.html
new file mode 100644
index 0000000000..ac665a9bed
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/all-prop-revert-layer.html
@@ -0,0 +1,459 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Cascade: "all: revert-layer"</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#revert-layer">
+<meta name="assert" content="Checks that adding 'all: revert-layer' on the last layer has no effect.">
+<style>
+/* Set properties to a value different than the initial one. */
+#nothing {
+ accent-color: #123;
+ align-content: baseline;
+ align-items: baseline;
+ align-self: baseline;
+ align-tracks: baseline;
+ alignment-baseline: central;
+ alt: "a";
+ animation-composition: add;
+ animation-delay: 123s;
+ animation-delay-start: 123s;
+ animation-delay-end: 456s;
+ animation-direction: reverse;
+ animation-duration: 123s;
+ animation-fill-mode: both;
+ animation-iteration-count: 123;
+ animation-name: \.;
+ animation-play-state: paused;
+ animation-timeline: none;
+ animation-timing-function: linear;
+ app-region: drag;
+ appearance: auto;
+ aspect-ratio: 3 / 4;
+ backdrop-filter: invert(1);
+ backface-visibility: hidden;
+ background-attachment: fixed;
+ background-blend-mode: overlay;
+ background-clip: content-box;
+ background-color: #123;
+ background-image: url("#ref");
+ background-origin: border-box;
+ background-position: 123px;
+ background-repeat: round;
+ background-size: 123px;
+ baseline-shift: 123px;
+ block-size: 123px;
+ border-block-end: 123px dashed #123;
+ border-block-start: 123px dashed #123;
+ border-bottom: 123px dashed #123;
+ border-collapse: collapse;
+ border-end-end-radius: 123px;
+ border-end-start-radius: 123px;
+ border-image-outset: 123;
+ border-image-repeat: round;
+ border-image-slice: 123;
+ border-image-source: url("#ref");
+ border-image-width: 123px;
+ border-inline-end: 123px dashed #123;
+ border-inline-start: 123px dashed #123;
+ border-left: 123px dashed #123;
+ border-radius: 123px;
+ border-right: 123px dashed #123;
+ border-start-end-radius: 123px;
+ border-start-start-radius: 123px;
+ border-spacing: 123px;
+ border-top: 123px dashed #123;
+ bottom: 123px;
+ box-decoration-break: clone;
+ box-shadow: #123 123px 123px 123px 123px;
+ box-sizing: border-box;
+ break-after: avoid;
+ break-before: avoid;
+ break-inside: avoid;
+ buffered-rendering: static;
+ caption-side: bottom;
+ caret-color: #123;
+ clear: both;
+ clip: rect(123px, 123px, 123px, 123px);
+ clip-path: url("#ref");
+ clip-rule: evenodd;
+ color: #123;
+ color-interpolation: auto;
+ color-interpolation-filters: auto;
+ color-rendering: optimizespeed;
+ color-scheme: dark;
+ column-count: 123;
+ column-fill: auto;
+ column-gap: 123px;
+ column-rule-color: #123;
+ column-rule-style: dashed;
+ column-rule-width: 123px;
+ column-span: all;
+ column-width: 123px;
+ contain: size;
+ contain-intrinsic-block-size: 123px;
+ contain-intrinsic-inline-size: 123px;
+ contain-intrinsic-size: 123px 123px;
+ container-name: foo;
+ container-type: size;
+ content: "b";
+ counter-increment: add 123;
+ counter-reset: add 123;
+ counter-set: add 123;
+ cursor: none;
+ cx: 123px;
+ cy: 123px;
+ d: path("M 1 1");
+ direction: rtl;
+ display: flow-root;
+ dominant-baseline: middle;
+ empty-cells: hide;
+ fill: #123;
+ fill-opacity: 0.123;
+ fill-rule: evenodd;
+ filter: url("#ref");
+ flex-basis: 123px;
+ flex-direction: column;
+ flex-grow: 123;
+ flex-shrink: 123;
+ flex-wrap: wrap;
+ float: right;
+ flood-color: #123;
+ flood-opacity: 0.123;
+ font-family: "c";
+ font-feature-settings: "smcp";
+ font-kerning: none;
+ font-language-override: "d";
+ font-optical-sizing: none;
+ font-palette: dark;
+ font-size: 123px;
+ font-size-adjust: 123;
+ font-stretch: 123%;
+ font-style: italic;
+ font-synthesis: none;
+ font-variant-alternates: historical-forms;
+ font-variant-caps: small-caps;
+ font-variant-east-asian: full-width;
+ font-variant-ligatures: none;
+ font-variant-numeric: tabular-nums;
+ font-variant-position: super;
+ font-variation-settings: "smcp" 1;
+ font-weight: 123;
+ glyph-orientation-horizontal: 123deg;
+ glyph-orientation-vertical: 123deg;
+ grid-auto-columns: 123px;
+ grid-auto-flow: column;
+ grid-auto-rows: 123px;
+ grid-column-end: 123;
+ grid-column-start: 123;
+ grid-row-end: 123;
+ grid-row-start: 123;
+ grid-template-areas: ".";
+ grid-template-columns: 123fr;
+ grid-template-rows: 123fr;
+ hanging-punctuation: first;
+ height: 123px;
+ hyphenate-character: "e";
+ hyphens: auto;
+ image-orientation: none;
+ image-rendering: pixelated;
+ ime-mode: normal;
+ initial-letter: 123;
+ inline-size: 123px;
+ input-security: none;
+ inset-block-end: 123px;
+ inset-block-start: 123px;
+ inset-inline-end: 123px;
+ inset-inline-start: 123px;
+ isolation: isolate;
+ justify-content: center;
+ justify-items: baseline;
+ justify-self: baseline;
+ justify-tracks: center;
+ kerning: 123px;
+ left: 123px;
+ letter-spacing: 123px;
+ lighting-color: #123;
+ line-break: anywhere;
+ line-height: 123px;
+ line-height-step: 123px;
+ list-style-image: url("#ref");
+ list-style-position: inside;
+ list-style-type: square;
+ margin-block-end: 123px;
+ margin-block-start: 123px;
+ margin-bottom: 123px;
+ margin-inline-end: 123px;
+ margin-inline-start: 123px;
+ margin-left: 123px;
+ margin-right: 123px;
+ margin-top: 123px;
+ marker-end: url("#ref");
+ marker-mid: url("#ref");
+ marker-start: url("#ref");
+ mask-clip: content-box;
+ mask-composite: exclude;
+ mask-image: url("#ref");
+ mask-mode: alpha;
+ mask-origin: content-box;
+ mask-position-x: 123px;
+ mask-position-y: 123px;
+ mask-repeat: round;
+ mask-size: 123px;
+ mask-type: alpha;
+ masonry-auto-flow: ordered;
+ math-depth: 123;
+ math-shift: compact;
+ math-style: compact;
+ max-block-size: 123px;
+ max-height: 123px;
+ max-inline-size: 123px;
+ max-width: 123px;
+ min-block-size: 123px;
+ min-height: 123px;
+ min-inline-size: 123px;
+ min-width: 123px;
+ mix-blend-mode: overlay;
+ object-fit: contain;
+ object-overflow: visible;
+ object-position: 123px 123%;
+ object-view-box: inset(123px);
+ offset-anchor: 123px 123%;
+ offset-distance: 123px;
+ offset-path: path("M 1 1");
+ offset-position: 123px;
+ offset-rotate: 123deg;
+ opacity: 0.123;
+ order: 123;
+ orphans: 123;
+ outline-color: #123;
+ outline-offset: 123px;
+ outline-style: auto;
+ outline-width: 123px;
+ overflow-anchor: none;
+ overflow-block: auto;
+ overflow-clip-margin: 123px;
+ overflow-inline: hidden;
+ overflow-wrap: anywhere;
+ overflow-x: auto;
+ overflow-y: hidden;
+ overscroll-behavior-block: contain;
+ overscroll-behavior-inline: contain;
+ overscroll-behavior-x: contain;
+ overscroll-behavior-y: contain;
+ padding-block-end: 123px;
+ padding-block-start: 123px;
+ padding-bottom: 123px;
+ padding-inline-end: 123px;
+ padding-inline-start: 123px;
+ padding-left: 123px;
+ padding-right: 123px;
+ padding-top: 123px;
+ paint-order: fill;
+ perspective: 123px;
+ perspective-origin: 123px 123%;
+ pointer-events: all;
+ position: relative;
+ print-color-adjust: exact;
+ quotes: none;
+ r: 123px;
+ resize: both;
+ right: 123px;
+ rotate: 123deg;
+ row-gap: 123px;
+ ruby-align: center;
+ ruby-position: under;
+ rx: 123px;
+ ry: 123px;
+ scale: 123;
+ scroll-behavior: smooth;
+ scroll-margin-block-end: 123px;
+ scroll-margin-block-start: 123px;
+ scroll-margin-bottom: 123px;
+ scroll-margin-inline-end: 123px;
+ scroll-margin-inline-start: 123px;
+ scroll-margin-left: 123px;
+ scroll-margin-right: 123px;
+ scroll-margin-top: 123px;
+ scroll-padding-block-end: 123px;
+ scroll-padding-block-start: 123px;
+ scroll-padding-bottom: 123px;
+ scroll-padding-inline-end: 123px;
+ scroll-padding-inline-start: 123px;
+ scroll-padding-left: 123px;
+ scroll-padding-right: 123px;
+ scroll-padding-top: 123px;
+ scroll-snap-align: center;
+ scroll-snap-stop: always;
+ scroll-snap-type: both;
+ scrollbar-color: #123 #123;
+ scrollbar-gutter: stable;
+ scrollbar-width: none;
+ shape-image-threshold: 123;
+ shape-margin: 123px;
+ shape-outside: border-box;
+ shape-rendering: optimizespeed;
+ speak: spell-out;
+ speak-as: spell-out;
+ stop-color: #123;
+ stop-opacity: 0.123;
+ stroke: #123;
+ stroke-color: #123;
+ stroke-dasharray: 123px;
+ stroke-dashoffset: 123px;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ stroke-miterlimit: 123;
+ stroke-opacity: 0.123;
+ stroke-width: 123px;
+ tab-size: 123;
+ table-layout: fixed;
+ text-align: center;
+ text-align-last: center;
+ text-anchor: middle;
+ text-combine-upright: all;
+ text-decoration-color: #123;
+ text-decoration-line: underline;
+ text-decoration-skip-ink: none;
+ text-decoration-style: dashed;
+ text-decoration-thickness: 123px;
+ text-emphasis-color: #123;
+ text-emphasis-position: under right;
+ text-emphasis-style: dot;
+ text-indent: 123px;
+ text-justify: none;
+ text-orientation: sideways;
+ text-overflow: ellipsis;
+ text-rendering: optimizespeed;
+ text-shadow: #123 123px 123px 123px;
+ text-size-adjust: none;
+ text-transform: lowercase;
+ text-underline-offset: 123px;
+ text-underline-position: under;
+ top: 123px;
+ touch-action: none;
+ transform: scale(-1);
+ transform-box: fill-box;
+ transform-origin: 123px 123px 123px;
+ transform-style: preserve-3d;
+ transition-delay: 123s;
+ transition-duration: 123s;
+ transition-property: add;
+ transition-timing-function: linear;
+ translate: 123px;
+ unicode-bidi: plaintext;
+ user-select: all;
+ vector-effect: non-scaling-stroke;
+ vertical-align: 123px;
+ visibility: collapse;
+ white-space: pre;
+ widows: 123;
+ width: 123px;
+ will-change: height;
+ word-break: break-word;
+ word-spacing: 123px;
+ word-wrap: break-word;
+ writing-mode: vertical-lr;
+ x: 123px;
+ y: 123px;
+ z-index: 123;
+ zoom: 123;
+}
+
+@layer layer1 {
+ /* Reset properties to their initial value */
+ #target {
+ all: initial;
+ }
+}
+
+@layer layer2 {
+ /* This will be populated with properties set to a non-initial value */
+ #target {}
+}
+
+@layer layer3 {
+ /* This should roll back to the values from the previous layer */
+ #target.rollback {
+ all: revert-layer;
+ }
+}
+</style>
+
+<div id="log"></div>
+
+<!-- This custom element is unlikely to get important UA styles -->
+<foo-bar id="target"></foo-bar>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const { sheet } = document.querySelector("style");
+const nonInitialStyle = sheet.cssRules[0].style;
+const layer2Style = sheet.cssRules[2].cssRules[0].style;
+
+const target = document.getElementById("target");
+const cs = getComputedStyle(target);
+
+// Some properties can be forced to compute to their initial value
+// unless another property is set to a certain value.
+function prerequisites(property) {
+ switch (property) {
+ case "border-block-end-width":
+ case "border-block-start-width":
+ case "border-bottom-width":
+ case "border-inline-end-width":
+ case "border-inline-start-width":
+ case "border-left-width":
+ case "border-right-width":
+ case "border-top-width":
+ return "border-style: solid";
+ case "column-rule-width":
+ return "column-rule-style: solid";
+ case "outline-width":
+ return "outline-style: solid";
+ case "rotate":
+ case "scale":
+ case "transform":
+ case "transform-style":
+ case "translate":
+ return "display: block";
+ default:
+ return "";
+ }
+}
+
+const initialValues = Object.create(null);
+for (let property of cs) {
+ if (!property.startsWith("-")) {
+ initialValues[property] = cs.getPropertyValue(property);
+ }
+}
+
+for (let property in initialValues) {
+ // Skip property if the stylesheet above doesn't provide a non-initial value.
+ // This is to avoid having to update the test every time a new CSS property is added.
+ const nonInitialValue = nonInitialStyle.getPropertyValue(property);
+ if (nonInitialValue === "") {
+ continue;
+ }
+
+ test(function() {
+ const initialValue = initialValues[property];
+ assert_not_equals(initialValue, "", "Should have the initial value.");
+
+ this.add_cleanup(() => {
+ layer2Style.cssText = "";
+ target.classList.remove("rollback");
+ });
+
+ layer2Style.cssText = prerequisites(property);
+ layer2Style.setProperty(property, nonInitialValue);
+ const changedValue = cs.getPropertyValue(property);
+ assert_not_equals(changedValue, initialValue, "Should get a different computed value.");
+
+ target.classList.add("rollback");
+ const revertedValue = cs.getPropertyValue(property);
+ assert_equals(revertedValue, changedValue, "Layer 3 should rollback to layer 2.");
+ }, property);
+}
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-revert-noop.html b/testing/web-platform/tests/css/css-cascade/all-prop-revert-noop.html
new file mode 100644
index 0000000000..e8f560d685
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/all-prop-revert-noop.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Cascade: "all: revert"</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-cascade-4/#default">
+<meta name="assert" content="Checks that adding 'all: revert' has no effect on elements with no other author rules.">
+<!-- Split into chunks to avoid timeouts. -->
+<meta name="variant" content="?include=0">
+<meta name="variant" content="?include=1">
+<meta name="variant" content="?include=2">
+<meta name="variant" content="?include=3">
+<meta name="variant" content="?include=4">
+<meta name="variant" content="?include=5">
+<meta name="variant" content="?include=6">
+<meta name="variant" content="?include=7">
+<script>
+ const CHUNKS = 8;
+</script>
+
+<style>
+.revert-all {
+ all: revert;
+}
+</style>
+
+<div id="log"></div>
+<div id="wrapper"></div>
+
+<script src="/common/subset-tests-by-key.js"></script>
+<script src="/html/resources/common.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+function cloneStyle(style) {
+ const clone = Object.create(null);
+ for (let property of style) {
+ clone[property] = style.getPropertyValue(property);
+ }
+ return clone;
+}
+
+function assertSameClones(clone1, clone2, callback) {
+ for (let property in clone1) {
+ const value1 = clone1[property];
+ const value2 = clone2[property];
+ // assert_equals is slow, so only call it if it's going to fail.
+ if (value1 !== value2) {
+ assert_equals(value1, value2, property);
+ }
+ }
+}
+
+const wrapper = document.getElementById("wrapper");
+const elementNames = [...HTML5_ELEMENTS, "math", "svg", "z-custom"].sort();
+for (let i = 0; i < elementNames.length; ++i) {
+ let elementName = elementNames[i];
+ let chunk = i % CHUNKS;
+ subsetTestByKey(chunk.toString(), test, function() {
+ const element = document.createElement(elementName);
+ wrapper.appendChild(element);
+ const style = getComputedStyle(element);
+ const clonedBaseStyle = cloneStyle(style);
+ element.classList.add("revert-all");
+ const clonedRevertedStyle = cloneStyle(style);
+ assertSameClones(clonedRevertedStyle, clonedBaseStyle);
+ }, elementName);
+}
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-revert-visited-ref.html b/testing/web-platform/tests/css/css-cascade/all-prop-revert-visited-ref.html
new file mode 100644
index 0000000000..0ef326c272
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/all-prop-revert-visited-ref.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<title>CSS Cascade: all:revert in :visited</title>
+<a href="">Test passes if this text has UA style for visited links</a> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-revert-visited.html b/testing/web-platform/tests/css/css-cascade/all-prop-revert-visited.html
new file mode 100644
index 0000000000..9df1277aca
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/all-prop-revert-visited.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>CSS Cascade: all:revert in :visited</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#all-shorthand">
+<link rel="match" href="all-prop-revert-visited-ref.html">
+<style>
+ :root { color: red; }
+ a:visited { color: red; }
+ a:visited { all: revert; }
+</style>
+<a href="">Test passes if this text has UA style for visited links</a>
diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-unset-color.html b/testing/web-platform/tests/css/css-cascade/all-prop-unset-color.html
new file mode 100644
index 0000000000..457901f841
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/all-prop-unset-color.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: "color" property preceded by "all: unset"</title>
+ <link rel="help" href="https://www.w3.org/TR/css-cascade-4/#all-shorthand">
+ <link rel="match" href="reference/ref-green-text.html">
+ <meta name="assert" content="Own 'color', preceded by 'all: unset', overrides inherited 'color'.">
+ <style>
+ .outer {
+ color: red;
+ }
+
+ .inner {
+ all: unset;
+ color: green;
+ }
+ </style>
+</head>
+<body>
+ <p class="outer"><span class="inner">Test passes if this text is green.</span></p>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-unset-visited-ref.html b/testing/web-platform/tests/css/css-cascade/all-prop-unset-visited-ref.html
new file mode 100644
index 0000000000..e67b972768
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/all-prop-unset-visited-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<title>CSS Cascade: all:unset in :visited</title>
+<style>
+ :root { color: green; }
+ a:visited { color: red; }
+ a:visited { color: unset; }
+</style>
+<a href="">Test passes if this text is green.</a>
diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-unset-visited.html b/testing/web-platform/tests/css/css-cascade/all-prop-unset-visited.html
new file mode 100644
index 0000000000..598d3f5edc
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/all-prop-unset-visited.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>CSS Cascade: all:unset in :visited</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#all-shorthand">
+<link rel="match" href="all-prop-unset-visited-ref.html">
+<style>
+ :root { color: green; }
+ a:visited { color: red; }
+ a:visited { all: unset; }
+</style>
+<a href="">Test passes if this text is green.</a>
diff --git a/testing/web-platform/tests/css/css-cascade/at-scope-parsing.html b/testing/web-platform/tests/css/css-cascade/at-scope-parsing.html
new file mode 100644
index 0000000000..a37f63b06b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/at-scope-parsing.html
@@ -0,0 +1,64 @@
+<!doctype html>
+<title>@scope: parsing</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<main id=main></main>
+<script>
+ function test_valid(actual, expected) {
+ if (expected === undefined)
+ expected = actual;
+ test(t => {
+ t.add_cleanup(() => main.replaceChildren());
+ let style = document.createElement('style');
+ style.textContent = `${actual}{}`;
+ main.append(style);
+ assert_equals(style.sheet.rules.length, 1);
+ let rule = style.sheet.rules[0];
+ assert_equals(rule.cssText, `${expected} {\n}`);
+ }, `${actual} is valid`);
+ }
+
+ function test_invalid(actual) {
+ test(t => {
+ t.add_cleanup(() => main.replaceChildren());
+ let style = document.createElement('style');
+ style.textContent = `${actual}{}`;
+ main.append(style);
+ assert_equals(style.sheet.rules.length, 0);
+ }, `${actual} is not valid`);
+ }
+
+ test_valid('@scope (.a)');
+ test_valid('@scope (.a + .b)');
+ test_valid('@scope (.a:hover)');
+ test_valid('@scope (.a:hover, #b, div)');
+ test_valid('@scope (:is(div, span))');
+
+ test_valid('@scope (.a) to (.b)');
+ test_valid('@scope (.a)to (.b)', '@scope (.a) to (.b)');
+ test_valid('@scope (.a) to (.b:hover, #c, div)');
+ test_valid('@scope');
+ test_valid('@scope (.a) to (&)');
+ test_valid('@scope (.a) to (& > &)');
+ test_valid('@scope (.a) to (> .b)');
+ test_valid('@scope (.a) to (+ .b)');
+ test_valid('@scope (.a) to (~ .b)');
+
+ // Forgiving behavior:
+ test_valid('@scope (.c <> .d)', '@scope ()');
+ test_valid('@scope (.a, .c <> .d)', '@scope (.a)');
+ test_valid('@scope (.a <> .b, .c)', '@scope (.c)');
+ test_valid('@scope (div::before)', '@scope ()');
+ test_valid('@scope (div::after)', '@scope ()');
+ test_valid('@scope (slotted(div))', '@scope ()');
+ test_valid('@scope (.a) to (div::before)', '@scope (.a)');
+
+ test_invalid('@scope (.a) unknown (.c)');
+ test_invalid('@scope (.a) to unknown (.c)');
+ test_invalid('@scope (.a) 1px (.c)');
+ test_invalid('@scope (.a) to unknown(c)');
+ test_invalid('@scope unknown(.a)');
+ test_invalid('@scope 1px');
+ test_invalid('@scope creep');
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/idlharness.html b/testing/web-platform/tests/css/css-cascade/idlharness.html
new file mode 100644
index 0000000000..9bde23b946
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/idlharness.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<title>CSS Cascade Layers IDL tests</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layer-apis">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+
+<style>
+@layer bar, baz;
+@import url('data:text/css,') layer(qux);
+@layer foo { }
+</style>
+
+<script>
+ 'use strict';
+ idl_test(
+ ['css-cascade'],
+ ['cssom'],
+ idl_array => {
+ try {
+ self.statement = document.styleSheets[0].cssRules.item(0);
+ self.layeredImport = document.styleSheets[0].cssRules.item(1);
+ self.block = document.styleSheets[0].cssRules.item(2);
+ } catch (e) {
+ // Will be surfaced when any rule is undefined below.
+ }
+
+ idl_array.add_objects({
+ CSSLayerBlockRule: ['block'],
+ CSSLayerStatementRule: ['statement'],
+ CSSImportRule: ['layeredImport']
+ });
+ }
+ );
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/import-conditional-001.html b/testing/web-platform/tests/css/css-cascade/import-conditional-001.html
new file mode 100644
index 0000000000..a841f26545
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/import-conditional-001.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: @import with basic media query</title>
+ <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact">
+ <link rel="help" href="https://www.w3.org/TR/css-cascade-3/#conditional-import">
+ <link rel="help" href="https://www.w3.org/TR/css-cascade-4/#conditional-import">
+ <link rel="help" href="https://www.w3.org/TR/css3-mediaqueries/#syntax">
+ <link rel="match" href="reference/ref-filled-green-100px-square.xht">
+ <meta name="assert" content="Test passes on visual UAs if @import can be combined with a media query.">
+ <style>
+ @import "support/test-red.css";
+ @import "support/test-green.css"
+ (min-width: 1px) and /* assuming screen < 1km */ (max-width: 40000in), nonsense;
+ @import "support/test-red.css"
+ (max-width: 1px), nonsense;
+ div {
+ box-sizing: border-box;
+ width: 100px;
+ height: 100px;
+ padding: 5px; /* Avoids text antialiasing issues */
+ background: red;
+ }
+ </style>
+</head>
+<body>
+ <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+
+ <div class="test">FAIL</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/import-conditional-002.html b/testing/web-platform/tests/css/css-cascade/import-conditional-002.html
new file mode 100644
index 0000000000..79e850a742
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/import-conditional-002.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: @import with basic supports condition</title>
+ <link rel="help" href="https://www.w3.org/TR/css-cascade-4/#conditional-import">
+ <link rel="help" href="https://www.w3.org/TR/css-cascade-5/#conditional-import">
+ <link rel="match" href="reference/ref-filled-green-100px-square.xht">
+ <meta name="assert" content="Test passes on visual UAs if @import can be combined with a supports condition.">
+ <style>
+ @import "support/test-red.css";
+ @import "support/test-green.css"
+ supports(display: block);
+ @import "support/test-red.css"
+ supports(foo: bar);
+ div {
+ box-sizing: border-box;
+ width: 100px;
+ height: 100px;
+ padding: 5px; /* Avoids text antialiasing issues */
+ background: red;
+ }
+ </style>
+</head>
+<body>
+ <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+
+ <div class="test"></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/import-conditions.html b/testing/web-platform/tests/css/css-cascade/import-conditions.html
new file mode 100644
index 0000000000..9c1e5c6e87
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/import-conditions.html
@@ -0,0 +1,132 @@
+<!DOCTYPE html>
+<title>CSS Cascade Test: import conditions</title>
+<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#import-conditions">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ @layer {
+ .target { color: red; }
+ }
+</style>
+<div id="target" class="target"></div>
+<script>
+ const testCases = [
+ {
+ importCondition: "supports(display:block)",
+ matches: true
+ },
+ {
+ importCondition: "supports((display:flex))",
+ matches: true
+ },
+ {
+ importCondition: "supports((display:block) and (display:flex))",
+ matches: true
+ },
+ {
+ importCondition: "supports((display:block) and (foo:bar))",
+ matches: false
+ },
+ {
+ importCondition: "supports((display:block) or (display:flex))",
+ matches: true
+ },
+ {
+ importCondition: "supports((display:block) or (foo:bar))",
+ matches: true
+ },
+ {
+ importCondition: "supports(not (display: flex))",
+ matches: false
+ },
+ {
+ importCondition: "supports(display: block !important)",
+ matches: true
+ },
+ {
+ importCondition: "supports(foo:bar)",
+ matches: false
+ },
+ {
+ importCondition: "supports(display:block) (width >= 0px)",
+ matches: true
+ },
+ {
+ importCondition: "(width >= 0px) supports(foo:bar)",
+ matches: false
+ },
+ {
+ importCondition: "(width >= 0px) supports(display:block)",
+ matches: false
+ },
+
+ // selector()
+ {
+ importCondition: "supports(selector(a))",
+ matches: true
+ },
+ {
+ importCondition: "supports(selector(p a))",
+ matches: true
+ },
+ {
+ importCondition: "supports(selector(p > a))",
+ matches: true
+ },
+ {
+ importCondition: "supports(selector(p + a))",
+ matches: true
+ },
+
+ // font-tech()
+ {
+ importCondition: "supports(font-tech(color-COLRv1))",
+ matches: true
+ },
+ {
+ importCondition: "supports(font-tech(invalid))",
+ matches: false
+ },
+
+ // font-format()
+ {
+ importCondition: "supports(font-format(opentype))",
+ matches: true
+ },
+ {
+ importCondition: "supports(font-format(woff))",
+ matches: true
+ },
+ {
+ importCondition: "supports(font-format(invalid))",
+ matches: false
+ },
+ {
+ importCondition: "layer(A.B) supports(font-format(opentype))",
+ matches: true
+ },
+ {
+ importCondition: "layer supports(selector(a))",
+ matches: true
+ },
+ ];
+ let target = document.getElementById("target");
+ for (let testCase of testCases) {
+ promise_test(async t => {
+ let styleElement = document.createElement("style");
+ styleElement.innerText = "@import url(data:text/css,.target{color:green}) " + testCase.importCondition + ";";
+
+ await new Promise(resolve => {
+ styleElement.onload = resolve;
+ styleElement.onerror = resolve;
+ document.head.appendChild(styleElement);
+ });
+
+ try {
+ assert_equals(getComputedStyle(target).color, testCase.matches ? "rgb(0, 128, 0)" : "rgb(255, 0, 0)");
+ } finally {
+ styleElement.remove();
+ }
+ }, testCase.importCondition + " is " + (testCase.matches ? "" : "not ") + "a valid import condition");
+ }
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/import-removal.html b/testing/web-platform/tests/css/css-cascade/import-removal.html
new file mode 100644
index 0000000000..6fb1ea3458
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/import-removal.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title></title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#at-import">
+<link rel="help" href="https://bugs.webkit.org/show_bug.cgi?id=235930">
+<link rel="match" href="../reference/ref-filled-green-100px-square-only.html">
+<meta name="assert" content="Checks that the page is rendered correctly when @import rule is removed with JS.">
+<p>Test passes if there is a filled green square.</p>
+<div style="width:100px; height:100px;"></div>
+<script>
+const style = document.createElement("style");
+document.head.append(style);
+const {sheet} = style;
+sheet.insertRule("@import url('data:text/css,div { background: red !important }');");
+sheet.insertRule("div { background: green }", 1);
+sheet.deleteRule(0);
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/important-prop-ref.html b/testing/web-platform/tests/css/css-cascade/important-prop-ref.html
new file mode 100644
index 0000000000..004679da73
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/important-prop-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Cascade Green Right Square Reference File</title>
+<link rel="author" title="David Burns" href="http://www.theautomatedtester.co.uk">
+<style>
+#success {
+ width: 100px;
+ height: 100px;
+ background-color: green;
+}
+</style>
+<body>
+ <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+
+ <div>
+ <div id="success"></div>
+ </div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/important-prop.html b/testing/web-platform/tests/css/css-cascade/important-prop.html
new file mode 100644
index 0000000000..e8abffdf88
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/important-prop.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: Important vs. Animations</title>
+ <link rel="author" title="David Burns" href="http://www.theautomatedtester.co.uk">
+ <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact">
+ <link rel="help" href="https://drafts.csswg.org/css-cascade/#importance">
+ <link rel="match" href="important-prop-ref.html">
+ <meta name="assert" content="Test passes if normal rules are overridden by animations, important rules override animations, and !important declarations are ignored in animations.">
+ <style>
+ @keyframes override {
+ from, to {
+ background: #f00; color: green;
+ border-color: green; border-color: red !important;
+ }
+ }
+
+ .square {
+ color:#00f;
+ animation: override 1s infinite;
+ width: 80px;
+ height: 80px;
+ border: 10px solid red;
+ text-align: center;
+ }
+ div {
+ background-color:green !important;
+ color: red;
+ }
+ </style>
+</head>
+<body>
+ <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+
+ <div class="square green">FAIL</div>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/important-transition-manual.html b/testing/web-platform/tests/css/css-cascade/important-transition-manual.html
new file mode 100644
index 0000000000..b9abed469f
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/important-transition-manual.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: Author !important vs. Transitions</title>
+ <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact">
+ <link rel="help" href="https://www.w3.org/TR/css-cascade-3/#cascade-sort">
+ <style>
+ .container { padding: 1em 0; border-style: dotted none; border-width: 1px; }
+ .container > div { width: 5em; text-align: center; border: solid; transition: all 3s; }
+
+ .container > .ref { border-color: blue ; color: navy ; background: aqua ; margin: 0.25em ; }
+ :hover > .ref { border-color: aqua ; color: orange ; background: teal ; margin-left: 40% ; }
+
+ :not(:hover) > .test { border-color: blue !important; color: navy !important; background: aqua ; }
+ div > .test { margin: 0.25em !important; }
+ :hover > .test { border-color: aqua !important; color: orange ; background: teal !important; margin-left: 40% !important; }
+ </style>
+
+<p>Test passes if the two boxes transition identically when hovering with a mouse below.
+
+<div class=container>
+ <div class=test>Box 1</div>
+ <div class=ref>Box 2</div>
+</div>
diff --git a/testing/web-platform/tests/css/css-cascade/important-vs-inline-001.html b/testing/web-platform/tests/css/css-cascade/important-vs-inline-001.html
new file mode 100644
index 0000000000..33b33bf943
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/important-vs-inline-001.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: inline style loses to !important</title>
+ <link rel="help" href="https://www.w3.org/TR/css-cascade-4/#cascade-sort">
+ <link rel="author" href="mailto:sesse@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <style>
+ .outer {
+ opacity: 0.5 !important;
+ }
+ </style>
+</head>
+<body>
+ <p class="outer" id="el">Test passes if this text is semi-transparent.</p>
+</body>
+<script>
+test(() => {
+ el.offsetTop;
+ assert_equals(getComputedStyle(el).opacity, "0.5", "style is set correctly");
+});
+test(() => {
+ el.offsetTop;
+ el.style.opacity = 0.75;
+ assert_equals(getComputedStyle(el).opacity, "0.5", "!important has higher priority than adding inline style");
+});
+test(() => {
+ el.offsetTop;
+ el.style.opacity = 1.0;
+ assert_equals(getComputedStyle(el).opacity, "0.5", "!important has higher priority than modifying inline style");
+});
+test(() => {
+ el.offsetTop;
+ el.style.opacity = null;
+ assert_equals(getComputedStyle(el).opacity, "0.5", "!important has higher priority than removing inline style");
+});
+</script>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/important-vs-inline-002.html b/testing/web-platform/tests/css/css-cascade/important-vs-inline-002.html
new file mode 100644
index 0000000000..e16aedc5bb
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/important-vs-inline-002.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: inline style loses to !important</title>
+ <link rel="help" href="https://www.w3.org/TR/css-cascade-4/#cascade-sort">
+ <link rel="author" href="mailto:sesse@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <style>
+ .outer {
+ font-size: 18px !important;
+ line-height: 2em;
+ border: 1px solid black;
+ }
+ </style>
+</head>
+<body>
+ <p class="outer" id="el">Test passes if the line-height is twice the font size.</p>
+</body>
+<script>
+test(() => {
+ el.offsetTop;
+ assert_equals(getComputedStyle(el).lineHeight, "36px", "style is set correctly");
+});
+test(() => {
+ el.offsetTop;
+ el.style.fontSize = "24px";
+ assert_equals(getComputedStyle(el).lineHeight, "36px", "!important has higher priority than adding inline style");
+});
+test(() => {
+ el.offsetTop;
+ el.style.fontSize = "36px";
+ assert_equals(getComputedStyle(el).lineHeight, "36px", "!important has higher priority than modifying inline style");
+});
+test(() => {
+ el.offsetTop;
+ el.style.fontSize = null;
+ assert_equals(getComputedStyle(el).lineHeight, "36px", "!important has higher priority than removing inline style");
+});
+</script>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/important-vs-inline-003.html b/testing/web-platform/tests/css/css-cascade/important-vs-inline-003.html
new file mode 100644
index 0000000000..b1103abbf2
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/important-vs-inline-003.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: inline style loses to !important</title>
+ <link rel="help" href="https://crbug.com/1332956">
+ <link rel="author" href="mailto:sesse@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <style>
+ .cls {
+ visibility: inherit !important;
+ }
+ </style>
+</head>
+<body>
+ <div class="cls" id="el" style="visibility: hidden; height: 200px;"><iframe></iframe></div>
+</body>
+<script>
+test(() => {
+ el.setAttribute('disabled', 'disabled');
+ el.offsetTop;
+ el.style.height = '400px';
+ assert_equals(getComputedStyle(el).visibility, "visible", "!important has higher priority than inline style");
+});
+</script>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/inherit-initial.html b/testing/web-platform/tests/css/css-cascade/inherit-initial.html
new file mode 100644
index 0000000000..8d8dfef38d
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/inherit-initial.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CSS Cascading and Inheritance test: Root element inherits from initial values</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:rune@opera.com">
+<link rel="help" href="https://www.w3.org/TR/css3-cascade/#inheriting">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+html {
+ z-index: inherit;
+ position: inherit;
+ overflow: inherit;
+ background-color: inherit;
+}
+body {
+ overflow: scroll;
+ background-color: pink;
+}
+</style>
+<script>
+ test(function() {
+ assert_equals(getComputedStyle(document.documentElement).zIndex, "auto");
+ }, "z-index:inherit on root element should compute to 'auto'.");
+
+ test(function() {
+ assert_equals(getComputedStyle(document.documentElement).position, "static");
+ }, "position:inherit on root element should compute to 'static'.");
+
+ test(function() {
+ assert_equals(getComputedStyle(document.documentElement).overflow, "visible");
+ }, "overflow:inherit on root element should compute to 'visible'.");
+
+ test(function() {
+ assert_equals(getComputedStyle(document.documentElement).backgroundColor, "rgba(0, 0, 0, 0)");
+ }, "background-color:inherit on root element should compute to 'rgba(0, 0, 0, 0)'.");
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/initial-background-color.html b/testing/web-platform/tests/css/css-cascade/initial-background-color.html
new file mode 100644
index 0000000000..80897e0ef2
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/initial-background-color.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>
+ CSS Cascading and Inheritance Test:
+ Initial property and background-color
+ </title>
+ <meta name="assert" content="
+ The initial keyword is supported on background-color.
+ " />
+
+ <link
+ rel="author"
+ title="François REMY"
+ href="mailto:fremycompany.developer@yahoo.fr"
+ / >
+
+ <link rel="help" href="https://www.w3.org/TR/css-cascade-3/#initial"/>
+
+ <link
+ rel="match"
+ href="reference/all-green.html"
+ />
+
+ <style type="text/css">
+
+ html, body { margin: 0px; padding: 0px; }
+
+ html { background: green; overflow: hidden; }
+ #outer { position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; }
+ #outer { background: red; background-color: initial; }
+
+ </style>
+
+</head>
+<body>
+
+ <div id="outer"></div>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/initial-color-background-001-ref.html b/testing/web-platform/tests/css/css-cascade/initial-color-background-001-ref.html
new file mode 100644
index 0000000000..ffac42763f
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/initial-color-background-001-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade "W" Reference File</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <style>
+body {
+ background-color: white;
+}
+div {
+ font-size: 100px;
+}
+ </style>
+</head>
+<body>
+ <p>Test passes if there is a "W" and <strong>no red</strong>.</p>
+ <div>W</div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/initial-color-background-001.html b/testing/web-platform/tests/css/css-cascade/initial-color-background-001.html
new file mode 100644
index 0000000000..50e1384b0f
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/initial-color-background-001.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: the "initial" value</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="http://www.w3.org/TR/css-cascade-3/#initial">
+ <link rel="help" href="http://www.w3.org/TR/css-cascade-4/#initial">
+ <link rel="match" href="initial-color-background-001-ref.html">
+ <meta name="assert" content="initial is not the same as inherit. color:initial results in non-red. background-color:initial results in transparent.">
+ <style>
+body {
+ background-color: white;
+}
+div {
+ font-size: 100px;
+}
+.outer {
+ color: red;
+}
+.inner {
+ background-color: red;
+}
+.inner {
+ color: initial;/* normally black, almost certainly not red */
+ background-color: initial;/* transparent, so the body's white will show thru */
+}
+ </style>
+</head>
+<body>
+ <p>Test passes if there is a "W" and <strong>no red</strong>.</p>
+ <div class="outer">
+ <div class="inner">W</div>
+ </div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-basic.html b/testing/web-platform/tests/css/css-cascade/layer-basic.html
new file mode 100644
index 0000000000..e214bffc25
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-basic.html
@@ -0,0 +1,524 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Cascade Layers: Basic functionality</title>
+<meta name="assert" content="Basic functionality of CSS Cascade Layers">
+<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com">
+<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#layering">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<target class="first"></target>
+<target class="second"></target>
+<div id="log"></div>
+<script>
+
+// In all test cases, the rule specified as "color: green" should win.
+var testCases = [
+ {
+ title: 'A1 Anonymous layers',
+ style: `
+ @layer { }
+ target { color: green; }
+ `,
+ },
+ {
+ title: 'A2 Anonymous layers',
+ style: `
+ target { color: green; }
+ @layer {
+ target { color: red; }
+ }
+ `,
+ },
+ {
+ title: 'A3 Anonymous layers',
+ style: `
+ @layer {
+ target { color: red; }
+ }
+ target { color: green; }
+ `,
+ },
+ {
+ title: 'A4 Anonymous layers',
+ style: `
+ @layer {
+ target { color: red; }
+ }
+ @layer {
+ target { color: green; }
+ }
+ `,
+ },
+ {
+ title: 'A5 Anonymous layers',
+ style: `
+ @layer {
+ target { color: green; }
+ @layer {
+ target { color: red; }
+ }
+ }
+ `,
+ },
+ {
+ title: 'A6 Anonymous layers',
+ style: `
+ @layer {
+ @layer {
+ target { color: red; }
+ }
+ target { color: green; }
+ }
+ `,
+ },
+ {
+ title: 'A7 Anonymous layers',
+ style: `
+ @layer {
+ @layer {
+ target { color: red; }
+ }
+ target { color: red; }
+ }
+ @layer {
+ @layer {
+ target { color: red; }
+ }
+ target { color: green; }
+ }
+ `,
+ },
+ {
+ title: 'A8 Anonymous layers',
+ style: `
+ @layer {
+ @layer {
+ @layer {
+ target { color: red; }
+ }
+ }
+ target { color: red; }
+ }
+ @layer {
+ @layer {
+ target { color: red; }
+ }
+ target { color: green; }
+ }
+ `,
+ },
+ {
+ title: 'A9 Anonymous layers',
+ style: `
+ @layer {
+ @layer {
+ target { color: red; }
+ }
+ target { color: red; }
+ }
+ @layer {
+ @layer {
+ @layer {
+ target { color: red; }
+ }
+ }
+ target { color: green; }
+ }
+ `,
+ },
+ {
+ title: 'B1 Named layers',
+ style: `
+ @layer A {
+ }
+ target { color: green; }
+ `,
+ },
+ {
+ title: 'B2 Named layers',
+ style: `
+ @layer A {
+ target { color: red; }
+ }
+ target { color: green; }
+ `,
+ },
+ {
+ title: 'B3 Named layers',
+ style: `
+ @layer A {
+ target { color: red; }
+ }
+ @layer A {
+ target { color: green; }
+ }
+ `,
+ },
+ {
+ title: 'B4 Named layers',
+ style: `
+ @layer A {
+ target { color: red; }
+ }
+ @layer B {
+ target { color: green; }
+ }
+ @layer A {
+ target { color: red; }
+ }
+ `,
+ },
+ {
+ title: 'B5 Named layers',
+ style: `
+ @layer A {
+ target { color: green; }
+ @layer A {
+ target { color: red; }
+ }
+ }
+ `,
+ },
+ {
+ title: 'B6 Named layers',
+ style: `
+ @layer A {
+ @layer A {
+ target { color: red; }
+ }
+ }
+ @layer A {
+ @layer A {
+ target { color: green; }
+ }
+ }
+ `,
+ },
+ {
+ title: 'B7 Named layers',
+ style: `
+ @layer A {
+ target { color: red; }
+ @layer A {
+ target { color: red; }
+ }
+ }
+ @layer B {
+ target { color: green; }
+ }
+ @layer A {
+ @layer A {
+ target { color: red; }
+ }
+ }
+ `,
+ },
+ {
+ title: 'B8 Named layers',
+ style: `
+ @layer A {
+ @layer A {
+ target { color: red; }
+ }
+ }
+ @layer B {
+ @layer A {
+ target { color: green; }
+ }
+ }
+ `,
+ },
+ {
+ title: 'B9 Named layers',
+ style: `
+ @layer A {
+ @layer A {
+ target { color: red; }
+ }
+ }
+ @layer B {
+ @layer A {
+ target.first { color: green; }
+ }
+ }
+ @layer A {
+ @layer A {
+ target.first { color: red; }
+ target.second { color: green; }
+ }
+ }
+ `,
+ },
+ {
+ title: 'B10 Named layers',
+ style: `
+ @layer A {
+ @layer A {
+ target { color: red; }
+ }
+ }
+ @layer B {
+ @layer A {
+ target.first { color: green; }
+ }
+ }
+ @layer A {
+ @layer B {
+ target.first { color: red; }
+ target.second { color: green; }
+ }
+ }
+ `,
+ },
+ {
+ title: 'C1 Named layers shorthand',
+ style: `
+ @layer A.A {
+ target { color: red; }
+ }
+ @layer B.A {
+ target { color: green; }
+ }
+ `,
+ },
+ {
+ title: 'C2 Named layers shorthand',
+ style: `
+ @layer A.A {
+ target { color: red; }
+ }
+ @layer B.A {
+ target.first { color: green; }
+ }
+ @layer A.A {
+ target.first { color: red; }
+ target.second { color: green; }
+ }
+ `,
+ },
+ {
+ title: 'C3 Named layers shorthand',
+ style: `
+ @layer A.A {
+ target { color: red; }
+ }
+ @layer B.A {
+ target.first { color: green; }
+ }
+ @layer A.B {
+ target.first { color: red; }
+ target.second { color: green; }
+ }
+ `,
+ },
+ {
+ title: 'C4 Named layers shorthand',
+ style: `
+ @layer A {
+ @layer A {
+ target { color: red; }
+ }
+ }
+ @layer B.A {
+ target { color: green; }
+ }
+ @layer A.A
+ target { color: red; }
+ }
+ `,
+ },
+ {
+ title: 'C5 Named layers shorthand',
+ style: `
+ @layer A {
+ @layer A {
+ target { color: red; }
+ }
+ }
+ @layer B.A {
+ target { color: green; }
+ }
+ @layer A.B {
+ target { color: red; }
+ }
+ `,
+ },
+ {
+ title: 'D1 Mixed named and anonymous layers',
+ style: `
+ @layer A {
+ target { color: red; }
+ }
+ @layer {
+ target { color: green; }
+ }
+ `,
+ },
+ {
+ title: 'D2 Mixed named and anonymous layers',
+ style: `
+ @layer A {
+ @layer {
+ target { color: red; }
+ }
+ }
+ @layer A {
+ target { color: green; }
+ }
+ `,
+ },
+ {
+ title: 'D3 Mixed named and anonymous layers',
+ style: `
+ @layer A {
+ @layer {
+ target { color: red; }
+ }
+ }
+ @layer A {
+ @layer {
+ target { color: green; }
+ }
+ }
+ `,
+ },
+ {
+ title: 'D4 Mixed named and anonymous layers',
+ style: `
+ @layer A {
+ @layer {
+ target { color: red; }
+ }
+ }
+ @layer {
+ target { color: green; }
+ }
+ @layer A {
+ @layer {
+ target { color: red; }
+ }
+ }
+ `,
+ },
+ {
+ title: 'D5 Mixed named and anonymous layers',
+ style: `
+ @layer {
+ @layer A {
+ target { color: red; }
+ }
+ }
+ @layer {
+ target { color: green; }
+ }
+ `,
+ },
+ {
+ title: 'E1 Statement syntax',
+ style: `
+ @layer A, B, C;
+ @layer A {
+ target.first { color: red; }
+ target.second { color: red; }
+ }
+ @layer B {
+ target.first { color: red; }
+ }
+ @layer C {
+ target.first { color: green; }
+ target.second { color: green; }
+ }
+ `,
+ },
+ {
+ title: 'E2 Statement syntax',
+ style: `
+ @layer A, C, B;
+ @layer A {
+ target.first { color: red; }
+ target.second { color: red; }
+ }
+ @layer B {
+ target.first { color: green; }
+ }
+ @layer C {
+ target.first { color: red; }
+ target.second { color: green; }
+ }
+ `,
+ },
+ {
+ title: 'E3 Statement syntax',
+ style: `
+ @layer C, B, A;
+ @layer A {
+ target.first { color: green; }
+ target.second { color: green; }
+ }
+ @layer B {
+ target.first { color: red; }
+ }
+ @layer C {
+ target.first { color: red; }
+ target.second { color: red; }
+ }
+ `,
+ },
+ {
+ title: 'E4 Statement syntax',
+ style: `
+ @layer B, A.B, A.A;
+ @layer A {
+ @layer A {
+ target.first { color: green; }
+ }
+ @layer B {
+ target.first { color: red; }
+ target.second { color: green; }
+ }
+ }
+ @layer B {
+ target { color: red; }
+ }
+ `,
+ },
+ {
+ title: 'E5 Statement syntax',
+ style: `
+ @layer A.B, B, A.A;
+ @layer A {
+ @layer A {
+ target.first { color: red; }
+ }
+ @layer B {
+ target.first { color: red; }
+ target.second { color: red; }
+ }
+ }
+ @layer B {
+ target { color: green; }
+ }
+ `,
+ },
+];
+
+for (let testCase of testCases) {
+ const styleElement = document.createElement('style');
+ styleElement.textContent = testCase['style'];
+ document.head.append(styleElement);
+
+ test(function () {
+ var targets = document.querySelectorAll('target');
+ for (target of targets)
+ assert_equals(window.getComputedStyle(target).color, 'rgb(0, 128, 0)',
+ testCase['title'] + ", target '" + target.classList[0] + "'");
+ }, testCase['title']);
+
+ styleElement.remove();
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-counter-style-override.html b/testing/web-platform/tests/css/css-cascade/layer-counter-style-override.html
new file mode 100644
index 0000000000..1720898457
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-counter-style-override.html
@@ -0,0 +1,150 @@
+<!DOCTYPE html>
+<title>Resolving @counter-style name conflicts with cascade layers</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#target, #reference {
+ font-family: monospace;
+ width: min-content;
+}
+
+#reference::before {
+ content: '0000';
+}
+
+@counter-style three {
+ system: cyclic;
+ symbols: '000';
+}
+
+@counter-style four {
+ system: cyclic;
+ symbols: '0000';
+}
+</style>
+
+<ul>
+ <li id="target"></li>
+ <li id="reference"></li>
+</ul>
+
+<script>
+// In all tests, #target::before should have 4 characters, same as #reference.
+
+const testCases = [
+ {
+ title: '@counter-style unlayered overrides layered',
+ style: `
+ #target::before {
+ content: counter(dont-care, custom-counter-style);
+ }
+
+ @counter-style custom-counter-style {
+ system: extends four;
+ }
+
+ @layer {
+ @counter-style custom-counter-style {
+ system: extends three;
+ }
+ }
+ `
+ },
+
+ {
+ title: '@counter-style override between layers',
+ style: `
+ @layer base, override;
+
+ #target::before {
+ content: counter(dont-care, custom-counter-style);
+ }
+
+ @layer override {
+ @counter-style custom-counter-style {
+ system: extends four;
+ }
+ }
+
+ @layer base {
+ @counter-style custom-counter-style {
+ system: extends three;
+ }
+ }
+ `
+ },
+
+ {
+ title: '@counter-style override update with appended sheet 1',
+ style: `
+ @layer base, override;
+
+ #target::before {
+ content: counter(dont-care, custom-counter-style);
+ }
+
+ @layer override {
+ @counter-style custom-counter-style {
+ system: extends four;
+ }
+ }
+ `,
+ append: `
+ @layer base {
+ @counter-style custom-counter-style {
+ system: extends three;
+ }
+ }
+ `
+ },
+
+ {
+ title: '@counter-style override update with appended sheet 2',
+ style: `
+ @layer base, override;
+
+ #target::before {
+ content: counter(dont-care, custom-counter-style);
+ }
+
+ @layer base {
+ @counter-style custom-counter-style {
+ system: extends three;
+ }
+ }
+ `,
+ append: `
+ @layer override {
+ @counter-style custom-counter-style {
+ system: extends four;
+ }
+ }
+ `
+ },
+];
+
+for (let testCase of testCases) {
+ var documentStyle = document.createElement('style');
+ documentStyle.appendChild(document.createTextNode(testCase['style']));
+ document.head.appendChild(documentStyle);
+
+ var appendedStyle;
+ if (testCase['append']) {
+ document.body.offsetLeft; // Force style update
+ appendedStyle = document.createElement('style');
+ appendedStyle.appendChild(document.createTextNode(testCase['append']));
+ document.head.appendChild(appendedStyle);
+ }
+
+ test(function () {
+ assert_equals(getComputedStyle(target).width,
+ getComputedStyle(reference).width);
+ }, testCase['title']);
+
+ if (appendedStyle)
+ appendedStyle.remove();
+ documentStyle.remove();
+}
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-cssom-order-reverse-at-property.html b/testing/web-platform/tests/css/css-cascade/layer-cssom-order-reverse-at-property.html
new file mode 100644
index 0000000000..dfa6bbfcfb
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-cssom-order-reverse-at-property.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: @property rule invalidation on layer order changes</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="stylesheet" href="/fonts/ahem.css">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#reference {
+ color: green;
+ --foo: green;
+}
+</style>
+
+<div id=target>Lorem ipsum</div>
+<div id=reference>Lorem ipsum</div>
+
+<script>
+const testCases = [
+ {
+ title: 'Insert layer invalidates @property',
+ sheets: [
+ '',
+ `
+ @layer first {
+ @property --foo {
+ syntax: '<color>';
+ inherits: false;
+ initial-value: green;
+ }
+ }
+ @layer second {
+ @property --foo {
+ syntax: '<color>';
+ inherits: false;
+ initial-value: red;
+ }
+ }
+ `,
+ ],
+ update: function(sheets) {
+ sheets[0].insertRule('@layer second {}', 0);
+ },
+ property: '--foo',
+ },
+ {
+ title: 'Delete layer invalidates @property',
+ sheets: [
+ '@layer second {}',
+ `
+ @layer first {
+ @property --foo {
+ syntax: '<color>';
+ inherits: false;
+ initial-value: red;
+ }
+ }
+ @layer second {
+ @property --foo {
+ syntax: '<color>';
+ inherits: false;
+ initial-value: green;
+ }
+ }
+ `,
+ ],
+ update: function(sheets) {
+ sheets[0].deleteRule(0);
+ },
+ property: '--foo',
+ },
+];
+
+for (let testCase of testCases) {
+ test(testObj => {
+ const styleElements = testCase.sheets.map(sheet => {
+ const element = document.createElement('style');
+ element.appendChild(document.createTextNode(sheet));
+ document.head.appendChild(element);
+ return element;
+ });
+ testObj.add_cleanup(() => {
+ for (let element of styleElements)
+ element.remove();
+ });
+
+ const sheets = styleElements.map(element => element.sheet);
+ testCase.update(sheets);
+ const actual = getComputedStyle(target).getPropertyValue(testCase.property);
+ const expected = getComputedStyle(reference).getPropertyValue(testCase.property);
+ assert_equals(actual, expected);
+ }, testCase.title);
+}
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-cssom-order-reverse.html b/testing/web-platform/tests/css/css-cascade/layer-cssom-order-reverse.html
new file mode 100644
index 0000000000..ddc5977d42
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-cssom-order-reverse.html
@@ -0,0 +1,137 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: at-rule and style invalidation on layer order changes</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="stylesheet" href="/fonts/ahem.css">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#reference {
+ color: green;
+ font: 20px/1 ahem;
+ width: max-content;
+}
+</style>
+
+<div id=target>Lorem ipsum</div>
+<div id=reference>Lorem ipsum</div>
+
+<script>
+const testCases = [
+ {
+ title: 'Insert layer invalidates style',
+ sheets: [
+ '',
+ `
+ @layer first {
+ #target { color: green; }
+ }
+ @layer second {
+ #target { color: red; }
+ }
+ `,
+ ],
+ update: function(sheets) {
+ sheets[0].insertRule('@layer second {}', 0);
+ },
+ property: 'color',
+ },
+ {
+ title: 'Delete layer invalidates style',
+ sheets: [
+ '@layer second {}',
+ `
+ @layer first {
+ #target { color: red; }
+ }
+ @layer second {
+ #target { color: green; }
+ }
+ `,
+ ],
+ update: function(sheets) {
+ sheets[0].deleteRule(0);
+ },
+ property: 'color',
+ },
+ {
+ title: 'Insert layer invalidates @font-face',
+ sheets: [
+ '',
+ `
+ @layer first {
+ @font-face {
+ font-family: custom;
+ src: local('Ahem'), url('/fonts/Ahem.ttf');
+ }
+ }
+ @layer second {
+ @font-face {
+ font-family: custom;
+ src: url('/fonts/noto/noto-sans-v8-latin-regular.woff') format('woff');
+ }
+ }
+ #target { font: 20px/1 custom; width: max-content; }
+ `,
+ ],
+ update: async function(sheets) {
+ await document.fonts.load('20px/1 ahem');
+ await document.fonts.load('20px/1 custom');
+ document.body.offsetLeft; // Force style recalc
+ sheets[0].insertRule('@layer second {}', 0);
+ await document.fonts.load('20px/1 custom');
+ },
+ property: 'width',
+ },
+ {
+ title: 'Delete layer invalidates @font-face',
+ sheets: [
+ '@layer second {}',
+ `
+ @layer first {
+ @font-face {
+ font-family: custom;
+ src: url('/fonts/noto/noto-sans-v8-latin-regular.woff') format('woff');
+ }
+ }
+ @layer second {
+ @font-face {
+ font-family: custom;
+ src: local('Ahem'), url('/fonts/Ahem.ttf');
+ }
+ }
+ #target { font: 20px/1 custom; width: max-content; }
+ `,
+ ],
+ update: async function(sheets) {
+ await document.fonts.load('20px/1 ahem');
+ await document.fonts.load('20px/1 custom');
+ document.body.offsetLeft; // Force style recalc
+ sheets[0].deleteRule(0);
+ await document.fonts.load('20px/1 custom');
+ },
+ property: 'width',
+ },
+];
+
+for (let testCase of testCases) {
+ promise_test(async test => {
+ const styleElements = testCase.sheets.map(sheet => {
+ const element = document.createElement('style');
+ element.appendChild(document.createTextNode(sheet));
+ document.head.appendChild(element);
+ return element;
+ });
+ test.add_cleanup(() => {
+ for (let element of styleElements)
+ element.remove();
+ });
+
+ const sheets = styleElements.map(element => element.sheet);
+ await testCase.update(sheets);
+ const actual = getComputedStyle(target).getPropertyValue(testCase.property);
+ const expected = getComputedStyle(reference).getPropertyValue(testCase.property);
+ assert_equals(actual, expected);
+}, testCase.title);
+}
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-font-face-override.html b/testing/web-platform/tests/css/css-cascade/layer-font-face-override.html
new file mode 100644
index 0000000000..d35caca012
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-font-face-override.html
@@ -0,0 +1,141 @@
+<!DOCTYPE html>
+<title>Resolving @keyframe name conflicts with cascade layers</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#target {
+ font-size: 20px;
+ width: min-content;
+}
+</style>
+
+<div id="target">Test</div>
+
+<script>
+// In all tests, width of #target should be 80px.
+
+const testCases = [
+ {
+ title: '@font-face unlayered overrides layered',
+ style: `
+ #target {
+ font-family: custom-font;
+ }
+
+ @font-face {
+ font-family: custom-font;
+ src: url('/fonts/Ahem.ttf');
+ }
+
+ @layer {
+ @font-face {
+ font-family: custom-font;
+ src: url('/fonts/noto/noto-sans-v8-latin-regular.woff') format('woff');
+ }
+ }
+ `
+ },
+
+ {
+ title: '@font-face override between layers',
+ style: `
+ @layer base, override;
+
+ #target {
+ font-family: custom-font;
+ }
+
+ @layer override {
+ @font-face {
+ font-family: custom-font;
+ src: url('/fonts/Ahem.ttf');
+ }
+ }
+
+ @layer base {
+ @font-face {
+ font-family: custom-font;
+ src: url('/fonts/noto/noto-sans-v8-latin-regular.woff') format('woff');
+ }
+ }
+ `
+ },
+
+ {
+ title: '@font-face override update with appended sheet 1',
+ style: `
+ @layer base, override;
+
+ #target {
+ font-family: custom-font;
+ }
+
+ @layer override {
+ @font-face {
+ font-family: custom-font;
+ src: url('/fonts/Ahem.ttf');
+ }
+ }
+ `,
+ append: `
+ @layer base {
+ @font-face {
+ font-family: custom-font;
+ src: url('/fonts/noto/noto-sans-v8-latin-regular.woff') format('woff');
+ }
+ }
+ `
+ },
+
+ {
+ title: '@font-face override update with appended sheet 2',
+ style: `
+ @layer base, override;
+
+ #target {
+ font-family: custom-font;
+ }
+
+ @layer base {
+ @font-face {
+ font-family: custom-font;
+ src: url('/fonts/noto/noto-sans-v8-latin-regular.woff') format('woff');
+ }
+ }
+ `,
+ append: `
+ @layer override {
+ @font-face {
+ font-family: custom-font;
+ src: url('/fonts/Ahem.ttf');
+ }
+ }
+ `
+ },
+];
+
+for (let testCase of testCases) {
+ promise_test(async () => {
+ var documentStyle = document.createElement('style');
+ documentStyle.appendChild(document.createTextNode(testCase['style']));
+ document.head.appendChild(documentStyle);
+
+ var appendedStyle;
+ if (testCase['append']) {
+ document.body.offsetLeft; // Force style update
+ appendedStyle = document.createElement('style');
+ appendedStyle.appendChild(document.createTextNode(testCase['append']));
+ document.head.appendChild(appendedStyle);
+ }
+
+ await document.fonts.load('20px/1 custom-font');
+ assert_equals(getComputedStyle(target).width, '80px');
+
+ if (appendedStyle)
+ appendedStyle.remove();
+ documentStyle.remove();
+ }, testCase['title']);
+}
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-import.html b/testing/web-platform/tests/css/css-cascade/layer-import.html
new file mode 100644
index 0000000000..821bc7d72f
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-import.html
@@ -0,0 +1,294 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Cascade Layers: Imports</title>
+<meta name="assert" content="Import functionality of CSS Cascade Layers">
+<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com">
+<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#layering">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<target class="first"></target>
+<div id="log"></div>
+<script>
+
+// In all test cases, the rule specified as "color: green" should win.
+const imports = {
+ "basic-green.css": `
+ target { color: green; }
+ `,
+ "basic-red.css": `
+ target { color: red; }
+ `,
+ "layer-green.css": `
+ @layer {
+ target { color: green; }
+ }
+ `,
+ "layer-red.css": `
+ @layer {
+ target { color: red; }
+ }
+ `,
+ "layer-A-green.css": `
+ @layer A {
+ target { color: green; }
+ }
+ `,
+ "layer-A-red.css": `
+ @layer A {
+ target { color: red; }
+ }
+ `,
+ "layer-B-green.css": `
+ @layer B {
+ target { color: green; }
+ }
+ `,
+ "layer-B-red.css": `
+ @layer B {
+ target { color: red; }
+ }
+ `,
+};
+
+const testCases = [
+ {
+ title: 'A1 Layer rules with import',
+ style: `
+ @import url(basic-green.css);
+ @layer {
+ target { color: red; }
+ }
+ `
+ },
+ {
+ title: 'A2 Layer rules with import',
+ style: `
+ @import url(layer-red.css);
+ target { color: green; }
+ `
+ },
+ {
+ title: 'A3 Layer rules with import',
+ style: `
+ @import url(basic-green.css);
+ @import url(layer-red.css);
+ `
+ },
+ {
+ title: 'A4 Layer rules with import',
+ style: `
+ @import url(layer-A-red.css);
+ @layer B {
+ target { color: green; }
+ }
+ @layer A {
+ target { color: red; }
+ }
+ `
+ },
+ {
+ title: 'B1 Anonymous imports',
+ style: `
+ @import url(basic-red.css) layer;
+ target { color: green; }
+ `
+ },
+ {
+ title: 'B2 Anonymous imports',
+ style: `
+ @import url(basic-red.css) layer;
+ @import url(basic-green.css) layer;
+ `
+ },
+ {
+ title: 'B3 Anonymous imports',
+ style: `
+ @import url(basic-red.css) layer;
+ @layer {
+ target { color: green; }
+ }
+ `
+ },
+ {
+ title: 'B4 Anonymous imports',
+ style: `
+ @import url(layer-red.css);
+ @import url(basic-green.css) layer;
+ `
+ },
+ {
+ title: 'C1 Named imports',
+ style: `
+ @import url(basic-red.css) layer(A);
+ target { color: green; }
+ `
+ },
+ {
+ title: 'C2 Named imports',
+ style: `
+ @import url(basic-red.css) layer(A);
+ @import url(basic-green.css) layer(A);
+ `
+ },
+ {
+ title: 'C3 Named imports',
+ style: `
+ @import url(basic-red.css) layer(A);
+ @layer A {
+ target { color: green; }
+ }
+ `
+ },
+ {
+ title: 'C4 Named imports',
+ style: `
+ @import url(layer-red.css) layer(A);
+ @layer A {
+ target { color: green; }
+ }
+ `
+ },
+ {
+ title: 'C5 Named imports',
+ style: `
+ @import url(layer-A-red.css) layer(A);
+ @layer A.A {
+ target { color: green; }
+ }
+ `
+ },
+ {
+ title: 'C6 Named imports',
+ style: `
+ @import url(layer-A-red.css) layer(A);
+ @layer B {
+ target { color: green; }
+ }
+ @layer A.B {
+ target { color: red; }
+ }
+ `
+ },
+ {
+ title: 'C7 Named imports',
+ style: `
+ @import url(basic-green.css) layer(A);
+ @import url(basic-red.css) layer(B);
+ @import url(basic-green.css) layer(C);
+ `
+ },
+ {
+ title: 'C8 Named imports',
+ style: `
+ @import url(basic-red.css) layer(A);
+ @import url(basic-green.css) layer(B);
+ @import url(basic-red.css) layer(A);
+ `
+ },
+ {
+ title: 'C9 Named imports',
+ style: `
+ @import url(basic-red.css) layer(A);
+ @import url(basic-red.css) layer(B.A);
+ @import url(basic-green.css) layer(B);
+ `
+ },
+ {
+ title: 'D1 Layer statement with imports',
+ style: `
+ @import url(basic-red.css) layer(A);
+ @import url(basic-green.css) layer(B);
+ @layer B, A;
+ `
+ },
+ {
+ title: 'D2 Layer statement with imports',
+ style: `
+ @layer B;
+ @import url(basic-green.css) layer(A);
+ @layer B {
+ target { color: red; }
+ }
+ `
+ },
+ {
+ title: 'D3 Layer statement with imports',
+ style: `
+ @layer B;
+ @import url(basic-green.css) layer(A);
+ @import url(basic-red.css) layer(B);
+ `
+ },
+ {
+ title: 'D4 Layer statement with imports',
+ style: `
+ @layer C, B, A;
+ @import url(basic-green.css) layer(A);
+ @import url(basic-red.css) layer(B);
+ @layer C {
+ target { color: red; }
+ }
+ `
+ },
+ {
+ title: 'D5 Layer statement with imports',
+ style: `
+ @layer A.B, A.A;
+ @import url(basic-green.css) layer(A.A);
+ @import url(layer-B-red.css) layer(A);
+ `
+ },
+ {
+ title: 'D6 Layer statement with imports',
+ style: `
+ @layer B, A;
+ @import url(layer-A-red.css) layer(A);
+ @import url(layer-A-red.css) layer(B);
+ @layer A.B {
+ target { color: green; }
+ }
+ `
+ },
+ {
+ title: 'E1 Named imports establish layer even with network errors',
+ style: `
+ @import "nonexist.css" layer(A);
+ @layer B {
+ target { color: green; }
+ }
+ @layer A {
+ target { color: red; }
+ }
+ `,
+ },
+];
+
+for (let testCase of testCases) {
+ promise_test(async t => {
+ const styleElement = document.createElement('style');
+ const styleText = testCase['style'].replaceAll(/url\((.+?)\)/g, (match, p1) => {
+ return `url(data:text/css,${ encodeURI(imports[p1]) })`;
+ });
+ styleElement.textContent = styleText;
+
+ await new Promise(resolve => {
+ styleElement.onload = resolve;
+ styleElement.onerror = resolve;
+ document.head.append(styleElement);
+ });
+
+ try {
+ const targets = document.querySelectorAll('target');
+ for (target of targets)
+ assert_equals(window.getComputedStyle(target).color, 'rgb(0, 128, 0)', testCase['title'] + ", target '" + target.classList[0] + "'");
+ } finally {
+ styleElement.remove();
+ }
+ }, testCase['title']);
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-important.html b/testing/web-platform/tests/css/css-cascade/layer-important.html
new file mode 100644
index 0000000000..23bfd167fd
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-important.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Cascade Layers: !important </title>
+<meta name="assert" content="!important functionality of CSS Cascade Layers">
+<link rel="author" title="Romain Menke" href="mailto:romainmenke@gmail.com">
+<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#cascade-layering">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<target class="first"></target>
+<target class="second"></target>
+<div id="log"></div>
+<script>
+
+// In all test cases, the rule specified as "color: green" should win.
+var testCases = [
+ {
+ title: 'A1 Unlayered !important style',
+ style: `
+ target { color: green !important; }
+ target { color: red; }
+ `
+ },
+ {
+ title: 'B1 Same specificity, layered !important first',
+ style: `
+ @layer { target { color: green !important; } }
+ target { color: red; }
+ `,
+ },
+ {
+ title: 'C1 Same specificity, layered !important second',
+ style: `
+ target { color: red; }
+ @layer { target { color: green !important; } }
+ `,
+ },
+ {
+ title: 'D1 Same specificity, all !important',
+ style: `
+ @layer { target { color: green !important; } }
+ @layer { target { color: red !important; } }
+ target { color: red !important; }
+ `,
+ },
+ {
+ title: 'D2 Same specificity, all !important',
+ style: `
+ @layer { target { color: green !important; } }
+ target { color: red !important; }
+ @layer { target { color: red !important; } }
+ `,
+ },
+ {
+ title: 'D3 Same specificity, all !important',
+ style: `
+ target { color: red !important; }
+ @layer { target { color: green !important; } }
+ @layer { target { color: red !important; } }
+ `,
+ },
+ {
+ title: 'D4 Same specificity, all !important',
+ style: `
+ @layer A, B;
+ @layer B { target { color: red !important; } }
+ @layer A { target { color: green !important; } }
+ target { color: red !important; }
+ `,
+ },
+ {
+ title: 'E1 Different specificity, all !important',
+ style: `
+ @layer { target { color: green !important; } }
+ @layer { target { color: red !important; } }
+ target.first { color: red !important; }
+ `,
+ },
+ {
+ title: 'E2 Different specificity, all !important',
+ style: `
+ @layer { target { color: green !important; } }
+ @layer { target.first { color: red !important; } }
+ target { color: red !important; }
+ `,
+ },
+];
+
+for (let testCase of testCases) {
+ const styleElement = document.createElement('style');
+ styleElement.textContent = testCase['style'];
+ document.head.append(styleElement);
+
+ test(function () {
+ var targets = document.querySelectorAll('target');
+ for (target of targets)
+ assert_equals(window.getComputedStyle(target).color, 'rgb(0, 128, 0)',
+ testCase['title'] + ", target '" + target.classList[0] + "'");
+ }, testCase['title']);
+
+ styleElement.remove();
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-keyframes-override.html b/testing/web-platform/tests/css/css-cascade/layer-keyframes-override.html
new file mode 100644
index 0000000000..d0f4044f1e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-keyframes-override.html
@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<title>Resolving @keyframe name conflicts with cascade layers</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#target, #reference {
+ width: 100px;
+ height: 100px;
+}
+
+#reference {
+ background-color: green;
+}
+</style>
+
+<div id="target"></div>
+<div id="reference"></div>
+
+<script>
+// In all tests, background color of #target should be green, same as #reference
+
+const testCases = [
+ {
+ title: '@keyframes unlayered overrides layered',
+ style: `
+ #target {
+ animation: anim 1s paused;
+ }
+
+ @keyframes anim {
+ from { background-color: green; }
+ }
+
+ @layer {
+ @keyframes anim {
+ from { background-color: red; }
+ }
+ }
+ `
+ },
+
+ {
+ title: '@keyframes override between layers',
+ style: `
+ @layer base, override;
+
+ #target {
+ animation: anim 1s paused;
+ }
+
+ @layer override {
+ @keyframes anim {
+ from { background-color: green; }
+ }
+ }
+
+ @layer base {
+ @keyframes anim {
+ from { background-color: red; }
+ }
+ }
+ `
+ },
+
+ {
+ title: '@keyframes override update with appended sheet 1',
+ style: `
+ @layer base, override;
+
+ #target {
+ animation: anim 1s paused;
+ }
+
+ @layer override {
+ @keyframes anim {
+ from { background-color: green; }
+ }
+ }
+ `,
+ append: `
+ @layer base {
+ @keyframes anim {
+ from { background-color: red; }
+ }
+ }
+ `
+ },
+
+ {
+ title: '@keyframes override update with appended sheet 2',
+ style: `
+ @layer base, override;
+
+ #target {
+ animation: anim 1s paused;
+ }
+
+ @layer base {
+ @keyframes anim {
+ from { background-color: red; }
+ }
+ }
+ `,
+ append: `
+ @layer override {
+ @keyframes anim {
+ from { background-color: green; }
+ }
+ }
+ `
+ },
+];
+
+for (let testCase of testCases) {
+ var documentStyle = document.createElement('style');
+ documentStyle.appendChild(document.createTextNode(testCase['style']));
+ document.head.appendChild(documentStyle);
+
+ var appendedStyle;
+ if (testCase['append']) {
+ document.body.offsetLeft; // Force style update
+ appendedStyle = document.createElement('style');
+ appendedStyle.appendChild(document.createTextNode(testCase['append']));
+ document.head.appendChild(appendedStyle);
+ }
+
+ test(function () {
+ assert_equals(getComputedStyle(target).backgroundColor,
+ getComputedStyle(reference).backgroundColor);
+ }, testCase['title']);
+
+ if (appendedStyle)
+ appendedStyle.remove();
+ documentStyle.remove();
+}
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-media-query.html b/testing/web-platform/tests/css/css-cascade/layer-media-query.html
new file mode 100644
index 0000000000..92a0f55a6a
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-media-query.html
@@ -0,0 +1,169 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Cascade Layers: Media queries</title>
+<meta name="assert" content="CSS Cascade Layers nested in Media Queries">
+<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com">
+<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#layering">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<iframe width=300 height=300 frameborder=0></iframe>
+<div id="log"></div>
+<script>
+
+const imports = {
+ "basic-green.css": `
+ target { color: green; }
+ `,
+ "basic-red.css": `
+ target { color: red; }
+ `,
+ "empty.css": "",
+};
+
+// For 300px wide iframe the target should be red and for 500px green.
+const testCases = [
+ {
+ title: 'A1 Basic',
+ style: `
+ @layer { target { color: red } }
+ @media (min-width: 500px) {
+ @layer {
+ target { color: green; }
+ }
+ }
+ `
+ },
+ {
+ title: 'A2 Basic',
+ style: `
+ @media (min-width: 500px) {
+ @layer {
+ target { color: green; }
+ }
+ }
+ @media (max-width: 300px) {
+ @layer {
+ target { color: red; }
+ }
+ }
+ `
+ },
+ {
+ title: 'B1 Basic import',
+ style: `
+ @import url(basic-red.css) layer;
+ @import url(basic-green.css) layer (min-width: 500px);
+ `
+ },
+ {
+ title: 'B2 Basic import',
+ style: `
+ @import url(basic-green.css) layer (min-width: 500px);
+ @import url(basic-red.css) layer (max-width: 300px);
+ `
+ },
+ {
+ title: 'C1 Reordering',
+ style: `
+ @media (max-width: 300px) {
+ @layer B {
+ target { color: green; }
+ }
+ @layer A {
+ target { color: red; }
+ }
+ }
+ @media (min-width: 500px) {
+ @layer A {
+ target { color: red; }
+ }
+ @layer B {
+ target { color: green; }
+ }
+ }
+ `
+ },
+ {
+ title: 'C2 Reordering',
+ style: `
+ @media (max-width: 300px) {
+ @layer B { }
+ @layer A { target { color: red; } }
+ }
+ @media (min-width: 500px) {
+ @layer A { target { color: red; } }
+ @layer B { }
+ }
+ @layer B {
+ target { color: green; }
+ }
+ `
+ },
+ {
+ title: 'C3 Reordering',
+ style: `
+ @media (max-width: 300px) {
+ @layer B, A;
+ }
+ @media (min-width: 500px) {
+ @layer A, B;
+ }
+ @layer A {
+ target { color: red; }
+ }
+ @layer B {
+ target { color: green; }
+ }
+ `
+ },
+ {
+ title: 'C4 Reordering',
+ style: `
+ @import url(empty.css) layer(B) (max-width: 300px);
+ @import url(empty.css) layer(A) (max-width: 300px);
+ @import url(empty.css) layer(A) (min-width: 500px);
+ @import url(empty.css) layer(B) (min-width: 500px);
+ @layer A {
+ target { color: red; }
+ }
+ @layer B {
+ target { color: green; }
+ }
+ `
+ },
+];
+
+let iframe = document.querySelector("iframe");
+
+for (let testCase of testCases) {
+ promise_test(async t => {
+ const styleText = testCase['style'].replaceAll(/url\((.+?)\)/g, (match, p1) => {
+ return `url(data:text/css,${ encodeURI(imports[p1]) })`;
+ });
+
+ iframe.width = 300;
+
+ await new Promise(resolve => {
+ iframe.onload = resolve;
+ iframe.srcdoc = `
+ <style>
+ ${styleText}
+ </style>
+ <target></target>
+ `;
+ });
+
+ const target = iframe.contentDocument.querySelector('target');
+ assert_equals(getComputedStyle(target).color, 'rgb(255, 0, 0)', testCase['title']);
+
+ iframe.width = 500;
+
+ assert_equals(getComputedStyle(target).color, 'rgb(0, 128, 0)', testCase['title']);
+ }, testCase['title']);
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-media-toggle.html b/testing/web-platform/tests/css/css-cascade/layer-media-toggle.html
new file mode 100644
index 0000000000..83a037a2bd
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-media-toggle.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: Tests against a Chrome bug that modifying a sheet affects existing layers</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1313357">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="reference/ref-filled-green-100px-square.xht">
+
+<style>
+@layer foo, bar;
+@layer bar {
+ #target { background-color: green; }
+}
+@layer foo {
+ #target { background-color: red; }
+}
+</style>
+<style media="print" id="toggle">
+#target {
+ width: 100px;
+ height: 100px;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="target"></div>
+
+<script>
+document.body.offsetWidth; // Force style calculation
+document.getElementById('toggle').media = 'all';
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-property-override.html b/testing/web-platform/tests/css/css-cascade/layer-property-override.html
new file mode 100644
index 0000000000..9d3f9cb926
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-property-override.html
@@ -0,0 +1,154 @@
+<!DOCTYPE html>
+<title>Resolving @property name conflicts with cascade layers</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#target, #reference {
+ width: 100px;
+ height: 100px;
+}
+
+#reference {
+ background-color: green;
+}
+</style>
+
+<div id="target"></div>
+<div id="reference"></div>
+
+<script>
+// In all tests, background color of #target should be green, same as #reference
+
+const testCases = [
+ {
+ title: '@property unlayered overrides layered',
+ style: `
+ #target {
+ background-color: var(--foo);
+ }
+
+ @property --foo {
+ syntax: '<color>';
+ inherits: false;
+ initial-value: green;
+ }
+
+ @layer {
+ @property --foo {
+ syntax: '<color>';
+ inherits: false;
+ initial-value: red;
+ }
+ }
+ `
+ },
+
+ {
+ title: '@property override between layers',
+ style: `
+ @layer base, override;
+
+ #target {
+ background-color: var(--foo);
+ }
+
+ @layer override {
+ @property --foo {
+ syntax: '<color>';
+ inherits: false;
+ initial-value: green;
+ }
+ }
+
+ @layer base {
+ @property --foo {
+ syntax: '<color>';
+ inherits: false;
+ initial-value: red;
+ }
+ }
+ `
+ },
+
+ {
+ title: '@property override update with appended sheet 1',
+ style: `
+ @layer base, override;
+
+ #target {
+ background-color: var(--foo);
+ }
+
+ @layer override {
+ @property --foo {
+ syntax: '<color>';
+ inherits: false;
+ initial-value: green;
+ }
+ }
+ `,
+ append: `
+ @layer base {
+ @property --foo {
+ syntax: '<color>';
+ inherits: false;
+ initial-value: red;
+ }
+ }
+ `
+ },
+
+ {
+ title: '@property override update with appended sheet 2',
+ style: `
+ @layer base, override;
+
+ #target {
+ background-color: var(--foo);
+ }
+
+ @layer base {
+ @property --foo {
+ syntax: '<color>';
+ inherits: false;
+ initial-value: red;
+ }
+ }
+ `,
+ append: `
+ @layer override {
+ @property --foo {
+ syntax: '<color>';
+ inherits: false;
+ initial-value: green;
+ }
+ }
+ `
+ },
+];
+
+for (let testCase of testCases) {
+ var documentStyle = document.createElement('style');
+ documentStyle.appendChild(document.createTextNode(testCase['style']));
+ document.head.appendChild(documentStyle);
+
+ var appendedStyle;
+ if (testCase['append']) {
+ document.body.offsetLeft; // Force style update
+ appendedStyle = document.createElement('style');
+ appendedStyle.appendChild(document.createTextNode(testCase['append']));
+ document.head.appendChild(appendedStyle);
+ }
+
+ test(function () {
+ assert_equals(getComputedStyle(target).backgroundColor,
+ getComputedStyle(reference).backgroundColor);
+ }, testCase['title']);
+
+ if (appendedStyle)
+ appendedStyle.remove();
+ documentStyle.remove();
+}
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-replaceSync-clears-stale.html b/testing/web-platform/tests/css/css-cascade/layer-replaceSync-clears-stale.html
new file mode 100644
index 0000000000..c9d88681bd
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-replaceSync-clears-stale.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: CSSStyleSheet.replaceSync clears stale statements</title>
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#layering">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="target"></div>
+<div id="reference" style="color: green"></div>
+
+<script>
+// In all test cases, the 'color' property value of #target should be green.
+
+const testCases = [
+ {
+ title: 'replaceSync clears stale layer statements',
+ style: `
+ @layer second, first;
+ @layer second {
+ #target { color: green; }
+ }
+ @layer first {
+ #target { color: red; }
+ }
+ `,
+ operations: function(sheet) {
+ sheet.replaceSync(`
+ @layer first {
+ #target { color: red; }
+ }
+ @layer second {
+ #target { color: green; }
+ }
+ `);
+ }
+ },
+];
+
+const target = document.getElementById('target');
+const reference = document.getElementById('reference');
+
+for (let testCase of testCases) {
+ test(t => {
+ let sheet = new CSSStyleSheet();
+ sheet.replaceSync(testCase.style);
+ document.adoptedStyleSheets = [sheet];
+
+ try {
+ testCase.operations(sheet);
+ assert_equals(getComputedStyle(target).color, getComputedStyle(reference).color);
+ } finally {
+ document.adoptedStyleSheets = [];
+ }
+ }, testCase.title);
+}
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-rules-cssom.html b/testing/web-platform/tests/css/css-cascade/layer-rules-cssom.html
new file mode 100644
index 0000000000..b81960df6f
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-rules-cssom.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<title>The CSSOM API for Cascade Layers</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layer-apis">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const testCases = [
+ {
+ style: `@layer foo { }`,
+ expectedNames: ['foo'],
+ title: 'Basic layer block name',
+ },
+ {
+ style: `@layer { }`,
+ expectedNames: [''],
+ title: 'Anonymous layer block name',
+ },
+ {
+ style: `
+ @layer foo;
+ `,
+ expectedNames: [['foo']],
+ title: 'Basic layer statement name',
+ },
+ {
+ style: `
+ @layer foo, bar;
+ `,
+ expectedNames: [['foo', 'bar']],
+ title: 'Layer statement with multiple names',
+ },
+ {
+ style: `
+ @layer outer {
+ @layer foo.bar { }
+ }
+ @layer outer.foo.bar { }
+ `,
+ expectedNames: ['outer', 'foo.bar', 'outer.foo.bar'],
+ title: 'Nested layer block names',
+ },
+ {
+ style: `
+ @layer outer {
+ @layer foo.bar, baz;
+ }
+ @layer outer.foo.bar, outer.baz;
+ `,
+ expectedNames: ['outer', ['foo.bar', 'baz'], ['outer.foo.bar', 'outer.baz']],
+ title: 'Nested layer statement name lists',
+ },
+ {
+ style: `
+ @import url('data:text/css,') layer;
+ `,
+ expectedNames: [''],
+ title: 'Import into anonymous layer',
+ },
+ {
+ style: `
+ @import url('data:text/css,') layer(foo);
+ `,
+ expectedNames: ['foo'],
+ title: 'Import into named layer',
+ },
+ {
+ style: `
+ @import url('data:text/css,');
+ `,
+ expectedNames: [null],
+ title: 'Import without layer',
+ },
+];
+
+for (let testCase of testCases) {
+ promise_test(async function (t) {
+ assert_implements(window.CSSLayerBlockRule);
+ assert_implements(window.CSSLayerStatementRule);
+
+ const style = document.createElement('style');
+ t.add_cleanup(() => style.remove());
+
+ const isLoadAsync = testCase.style.includes("@import");
+ const load = new Promise(resolve => {
+ style.addEventListener("load", resolve, { once: true });
+ });
+
+ style.appendChild(document.createTextNode(testCase.style));
+ document.head.appendChild(style);
+
+ if (isLoadAsync) {
+ await load;
+ }
+
+ let index = 0;
+ function compareNames(ruleOrSheet) {
+ if (ruleOrSheet instanceof CSSLayerBlockRule)
+ assert_equals(ruleOrSheet.name, testCase.expectedNames[index++]);
+ else if (ruleOrSheet instanceof CSSImportRule)
+ assert_equals(ruleOrSheet.layerName, testCase.expectedNames[index++]);
+ else if (ruleOrSheet instanceof CSSLayerStatementRule)
+ assert_array_equals(ruleOrSheet.nameList, testCase.expectedNames[index++]);
+ if (ruleOrSheet.cssRules) {
+ for (let i = 0; i < ruleOrSheet.cssRules.length; ++i)
+ compareNames(ruleOrSheet.cssRules.item(i));
+ }
+ }
+ compareNames(style.sheet);
+ assert_equals(index, testCase.expectedNames.length);
+ }, testCase.title);
+}
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-slotted-rule.html b/testing/web-platform/tests/css/css-cascade/layer-slotted-rule.html
new file mode 100644
index 0000000000..a33a5a6787
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-slotted-rule.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>::slotted rules should be associated with the correct cascade layers</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="reference/ref-filled-green-100px-square.xht">
+<style>
+#target {
+ width: 100px;
+ height: 100px;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="host">
+ <div id="target"></target>
+</div>
+
+<script>
+const host = document.getElementById('host');
+host.attachShadow({mode: 'open'}).innerHTML = `
+<style>
+@layer {
+ ::slotted(*) {
+ background-color: green !important;
+ }
+}
+::slotted(*) {
+ background-color: red !important;
+}
+</style>
+<slot></slot>
+`;
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-statement-before-import.html b/testing/web-platform/tests/css/css-cascade/layer-statement-before-import.html
new file mode 100644
index 0000000000..fcde960532
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-statement-before-import.html
@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: Empty layer statements before import rules</title>
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#layering">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="target"></div>
+<div id="reference" style="color: green"></div>
+
+<script>
+// In all test cases, the 'color' property value of #target should be green.
+
+const testCases = [
+ {
+ title: 'length and item',
+ style: `
+ @layer first, second;
+ @import url(data:text/css,);
+ @layer second {
+ #target { color: green; }
+ }
+ @layer first {
+ #target { color: red; }
+ }
+ `,
+ operations: function(sheet) {
+ assert_equals(sheet.cssRules.length, 4);
+ assert_equals(sheet.cssRules.item(0).cssText, '@layer first, second;');
+ assert_equals(sheet.cssRules.item(1).cssText, `@import url("data:text/css,");`);
+ assert_equals(sheet.cssRules.item(2).cssText,
+ '@layer second {\n #target { color: green; }\n}');
+ assert_equals(sheet.cssRules.item(3).cssText,
+ '@layer first {\n #target { color: red; }\n}');
+ }
+ },
+ {
+ title: 'insertRule before imports',
+ style: `
+ @import url(data:text/css,);
+ @layer second {
+ #target { color: green; }
+ }
+ @layer first {
+ #target { color: red; }
+ }
+ `,
+ operations: function(sheet) {
+ sheet.insertRule('@layer first, second', 0);
+ }
+ },
+ {
+ title: 'insertRule after imports',
+ style: `
+ @layer first, second;
+ @import url(data:text/css,);
+ @layer first {
+ #target { color: red; }
+ }
+ `,
+ operations: function(sheet) {
+ sheet.insertRule('@layer second { #target { color: green; } }', 2);
+ }
+ },
+ {
+ title: 'insert other rules to pre-import layer statements fails',
+ style: `
+ @layer first, second;
+ @import url(data:text/css,);
+ @layer second {
+ #target { color: green; }
+ }
+ @layer first {
+ #target { color: red; }
+ }
+ `,
+ operations: function(sheet) {
+ assert_throws_dom('HierarchyRequestError',
+ () => sheet.insertRule('#target { color: red !important; }', 0));
+ assert_throws_dom('HierarchyRequestError',
+ () => sheet.insertRule('#target { color: red !important; }', 1));
+ }
+ },
+ {
+ title: 'insert other rules before the first layer statement without imports',
+ style: `
+ @layer first, second;
+ @layer second {
+ #target { color: red !important; }
+ }
+ `,
+ operations: function(sheet) {
+ sheet.insertRule(`@layer first {
+ #target { color: green !important; }
+ }`, 0);
+ }
+ },
+ {
+ title: 'deleteRule before imports',
+ style: `
+ @layer second, first;
+ @import url(data:text/css,);
+ @layer first {
+ #target { color: red; }
+ }
+ @layer second {
+ #target { color: green; }
+ }
+ `,
+ operations: function(sheet) {
+ sheet.deleteRule(0);
+ }
+ },
+ {
+ title: 'deleteRule after imports',
+ style: `
+ @layer first, second;
+ @import url(data:text/css,);
+ @layer second {
+ #target { color: green; }
+ }
+ @layer first {
+ #target { color: red; }
+ }
+ #target {
+ color: red;
+ }
+ `,
+ operations: function(sheet) {
+ sheet.deleteRule(4);
+ }
+ },
+];
+
+const target = document.getElementById('target');
+const reference = document.getElementById('reference');
+
+for (let testCase of testCases) {
+ promise_test(async t => {
+ let styleElement = document.createElement('style');
+ styleElement.textContent = testCase.style;
+ await new Promise(resolve => {
+ styleElement.onload = resolve;
+ styleElement.onerror = resolve;
+ document.head.append(styleElement);
+ });
+ let sheet = styleElement.sheet;
+
+ try {
+ testCase.operations(sheet);
+ assert_equals(getComputedStyle(target).color, getComputedStyle(reference).color);
+ } finally {
+ styleElement.remove();
+ }
+ }, testCase.title);
+}
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-statement-copy-crash.html b/testing/web-platform/tests/css/css-cascade/layer-statement-copy-crash.html
new file mode 100644
index 0000000000..f183ab30ab
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-statement-copy-crash.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Chromium bug: Crash when copying layer statement rule from memory cache</title>
+<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#layer-empty">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1345181">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="stylesheet" href="data:text/css,@layer foo;@media(all){}">
+<link rel="stylesheet" href="data:text/css,@layer foo;@media(all){}">
+<body>
+ <p style="color: green">Test passes if it does not crash.</p>
+</body>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing-important.html b/testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing-important.html
new file mode 100644
index 0000000000..7b3ff4abc6
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing-important.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>Test important style in anonymous layers with stylesheet sharing</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="layer-stylesheet-sharing-ref.html">
+<style>
+ target {
+ display: block;
+ width: 100px;
+ height: 100px;
+ }
+</style>
+<link rel=stylesheet href="data:text/css,@layer{target{background-color:green !important}}">
+<style>
+@layer A { target { background-color: red !important} }
+</style>
+<link rel=stylesheet href="data:text/css,@layer{target{background-color:green !important}}">
+<target></target> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing-ref.html b/testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing-ref.html
new file mode 100644
index 0000000000..fe004e5bda
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing-ref.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<style>
+ target {
+ display: block;
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ }
+</style>
+<target></target>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing.html b/testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing.html
new file mode 100644
index 0000000000..c172baaf81
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>Test anonymous layers with stylesheet sharing</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1730123">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mozilla" href="https://mozilla.org">
+<link rel="match" href="layer-stylesheet-sharing-ref.html">
+<style>
+ target {
+ display: block;
+ width: 100px;
+ height: 100px;
+ }
+</style>
+<link rel=stylesheet href="data:text/css,@layer{target{background-color:green}}">
+<style>
+@layer A { target { background-color: red } }
+</style>
+<link rel=stylesheet href="data:text/css,@layer{target{background-color:green}}">
+<target></target>
diff --git a/testing/web-platform/tests/css/css-cascade/layer-vs-inline-style.html b/testing/web-platform/tests/css/css-cascade/layer-vs-inline-style.html
new file mode 100644
index 0000000000..9ddfbc3907
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/layer-vs-inline-style.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#cascade-sort">
+<link rel="author" title="Xiaocheng Hu" href="mailto:xiaochengh@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#target, #reference {
+ width: 100px;
+ height: 100px;
+}
+
+#reference {
+ background-color: green;
+}
+</style>
+
+<div id="target"></div>
+<div id="reference"></div>
+
+<script>
+// In all tests, #target should have green background color, same as #reference
+
+const testCases = [
+ {
+ title: 'Normal inline style > normal layered style',
+ style: '@layer { #target { background-color: red; }}',
+ inlineStyle: 'background-color: green'
+ },
+ {
+ title: 'Normal inline style < important layered style',
+ style: '@layer { #target { background-color: green !important; }}',
+ inlineStyle: 'background-color: red'
+ },
+ {
+ title: 'Important inline style > normal layered style',
+ style: '@layer { #target { background-color: red; }}',
+ inlineStyle: 'background-color: green !important'
+ },
+ {
+ title: 'Important inline style > important layered style',
+ style: '@layer { #target { background-color: red !important; }}',
+ inlineStyle: 'background-color: green !important'
+ },
+];
+
+for (let testCase of testCases) {
+ var documentStyle = document.createElement('style');
+ documentStyle.appendChild(document.createTextNode(testCase['style']));
+ document.head.appendChild(documentStyle);
+
+ target.style = testCase['inlineStyle'];
+
+ test(function () {
+ assert_equals(getComputedStyle(target).backgroundColor,
+ getComputedStyle(reference).backgroundColor);
+ }, testCase['title']);
+
+ documentStyle.remove();
+}
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/parsing/all-invalid.html b/testing/web-platform/tests/css/css-cascade/parsing/all-invalid.html
new file mode 100644
index 0000000000..4a1d045ecc
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/parsing/all-invalid.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Cascading and Inheritance Level 3: parsing all with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-cascade-3/#propdef-all">
+<meta name="assert" content="all supports only the grammar 'initial | inherit | unset'.">
+<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("all", "auto");
+test_invalid_value("all", "none");
+test_invalid_value("all", "filter");
+test_invalid_value("all", "unset inherit");
+test_invalid_value("all", "inherit initial");
+test_invalid_value("all", "initial unset");
+test_invalid_value("all", "opacity transform");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/parsing/all-valid.html b/testing/web-platform/tests/css/css-cascade/parsing/all-valid.html
new file mode 100644
index 0000000000..3a9e5922de
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/parsing/all-valid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Cascading and Inheritance Level 3: parsing all with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-cascade-3/#propdef-all">
+<meta name="assert" content="all supports the full grammar 'initial | inherit | unset'.">
+<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("all", "initial");
+test_valid_value("all", "inherit");
+test_valid_value("all", "unset");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/parsing/layer-import-parsing.html b/testing/web-platform/tests/css/css-cascade/parsing/layer-import-parsing.html
new file mode 100644
index 0000000000..f879ba8897
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/parsing/layer-import-parsing.html
@@ -0,0 +1,79 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>@import rule with layer parsing / serialization</title>
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#at-import">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ function setupSheet(rule) {
+ const style = document.createElement("style");
+ document.head.append(style);
+ const {sheet} = style;
+ const {cssRules} = sheet;
+
+ assert_equals(cssRules.length, 0, "Sheet should have no rules");
+ sheet.insertRule(rule);
+ assert_equals(cssRules.length, 1, "Sheet should have 1 rule");
+
+ return {sheet, cssRules};
+ }
+
+ function test_valid_layer_import(rule, serialized) {
+ if (serialized === undefined)
+ serialized = rule;
+
+ test(function() {
+ const {sheet, cssRules} = setupSheet(rule);
+
+ const serialization = cssRules[0].cssText;
+ assert_equals(serialization, serialized, 'serialization should be canonical');
+
+ const media = cssRules[0].media;
+ assert_equals(media.length, 0, 'layer() should be valid');
+
+ sheet.deleteRule(0);
+ assert_equals(cssRules.length, 0, 'Sheet should have no rule');
+ sheet.insertRule(serialization);
+ assert_equals(cssRules.length, 1, 'Sheet should have 1 rule');
+
+ assert_equals(cssRules[0].cssText, serialization, 'serialization should round-trip');
+ }, rule + ' should be a valid layered import rule');
+ }
+
+ function test_invalid_layer_import(rule) {
+ test(function() {
+ const {sheet, cssRules} = setupSheet(rule);
+
+ const media = cssRules[0].media;
+ assert_not_equals(media.length, 0,
+ 'invalid layer declaration should be parsed as <general-enclosed> media query');
+
+ sheet.deleteRule(0);
+ assert_equals(cssRules.length, 0, 'Sheet should have no rule');
+ }, rule + ' should still be a valid import rule with an invalid layer declaration');
+ }
+
+ test_valid_layer_import('@import url("nonexist.css") layer;');
+ test_valid_layer_import('@import url("nonexist.css") layer(A);');
+ test_valid_layer_import('@import url("nonexist.css") layer(A.B);');
+
+ test_valid_layer_import('@import url(nonexist.css) layer;',
+ '@import url("nonexist.css") layer;');
+ test_valid_layer_import('@import url(nonexist.css) layer(A);',
+ '@import url("nonexist.css") layer(A);');
+ test_valid_layer_import('@import url(nonexist.css) layer(A.B);',
+ '@import url("nonexist.css") layer(A.B);');
+
+ test_valid_layer_import('@import "nonexist.css" layer;',
+ '@import url("nonexist.css") layer;');
+ test_valid_layer_import('@import "nonexist.css" layer(A);',
+ '@import url("nonexist.css") layer(A);');
+ test_valid_layer_import('@import "nonexist.css" layer(A.B);',
+ '@import url("nonexist.css") layer(A.B);');
+
+ test_invalid_layer_import('@import url("nonexist.css") layer();');
+ test_invalid_layer_import('@import url("nonexist.css") layer(A B);');
+ test_invalid_layer_import('@import url("nonexist.css") layer(A . B);');
+ test_invalid_layer_import('@import url("nonexist.css") layer(A, B, C);');
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/parsing/layer.html b/testing/web-platform/tests/css/css-cascade/parsing/layer.html
new file mode 100644
index 0000000000..3bfc863ede
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/parsing/layer.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>@layer rule parsing / serialization</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mozilla" href="https://mozilla.org">
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+<script>
+ test_valid_rule("@layer A;");
+ test_valid_rule("@layer A, B, C;");
+ test_valid_rule("@layer A.A;");
+ test_valid_rule("@layer A, B.C.D, C;");
+
+ test_invalid_rule("@layer;");
+ test_invalid_rule("@layer A . A;");
+
+ test_valid_rule("@layer {\n}");
+ test_valid_rule("@layer A {\n}");
+ test_valid_rule("@layer A.B {\n}");
+ test_invalid_rule("@layer A . B {\n}");
+
+ test_invalid_rule("@layer A, B, C {\n}");
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/parsing/supports-import-parsing.html b/testing/web-platform/tests/css/css-cascade/parsing/supports-import-parsing.html
new file mode 100644
index 0000000000..64cf930e6c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/parsing/supports-import-parsing.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>@import rule with supports parsing / serialization</title>
+<link rel="author" href="mailto:oj@oojmed.com">
+<link rel="help" href="https://drafts.csswg.org/css-cascade-4/#at-import">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ function setupSheet(rule) {
+ const style = document.createElement("style");
+ document.head.append(style);
+ const {sheet} = style;
+ const {cssRules} = sheet;
+
+ assert_equals(cssRules.length, 0, "Sheet should have no rules");
+ sheet.insertRule(rule);
+ assert_equals(cssRules.length, 1, "Sheet should have 1 rule");
+
+ return {sheet, cssRules};
+ }
+
+ function test_valid_supports_import(rule, serialized) {
+ if (serialized === undefined)
+ serialized = rule;
+
+ test(function() {
+ const {sheet, cssRules} = setupSheet(rule);
+
+ const serialization = cssRules[0].cssText;
+ assert_equals(serialization, serialized, 'serialization should be canonical');
+
+ sheet.deleteRule(0);
+ assert_equals(cssRules.length, 0, 'Sheet should have no rule');
+ sheet.insertRule(serialization);
+ assert_equals(cssRules.length, 1, 'Sheet should have 1 rule');
+
+ assert_equals(cssRules[0].cssText, serialization, 'serialization should round-trip');
+ }, rule + ' should be a valid supports() import rule');
+ }
+
+ function test_invalid_supports_import(rule) {
+ test(function() {
+ const {sheet, cssRules} = setupSheet(rule);
+
+ sheet.deleteRule(0);
+ assert_equals(cssRules.length, 0, 'Sheet should have no rule');
+ }, rule + ' should still be a valid import rule with an invalid supports() declaration');
+ }
+
+ test_valid_supports_import('@import url("nonexist.css") supports();');
+ test_valid_supports_import('@import url("nonexist.css") supports(display:block);');
+ test_valid_supports_import('@import url("nonexist.css") supports((display:flex));');
+ test_valid_supports_import('@import url("nonexist.css") supports(not (display: flex));');
+ test_valid_supports_import('@import url("nonexist.css") supports((display: flex) and (display: block));');
+ test_valid_supports_import('@import url("nonexist.css") supports((display: flex) or (display: block));');
+ test_valid_supports_import('@import url("nonexist.css") supports((display: flex) or (foo: bar));');
+ test_valid_supports_import('@import url("nonexist.css") supports(display: block !important);');
+
+ test_valid_supports_import('@import url("nonexist.css") layer supports();');
+ test_valid_supports_import('@import url("nonexist.css") layer(A) supports((display: flex) or (foo: bar));');
+ test_valid_supports_import('@import url("nonexist.css") layer(A.B) supports((display: flex) and (foo: bar));');
+
+ test_valid_supports_import('@import url("nonexist.css") supports(selector(a));');
+ test_valid_supports_import('@import url("nonexist.css") supports(selector(p a));');
+ test_valid_supports_import('@import url("nonexist.css") supports(selector(p > a));');
+ test_valid_supports_import('@import url("nonexist.css") supports(selector(p + a));');
+
+ test_valid_supports_import('@import url("nonexist.css") supports(font-tech(color-colrv1));');
+ test_valid_supports_import('@import url("nonexist.css") supports(font-format(opentype));');
+
+ test_valid_supports_import('@import url(nonexist.css) supports(display:block);',
+ '@import url("nonexist.css") supports(display:block);');
+
+ test_valid_supports_import('@import "nonexist.css" supports(display:block);',
+ '@import url("nonexist.css") supports(display:block);');
+
+ test_invalid_supports_import('@import url("nonexist.css") supports;');
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/presentational-hints-cascade.html b/testing/web-platform/tests/css/css-cascade/presentational-hints-cascade.html
new file mode 100644
index 0000000000..c3188fd0d7
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/presentational-hints-cascade.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#preshint">
+<link rel="author" title="Xiaocheng Hu" href="mailto:xiaochengh@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+.test {
+ height: 100px;
+ background-color: green;
+}
+
+#target1 {
+ width: 100px;
+}
+
+@layer {
+ #target3 {
+ width: 100px;
+ }
+}
+</style>
+
+<img class=test id=target1 width=200>
+<img class=test id=target2 width=200 style="width: 100px">
+<img class=test id=target3 width=200>
+
+<script>
+test(() => {
+ assert_equals(getComputedStyle(target1).width, '100px');
+}, 'Presentational hints have lower precedence than regular author style sheets');
+
+test(() => {
+ assert_equals(getComputedStyle(target2).width, '100px');
+}, 'Presentational hints have lower precedence than the style attribute');
+
+test(() => {
+ assert_equals(getComputedStyle(target3).width, '100px');
+}, 'Presentational hints have lower precedence than layered style');
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/presentational-hints-rollback.html b/testing/web-platform/tests/css/css-cascade/presentational-hints-rollback.html
new file mode 100644
index 0000000000..8178daf60c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/presentational-hints-rollback.html
@@ -0,0 +1,125 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Cascade: rolling back the cascade with presentation hints</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#preshint">
+<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#default">
+<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#revert-layer">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/embedded-content-other.html#dimension-attributes">
+<meta name="assert" content="Checks that 'revert' considers presentational hints as part of the author origin,
+ and 'revert-layer' considers them an independent origin between the user origin and the author origin.">
+
+<style>
+@layer {
+ .revert-1 {
+ width: revert;
+ height: revert;
+ }
+ .revert-layer-1 {
+ width: revert-layer;
+ height: revert-layer;
+ }
+}
+
+.revert-2 {
+ width: revert;
+ height: revert;
+}
+.revert-layer-2 {
+ width: revert-layer;
+ height: revert-layer;
+}
+
+.revert-3 {
+ animation: revert-3 paused 2s -1s;
+}
+.revert-layer-3 {
+ animation: revert-layer-3 paused 2s -1s;
+}
+@keyframes revert-3 {
+ from, to {
+ width: revert;
+ height: revert;
+ }
+}
+@keyframes revert-layer-3 {
+ from, to {
+ width: revert-layer;
+ height: revert-layer;
+ }
+}
+</style>
+
+<div id="log"></div>
+
+<div id="tests">
+ <!-- 'revert' considers presentational hints as part of the author origin, so it rolls back to user origin.
+ The images should then get an 'auto' size, which will use the natural size of the resource. -->
+ <img class="revert-1" src="/css/support/60x60-green.png"
+ width="44" data-expected-client-width="60"
+ height="33" data-expected-client-height="60">
+ <img class="revert-2" src="/css/support/60x60-green.png"
+ width="44" data-expected-client-width="60"
+ height="33" data-expected-client-height="60">
+ <img class="revert-3" src="/css/support/60x60-green.png"
+ width="44" data-expected-client-width="60"
+ height="33" data-expected-client-height="60">
+ <img style="width: revert; height: revert" src="/css/support/60x60-green.png"
+ width="44" data-expected-client-width="60"
+ height="33" data-expected-client-height="60">
+
+ <!-- 'revert-layer' considers presentational hints as an independent origin, so it rolls back to them.
+ The images should then get size specified in the attributes. -->
+ <img class="revert-layer-1" src="/css/support/60x60-green.png"
+ width="44" data-expected-client-width="44"
+ height="33" data-expected-client-height="33">
+ <img class="revert-layer-2" src="/css/support/60x60-green.png"
+ width="44" data-expected-client-width="44"
+ height="33" data-expected-client-height="33">
+ <img class="revert-layer-3" src="/css/support/60x60-green.png"
+ width="44" data-expected-client-width="44"
+ height="33" data-expected-client-height="33">
+ <img style="width: revert-layer; height: revert-layer" src="/css/support/60x60-green.png"
+ width="44" data-expected-client-width="44"
+ height="33" data-expected-client-height="33">
+
+ <!-- 'revert' considers presentational hints as part of the author origin, so it rolls back to user origin.
+ The iframes should then get an 'auto' size, which will default to 300x150. -->
+ <iframe class="revert-1" src="/css/support/60x60-green.png"
+ width="44" data-expected-client-width="300"
+ height="33" data-expected-client-height="150"></iframe>
+ <iframe class="revert-2" src="/css/support/60x60-green.png"
+ width="44" data-expected-client-width="300"
+ height="33" data-expected-client-height="150"></iframe>
+ <iframe class="revert-3" src="/css/support/60x60-green.png"
+ width="44" data-expected-client-width="300"
+ height="33" data-expected-client-height="150"></iframe>
+ <iframe style="width: revert; height: revert" src="/css/support/60x60-green.png"
+ width="44" data-expected-client-width="300"
+ height="33" data-expected-client-height="150"></iframe>
+
+ <!-- 'revert-layer' considers presentational hints as an independent origin, so it rolls back to them.
+ The iframes should then get size specified in the attributes. -->
+ <iframe class="revert-layer-1" src="/css/support/60x60-green.png"
+ width="44" data-expected-client-width="44"
+ height="33" data-expected-client-height="33"></iframe>
+ <iframe class="revert-layer-2" src="/css/support/60x60-green.png"
+ width="44" data-expected-client-width="44"
+ height="33" data-expected-client-height="33"></iframe>
+ <iframe class="revert-layer-3" src="/css/support/60x60-green.png"
+ width="44" data-expected-client-width="44"
+ height="33" data-expected-client-height="33"></iframe>
+ <iframe style="width: revert-layer; height: revert-layer" src="/css/support/60x60-green.png"
+ width="44" data-expected-client-width="44"
+ height="33" data-expected-client-height="33"></iframe>
+</div>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<script>
+addEventListener("load", function() {
+ checkLayout("#tests > *", false);
+ done();
+}, {once: true});
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/reference/all-green.html b/testing/web-platform/tests/css/css-cascade/reference/all-green.html
new file mode 100644
index 0000000000..c70532129a
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/reference/all-green.html
@@ -0,0 +1 @@
+<html style="background: green"></html> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-cascade/reference/ref-filled-green-100px-square.xht b/testing/web-platform/tests/css/css-cascade/reference/ref-filled-green-100px-square.xht
new file mode 100644
index 0000000000..05a1379448
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/reference/ref-filled-green-100px-square.xht
@@ -0,0 +1,19 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>CSS Reftest Reference</title>
+ <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
+ <style type="text/css"><![CDATA[
+ div
+ {
+ background-color: green;
+ height: 100px;
+ width: 100px;
+ }
+ ]]></style>
+ </head>
+ <body>
+ <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+ <div></div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/reference/ref-green-text.html b/testing/web-platform/tests/css/css-cascade/reference/ref-green-text.html
new file mode 100644
index 0000000000..8183c04087
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/reference/ref-green-text.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Green text reference</title>
+<style>
+ .test { color: green; }
+</style>
+<body>
+ <p class="test">Test passes if this text is green.</p>
+</body>
diff --git a/testing/web-platform/tests/css/css-cascade/resources/scope.css b/testing/web-platform/tests/css/css-cascade/resources/scope.css
new file mode 100644
index 0000000000..780e90fb67
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/resources/scope.css
@@ -0,0 +1,4 @@
+@scope {
+ :scope { z-index:1; }
+ .a { z-index:2; }
+}
diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-001.html b/testing/web-platform/tests/css/css-cascade/revert-layer-001.html
new file mode 100644
index 0000000000..009867e751
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-layer-001.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: 'revert-layer' from one explicit layer to another</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="reference/ref-filled-green-100px-square.xht">
+
+<style>
+#target {
+ width: 100px;
+ height: 100px;
+}
+
+@layer {
+ #target { background-color: green; }
+}
+
+@layer {
+ #target {
+ background-color: red;
+ background-color: revert-layer;
+ }
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="target"></div>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-002.html b/testing/web-platform/tests/css/css-cascade/revert-layer-002.html
new file mode 100644
index 0000000000..38d3d33d93
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-layer-002.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: 'revert-layer' from the implicit outer layer to explicit</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="reference/ref-filled-green-100px-square.xht">
+
+<style>
+#target {
+ width: 100px;
+ height: 100px;
+}
+
+@layer {
+ #target { background-color: green; }
+}
+
+#target {
+ background-color: red;
+ background-color: revert-layer;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="target"></div>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-003.html b/testing/web-platform/tests/css/css-cascade/revert-layer-003.html
new file mode 100644
index 0000000000..e4e331c82d
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-layer-003.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: 'all: revert-layer'</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="reference/ref-filled-green-100px-square.xht">
+
+<style>
+@layer {
+ #target {
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ }
+}
+
+@layer {
+ #target {
+ width: 200px;
+ height: 200px;
+ background-color: red;
+ }
+
+ #target {
+ all: revert-layer;
+ }
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="target"></div>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-004.html b/testing/web-platform/tests/css/css-cascade/revert-layer-004.html
new file mode 100644
index 0000000000..b751359857
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-layer-004.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: 'revert-layer' to previous context</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="reference/ref-filled-green-100px-square.xht">
+
+<style>
+#target {
+ width: 100px;
+ height: 100px;
+ background-color: red;
+ background-color: revert-layer;
+}
+
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="target"></div>
+
+<script>
+target.attachShadow({mode: 'open'}).innerHTML = `
+<style>
+:host {
+ background-color: green;
+}
+</style>
+`;
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-005.html b/testing/web-platform/tests/css/css-cascade/revert-layer-005.html
new file mode 100644
index 0000000000..6cd4030727
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-layer-005.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: important 'revert-layer'</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="reference/ref-filled-green-100px-square.xht">
+
+<style>
+#target {
+ width: 100px;
+ height: 100px;
+}
+
+@layer {
+ #target { background-color: green; }
+}
+
+@layer {
+ #target {
+ background-color: red;
+ background-color: red !important;
+ background-color: revert-layer !important;
+ }
+}
+
+@layer {
+ #target {
+ background-color: red;
+ background-color: red !important;
+ }
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="target"></div>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-006.html b/testing/web-platform/tests/css/css-cascade/revert-layer-006.html
new file mode 100644
index 0000000000..678c3e1e9b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-layer-006.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: 'revert-layer' reverts origin when no lower priority declarations in the same origin</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="reference/ref-filled-green-100px-square.xht">
+
+<style>
+#outer {
+ background-color: red;
+ width: 100px;
+ height: 100px;
+ overflow: hidden;
+}
+#inner {
+ color: green;
+ background-color: green;
+ display: inline;
+ display: revert-layer; /* This should behave as 'revert', setting 'display' to 'block' */
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="outer">
+ <div id="inner">
+ This<br>
+ is<br>
+ filler<br>
+ text.<br>
+ This<br>
+ is<br>
+ filler<br>
+ text.
+ </div>
+</div>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-007.html b/testing/web-platform/tests/css/css-cascade/revert-layer-007.html
new file mode 100644
index 0000000000..7915beeafa
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-layer-007.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: 'revert-layer' chain</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="reference/ref-filled-green-100px-square.xht">
+
+<style>
+#target {
+ width: 100px;
+ height: 100px;
+}
+
+@layer {
+ #target { background-color: green; }
+}
+
+@layer {
+ #target {
+ background-color: red;
+ background-color: revert-layer;
+ }
+}
+
+@layer {
+ #target {
+ background-color: red;
+ background-color: revert-layer;
+ }
+}
+
+@layer {
+ #target {
+ background-color: red;
+ background-color: revert-layer;
+ }
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="target"></div>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-008.html b/testing/web-platform/tests/css/css-cascade/revert-layer-008.html
new file mode 100644
index 0000000000..cafb17dee1
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-layer-008.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: 'revert-layer' triggers a smooth transition</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+@layer revert-to, revert-from;
+
+@layer revert-from {
+ #target {
+ font-size: 10px;
+ transition: font-size 2s linear -1s;
+ }
+
+ #target.reverted {
+ font-size: revert-layer;
+ }
+}
+
+@layer revert-to {
+ #target { font-size: 20px; }
+}
+</style>
+
+<div id="target"></div>
+
+<script>
+function raf() {
+ return new Promise(resolve => requestAnimationFrame(resolve));
+}
+promise_test(async () => {
+ const target = document.getElementById('target');
+ getComputedStyle(target).getPropertyValue('font-size');
+
+ await raf();
+ target.classList.toggle('reverted');
+
+ const result = getComputedStyle(target).getPropertyValue('font-size');
+ assert_equals(result, '15px');
+}, "'revert-layer' should revert font-size to 20px and trigger a smooth transition");
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-009.html b/testing/web-platform/tests/css/css-cascade/revert-layer-009.html
new file mode 100644
index 0000000000..e5c8e62ae0
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-layer-009.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: 'revert-layer' from the style attribute to other style sheets</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="reference/ref-filled-green-100px-square.xht">
+
+<style>
+#target {
+ width: 100px;
+ height: 100px;
+ background-color: green;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="target" style="background-color: red; background-color: revert-layer"></div>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-010.html b/testing/web-platform/tests/css/css-cascade/revert-layer-010.html
new file mode 100644
index 0000000000..278905c6cf
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-layer-010.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: 'revert-layer' from animation origin to author origin</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="reference/ref-filled-green-100px-square.xht">
+
+<style>
+#target {
+ width: 150px;
+ height: 100px;
+ background-color: green;
+ animation: anim linear 2s -1s paused;
+}
+
+@keyframes anim {
+ from { width: 50px; }
+ to { width: revert-layer; }
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="target"></div>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-011.html b/testing/web-platform/tests/css/css-cascade/revert-layer-011.html
new file mode 100644
index 0000000000..73a3772f80
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-layer-011.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: 'revert-layer' from animation origin to author origin on custom property</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="reference/ref-filled-green-100px-square.xht">
+
+<style>
+#target {
+ width: var(--x);
+ --x: 150px;
+ height: 100px;
+ background-color: green;
+ animation: anim linear 2s -1s paused;
+}
+
+@property --x {
+ syntax: '<length>';
+ initial-value: 0px;
+ inherits: false;
+}
+
+@keyframes anim {
+ from { --x: 50px; }
+ to { --x: revert-layer; }
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="target"></div>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-012.html b/testing/web-platform/tests/css/css-cascade/revert-layer-012.html
new file mode 100644
index 0000000000..e065defb53
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-layer-012.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: important 'revert-layer' from the style attribute to other style sheets</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="reference/ref-filled-green-100px-square.xht">
+
+<style>
+#target {
+ width: 100px;
+ height: 100px;
+ background-color: green !important;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="target" style="background-color: red !important; background-color: revert-layer !important"></div>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-013.html b/testing/web-platform/tests/css/css-cascade/revert-layer-013.html
new file mode 100644
index 0000000000..862ee72746
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-layer-013.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: 'revert-layer' to host context</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer">
+<link rel="match" href="reference/ref-filled-green-100px-square.xht">
+
+<style>
+#target {
+ width: 100px;
+ height: 100px;
+ background-color: revert-layer;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="target"></div>
+
+<script>
+target.attachShadow({mode: 'open'}).innerHTML = `
+<style>
+@layer first {
+ :host { background-color: green; }
+}
+@layer second {
+ :host { background-color: revert-layer; }
+}
+</style>
+`;
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-014.html b/testing/web-platform/tests/css/css-cascade/revert-layer-014.html
new file mode 100644
index 0000000000..6b96862562
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-layer-014.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: 'revert-layer' in slotted context</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer">
+<link rel="match" href="reference/ref-filled-green-100px-square.xht">
+
+<style>
+#target {
+ width: 100px;
+ height: 100px;
+ background-color: revert-layer;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="host"><div id="target"></div></div>
+
+<script>
+host.attachShadow({mode: 'open'}).innerHTML = `
+<style>
+@layer first {
+ ::slotted(*) { background-color: green; }
+}
+@layer second {
+ ::slotted(*) { background-color: revert-layer; }
+}
+</style>
+<slot></slot>
+`;
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-015-ref.html b/testing/web-platform/tests/css/css-cascade/revert-layer-015-ref.html
new file mode 100644
index 0000000000..661016619c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-layer-015-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<style>
+input::placeholder { background-color: green; }
+</style>
+<input placeholder="placeholder">
diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-015.html b/testing/web-platform/tests/css/css-cascade/revert-layer-015.html
new file mode 100644
index 0000000000..a60f5d78f5
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-layer-015.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>CSS Cascade Layers: 'revert-layer' with shadow pseudo-element</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer">
+<link rel="match" href="revert-layer-015-ref.html">
+
+<style>
+@layer first {
+ input::placeholder { background-color: green; }
+}
+@layer second {
+ input::placeholder { background-color: revert-layer; }
+}
+</style>
+<input placeholder="placeholder">
diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-001.html b/testing/web-platform/tests/css/css-cascade/revert-val-001.html
new file mode 100644
index 0000000000..b3d79d9e69
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-val-001.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: 'revert' keyword for 'display' property of div element</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="https://www.w3.org/TR/css-cascade-4/#default">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/rendering.html#flow-content-3">
+ <link rel="match" href="reference/ref-filled-green-100px-square.xht">
+ <meta name="assert" content="On a <div>, display:revert should compute to display:block per the default styles for <div>s in the UA stylesheet.">
+ <style>
+#outer {
+ background-color: red;
+ width: 100px;
+ height: 100px;
+ overflow: hidden;
+}
+#inner {
+ color: green;
+ background-color: green;
+ display: inline;
+ display: revert;/* since #inner is a <div>, this should compute to 'block' */
+}
+ </style>
+</head>
+<body>
+ <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+ <div id="outer">
+ <div id="inner">
+ This<br>
+ is<br>
+ filler<br>
+ text.<br>
+ This<br>
+ is<br>
+ filler<br>
+ text.
+ </div>
+ </div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-002.html b/testing/web-platform/tests/css/css-cascade/revert-val-002.html
new file mode 100644
index 0000000000..d145ea42b2
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-val-002.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Cascade: 'revert' keyword interaction with !important</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mozilla" href="https://mozilla.org">
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#default">
+<link rel="match" href="reference/ref-filled-green-100px-square.xht">
+<style>
+#outer {
+ background-color: red;
+ width: 100px;
+ height: 100px;
+ overflow: hidden;
+}
+#inner {
+ /* This should win over `revert` */
+ display: block !important;
+}
+#inner {
+ color: green;
+ background-color: green;
+ display: revert;
+}
+</style>
+</head>
+<body>
+ <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+ <div id="outer">
+ <span id="inner">
+ This<br>
+ is<br>
+ filler<br>
+ text.<br>
+ This<br>
+ is<br>
+ filler<br>
+ text.
+ </span>
+ </div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-003.html b/testing/web-platform/tests/css/css-cascade/revert-val-003.html
new file mode 100644
index 0000000000..b819eb0b2d
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-val-003.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Cascade: 'revert' keyword in transition</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mozilla" href="https://mozilla.org">
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#default">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+html, body { margin: 0 }
+h1 {
+ margin: 0;
+ transition: margin 10s;
+ transition-delay: -5s; /* So we can expect it to be half-way the transition when toggling the property */
+}
+</style>
+<h1>This is a header that should get some margin</h1>
+<script>
+test(function() {
+ const el = document.querySelector("h1");
+ const cs = getComputedStyle(el);
+ assert_equals(cs.marginTop, "0px", "Margin before transition");
+ el.style.margin = "revert";
+ const midTransition = cs.marginTop;
+ assert_not_equals(midTransition, "0px", "Margin mid transition");
+ el.style.transition = "none";
+ assert_not_equals(cs.marginTop, midTransition, "Default margin");
+}, "revert works with transitions");
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-004.html b/testing/web-platform/tests/css/css-cascade/revert-val-004.html
new file mode 100644
index 0000000000..6a7046c0e4
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-val-004.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<title>CSS Cascade: using 'revert' with the 'all' property</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#default">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ div {
+ display: inline;
+ }
+ .revert {
+ all: revert;
+ }
+</style>
+<div id=div></div>
+<script>
+ test(function() {
+ let cs = getComputedStyle(div);
+ assert_equals(cs.display, 'inline');
+ div.className = 'revert';
+ assert_equals(cs.display, 'block');
+ }, 'The revert keyword works with the all property');
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-005.html b/testing/web-platform/tests/css/css-cascade/revert-val-005.html
new file mode 100644
index 0000000000..7295605d4b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-val-005.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<title>CSS Cascade: 'revert' in css-logical properties</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#default">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ #h1_physical {
+ margin: 0px;
+ margin: revert;
+ }
+ #h1_logical {
+ margin: 0px;
+ margin-inline-start: revert;
+ margin-inline-end: revert;
+ margin-block-start: revert;
+ margin-block-end: revert;
+ }
+</style>
+<h1 id=h1_physical></h1>
+<h1 id=h1_logical></h1>
+<h1 id=ref></h1>
+<script>
+ test(function() {
+ let actual = getComputedStyle(h1_physical).marginTop;
+ let expected = getComputedStyle(ref).marginTop;
+ // This test assumes that the UA style sheet sets a non-0px value on
+ // <h1> elements:
+ assert_not_equals(expected, '0px');
+ assert_equals(actual, expected);
+ }, 'The revert keyword works with physical properties');
+
+ test(function() {
+ let actual = getComputedStyle(h1_logical).marginTop;
+ let expected = getComputedStyle(ref).marginTop;
+ // This test assumes that the UA style sheet sets a non-0px value on
+ // <h1> elements:
+ assert_not_equals(expected, '0px');
+ assert_equals(actual, expected);
+ }, 'The revert keyword works with logical properties');
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-006.html b/testing/web-platform/tests/css/css-cascade/revert-val-006.html
new file mode 100644
index 0000000000..2b238f6d7e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-val-006.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>CSS Cascade: 'revert' keyword in keyframe animations</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#default">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ @keyframes test {
+ from { margin-top: 0px; }
+ 50% { margin-top: revert; }
+ to { margin-top: 0px; }
+ }
+ #h1 {
+ margin-top: 0px;
+ animation: test linear 1000s -500s;
+ }
+</style>
+<h1 id=h1></h1>
+<h1 id=ref></h1>
+<script>
+ test(function() {
+ let actual = getComputedStyle(h1).marginTop;
+ let expected = getComputedStyle(ref).marginTop;
+ // This test assumes that the UA style sheet sets a non-0px value on
+ // <h1> elements:
+ assert_not_equals(expected, '0px');
+ assert_equals(actual, expected);
+ }, 'The revert keyword works with @keyframes');
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-007.html b/testing/web-platform/tests/css/css-cascade/revert-val-007.html
new file mode 100644
index 0000000000..38078fcfa9
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-val-007.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>CSS Cascade: 'revert' in keyframe animations on identical elements</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#default">
+<link rel="help" href="https://crbug.com/1065387">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ @keyframes test {
+ from { margin-top: revert; }
+ to { margin-top: 100px; }
+ }
+ .anim {
+ margin-top: 0px;
+ animation: test linear 1s paused;
+ }
+</style>
+<h1 class="anim"></h1>
+<h1 class="anim"></h1>
+<h1 class="anim"></h1>
+<h1 id=ref></h1>
+<script>
+ test(function() {
+ // This querySelectorAll includes #ref, but that's OK.
+ let targets = document.querySelectorAll('h1');
+ for (let t of targets) {
+ let actual = getComputedStyle(t).marginTop;
+ let expected = getComputedStyle(ref).marginTop;
+ // This test assumes that the UA style sheet sets a non-0px value on
+ // <h1> elements:
+ assert_not_equals(expected, '0px');
+ assert_equals(actual, expected);
+ }
+ }, 'A @keyframe animation with revert works when applied to multiple identical elements');
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-008.html b/testing/web-platform/tests/css/css-cascade/revert-val-008.html
new file mode 100644
index 0000000000..77cceae7a3
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-val-008.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>CSS Cascade: 'revert' in final keyframe of web animation</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#default">
+<link rel="help" href="https://crbug.com/1065387">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h1 id=h1></h1>
+<h1 id=ref></h1>
+<script>
+ test(function() {
+ let expected_lower = parseInt(getComputedStyle(ref).marginTop);
+ let expected_upper = expected_lower * 2;
+
+ h1.animate([
+ { marginTop: `${expected_lower * 4}px` },
+ { marginTop: `${expected_lower * 3}px` },
+ { marginTop: `${expected_lower * 2}px` },
+ { marginTop: 'revert' },
+ ], {
+ duration: 4000,
+ delay: -3500,
+ }).pause();
+
+ let actual = parseInt(getComputedStyle(h1).marginTop);
+
+ // This test assumes that the UA style sheet sets a non-0px value on
+ // <h1> elements:
+ assert_not_equals(expected_lower, 0);
+ assert_not_equals(expected_upper, 0);
+ assert_between_exclusive(actual, expected_lower, expected_upper);
+ }, 'The revert keyword works in the final frame of a web animation');
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-009.html b/testing/web-platform/tests/css/css-cascade/revert-val-009.html
new file mode 100644
index 0000000000..e9683e90f4
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-val-009.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>CSS Cascade: 'revert' in implicit keyframes</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#default">
+<link rel="help" href="https://crbug.com/1065387">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h1 id=h1></h1>
+<h1 id=ref></h1>
+<script>
+ test(function() {
+ let expected_lower = parseInt(getComputedStyle(ref).marginTop);
+ let expected_upper = expected_lower * 2;
+ h1.style = `margin-top: ${expected_lower * 1000}px; margin-top: revert;`;
+
+ h1.animate([
+ { marginTop: `${expected_upper}px` },
+ ], {
+ duration: 1000,
+ delay: -500,
+ }).pause();
+
+ let actual = parseInt(getComputedStyle(h1).marginTop);
+
+ // This test assumes that the UA style sheet sets a non-0px value on
+ // <h1> elements:
+ assert_not_equals(expected_lower, 0);
+ assert_not_equals(expected_upper, 0);
+ assert_between_exclusive(actual, expected_lower, expected_upper);
+ }, 'The revert keyword works in implicit keyframes');
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-010.html b/testing/web-platform/tests/css/css-cascade/revert-val-010.html
new file mode 100644
index 0000000000..58449e9132
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-val-010.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>CSS Cascade: 'revert' appearing in setKeyframes</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#default">
+<link rel="help" href="https://drafts.csswg.org/web-animations-1/#dom-keyframeeffect-setkeyframes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<h1 id=h1></h1>
+<script>
+ test(function() {
+ let original = parseInt(getComputedStyle(h1).marginTop);
+
+ // This test assumes that the UA style sheet sets a non-0px value on
+ // <h1> elements:
+ assert_not_equals(original, 0);
+
+ let animation = h1.animate([
+ { marginTop: `${original*4}px` },
+ { marginTop: `${original*8}px` },
+ ], {
+ duration: 1000000,
+ delay: -500000,
+ easing: 'steps(2, end)'
+ });
+
+ let animated = parseInt(getComputedStyle(h1).marginTop);
+ assert_equals(animated, original*6);
+
+ animation.effect.setKeyframes([
+ { marginTop: 'revert' },
+ { marginTop: `${original*3}px` },
+ ]);
+
+ let animated_revert = parseInt(getComputedStyle(h1).marginTop);
+ assert_equals(animated_revert, original*2);
+ }, 'The revert works when appearing in setKeyframes');
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-011.html b/testing/web-platform/tests/css/css-cascade/revert-val-011.html
new file mode 100644
index 0000000000..9c034084c5
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/revert-val-011.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: 'revert' from mutating inline style</title>
+ <link rel="help" href="https://drafts.csswg.org/css-cascade/#default">
+ <link rel="author" href="mailto:sesse@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <style>
+ .outer {
+ left: 1px;
+ }
+ </style>
+</head>
+<body>
+ <p class="outer" id="el">Test passes if the text is black (not red).</p>
+</body>
+<script>
+test(() => {
+ el.offsetTop;
+ assert_equals(getComputedStyle(el).left, "1px", "style is set correctly");
+});
+test(() => {
+ el.offsetTop;
+ el.style.left = "2px";
+ assert_equals(getComputedStyle(el).left, "2px", "style is modified correctly");
+});
+test(() => {
+ el.offsetTop;
+ el.style.left = "revert";
+ assert_equals(getComputedStyle(el).left, "auto", "style is reverted correctly");
+});
+</script>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/scope-deep.html b/testing/web-platform/tests/css/css-cascade/scope-deep.html
new file mode 100644
index 0000000000..e7dd96c5ac
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/scope-deep.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<title>@scope - deeply nested</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ main * { background-color: black; }
+</style>
+<main id=main></main>
+<script>
+
+// @scope (.s0) { @scope (.s1) { ... span {} ... } }
+function createStyleSheet(length, i) {
+ if (length == 0)
+ return 'span { background-color: green; }';
+ if (i === undefined)
+ i = 0;
+ return `
+ @scope (.s${i}) {
+ ${createStyleSheet(length - 1, i + 1)}
+ }
+ `.trim();
+}
+
+// <div class=s0><div class=s1>...<span/>...</div></div>
+function createElementChain(length, i) {
+ if (length < 1)
+ throw 'Invalid length';
+ if (i === undefined)
+ i = 0;
+ let e = document.createElement('div');
+ e.classList.add(`s${i}`);
+ if (length > 1)
+ e.append(createElementChain(length - 1, i + 1));
+ else
+ e.append(document.createElement('span'));
+ return e;
+}
+
+const COUNT = 90;
+
+let style_node = document.createElement('style');
+style_node.textContent = createStyleSheet(COUNT);
+main.append(style_node);
+
+main.append(createElementChain(COUNT));
+
+test(() => {
+ for (let span of main.querySelectorAll('span'))
+ assert_equals(getComputedStyle(span).backgroundColor, 'rgb(0, 128, 0)');
+}, 'Deep @scope nesting');
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/scope-evaluation.html b/testing/web-platform/tests/css/css-cascade/scope-evaluation.html
new file mode 100644
index 0000000000..7c92ae1a73
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/scope-evaluation.html
@@ -0,0 +1,513 @@
+<!DOCTYPE html>
+<title>@scope - evaluation</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+function test_scope(script_element, callback_fn, description) {
+ test((t) => {
+ // The provided <script> element must be an immedate subsequent sibling of
+ // a <template> element.
+ let template_element = script_element.previousElementSibling;
+ assert_equals(template_element.tagName, 'TEMPLATE');
+
+ t.add_cleanup(() => main.replaceChildren());
+
+ main.append(template_element.content.cloneNode(true));
+
+ callback_fn();
+ }, description);
+}
+
+function assert_green(selector) {
+ assert_equals(getComputedStyle(main.querySelector(selector)).backgroundColor, 'rgb(0, 128, 0)');
+}
+function assert_not_green(selector) {
+ assert_equals(getComputedStyle(main.querySelector(selector)).backgroundColor, 'rgb(0, 0, 0)');
+}
+</script>
+<style>
+ main * {
+ background-color: black;
+ }
+</style>
+<main id=main>
+</main>
+
+<!-- Tests follow -->
+
+<template>
+ <style>
+ @scope (.a) {
+ span { background-color: green; }
+ }
+ </style>
+ <div class=a>
+ <span>green</span>
+ </div>
+ <div class=b>
+ <span>not green</span>
+ </div>
+ <span>not green</span>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_green('.a > span');
+ assert_not_green('.b > span');
+ assert_not_green(':scope > span');
+}, 'Single scope');
+</script>
+
+<template>
+ <style>
+ @scope (.a) {
+ .a { background-color: green; }
+ }
+ </style>
+ <div class=a> <!-- green -->
+ <span>not green</span>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_not_green('.a');
+ assert_not_green('.a > span');
+}, 'Scope can not match its own root without :scope');
+</script>
+
+<template>
+ <style>
+ @scope (.a) {
+ :scope { background-color: green; }
+ }
+ </style>
+ <div class=a> <!-- green -->
+ <span>not green</span>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_green('.a');
+ assert_not_green('.a > span');
+}, 'Selecting self with :scope');
+</script>
+
+<template>
+ <style>
+ @scope (.a) to (.c) {
+ span { background-color: green; }
+ }
+ </style>
+ <div class=a>
+ <div class=b>
+ <span>green</span>
+ </div>
+ <div class=c>
+ <span>not green</span>
+ </div>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_green('.b > span');
+ assert_not_green('.c > span');
+}, 'Single scope with limit');
+</script>
+
+<template>
+ <style>
+ @scope (.a) {
+ :scope > span { background-color: green; }
+ }
+ </style>
+ <div class=a>
+ <span>green</span>
+ <div class=b>
+ <span>not green</span>
+ </div>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_green('.a > span');
+ assert_not_green('.b > span');
+}, 'Single scope, :scope pseudo in main selector');
+</script>
+
+<template>
+ <style>
+ @scope (.a) to (:scope > .b) {
+ span { background-color: green; }
+ }
+ </style>
+ <div class=a>
+ <div class=b>
+ <span>not green</span>
+ </div>
+ <div class=c>
+ <div class=b>
+ <span>green</span>
+ </div>
+ </div>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_not_green('.a > .b > span');
+ assert_green('.a > .c > .b > span');
+}, 'Single scope, :scope pseudo in to-selector');
+</script>
+
+<template>
+ <style>
+ @scope (.a) to (:scope > .b) {
+ span { background-color: green; }
+ }
+ </style>
+ <div class=a>
+ <div class=b>
+ <span>not green</span>
+ </div>
+ <div class=a>
+ <div class=b>
+ <span>green</span>
+ </div>
+ </div>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_not_green('.a > .b > span');
+ // Note that this span is in the outer .a-scope, but not in the inner scope.
+ assert_green('.a > .a > .b > span');
+}, 'Multiple scopes, :scope pseudo in to-selector');
+</script>
+
+<template>
+ <style>
+ @scope (.a) {
+ @scope (:scope > .b) {
+ span { background-color: green; }
+ }
+ }
+ </style>
+ <div class=a>
+ <div class=b>
+ <span>green</span>
+ </div>
+ <div>
+ <div class=b>
+ <span>not green</span>
+ </div>
+ </div>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_green('.a > .b > span');
+ assert_not_green('.a > div > .b > span');
+}, 'Inner @scope with :scope in from-selector');
+</script>
+
+<template>
+ <style>
+ @scope (.a) to (:scope > .b) {
+ .c { background-color: green; }
+ }
+ </style>
+ <div class=a>
+ <div>
+ <div class=a>
+ <div class=b>
+ <div class=c></div>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ // Not in the inner scope, but is in the outer scope.
+ assert_green('.c');
+}, 'Multiple scopes from same @scope-rule, only one limited');
+</script>
+
+<template>
+ <style>
+ @scope (.a) to (.b) {
+ .c { background-color: green; }
+ }
+ </style>
+ <div class=a>
+ <div>
+ <div class=a>
+ <div class=b>
+ <div class=c></div>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_not_green('.c');
+}, 'Multiple scopes from same @scope-rule, both limited');
+</script>
+
+<template>
+ <style>
+ @scope (.a) {
+ @scope (.b) {
+ span { background-color: green; }
+ }
+ }
+ </style>
+ <div class=a>
+ <div class=b>
+ <span>green</span>
+ </div>
+ <span>not green</span>
+ </div>
+ <div class=b>
+ <span>not green</span>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_green('.a > .b > span');
+ assert_not_green('.a > span');
+ assert_not_green(':scope > .b > span');
+}, 'Nested scopes');
+</script>
+
+<template>
+ <style>
+ @scope (.b) {
+ @scope (.a) {
+ span { background-color: green; }
+ }
+ }
+ </style>
+ <div class=a>
+ <div class=b>
+ <span>not green</span>
+ </div>
+ <span>not green</span>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_not_green('.a > .b > span');
+ assert_not_green('.a > span');
+}, 'Nested scopes, reverse');
+</script>
+
+
+<template>
+ <style>
+ @scope (.a) {
+ @scope (.b) to (.c) {
+ span { background-color: green; }
+ }
+ }
+ </style>
+ <div class=a>
+ <div class=b>
+ <span>green</span>
+ </div>
+ <div class=b>
+ <div class=c>
+ <span>not green</span>
+ </div>
+ </div>
+ <span>not green</span>
+ </div>
+ <div class=b>
+ <span>not green</span>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_green('.a > .b > span');
+ assert_not_green('.a > span');
+ assert_not_green('.a > .b > .c > span');
+ assert_not_green(':scope > .b > span');
+}, 'Nested scopes, with to-selector');
+</script>
+
+<template>
+ <style>
+ @scope (.a) {
+ :scope { background-color: green; }
+ }
+ </style>
+ <div class=a></div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_green('.a');
+}, ':scope selecting itself');
+</script>
+
+<template>
+ <style>
+ @scope (.a) to (.b) {
+ * { background-color: green; }
+ }
+ </style>
+ <div id=above>
+ <div class=a>
+ <div>
+ <div class=b>
+ <div id=below></div>
+ </div>
+ </div>
+ </div>
+ <div id=adjacent></div>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_not_green('#above');
+ assert_not_green('#adjacent');
+ assert_not_green('.a');
+ assert_green('.a > div');
+ assert_not_green('.b');
+ assert_not_green('#below');
+}, 'The scoping limit is not in scope');
+</script>
+
+<template>
+ <style>
+ @scope (.a) to (.b > *) {
+ * { background-color: green; }
+ }
+ </style>
+ <div id=above>
+ <div class=a>
+ <div>
+ <div class=b>
+ <div id=limit></div>
+ </div>
+ </div>
+ </div>
+ <div id=adjacent></div>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_not_green('#above');
+ assert_not_green('#adjacent');
+ assert_not_green('.a');
+ assert_green('.a > div');
+ assert_green('.b');
+ assert_not_green('#limit');
+}, 'Simulated inclusive scoping limit');
+</script>
+
+<template>
+ <style>
+ @scope (.a) to (:scope) {
+ * { background-color: green; }
+ }
+ </style>
+ <div id=above>
+ <div class=a>
+ <div>
+ <div class=b>
+ <div id=inner></div>
+ </div>
+ </div>
+ </div>
+ <div id=adjacent></div>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_not_green('#above');
+ assert_not_green('#adjacent');
+ assert_not_green('.a');
+ assert_not_green('.a > div');
+ assert_not_green('.b');
+ assert_not_green('#inner');
+}, 'Scope with no elements');
+</script>
+
+
+<template>
+ <style>
+ @scope (.a) {
+ :scope + .c { background-color: green; }
+ }
+ </style>
+ <div class=a>
+ <div class=a></div>
+ <div class=c></div>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ // A :scope sibling can never match, as the scoping element must
+ // be on the ancestor chain.
+ assert_not_green('.c');
+}, ':scope direct adjacent sibling');
+</script>
+
+
+<template>
+ <style>
+ @scope (.a) {
+ :scope + .c { background-color: green; }
+ }
+ </style>
+ <div class=a>
+ <div class=a></div>
+ <div></div>
+ <div class=c></div>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ // A :scope sibling can never match, as the scoping element must
+ // be on the ancestor chain.
+ assert_not_green('.c');
+}, ':scope indirect adjacent sibling');
+</script>
+
+
+<template>
+ <style>
+ @scope (.a) {
+ > span { background-color: green; }
+ }
+ </style>
+ <div class=a>
+ <span>green</span>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_green('.a > span');
+}, 'Relative selector inside @scope');
+</script>
+
+
+<template>
+ <style>
+ @scope (.a) {
+ /* Can never match anything. */
+ :scope > :scope { background-color: green; }
+ }
+ </style>
+ <div class=a>
+ <div id=inner class=a>
+ </div>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_not_green('.a');
+ assert_not_green('#inner');
+}, ':scope in two different compounds');
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/scope-implicit-external.html b/testing/web-platform/tests/css/css-cascade/scope-implicit-external.html
new file mode 100644
index 0000000000..d1ac738b77
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/scope-implicit-external.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>@scope - implicit scope root (external sheet)</title>
+ <link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div class="a outside"><div>
+ <div id=root>
+ <link rel="stylesheet" href="resources/scope.css">
+ <div class=a></div>
+ </div>
+ <div class="a outside"><div>
+
+ <script>
+ test((t) => {
+ assert_equals(getComputedStyle(root).zIndex, '1');
+ assert_equals(getComputedStyle(document.querySelector('#root > .a')).zIndex, '2');
+
+ let outside = document.querySelectorAll('.outside');
+ assert_equals(outside.length, 2);
+ for (let div of outside) {
+ assert_equals(getComputedStyle(div).zIndex, 'auto');
+ }
+ }, '@scope with external stylesheet');
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/scope-implicit.html b/testing/web-platform/tests/css/css-cascade/scope-implicit.html
new file mode 100644
index 0000000000..c49abb0a38
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/scope-implicit.html
@@ -0,0 +1,173 @@
+<!DOCTYPE html>
+<title>@scope - implicit scope root</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<main id=main></main>
+
+<template id=test_basic>
+ <div>
+ <style>
+ @scope {
+ .a { z-index:1; }
+ }
+ </style>
+ <div id=inner class=a></div>
+ </div>
+ <div id=outer class=a></div>
+</template>
+<script>
+test((t) => {
+ t.add_cleanup(() => main.replaceChildren());
+ main.append(test_basic.content.cloneNode(true));
+
+ assert_equals(getComputedStyle(inner).zIndex, '1');
+ assert_equals(getComputedStyle(outer).zIndex, 'auto');
+}, '@scope without prelude implicitly scopes to parent of owner node');
+</script>
+
+<template id=test_scope_pseudo>
+ <div>
+ <div></div>
+ </div>
+ <div>
+ <div id=root>
+ <style>
+ @scope {
+ :scope { z-index:1; }
+ }
+ </style>
+ <div>
+ <div></div>
+ </div>
+ </div>
+ </div>
+ <div>
+ <div></div>
+ </div>
+</template>
+<script>
+test((t) => {
+ t.add_cleanup(() => main.replaceChildren());
+ main.append(test_scope_pseudo.content.cloneNode(true));
+
+ assert_equals(getComputedStyle(root).zIndex, '1');
+
+ // Only #root should be affected.
+ for (let div of main.querySelectorAll('div:not(#root)')) {
+ assert_equals(getComputedStyle(div).zIndex, 'auto');
+ }
+}, ':scope can style implicit root');
+</script>
+
+<template id=test_duplicate>
+ <div>
+ <style>
+ @scope {
+ .a { z-index:1; }
+ }
+ </style>
+ <div id=first class=a></div>
+ </div>
+ <div>
+ <style>
+ @scope {
+ .a { z-index:1; }
+ }
+ </style>
+ <div id=second class=a></div>
+ </div>
+ <div id=outer class=a></div>
+</template>
+<script>
+test((t) => {
+ t.add_cleanup(() => main.replaceChildren());
+ main.append(test_duplicate.content.cloneNode(true));
+
+ assert_equals(getComputedStyle(first).zIndex, '1');
+ assert_equals(getComputedStyle(second).zIndex, '1');
+ assert_equals(getComputedStyle(outer).zIndex, 'auto');
+}, '@scope works with two identical stylesheets');
+</script>
+
+
+<template id=test_forgiving>
+ <div>
+ <style>
+ @scope ($invalid) {
+ #a { z-index:1; }
+ }
+ </style>
+ <div id=a></div>
+ </div>
+</template>
+<script>
+test((t) => {
+ t.add_cleanup(() => main.replaceChildren());
+ main.append(test_forgiving.content.cloneNode(true));
+
+ assert_equals(getComputedStyle(a).zIndex, 'auto');
+}, '@scope with effectively empty :is() must not match anything');
+</script>
+
+<template id=test_implicit_descendant>
+ <div id=div>
+ <style>
+ @scope {
+ #div { z-index:1; }
+ }
+ </style>
+ </div>
+</template>
+<script>
+test((t) => {
+ t.add_cleanup(() => main.replaceChildren());
+ main.append(test_implicit_descendant.content.cloneNode(true));
+
+ assert_equals(getComputedStyle(div).zIndex, 'auto');
+}, 'Implicit @scope has implicitly added :scope descendant combinator');
+</script>
+
+<template id=test_implicit_relative>
+ <div id=outer>
+ <style>
+ @scope {
+ > div { z-index:1; }
+ }
+ </style>
+ <div id=child>
+ <div id=inner></div>
+ </div>
+ </div>
+</template>
+<script>
+test((t) => {
+ t.add_cleanup(() => main.replaceChildren());
+ main.append(test_implicit_relative.content.cloneNode(true));
+
+ assert_equals(getComputedStyle(outer).zIndex, 'auto');
+ assert_equals(getComputedStyle(child).zIndex, '1');
+ assert_equals(getComputedStyle(inner).zIndex, 'auto');
+}, 'Implicit @scope with inner relative selector');
+</script>
+
+<template id=test_implicit_descendant_nesting_selector>
+ <div id=div>
+ <style>
+ @scope {
+ /* Behaves like :scope */
+ & { z-index:1; }
+ }
+ </style>
+ <div id=inner></div>
+ </div>
+</template>
+<script>
+test((t) => {
+ t.add_cleanup(() => main.replaceChildren());
+ main.append(test_implicit_descendant_nesting_selector.content.cloneNode(true));
+
+ assert_equals(getComputedStyle(div).zIndex, '1');
+ assert_equals(getComputedStyle(inner).zIndex, 'auto');
+}, 'Implicit @scope with inner nesting selector');
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/scope-invalidation.html b/testing/web-platform/tests/css/css-cascade/scope-invalidation.html
new file mode 100644
index 0000000000..62212f7921
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/scope-invalidation.html
@@ -0,0 +1,170 @@
+<!DOCTYPE html>
+<title>@scope - invalidation</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+function test_scope_invalidation(script_element, callback_fn, description) {
+ test((t) => {
+ // The provided <script> element must be an immedate subsequent sibling of
+ // a <template> element.
+ let template_element = script_element.previousElementSibling;
+ assert_equals(template_element.tagName, 'TEMPLATE');
+
+ t.add_cleanup(() => {
+ while (main.firstChild)
+ main.firstChild.remove()
+ });
+
+ main.append(template_element.content.cloneNode(true));
+
+ callback_fn();
+ }, description);
+}
+
+function assert_green(element) {
+ assert_equals(getComputedStyle(element).backgroundColor, 'rgb(0, 128, 0)');
+}
+function assert_not_green(element) {
+ assert_equals(getComputedStyle(element).backgroundColor, 'rgb(0, 0, 0)');
+}
+</script>
+<style>
+ main * {
+ background-color: black;
+ }
+</style>
+<main id=main>
+</main>
+
+<!-- Tests follow -->
+
+<template>
+ <style>
+ @scope (.a) {
+ span { background-color: green; }
+ }
+ </style>
+ <div>
+ <span></span>
+ </div>
+</template>
+<script>
+test_scope_invalidation(document.currentScript, () => {
+ let div = main.querySelector('div');
+ let span = main.querySelector('div > span');
+ assert_not_green(span);
+ div.classList.add('a');
+ assert_green(span);
+ div.classList.remove('a');
+ assert_not_green(span);
+}, 'Element becoming scope root');
+</script>
+
+
+<template>
+ <style>
+ @scope (.a) {
+ :scope { background-color: green; }
+ }
+ </style>
+ <div class=b></div>
+</template>
+<script>
+test_scope_invalidation(document.currentScript, () => {
+ let b = main.querySelector('.b');
+ assert_not_green(b);
+ b.classList.add('a');
+ assert_green(b);
+ b.classList.remove('a');
+ assert_not_green(b);
+}, 'Element becoming scope root, with inner :scope rule');
+</script>
+
+<template>
+ <style>
+ @scope (.a) to (.b) {
+ span { background-color: green; }
+ }
+ </style>
+ <div class=a>
+ <div>
+ <span></span>
+ </div>
+ </div>
+</template>
+<script>
+test_scope_invalidation(document.currentScript, () => {
+ let inner_div = main.querySelector('.a > div');
+ let span = main.querySelector('.a > div > span');
+ assert_green(span);
+ inner_div.classList.add('b');
+ assert_not_green(span);
+ inner_div.classList.remove('b');
+ assert_green(span);
+}, 'Element becoming scope limit');
+</script>
+
+
+<template>
+ <style>
+ @scope (.a) {
+ @scope (.b) {
+ span { background-color: green; }
+ }
+ }
+ </style>
+ <div>
+ <div>
+ <span></span>
+ </div>
+ </div>
+</template>
+<script>
+test_scope_invalidation(document.currentScript, () => {
+ let outer_div = main.querySelector(':scope > div');
+ let inner_div = main.querySelector(':scope > div > div');
+ let span = main.querySelector('div > div > span');
+
+ assert_not_green(span);
+
+ outer_div.classList.add('a');
+ assert_not_green(span);
+
+ inner_div.classList.add('b');
+ assert_green(span);
+
+ // Toggle .b while .a remains.
+ inner_div.classList.remove('b');
+ assert_not_green(span);
+ inner_div.classList.add('b');
+ assert_green(span);
+
+ // Toggle .a while .b remains.
+ outer_div.classList.remove('a');
+ assert_not_green(span);
+ outer_div.classList.add('a');
+ assert_green(span);
+}, 'Toggling inner/outer scope roots');
+</script>
+
+
+<template>
+ <style>
+ @scope (.a) {
+ :scope { background-color:green; }
+ }
+ </style>
+ <div></div>
+</template>
+<script>
+test_scope_invalidation(document.currentScript, () => {
+ let div = main.querySelector('main > div');
+ assert_not_green(div);
+ div.classList.add('a');
+ assert_green(div);
+ div.classList.remove('a');
+ assert_not_green(div);
+}, 'Element becoming root, with :scope in subject');
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/scope-nesting.html b/testing/web-platform/tests/css/css-cascade/scope-nesting.html
new file mode 100644
index 0000000000..34a13a1cb1
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/scope-nesting.html
@@ -0,0 +1,170 @@
+<!DOCTYPE html>
+<title>@scope - nesting (&)</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/#nest-selector">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<main id=main></main>
+
+<template id=test_nest_scope_end>
+ <div>
+ <style>
+ @scope (.a) to (& > &) {
+ * { z-index:1; }
+ }
+ </style>
+ <div class=a> <!-- This scope is limited by the element below. -->
+ <div class=a> <!-- This scope is limited by its own root. -->
+ <div id=below></div>
+ </div>
+ </div>
+ </div>
+ <div id=outside></div>
+</template>
+<script>
+test((t) => {
+ t.add_cleanup(() => main.replaceChildren());
+ main.append(test_nest_scope_end.content.cloneNode(true));
+
+ assert_equals(getComputedStyle(below).zIndex, 'auto');
+ assert_equals(getComputedStyle(outside).zIndex, 'auto');
+}, 'Nesting-selector in <scope-end>');
+</script>
+
+<template id=test_nest_scope_end_implicit_scope>
+ <div>
+ <style>
+ /* (.b) behaves like (:scope .b), due :scope being prepended
+ implicitly. */
+ @scope (.a) to (.b) {
+ :scope { z-index:1; }
+ }
+
+ /* Should not match, since <scope-end> refers to the scope itself. */
+ @scope (.a) to (.b:scope) {
+ :scope { z-index:42; }
+ }
+ </style>
+ <div class="a b">
+ <div class=b>
+ <div id=below></div>
+ </div>
+ </div>
+ </div>
+ <div id=outside></div>
+</template>
+<script>
+test((t) => {
+ t.add_cleanup(() => main.replaceChildren());
+ main.append(test_nest_scope_end_implicit_scope.content.cloneNode(true));
+ let a = document.querySelector('.a');
+ let b = document.querySelector('.a > .b');
+ assert_equals(getComputedStyle(a).zIndex, '1');
+ assert_equals(getComputedStyle(b).zIndex, 'auto');
+ assert_equals(getComputedStyle(below).zIndex, 'auto');
+ assert_equals(getComputedStyle(outside).zIndex, 'auto');
+}, 'Implicit :scope in <scope-end>');
+</script>
+
+<template id=test_relative_selector_scope_end>
+ <div>
+ <style>
+ @scope (.a) to (> .b) {
+ *, :scope { z-index:1; }
+ }
+ </style>
+ <div class="a b">
+ <div class=b>
+ <div id=below></div>
+ </div>
+ </div>
+ </div>
+ <div id=outside></div>
+</template>
+<script>
+test((t) => {
+ t.add_cleanup(() => main.replaceChildren());
+ main.append(test_relative_selector_scope_end.content.cloneNode(true));
+ let a = document.querySelector('.a');
+ let b = document.querySelector('.a > .b');
+ assert_equals(getComputedStyle(a).zIndex, '1');
+ assert_equals(getComputedStyle(b).zIndex, 'auto');
+ assert_equals(getComputedStyle(below).zIndex, 'auto');
+ assert_equals(getComputedStyle(outside).zIndex, 'auto');
+}, 'Relative selectors in <scope-end>');
+</script>
+
+<template id=test_inner_nest>
+ <div>
+ <style>
+ @scope (.a) {
+ & + & {
+ z-index:1;
+ }
+ }
+ </style>
+ <div class=a>
+ <div id=inner1 class=a></div>
+ <div id=inner2 class=a></div>
+ </div>
+ </div>
+</template>
+<script>
+test((t) => {
+ t.add_cleanup(() => main.replaceChildren());
+ main.append(test_inner_nest.content.cloneNode(true));
+
+ assert_equals(getComputedStyle(inner1).zIndex, 'auto');
+ assert_equals(getComputedStyle(inner2).zIndex, '1');
+}, 'Nesting-selector in the scope\'s <stylesheet>');
+</script>
+
+<template id=test_parent_in_pseudo_scope>
+ <div>
+ <style>
+ @scope (#div) {
+ :scope {
+ z-index: 1;
+ & {
+ z-index: 2;
+ }
+ }
+ }
+ </style>
+ <div id=div></div>
+ </div>
+</template>
+<script>
+test((t) => {
+ t.add_cleanup(() => main.replaceChildren());
+ main.append(test_parent_in_pseudo_scope.content.cloneNode(true));
+
+ assert_equals(getComputedStyle(div).zIndex, '2');
+}, 'Nesting-selector within :scope rule');
+</script>
+
+<template id=test_parent_in_pseudo_scope_double>
+ <div>
+ <style>
+ @scope (#div) {
+ :scope {
+ z-index: 1;
+ & {
+ & {
+ z-index: 2;
+ }
+ }
+ }
+ }
+ </style>
+ <div id=div></div>
+ </div>
+</template>
+<script>
+test((t) => {
+ t.add_cleanup(() => main.replaceChildren());
+ main.append(test_parent_in_pseudo_scope_double.content.cloneNode(true));
+
+ assert_equals(getComputedStyle(div).zIndex, '2');
+}, 'Nesting-selector within :scope rule (double nested)');
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-cascade/scope-proximity.html b/testing/web-platform/tests/css/css-cascade/scope-proximity.html
new file mode 100644
index 0000000000..c133a71e9a
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/scope-proximity.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<title>@scope - proximity to root</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-proximity">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+function test_scope(script_element, callback_fn, description) {
+ test((t) => {
+ // The provided <script> element must be an immedate subsequent sibling of
+ // a <template> element.
+ let template_element = script_element.previousElementSibling;
+ assert_equals(template_element.tagName, 'TEMPLATE');
+
+ t.add_cleanup(() => {
+ while (main.firstChild)
+ main.firstChild.remove()
+ });
+
+ main.append(template_element.content.cloneNode(true));
+
+ callback_fn();
+ }, description);
+}
+
+function assert_green(selector) {
+ assert_equals(getComputedStyle(main.querySelector(selector)).backgroundColor, 'rgb(0, 128, 0)');
+}
+function assert_not_green(selector) {
+ assert_equals(getComputedStyle(main.querySelector(selector)).backgroundColor, 'rgb(0, 0, 0)');
+}
+</script>
+<style>
+ main * {
+ background-color: black;
+ }
+</style>
+<main id=main>
+</main>
+
+<template>
+ <style>
+ .item {
+ padding: 0px;
+ border: 5px solid red;
+ }
+
+ @scope (.light) {
+ [id] { border-color: rgb(100, 100, 100); }
+ }
+
+ @scope (.dark) {
+ [id] { border-color: rgb(200, 200, 200); }
+ }
+ </style>
+ <div class=light>
+ <div id=item1>
+ <div class=dark>
+ <div id=item2>
+ <div class=light>
+ <div id=item3>
+ <div class=dark>
+ <div id=item4></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_equals(getComputedStyle(item1).borderColor, 'rgb(100, 100, 100)');
+ assert_equals(getComputedStyle(item2).borderColor, 'rgb(200, 200, 200)');
+ assert_equals(getComputedStyle(item3).borderColor, 'rgb(100, 100, 100)');
+ assert_equals(getComputedStyle(item4).borderColor, 'rgb(200, 200, 200)');
+}, 'Alternating light/dark');
+</script>
+
+
+<template>
+ <style>
+ @scope (.b) {
+ [id] { border-color:green; }
+ }
+ @scope (.a) {
+ [id] { border-color:red; }
+ }
+ </style>
+ <div class=a>
+ <div class=b>
+ <span id=item></span>
+ </div>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_equals(getComputedStyle(item).borderColor, 'rgb(0, 128, 0)');
+}, 'Proximity wins over order of appearance');
+</script>
+
+
+<template>
+ <style>
+ @scope (.a) {
+ span[id] { border-color:green; }
+ }
+ @scope (.b) {
+ [id] { border-color:red; }
+ }
+ </style>
+ <div class=a>
+ <div class=b>
+ <span id=item></span>
+ </div>
+ </div>
+</template>
+<script>
+test_scope(document.currentScript, () => {
+ assert_equals(getComputedStyle(item).borderColor, 'rgb(0, 128, 0)');
+}, 'Specificity wins over proximity');
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/scope-shadow.html b/testing/web-platform/tests/css/css-cascade/scope-shadow.html
new file mode 100644
index 0000000000..100438f534
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/scope-shadow.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<title>@scope - ShadowDOM</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/declarative-shadow-dom-polyfill.js"></script>
+
+<div id=host_plain>
+ <template shadowrootmode=open>
+ <style>
+ @scope (:host) {
+ .a {
+ z-index: 1;
+ }
+ }
+ </style>
+ <div class=a>
+ </div>
+ </template>
+</div>
+<script>
+ setup(() => {
+ polyfill_declarative_shadow_dom(document);
+ });
+
+ test(() => {
+ let a = host_plain.shadowRoot.querySelector('.a');
+ assert_equals(getComputedStyle(a).zIndex, '1');
+ }, '@scope can match :host');
+</script>
+
+<div id=host_functional>
+ <template shadowrootmode=open>
+ <style>
+ @scope (:host(div)) {
+ .a {
+ z-index: 1;
+ }
+ }
+ /* Should not match: */
+ @scope (:host(span)) {
+ .a {
+ z-index: 42;
+ }
+ }
+ </style>
+ <div class=a>
+ </div>
+ </template>
+</div>
+<script>
+ setup(() => {
+ polyfill_declarative_shadow_dom(document);
+ });
+
+ test(() => {
+ let a = host_functional.shadowRoot.querySelector('.a');
+ assert_equals(getComputedStyle(a).zIndex, '1');
+ }, '@scope can match :host(...)');
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/scope-specificity.html b/testing/web-platform/tests/css/css-cascade/scope-specificity.html
new file mode 100644
index 0000000000..b39ce9e7b9
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/scope-specificity.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>@scope - added specificty</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+ @scope (#a) {
+ .b { --x:first; }
+ }
+
+ @scope (.a) {
+ .b { --x:second; }
+ }
+
+ @scope (#a) {
+ @scope (*) {
+ .b { --y:first; }
+ }
+ }
+
+ div.b { --y:second; }
+
+</style>
+<div class=a id=a>
+ <div class=b id=target></div>
+</div>
+<script>
+
+test(() => {
+ assert_equals(getComputedStyle(target).getPropertyValue('--x'), 'first');
+}, '@scope adds to specificity of inner selector');
+
+test(() => {
+ assert_equals(getComputedStyle(target).getPropertyValue('--y'), 'first');
+}, 'Parent @scope adds to specificity of inner selector');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-cascade/support/test-green.css b/testing/web-platform/tests/css/css-cascade/support/test-green.css
new file mode 100644
index 0000000000..da8e1014d2
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/support/test-green.css
@@ -0,0 +1,4 @@
+.test {
+ background: green;
+ color: green;
+}
diff --git a/testing/web-platform/tests/css/css-cascade/support/test-red.css b/testing/web-platform/tests/css/css-cascade/support/test-red.css
new file mode 100644
index 0000000000..bb309fcd57
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/support/test-red.css
@@ -0,0 +1,4 @@
+.test {
+ background: red;
+ color: red;
+}
diff --git a/testing/web-platform/tests/css/css-cascade/unset-val-001.html b/testing/web-platform/tests/css/css-cascade/unset-val-001.html
new file mode 100644
index 0000000000..857cb5d40d
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/unset-val-001.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: the "unset" value</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact">
+ <link rel="help" href="http://www.w3.org/TR/css-cascade-3/#inherit-initial">
+ <link rel="help" href="http://www.w3.org/TR/css-cascade-4/#inherit-initial">
+ <link rel="match" href="reference/ref-filled-green-100px-square.xht">
+ <meta name="assert" content="color:unset is the same as color:inherit since color is an inherited property. background-color:unset is the same as background-color:initial since background-color is a non-inherited property.">
+ <style>
+.square {
+ width: 100px;
+ height: 100px;
+}
+.under {
+ background-color: green;
+ margin-bottom: -100px;
+}
+.outer {
+ color: green;
+ background: red;
+}
+.inner {
+ color: red;
+ background-color: red;
+ font-size: 40px;
+ text-align: center;
+}
+.inner {
+ color: unset;/* inherit from .outer */
+ background-color: unset;/* initial, transparent, .under shows thru */
+}
+ </style>
+</head>
+<body>
+ <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+
+ <div class="outer square">
+ <div class="under square"></div>
+ <div class="inner square">XX</div>
+ </div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/unset-val-002.html b/testing/web-platform/tests/css/css-cascade/unset-val-002.html
new file mode 100644
index 0000000000..61f941a038
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/unset-val-002.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CSS Cascade: the "unset" value</title>
+ <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+ <link rel="help" href="http://www.w3.org/TR/css-cascade-3/#inherit-initial">
+ <link rel="help" href="http://www.w3.org/TR/css-cascade-4/#inherit-initial">
+ <link rel="match" href="reference/ref-filled-green-100px-square.xht">
+ <meta name="assert" content="display:unset should be the same as display:initial since 'display' is not an inherited property. display:unset should be the same as display:inline since 'inline' is the initial value of 'display'.">
+ <style>
+.square {
+ width: 100px;
+ height: 100px;
+}
+.green {
+ background-color: green;
+}
+.top {
+ position: absolute;
+}
+.fail {
+ background-color: red;
+ display: inline-block;
+ display: unset;/* initial, inline */
+}
+ </style>
+</head>
+<body>
+ <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+
+ <div class="top"><span class="square fail"></span></div>
+ <div class="green square"></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/css/css-cascade/unset-value-storage.html b/testing/web-platform/tests/css/css-cascade/unset-value-storage.html
new file mode 100644
index 0000000000..97a27ff67b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-cascade/unset-value-storage.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Storage of "unset" value</title>
+<meta name="author" title="Xidorn Quan" href="https://www.upsuper.org">
+<link rel="help" href="https://www.w3.org/TR/css-cascade-3/#inherit-initial"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ div {
+ color: unset;
+ border: unset;
+ }
+</style>
+<body>
+ <div id="log"></div>
+ <script>
+ test(function() {
+ let properties = ["color", "border", "border-left", "border-color", "border-right-style"];
+ let style = document.styleSheets[0].cssRules[0].style;
+ for (let prop of properties) {
+ assert_equals(style.getPropertyValue(prop), "unset", `${prop} is expected to be "unset"`);
+ }
+ });
+ </script>