<!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 { /* 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) { z-index: 1; /* Should win due to proximity */ } } .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>