1
0
Fork 0
firefox/testing/web-platform/tests/css/css-cascade/scope-nesting.html
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

713 lines
18 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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 were 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>
<template id=test_scoped_within_scoped>
<div>
<style>
@scope (.a) {
@scope(#descendant) {
:scope {
z-index: 1;
}
}
@scope (> #child) {
:scope {
z-index: 1;
}
}
}
</style>
<div class="a">
<div id="descendant">
</div>
<div id="child">
</div>
</div>
</div>
</template>
<script>
test((t) => {
t.add_cleanup(() => main.replaceChildren());
main.append(test_scoped_within_scoped.content.cloneNode(true));
assert_equals(getComputedStyle(descendant).zIndex, '1');
assert_equals(getComputedStyle(child).zIndex, '1');
}, 'Scoped nested within another scope');
</script>
<template id=test_implicit_scope_nested_group_rule>
<div class=nest>
<style>
.nest {
@scope {
#child {
color: green;
}
}
}
</style>
<div id=child>Foo</div>
</div>
</template>
<script>
test((t) => {
t.add_cleanup(() => main.replaceChildren());
main.append(test_implicit_scope_nested_group_rule.content.cloneNode(true));
assert_equals(getComputedStyle(child).color, 'rgb(0, 128, 0)');
}, 'Implicit (prelude-less) @scope as a nested group rule');
</script>
<template id=test_insert_ampersand_rule_within_scope>
<style>
.a {
@scope (.b) {
#child {
color: red;
}
}
}
</style>
<div class=a>
<div class=b>
<div id=child>Foo</div>
</div>
</div>
</template>
<script>
test((t) => {
t.add_cleanup(() => main.replaceChildren());
main.append(test_insert_ampersand_rule_within_scope.content.cloneNode(true));
assert_equals(getComputedStyle(child).color, 'rgb(255, 0, 0)');
let scope_rule = main.querySelector('style').sheet.cssRules[0].cssRules[0];
scope_rule.insertRule('& #child { color: green; }');
assert_equals(getComputedStyle(child).color, 'rgb(0, 128, 0)');
}, 'Insert a nested style rule within @scope, &');
</script>
<template id=test_insert_pseudo_scope_rule_within_scope>
<style>
.a {
@scope (.b) {
#child {
color: red;
}
}
}
</style>
<div class=a>
<div class=b>
<div id=child>Foo</div>
</div>
</div>
</template>
<script>
test((t) => {
t.add_cleanup(() => main.replaceChildren());
main.append(test_insert_pseudo_scope_rule_within_scope.content.cloneNode(true));
assert_equals(getComputedStyle(child).color, 'rgb(255, 0, 0)');
let scope_rule = main.querySelector('style').sheet.cssRules[0].cssRules[0];
scope_rule.insertRule(':scope #child { color: green; }');
assert_equals(getComputedStyle(child).color, 'rgb(0, 128, 0)');
}, 'Insert a nested style rule within @scope, :scope');
</script>
<template id=test_insert_nested_declarations_directly>
<style>
:where(.a) {
color: red;
}
@scope (.a) {
}
</style>
<div class=a>
<div id=child>Foo</div>
</div>
</template>
<script>
test((t) => {
t.add_cleanup(() => main.replaceChildren());
main.append(test_insert_nested_declarations_directly.content.cloneNode(true));
assert_equals(getComputedStyle(child).color, 'rgb(255, 0, 0)');
let scope_rule = main.querySelector('style').sheet.cssRules[1];
assert_true(scope_rule instanceof CSSScopeRule);
scope_rule.insertRule('color: green');
assert_true(scope_rule.cssRules[0] instanceof CSSNestedDeclarations);
assert_equals(getComputedStyle(child).color, 'rgb(0, 128, 0)');
}, 'Insert a CSSNestedDeclarations rule directly in top-level @scope');
</script>
<template id=test_mutate_outer_selector_text_nested_declaration>
<style>
#child {
color: green; /* Specificity: (1, 0, 0) */
}
.b {
#child {
@scope (&) {
--x: 1;
color: red; /* Specificity: (0, 0, 0), effectively :where(:scope) */
}
}
}
</style>
<div class=a>
<div id=child>Foo</div>
</div>
</template>
<script>
test((t) => {
t.add_cleanup(() => main.replaceChildren());
main.append(test_mutate_outer_selector_text_nested_declaration.content.cloneNode(true));
assert_equals(getComputedStyle(child).color, 'rgb(0, 128, 0)');
assert_equals(getComputedStyle(child).getPropertyValue('--x'), '');
let outer_rule = main.querySelector('style').sheet.cssRules[1];
assert_equals(outer_rule.selectorText, '.b');
outer_rule.selectorText = '.a';
assert_equals(getComputedStyle(child).color, 'rgb(0, 128, 0)'); // Unchanged.
assert_equals(getComputedStyle(child).getPropertyValue('--x'), '1'); // Changed.
}, 'Mutating selectorText on outer style rule causes correct inner specificity');
</script>