-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bug 1914427 [wpt PR 47744] - [css-nesting] Implement CSSNestedDeclara…
…tions behind a flag, a=testonly Automatic update from web-platform-tests [css-nesting] Implement CSSNestedDeclarations behind a flag The CSSWG recently resolved to change how "bare" declarations work when mixed with nested style rules. Previously, any declaration following a nested style rule would be "shifted up" to join the leading declarations, but as of the work leading up to Issue 10234 [1], such declarations now instead remain in place (wrapped in a CSSNestedDeclarations rule). This CL implements this rule, as well as the parser changes needed to produce those rules during ConsumeDeclarationList. The parsing behavior is similar to the behavior previously seen for emitting signalling/invisible rules (removed in CL:5593832), although naturally without any signalling, nor any invisibility. Per spec, CSSNestedDeclarations is a kind of style rule that matches exactly what its parent style rule matches, and with the same specificity behavior. This is different from the '&' pseudo-class, which uses the maximum specificity across its arguments, and can't match pseudo-elements. This CL implements this via an inner StyleRule, held by the CSSNestedDeclarations rule. This inner StyleRule can't be observed via CSSOM. It exists primarily to be able to bucket the rule normally on RuleSet. [1] w3c/csswg-drafts#10234 Change-Id: If9afe0cbb41e7de0acdd781ecfbf6884d677c6f8 Binary-Size: crbug.com/344608183 Bug: 343463516, 361600667 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5594117 Reviewed-by: Steinar H Gunderson <sesse@chromium.org> Commit-Queue: Anders Hartvoll Ruud <andruud@chromium.org> Reviewed-by: Philip Pfaffe <pfaffe@chromium.org> Cr-Commit-Position: refs/heads/main@{#1347266} -- wpt-commits: b23720e948f2af9d5ddb01d94ca49fbd1c40382e wpt-pr: 47744
- Loading branch information
1 parent
6b3d26d
commit 9da44f3
Showing
6 changed files
with
363 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
150 changes: 150 additions & 0 deletions
150
testing/web-platform/tests/css/css-nesting/nested-declarations-cssom.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
197
testing/web-platform/tests/css/css-nesting/nested-declarations-matching.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.