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-scoping] Consider aligning ::slotted() for fallback content with implementations #5482

Open
lilles opened this issue Aug 28, 2020 · 7 comments

Comments

@lilles
Copy link
Member

lilles commented Aug 28, 2020

The scoping spec states that:

"The ::slotted() pseudo-element represents the elements assigned, after flattening"

According to the flattening algorithm in the HTML spec, that also includes the fallback children of a slot if that slot has no assigned nodes. This behavior was agreed on in WebComponents issue 631 and issues were reported for Blink and WebKit, but have not been fixed.

The current implementations walk the assignedSlot chain and apply ::slotted rules from each slot's scope, assignedSlot being null for fallback elements.

This is an example of how this works (in all browsers per current spec) with nested slot assignment:

<!doctype html>
<div id="host"><span class="a">Orange with blue border</span></div>
<script>
  const root = host.attachShadow({mode:"open"});
  root.innerHTML = `
    <style>
      ::slotted(.a) { border: 2px solid blue }
    </style>
    <div id="host">
      <slot></slot>
    </div>
  `;
  const inner_root = root.querySelector("#host").attachShadow({mode:"open"});
  inner_root.innerHTML = `
    <style>
      ::slotted(.a) { color: orange; }
    </style>
    <slot></slot>
  `;
</script>

Per current spec, moving the span into fallback content for the slot should also result in orange text and blue border, but this is unstyled in all browsers):

<!doctype html>
<div id="host"></div>
<script>
  const root = host.attachShadow({mode:"open"});
  root.innerHTML = `
    <style>
      ::slotted(.a) { border: 2px solid blue }
    </style>
    <div id="host">
      <slot><span class="a">Orange with blue border</span></slot>
    </div>
  `;
  const inner_root = root.querySelector("#host").attachShadow({mode:"open"});
  inner_root.innerHTML = `
    <style>
      ::slotted(.a) { color: orange; }
    </style>
    <slot></slot>
  `;
</script>

If the author wants to style a certain type of element the same whether it is assigned, or fallback, it can be accomplished with the current implementations adding an extra selector like this:

::slotted(.a), slot > .a

However, that only works in the scope where the fallback is. For the example above, you can do this for the tree with the border rule, but not when that slot is assigned to the slot of the inner root.

I have done an implementation which matches the current spec in Blink, and it is pretty straightforward. However, compat is a big concern here. The impact of changing the implementation would be hard to use-count/quantify. I've been in contact with engineers at Google working on Web Components and Polymer who thinks that changing this is too risky compat-wise and would prefer a spec change.

This surfaced in a bug report from Salesforce which found inconsistencies between implementations, but it turns out that is about ::slotted(slot) matching nested slots in WebKit, while Blink and Firefox do not match those:

<!doctype html>
<div id="host"><span class="a" slot="inner">Orange with blue border</span></div>
<script>
  const root = host.attachShadow({mode:"open"});
  root.innerHTML = `
    <style>
      ::slotted(.a) { border: 2px solid blue }
    </style>
    <div id="host">
      <slot>
        <slot name="inner"></slot>
      </slot>
    </div>
  `;
  const inner_root = root.querySelector("#host").attachShadow({mode:"open"});
  inner_root.innerHTML = `
    <style>
      ::slotted(slot) { color: orange; }
    </style>
    <slot></slot>
  `;
</script>

@emilio @rniwa @gregwhitworth

@gregwhitworth
Copy link
Contributor

gregwhitworth commented Aug 31, 2020

The impact of changing the implementation would be hard to use-count/quantify.

Can we start at least with utilization of ::slotted() and it being truly evoked via usecounter? I agree that mapping it default content fallback will be a bit more work.

Beyond that I think we shouldn't prolong checking on the compat because the compat will simply continue to grow. If we do discover major compat implications (a lot of unique utilization from different sources, not solely traffic) we can do a measured release with a lot of communication around it. If it seems the issue has proliferated beyond where outreach may be of help (eg: it isn't 5-10 major web teams/libs) we can then discuss how we want to handle compat.

TLDR; I'd like a slotted() usecounter and to ship through Canary/Dev channels to check compat and circle back with the CSSWG for informed resolution

@lilles
Copy link
Member Author

lilles commented Aug 31, 2020

@rniwa
Copy link

rniwa commented Sep 1, 2020

FWIW, WebKit bug about this is https://bugs.webkit.org/show_bug.cgi?id=169948 but we have no immediate plan to take actions on this.

