Tests: Import all WPT css/css-nesting tests

A few are skipped for now:
- A few ref tests fail
- Crash tests are not supported by our runner and time out
- top-level-is-scope.html crashes and needs further investigation
This commit is contained in:
Sam Atkins 2024-11-06 16:43:57 +00:00 committed by Andreas Kling
commit b0e79ce549
Notes: github-actions[bot] 2024-11-07 14:12:28 +00:00
62 changed files with 2098 additions and 0 deletions

View file

@ -0,0 +1,6 @@
.test{span{color:red}span{color:green}}
/*
* There needs to be some text here, or the bug
* will not manifest for unrelated reasons.
*/

View file

@ -0,0 +1,17 @@
<!doctype html>
<title>CSS Nesting bug: Block ignored after lack of whitespace</title>
<link rel="help" href="https://crbug.com/362674384">
<link rel="stylesheet" href="block-skipping.css"> <!-- Bug only manifests in external stylesheets. -->
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<div class="test">
<span id="target">Test passes if this text is green.</span>
</div>
<script>
test(() => {
assert_equals(getComputedStyle(target).color, 'rgb(0, 128, 0)');
});
</script>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -0,0 +1,52 @@
<!DOCTYPE html>
<title>CSS Nesting: CSSNestedDeclarations CSSOM (whitespace)</title>
<link rel="help" href="https://drafts.csswg.org/css-nesting-1/#nested-declarations-rule">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script>
// https://drafts.csswg.org/cssom/#serialize-a-css-rule
test(() => {
let s = new CSSStyleSheet();
s.replaceSync(`
.a {
& { }
left: 1px;
& { }
right: 1px;
}
`);
assert_equals(s.cssRules.length, 1);
let a_rule = s.cssRules[0];
assert_equals(a_rule.cssRules.length, 4);
for (let child_rule of a_rule.cssRules) {
child_rule.style = '';
}
assert_equals(a_rule.cssText, '.a {\n & { }\n & { }\n}');
}, 'Empty CSSNestedDeclarations do not affect outer serialization');
// https://drafts.csswg.org/cssom/#serialize-a-css-rule
test(() => {
let s = new CSSStyleSheet();
s.replaceSync(`
.a {
@media (width > 1px) {
& { }
left: 1px;
& { }
right: 1px;
}
}
`);
assert_equals(s.cssRules.length, 1);
let outer = s.cssRules[0];
assert_equals(outer.cssRules.length, 1);
// @media
let media = outer.cssRules[0];
assert_equals(media.cssRules.length, 4);
for (let child_rule of media.cssRules) {
child_rule.style = '';
}
assert_equals(media.cssText, '@media (width > 1px) {\n & { }\n & { }\n}');
}, 'Empty CSSNestedDeclarations do not affect outer serialization (nested grouping rule)');
</script>

View file

