Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- This WPT test may be referenced by the following Test IDs:
            
- /css/css-cascade/scope-nesting.html - WPT Dashboard Interop Dashboard
 
 
<!DOCTYPE html>
<title>@scope - nesting (&)</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<main id=main></main>
<template id=test_nest_scope_end>
  <div>
    <style>
      /* (& > .b) behaves like (:where(:scope) > .b), due & mapping to :where(:scope).*/
      @scope (.a) to (& > .b) {
        :scope { z-index:1; }
      }
      /* Should not match, since <scope-end> refers to the scope itself. */
      @scope (.a) to (.b&) {
        :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.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');
}, '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 (#div) {
        & {
          z-index: 1;
          & {
            z-index: 2;
          }
        }
      }
    </style>
    <div id=div></div>
  </div>
</template>
<script>
test((t) => {
  t.add_cleanup(() => main.replaceChildren());
  main.append(test_inner_nest.content.cloneNode(true));
  assert_equals(getComputedStyle(div).zIndex, '2');
}, 'Nested nesting-selectors within scope\'s <stylesheet> select inclusive descendants of the scope root');
</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_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>
<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];
  // & does not add specificity - inserting it up front does nothing...
  scope_rule.insertRule('& #child { color: green; }', 0);
  assert_equals(getComputedStyle(child).color, 'rgb(255, 0, 0)');
  scope_rule.deleteRule(0);
  // ... But inserting it at the end makes it win by order of appearance.
  scope_rule.insertRule('& #child { color: green; }', scope_rule.cssRules.length);
  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>
<template id=test_set_selector_text>
  <style id=style>
    :where(.x) {
      background-color: black;
    }
    .a {
      @scope (&) {
        :scope .x { background-color: green; }
      }
    }
  </style>
  <div class=a>
    <div class=x>
    </div>
  </div>
  <div class=b>
    <div class=x>
    </div>
  </div>
</template>
<script>
test((t) => {
  t.add_cleanup(() => main.replaceChildren());
  main.append(test_set_selector_text.content.cloneNode(true));
  let ax = main.querySelector('.a > .x');
  let bx = main.querySelector('.b > .x');
  assert_equals(getComputedStyle(ax).backgroundColor, 'rgb(0, 128, 0)');
  assert_equals(getComputedStyle(bx).backgroundColor, 'rgb(0, 0, 0)');
  style.sheet.cssRules[1].selectorText = '.b'; // .a -> .
  assert_equals(getComputedStyle(ax).backgroundColor, 'rgb(0, 0, 0)');
  assert_equals(getComputedStyle(bx).backgroundColor, 'rgb(0, 128, 0)');
}, 'Modifying selectorText invalidates inner scoped rules');
</script>
<template id=test_set_selector_text_nested_declarations>
  <style id=style>
    :where(.x) {
      background-color: black;
    }
    .a {
      @scope (&) {
        :scope .x {
          .unused {}
          /* CSSNestedDeclarations { */
          background-color: green;
          /* } { */
        }
      }
    }
  </style>
  <div class=a>
    <div class=x>
    </div>
  </div>
  <div class=b>
    <div class=x>
    </div>
  </div>
</template>
<script>
test((t) => {
  t.add_cleanup(() => main.replaceChildren());
  main.append(test_set_selector_text_nested_declarations.content.cloneNode(true));
  let ax = main.querySelector('.a > .x');
  let bx = main.querySelector('.b > .x');
  assert_equals(getComputedStyle(ax).backgroundColor, 'rgb(0, 128, 0)');
  assert_equals(getComputedStyle(bx).backgroundColor, 'rgb(0, 0, 0)');
  style.sheet.cssRules[1].selectorText = '.b'; // .a -> .
  assert_equals(getComputedStyle(ax).backgroundColor, 'rgb(0, 0, 0)');
  assert_equals(getComputedStyle(bx).backgroundColor, 'rgb(0, 128, 0)');
}, 'Modifying selectorText invalidates inner scoped rules (nested declarations)');
</script>