Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[css-nesting] Implement CSSNestedDeclarations behind a flag #47744

Merged
merged 1 commit into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions css/css-cascade/scope-nesting.html
Original file line number Diff line number Diff line change
Expand Up @@ -339,16 +339,15 @@
<div>
<style>
.a {
/* We're supposed to prepend :scope to this declaration. If we do that,
then :where() does not matter, since :scope does not gain any
specificity from the enclosing @scope rule. However, if an
implementation incorrectly prepends & instead, then :where() is
needed to avoid the test incorrectly passing due to specificity. */
@scope (:where(&) .b) {
/* The '& .b' selector is wrapped in :where() to prevent a false
positive when the implementation incorrectly wraps
the z-index declaration in a rule with &-behavior
rather than :where(:scope)-behavior. */
@scope (:where(& .b)) {
z-index: 1; /* Should win due to proximity */
}
}
.b { z-index: 2; }
:where(.b) { z-index: 2; }
</style>
<div class=a>
<div class="b x">
Expand Down
150 changes: 150 additions & 0 deletions css/css-nesting/nested-declarations-cssom.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<!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];
assert_equals(outer.cssRules.length, 2);

// @scope
let scope = outer.cssRules[0];
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;`);
}, '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');
</script>
197 changes: 197 additions & 0 deletions css/css-nesting/nested-declarations-matching.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
<!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 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 declarations rule responds to parent selector text change');
</script>
4 changes: 2 additions & 2 deletions css/css-nesting/nesting-basic.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@

.test-10 {
& {
background-color: green;
background-color: red;
}
background-color: red;
background-color: green;
}

.test-11 {
Expand Down
Loading