Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[css-contain] CQ vs shadow boundaries #5984

Closed
tabatkins opened this issue Feb 12, 2021 · 51 comments
Closed

[css-contain] CQ vs shadow boundaries #5984

tabatkins opened this issue Feb 12, 2021 · 51 comments

Comments

@tabatkins
Copy link
Member

Can a CQ see past shadow boundaries? If so, exactly how?

If the CQ is done via selectors, the answer is obvious - you can only CQ against elements you can see via selectors. So from within a shadow, the highest ancestor you can CQ against is your host element, no higher.

If CQ is done via an at-rule, the answer is not quite as clear; theoretically, we can walk the box tree (flat tree) to find ancestors. However, we do not want a ::part() to be able to see an ancestor within the targeted shadow (it violates shadow encapsulation). And letting a shadow see ancestors outside in the light DOM gives us similar power to :host-context(), which already makes browser vendors unhappy, so maybe we don't want that either. And that's all the cases, so presumably we don't want it to work in either case.


As a related issue, we'll need to specifically define that the CQ pseudo-class matches on host elements, since they're featureless and don't match anything besides :host by default otherwise.

@SebastianZ
Copy link
Contributor

And with CQ you probably mean container queries, right?

Sebastian

@svgeesus
Copy link
Contributor

svgeesus commented Feb 12, 2021

probably

Yes. (Cadit quaestio)

@mirisuzanne
Copy link
Contributor

To clarify, neither approach uses a selector to target the container. Both of these would select a .card for conditional styling, and the container remains implicit/contextual:

@container (width > 40em) { .card { ... } }
.card:container(width > 40em) { ... }

Maybe they still have different shadow-DOM implications under the hood?

@lilles
Copy link
Member

lilles commented Jun 8, 2021

For the implicit case, it follows from the layout tree, but what if you have a web component where you want to have your styles depend on the closest container and there is no such container inside the component. Do you really want to query a container outside of your component? Would it be natural that the shadow host is the outermost container to query and that the CQ fallback happens on the shadow host level?

For the container-name proposal it's a question of name clashes. If you have a @container mycontainer (inline-size >= 200px), you probably don't want to match an arbitrary mycontainer outside of your web component when you don't have a mycontainer in your ancestor chain inside the component?

@mirisuzanne
Copy link
Contributor

I think I would often want the ability to query the host page - this feature is all about access to contextual information - but that the host element might suffice in most situations (especially for dimensional queries), if we need that limitation.

If I was building a component library with shadow-DOM, I would want content-based components to query external layout containers - and I would likely try to do that with containers named by type (e.g. @container component (…) and @container layout (…)).

@bkardell
Copy link
Contributor

I think a bunch of people are playing with container queries, have we collected any feedback on specifically this from others? It seems like it would be very valuable... Has someone asked for either?

@lilles
Copy link
Member

lilles commented Nov 1, 2021

The current implementation in Blink uses the flat tree for looking up containers. That was the most straightforward way to implement it. I agree that ::part() and pseudo elements like ::placeholder are problematic if they match containers inside the shadow tree.

This issue should probably be considered together with issue #6711.

@lilles
Copy link
Member

lilles commented Nov 1, 2021

I think this is an interesting case. Two selectors for the same element in different scopes querying the closest container for inline-size:

<!doctype html>
<style>
  host-element, host-child {
    container-type: inline-size;
  }
  @container (width <= 300px) {
    host-child {}
  }
</style>
<host-element>
  <template shadowroot="open">
    <style>
      inner-container { container-type: inline-size; }
      @container (width <= 200px) {
        ::slotted(host-child) {}
      }
    </style>
    <inner-container>
      <slot></slot>
    </inner-container>
  </template>
  <host-child>Light content</host-child>
</host-element>

Do we want host-child to have host-element as its container when the query/selector is in the host-child tree, and have inner-container as the container for the ::slotted rule?

If we just look for the container in the element's own tree, we would not be able to style slotted elements inside the shadow tree. Is that acceptable?

@lilles
Copy link
Member

lilles commented Nov 8, 2021

With container-name it gets even more interesting.

Should container-name be tree-scoped names?

In the example below, using tree-scoped names would mean that the container rules for host-child below would match differerent containers based on the scope of the rule:

<!doctype html>
<style>
  host-element {
    container-type: inline-size;
    container-name: a;
  }
  @container a (width <= 300px) {
    host-child {}
  }
