w3c / csswg-drafts

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

[cssom-1] Expose nested selectorText on CSSStyleRule #10246

Open bramus opened 4 months ago

bramus commented 4 months ago

On a CSSStyleRule you can get the selectorText that returns the textual representation of the selector.

With CSS nesting, this value has become somewhat less useful as it doesn’t represent the full selector.

While an author can use script to compute the flattened selector by manually walking the .parentRule to the top and then doing the checks + replacements for &, it would be nice if this was built into the platform. That way, an author can directly use it in something like document.querySelectorAll.

I’m thinking of a property named flattenedSelectorText or fullSelectorText.

romainmenke commented 4 months ago

This would be very useful!

For all things PostCSS, Stylelint, ... we also need to determine the full selector and I created a helper for this : https://github.com/csstools/postcss-plugins/tree/main/packages/selector-resolve-nested#readme

The wording we use is a bit different : https://github.com/csstools/postcss-plugins/blob/main/packages/selector-resolve-nested/docs/selector-resolve-nested.md#functions

We use flatten for naive replacements. This does a simple replacement of & with the parent selector. The result is a mangled selector that won't match the same elements but this is useful for static analysis. (e.g. how many type selectors did the author write in source?)

We use resolve to get a full selector that would also match the same elements. (e.g. what is the specificity of this selector?)

If there was a specification for this action we should have similar behavior between tooling and browsers.

bramus commented 4 months ago

Great distinction between flattened and resolved. Given this, what I suggested would better be named resolvedSelectorText.

Loirooriol commented 4 months ago

This seems vulnerable to a "billion laughs attack".

romainmenke commented 4 months ago

It is, but given that it is a script API it could have limits and throw when those limits are exceeded.

Edit : It is not. This would serialize selectors with :is()

Loirooriol commented 4 months ago

it could have limits and throw when those limits are exceeded

This seems very fragile. I would prefer a different design that doesn't suffer from this, e.g.

document.querySelectorAllWithNesting("a, b", "& c, & d", "& e, & f")

instead of

document.querySelectorAll("a c e, a c f, a d e, a d f, b c e, b c f, b d e, b d f")
document.querySelectorAll(":is(:is(a, b) c, :is(a, b) d) e, :is(:is(a, b) c, :is(a, b) d) f")

This would serialize selectors with :is()

This doesn't avoid the problem.

romainmenke commented 4 months ago

You are right, it makes it slightly smaller.

But still quickly explodes in cases like this one :

a,
b {
    &+&,
    &>& {
        &+&,
        &>& {
            color: green;
        }
    }
}
tabatkins commented 4 months ago

The current OM does represent the full selector; the & selector is not evaluated by substitution, but rather refers directly to the elements selected by the parent rule.

Understanding the use-cases better would be useful. If it's about making it easier to select the same elements in qSA() as a particular style block, we can cater to that more directly. @Loirooriol's suggestion (multiple selector arguments, representing nested selectors) might work, for example. But also, you seem to be suggesting that authors would be willing to crawl the OM and obtain a selector directly off of the CSSStyleRule - how serious were you about that in particular as a use-case? Because we could also, say, allow qSA() to take a CSSStyleRule directly, and return the same set of elements the rule matches, if that code pattern seems realistic.

Romain mentions some preprocessor uses, notably determining the specificity. Is there more?

romainmenke commented 4 months ago

preprocessor uses, notably determining the specificity. Is there more?

For preprocessors or author tooling in general? No, all variations on the same theme :)

e.g. determining if &::before {} would be valid, which it might not be if & represents something like ::after. This is essentially the same problem as calculating specificity.

You need to do something with & to be able to give feedback on what the author wrote. This is true for any indirection (e.g. var())

bramus commented 4 months ago

The current OM does represent the full selector; the & selector is not evaluated by substitution, but rather refers directly to the elements selected by the parent rule.

Sorry, should have said “flattened” or “resolved” selector.

Understanding the use-cases better would be useful. If it's about making it easier to select the same elements in qSA() as a particular style block, we can cater to that more directly.

That’s my specific use-case indeed.

You seem to be suggesting that authors would be willing to crawl the OM and obtain a selector directly off of the CSSStyleRule - how serious were you about that in particular as a use-case?

It came up when writing a (rough) polyfill for ident() + attr() for this demo but admittedly not all authors write that type of stuff.

Another use case is doing an analysis of the loaded CSS (nesting depth, layers extraction, etc.) with userland code.

LeaVerou commented 4 months ago

Agree that the OM needs to provide both. If selectorText produces the full/resolved selector, then localSelector could produce the local one.

Loirooriol commented 4 months ago

@LeaVerou I'm confused, isn't the current selector the same as the local one?

LeaVerou commented 4 months ago

@Loirooriol edited

bramus commented 4 months ago

@LeaVerou You still got it flipped, no?

selectorText is the existing property which returns the local selector. The ask in c0 is for a new property that returns the resolved selector.