@gregwhitworth
Copy link
Contributor

Thanks @lilles much appreciated. So comparing ::slotted() with something like focus-visible which is relatively new:

focus-visible 2.18%
Screen Shot 2020-09-01 at 5 05 56 PM

::slotted() .19%
Screen Shot 2020-09-01 at 5 07 28 PM

While ::slotted() is used, as @lilles noted, this doesn't account for if it is default content or not and my hunch is that most don't target default content. I think, if feasible, is for Chrome (since they have a patch ready) to ship to canary and dev and see if bug reports come in from the change. If we don't, then potential compat concerns will continue to rise.

I'd love to see us at the very least try and determine if the compat risk is large, which currently being well under 1%, while not inconsequential I do think we're at a point this is a worthwhile change. If we don't then we'll need to add some type of switch to allow for default content to achieve the behavior as currently specified.

@tabatkins
Copy link
Member

I'll note that having ::slotted() target fallback contents was not an explicit goal when I was writing the spec text; I just wrote the simplest thing that did the job using the tree constructs I had available. I don't think I have an opinion on which way we go with this; the argument that you'll probably want to style fallback differently from slotted content sounds reasonable to me, tho (and thus having ::slotted() not match would be better).

Looking into the details a bit more, it looks like the spec might actually be wrong, and not match any implementations anyway! In particular, I'm using "find slottables" to produce the flattened tree (which is correct), and that means the inner shadow root in Rune's original example will have a <slot> (from the outer shadow root) as its direct child in the flat tree; the span.a element will be a grandchild instead, and is not "assigned, after flattening, to the slot that is ::slotted’s originating element" (instead it's just carried along by its parent, the outer shadow's <slot> that is "assigned, after flattening"), and thus won't be selected by ::slotted(.a) in the inner shadow.

That's probably related to:

This surfaced in a bug report from Salesforce which found inconsistencies between implementations, but it turns out that is about ::slotted(slot) matching nested slots in WebKit, while Blink and Firefox do not match those:

Because nested slots should, per spec, be matched by ::slotted(slot). But apparently WebKit also colors Rune's first example orange, indicating they're also matching the .a grandchild of the inner slot???

Basically I'm very confused about what browsers are doing and am open to clarifying the spec any which way.

@tabatkins
Copy link
Member

Looking at this again, yeah, I'm pretty sure I need to, somehow, invoke find flattened slotables for the purpose of gathering the elements that'll be matched by the ::slotted()'s selector argument.

That'll mean that slot elements never match ::slotted(slot); this won't match Safari, but it'll match other parts of their behavior, and match Chrome and Firefox, and I think match the intended mental model better.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-scoping] Consider aligning ::slotted() for fallback content with implementations, and agreed to the following:

  • RESOLVED: change the spec to match current web behavior so fallback content is not matched by ::slotted()
  • RESOLVED: Add more examples to this section of the spec
