tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit f1a2478b2bd1e44a774f0d2ba618ef15233efcf7
parent e64275bbc6fae28f77addf983ccd485c51655ddd
Author: Steinar H. Gunderson <sesse@chromium.org>
Date:   Mon, 27 Oct 2025 10:07:45 +0000

Bug 1996267 [wpt PR 55639] - Fix specificity of declarations in @contents., a=testonly

Automatic update from web-platform-tests
Fix specificity of declarations in @contents.

Assume we have declarations like this:

  @mixin --m1(@contents) {
    color: red;
    @contents;
  }
  div {
    @apply --m1 { background-color: green; }
  }

Internally, after cloning and such, we get roughly this object tree:

  div [StyleRule] {
    MixinRule {
      & [outer fake StyleRule] {
        StyleRuleNestedDeclarations {
          & [inner fake StyleRule] {
            color: red;
          }
        }
      }
      StyleRuleContents {
        & [outer fake StyleRule] {
          StyleRuleNestedDeclarations {
            & [inner fake StyleRule] {
              background-color: green;
            }
          }
        }
      }
    }
  }

During parsing, StyleRuleNestedDeclarations copies the selector from
its parent rule, which during mixin parsing is a dummy & rule.
During cloning (for mixin application), we reparent these rules;
the parent pointer in the StyleRules is updated, and the selector
in the StyleRuleNestedDeclarations is copied from its parent.

However, StyleRuleNestedDeclarations does not actually want & as its
parent; it wants div. (This matters especially if the outermost rule
has a selector list, with differing selectivities.) Somehow this worked
for the normal @apply case, but not for @contents. The reason is that
StyleRuleMixin does not actually hold the fake parent rule itself;
it is a StyleRuleGroup and holds the child rules directly. So the fake
parent rule, while it _is_ the parent rule of the rules in the @mixin,
does not come into play; when cloning, we reparent the rules within
to have the mixin's parent (which is div).

We need to do the same for @apply; it is _not_ a StyleRuleGroup
but should perhaps be so (the spec writer is not entirely decided),
so that instead of cloning (outer) fake StyleRule holding its
@contents block, we should skip over that and clone/reparent
the rules within directly. (We used to simply not clone @apply
and @contents rules in StyleRule::Clone(), since the caller would
clone their fake parent rule itself. But as we've seen, cloning
the fake parent rule means that the selector is not replaced,
only reparented.)

Fallback blocks in @contents had the same issue; added a test
that is similar to the existing ones.

Bug: 406935599
Change-Id: I328a9a8026d82c23b7cc4543f066baf2f83a2e6b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7080794
Reviewed-by: Anders Hartvoll Ruud <andruud@chromium.org>
Commit-Queue: Steinar H Gunderson <sesse@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1534958}

--

wpt-commits: 26541fae4522e18baccbb2143d4a81959c716d1a
wpt-pr: 55639

Diffstat:
Atesting/web-platform/tests/css/css-mixins/contents-nested-declarations-fallback.html | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+), 0 deletions(-)

diff --git a/testing/web-platform/tests/css/css-mixins/contents-nested-declarations-fallback.html b/testing/web-platform/tests/css/css-mixins/contents-nested-declarations-fallback.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<html> + <head> + <title>CSS Mixins: @contents fallbacks become nested declarations</title> + <link rel="help" href="https://drafts.csswg.org/css-mixins-1/#contents-rule"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + + <style> + @mixin --m1(@contents) { + @contents { + color: green; + } + } + #e1::after { + content: "AFTER"; + @apply --m1; + } + </style> + <div id="e1"></div> + <script> + test(() => { + assert_equals(getComputedStyle(e1, '::after').color, 'rgb(0, 128, 0)'); + }, 'Can mix declarations into pseudo-elements via @contents'); + </script> + + + <style> + @mixin --m2(@contents) { + @contents { + color: red; + } + } + /* Should match <div id=e2> with the specificity of :where(#e2) (zero), + not with the specificity of :is(:where(#e2), #u1). */ + :where(#e2), #u1 { + @apply --m2; + } + :where(#e2) { + color: green; /* Wins. */ + } + </style> + <div id="e2"></div> + <script> + test(() => { + assert_equals(getComputedStyle(e2).color, 'rgb(0, 128, 0)'); + }, 'Nested declarations from @contents have top-level specificity behavior'); + </script> + + </body> +</html>