</style>
<host-element>
  <template shadowroot="open">
    <style>
      inner-container {
        container-type: inline-size;
        container-name: a;
      }
      @container a (width <= 200px) {
        ::slotted(host-child) {}
      }
    </style>
    <inner-container>
      <slot></slot>
    </inner-container>
  </template>
  <host-child>Light content</host-child>
</host-element>

@tabatkins
Copy link
Member Author

Container names don't need to be tree-scoped; they're not globally registered/visible, but are instead solely looked up via an ancestor search.

We could potentially still censor names past a shadow boundary if we wanted, but that would be an independent decision; it's not forced by this issue.

@mirisuzanne
Copy link
Contributor

I like the idea of allowing slotted elements to be styled based on the slot location in the shadow tree. It seems likely to me that both styles from inside and outside the shadow boundary are attempting to determine actual space available, and in most cases the shadow parent will give the better answer - but that may not be true in all cases.

@lilles
Copy link
Member

lilles commented Nov 11, 2021

@tabatkins By ancestor search, do you mean looking up ancestors in the light tree without walking past shadow roots?

If so, it means host-child will never match any containers inside host's shadow tree:

<style>
  @container name (min-width: 100px) { host-child {} }
  @container (min-width: 100px) { host-child {} }
</style>
<host-element><host-child></host-child></host-element>

What about slotted rules inside a shadow tree:

<style>
  @container name (min-width: 100px) { ::slotted(div) {} }
  @container (min-width: 100px) { ::slotted(div) {} }
</style>
<div>
  <slot></slot>
</div>

Do we start walking from the slot instead of the slotted element to find the container so that it's possible to match containers inside the shadow tree?

@lilles
Copy link
Member

lilles commented Nov 11, 2021

The current spec covers first case if "descendants" are light tree descendants.

If ::slotted rules should match containers walking from the slot instead of the slotted element being matched, the spec needs to say something about that.

@andruud
Copy link
Member

andruud commented Nov 11, 2021

However, we do not want a ::part() to be able to see an ancestor within the targeted shadow (it violates shadow encapsulation).

Possibly stupid question: why is this worse than e.g. host::part(thing) { background-color: var(--defined-in-shadow-tree); }?

@tabatkins
Copy link
Member Author

By ancestor search, do you mean looking up ancestors in the light tree without walking past shadow roots?

No, I was referring to jumping up shadow roots as well; CSS almost always operates on the flat tree and doesn't care about shadow boundaries, particularly when layout is involved.

That said, while shadows being able to see ancestor light containers seems reasonable, i agree that parts being able to see shadow containers isn't good.

Possibly stupid question: why is this worse than e.g. host::part(thing) { background-color: var(--defined-in-shadow-tree); }?

Name collisions among variables, while possible, aren't too likely in general. On the other hand, the common use-case for CQs doesn't even refer to the container by name, so you'll often end up accidentally being intercepted by the shadow's container, when you intended to be querying off of some light-dom container that's an ancestor to the shadow.

@andruud
Copy link
Member

andruud commented Nov 11, 2021

OK, so what about specifying that container selection can't see containers in tree scopes below the scope where the @container rule is defined? Basically always flat tree, but skips containers that are below the "originating tree scope".

@lilles
Copy link
Member

lilles commented Nov 11, 2021

OK, so what about specifying that container selection can't see containers in tree scopes below the scope where the @container rule is defined? Basically always flat tree, but skips containers that are below the "originating tree scope".

That's per definition shadow-including descendants/ancestors

@tabatkins
Copy link
Member Author

That's per definition shadow-including descendants/ancestors

No, that's basically flat tree - a ::part(), selecting an element in the shadow, would see shadow ancestors before it escapes back into the light tree and starts seeing light ancestors.

Anders is talking about something more akin to the tree-scoped names rule, where the tree scope of the @container rule affects what container elements are visible to it. A @container { ::part() {...} }, since it appears in the light tree's styles, will skip container elements automatically until it's back in the tree scope it was defined in, at which point it can see all ancestor scopes as well.

I think that makes sense?

@lilles
Copy link
Member

lilles commented Nov 15, 2021

I meant that the part about: "... but skips containers that are below the "originating tree scope" ..." is about shadow-including descendants/ancestors.

@andruud
Copy link
Member

andruud commented Nov 16, 2021