The full IRC log of that discussion <dael> Topic: [css-scoping] Consider aligning ::slotted() for fallback content with implementations
<dael> github: https://github.com//issues/5482
<dael> rune: Case is none of browsers impl spec. Spec says fallback should be styled by ::slotted() but all browsers style elements which are slotted following assigned slot chain so you can get not just first slot scope but if child of shadow host it's flattened to next scope.
<dael> rune: Question is if we should change spec to match impl or if we should try to agree all impl should move to what spec says.
<dael> rune: Started as a bug reported by Salesforce. Bug in corner case in WK which makes Salesforce think they impl incorrectly, but mostly in agreement with Blink and WK.
<dael> rune: First level of slotting possible to style with a normal child selector on the slot. If you want to style the fallback in a nested slotting that's currently not possible.
<dael> rune: Talked to polymer team at Google and people working on web components and they think should stick with current. Worried this has web compat concerns if we shift to match spec
<dael> gregwhitworth: As I said in GH I'd like to understand what compat they're worried about. Is it their own specific compat?
<dael> rune: I don't think they have specific cases. Worried in general there is content that will break
<dael> gregwhitworth: Not sure why Polymer that's a component library...i'm confused.
<dael> rune: Need to check again
<dael> gregwhitworth: Chrome status data it's low in utilization. Part of me feels the scenario you outlined is not unheard of. My expectation...I would phrase do you match spec now or do we come up with another method that enables that use case?
<dael> gregwhitworth: The use case is not invalid. Browsers aren't impl to support it. We can fix by browsers match spec or we add new functionality to ::slotted()
<TabAtkins> q+
<emilio> q+
<dael> rune: Most common use case is style fallback in same scope where we have a solution. this is case of slot child of shadow host which slotted into a different scope.
<dael> rune: Possibility to have syntax to target the reslotted as fallback.
<dael> rune: I don't think we would like to change Blink impl unless there's a plan to change in all browsers. I saw WK didn't have immediate plans to do anything about it. Not sure if anyone from WK has specific thoughts or if it's just low priority
<astearns> ack TabAtkins
<dael> smfr: I don't know status, guessing low priority for us
<dael> TabAtkins: As I put in comment late in the bug the fact that spec says the selector passed to ::slotted() should match fallback content as well as actual slotted is more accidental. Easiest way to spec what I wanted. I don't have opinion on one way or other. Weak behavior that current browser is probably right b/c usually want fallback styled diffeerently.
<gregwhitworth> lol
<dael> TabAtkins: I'm fine with browsers keeping current. I found on investigation that how spec is written I don't think does what we want. Everyone is doing some funky I think you know what I mean behavior. The algo in the spec if you have nested shadow roots so slot going into light dom as written that doesn't matter for ::slotted() and it just sees slot children
<dael> TabAtkins: Apparently works in Safari but Safari styles actual children? Confused
<dael> rune: Bug is Safari is they match the slot which is not what's in spec. In Salesforce they styled the color and in Safari targetted the slot. If they set hte border wouldn't have worked.
<dael> TabAtkins: GOt it. I thought border styles also applied but if they don't that's fine.
<dael> rune: Behavior in Safari is corner case
<dael> TabAtkins: Find slottables is you find the slot elements themselves
<dael> rune: Flattening in HTML collapses all the slots. Only thing you style is the elements. Can't style slots themselves
<dael> TabAtkins: I'm pretty sure that's wrong. It's want I wanted, but not what I wrote.
<dael> TabAtkins: I would like to change to match current Chrome and FF behavior. I support that.
<astearns> ack emilio
<TabAtkins> https://drafts.csswg.org/css-scoping/#slotted-pseudo Or hell, maybe I did write it correctly; it does say "assigned, after flattening", and links over to the "find flattened slotables" algo.
<dael> emilio: Going to say along lines of TabAtkins opinion. Regardless of which way you go if you want one or the other behavior you need to work around it. If you want fallback and sloted identical style you need to spec 2 selectors. If style differently you need to add a bunch of rules which is tricky for specificity.
<gregwhitworth> smfr: https://bugs.webkit.org/show_bug.cgi?id=169948
<dael> emilio: As far as I can tell only thing you can't do in FF and Chrome is style fallback of nested slots. That doesn't seem like something a lot of people would want to do b/c don't know dom hierarchy of nested slot. Could be wrong.
<dael> rune: Share understanding with emilio. You're correct about case where you cannot targer
<dael> emilio: Like current b/c if you want style and slotted children the same it's way easier to do than if we impl what spec says.
<dael> TabAtkins: I prop we resolve to change the spec so fallback content are not part of content matched by ::slotted() to have spec match current Chrome and FF behavior
<dael> TabAtkins: Does that work?
<dael> gregwhitworth: Addendum request; I would love some examples about here's what flattening does. I understand that's WHATWG but examples in our spec as to what happened
<dael> TabAtkins: I agree, could use examples
<dael> gregwhitworth: If we can defautl style the default content it works for us
<dael> astearns: Prop: change the spec to match Chrome and Gecko behavior so fallback content is not matched by ::slotted()
<dael> rune: Noted Safari is mostly correct. It is mostly aligned
<dael> astearns: Yeah, it's the edge case
<dael> gregwhitworth: Yeah, it's that one bug
<dael> RESOLVED: change the spec to match current web behavior so fallback content is not matched by ::slotted()
<dael> astearns: Obj to add more examples?
<dael> RESOLVED: Add more examples to this section of the spec
<dael> astearns: If there is a further use case for finding some way to have a style that matches slotted and fallback content we can add that in the future, but not necessary right now
<dael> TabAtkins: umhum
<dael> rune: Need tests
<dael> rune: When I changed impl in Chrome I didn't fail any tests so we need more
<dael> emilio: I think the tests are for what should match but not for waht should not
<dael> gregwhitworth: Leo on our side is good with tests and I can loop him in if you need help on adding tests

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

No branches or pull requests

5 participants