[CSSWG] Minutes Nesting Breakout 2023-01-25 [css-nesting]

=========================================
   These are the official CSSWG minutes.
   Unless you're correcting the minutes,
  Please respond by starting a new thread
    with an appropriate subject line.
=========================================

CSS Nesting
-----------

   Clearer definition of "nest-containing"
   https://github.com/w3c/csswg-drafts/issues/7972
   - RESOLVED: Relative-ness is decided based on & appearing *anywhere*
               in the selector, even nested inside anything else
   - RESOLVED: serialize any invalid :is() branch that contains `&` as-is
   - Emilio opened https://github.com/w3c/csswg-drafts/issues/8356
     about preserving invalid selectors in :is() and :where()

   CSSOM for nested media query rules
   https://github.com/w3c/csswg-drafts/issues/7850
   - RESOLVED: Property declarations nested directly into a conditional
               rule get auto-wrapped in a `& { ... }` style rule.
   - Group discussed three options for handling interleaved declarations:
       1. Similar to style-rules-in-style-rules, treat all
          nested props as if they occurred together at the top,
          before any other rules.
       2. Wrap properties in the default-style-rule where they
          stand, letting them interleave.
       3. Treat this as invalid (presumably dropping properties
          that occur after rules?)
   - The first option means interleaved declarations get reshuffled in
     the cascade, which breaks the "last declaration of equal specificity
     wins" expectation.
   - Group also discussed how the .style option would map under each of
     these options. There was some concern about trying to keep behavior
     consistent between conditional rules and style rules. One proposal
     was mapping .style to the first & { ... } rule, or to one that
     represents only declarations ahead of the first style rule.
   - Discussion will continue in the issue.

===== FULL MEETING MINUTES ======

Agenda: https://lists.w3.org/Archives/Public/www-style/2023Jan/0011.html
Log: https://www.w3.org/2023/01/25-css-irc

Present:
   Patrick Angle(?)
   Rossen Atanassov
   Tab Atkins
   David Baron
   Oriol Brufau
   Elika Etemad
   Chris Lilley
   François Remy
   Miriam Suzanne
   Lea Verou (via IRC)

Scribe: fantasai

CSS Nesting
===========

Administrative
--------------

   Swapping order of 2 & 3 on agenda.
   Adding republication as a topic

