Csswg-drafts: [css-scoping-1] Specificity of :host, ::slotted, and :host-context doesn't seem to be defined?

Created on 28 Oct 2017  Â·  30Comments  Â·  Source: w3c/csswg-drafts

Which is important for cascade order.

I think there are similar issues with other selectors like :matches and such, but those seem to have been resolved (I think?) and now matches is defined as:

The specificity of a :matches() pseudo-class is replaced by the specificity of its selector list argument. (The full selector’s specificity is equivalent to expanding out all the combinations in full, without :matches().)

Anyway. Blink calculates it dynamically based on the biggest specificity of the compound selectors that match. But as far as I can tell there's no spec that defines this (sorry if I overlooked it).

There's a problem with calculating specificity dynamically, which is that doing it converts the specificity not in just a function of the selector, but also in a function of the element, which is unfortunate (specially since that affects style sharing, since you can't assume that proving you match all the same selectors you have the same style, you also need to prove you match the exact same sub-selectors).

Also, doing it in a way that is not inconsistent requires matching through all the selectors, instead of just one, which isn't particularly great, but also not a big deal I suppose.

css-scoping-1 shadow

Most helpful comment

Is that consistent with the currently specified behavior, or is this a Chrome-specific issue? It’s not clear to me from reading this thread what the decision was.

Yep, global styles are higher specificity than ::slotted()

Strictly, specificity is not the correct term here, it's about the cascading order and the cascade criteria added here:

https://drafts.csswg.org/css-scoping/#shadow-cascading

* {} and ::slotted() {} are never compared wrt specificity. If they are in the same shadow tree, they will never match the same elements, and if they are in different trees, the mentioned cascading order will take precedence over specificity and !important is the only way to make the other rule apply.

All 30 comments

Related: #1027 and #1170

My thought was just that it would have the standard specificity of a pseudo-class, and the contained selector wouldn't matter; that's why nothing else is specified, so it's covered by the Selectors default.

My thought was just that it would have the standard specificity of a pseudo-class, and the contained selector wouldn't matter; that's why nothing else is specified, so it's covered by the Selectors default.

That definitely works for me, but it's not what Blink implements, nor WebKit, cc @rniwa

@lilles do you have any opinion here?

Having the same specificity for the two rules below sounds unfortunate to me as I can imagine you'd want to do host overrides on classes/attributes on the host element, but I don't know to what degree it's used in practice:

:host(.pink) { color: pink }
:host { color: green }

I have seen pairs of :host {...} and :host{.pink} selectors for custom elements for theming apps, so I definitely think that's a case to consider.

Sure. This is only relevant if :host came later of :host(.pink), but I agree. I just want to get this spec'd. Presumably this can be resolved at the same time as #2271, and ideally with #2158, to avoid going back to eternal discussion like #1027.

The Working Group just discussed Specificity of :host, ::slotted, and :host-context doesn't seem to be defined?, and agreed to the following resolutions:

  • RESOLVED: Account for the specificity of the selector item and change the spec to say pseudo elements have same specificity of pseudo classes.

The full IRC log of that discussion
<dael> Topic: Specificity of :host, ::slotted, and :host-context doesn't seem to be defined?

<dael> github: https://github.com/w3c/csswg-drafts/issues/1915

<dael> emilio: Also if we want to keep it. Per spec this has spec of normal pseudo class, but there's a request to effect specificity of selector. I Impl the spec, but blink and webkit don't touch slotted....whatever we decide I'm fine.

<dael> TabAtkins: Should be consistent.

<dael> Rossen: Anyone have a preference?

<dael> rune_: I can't see why we would do it differently

<dael> dbaron: pseudo class vs pseudo element?

<dael> TabAtkins: No, a selector with a pseudo eleemnt add specififity.

<dael> dbaron: Match different things.

<dael> emilio: slotted does that.

<dael> dbaron: Add pseudo elements to specificity

<dael> TabAtkins: Like pseudo class when you can opt in to specila specificity

<dael> TabAtkins: Should change selectors to ask spec of pseudo element defaulting to none at all.

<dael> TabAtkins: Servo asked for it explicity with a similar example.

<TabAtkins> https://github.com/w3c/csswg-drafts/issues/2271

<dael> TabAtkins: I'm okay resolving that we make all these psuedo classes have the specificity of their elements and clarify pseudo elements can have the specificity.

<dael> dbaron: The reason pseudo elements don't have specificity if you have a :before nothing before cna matc so there's no point in changing specificity when you have exactly one. You can change pseudo elements to have a class level specificity.

<dael> TabAtkins: Seems fine.

<dael> Rossen: Prop:

<dael> emilio: Accounting for the specificty of the selector item and change the spec to say pseudo elements have same specificy of pseudo classes.

<dael> emilio: If you have slotted div the spec would be the class?

<dbaron> Interesting, CSS1 said pseudo-elements count as elements: https://www.w3.org/TR/CSS1/#cascading-order

<dael> TabAtkins: It would be the same.

<dael> emilio: Override it.

<dael> Rossen: Objections?

<dael> RESOLVED: Account for the specificity of the selector item and change the spec to say pseudo elements have same specificity of pseudo classes.

@lilles could we get this fixed in Blink? (I can submit a patch for it if you want).

We already got a report due to this behavior change in Gecko not behaving as Blink: https://bugzilla.mozilla.org/show_bug.cgi?id=1492071

Oh, the incompatibility is more subtle:

<!doctype html>
<div id="host"></div>
<script>
  host.attachShadow({ mode: "open" }).innerHTML = `
    <style>
      :host div[foo] {
        width: 100px;
        height: 100px;
        background: green;
      }
      div[foo] {
        background: red;
      }
    </style>
    <div foo></div>
  `;
</script>

Gecko shows green because it accounts the :host pseudo-element specificity plus the inner selector's specificity. Blink only seems to account for the inner selector's specificity only.

Is this expected? I'd expect the specificity to be added, and thus :host div be a more specific selector than div.

Turns out I had reported this to blink before... https://bugs.chromium.org/p/chromium/issues/detail?id=857415

FWIW, WebKit also renders it green.

Yeah, I gave up and fixed it on Blink, before it keeps biting us.

Regarding inner specificity, this is what's specified for :matches() and :not(). I could not find a spec'ed specificity for :host().

What's special about :host is that it's both functional and a single pseudo class, so not having the same specificity for the selectors below would be strange?

:host(*) div {}
:host div {}

I think it would though, wouldn't it? The specificity of * is zero.

In any case, I agree this is inconsistent with :matches and :not... I guess we need another discussion about this? I think in this case it makes sense to account for the :host specificity as well because :host is (unlike :not / :matches) selecting something, instead of just applying a union or negation operation to another selector.

So it makes more sense IMO to treat it as div:host would be treated than as :matches(div) is.

I'm fine with whatever includes the specificity of the sub-selectors, but I think we need to explicitly specify it in the spec. The non-functional :host follows from pseudo class specificity, but :host() doesn't, I think.

Yeah, that's fair. I'll add this back to the agenda to get that point clarified.

To be clear, the question is whether the specificity of :host(<selector>) should be the specificity of :host + the specificity of <selector>, or just the specificity of <selector>.

WebKit and Gecko do the first right now, Blink does the second. Testcase is:

<!doctype html>
<div id="host"></div>
<script>
  host.attachShadow({ mode: 'open' }).innerHTML = `
    <style>
      :host(*) div {
        width: 100px;
        height: 100px;
        background: green;
      }
      div {
        background: red;
      }
    </style>
    <div></div>
  `;
</script>

Yes. There is also ::slotted() which is a pseudo element. Is there a separate issue for that one?

I don't think so, guess we can discuss it here as well. Though I think ::slotted doesn't really matter because its specificity IIRC can't conflict with something that isn't another ::slotted selector.

That is true.

Whether we add a pseudo element specificity for ::slotted() doesn't matter, but the spec needs to say that the selector inside ::slotted() contributes to specificity.

@rniwa any opinion?

The CSS Working Group just discussed Specificity of :host, ::slotted, and :host-context doesn't seem to be defined?, and agreed to the following:

  • RESOLVED: Specificity of :host() is 1 pseudoclass + specificity of argument. (Not just specificity of argument.)

The full IRC log of that discussion
<dael> Topic: Specificity of :host, ::slotted, and :host-context doesn't seem to be defined?

<dael> github: https://github.com/w3c/csswg-drafts/issues/1915

<dael> emilio: We resolved on :host and ::slotted for specificity of inner selector. Should :host account for own specificity? There's incompat. Should :host* be same as :host-context?

<emilio> s/:host-context
/:host()

<dael> Rossen_: Does anyone have an opinion?

<fantasai> Quesiton was whether :host(
) should be the same as :host

<dael> emilio: Should specificity of functiona host selector be only the selector or selector+pseudo class for specificity. I think 2nd. That is consistent and increases specificity of the selector.

<fantasai> s/:host/:host/

<dael> TabAtkins: This is inconsistent with :matches, but host on its own just making a * argument should be 0 makes me lean toward your argument.

<dael> emilio: Works great

<dael> Rossen_: Other opinions?

<bkardell_> that's a new precident right?

