w3c / csswg-drafts

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

[css-scoping][css-cascade] :scope in shadowy prelude-less @scope matches nothing #9178

Closed andruud closed 2 days ago

andruud commented 1 year ago

A prelude-less @scope rule, e.g. @scope { ... } scopes the styles to the root of the containing tree if the owner element has no parent element. Example:

<div class=host>
  <template shadowrootmode=open>
    <style>
      @scope {
        /* :scope is the _shadow root_ */
      }
    </style>
  </template>
</div>

However, the shadow root can't be matched by :scope, because it's replaced by the shadow host for selector purposes. CSS Scoping 1:

For the purpose of Selectors, a shadow host also appears in its shadow tree, with the contents of the shadow tree treated as its children. (In other words, the shadow host is treated as replacing the shadow root node.)

This makes prelude-less @scope rules that exist directly beneath a shadow root mostly non-usable, since the inner selectors either have :scope in them somewhere, or get an implicit :scope prepended if not.

Not sure what the best fix is here ...

@tabatkins


I noticed that https://drafts.csswg.org/css-scoping-1/#shadow-gloss contradicts the other part of the spec:

The elements in a shadow tree are not descendants of the shadow host in general (including for the purposes of Selectors like the descendant combinator). However, the shadow tree, when it exists, is used in the construction of the flattened element tree, which CSS uses for all purposes after Selectors (including inheritance and box construction).

But since this doesn't match browser implementations, I assume it's a remnant of a previous spec version.


EDIT: Related: https://github.com/w3c/csswg-drafts/issues/7261#issuecomment-1676924402

andruud commented 1 year ago

Looking a bit closer at the specs, the most consistent thing might be to scope to the shadow host instead, though still match in the context of the shadow tree. So the scoping root is:

  1. The parent element of the owner node, if such an element exists
  2. Otherwise the shadow host, if the containing node tree is a shadow tree
  3. Otherwise the root of the containing node tree.

EDIT: And we'd need to pair that with https://github.com/w3c/csswg-drafts/issues/9025, otherwise :scope wouldn't match in this case either.

mirisuzanne commented 1 year ago

Yes, this solution makes sense to me. I would expect this scope to be the shadow host.

tabatkins commented 1 year ago

Yes, this seems pretty clearly to just be an oversight; if the stylesheet is a root element of the shadow tree, it should scope to the shadow host. (And :scope should be able to match the host element in this instance, like & can (search for "featureless").) This effectively makes the @scope a no-op, I think, since shadow styles are always scoped to the shadow anyway? But it should def work, rather than breaking.

I noticed that drafts.csswg.org/css-scoping-1/#shadow-gloss contradicts the other part of the spec: [snip] But since this doesn't match browser implementations, I assume it's a remnant of a previous spec version.

Hm, yeah, that might predate some of the featureless text, or maybe it's just being too general, and meant like "you can't do my-component > .in-the-shadow". I'll tweak the wording.

andruud commented 1 year ago

@astearns Proposed async resolution: @scope without <scope-start> scopes to the shadow host instead of the shadow root.

mirisuzanne commented 1 year ago

I don't think this would necessarily make it no-op, since @scope has a cascade-priority aspect. It would still impact how styles scoped to the host element interact with differently-scoped (or unscoped) styles in the same shadow dom.

Then again, the host is likely the default scope-root for un-scoped styles in shadow dom? So maybe that is still no-op?

andruud commented 1 year ago

Yeah, scoping-wise it's a no-op, but you'd get proximity applied between the subject and the host.

astearns commented 1 year ago

The CSSWG will automatically accept this resolution one week from now if no objections are raised here. Anyone can add an emoji to this comment to express support. If you do not support this resolution, please add a new comment.

Proposed Resolution: @scope without <scope-start> scopes to the shadow host instead of the shadow root.

astearns commented 1 year ago

RESOLVED: @scope without <scope-start> scopes to the shadow host instead of the shadow root.

jacobrask commented 9 months ago

Would this mean that you can author a stylesheet with :scope { }, include that stylesheet in either the root of the document, where it would match :root, or in a shadow root, where it would match :host? E.g. :is(:root, :host)

mirisuzanne commented 1 month ago

Would this mean that you can author a stylesheet with :scope { }, include that stylesheet in either the root of the document, where it would match :root, or in a shadow root, where it would match :host? E.g. :is(:root, :host)

@jacobrask I don't think this change would have any impact on how :scope works outside an @scope rule. This is specific to the scope created by @scope { … } in a shadow context. Used together… I think you would be able to create the effect?

If no @scope is defined, then :scope is treated as :root.