(After decoding cryptic DOM spec language):

Shadow-including ancestors is almost what we're talking about, but we need to skip the "originating tree scope" based on where the container rule comes from, not based on where the element is in the tree. Otherwise ::slotted will not work as expected.

@lilles
Copy link
Member

lilles commented Nov 27, 2021

After an off-line discussion with @andruud we think the discussed behavior could be specified as:

In https://drafts.csswg.org/css-contain-3/#query-container, change:

"... styling its descendants ..."

to:

"... styling its shadow-including descendants ..."

And add:

"For selectors with pseudo elements, query containers can be established in the shadow-including inclusive ancestors of the originating element."

Resolving on this could also resolve issue #6711.

  1. ::slotted() selectors can query containers inside the shadow tree (including the slot itself)
  2. ::part() selectors can query the host, but not internal query containers inside the shadow tree
  3. ::placeholder and ::file-selector-button can query the input element, but not any internals if the input element is implemented using Shadow DOM
  4. ::before, ::after, ::marker, and ::backdrop queries its originating element
  5. ::first-letter and ::first-line queries its originating element, even if the fictional elements may be pushed down past other elements establishing query containers for the purpose of inheritance and rendering.
  6. Multiple pseudo elements do not allow pseudo elements to be query containers for other pseudo elements. E.g., the host, but not the part() can be the query container for ::before in: host::part()::before. Similarly, the ::before element can not be the query container for the ::marker in: div::before::marker.

I have uploaded a Chromium CL with supporting tests for the Shadow DOM cases here:

https://chromium-review.googlesource.com/c/chromium/src/+/3304155

@andruud
Copy link
Member

andruud commented Nov 29, 2021

👍

But regarding:

"For selectors with pseudo elements, query containers can be established in the shadow-including inclusive ancestors of the originating element."

we need to describe how to look for containers without mentioning the "kind" of selector.

For e.g. div::before::marker, is there a definition which refers to div from the perspective of both ::before and ::marker? If not, call this "originating element root" or something. Then specify that we look for containers in the shadow-including inclusive ancestors starting from the "originating element root".

@lilles
Copy link
Member

lilles commented Nov 29, 2021

👍

But regarding:

"For selectors with pseudo elements, query containers can be established in the shadow-including inclusive ancestors of the originating element."

we need to describe how to look for containers without mentioning the "kind" of selector.

For e.g. div::before::marker, is there a definition which refers to div from the perspective of both ::before and ::marker? If not, call this "originating element root" or something. Then specify that we look for containers in the shadow-including ancestors starting from the "originating element root".

I don't think it looks like the spec is written with multiple pseudo elements in mind:

https://drafts.csswg.org/selectors-4/#pseudo-element-attachment

As you say, we either need to clarify that div is the originating element for ::marker in div::before::marker, or introduce something like "root originating element", yes.

@lilles
Copy link
Member

lilles commented Jan 18, 2022

The implementation behind a flag for container queries in Blink now implements the behavior proposed in #5984 (comment)

Tentative tests landed here: https://wpt.live/css/css-contain/container-queries/container-for-shadow-dom.tentative.html

See #6711 (comment) for tests for pseudo elements.

@lilles lilles added the Agenda+ label Jan 18, 2022
@tabatkins
Copy link
Member Author

Excellent! And I agree that the proposed behavior (use shadow-inclusive descendants/ancestors, with pseudos jumping straight to their originating element) sounds good; it uses only information available at selector-evaluation time, and respects tree scopes reasonably.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-contain] CQ vs shadow boundaries, and agreed to the following:

  • RESOLVED: Accept proposal to in all cases use originating element
The full IRC log of that discussion <dael> Topic: [css-contain] CQ vs shadow boundaries
<dael> github: https://github.com//issues/5984
<dael> RESOLVED: Accept proposal to in all cases use originating element

@emilio emilio added the Agenda+ label Feb 28, 2024
@mirisuzanne
Copy link
Contributor

I think this is true for basically all queries: the goal is to measure your nearest context. But style queries and query units in particular expose the ways a container query is more akin to 'inheritance' than selection. The shadow DOM has always provided context for slotted elements, and containers are explicitly designed for providing that sort of context. If we need to make container names private to a shadow tree, or allow container names to be marked as private, that feels like a separate issue to me.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-contain] CQ vs shadow boundaries, and agreed to the following:

  • RESOLVED: Container queries and units use the flat tree
