summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/css/css-nesting
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/css/css-nesting')
-rw-r--r--testing/web-platform/tests/css/css-nesting/WEB_FEATURES.yml3
-rw-r--r--testing/web-platform/tests/css/css-nesting/conditional-properties-ref.html33
-rw-r--r--testing/web-platform/tests/css/css-nesting/conditional-properties.html34
-rw-r--r--testing/web-platform/tests/css/css-nesting/conditional-rules-ref.html64
-rw-r--r--testing/web-platform/tests/css/css-nesting/conditional-rules.html75
-rw-r--r--testing/web-platform/tests/css/css-nesting/cssom.html188
-rw-r--r--testing/web-platform/tests/css/css-nesting/delete-other-rule-crash.html15
-rw-r--r--testing/web-platform/tests/css/css-nesting/has-nesting-ref.html8
-rw-r--r--testing/web-platform/tests/css/css-nesting/has-nesting.html21
-rw-r--r--testing/web-platform/tests/css/css-nesting/host-nesting-001.html23
-rw-r--r--testing/web-platform/tests/css/css-nesting/host-nesting-002.html22
-rw-r--r--testing/web-platform/tests/css/css-nesting/host-nesting-003.html26
-rw-r--r--testing/web-platform/tests/css/css-nesting/host-nesting-004.html25
-rw-r--r--testing/web-platform/tests/css/css-nesting/host-nesting-005.html24
-rw-r--r--testing/web-platform/tests/css/css-nesting/implicit-nesting-ident-recovery.html29
-rw-r--r--testing/web-platform/tests/css/css-nesting/implicit-nesting-ident.html22
-rw-r--r--testing/web-platform/tests/css/css-nesting/implicit-nesting-ref.html27
-rw-r--r--testing/web-platform/tests/css/css-nesting/implicit-nesting.html82
-rw-r--r--testing/web-platform/tests/css/css-nesting/implicit-parent-insertion-crash.html16
-rw-r--r--testing/web-platform/tests/css/css-nesting/invalid-inner-rules.html56
-rw-r--r--testing/web-platform/tests/css/css-nesting/invalidation-001.html33
-rw-r--r--testing/web-platform/tests/css/css-nesting/invalidation-002.html33
-rw-r--r--testing/web-platform/tests/css/css-nesting/invalidation-003.html34
-rw-r--r--testing/web-platform/tests/css/css-nesting/invalidation-004.html30
-rw-r--r--testing/web-platform/tests/css/css-nesting/nest-containing-forgiving-ref.html19
-rw-r--r--testing/web-platform/tests/css/css-nesting/nest-containing-forgiving.html34
-rw-r--r--testing/web-platform/tests/css/css-nesting/nesting-basic-ref.html30
-rw-r--r--testing/web-platform/tests/css/css-nesting/nesting-basic.html112
-rw-r--r--testing/web-platform/tests/css/css-nesting/nesting-layer.html52
-rw-r--r--testing/web-platform/tests/css/css-nesting/nesting-type-selector.html18
-rw-r--r--testing/web-platform/tests/css/css-nesting/parsing.html86
-rw-r--r--testing/web-platform/tests/css/css-nesting/pseudo-part-crash.html12
-rw-r--r--testing/web-platform/tests/css/css-nesting/serialize-group-rules-with-decls.html91
-rw-r--r--testing/web-platform/tests/css/css-nesting/supports-is-consistent-ref.html5
-rw-r--r--testing/web-platform/tests/css/css-nesting/supports-is-consistent.html17
-rw-r--r--testing/web-platform/tests/css/css-nesting/top-level-is-scope.html30
36 files changed, 1429 insertions, 0 deletions
diff --git a/testing/web-platform/tests/css/css-nesting/WEB_FEATURES.yml b/testing/web-platform/tests/css/css-nesting/WEB_FEATURES.yml
new file mode 100644
index 0000000000..297d31d99d
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: nesting
+ files: "**"
diff --git a/testing/web-platform/tests/css/css-nesting/conditional-properties-ref.html b/testing/web-platform/tests/css/css-nesting/conditional-properties-ref.html
new file mode 100644
index 0000000000..0285acbf33
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/conditional-properties-ref.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Properties in nested conditional rules</title>
+<link rel="author" title="Adam Argyle" href="mailto:argyle@google.com">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<style>
+ .test {
+ background-color: red;
+ width: 100px;
+ height: 100px;
+ display: grid;
+ }
+
+ @media (min-width: 50px) {
+ .test-5 > div {
+ background-color: green;
+ }
+ }
+
+ @supports (display: grid) {
+ .test-10 {
+ background-color: green;
+ }
+ }
+
+ body * + * {
+ margin-top: 8px;
+ }
+</style>
+<body>
+ <p>Tests pass if <strong>block is green</strong></p>
+ <div class="test test-5"><div></div></div>
+ <div class="test test-10"><div></div></div>
+</body>
diff --git a/testing/web-platform/tests/css/css-nesting/conditional-properties.html b/testing/web-platform/tests/css/css-nesting/conditional-properties.html
new file mode 100644
index 0000000000..122b8635c1
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/conditional-properties.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Properties in nested conditional rules</title>
+<link rel="author" title="Adam Argyle" href="mailto:argyle@google.com">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<link rel="match" href="conditional-properties-ref.html">
+<style>
+ .test {
+ background-color: red;
+ width: 100px;
+ height: 100px;
+ display: grid;
+ }
+
+ .test-5 {
+ @media (min-width: 50px) {
+ background-color: green;
+ }
+ }
+
+ .test-10 {
+ @supports (display: grid) {
+ background-color: green;
+ }
+ }
+
+ body * + * {
+ margin-top: 8px;
+ }
+</style>
+<body>
+ <p>Tests pass if <strong>block is green</strong></p>
+ <div class="test test-5"><div></div></div>
+ <div class="test test-10"><div></div></div>
+</body>
diff --git a/testing/web-platform/tests/css/css-nesting/conditional-rules-ref.html b/testing/web-platform/tests/css/css-nesting/conditional-rules-ref.html
new file mode 100644
index 0000000000..c4fabd672a
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/conditional-rules-ref.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<title>Conditional rules with nesting</title>
+<link rel="author" title="Adam Argyle" href="mailto:argyle@google.com">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<style>
+ .test {
+ background-color: red;
+ width: 30px;
+ height: 30px;
+ display: grid;
+ }
+
+ @media (min-width: 10px) {
+ .test-5 > div {
+ background-color: green;
+ }
+ }
+
+ @media (min-width: 10px) {
+ .test-6 > div {
+ background-color: green;
+ }
+ }
+
+ @supports (display: grid) {
+ .test-10 {
+ background-color: green;
+ }
+ }
+
+ @layer {
+ .test-11 {
+ background-color: green !important;
+ }
+ }
+
+ @scope (.test-12) {
+ :scope {
+ background-color: green;
+ }
+ }
+
+ div {
+ container-type: inline-size;
+ }
+ @container (width >= 0px) {
+ .test-13 {
+ background-color: green;
+ }
+ }
+
+ body * + * {
+ margin-top: 8px;
+ }
+</style>
+<body>
+ <p>Tests pass if <strong>block is green</strong></p>
+ <div class="test test-5"><div></div></div>
+ <div class="test test-6"><div></div></div>
+ <div class="test test-10"><div></div></div>
+ <div class="test test-11"><div></div></div>
+ <div class="test"><div class="test-12"></div></div>
+ <div class="test"><div class="test-13"></div></div>
+</body>
diff --git a/testing/web-platform/tests/css/css-nesting/conditional-rules.html b/testing/web-platform/tests/css/css-nesting/conditional-rules.html
new file mode 100644
index 0000000000..0172db645b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/conditional-rules.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<title>Conditional rules with nesting</title>
+<link rel="author" title="Adam Argyle" href="mailto:argyle@google.com">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<link rel="match" href="conditional-rules-ref.html">
+<style>
+ .test {
+ background-color: red;
+ width: 30px;
+ height: 30px;
+ display: grid;
+ }
+
+ .test-5 {
+ @media (min-width: 10px) {
+ & {
+ background-color: green;
+ }
+ }
+ }
+
+ .test-6 {
+ @media (min-width: 10px) {
+ background-color: green;
+ }
+ }
+
+ .test-10 {
+ @supports (display: grid) {
+ & {
+ background-color: green;
+ }
+ }
+ }
+
+ .test-11 {
+ @layer {
+ & {
+ background-color: green !important;
+ }
+ }
+ }
+
+ .test-12 {
+ @scope (&) {
+ :scope {
+ background-color: green;
+ }
+ }
+ }
+
+ div {
+ container-type: inline-size;
+ }
+ .test-13 {
+ @container (width >= 0px) {
+ & {
+ background-color: green;
+ }
+ }
+ }
+
+ body * + * {
+ margin-top: 8px;
+ }
+</style>
+<body>
+ <p>Tests pass if <strong>block is green</strong></p>
+ <div class="test test-5"></div>
+ <div class="test test-6"></div>
+ <div class="test test-10"></div>
+ <div class="test test-11"></div>
+ <div class="test test-12"><div class="test-12"></div></div>
+ <div class="test"><div class="test-13"></div></div>
+</body>
diff --git a/testing/web-platform/tests/css/css-nesting/cssom.html b/testing/web-platform/tests/css/css-nesting/cssom.html
new file mode 100644
index 0000000000..226fb791b5
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/cssom.html
@@ -0,0 +1,188 @@
+<!doctype html>
+<title>Simple CSSOM manipulation of subrules</title>
+<link rel="author" title="Steinar H. Gunderson" href="mailto:sesse@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style id="ss"></style>
+
+<script>
+ test(() => {
+ assert_equals(CSSStyleRule.__proto__, CSSGroupingRule);
+ }, "CSSStyleRule is a CSSGroupingRule");
+
+ test(() => {
+ let [ss] = document.styleSheets;
+ assert_equals(ss.cssRules.length, 0);
+ ss.insertRule('.a { color: red; }');
+ assert_equals(ss.cssRules.length, 1);
+ assert_equals(ss.cssRules[0].cssText, '.a { color: red; }');
+
+ // Test inserting sub-cssRules, at various positions.
+ ss.cssRules[0].insertRule('& .b { color: green; }');
+ ss.cssRules[0].insertRule('& .c { color: blue; }', 1);
+ ss.cssRules[0].insertRule('& .d { color: hotpink; }', 1);
+ assert_equals(ss.cssRules[0].cssText,
+`.a {
+ color: red;
+ & .b { color: green; }
+ & .d { color: hotpink; }
+ & .c { color: blue; }
+}`, 'inserting should work');
+
+ // Test deleting a rule.
+ ss.cssRules[0].deleteRule(1);
+ assert_equals(ss.cssRules[0].cssText,
+`.a {
+ color: red;
+ & .b { color: green; }
+ & .c { color: blue; }
+}`, 'deleting should work');
+ });
+
+ // Test that out-of-bounds throws exceptions and does not affect the stylesheet.
+ const sampleSheetText =
+`.a {
+ color: red;
+ & .b { color: green; }
+ & .c { color: blue; }
+}`;
+
+ test(() => {
+ document.getElementById('ss').innerHTML = sampleSheetText;
+ let [ss] = document.styleSheets;
+ assert_throws_dom('IndexSizeError', () => { ss.cssRules[0].insertRule('& .broken {}', 3); });
+ assert_equals(ss.cssRules[0].cssText, sampleSheetText, 'unchanged after no-insert');
+ });
+
+ test(() => {
+ document.getElementById('ss').innerHTML = sampleSheetText;
+ let [ss] = document.styleSheets;
+ assert_throws_dom('IndexSizeError', () => { ss.cssRules[0].insertRule('& .broken {}', -1); });
+ assert_equals(ss.cssRules[0].cssText, sampleSheetText, 'unchanged after no-insert');
+ });
+
+ test(() => {
+ document.getElementById('ss').innerHTML = sampleSheetText;
+ let [ss] = document.styleSheets;
+ assert_throws_dom('IndexSizeError', () => { ss.cssRules[0].deleteRule(5); });
+ assert_equals(ss.cssRules[0].cssText, sampleSheetText, 'unchanged after no-delete');
+ });
+
+ test(() => {
+ document.getElementById('ss').innerHTML = sampleSheetText;
+ let [ss] = document.styleSheets;
+ assert_equals(ss.cssRules[0].cssRules[2], undefined, 'subscript out-of-bounds returns undefined');
+ assert_equals(ss.cssRules[0].cssRules.item(2), null, 'item() out-of-bounds returns null');
+ assert_equals(ss.cssRules[0].cssText, sampleSheetText, 'unchanged after no-access');
+ });
+
+ // Test that inserting an invalid rule throws an exception.
+ test(() => {
+ document.getElementById('ss').innerHTML = sampleSheetText;
+ let [ss] = document.styleSheets;
+ let exception;
+ assert_throws_dom('SyntaxError', () => { ss.cssRules[0].insertRule('% {}'); });
+ assert_equals(ss.cssRules[0].cssText, sampleSheetText, 'unchanged after invalid rule');
+ });
+
+ // Test that we can get out single rule through .cssRules.
+ test(() => {
+ document.getElementById('ss').innerHTML = sampleSheetText;
+ let [ss] = document.styleSheets;
+ assert_equals(ss.cssRules[0].cssRules[1].cssText, '& .c { color: blue; }');
+ });
+
+ // Test that we can insert a @supports rule, that it serializes in the right place
+ // and has the right parent. Note that the indentation is broken per-spec.
+ test(() => {
+ document.getElementById('ss').innerHTML = sampleSheetText;
+ let [ss] = document.styleSheets;
+ ss.cssRules[0].insertRule('@supports selector(&) { & div { font-size: 10px; }}', 1);
+ assert_equals(ss.cssRules[0].cssText,
+`.a {
+ color: red;
+ & .b { color: green; }
+ @supports selector(&) {
+ & div { font-size: 10px; }
+}
+ & .c { color: blue; }
+}`, '@supports is added');
+
+ assert_equals(ss.cssRules[0].cssRules[1].parentRule, ss.cssRules[0]);
+ ss.cssRules[0].deleteRule(1);
+ assert_equals(ss.cssRules[0].cssText, sampleSheetText);
+ });
+
+ // Nested rules are not part of declaration lists, and thus should not
+ // be possible to insert with .style.
+ test(() => {
+ document.getElementById('ss').innerHTML = sampleSheetText;
+ let [ss] = document.styleSheets;
+ ss.cssRules[0].style = 'color: olivedrab; &.d { color: peru; }';
+ assert_equals(ss.cssRules[0].cssText,
+`.a {
+ color: olivedrab;
+ & .b { color: green; }
+ & .c { color: blue; }
+}`, 'color is changed, new rule is ignored');
+ });
+
+ test(() => {
+ document.getElementById('ss').innerHTML = sampleSheetText;
+ let [ss] = document.styleSheets;
+ ss.cssRules[0].cssRules[0].selectorText = 'div.b .c &'; // Allowed
+ ss.cssRules[0].cssRules[1].selectorText = '.c div.b &, div &'; // Allowed.
+ ss.cssRules[0].insertRule('div & {}'); // Allowed.
+ assert_equals(ss.cssRules[0].cssText,
+`.a {
+ color: red;
+ div & { }
+ div.b .c & { color: green; }
+ .c div.b &, div & { color: blue; }
+}`, 'selectorText and insertRule');
+ });
+
+ // Rules that are dropped in forgiving parsing but that contain &,
+ // must still be serialized out as they were.
+ test(() => {
+ const text = '.a { :is(!& .foo, .b) { color: green; } }';
+ document.getElementById('ss').innerHTML = text;
+ let [ss] = document.styleSheets;
+ assert_equals(ss.cssRules[0].cssText,
+`.a {
+ :is(!& .foo, .b) { color: green; }
+}`, 'invalid rule containing ampersand is kept in serialization');
+ });
+
+ test((t) => {
+ let main = document.createElement('main');
+ main.innerHTML = `
+ <style>
+ .a {
+ & { z-index:1; }
+ & #inner1 { z-index:1; }
+ .stuff, :is(&) #inner2 { z-index:1; }
+ }
+ </style>
+ <div id="outer" class="b">
+ <div id="inner1"></div>
+ <div id="inner2"></div>
+ </div>
+ `;
+ document.documentElement.append(main);
+ t.add_cleanup(() => main.remove());
+
+ assert_equals(getComputedStyle(outer).zIndex, 'auto');
+ assert_equals(getComputedStyle(inner1).zIndex, 'auto');
+ assert_equals(getComputedStyle(inner2).zIndex, 'auto');
+
+ // .a => .b
+ main.firstElementChild.sheet.cssRules[0].selectorText = '.b';
+
+ assert_equals(getComputedStyle(outer).zIndex, '1');
+ assert_equals(getComputedStyle(inner1).zIndex, '1');
+ assert_equals(getComputedStyle(inner2).zIndex, '1');
+ }, 'Mutating the selectorText of outer rule invalidates inner rules');
+</script>
diff --git a/testing/web-platform/tests/css/css-nesting/delete-other-rule-crash.html b/testing/web-platform/tests/css/css-nesting/delete-other-rule-crash.html
new file mode 100644
index 0000000000..bde7c554e8
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/delete-other-rule-crash.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<body>
+<title>Crash with lazy parsing child rules and stylesheet copy-on-write</title>
+<link rel="help" href="https://crbug.com/1404879">
+<link href="../support/delete-other-rule-crash.css" rel="stylesheet">
+<script src="/common/gc.js"></script>
+<script>
+addEventListener('DOMContentLoaded', async () => {
+ requestAnimationFrame(async () => {
+ document.styleSheets[0].deleteRule(0);
+ await garbageCollect();
+ document.styleSheets[0].cssRules[0].cssText;
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/css/css-nesting/has-nesting-ref.html b/testing/web-platform/tests/css/css-nesting/has-nesting-ref.html
new file mode 100644
index 0000000000..5ab79b68f1
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/has-nesting-ref.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<title>Nested has shouldn't match</title>
+<style>
+ul { background: green }
+</style>
+<ul>
+ <li>Bar</li>
+</ul>
diff --git a/testing/web-platform/tests/css/css-nesting/has-nesting.html b/testing/web-platform/tests/css/css-nesting/has-nesting.html
new file mode 100644
index 0000000000..83fe720236
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/has-nesting.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>Nested has shouldn't match</title>
+<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1864647">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/9600">
+<link rel="match" href="has-nesting-ref.html">
+<style>
+ul { background: green }
+
+li:has(strong) {
+ display: none;
+
+ :has(> &) {
+ background: red;
+ }
+}
+</style>
+<ul>
+ <li><strong>Foo</strong></li>
+ <li>Bar</li>
+</ul>
diff --git a/testing/web-platform/tests/css/css-nesting/host-nesting-001.html b/testing/web-platform/tests/css/css-nesting/host-nesting-001.html
new file mode 100644
index 0000000000..a166efeac5
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/host-nesting-001.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>:host and nesting (basic) </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-nesting/#nest-selector">
+<link rel="match" href="/css/reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<div id="host"></div>
+<script>
+ host.attachShadow({mode: "open"}).innerHTML = `
+ <style>
+ :host {
+ .nested {
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ }
+ }
+ </style>
+ <div class="nested"></div>
+ `;
+</script>
diff --git a/testing/web-platform/tests/css/css-nesting/host-nesting-002.html b/testing/web-platform/tests/css/css-nesting/host-nesting-002.html
new file mode 100644
index 0000000000..8f087c4128
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/host-nesting-002.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>:host and nesting (bare declarations)</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-nesting/#nest-selector">
+<link rel="match" href="/css/reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<div id="host"></div>
+<script>
+ host.attachShadow({mode: "open"}).innerHTML = `
+ <style>
+ :host {
+ @media (width >= 0) {
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ }
+ }
+ </style>
+ `;
+</script>
diff --git a/testing/web-platform/tests/css/css-nesting/host-nesting-003.html b/testing/web-platform/tests/css/css-nesting/host-nesting-003.html
new file mode 100644
index 0000000000..f9830b4fb6
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/host-nesting-003.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>:host and nesting (combined with something else)</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-nesting/#nest-selector">
+<link rel="match" href="/css/reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<div id="host"></div>
+<script>
+ host.attachShadow({mode: "open"}).innerHTML = `
+ <style>
+ .nested {
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ }
+ :host(#not-host), #host {
+ .nested {
+ background-color: red;
+ }
+ }
+ </style>
+ <div class="nested"></div>
+ `;
+</script>
diff --git a/testing/web-platform/tests/css/css-nesting/host-nesting-004.html b/testing/web-platform/tests/css/css-nesting/host-nesting-004.html
new file mode 100644
index 0000000000..8b84720075
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/host-nesting-004.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>:host and nesting (combined with something else, bare declarations)</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-nesting/#nest-selector">
+<link rel="match" href="/css/reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<div id="host"></div>
+<script>
+ host.attachShadow({mode: "open"}).innerHTML = `
+ <style>
+ :host {
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ }
+ :host(#not-host), #host {
+ @media (width >= 0) {
+ background-color: red;
+ }
+ }
+ </style>
+ `;
+</script>
diff --git a/testing/web-platform/tests/css/css-nesting/host-nesting-005.html b/testing/web-platform/tests/css/css-nesting/host-nesting-005.html
new file mode 100644
index 0000000000..ca5449825a
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/host-nesting-005.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>:host and nesting (with pseudos)</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-nesting/#nest-selector">
+<link rel="match" href="/css/reference/ref-filled-green-100px-square-only.html">
+<p>Test passes if there is a filled green square.</p>
+<div id="host"></div>
+<script>
+ host.attachShadow({mode: "open"}).innerHTML = `
+ <style>
+ :host {
+ &::before {
+ display: block;
+ content: "";
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ }
+ }
+ </style>
+ `;
+</script>
diff --git a/testing/web-platform/tests/css/css-nesting/implicit-nesting-ident-recovery.html b/testing/web-platform/tests/css/css-nesting/implicit-nesting-ident-recovery.html
new file mode 100644
index 0000000000..f3648324ce
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/implicit-nesting-ident-recovery.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>CSS Nesting: Nesting, error recovery</title>
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ #target1 {
+ display:block;
+ display:new-block;
+ color:green;
+ }
+ #target2 {
+ display:block;
+ display:hover {};
+ color:green;
+ }
+</style>
+<div id=target1>Green</div>
+<div id=target2>Green</div>
+<script>
+ test(() => {
+ assert_equals(getComputedStyle(target1).color, 'rgb(0, 128, 0)');
+ }, 'Unknown declaration does not consume subsequent declaration');
+</script>
+<script>
+ test(() => {
+ assert_equals(getComputedStyle(target2).color, 'rgb(0, 128, 0)');
+ }, 'Unknown declaration with blocks does not consume subsequent declaration');
+</script>
diff --git a/testing/web-platform/tests/css/css-nesting/implicit-nesting-ident.html b/testing/web-platform/tests/css/css-nesting/implicit-nesting-ident.html
new file mode 100644
index 0000000000..d6d06b9b31
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/implicit-nesting-ident.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<title>CSS Nesting: Implicit nesting (ident)</title>
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ #main {
+ div {
+ color: green;
+ }
+ }
+</style>
+<div id=main>
+ <div id=target>
+ Green
+ </div>
+</main>
+<script>
+ test(() => {
+ assert_equals(getComputedStyle(target).color, 'rgb(0, 128, 0)');
+ }, 'Nested rule starting with tag');
+</script>
diff --git a/testing/web-platform/tests/css/css-nesting/implicit-nesting-ref.html b/testing/web-platform/tests/css/css-nesting/implicit-nesting-ref.html
new file mode 100644
index 0000000000..0057a67fd0
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/implicit-nesting-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Implicit nesting</title>
+<link rel="author" title="Steinar H. Gunderson" href="mailto:sesse@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<style>
+ .test {
+ background-color: green;
+ width: 30px;
+ height: 30px;
+ display: grid;
+ }
+
+ body * + * {
+ margin-top: 8px;
+ }
+</style>
+<body>
+ <p>Tests pass if <strong>block is green</strong></p>
+ <div class="test"></div>
+ <div class="test"></div>
+ <div class="test"></div>
+ <div class="test"></div>
+ <div class="test"></div>
+ <div class="test"></div>
+ <div class="test"></div>
+ <div class="test"></div>
+</body>
diff --git a/testing/web-platform/tests/css/css-nesting/implicit-nesting.html b/testing/web-platform/tests/css/css-nesting/implicit-nesting.html
new file mode 100644
index 0000000000..0a76dedc5b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/implicit-nesting.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<title>Implicit nesting</title>
+<link rel="author" title="Steinar H. Gunderson" href="mailto:sesse@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<link rel="match" href="implicit-nesting-ref.html">
+<style>
+ .test {
+ background-color: red;
+ width: 30px;
+ height: 30px;
+ display: grid;
+ }
+
+ .test-1 {
+ > div {
+ background-color: green;
+ }
+ }
+
+ .test-2 {
+ .test-2-child {
+ background-color: green;
+ }
+ }
+ .test-2-child {
+ background-color: red;
+ }
+
+ .test-3-child {
+ background-color: red;
+ }
+ .test-3-child {
+ .test-3 & {
+ background-color: green;
+ }
+ }
+
+ .test-4 {
+ :is(&) {
+ background-color: green;
+ }
+ }
+
+ .test-5 {
+ :is(.test-5, &.does-not-exist) {
+ background-color: green;
+ }
+ }
+
+ .test-6 {
+ > .foo,.test-6-child,+ .bar {
+ background-color: green;
+ }
+ }
+
+ .test-7 {
+ > .foo, .bar, + .test-7-sibling {
+ background-color: green;
+ }
+ }
+
+ .test-8 {
+ > .foo, .test-8-child, + .bar {
+ background-color: green;
+ }
+ }
+
+ body * + * {
+ margin-top: 8px;
+ }
+</style>
+<body>
+ <p>Tests pass if <strong>block is green</strong></p>
+ <div class="test test-1"><div></div></div>
+ <div class="test test-2"><div class="test-2-child"></div></div>
+ <div class="test test-3"><div class="test-3-child"></div></div>
+ <div class="test test-4"></div>
+ <div class="test test-5"><div class="test-5"></div></div>
+ <div class="test test-6"><div class="test-6-child"></div></div>
+ <div class="test test-7" style="display:none"></div><div class="test test-7-sibling"></div>
+ <div class="test test-8"><div class="test-8-child"></div></div>
+</body>
diff --git a/testing/web-platform/tests/css/css-nesting/implicit-parent-insertion-crash.html b/testing/web-platform/tests/css/css-nesting/implicit-parent-insertion-crash.html
new file mode 100644
index 0000000000..4be1e1c8f9
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/implicit-parent-insertion-crash.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<body>
+<title>Use-after-free when inserting implicit parent selector</title>
+<link rel="help" href="https://crbug.com/1380313">
+<style>
+:root {
+ :lang(en), :lang(en) {
+ }
+}
+</style>
+<div lang="en"></div>
+<script>
+ // Allocate a large chunk of memory, to trigger a GC.
+ new Int32Array(536870911);
+</script>
+
diff --git a/testing/web-platform/tests/css/css-nesting/invalid-inner-rules.html b/testing/web-platform/tests/css/css-nesting/invalid-inner-rules.html
new file mode 100644
index 0000000000..87c7c9a934
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/invalid-inner-rules.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<title>Simple CSSOM manipulation of subrules</title>
+<link rel="author" title="Steinar H. Gunderson" href="mailto:sesse@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style id="ss">
+div {
+ /* This is not a conditional rule, and thus cannot be in nesting context. */
+ @font-face {
+ &.a { font-size: 10px; }
+ }
+
+ @media screen {
+ &.a { color: red; }
+
+ /* Same. */
+ @font-face {
+ &.a { font-size: 10px; }
+ }
+ }
+}
+</style>
+
+<script>
+ test(() => {
+ let [ss] = document.styleSheets;
+ assert_equals(ss.cssRules.length, 1);
+
+ // The @font-face rule should be ignored.
+ assert_equals(ss.cssRules[0].cssText,
+`div {
+ @media screen {
+ &.a { color: red; }
+}
+}`);
+ });
+
+ test(() => {
+ let [ss] = document.styleSheets;
+ assert_equals(ss.cssRules.length, 1);
+ assert_throws_dom('HierarchyRequestError',
+ () => { ss.cssRules[0].cssRules[0].insertRule('@font-face {}', 0); });
+ assert_throws_dom('HierarchyRequestError',
+ () => { ss.cssRules[0].insertRule('@font-face {}', 0); });
+
+ // The @font-face rules should be ignored (again).
+ assert_equals(ss.cssRules[0].cssText,
+`div {
+ @media screen {
+ &.a { color: red; }
+}
+}`);
+ });
+</script>
diff --git a/testing/web-platform/tests/css/css-nesting/invalidation-001.html b/testing/web-platform/tests/css/css-nesting/invalidation-001.html
new file mode 100644
index 0000000000..a9a4284cc3
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/invalidation-001.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<title>CSS Selectors nested invalidation on changed parent</title>
+<link rel="author" title="Steinar H. Gunderson" href="mailto:sesse@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ .b {
+ color: red;
+ }
+ .a {
+ & .b {
+ color: green;
+ }
+ }
+</style>
+
+<div id="container">
+ <div id="child" class="b">
+ Test passes if color is green.
+ </div>
+</div>
+
+<script>
+ test(() => {
+ let container = document.getElementById('container');
+ let child = document.getElementById('child');
+ assert_equals(getComputedStyle(child).color, 'rgb(255, 0, 0)');
+ container.classList.add('a');
+ assert_equals(getComputedStyle(child).color, 'rgb(0, 128, 0)');
+ });
+</script>
diff --git a/testing/web-platform/tests/css/css-nesting/invalidation-002.html b/testing/web-platform/tests/css/css-nesting/invalidation-002.html
new file mode 100644
index 0000000000..8419c4526e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/invalidation-002.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<title>CSS Selectors nested invalidation on changed child</title>
+<link rel="author" title="Steinar H. Gunderson" href="mailto:sesse@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ .a {
+ color: green;
+ }
+ .a {
+ & .b {
+ color: red;
+ }
+ }
+</style>
+
+<div id="container" class="a">
+ <div id="child" class="b">
+ Test passes if color is green.
+ </div>
+</div>
+
+<script>
+ test(() => {
+ let container = document.getElementById('container');
+ let child = document.getElementById('child');
+ assert_equals(getComputedStyle(child).color, 'rgb(255, 0, 0)');
+ child.classList.remove('b');
+ assert_equals(getComputedStyle(child).color, 'rgb(0, 128, 0)');
+ });
+</script>
diff --git a/testing/web-platform/tests/css/css-nesting/invalidation-003.html b/testing/web-platform/tests/css/css-nesting/invalidation-003.html
new file mode 100644
index 0000000000..d1d6d4b9ae
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/invalidation-003.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<title>CSS Selectors nested invalidation with :has()</title>
+<link rel="author" title="Steinar H. Gunderson" href="mailto:sesse@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ .a {
+ color: red;
+ :has(&) {
+ color: green;
+ }
+ }
+</style>
+
+<div id="container">
+ Test passes if color is green.
+ <div>
+ <div id="child"></div>
+ </div>
+</div>
+
+<script>
+ test(() => {
+ let container = document.getElementById('container');
+ let child = document.getElementById('child');
+ assert_equals(getComputedStyle(container).color, 'rgb(0, 0, 0)');
+ assert_equals(getComputedStyle(child).color, 'rgb(0, 0, 0)');
+ child.classList.add('a');
+ assert_equals(getComputedStyle(container).color, 'rgb(0, 128, 0)');
+ assert_equals(getComputedStyle(child).color, 'rgb(255, 0, 0)');
+ });
+</script>
diff --git a/testing/web-platform/tests/css/css-nesting/invalidation-004.html b/testing/web-platform/tests/css/css-nesting/invalidation-004.html
new file mode 100644
index 0000000000..a66c47cf16
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/invalidation-004.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<title>CSS Selectors nested invalidation through @media by selectorText</title>
+<link rel="author" title="Steinar H. Gunderson" href="mailto:sesse@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ .b {
+ color: red;
+ }
+ & {
+ @media screen {
+ &.b { color: green; }
+ }
+ }
+</style>
+
+<div id="elem" class="a b">
+ Test passes if color is green.
+</div>
+
+<script>
+ test(() => {
+ let elem = document.getElementById('elem');
+ assert_equals(getComputedStyle(elem).color, 'rgb(255, 0, 0)');
+ document.styleSheets[0].rules[1].selectorText = '.a';
+ assert_equals(getComputedStyle(elem).color, 'rgb(0, 128, 0)');
+ });
+</script>
diff --git a/testing/web-platform/tests/css/css-nesting/nest-containing-forgiving-ref.html b/testing/web-platform/tests/css/css-nesting/nest-containing-forgiving-ref.html
new file mode 100644
index 0000000000..8fb7c8674a
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/nest-containing-forgiving-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>Nest-containing in forgiving parsing</title>
+<style>
+ .test {
+ background-color: green;
+ width: 100px;
+ height: 100px;
+ display: grid;
+ }
+
+ body * + * {
+ margin-top: 8px;
+ }
+</style>
+<body>
+ <p>Tests pass if <strong>block is green</strong></p>
+ <div class="test"></div>
+ <div class="test"></div>
+</body>
diff --git a/testing/web-platform/tests/css/css-nesting/nest-containing-forgiving.html b/testing/web-platform/tests/css/css-nesting/nest-containing-forgiving.html
new file mode 100644
index 0000000000..561b5a3af5
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/nest-containing-forgiving.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Nest-containing in forgiving parsing</title>
+<link rel="author" title="Steinar H. Gunderson" href="mailto:sesse@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<link rel="match" href="nest-containing-forgiving-ref.html">
+<style>
+ .test {
+ background-color: red;
+ width: 100px;
+ height: 100px;
+ display: grid;
+ }
+
+ .does-not-exist {
+ :is(.test-1, !&) {
+ background-color: green;
+ }
+ }
+
+ .does-not-exist {
+ :is(.test-2, :unknown(div,&)) {
+ background-color: green;
+ }
+ }
+
+ body * + * {
+ margin-top: 8px;
+ }
+</style>
+<body>
+ <p>Tests pass if <strong>block is green</strong></p>
+ <div class="test test-1"></div>
+ <div class="test test-2"></div>
+</body>
diff --git a/testing/web-platform/tests/css/css-nesting/nesting-basic-ref.html b/testing/web-platform/tests/css/css-nesting/nesting-basic-ref.html
new file mode 100644
index 0000000000..8038f369cd
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/nesting-basic-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>Basic nesting</title>
+<link rel="author" title="Adam Argyle" href="mailto:argyle@google.com">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<style>
+ .test {
+ background-color: green;
+ width: 30px;
+ height: 30px;
+ display: grid;
+ }
+
+ body * + * {
+ margin-top: 8px;
+ }
+</style>
+<body>
+ <p>Tests pass if <strong>block is green</strong></p>
+ <div class="test"></div>
+ <div class="test"></div>
+ <div class="test"></div>
+ <div class="test"></div>
+ <div class="test"></div>
+ <div class="test"></div>
+ <div class="test"></div>
+ <div class="test"></div>
+ <div class="test"></div>
+ <div class="test"></div>
+ <div class="test"></div>
+</body>
diff --git a/testing/web-platform/tests/css/css-nesting/nesting-basic.html b/testing/web-platform/tests/css/css-nesting/nesting-basic.html
new file mode 100644
index 0000000000..19ff48e4a2
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/nesting-basic.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<title>Basic nesting</title>
+<link rel="author" title="Adam Argyle" href="mailto:argyle@google.com">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<link rel="match" href="nesting-basic-ref.html">
+<style>
+ .test {
+ background-color: red;
+ width: 30px;
+ height: 30px;
+ display: grid;
+ }
+
+ .test-1 {
+ & > div {
+ background-color: green;
+ }
+ }
+
+ .test-2 {
+ & > div {
+ background-color: green;
+ }
+ }
+
+ .test-3 {
+ & .test-3-child {
+ background-color: green;
+ }
+ }
+
+ span > b {
+ .test-4 section & {
+ display: inline-block;
+ background-color: green;
+ width: 100%;
+ height: 100%;
+ }
+
+ .test-4 section > & {
+ background-color: red;
+ }
+ }
+
+ .test-6 {
+ &.test {
+ background-color: green;
+ }
+ }
+
+ .test-7, .t7- {
+ & + .test-7-child, &.t7-- {
+ background-color: green;
+ }
+ }
+
+ .test-8 {
+ & {
+ background-color: green;
+ }
+ }
+
+ .test-9 {
+ &:is(.t9-, &.t9--) {
+ background-color: green;
+ }
+ }
+
+ .test-10 {
+ & {
+ background-color: green;
+ }
+ background-color: red;
+ }
+
+ .test-11 {
+ & {
+ background-color: red;
+ }
+ background-color: green !important;
+ }
+
+ /* & at top level counts as :scope, i.e. the root element here */
+ & .test-12 {
+ background-color: green;
+ }
+ & > .test-12 {
+ background-color: red !important;
+ }
+
+ body * + * {
+ margin-top: 8px;
+ }
+</style>
+<body>
+ <p>Tests pass if <strong>block is green</strong></p>
+ <div class="test test-1"><div></div></div>
+ <div class="test test-2"><div></div></div>
+ <div class="test test-3"><div class="test-3-child"></div></div>
+ <div class="test test-4">
+ <section>
+ <span><b></b></span>
+ </section>
+ </div>
+ <div class="test test-6"><div></div></div>
+ <div class="test t7- t7--"><div class="test-7-child"></div></div>
+ <div class="test test-8"><div></div></div>
+ <div class="test test-9 t9-- t9-"><div></div></div>
+ <div class="test test-10"><div></div></div>
+ <div class="test test-11"><div></div></div>
+ <div class="test test-12"></div>
+</body>
diff --git a/testing/web-platform/tests/css/css-nesting/nesting-layer.html b/testing/web-platform/tests/css/css-nesting/nesting-layer.html
new file mode 100644
index 0000000000..91db883059
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/nesting-layer.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<title>Nested @layers</title>
+<link rel="help" href="https://drafts.csswg.org/css-nesting/#nested-group-rules">
+<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+ .a {
+ /* This should have no effect. Only at-rules containing style rules
+ are vaild when nested. */
+ @layer theme, base;
+ }
+
+ /* The theme layer wins over the base layer. */
+ @layer base, theme;
+
+ .a {
+ @layer theme {
+ & {
+ z-index: 1;
+ }
+
+ .b {
+ background-color: green;
+ }
+ }
+ }
+
+ @layer base {
+ .a {
+ z-index: 0;
+ }
+ .a .b {
+ background-color: red;
+ }
+ }
+</style>
+<main>
+ <div class="a">
+ <div class="b">
+ </div>
+ </div>
+</main>
+<script>
+ test(() => {
+ let a = document.querySelector("main > .a");
+ let b = document.querySelector("main > .a > .b");
+ assert_equals(getComputedStyle(a).zIndex, "1");
+ assert_equals(getComputedStyle(b).backgroundColor, "rgb(0, 128, 0)");
+ }, '@layer can be nested');
+</script>
diff --git a/testing/web-platform/tests/css/css-nesting/nesting-type-selector.html b/testing/web-platform/tests/css/css-nesting/nesting-type-selector.html
new file mode 100644
index 0000000000..1805896b8d
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/nesting-type-selector.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Nesting works with bare type selectors</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-nesting-1/">
+<link rel="match" href="/css/reference/ref-filled-green-100px-square-only.html">
+<style>
+:root {
+ div {
+ width: 100px;
+ height: 100px;
+ background: green;
+ }
+}
+</style>
+<p>Test passes if there is a filled green square.</p>
+<div></div>
diff --git a/testing/web-platform/tests/css/css-nesting/parsing.html b/testing/web-platform/tests/css/css-nesting/parsing.html
new file mode 100644
index 0000000000..f29eff9730
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/parsing.html
@@ -0,0 +1,86 @@
+<!doctype html>
+<title>CSS Selectors parsing</title>
+<link rel="author" title="Adam Argyle" href="mailto:argyle@google.com">
+<link rel="author" title="Tab Atkins-Bittner" href="https://tabatkins.com/contact/">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style id="test-sheet"></style>
+<script>
+ let [ss] = document.styleSheets
+
+ function resetStylesheet() {
+ while (ss.rules.length)
+ ss.removeRule(0)
+ }
+
+ function testNestedSelector(sel, {expected=sel, parent=".foo"}={}) {
+ resetStylesheet();
+ const ruleText = `${parent} { ${sel} { color: green; }}`
+ test(()=>{
+ ss.insertRule(ruleText);
+ assert_equals(ss.rules.length, 1, "Outer rule should exist.");
+ const rule = ss.rules[0];
+ assert_equals(rule.cssRules.length, 1, "Inner rule should exist.");
+ const innerRule = rule.cssRules[0];
+ assert_equals(innerRule.selectorText, expected, `Inner rule's selector should be "${expected}".`);
+ }, ruleText);
+ }
+
+ function testInvalidNestingSelector(sel, {parent=".foo"}={}) {
+ resetStylesheet();
+ const ruleText = `${parent} { ${sel} { color: green; }}`
+ test(()=>{
+ ss.insertRule(ruleText);
+ assert_equals(ss.rules.length, 1, "Outer rule should exist.");
+ const rule = ss.rules[0];
+ assert_equals(rule.cssRules.length, 0, "Inner rule should not exist.");
+ }, "INVALID: " + ruleText);
+ }
+
+ // basic usage
+ testNestedSelector("&");
+ testNestedSelector("&.bar");
+ testNestedSelector("& .bar");
+ testNestedSelector("& > .bar");
+
+ // relative selector
+ testNestedSelector("> .bar", {expected:"& > .bar"});
+ testNestedSelector("> & .bar", {expected:"& > & .bar"});
+ testNestedSelector("+ .bar &", {expected:"& + .bar &"});
+ testNestedSelector("+ .bar, .foo, > .baz", {expected:"& + .bar, & .foo, & > .baz"});
+
+ // implicit relative (and not)
+ testNestedSelector(".foo", {expected:"& .foo"});
+ testNestedSelector(".test > & .bar");
+ testNestedSelector(".foo, .foo &", {expected:"& .foo, .foo &"});
+ testNestedSelector(":is(.bar, .baz)", {expected:"& :is(.bar, .baz)"});
+ testNestedSelector("&:is(.bar, .baz)");
+ testNestedSelector(":is(.bar, &.baz)");
+ testNestedSelector("&:is(.bar, &.baz)");
+
+ // Mixing nesting selector with other simple selectors
+ testNestedSelector("div&");
+ testInvalidNestingSelector("&div"); // type selector must be first
+ testNestedSelector(".class&");
+ testNestedSelector("&.class");
+ testNestedSelector("[attr]&");
+ testNestedSelector("&[attr]");
+ testNestedSelector("#id&");
+ testNestedSelector("&#id");
+ testNestedSelector(":hover&");
+ testNestedSelector("&:hover");
+ testNestedSelector(":is(div)&");
+ testNestedSelector("&:is(div)");
+
+ // Multiple nesting selectors
+ testNestedSelector("& .bar & .baz & .qux");
+ testNestedSelector("&&");
+
+ // Selector list in inner rule
+ testNestedSelector("& > section, & > article");
+
+ // Selector list in both inner and outer rule.
+ testNestedSelector("& + .baz, &.qux", {parent:".foo, .bar"});
+</script>
diff --git a/testing/web-platform/tests/css/css-nesting/pseudo-part-crash.html b/testing/web-platform/tests/css/css-nesting/pseudo-part-crash.html
new file mode 100644
index 0000000000..3ab521d71c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/pseudo-part-crash.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>Nesting pseudo element selectors should not crash</title>
+<link rel="help" href="https://crbug.com/1376227">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7912">
+<style>
+ div::part(x) {
+ & {
+ color: red;
+ }
+ }
+</style>
diff --git a/testing/web-platform/tests/css/css-nesting/serialize-group-rules-with-decls.html b/testing/web-platform/tests/css/css-nesting/serialize-group-rules-with-decls.html
new file mode 100644
index 0000000000..7e9aaafbeb
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/serialize-group-rules-with-decls.html
@@ -0,0 +1,91 @@
+<!doctype html>
+<title>Serialization of declarations in group rules</title>
+<link rel="author" title="Steinar H. Gunderson" href="mailto:sesse@chromium.org">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/7850">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style id="test-sheet"></style>
+<script>
+ function serialize(cssText) {
+ let [ss] = document.styleSheets;
+ while (ss.rules.length) {
+ ss.removeRule(0)
+ }
+ ss.insertRule(cssText);
+ return ss.rules[0].cssText;
+ }
+
+ function assert_unchanged(cssText, description) {
+ test(() => {
+ assert_equals(serialize(cssText), cssText, description);
+ }, description);
+ }
+
+ function assert_becomes(cssText, serializedCssText, description) {
+ test(() => {
+ assert_equals(serialize(cssText), serializedCssText, description);
+ }, description);
+ }
+
+ assert_unchanged(
+ "@media screen {\n div { color: red; background-color: green; }\n}",
+ "Declarations are serialized on one line, rules on two."
+ )
+
+ assert_becomes(
+ "div { @media screen { color: red; background-color: green; } }",
+ "div {\n @media screen {\n & { color: red; background-color: green; }\n}\n}",
+ "Mixed declarations/rules are on two lines."
+ );
+ assert_becomes(
+ "div {\n @supports selector(&) { color: red; background-color: green; } &:hover { color: navy; } }",
+ "div {\n @supports selector(&) {\n & { color: red; background-color: green; }\n}\n &:hover { color: navy; }\n}",
+ "Implicit rule is serialized",
+ );
+
+ assert_unchanged("div {\n @media screen {\n & { color: red; }\n}\n}", "Implicit rule not removed");
+ assert_becomes(
+ "div { @media screen { & { color: red; &:hover { } } }",
+ "div {\n @media screen {\n & {\n color: red;\n &:hover { }\n}\n}\n}",
+ "Implicit + empty hover rule"
+ );
+ assert_becomes(
+ "div { @media screen { &.cls { color: red; } & { color: red; }",
+ "div {\n @media screen {\n &.cls { color: red; }\n & { color: red; }\n}\n}",
+ "Implicit like rule not in first position"
+ );
+ assert_becomes(
+ "div { @media screen { & { color: red; } & { color: red; }",
+ "div {\n @media screen {\n & { color: red; }\n & { color: red; }\n}\n}",
+ "Two implicit-like rules"
+ );
+ assert_becomes(
+ "div { @media screen { color: red; & { color: red; }",
+ "div {\n @media screen {\n & { color: red; }\n & { color: red; }\n}\n}",
+ "Implicit like rule after decls"
+ );
+ assert_becomes(
+ "div { @media screen { color: red; & { color: blue; }",
+ "div {\n @media screen {\n & { color: red; }\n & { color: blue; }\n}\n}",
+ "Implicit like rule after decls, missing closing braces"
+ );
+ assert_becomes(
+ "div { @media screen { &, p > & { color: blue; }",
+ "div {\n @media screen {\n &, p > & { color: blue; }\n}\n}",
+ "Implicit like rule with other selectors"
+ );
+
+ assert_becomes(
+ "div { & { color: red; } }",
+ "div {\n & { color: red; }\n}",
+ "Implicit-like rule in style rule"
+ );
+
+ // Empty rules (confusingly?) serialize different between style rules
+ // and conditional group rules.
+ assert_unchanged("@media screen {\n}", "Empty conditional rule");
+ assert_unchanged("div { }", "Empty style rule");
+ assert_unchanged("div {\n @media screen {\n}\n}", "Empty conditional inside style rule");
+ assert_unchanged("@media screen {\n div { }\n}", "Empty style inside conditional");
+</script>
diff --git a/testing/web-platform/tests/css/css-nesting/supports-is-consistent-ref.html b/testing/web-platform/tests/css/css-nesting/supports-is-consistent-ref.html
new file mode 100644
index 0000000000..0eb8cd16a1
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/supports-is-consistent-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<title>@supports needs to be consistent with actual nesting support</title>
+<body>
+ <p>Test passes if this text is not red</p>
+</body>
diff --git a/testing/web-platform/tests/css/css-nesting/supports-is-consistent.html b/testing/web-platform/tests/css/css-nesting/supports-is-consistent.html
new file mode 100644
index 0000000000..8be0d666d6
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/supports-is-consistent.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>@supports needs to be consistent with actual nesting support</title>
+<link rel="author" title="Steinar H. Gunderson" href="mailto:sesse@chromium.org">
+<link rel="help" href="https://crbug.com/1414012">
+<link rel="match" href="supports-is-consistent-ref.html">
+<style>
+ /* This test is expected to pass even if the browser does not support nesting. */
+ @supports selector(&) {
+ p {
+ color: red;
+ & { color: inherit; }
+ }
+ }
+</style>
+<body>
+ <p>Test passes if this text is not red</p>
+</body>
diff --git a/testing/web-platform/tests/css/css-nesting/top-level-is-scope.html b/testing/web-platform/tests/css/css-nesting/top-level-is-scope.html
new file mode 100644
index 0000000000..fefc77d5d0
--- /dev/null
+++ b/testing/web-platform/tests/css/css-nesting/top-level-is-scope.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<title>Top-level & is treated like :scope</title>
+<link rel="author" title="Steinar H. Gunderson" href="mailto:sesse@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-nesting-1/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="p">
+ <div class="match" id="level1">
+ <div class="match" id="level2"></div>
+ </div>
+</div>
+
+<script>
+ test(() => {
+ let matched = [];
+ for (const elem of p.querySelectorAll('& .match')) {
+ matched.push(elem.getAttribute('id'));
+ }
+ assert_array_equals(matched, ['level1', 'level2']);
+ }, '& as direct ancestor');
+
+ test(() => {
+ let matched = [];
+ for (const elem of p.querySelectorAll('& > .match')) {
+ matched.push(elem.getAttribute('id'));
+ }
+ assert_array_equals(matched, ['level1']);
+ }, '& matches scoped element only, not everything');
+</script>