@ -0,0 +1,237 @@
<!DOCTYPE html>
<title>CSS Nesting: CSSNestedDeclarations CSSOM</title>
<link rel="help" href="https://drafts.csswg.org/css-nesting-1/#nested-declarations-rule">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script>
test(() => {
let s = new CSSStyleSheet();
s.replaceSync(`
.a {
& { --x:1; }
--x:2;
}
`);
assert_equals(s.cssRules.length, 1);
let outer = s.cssRules[0];
assert_equals(outer.cssRules.length, 2);
assert_equals(outer.cssRules[0].cssText, `& { --x: 1; }`);
assert_equals(outer.cssRules[1].cssText, `--x: 2;`);
}, 'Trailing declarations');
test(() => {
let s = new CSSStyleSheet();
s.replaceSync(`
.a {
--a:1;
--b:1;
& { --c:1; }
--d:1;
--e:1;
& { --f:1; }
--g:1;
--h:1;
--i:1;
& { --j:1; }
--k:1;
--l:1;
}
`);
assert_equals(s.cssRules.length, 1);
let outer = s.cssRules[0];
assert_equals(outer.cssRules.length, 6);
assert_equals(outer.cssRules[0].cssText, `& { --c: 1; }`);
assert_equals(outer.cssRules[1].cssText, `--d: 1; --e: 1;`);
assert_equals(outer.cssRules[2].cssText, `& { --f: 1; }`);
assert_equals(outer.cssRules[3].cssText, `--g: 1; --h: 1; --i: 1;`);
assert_equals(outer.cssRules[4].cssText, `& { --j: 1; }`);
assert_equals(outer.cssRules[5].cssText, `--k: 1; --l: 1;`);
}, 'Mixed declarations');
test(() => {
let s = new CSSStyleSheet();
s.replaceSync(`
.a {
& { --x:1; }
--y:2;
--z:3;
}
`);
assert_equals(s.cssRules.length, 1);
let outer = s.cssRules[0];
assert_equals(outer.cssRules.length, 2);
let nested_declarations = outer.cssRules[1];
assert_true(nested_declarations instanceof CSSNestedDeclarations);
assert_equals(nested_declarations.style.length, 2);
assert_equals(nested_declarations.style.getPropertyValue('--x'), '');
assert_equals(nested_declarations.style.getPropertyValue('--y'), '2');
assert_equals(nested_declarations.style.getPropertyValue('--z'), '3');
}, 'CSSNestedDeclarations.style');
test(() => {
let s = new CSSStyleSheet();
s.replaceSync(`
.a {
@media (width > 100px) {
--x:1;
--y:1;
.b { }
--z:1;
}
--w:1;
}
`);
assert_equals(s.cssRules.length, 1);
let outer = s.cssRules[0];
assert_equals(outer.cssRules.length, 2);
// @media
let media = outer.cssRules[0];
assert_equals(media.cssRules.length, 3);
assert_true(media.cssRules[0] instanceof CSSNestedDeclarations);
assert_equals(media.cssRules[0].cssText, `--x: 1; --y: 1;`);
assert_equals(media.cssRules[1].cssText, `& .b { }`);
assert_true(media.cssRules[2] instanceof CSSNestedDeclarations);
assert_equals(media.cssRules[2].cssText, `--z: 1;`);
assert_true(outer.cssRules[1] instanceof CSSNestedDeclarations);
assert_equals(outer.cssRules[1].cssText, `--w: 1;`);
}, 'Nested group rule');
test(() => {
let s = new CSSStyleSheet();
s.replaceSync(`
.a {
@scope (.foo) {
--x:1;
--y:1;
.b { }
--z:1;
}
--w:1;
}
`);
assert_equals(s.cssRules.length, 1);
let outer = s.cssRules[0];
if (window.CSSScopeRule) {
assert_equals(outer.cssRules.length, 2);
// @scope
let scope = outer.cssRules[0];
assert_true(scope instanceof CSSScopeRule);
assert_equals(scope.cssRules.length, 3);
assert_true(scope.cssRules[0] instanceof CSSNestedDeclarations);
assert_equals(scope.cssRules[0].cssText, `--x: 1; --y: 1;`);
assert_equals(scope.cssRules[1].cssText, `.b { }`); // Implicit :scope here.
assert_true(scope.cssRules[2] instanceof CSSNestedDeclarations);
assert_equals(scope.cssRules[2].cssText, `--z: 1;`);
assert_true(outer.cssRules[1] instanceof CSSNestedDeclarations);
assert_equals(outer.cssRules[1].cssText, `--w: 1;`);
} else {
assert_equals(outer.cssRules.length, 0);
}
}, 'Nested @scope rule');
test(() => {
let s = new CSSStyleSheet();
s.replaceSync(`
a {
& { --x:1; }
width: 100px;
height: 200px;
color:hover {}
--y: 2;
}
`);
assert_equals(s.cssRules.length, 1);
let outer = s.cssRules[0];
assert_equals(outer.cssRules.length, 4);
assert_equals(outer.cssRules[0].cssText, `& { --x: 1; }`);
assert_equals(outer.cssRules[1].cssText, `width: 100px; height: 200px;`);
assert_equals(outer.cssRules[2].cssText, `& color:hover { }`);
assert_equals(outer.cssRules[3].cssText, `--y: 2;`);
}, 'Inner rule starting with an ident');
test(() => {
let s = new CSSStyleSheet();
s.replaceSync('.a {}');
assert_equals(s.cssRules.length, 1);
let a_rule = s.cssRules[0];
assert_equals(a_rule.cssRules.length, 0);
a_rule.insertRule(`
width: 100px;
height: 200px;
`);
assert_equals(a_rule.cssRules.length, 1);
assert_true(a_rule.cssRules[0] instanceof CSSNestedDeclarations);
assert_equals(a_rule.cssRules[0].cssText, `width: 100px; height: 200px;`);
}, 'Inserting a CSSNestedDeclaration rule into style rule');
test(() => {
let s = new CSSStyleSheet();
s.replaceSync('.a { @media (width > 100px) {} }');
assert_equals(s.cssRules.length, 1);
assert_equals(s.cssRules[0].cssRules.length, 1);
let media_rule = s.cssRules[0].cssRules[0];
assert_true(media_rule instanceof CSSMediaRule);
assert_equals(media_rule.cssRules.length, 0);
media_rule.insertRule(`
width: 100px;
height: 200px;
`);
assert_equals(media_rule.cssRules.length, 1);
assert_true(media_rule.cssRules[0] instanceof CSSNestedDeclarations);
assert_equals(media_rule.cssRules[0].cssText, `width: 100px; height: 200px;`);
}, 'Inserting a CSSNestedDeclaration rule into nested group rule');
test(() => {
let s = new CSSStyleSheet();
s.replaceSync('@media (width > 100px) {}');
assert_equals(s.cssRules.length, 1);
let media_rule = s.cssRules[0];
assert_true(media_rule instanceof CSSMediaRule);
assert_equals(media_rule.cssRules.length, 0);
assert_throws_dom('SyntaxError', () => {
media_rule.insertRule(`
width: 100px;
height: 200px;
`);
});
}, 'Attempting to insert a CSSNestedDeclaration rule into top-level @media rule');
test(() => {
let sheet = new CSSStyleSheet();
assert_throws_dom('SyntaxError', () => {
sheet.insertRule(`
width: 100px;
height: 200px;
`);
});
}, 'Attempting to insert a CSSNestedDeclaration rule into a stylesheet');
test(() => {
let s = new CSSStyleSheet();
s.replaceSync('.a {}');
assert_equals(s.cssRules.length, 1);
let a_rule = s.cssRules[0];
assert_equals(a_rule.cssRules.length, 0);
assert_throws_dom('SyntaxError', () => {
a_rule.insertRule('');
});
}, 'Attempting to insert a CSSNestedDeclaration rule, empty block');
test(() => {
let s = new CSSStyleSheet();
s.replaceSync('.a {}');
assert_equals(s.cssRules.length, 1);
let a_rule = s.cssRules[0];
assert_equals(a_rule.cssRules.length, 0);
assert_throws_dom('SyntaxError', () => {
a_rule.insertRule(`
xwidth: 100px;
xheight: 200px;
`);
});
}, 'Attempting to insert a CSSNestedDeclaration rule, all invalid declarations');
</script>

