w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.35k stars 641 forks source link

[css-cascade-6] Specificity of Implicitly-Added `:scope` in Scoped Rules #10196

Open dshin-moz opened 2 months ago

dshin-moz commented 2 months ago

Current WPT test for scoped styles' specificity seems to assume that an unscoped rule has the same specificity as the identically written out scoped rule. However, the spec's example seems to imply that :scope <descendant-combinator> is implicitly added, and that :scope's specificity is equal to that of other pseudo-classes (i.e. class-like +1).

WebKit and Blink are shipping the behaviour matching the WPT.

This leads to behaviours where functionally-identical selectors with explicitly-added :scope selector wins despite the ordering. In the example below, WebKit and Chrome both show .foo being purple:

<!doctype html>
<style>
.foo { color: green }
@scope (:root) {
  :scope .foo { color: purple }
  .foo { color: blue }
}
</style>
<div class="foo">Color?</div>

cc: @emilio, @mdubet, @andruud

dshin-moz commented 2 months ago

On the other hand, a parallel could potentially be drawn to:

<!doctype html>
<style>
:root .foo {
    color: red;
}
.foo {
    color: lime;
}
</style>
<div class="foo"></div>

Where .foo is red.

emilio commented 2 months ago

Yeah this is also important because depending on what behavior we decide, we might need to revert the resolution in https://github.com/w3c/csswg-drafts/issues/9621 (or change it so that we serialize to :where(:scope), but that's a bit weird...

I think it's very weird that:

> .foo

Has different specificity than:

:scope > .foo

in this case...

cc @mirisuzanne @tabatkins

andruud commented 2 months ago

Right, right.

Well, it looks like the current behavior is intentional at least, as @mirisuzanne and I apparently briefly discussed this June 19th, 2023 (chat history). We both concluded (from the discussion in https://github.com/w3c/csswg-drafts/issues/8500) that any implicit :scope should not contribute to specificity.

I also noted at the time:

"That means @scope (.a) { .b { ... } } and @scope (.a) { :scope .b { ... } } are different rules, and we can not serialize one as the other."

So yeah, oopsie. :-)

With both Chrome and Webkit having shipped now, a change here could be too late already. I'll try to investigate that if we indeed want to change.

mirisuzanne commented 2 months ago

It was intentional, and designed to match e.g. :root. The goal is for the scope rule itself not to impact specificity, while both :scope and & selectors can reference it in different ways, and bring their own specificity implications to bear. I don't like either of the alternatives:

dshin-moz commented 2 months ago

Should :scope inserted by & + Implicit scope root add to specificity? Blink & WebKit diverge on it:

<!DOCTYPE html>
<div>
  <style>
    @scope {
      & .foo {
        color: blue;
      }   
    }   
    :scope .foo {
      color: red;
    }   
  </style>
  <div class="foo">What Color?</div>
</div>

Shows red on Chrome dev 125.0.6382.3 and blue on Safari Technology Preview 17.4.

andruud commented 2 months ago

I would expect the & to have its regular "as if :is()"-specificity behavior, which in this case means the specificity of :is(:scope).

..... which means Chrome doesn't do what I expect.

mirisuzanne commented 2 months ago

I would expect the & to have its regular "as if :is()"-specificity behavior…

That's what we intended, yes. See https://www.w3.org/TR/css-cascade-6/#example-66991251 for example.

emilio commented 1 month ago

@astearns this one was also unexpectedly closed I believe.

css-meeting-bot commented 3 weeks ago

The CSS Working Group just discussed [css-cascade-6] Specificity of Implicitly-Added `:scope` in Scoped Rules, and agreed to the following:

The full IRC log of that discussion <jarhar> emilio: the at scope rules insert an explicit :scope selector when you dont have it, much like nesting
<jarhar> emilio: its weird but it seems intentional as per the discussion in the issue that :scope doesnt contribute to specificity
<jarhar> emilio: its weird but it also seems like if you do the ampersand then webkit and blink have different behavior which is weird
<jarhar> emilio: i think i would expect webkits behavior there
<jarhar> emilio: thats basically a question
<jarhar> miriam: webkits behavior in which?
<jarhar> emilio: in the ampersand case.
<TabAtkins> https://github.com/w3c/csswg-drafts/issues/10196#issuecomment-2065371007
<jarhar> TabAtkins: the example with which behavior chrome is and which behavior webkit is
<jarhar> emilio: basically ampersand inside scope rules means :scope and right now chrome seems to ignore the specificity of scope ? you have explicitly written the ampersand
<jarhar> miriam: ampsersand isnt :scope exactly, it refers to the scope root element, and the ampersand refers to the seelector in the scope start. consistent with how those two selectors have worked in other places. ampersand refers to a selector and ? refers to an element?
<jarhar> TabAtkins: the ampersand the referring selector is empty
<jarhar> emilio: ampersand and :scope are effectively the same right?
<jarhar> TabAtkins: behavior is the same except for this case because ones a pseudo class and one is a parent selector. how academic this is is a good question
<jarhar> kizu: you could use it not to just target the scope itself ?
<jarhar> kizu: i managed to do something with it but i dont remember exactly
<jarhar> miriam: what does ampersand do on its own?
<jarhar> TabAtkins: its supposed to match the same element in the parent rule?
<jarhar> miriam: what about root level of a stylesheet
<jarhar> matthieud: the root level - it should be :root and it gets one specificity of the pseudo class
<jarhar> TabAtkins: that is not specified in the spec. the specificity line explicitly refers to the line ? selector list, doesnt say what specificity is
<jarhar> TabAtkins: thats just what browsers are doing on their own right now
<jarhar> miriam: im getting zero specificity in chromium
<jarhar> emilio: another case where gecko and chromium do zero specificity but webkit does use specificity
<emilio> data:text/html,<style>& p { color: green } p { color: blue }</style><p>ABC
<emilio> green in webkit, blue in gecko / blink
<jarhar> astearns: so we need to fix both things
<jarhar> TabAtkins: we can decide on which behavior we like better, but ...
<jarhar> TabAtkins: zero specificity in chrome
<jarhar> miriam: that feels a little strange to me but i dont have an argument why the other would feel less strange. i dont think of it as a zero specificity selector
<jarhar> TabAtkins: doesnt have a specificity to refer to, it just matches the same thing as scope
<jarhar> TabAtkins: not defined in terms of that selector existing having specificity
<jarhar> miriam: if we have it at zero specificity, then we specify everything on ampersand instead of root
<jarhar> miriam: people will say where root to get zero specificity
<jarhar> TabAtkins: the only difference is if ? target root to ? some of the properties with html as a selector. that would lose if it had a pseuco class specificity. in all cases it wouldnt matter
<jarhar> TabAtkins: they can already do it with where
<jarhar> miriam: maybe the scope case is a better one to look at. is it surprising you write styles scoped styles in an embedded stylesheet you use the ampersand, you get zero specificity
<jarhar> miriam: thats where it would become an issue more often because someone woudl be targeting that div
<jarhar> miriam: if you use solely an ampersand it becomes zero outside stylesheets have ? specificitity, scope comes into play, that becomes riskky
<jarhar> TabAtkins: giving it a specificyt of zero pseudo class is easy to override, zero is easier but not by a huge amount
<jarhar> astearns: im hearing lots of strong opinions, how are we going to decide?
<jarhar> TabAtkins: technically chrome and firefox agree and spec. as 2/3 aint bad
<jarhar> emilio: yeah not strong opinion. just some of it - having a ? context selector doesn't increase specificity
<jarhar> emilio: yeah i guess its ok
<emilio> s/? context selector/more complex selector which/
<jarhar> matthieud: i dont have a strtong opionin either, but when you ? a selector more when you add some part to a selector :root i guess itm kaes sense when you are more explicit that you get something from it
<jarhar> matthieud: all this is really edge case i dont know if anyone will check this
<jarhar> miriam: so thats an argument for zero?
<jarhar> astearns: and i believe that the issue about wpt, do they test for zero or something else?
<jarhar> emilio: i think that they test for zero specificity but thats for visit scope not ampersand
<jarhar> astearns: so we could resolve on zeroing both a bare ampersand and scope
<jarhar> astearns: since nobody has a strong opinion about it and they should probably match and we have tests that browsers pass, but with the option of reopening it if we find a use case for doing non-zero at some point in the future
<jarhar> TabAtkins: ultimately authors can do that - they can just put :scope in the @scope selector and that will do it
<jarhar> astearns: and if we find people doing that maybe we can say hey maybe we should be doing hta tautomatically
<jarhar> fantasai: summary of what we're talking about?
<jarhar> TabAtkins: do you want to the proposed resolution?
<jarhar> fantasai: we talked about :scope and ampersand, are we talking about both fo tth eresoultion?
<emilio> explicit :scope is fine, things to discuss are "implicit" scope and bare ampersand
<jarhar> TabAtkins: no, ampersand doesn't have a selector for its parent, when it implicitly selects nothing at all its specificity is zero
<jarhar> astearns: which is tested but not specified
<jarhar> TabAtkins: it is technically specified, not explicitly. it should be more explicit
<jarhar> fantasai: and whats an example of a case where ? selector
<jarhar> TabAtkins: its at the root level or inside a scope which doesnt have a scope start
<jarhar> fantasai: for example inside a style element
<jarhar> TabAtkins:
<jarhar> TabAtkins: right
<jarhar> astearns: there would also be a resolution that the - that scope would match this as well
<jarhar> fantasai: :scope would be zero always or only when its implicit
<jarhar> fantasai: if i have an at scope and a id selector, then when i write foo inside there, im implicitly adding :scope and that adds a pseudo class to it
<jarhar> TabAtkins: no, that was a decision we made on purpose. you just get the selector that you wrote plus the magical scoping behavior
<jarhar> fantasai: so it doesn't inherit the scope start specificity unless you write :scope explicitly
<jarhar> fantasai: so why dont we do that in the case ??
<jarhar> matthieud: its not true for the ? selector
<jarhar> TabAtkins: for general nesting, its expected - if nesting didnt ? the specificity then everything would break. scoping gives us some ? for nesting you assume your rules are more specific
<jarhar> TabAtkins: otherwise &hover would almost never match because the parent rule would override it
<jarhar> fantasai: i was trying to figure out why it would suddenly gain some specificity
<jarhar> miriam: nobody is proposing that
<jarhar> miriam: its not clearly defined in either place, lets make sure its the same
<emilio> s/&hover/&:hover/
<jarhar> TabAtkins: explicitly state in specs that when an ampersand doesn't actually have a parent rule to draw from then its specificity is zero
<jarhar> TabAtkins: explicitly defined in the spec
<jarhar> astearns: any objections?
<jarhar> astearns: do we also have to make a scope resolution?
<jarhar> TabAtkins: neither ampersand nor explicit scope - thats already in the spec
<jarhar> astearns: i think we are done
<astearns> RESOLVED: explicitly state in specs that when an ampersand doesn't actually have a parent rule to draw from then its specificity is zero
mdubet commented 3 weeks ago

So as @emilio said before, we might need to revert "always serialize implicit :scope" https://github.com/w3c/csswg-drafts/issues/9621 then ?

mirisuzanne commented 3 weeks ago

@mdubet I added agenda+ to #9621

emilio commented 3 weeks ago

Ah, yes of course.