The full IRC log of that discussion <flackr> emilio: the current behavior of cq and cq units is weird for authors, especially the units. For stuff like style queries, current behavior is using the regular dom which kind of makes sense. But for style queries you really want the flat tree
<miriam> q+
<TabAtkins> q+
<flackr> emilio: we opened this as a result of another issue filed on the units which are right now defined to behave like the queries. The original resolution isn't the best for authors, I think it's a bit weird. miriam thinks so as well and maybe this is worth reverting.
<flackr> emilio: firefox implements what i'm proposing and we haven't run into any issues so i think the compat risk is small
<astearns> ack miriam
<flackr> miriam: I've felt strongly from the start that we went the wrong way. CQ are very much akin to inheritance in a lot of ways and should be treated in that way rather than as selectors in terms of relation to shadow dom. They're all about context and shadow elements and slotting creates context which should be able to be accessed
<astearns> ack TabAtkins
<kizu> q+
<flackr> TabAtkins: ultimately I agree [missed]
<astearns> ack kizu
<flackr> kizu: may be worth mentioning named CQ where we cannot assess a named entity from outside of the shadow dom
<flackr> astearns: any other comments / concerns?
<flackr> emilio: Would be interested in hearing rune's thoughts, can weigh in on issue
<flackr> Proposed resolution: Container queries and units use the flat tree
<flackr> RESOLVED: Container queries and units use the flat tree
<emilio> scribenick: emilio

moz-wptsync-bot pushed a commit to web-platform-tests/wpt that referenced this issue Jun 3, 2024
As per CSSWG resolution in:

  w3c/csswg-drafts#5984 (comment)

Differential Revision: https://phabricator.services.mozilla.com/D212412

bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=1790886
gecko-commit: bc97304e5b3d4d2110fd8f2120ba9914406a116a
gecko-reviewers: jwatt
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this issue Jun 4, 2024
moz-wptsync-bot pushed a commit to web-platform-tests/wpt that referenced this issue Jun 4, 2024
As per CSSWG resolution in:

  w3c/csswg-drafts#5984 (comment)

Differential Revision: https://phabricator.services.mozilla.com/D212412

bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=1790886
gecko-commit: bc97304e5b3d4d2110fd8f2120ba9914406a116a
gecko-reviewers: jwatt
ErichDonGubler pushed a commit to erichdongubler-mozilla/firefox that referenced this issue Jun 5, 2024
i3roly pushed a commit to i3roly/firefox-dynasty that referenced this issue Jun 14, 2024
gordonbrander added a commit to commontoolsinc/labs that referenced this issue Jun 20, 2024
...Unfortunately they do not work across slot boundaries. The container
unit is sized by the light dom container, not the shadow dom container.

See w3c/csswg-drafts#5984

Solution: use percentage units instead. Oddly enough, these do work
across shadow boundaries.
gordonbrander added a commit to commontoolsinc/labs that referenced this issue Jun 20, 2024
* Add exports

* Stop using container units
...Unfortunately they do not work across slot boundaries. The container
unit is sized by the light dom container, not the shadow dom container.

See w3c/csswg-drafts#5984

Solution: use percentage units instead. Oddly enough, these do work
across shadow boundaries.

* Update all classes to use Common prefix
@lilles
Copy link
Member

lilles commented Aug 5, 2024

I am trying to write up a PR for the spec change, but not entirely sure how to express the pseudo element case.

We cannot keep the "ultimately originating element" as the starting candidate as that would make us incorrectly skip flat tree ancestors for ::slotted() and ::part(). But I think we should keep starting from the originating element for ::first-line and ::first-letter, and not consider any in-between containers if the originating element is not the inner-most container.

I was thinking about keeping the current wording for pseudo elements which are not part-like pseudo-elements, but what about pseudo elements for UA controls like which represent ::file-selector-button? We should not expose any non-named containers inside that implementation if it uses a UA shadow DOM?

Perhaps it works with just explicitly call out ::slotted() and ::part() and change "ultimately originating element" to "originating element" for the other cases?

lilles pushed a commit to lilles/csswg-drafts that referenced this issue Aug 6, 2024
lilles added a commit that referenced this issue Aug 8, 2024
@lilles
Copy link
Member

lilles commented Aug 15, 2024

Tests for size and style queries have also been updated. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

No branches or pull requests