View file

@ -0,0 +1,221 @@
<!DOCTYPE html>
<title>CSS Nesting: CSSNestedDeclarations matching</title>
<link rel="help" href="https://drafts.csswg.org/css-nesting-1/#nested-declarations-rule">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<style>
.trailing {
--x: FAIL;
& { --x: FAIL; }
--x: PASS;
}
</style>
<div class=trailing></div>
<script>
test(() => {
let e = document.querySelector('.trailing');
assert_equals(getComputedStyle(e).getPropertyValue('--x'), 'PASS');
}, 'Trailing declarations apply after any preceding rules');
</script>
<style>
.trailing_no_leading {
& { --x: FAIL; }
--x: PASS;
}
</style>
<div class=trailing_no_leading></div>
<script>
test(() => {
let e = document.querySelector('.trailing_no_leading');
assert_equals(getComputedStyle(e).getPropertyValue('--x'), 'PASS');
}, 'Trailing declarations apply after any preceding rules (no leading)');
</script>
<style>
.trailing_multiple {
--x: FAIL;
--y: FAIL;
--z: FAIL;
--w: FAIL;
& { --x: FAIL; }
--x: PASS;
--y: PASS;
& { --z: FAIL; }
--z: PASS;
--w: PASS;
}
</style>
<div class=trailing_multiple></div>
<script>
test(() => {
let e = document.querySelector('.trailing_multiple');
let s = getComputedStyle(e);
assert_equals(s.getPropertyValue('--x'), 'PASS');
assert_equals(s.getPropertyValue('--y'), 'PASS');
assert_equals(s.getPropertyValue('--z'), 'PASS');
assert_equals(s.getPropertyValue('--w'), 'PASS');
}, 'Trailing declarations apply after any preceding rules (multiple)');
</script>
<style>
.trailing_specificity {
--x: FAIL;
:is(&, div.nomatch2) { --x: PASS; } /* Specificity: (0, 1, 1) */
--x: FAIL; /* Specificity: (0, 1, 0) */
}
</style>
<div class=trailing_specificity></div>
<script>
test(() => {
let e = document.querySelector('.trailing_specificity');
assert_equals(getComputedStyle(e).getPropertyValue('--x'), 'PASS');
}, 'Nested declarations rule has same specificity as outer selector');
</script>
<style>
#nomatch, .specificity_top_level {
--x: FAIL;
:is(&, div.nomatch2) { --x: PASS; } /* Specificity: (0, 1, 1) */
--x: FAIL; /* Specificity: (0, 1, 0). In particular, this does not have
specificity like :is(#nomatch, .specificity_top_level). */
}
</style>
<div class=specificity_top_level></div>
<script>
test(() => {
let e = document.querySelector('.specificity_top_level');
assert_equals(getComputedStyle(e).getPropertyValue('--x'), 'PASS');
}, 'Nested declarations rule has top-level specificity behavior');
</script>
<style>
#nomatch, .specificity_top_level_max, div.specificity_top_level_max {
--x: FAIL;
:is(:where(&), div.nomatch2) { --x: FAIL; } /* Specificity: (0, 1, 1) */
--x: PASS; /* Specificity: (0, 1, 1) (for div.specificity_top_level_max) */
}
</style>
<div class=specificity_top_level_max></div>
<script>
test(() => {
let e = document.querySelector('.specificity_top_level_max');
assert_equals(getComputedStyle(e).getPropertyValue('--x'), 'PASS');
}, 'Nested declarations rule has top-level specificity behavior (max matching)');
</script>
<style>
.nested_pseudo::after {
--x: FAIL;
@media (width > 0px) {
--x: PASS;
}
}
</style>
<div class=nested_pseudo></div>
<script>
test(() => {
let e = document.querySelector('.nested_pseudo');
assert_equals(getComputedStyle(e, '::after').getPropertyValue('--x'), 'PASS');
}, 'Bare declartaion in nested grouping rule can match pseudo-element');
</script>
<style>
#nomatch, .nested_group_rule {
--x: FAIL;
@media (width > 0px) {
--x: FAIL; /* Specificity: (0, 1, 0) */
}
--x: PASS;
}
</style>
<div class=nested_group_rule></div>
<script>
test(() => {
let e = document.querySelector('.nested_group_rule');
assert_equals(getComputedStyle(e).getPropertyValue('--x'), 'PASS');
}, 'Nested group rules have top-level specificity behavior');
</script>
<style>
.nested_scope_rule {
div:where(&) { /* Specificity: (0, 0, 1) */
--x: PASS;
}
@scope (&) {
--x: FAIL; /* Specificity: (0, 0, 0) */
}
}
</style>
<div class=nested_scope_rule></div>
<script>
test(() => {
let e = document.querySelector('.nested_scope_rule');
assert_equals(getComputedStyle(e).getPropertyValue('--x'), 'PASS');
}, 'Nested @scope rules behave like :where(:scope)');
</script>
<style>
.nested_scope_rule_trailing {
div:where(&) { /* Specificity: (0, 0, 1) */
--x: PASS;
}
@scope (&) {
--ignored: 1;
.ignored {}
--x: FAIL; /* Specificity: (0, 0, 0) */
}
}
</style>
<div class=nested_scope_rule_trailing></div>
<script>
test(() => {
let e = document.querySelector('.nested_scope_rule_trailing');
assert_equals(getComputedStyle(e).getPropertyValue('--x'), 'PASS');
}, 'Nested @scope rules behave like :where(:scope) (trailing)');
</script>
<style id=set_parent_selector_text_style>
.set_parent_selector_text {
div {
color: red;
}
.a1 {
.ignored {}
color: green;
}
}
</style>
<div class=set_parent_selector_text>
<div class=a1>A1</div>
<div class=a2>A2</div>
</div>
<script>
test(() => {
let a1 = document.querySelector('.set_parent_selector_text > .a1');
let a2 = document.querySelector('.set_parent_selector_text > .a2');
assert_equals(getComputedStyle(a1).color, 'rgb(0, 128, 0)');
assert_equals(getComputedStyle(a2).color, 'rgb(255, 0, 0)');
let rules = set_parent_selector_text_style.sheet.cssRules;
assert_equals(rules.length, 1);
assert_equals(rules[0].cssRules.length, 2);
let a_rule = rules[0].cssRules[1];
assert_equals(a_rule.selectorText, '& .a1');
a_rule.selectorText = '.a2';
assert_equals(a_rule.selectorText, '& .a2');
assert_equals(getComputedStyle(a1).color, 'rgb(255, 0, 0)');
assert_equals(getComputedStyle(a2).color, 'rgb(0, 128, 0)');
}, 'Nested declarations rule responds to parent selector text change');
</script>

