mirror of
https://github.com/HeyPuter/firefox.git
synced 2026-11-13 20:14:57 -10:00
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] https://github.com/w3c/csswg-drafts/issues/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 <[email protected]> Commit-Queue: Anders Hartvoll Ruud <[email protected]> Reviewed-by: Philip Pfaffe <[email protected]> Cr-Commit-Position: refs/heads/main@{#1347266} -- wpt-commits: b23720e948f2af9d5ddb01d94ca49fbd1c40382e wpt-pr: 47744
545 lines
14 KiB
HTML
545 lines
14 KiB
HTML
<!DOCTYPE html>
|
||
<title>@scope - nesting (&)</title>
|
||
<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule">
|
||
<link rel="help" href="https://drafts.csswg.org/css-nesting-1/#nest-selector">
|
||
<script src="/resources/testharness.js"></script>
|
||
<script src="/resources/testharnessreport.js"></script>
|
||
<main id=main></main>
|
||
|
||
<template id=test_nest_scope_end>
|
||
<div>
|
||
<style>
|
||
@scope (.a) to (& > &) {
|
||
* { z-index:1; }
|
||
}
|
||
</style>
|
||
<div class=a> <!-- This scope is limited by the element below. -->
|
||
<div class=a> <!-- This scope is limited by its own root. -->
|
||
<div id=below></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id=outside></div>
|
||
</template>
|
||
<script>
|
||
test((t) => {
|
||
t.add_cleanup(() => main.replaceChildren());
|
||
main.append(test_nest_scope_end.content.cloneNode(true));
|
||
|
||
assert_equals(getComputedStyle(below).zIndex, 'auto');
|
||
assert_equals(getComputedStyle(outside).zIndex, 'auto');
|
||
}, 'Nesting-selector in <scope-end>');
|
||
</script>
|
||
|
||
<template id=test_nest_scope_end_implicit_scope>
|
||
<div>
|
||
<style>
|
||
/* (.b) behaves like (:scope .b), due :scope being prepended
|
||
implicitly. */
|
||
@scope (.a) to (.b) {
|
||
:scope { z-index:1; }
|
||
}
|
||
|
||
/* Should not match, since <scope-end> refers to the scope itself. */
|
||
@scope (.a) to (.b:scope) {
|
||
:scope { z-index:42; }
|
||
}
|
||
</style>
|
||
<div class="a b">
|
||
<div class=b>
|
||
<div id=below></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id=outside></div>
|
||
</template>
|
||
<script>
|
||
test((t) => {
|
||
t.add_cleanup(() => main.replaceChildren());
|
||
main.append(test_nest_scope_end_implicit_scope.content.cloneNode(true));
|
||
let a = document.querySelector('.a');
|
||
let b = document.querySelector('.a > .b');
|
||
assert_equals(getComputedStyle(a).zIndex, '1');
|
||
assert_equals(getComputedStyle(b).zIndex, 'auto');
|
||
assert_equals(getComputedStyle(below).zIndex, 'auto');
|
||
assert_equals(getComputedStyle(outside).zIndex, 'auto');
|
||
}, 'Implicit :scope in <scope-end>');
|
||
</script>
|
||
|
||
<template id=test_relative_selector_scope_end>
|
||
<div>
|
||
<style>
|
||
@scope (.a) to (> .b) {
|
||
*, :scope { z-index:1; }
|
||
}
|
||
</style>
|
||
<div class="a b">
|
||
<div class=b>
|
||
<div id=below></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id=outside></div>
|
||
</template>
|
||
<script>
|
||
test((t) => {
|
||
t.add_cleanup(() => main.replaceChildren());
|
||
main.append(test_relative_selector_scope_end.content.cloneNode(true));
|
||
let a = document.querySelector('.a');
|
||
let b = document.querySelector('.a > .b');
|
||
assert_equals(getComputedStyle(a).zIndex, '1');
|
||
assert_equals(getComputedStyle(b).zIndex, 'auto');
|
||
assert_equals(getComputedStyle(below).zIndex, 'auto');
|
||
assert_equals(getComputedStyle(outside).zIndex, 'auto');
|
||
}, 'Relative selectors in <scope-end>');
|
||
</script>
|
||
|
||
<template id=test_inner_nest>
|
||
<div>
|
||
<style>
|
||
@scope (.a) {
|
||
& + & {
|
||
z-index:1;
|
||
}
|
||
}
|
||
</style>
|
||
<div class=a>
|
||
<div id=inner1 class=a></div>
|
||
<div id=inner2 class=a></div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
test((t) => {
|
||
t.add_cleanup(() => main.replaceChildren());
|
||
main.append(test_inner_nest.content.cloneNode(true));
|
||
|
||
assert_equals(getComputedStyle(inner1).zIndex, 'auto');
|
||
assert_equals(getComputedStyle(inner2).zIndex, '1');
|
||
}, 'Nesting-selector in the scope\'s <stylesheet>');
|
||
</script>
|
||
|
||
<template id=test_parent_in_pseudo_scope>
|
||
<div>
|
||
<style>
|
||
@scope (#div) {
|
||
:scope {
|
||
z-index: 1;
|
||
& {
|
||
z-index: 2;
|
||
}
|
||
}
|
||
}
|
||
</style>
|
||
<div id=div></div>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
test((t) => {
|
||
t.add_cleanup(() => main.replaceChildren());
|
||
main.append(test_parent_in_pseudo_scope.content.cloneNode(true));
|
||
|
||
assert_equals(getComputedStyle(div).zIndex, '2');
|
||
}, 'Nesting-selector within :scope rule');
|
||
</script>
|
||
|
||
<template id=test_parent_in_pseudo_scope_double>
|
||
<div>
|
||
<style>
|
||
@scope (#div) {
|
||
:scope {
|
||
z-index: 1;
|
||
& {
|
||
& {
|
||
z-index: 2;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|
||
<div id=div></div>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
test((t) => {
|
||
t.add_cleanup(() => main.replaceChildren());
|
||
main.append(test_parent_in_pseudo_scope_double.content.cloneNode(true));
|
||
|
||
assert_equals(getComputedStyle(div).zIndex, '2');
|
||
}, 'Nesting-selector within :scope rule (double nested)');
|
||
</script>
|
||
|
||
<template id=test_scope_within_style_rule>
|
||
<div>
|
||
<style>
|
||
.a {
|
||
@scope (.b) {
|
||
.c { z-index: 1; }
|
||
}
|
||
}
|
||
</style>
|
||
<div class=a>
|
||
<div class=b>
|
||
<div class=c>
|
||
</div>
|
||
</div>
|
||
<div id=out_of_scope class=c>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
test((t) => {
|
||
t.add_cleanup(() => main.replaceChildren());
|
||
main.append(test_scope_within_style_rule.content.cloneNode(true));
|
||
|
||
let c = document.querySelector('.c');
|
||
assert_equals(getComputedStyle(c).zIndex, '1');
|
||
assert_equals(getComputedStyle(out_of_scope).zIndex, 'auto');
|
||
}, '@scope nested within style rule');
|
||
</script>
|
||
|
||
<template id=test_parent_pseudo_in_nested_scope_start>
|
||
<div>
|
||
<style>
|
||
.a {
|
||
@scope (&.b) {
|
||
:scope { z-index: 1; }
|
||
}
|
||
}
|
||
</style>
|
||
<div class=a></div>
|
||
<div class=b></div>
|
||
<div class="a b"></div>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
test((t) => {
|
||
t.add_cleanup(() => main.replaceChildren());
|
||
main.append(test_parent_pseudo_in_nested_scope_start.content.cloneNode(true));
|
||
|
||
let a = document.querySelector('.a:not(.b)');
|
||
let b = document.querySelector('.b:not(.a)');
|
||
let ab = document.querySelector('.a.b');
|
||
assert_equals(getComputedStyle(a).zIndex, 'auto');
|
||
assert_equals(getComputedStyle(b).zIndex, 'auto');
|
||
assert_equals(getComputedStyle(ab).zIndex, '1');
|
||
}, 'Parent pseudo class within scope-start');
|
||
</script>
|
||
|
||
<template id=test_parent_pseudo_in_nested_scope_end>
|
||
<div>
|
||
<style>
|
||
.a {
|
||
/* Note that & in <scope-end> refers to <scope-start>,
|
||
not the outer style rule. */
|
||
@scope (&.b) to (&.c) {
|
||
:scope, * { z-index: 1; }
|
||
}
|
||
}
|
||
</style>
|
||
<div class="a b">
|
||
<div class="a c">
|
||
<div class="a b c">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
test((t) => {
|
||
t.add_cleanup(() => main.replaceChildren());
|
||
main.append(test_parent_pseudo_in_nested_scope_end.content.cloneNode(true));
|
||
|
||
let ab = document.querySelector('.a.b:not(.c)');
|
||
let ac = document.querySelector('.a.c:not(.b)');
|
||
let abc = document.querySelector('.a.b.c');
|
||
assert_equals(getComputedStyle(ab).zIndex, '1');
|
||
assert_equals(getComputedStyle(ac).zIndex, '1');
|
||
assert_equals(getComputedStyle(abc).zIndex, 'auto', 'limit element is not in scope');
|
||
}, 'Parent pseudo class within scope-end');
|
||
</script>
|
||
|
||
<template id=test_parent_pseudo_in_nested_scope_body>
|
||
<div>
|
||
<style>
|
||
.a {
|
||
@scope (.b) {
|
||
/* The & points to <scope-start>, which contains an implicit &
|
||
which points to .a. */
|
||
&.c { z-index: 1; }
|
||
}
|
||
}
|
||
</style>
|
||
<div class=a>
|
||
<div class=b>
|
||
<div class="c"></div>
|
||
<div class="a c"></div>
|
||
<div class="a b c" matching></div>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div class=a></div>
|
||
<div class=b></div>
|
||
<div class=c></div>
|
||
<div class="a b"></div>
|
||
<div class="a c"></div>
|
||
<div class="b c"></div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
test((t) => {
|
||
t.add_cleanup(() => main.replaceChildren());
|
||
main.append(test_parent_pseudo_in_nested_scope_body.content.cloneNode(true));
|
||
|
||
let matching = main.querySelectorAll("div[matching]");
|
||
let non_matching = main.querySelectorAll("div:not([matching])");
|
||
|
||
for (let m of matching) {
|
||
assert_equals(getComputedStyle(m).zIndex, '1', `matching: ${m.nodeName}${m.className}`);
|
||
}
|
||
for (let m of non_matching) {
|
||
assert_equals(getComputedStyle(m).zIndex, 'auto', `non-matching: ${m.nodeName}${m.className}`);
|
||
}
|
||
}, 'Parent pseudo class within body of nested @scope');
|
||
</script>
|
||
|
||
<template id=test_direct_declarations_in_nested_scope>
|
||
<div>
|
||
<style>
|
||
.a {
|
||
@scope (.b) {
|
||
z-index: 1;
|
||
}
|
||
}
|
||
</style>
|
||
<div class=a>
|
||
<div class=b>
|
||
<div class="c"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
test((t) => {
|
||
t.add_cleanup(() => main.replaceChildren());
|
||
main.append(test_direct_declarations_in_nested_scope.content.cloneNode(true));
|
||
|
||
let a = document.querySelector('.a');
|
||
let b = document.querySelector('.b');
|
||
let c = document.querySelector('.c');
|
||
assert_equals(getComputedStyle(a).zIndex, 'auto');
|
||
assert_equals(getComputedStyle(b).zIndex, '1');
|
||
assert_equals(getComputedStyle(c).zIndex, 'auto');
|
||
}, 'Implicit rule within nested @scope ');
|
||
</script>
|
||
|
||
<template id=test_direct_declarations_in_nested_scope_proximity>
|
||
<div>
|
||
<style>
|
||
.a {
|
||
/* 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 */
|
||
}
|
||
}
|
||
:where(.b) { z-index: 2; }
|
||
</style>
|
||
<div class=a>
|
||
<div class="b x">
|
||
<div class=c>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
test((t) => {
|
||
t.add_cleanup(() => main.replaceChildren());
|
||
main.append(test_direct_declarations_in_nested_scope_proximity.content.cloneNode(true));
|
||
|
||
let a = document.querySelector('.a');
|
||
let b = document.querySelector('.b');
|
||
let c = document.querySelector('.c');
|
||
assert_equals(getComputedStyle(a).zIndex, 'auto');
|
||
assert_equals(getComputedStyle(b).zIndex, '1');
|
||
assert_equals(getComputedStyle(c).zIndex, 'auto');
|
||
}, 'Implicit rule within nested @scope (proximity)');
|
||
</script>
|
||
|
||
<template id=test_nested_scope_inside_an_is>
|
||
<div>
|
||
<style>
|
||
@scope (.a) {
|
||
.b {
|
||
/* When nesting, because we’re inside a defined scope,
|
||
the `:scope` should reference the scoping root node properly, and
|
||
check for the presence of an extra class on it, essentially
|
||
being equal to `:scope.x .b { z-index: 1 }`. */
|
||
&:is(:scope.x *) {
|
||
z-index: 1;
|
||
}
|
||
/* This should not match, as we have a defined scope, and should
|
||
not skip to the root. */
|
||
&:is(:root:scope *) {
|
||
z-index: 2;
|
||
}
|
||
}
|
||
/* The nested case can be though of the following when expanded: */
|
||
.c:is(:scope.x *) {
|
||
z-index: 3;
|
||
}
|
||
}
|
||
</style>
|
||
<div class="b">
|
||
</div>
|
||
<div class="a x">
|
||
<div class="b">
|
||
</div>
|
||
<div class="c">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
test((t) => {
|
||
t.add_cleanup(() => main.replaceChildren());
|
||
main.append(test_nested_scope_inside_an_is.content.cloneNode(true));
|
||
|
||
let b_outside = document.querySelector('.b');
|
||
let b_inside = document.querySelector('.a .b');
|
||
let c = document.querySelector('.c');
|
||
assert_equals(getComputedStyle(b_outside).zIndex, 'auto');
|
||
assert_equals(getComputedStyle(b_inside).zIndex, '1');
|
||
assert_equals(getComputedStyle(c).zIndex, '3');
|
||
}, 'Nested :scope inside an :is');
|
||
</script>
|
||
|
||
<template id=test_nested_scope_pseudo>
|
||
<div>
|
||
<style>
|
||
@scope (.b) {
|
||
.a:not(:scope) {
|
||
& :scope {
|
||
z-index: 1;
|
||
}
|
||
}
|
||
}
|
||
</style>
|
||
<div class="b">
|
||
</div>
|
||
<div class="a">
|
||
<div class="b">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
test((t) => {
|
||
t.add_cleanup(() => main.replaceChildren());
|
||
main.append(test_nested_scope_pseudo.content.cloneNode(true));
|
||
|
||
let b_outside = document.querySelector('.b');
|
||
let b_inside = document.querySelector('.a .b');
|
||
assert_equals(getComputedStyle(b_outside).zIndex, 'auto');
|
||
assert_equals(getComputedStyle(b_inside).zIndex, '1');
|
||
}, ':scope within nested and scoped rule');
|
||
</script>
|
||
|
||
<template id=test_nested_scope_pseudo_implied>
|
||
<div>
|
||
<style>
|
||
@scope (.b) {
|
||
.a:not(:scope) {
|
||
:scope { /* & implied */
|
||
z-index: 1;
|
||
}
|
||
}
|
||
}
|
||
</style>
|
||
<div class="b">
|
||
</div>
|
||
<div class="a">
|
||
<div class="b">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
test((t) => {
|
||
t.add_cleanup(() => main.replaceChildren());
|
||
main.append(test_nested_scope_pseudo_implied.content.cloneNode(true));
|
||
|
||
let b_outside = document.querySelector('.b');
|
||
let b_inside = document.querySelector('.a .b');
|
||
assert_equals(getComputedStyle(b_outside).zIndex, 'auto');
|
||
assert_equals(getComputedStyle(b_inside).zIndex, '1');
|
||
}, ':scope within nested and scoped rule (implied &)');
|
||
</script>
|
||
|
||
<template id=test_nested_scope_pseudo_relative>
|
||
<div>
|
||
<style>
|
||
@scope (.b) {
|
||
.a:not(:scope) {
|
||
> :scope { /* & implied */
|
||
z-index: 1;
|
||
}
|
||
}
|
||
}
|
||
</style>
|
||
<div class="b">
|
||
</div>
|
||
<div class="a">
|
||
<div class="b">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
test((t) => {
|
||
t.add_cleanup(() => main.replaceChildren());
|
||
main.append(test_nested_scope_pseudo_relative.content.cloneNode(true));
|
||
|
||
let b_outside = document.querySelector('.b');
|
||
let b_inside = document.querySelector('.a .b');
|
||
assert_equals(getComputedStyle(b_outside).zIndex, 'auto');
|
||
assert_equals(getComputedStyle(b_inside).zIndex, '1');
|
||
}, ':scope within nested and scoped rule (relative)');
|
||
</script>
|
||
|
||
<template id=test_scoped_nested_group_rule>
|
||
<div>
|
||
<style>
|
||
@scope (.a) {
|
||
.b:not(:scope) {
|
||
@media (width) {
|
||
z-index: 1;
|
||
}
|
||
}
|
||
}
|
||
</style>
|
||
<div class="b">
|
||
</div>
|
||
<div class="a">
|
||
<div class="b">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
test((t) => {
|
||
t.add_cleanup(() => main.replaceChildren());
|
||
main.append(test_scoped_nested_group_rule.content.cloneNode(true));
|
||
|
||
let b_outside = document.querySelector('.b');
|
||
let b_inside = document.querySelector('.a .b');
|
||
assert_equals(getComputedStyle(b_outside).zIndex, 'auto');
|
||
assert_equals(getComputedStyle(b_inside).zIndex, '1');
|
||
}, 'Scoped nested group rule');
|
||
</script>
|