<fantasai> fantasai: +1 from me

<dael> Rossen_: Objections?

<bkardell_> nothing else works like that I think?

<TabAtkins> bkardell_: Yeah, new precedent, kinda sorta.

<dael> bkardell_: It's the only decision that makes sense, but it's new. NOthing else works this way today.

<dael> emilio: Yeah, don't have many psuedo classes with selector arguments.

<bkardell_> +1

<bkardell_> it makes sense

<dael> Rossen_: Makes sense as a pattern going forward.

<TabAtkins> I mean, :matches()/:not() mean *absolutely nothing
absent their selector; there's nothing there otherwise. :host does affirmatively select an element all by itself, and then the selector narrows it down.

<TabAtkins> Proposed resolution: specificity of :host() is 1 pseudoclass + specificity of argument. (Not just specificity of argument.)

<dael> RESOLVED: Specificity of :host() is 1 pseudoclass + specificity of argument. (Not just specificity of argument.)

The CSS Working Group just discussed :host-context.

The full IRC log of that discussion
<emilio> Topic: :host-context

<emilio> github: https://github.com/w3c/csswg-drafts/issues/1915

<fantasai> emilio: When looking into this stuff, what I didn't implement which Apple also doesn't, is because it's really slow

<fantasai> emilio: Reason why it's really slow is that for every class change, you either need to store everything for the whoel docuemnt, or you need to look at every shadow root that has a host context selector, and go through all the shadow roots inside your subtree to check if they have a relevant host-context rule...

<fantasai> emilio: Handling DOM changes when host-context selectors are involved

<fantasai> emilio: Blink solves this by doing subtree restyles?

<fantasai> futhark: So in Blink we aggregate style info at the docuemnt scope for everything

<fantasai> futhark: we don't use subtree calc

<fantasai> futhark: We invalidate inside the shadow root

<fantasai> futhark: We use a flag for it

<fantasai> emilio: One of the nice things in our impl is that the style info inside the shadow root is contained

<fantasai> emilio: Blink wants to make such a change, too

<fantasai> emilio: So effectively it's similarly slow as /deep/

<fantasai> emilio: Since I haven't seen anything in Bugzilla requesting it

<fantasai> emilio: And WebKit also hasn't implemented

<fantasai> emilio: So would like to drop

<fantasai> futhark: I don't have the usage numbers...

<fantasai> TabAtkins: We should see if it's used

<fantasai> TabAtkins: It offers a useful functionality but if ppl aren't suing it, I"m fine with dropping it

<fantasai> Rossen: So, collect data, come back and based on this we can move forward

<fantasai> fantasai: So maybe flag for next F2F so we don't forget

<fantasai> Rossen: Hope to get to it before

In Chrome, using adoptedStyleSheets for what I would expect is a core use case — a common reset sheet used by various elements with shadow roots — I was surprised to find that if the common reset sheet has e.g. * { foo: bar }, then no ::slotted(...) rule can override foo for any element, except through use of !important.

Is that consistent with the currently specified behavior, or is this a Chrome-specific issue? It’s not clear to me from reading this thread what the decision was.

Is that consistent with the currently specified behavior, or is this a Chrome-specific issue? It’s not clear to me from reading this thread what the decision was.

Yep, global styles are higher specificity than ::slotted()

Is that consistent with the currently specified behavior, or is this a Chrome-specific issue? It’s not clear to me from reading this thread what the decision was.

Yep, global styles are higher specificity than ::slotted()

Strictly, specificity is not the correct term here, it's about the cascading order and the cascade criteria added here:

https://drafts.csswg.org/css-scoping/#shadow-cascading

* {} and ::slotted() {} are never compared wrt specificity. If they are in the same shadow tree, they will never match the same elements, and if they are in different trees, the mentioned cascading order will take precedence over specificity and !important is the only way to make the other rule apply.

Yep, I was talking about scenarios where the slot content belongs to one shadow (with the reset adopted) and the slot (and the use of ::slotted()) belong to another (which also has the reset adopted, though that doesn’t contribute to the effect).

This behavior seems to mean a lot of what would normally go in a generic, shared reset sheet isn’t viable with custom elements unless one can say for sure that they don’t and will never need to (for example) set a border color on a slotted input element or a font-weight on a slotted h2.

However I see now that this isn’t the same as the issue this thread concerns. Thanks for clearing it up.

See also: #3995

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fantasai picture fantasai  Â·  3Comments

nigelmegitt picture nigelmegitt  Â·  4Comments

svgeesus picture svgeesus  Â·  3Comments

Meteor0id picture Meteor0id  Â·  3Comments

CJKu picture CJKu  Â·  4Comments