Clearer definition of "nest-containing"
---------------------------------------
github: https://github.com/w3c/csswg-drafts/issues/7972

   TabAtkins: If a selector starts with a combinator, we assume relative
              selector: insert & and interpret accordingly
   TabAtkins: If no such combinator, we need to check if this should be
              a relative selector or if there's an & deeper in
   TabAtkins: Could have something like "foo &" or ":not(&) ..>"
   TabAtkins: But spec is not super clear what it means by "contain" &
   TabAtkins: e.g. what happens if & is inside :is() within branch that's
              ignored by invalidity?
   <TabAtkins> :is(&:unknown, .foo)
   TabAtkins: is that &-containing or not?
   TabAtkins: Depending on our answer, how does it affect serialization?

   TabAtkins: Listed a few options
   TabAtkins: 1. Only look at top-level, not inside parens
   TabAtkins: 2. Look everywhere except inside forgiving parsing list
   TabAtkins: 3. Get deeper into parsing guts, say something about &
                 inside even forgiving selector lists that might be
                 invalid, and preserve that info across serialization
                 even though normally drop such branches during
                 reserialization

   TabAtkins: fantasai and I thought #3 would be weird, because [missed]
   TabAtkins: so initial suggestion was #1, simple suggestion
   TabAtkins: after discussion with Natalie and Miriam, there were too
              many use cases for & deeper than top-level
   TabAtkins: and having selector only in one branch and not others is
              weird and unusual, so not that important if behavior
              differs in branches
   TabAtkins: so suggestion is to look for & in every branch, whether
              invalid or not
   TabAtkins: needs authors to be a little more careful
   TabAtkins: but simple: if there's an & anywhere, it's got one
   TabAtkins: Might need to tighten up parsing by having known-invalid
              selectors serialize as something particular
   TabAtkins: So definition proposed is that it's containing an & if it
              contains an & anywhere

   fremy: Tab mentioned that it's weird to have branches where some
          contain & but some do not
   fremy: I agree, if you're doing that it's probably a mistake
   fremy: But then why can't we make this a rule?
   fremy: if you have a branching selector, must have & in every branch
   fremy: otherwise invalid
   TabAtkins: Valid question, we could, but probably doesn't matter
              because unusual case
   TabAtkins: so I don't think it's necessary
   TabAtkins: but I could do either way
   fantasai asks for an example of where this differs
   TabAtkins: Consider the previous example, where only one branch of
              :is() has an &. Difference is we'd make that illegal
   <TabAtkins> so `:is(&:unknown, .foo)` would be an invalid selector
   TabAtkins: Also weird, it would be the only way for :is() to
              invalidate a selector
   TabAtkins: another little bit of complexity that we could avoid for
              a corner case
   fremy: Yes, I think I agree. It might not make much sense, but you
          might want it

   <lea> in case it helps, as an author, I'd expect .foo { :is(&, .bar) {} }
         to be interpreted as .foo, .foo .bar , not .foo, .bar
   TabAtkins: Lea posts a different suggestion, which is that you take
              different paths in deciding relative selector or not
   TabAtkins: Problem is that doesn't map directly to :is() notation,
              so suspect it would complexify implementations
   fantasai: Do we have any comments from implementers on lea's question?
   TabAtkins: I can't say for sure because our implementers aren't on the
              call, but I think  our implementation would find it difficult
   emilio: Doing what Lea suggests would be fairly annoying
   emilio: and could have problems with selectors growing exponentially,
           which is not great
   emilio: I think this is a problem Blink had when they initially
           implemented this
   TabAtkins: When trying to do naïve impleementation of :is(), it was
              a problem
   <fremy> @ lea, the question was whether TabAtkins's reply to you
          addressed your concern or not
   <fremy> @ lea, and (I guess) whether this was something you felt
           strongly about it
   <lea> fremy: no, this is not something I feel strongly about
   <lea> though I do find it fairly weird if you can "break" out of the
         nest by doing `:is(&, .bar)`. Also if :is(.bar) matches
         different things than the .bar in :is(&, .bar)

   Rossen: Current proposal is simply option 3 without any further
           modification (and by option 3 we mean option 3 in IRC,
           which is option 2 in the issue)
   <TabAtkins> proposed resolution: Relative-ness is decided based on &
               appearing *anywhere* in the selector, including in
               (potentially-invalid) branches of a forgiving-selector-list
   plinss: I presume it's only legal for one & in selector?
   TabAtkins: no can have multiple
   plinss: so I can say &.foo &.bar?
   TabAtkins: & represents elements matched by the parent selector
   TabAtkins: would be two different elements that both happen to match
              the same parent
   plinss: fair enough

   fantasai: suggest we clarify that if I have any kind of selector
             garbage inside :is(), including an & nested deep inside of
             the garbage, that & is considered to be an & for these purposes?
   TabAtkins: correct
   <fantasai> :is(:unknown(&))
   <fantasai> :is([something (&something else { } ) ])
   <TabAtkins> :is([{(&)}]) has an ampersand in it, per the suggested
               resolution
   TabAtkins: If during selector parsing you see an & delim token, mark
              the selector as having an & and continue normal parsing

   Rossen: any other feedback?
   RESOLVED: Relative-ness is decided based on & appearing *anywhere*
             in the selector, even nested inside anything else

   TabAtkins: Question is what to do about serialization
   TabAtkins: Option 1: do nothing special. This might mean that an
              invalid branch is dropped, which changes selector
              interpretation
   TabAtkins: Option 2: serialize any :is() branch that contains an & as-is
   TabAtkins: Option 3: serialize any :is() branch that contains & as
              something guaranteed to be invalid, e.g. "&:not(&)" (which
              is guaranteed to not match but definitely contains an &)
   emilio: Only doing [missed]
   TabAtkins: Option 4: do 2 or 3 if that's the only branch that contains
              an & (and thus the selector interpretation is dependent on
              that branch existing)

   emeyer: wrt option 1, what are the concerns?
   emilio: It's bad if something serializes and then when you re-parse it
           it's interpreted differently than the original
   TabAtkins: So you can have a selector that's relative in new browsers
              and non-relative in older browsers, which is a significant
              change
   emilio: You lose the forgiveness ... which is annoying
   [missed]
   emilio: Implementation-wise, it feels like it would be nice not to
           account for & in invalid branches, so you can compute the
           relativeness of a selector independently of parsing process
   emilio: ... but all the options are weird in their own way
   <dbaron> My preferences: definitely not option 1, weakly prefer
            option 3 (without the option 4 variant).
   <fantasai> +1 dbaron
   <fremy> @ TabAtkins, for option 3 `&:not(&)` might be costly to compute,
           how about something truly invalid like `::-invalid-(&)`
   [Tabatkins summarizes dbaron's comment into audio]
   plinss: [asks about option 2]
   emilio: option 2 is consistent with media queries, where we preserve
           the CSS string for general enclosed
   TabAtkins: fair
   [Leaning towards 2 or 3]
   <dbaron> I'm also fine with 2.

   Straw Poll: Option 2 vs Option 3
   <TabAtkins> abstain
   abstain (defer to implementers)
   <emilio> either wfm
   <plinss> 2
   <lea> 2 (based on the descriptions in the comment primarily, as I've
            missed a lot of the discussion due to being on another call)
   <Rossen> 3, 2
   <fremy> abstain
   <emeyer> abstain
   <chris> abstain
   <davidleininger> 2
   <dbaron> abstain
   <florian> abstain
   <jensimmons> abstain

   emilio: other issue of option 2 is that it changes behavior from today
   TabAtkins: in theory, yes, if someone is putting & inside their
              invalid :is() selector, it will change serialization
   emilio: We probably want to do it for all invalid selectors, so that
           :unknown is reserialized as-is, regardless of &
   TabAtkins: I suggest taking up separately
   Proposed to go with option 2
   Emilio to open issue about switching serialization for all selectors
   plinss: In my mind, would want to be consistent for all selectors
   TabAtkins: I agree in theory, don't care too much
   RESOLVED: Option 2

   TabAtkins: open separate issue for the rest, because it may have a
              compat risk
   plinss: If the other one goes the other way, might want to revert
           this one
   TabAtkins: currently don't have compat either way for & stuff
   plinss: Yes, but I want us to note in the other issue to reconsider
           this one
   <emilio> I can open the issue
   ACTION: emilio to open new issue

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

CSSOM for nested media query rules
----------------------------------
github: https://github.com/w3c/csswg-drafts/issues/7850

   TabAtkins: Spec as currently defined allows you to nest conditional
              rules and other rules directly inside a style rule
   TabAtkins: in that context, they can have properties directly in them
   TabAtkins: so e.g. div { ... @media something { color: blue; } }
   TabAtkins: question is, how do we reflect this in the OM
   TabAtkins: CSSMediaRule doesn't have .style
   TabAtkins: One possibility is to add .style
   TabAtkins: other possibility is treat such properties as wrapped
              in & { .. }
   TabAtkins: They would be exposed in their current OM

   TabAtkins: I suggest going with #2, because then we don't need to
              change the OM for any of these rules
   TabAtkins: And also, if you're clearing out an at-rule, currently you
              can just replace .cssRules but if we add .style then you
              have to remember to clear out .style also
   TabAtkins: So that's my proposal
   <fremy> +1 to the second option
   TabAtkins: wrap directly-contained properties with `& { ... }`

   dbaron: Is there a way to distinguish these rules from real ones
           written that way?
   TabAtkins: No, and we have a followup issue to discuss whether they
              reserialize out with the & { ... } wrapper
   TabAtkins: other than that no author-visible difference

   lea: I think option 2 is fine for reading code, but could we author a
        shortcut for this sort of thing, e.g. adding .style to the OM
   lea: to provide a shortcut for creating this rule if it doesn't exist
   lea: if you read back .cssRules, it's fine to have the extra rule
   TabAtkins: setting .style sometimes creating a new rule is a bit magic
   <emilio> +1 tab
   TabAtkins: for any other case would have to manually construct the
              rule anyway? So I don't think it's worthwhile to have the
              additional magic for this case
   Rossen: Lea, what you're suggesting can be added later
   lea: yes, that's why I don't feel too strongly about it

   dbaron: When I asked if they were distinguishable, I felt they should
           be distinguishable somehow
   dbaron: Seems nice if can reserialize in original form
   dbaron: also nice to distinguish
   TabAtkins: other than serialization, why distinguish?
   dbaron: can't think of anything
   dbaron: if there was an accessor on the container for this kind of
           thing ...
   dbaron: then you could compare to see if it's different
   dbaron: it would also let you do what lea was asking with a single
           additional access, if the access implicitly created it, though
           maybe that's a bad idea
   emilio: Is this rule always need to be the first one?
   TabAtkins: that's a great question, I don't have a strong opinion
   TabAtkins: e.g. do properties outside rules all glom together ?
   TabAtkins: I don't have a strong opinion

   patrickangle: Going back 1 step, we'll need to be able to distinguish
                 for developer tools anyway, so will have to support in
                 the engine even if not Web-exposed

   fantasai: question of whether these glom together or not is pretty
             important b/c it changes the cascade implications
   fantasai: can have a selector that has the same specificity as the &,
             so if you have interleaved declarations and they all have
             the same specificity, where they occur in order is going to
             make a difference as to the output of the cascade
   <miriam> +1 - I don't think declarations should get merged like that
   fantasai: so whether declarations get glommed together and shifted to
             the top is something that needs to be definitely decided
   fantasai: because that impacts the cascade
   <fremy> I would not support "moving to the top" the declarations

   fantasai: I think we should do whatever we do outside of conditional
             rules
   fantasai: so if outside this context we glom them all together and
             handle them as occurring before all other rules, then we
             should do the same thing inside of conditional rules
   fantasai: And if it's not, we shouldn't
   TabAtkins: We do indeed effectively glom all the un-nested declarations
              together outside of conditional rules, so I suggest we glom
              them together
   fremy: I don't like that. I prefer in both cases we create an & rule

   <TabAtkins> today, in `.foo { color: red; & { ... } width: 100px; }`,
               we treat it as equivalent to
               `.foo { color: red; width: 100px; & {...}}`

   TabAtkins: First proposed resolution, properties in a default style
              rule get auto-wrapped in `& { ... }`
   Rossen: objections?
   RESOLVED: properties in a conditional rule get auto-wrapped in
             `& { ... }`

   TabAtkins: For declarations which are not at the top, do we do the
              same thing as style rules or do we wrap each bunch
              independently or move them all to the top?
   fremy: another option is to make them invalid
   fremy: idk if worth considering
   fremy: I find it pretty confusing that you can write something after
          something else, and it's treated as coming before

   <TabAtkins> 1. Similar to style-rules-in-style-rules, treat all
                  nested props as if they occurred together at the top,
                  before any other rules?
   <TabAtkins> 2. Wrap properties in the default-style-rule where they
                  stand, letting them interleave.
   <TabAtkins> 3. Treat this as invalid (presumably dropping properties
                  that occur after rules?)

   matthieudubet: If serialize with .cssText, then [could be confusing]
   * scribe sorry, missed half that last comment :(
   TabAtkins: for .cssText, I would serialize in canonical form
   TabAtkins: assuming we glom together, then if first rule in your
              .cssRules is & by itself, then we serialize that as naked
              properties
   matthieud: That's what we assume, but then ppl write declarations and
              then [missed]
   matthieud: but maybe it's not an issue?
   TabAtkins: My proposed resolution is that we treat interleaved
              declarations the same way in style rules, i.e. they are all
              sorted to the top
   fremy: Can we resolve to do exactly as for style rules, but then
          discuss if that's what we actually want for style rules?
   TabAtkins: works for me
   <miriam> +1
   Proposed resolution: do exactly as for style rules wrt sorting of
                        declarations interleaved with style rules

   fantasai: the reason we didn't do it for style rules is we don't have
             OM ability to support that for style rules
   fantasai: the expectation is that all declarations would be in .style
             property, and to do that we can't care about their order
             wrt .cssRules
   fantasai: So worth pointing out that a .cssRules in a style rule will
             not contain the naked properties
   fantasai: So that's different from in conditional rules, which is a
             little inconsistent.
   fantasai: So do we really want that inconsistency, or do we want to do
             something like Lea's proposed magic, or...?
   fantasai: but it is inconsistent
   TabAtkins: I would have preferred something like this resolution for
              style rules as well, but for clear legacy reasons we can't
              do that for style rules -- authors need to be able to
              manipulate in .style
   TabAtkins: but we need to make sure that the cascade is consistently
              handled
   fantasai: one thing we could do to keep it consistent is to have
             .style represent the first &-selector'd rule inside of
             .cssRules
   fantasai: and then do that for both style and conditional rules
   fantasai: And all subsequent groups of declarations would each get
             their own &-rule that would *not* be accessible via .style
   fantasai: And that would give the same interface for both. It would
             keep the interleaved order of decls.
   fantasai: Not sure if that's what we want, but it would give
             consistent OM & cascading while allowing preserving
             interleaved order.
   <fremy> (what fantasai just described is my preference)

   plinss: [something]
   TabAtkins: Yes
   plinss: An alternative is, inside conditional rules all properties
           are wrapped in an &-rule, but in a regular style rule the
           *first* group of properties is not, but later ones do
   plinss: so in `.foo { color: red; &{...} color: blue; }`, it's
           equivalent to `.foo { color: red; &{...} &{color: blue;}}`
   emilio: perf-wise that seems slightly problematic
   emilio: not quite clear how it would work with shorthands
   emilio: I think coalescing them makes sense
   emilio: otherwise using shorthands becomes different inside a
           conditional rule vs in a style rule
   emilio: I'm not a fan of wrapping everything inside of different
           rules - it means now you have to selector-match more times
   <fremy> I am not sure I understood that, but we are out of time, so
           we should clarify on thread

   Rossen: Objections?
   fantasai: Seems like there are more things to consider than we thought
             at the start, wouldn't be good to resolve yet
   Rossen: Okay, take the rest to the thread

Received on Tuesday, 23 May 2023 05:33:31 UTC