View file

@ -0,0 +1,41 @@
<!DOCTYPE html>
<title>CSS Nesting: Style invalidates after CSSOM mutations to nested rules</title>
<link rel="help" href="https://drafts.csswg.org/css-nesting-1/#nested-style-rule">
<link rel="help" href="https://drafts.csswg.org/css-nesting-1/#nested-group-rules">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<style id=set_parent_selector_text_style>
.set_parent_selector_text {
div {
color: red;
}
.a1 {
color: green;
}
}
</style>
<div class=set_parent_selector_text>
<div class=a1>A1</div>
<div class=a2>A2</div>
</div>
<script>
test(() => {
let a1 = document.querySelector('.set_parent_selector_text > .a1');
let a2 = document.querySelector('.set_parent_selector_text > .a2');
assert_equals(getComputedStyle(a1).color, 'rgb(0, 128, 0)');
assert_equals(getComputedStyle(a2).color, 'rgb(255, 0, 0)');
let rules = set_parent_selector_text_style.sheet.cssRules;
assert_equals(rules.length, 1);
assert_equals(rules[0].cssRules.length, 2);
let a_rule = rules[0].cssRules[1];
assert_equals(a_rule.selectorText, '& .a1');
a_rule.selectorText = '.a2';
assert_equals(getComputedStyle(a1).color, 'rgb(255, 0, 0)');
assert_equals(getComputedStyle(a2).color, 'rgb(0, 128, 0)');
}, 'Nested rule responds to parent selector text change');
</script>

View file

@ -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>

View file

@ -0,0 +1,87 @@
<!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(".foo, .bar", {expected:"& .foo, & .bar"});
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>

View file

@ -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>

View file

@ -0,0 +1,9 @@
<!DOCTYPE html>
<style>
.foo {
::before:where(&) {
color: red;
}
}
</style>
<div class=foo></div>

View file

@ -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>

View file

@ -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>

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<title>CSS Nesting: Specificity of top-level '&'</title>
<link rel="help" href="https://drafts.csswg.org/css-nesting-1">
<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/10196">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<style>
/* Note: at the top level, '&' matches like ':root'. */
/* Should have zero specificity: */
& { color: red; }
/* Should also have zero specificity: */
:where(&) { color: green; }
</style>
<script>
test(() => {
assert_equals(getComputedStyle(document.documentElement).color, 'rgb(0, 128, 0)');
});
</script>