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-conditional-4] Feature detection for descriptors #2463

Open
xfq opened this issue Mar 22, 2018 · 36 comments
Open

[css-conditional-4] Feature detection for descriptors #2463

xfq opened this issue Mar 22, 2018 · 36 comments

Comments

@xfq
Copy link
Member

xfq commented Mar 22, 2018

Currently, @supports and CSS.supports only support property: value pairs. The only way for web developers to detect support for a certain descriptor in an at-rule is to use JavaScript. For example:

let e = document.createElement("style");
e.textContent = "@font-face { font-display: swap; }";
document.documentElement.appendChild(e);
let isFontDisplaySupported = e.sheet.cssRules[0].cssText.indexOf("font-display") != -1;

(from https://bugzilla.mozilla.org/show_bug.cgi?id=1296373#c6)

It would be good if CSS can test whether a user agent supports a descriptor: value pair.

See also a related issue in Chromium: https://bugs.chromium.org/p/chromium/issues/detail?id=600137

Tagged as "unknown/future spec" for now, because I'm not sure if we should extend @supports (which is in css-conditional), or develop a new mechanism for it.

@svgeesus
Copy link
Contributor

I agree that being able to detect descriptor support is valuable. I would have thought it belongs in CSS Conditional.

@tabatkins
Copy link
Member

Yeah, it's Conditional - this is precisely why we took care to include arbitrary functional terms in the @supports grammar which resolve to unknown, so we can add more types of support-testing in the future.

(Need to also be able to test for types, like if "1foo" is a <length>, etc.)

@tabatkins
Copy link
Member

Something like @supports descriptor(@media, font-display: block) { ... }.

@frivoal
Copy link
Collaborator

frivoal commented Mar 23, 2018

@tabatkins Did you mean to write:

@supports descriptor(@font-face, font-display: block) { ... }

@frivoal
Copy link
Collaborator

frivoal commented Mar 23, 2018

(Need to also be able to test for types, like if "1foo" is a , etc.)

Maybe. We have a clear syntax extension path if we want to, but why do we need to test if 1foo is supported independently of testing if bar: 1foo is supported?

@xfq xfq changed the title Feature detection for descriptors [css-conditional-4] Feature detection for descriptors Mar 23, 2018
@tabatkins
Copy link
Member

Did you mean to write:

Yes, I did mean that.

Maybe. We have a clear syntax extension path if we want to, but why do we need to test if 1foo is supported independently of testing if bar: 1foo is supported?

Yeah, one could do that, but given that UAs will have explicit parsers for each value type for Typed OM purposes, we'll be able to expose that directly, if it makes one's intent clearer.

@frivoal
Copy link
Collaborator

frivoal commented Mar 23, 2018

Sure, but when you want to use a value, you're going to use it somewhere. And it is possible that the UA supports that value without supporting it where you planned to use it.

For instance, a newer version of a property may accept % and length while the old one only took %. The browser you're in does not yet support the length version yet, but does support the new fancy length unit you want to use in places where it does support length.

In that sense, while @supports unit(1ch) would something that is technically true, it seems a bit of a footgun: @supports unit(1ch) { prop-that-used-to-take-percent-only: 1ch; }

@ExE-Boss
Copy link
Contributor

ExE-Boss commented Oct 17, 2018

The unit(…) function could only be exposed from within CSS.supports(…) but not from @supports, that would solve the footgun issue while allowing developers to test for unit support from JavaScript, also the unit(…) function is off‑topic and should be discussed in a separate issue.

@mrego
Copy link
Member

mrego commented Apr 24, 2020

It'd be nice to have this for @property so we could feature detect the support from CSS directly with something like:

  @supports descriptor(@property, syntax: "<color>") { ... }

@yisibl
Copy link
Contributor

yisibl commented Apr 24, 2020

It'd be nice to have this for @property so we could feature detect the support from CSS directly with something like:

  @supports descriptor(@property, syntax: "<color>") { ... }

Yeah, @property urgently needs this feature, Chrome is ready to ship: https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/3ygpsew53a0

@yisibl
Copy link
Contributor

yisibl commented Apr 24, 2020

Especially in [css-properties-values-api] currently implemented by Chrome, syntax: '<image>' is not yet supported, which makes it difficult to detect it. cc @andruud

@yisibl
Copy link
Contributor

yisibl commented Apr 24, 2020

Is it possible to do a CSS @supports check on a @media rule?
https://stackoverflow.com/questions/44244221/is-it-possible-to-do-a-css-supports-check-on-a-media-rule

@andruud
Copy link
Member

andruud commented Apr 30, 2020

I like the @supports descriptor() proposal. However, I'm not sure if it covers the @property use-case. The syntax descriptor in particular accepts any <string> parse time, so simply checking if there's a parse error (like we do in other @support cases) won't be enough. Probably each descriptor would need to define how to behave when used in a @supports descriptor().

I also wonder if there are any traps regarding some descriptors depending on other descriptors. For example, the following is hard to evaluate if we don't also know the syntax:

@supports descriptor(@property, initial-value: 1em) { ... }

For syntax "*" it's valid, but for e.g. "<length>" it's not.

@tabatkins
Copy link
Member

It's syntactically valid in either case, we would just invalidate the rule as a whole if syntax wasn't a compatible value. So that would resolve to true. (We don't, and can't, make declaration parsing validity dependent on other declarations, not least because the relevant "other declaration" might come after the one in question.)

@andruud
Copy link
Member

andruud commented Apr 30, 2020

Of course, but my point was that checking for parsing validity alone isn't enough to do what people (/ @yisibl) want for @property. 🙂

For example @supports descriptor(@property, syntax: "<image>") { ... } would be true in Chrome, even though <image> syntax is not implemented.

@tabatkins
Copy link
Member

tabatkins commented Apr 30, 2020

Hm, that's true. Validity of syntax does depend on us parsing the string, so if you wrote gibberish it would be thrown out as invalid. Seems like we could similarly test for validity of non-terminals in the string? That's information known at parse-time, certainly. I'll open an issue on the correct spec so we're not clogging up this issue. ^_^

Editted to add: wait no that's not the case, it does check for known non-terminals. The problem is just that we don't support the "<image>" non-terminal, but the spec doesn't have an allowance for "only recognize the ones you actually support". That seems much more straightforward of an obvious bug-fix, so I'll just do that.

@SebastianZ
Copy link
Contributor

Just want to mention my syntax suggestion from #5929 here:

@supports (@page { size: 10cm; }) {
...
}

That would work like the existing parsing checks for properties and selectors, so no new logic here. It only requires to extend the syntax of @supports to take at-rules.

Sebastian

@LeaVerou
Copy link
Member

LeaVerou commented Nov 9, 2021

We also need a way to query support for @rules (without any particular descriptor), and I would hate it if we had to include a random descriptor to do so (like the @property examples above). So, ideally, the syntax should allow querying for:

  • Recognition of @rule
  • Descriptor within specific @rule
  • Descriptor:value pair within specific @rule

Through the same general syntax.

I like @SebastianZ's suggestion above and it does lend itself nicely to detection of @rule with no descriptor (@supports (@property) or even @supports (@property {})). If it's feasible parsing-wise, I think it's probably the best option.

Also there have been proposals about at-rules that would be nested inside other at-rules or regular CSS rules. We should take that into account when designing this syntax, so we don't need to add more ad hoc syntax in the future to cover these use cases.

@fantasai fantasai added the css-conditional-5 Current Work label Dec 25, 2021
@svgeesus
Copy link
Contributor

svgeesus commented Jan 18, 2022

@bramus good point, yes they do.

@Loirooriol
Copy link
Contributor

We can't test CSS properties without a value. So allowing to check descriptors without a value seems inconsistent.

@tabatkins
Copy link
Member

Yeah, was coming here to bring up that same point; I don't think we need or want this syntax:

@supports (@font-face{ src; }) {
  // yay webfonts
}

I didn't think about nested at-rules, hm. That's in a weird middle ground; it's not as trivial as "do we support this at-rule" nor is it as hands-off as "does this successfully parse". Instead you have to maintain a somewhat more complex side-list of "do we support this at-rule inside of this other at-rule", which is precisely the sort of thing that can easily get out-of-date or be missed when updating things (especially for catch-alls like @media or @supports). I suspect we might want to leave this out, and just stick with asking for a single level of support.

So in other words, I think we want to limit things to just:

  1. Testing for the existence of an at-rule by name; (@foo) only, nothing else. (Meaning anything in the prelude is also left out, like (@scroll-timeline) with no name provided.) This is a manual test but it's extremely trivial and it's likely there's a list of valid at-rule names stashed in the impl anyway (I know Blink has one).
  2. Testing full parsing of an at-rule, as an entire block of syntax: (@scroll-timeline foo {}) is valid (needs a name in the prelude, but doesn't require any additional descriptors to validly parse); (@counter-style foo { system: cyclic; symbols: a; }) is valid (needs a name in the prelude, the 'system' descriptor, and the 'symbols' or 'additive-symbols' descriptor); (@counter-style foo {}) is false because it doesn't parse as a valid rule, due to missing its required descriptors; (@font-face { unknown: property; }) is false due to having an unknown descriptor in it.

@Loirooriol
Copy link
Contributor

Testing full parsing of an at-rule, as an entire block of syntax

I was also thinking about this. But should this be restricted to at-rules? We could check any block I guess

@supports (:foo { bar: baz; })

Like @supports selector(:foo) and (bar: baz)?

@tabatkins
Copy link
Member

We can't do naked testing of style rules; it's the same problem as Nesting, where the grammar of properties and style rules overlap. But yeah, testing for selectors and properties separately is fine.

@SebastianZ
Copy link
Contributor

Thinking more about it, I am not feeling totally comfortable with introducing a syntax that only checks the at-rule's name. It definitely looks nicer that way but I fear it may let authors conclude that they can use the rule without limitations. Though it doesn't cover any info about its descriptors or its syntax.

Authors don't win anything if they test for an at-rule that way but the descriptors or the syntax they use isn't supported.

Sebastian

@tabatkins
Copy link
Member

Sure, they can test for an at-rule and miss a descriptor, but the same is true of existing property tests; you can easily test for a simple version of a property and fail to realize that the more complex version you're using in your actual styles isn't supported. That's a risk authors take when they're weighing the competing concerns of terseness/readability vs accuracy, and it's not something we need to work too hard to protect people from, imo.

(Fun fact: an early proposal for @supports, way back when, worked by having no test in the prelude; instead you tagged individual properties in the contained styles with a !keyword, and it matched based on whether those properties parsed or not. No repetition at all, but what was being matched was a lot less clear up-front.)

I think the trade-off of accuracy vs terseness in allowing just the at-rule name is reasonable here. Most of the time, the mere presence of the at-rule is a sufficient test. When there have been gradual upgrades to an at-rule's feature set and you need to discriminate between support levels, the full-fat version of the query is there for you, but I don't think we need to force that on everyone the rest of the time. For example, I do not want people to have to write out that full @counter-style example just to test if the at-rule is allowed at all; that's way excessive.

(Properties, on the other hand, change their value set pretty regularly, and are a lot shorter to write, so the trade-offs of requiring the full (prop: val) over a simple (prop) isn't nearly as onerous.)

@Loirooriol
Copy link
Contributor

We can't do naked testing of style rules

Then something like @supports block(@scroll-timeline foo {}) or @supports block(:foo { bar: baz; })?

It seems a bit strange to mix both properties and at-rule blocks in naked (), but not style blocks. May be better to just have a function for blocks in general.

@tabatkins
Copy link
Member

Different blocks have different requirements - the child rules in @Keyframes use a different prelude syntax than style rules. Can't really be generic here.

There's also the question of "why?" - what does putting an entire style rule into an @supports test achieve? If it's testing selectors, we already have selector(); if it's testing properties, that's the original feature. There's no interaction between the two to worry about, either.

@Loirooriol
Copy link
Contributor

For consistency: if we allow at-rules but not style rules, it will probably be confusing for authors. @keyframes using different preludes would still need to be handled if mixing at-rules with properties in naked parenthesis, right?

@tabatkins
Copy link
Member

For consistency: if we allow at-rules but not style rules, it will probably be confusing for authors.

I don't think I agree? They're pretty different things. I suspect authors will be fine.

@Keyframes using different preludes would still need to be handled if mixing at-rules with properties in naked parenthesis, right?

Apologies, I'm not sure what you're asking here.

@Loirooriol
Copy link
Contributor

Apologies, I'm not sure what you're asking here.

So you said "Different blocks have different requirements - the child rules in @keyframes use a different prelude syntax than style rules". But this syntax needs to be supported in @supports(@keyframes k{from{color:blue}}). So I don't get why supporting it in a more generic function that includes style rule blocks would be problematic.

@tabatkins
Copy link
Member

But this syntax needs to be supported in @supports(@Keyframes k{from{color:blue}})

In that example it's not any sort of special thing. You just literally feed the entire contents of the query (everything between the parens) into the parser and see if there are any parsing errors. That's just fine! No special-casing required, just possibly adding a mode to your existing parser to flag parsing errors rather than just silently dropping them.

This is very distinct from a special-case syntax for testing rule nesting.

@SebastianZ
Copy link
Contributor

Quick note to everyone here: Be mindful to surround at-rules with backticks in discussions to avoid pinging people with that GitHub account name!

Sebastian

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed feature detection for descriptors, and agreed to the following:

  • RESOLVED: Add an `at-rule` function with syntax `at-rule(@keyword)` or `at-rule(@keyword; descriptor: value)`
The full IRC log of that discussion <emilio> topic: feature detection for descriptors
<emilio> TabAtkins: seems reasonable to ask for this given we can do it for properties
<emilio> ... as @rules grow you might want to be able to test for given descriptors
<emilio> ... there's a suggestion that we add a new @supports query to test for descriptors
<lea> github: https://github.com//issues/2463
<emilio> ... I'd describe the two that I think we should add
<emilio> ... and lea has other ideas
<emilio> ... I think we should test for general @-rule support
<emilio> ... so "can you identify this rule at all"
<emilio> ... the other one should be a more complex one for testing for a whole @rule
<emilio> q+
<TabAtkins> @supports (@rule) {...}
<TabAtkins> and @supports (@rule { desc: value; }) {...}
<fantasai> TabAtkins was summarizing https://github.com//issues/2463#issuecomment-1015709662 right?
<TabAtkins> yes
<emilio> lea: I think the only additional thing I proposed is that we shouldn't have to add random descriptors to see if the browser supports particular descriptors or what not
<emilio> ... so test for support for e.g. @Property or so
<chris> q+
<TabAtkins> emilio: Regarding testing the whole rule
<oriol> q+
<Rossen_> ack emilio
<TabAtkins> emilio: It's a bit weird, because we don't track parse errors, we just drop
<TabAtkins> emilio: We could od that potentially, but it goes against th eprinciple
<TabAtkins> q+
<TabAtkins> emilio: I think it would be nicer to do somethign else, but not sure what else could be
<TabAtkins> emilio: Don't have a generic idea for it
<TabAtkins> emilio: Like would great for `font-face-descriptor(desc: value)` but then you'd need it for each
<TabAtkins> emilio: Just scares me if it gets out of sync
<emilio> chris: wanted to check where we are in nested at rules
<Rossen_> ack chris
<fantasai> emilio: Another related concern, some rules are related
<fantasai> emilio: so if you parse an at-rule in an @supports block ...
<fantasai> emilio: should we consider position in the style sheet?
<Rossen_> ack oriol
<emilio> ack oriol
<emilio> oriol: it seems strange to me the including-the-whole-at-rule
<emilio> ... because parens would accept a weird set of syntax
<emilio> ... and this seems a bit inconsistent / confusing to authors to me
<TabAtkins> (I'm fine with an `at-rule()` function, fwiw.)
<emilio> ... because they might want to test for style rules
<emilio> ... but we're not supporting that, so I'd prefer another function
<emilio> ... or an option for testing general rules that could be an at-rule() or general rule
<emilio> ... but mixing some but not all rules, and also property-declarations in parens would be a strange mix
<Rossen_> ack TabAtkins
<emilio> TabAtkins: emilio's point about the whole rule testing is a very reasonable point and I don't want to do this if it requires special cases
<emilio> ... I'd like to instrument the existing parser if possible
<fantasai> emilio: Even with instrumentation, it can still get out of sync
<fantasai> emilio: e.g. someone forgets to propagate the error
<fantasai> emilio: so even if set just a boolean that indicates parser error
<fantasai> emilio: if don't set it at the right time, is a problem
<fantasai> emilio: It wouldn't be a whole new parser, just concerned about sync
<emilio> TabAtkins: connected to that, there's chris' question about nesting
<emilio> ... if you have per-at-rule descriptor function then you don't get nested at-rules for free
<emilio> ... the other question was about context
<emilio> ... I'd say we should specify what the parsing context is for this
<emilio> ... which would be the generic top-level stylesheet context, so @import would fail
<emilio> ... and re oriol's point I'm totally fine with `at-rule()` or something if you think it's less fair
<oriol> Yeah it think that's better
<Rossen_> ack dbaron
<emilio> dbaron: I think when I wrote the @supports proposal the way I had envisioned extending them is that we'd add new functions for other points where CSS drops things
<emilio> ... so at the point where the CSS parser says "oh, that is invalid so we drop it as a unit", that seems sensible to add a function for
<emilio> ... and I think it's a bit weird to put a whole rule inside @supports
<TabAtkins> q+
<emilio> ... that said I think TabAtkins' argument about nesting is interesting, because the approach I was thinking of at the time doesn't allow you to test for such things
<emilio> ... so I have mixed feelings about it
<Rossen_> ack fantasai
<emilio> fantasai: regarding the TabAtkins' generic top-level context I think that'd be confusing to authors, I think it'd be more understandable and useful to authors if we allowed both that or anything that's in the prelude, so that e.g. @import would count as supported
<emilio> ... I think it'd be less confusing to authors
<emilio> ... and I can see use cases for doing that if you want to do something conditional in whether some extended at-import syntax is supported
<emilio> ... I also wanted to say about dbaron's comment that it would be better to have the nested syntax rather than having many functions
<emilio> ... I'd prefer parenthesis rather than a function, I think it'd be a little bit cleaner and easier to type
<emilio> q+
<TabAtkins> @supports at-rule(@foo) {...} and @supports at-rule(@foo, desc: value) {...}
<emilio> ack TabAtkins
<Rossen_> ack TabAtkins
<emilio> TabAtkins: thanks dbaron for all that context. If we follow those premises we also avoid emilio's concerns. In that context perhaps we could do something like what I typed above in the chat
<emilio> ... it doesn't address nesting right now but we can extend it if needed
<fantasai> s/many functions/many custom functions per at-rule/
<emilio> ... we also don't and probably won't have many at-rules that are inconsistently nested
<faceless> Surely @supports (@import) should always fail, as @import must be the first rule? So if you precede it with @supports, it's not valid?
<miriam> q+
<fantasai> faceless, I don't think @supports queries should be sensitive to position
<Rossen_> ack emilio
<lea> faceless: is there a use case for @supports(@import)? Literally every browser supports @import, no?
<fantasai> emilio: @supports(@import) { } and then not put an @import inside
<TabAtkins> Not as a positive test, as a negative test.
<fantasai> TabAtkins: My proposal is not parsing, just is this at-rule in your list of recognize at-rules
<fantasai> emilio: fantasai it might be useful to test e.g. @supports(@import "" layer) or something
<fantasai> emilio: though for layer you could check to @layer
<fantasai> emilio: I think having a generic at-rule() function is better than having function per at-rule
<fantasai> TabAtkins: The downside is it wouldn't allow testing the prelude of an at-rule
<Rossen_> ack miriam
<emilio> miriam: prelude seem important for several things like layers and container
<fantasai> miriam: That was my question, because preludes seem important for many things, such as @layer and @container
<emilio> ... so it seems odd to leave it
<emilio> fantasai: I see that at-rule() as an improvement to having a function per at-rule
<emilio> ... but I don't see how that is better than just dropping the whole css syntax
<TabAtkins> If we wanted to add it, could have the form `at-rule(@foo prelude stuff)`; when there's >1 token there we test full prelude parsing
<emilio> ... it'd cover handling the prelude / descriptors / nested at-rules / etc
<emilio> ... so I don't understand why we'd go for the function rather than the proposal that was on the issue
<emilio> TabAtkins: emilio and dbaron explained why that wasn't great
<emilio> ... you'd need to detect whether there's a parse error somewhere in a rule needs intrumenting the parser
<emilio> ... whether testing whether something is dropped entirely or not is easy and can be done with no possibility of missing things
<Rossen_> q?
<Rossen_> ack fantasai
<emilio> ... because we definitely drop things that are invalid and is detectable, but detecting whether an inner descriptor failed to parse inside an at-rule requires special-casing
<emilio> Rossen: does that answer your question elika?
<emilio> fantasai: yes
<TabAtkins> @supports at-rule(@foo) {...} and @supports at-rule(@foo, desc: value) {...}
<emilio> TabAtkins: proposal is having the at-rule function with two syntax variants: `at-rule(@Keyword)` and `at-rule(@Keyword, descriptor)`
<emilio> fantasai: not against that but I have a question about how do we extend to the prelude
<emilio> TabAtkins: posted that up as well, we could have it drop the whole prelude in there or something
<emilio> fantasai: but prelude might include commas
<emilio> ... if you want something not in the prelude you're going to need a semi-colon
<lea> what about descriptor values? at-rule(@Keyword, descriptor, value)?
<emilio> TabAtkins: alright let's use semi-colons
<fantasai> emilio: Meant to write descriptor:value
<emilio> lea: would there be a way to test for @rule <name> or so?
<emilio> TabAtkins: that's the prelude extension we were discussing above
<TabAtkins> at-rule(@Keyword; desc:value)
<emilio> dbaron: so to clarify you wouldn't extend it to put the whole at-rule inside right?
<emilio> TabAtkins: right, or we just drop it and if we drop descriptors inside then it'd test true
<emilio> ... I think we should resolve on the keyword and descriptor variants and we can extend to support the whole prelude
<emilio> RESOLVED: Add an `at-rule` function with syntax `at-rule(@Keyword)` or `at-rule(@Keyword; descriptor: value)`

@kizu
Copy link
Member

kizu commented Oct 22, 2023

@tabatkins Should this issue have a “Needs Edits” label? I looked into the specs and didn't find a mention of the at-rule().

Context: https://codepen.io/propjockey/pen/OJdJmya?editors=1100 — right now developers have to test for “features that co-exist” with the at-rule implementations in order to test the at-rule support. It would be awesome to have a better way to do this, and I think the sooner it would be possible to do so — the better, as more potential at-rules would get covered by this.

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