Open SimonSapin opened 5 years ago
I don't think this is possible. calc() can be value-level because it resolves to a single leaf value in the grammars. The built-in env()s already have things that resolve to multiple values, however, and we don't have productions to recognize those. Author-defined env() will be even wider, with no control over what's put in there.
However, the "infection" behavior of var() isn't quite present here. You don't have to wait for any other values to be set up before you resolve it; you can just sub it in during parsing, and then invalidate the thing it's used in like normal. My current plan for author-defined env() is to just provide a JS API too (not a CSS rule), so that invariant will be maintained anyway. Should I make that clearer in the spec, to help guide implementation?
@tabatkins Separately, when you say...
My current plan for author-defined env() is to just provide a JS API too (not a CSS rule), so that invariant will be maintained anyway.
Does this mean that for use of author-defined env()
, we will have to separate out scripts that block the document to prevent FOUC?
Presumably, yeah. Setting a new env() value would trigger a reparse in this idea.
I don't think this is possible. calc() can be value-level because it resolves to a single leaf value in the grammars. The built-in env()s already have things that resolve to multiple values, however, and we don't have productions to recognize those.
Can you put an example? All env vars that I know of are either lengths or times. And all the ones that are in the spec are only lengths.
However, the "infection" behavior of var() isn't quite present here. You don't have to wait for any other values to be set up before you resolve it; you can just sub it in during parsing, and then invalidate the thing it's used in like normal. My current plan for author-defined env() is to just provide a JS API too (not a CSS rule), so that invariant will be maintained anyway. Should I make that clearer in the spec, to help guide implementation?
This is not quite true, that's now how it works in any browser today. All browsers reuse the custom property machinery because well, these values do change, semi-frequently actually, due to the rotation of the device for example. And nobody wants to reparse CSS rules when something in the page changes, and less so when a author-accessible API is called.
Can you put an example? All env vars that I know of are either lengths or times. And all the ones that are in the spec are only lengths.
Ah, I thought there was an env() that sets all four margins at once, suitable for dropping directly into margin
. If they're all value-level (and we feel like restricting things to that would be acceptable), then maybe.
And nobody wants to reparse CSS rules when something in the page changes, and less so when a author-accessible API is called.
That's a necessity if env() is used in selectors, which is one of the things that author-defined env() is intended to be useful for...
That's a necessity if env() is used in selectors, which is one of the things that author-defined env() is intended to be useful for...
Do we need to reparse the CSS rule as a whole or just the selector? Specifically, can authors specify for example an environment --div
to be { display: inline-block }
and then div env(--div)
to be expanded to div { display: inline-block }
? Or would an env()
-in-selector be restricted to be just selectors?
(This comment makes me a lot more concerned about the implementation complexity of custom env()
now...)
Ideally it should be restricted to just the selector; I don't particularly want arbitrary substitution of stylesheet contents.
That said, imposing such a restriction would require more work than we probably want to mix into this particular feature. I guess I should back off of env()
doing arbitrary substitution thruout the stylesheet, and stick with it being able to substitute in values. (Maybe still want to sub in MQs, tho perhaps that's also best done by Custom MQs...)
So yeah, let's go ahead and make these assumptions:
Actually, I think we can just lean on the list of possible types for the attr() function; attr() is almost exactly the same feature as env(), just drawing from the element rather than from the document. That also suggests the API shape to handle custom env() - have a map of names to string/type pairs, and parse the string as the type (same as attr()) or else it's invalid and throws.
I'm not sure why I didn't see that parallel before. Hmm tho, that still doesn't let you type-check during parsing, like you can with built-in env() or with attr(); you'd have to wait until you got the custom value set. Hm. I guess we will still have to do something at the call site for this.
Okay, so we'll set aside custom env() for a moment; I'm confident we can engineer around any problems later. Let's just talk about built-in env(). A built-in env() is typed to one of the attr() types, according to the name given to it, so you can type-check at parse time. (Currently, all the names are lengths.) I'll rewrite the spec accordingly.
I submit for your consideration a practical case where the current parsing definition of env()
is hostile to reasonable expectations, from https://github.com/w3c/csswg-drafts/issues/3792:
margin-bottom: 15px;
margin-bottom: env(safe-area-inset-bottom, 15px);
margin-bottom: max(env(safe-area-inset-bottom, 15px), 15px);
First rule, fine. Second rule, fine: discarded if env()
is not supported, and handled properly with a suitable default if it is. Third rule, the defined behaviour of env()
means that it’s valid on env()
-supporting browsers, but treated as unset
if max()
isn’t defined, and so it overrides the second rule.
The currently necessary workaround is to shift the third rule into a @supports (margin-bottom: max(0, 15px))
block, but this is nasty.
A built-in
env()
is typed to one of theattr()
types, according to the name given to it, so you can type-check at parse time.
Should it still be assumed to be valid at parse time? If yes, I do not get the reason for type checking. It is required for attr()
mainly for back compat with untyped attr()
, if I am not mistaken. And a valid type does not always mean a valid value in the context.
Background
The syntax of the
calc()
function is defined "at the value level":https://drafts.csswg.org/css-values/#calc-notation
This allows both authors and implementers to only consider those functions in contexts where these value can be used. For example,
display: calc(…)
is never valid for any…
.The
var()
function however works "at the token stream level", before even considering the value syntax of a given property:https://drafts.csswg.org/css-variables/#using-variables
Being so general is useful for
var()
because many authors will be defining custom properties with arbitrary bits of syntax. We don’t want the spec to have to anticipate and enumerate each value type that could be used in custom property values.However, this causes
var()
to "infect" more of the language. The presence of avar()
function somewhere in the middle of a declaration affects the behavior of the entire declaration in ways that both authors and implementers need to be aware of and deal with:https://drafts.csswg.org/css-variables/#using-variables
In this sense,
var()
is a more fundamental part of CSS syntax than just another feature that can be used in values.env()
Currently, the syntax of the
env()
function is defined in a way very similar tovar()
:https://drafts.csswg.org/css-env-1/#env-function
I think it doesn’t need to be, and it should be redefined in a way similar to the
calc()
function: it is an additional set of valid values for a given set of types like<length>
.The spec defines a closed set of environment variables, and all of them are currently defined as
<length>
. In the future if/when new variables of other types are added (possibly<color>
?), we can extend parsing support to those types.Compat
I understand that this feature is already shipping in some browsers (despite not having a working draft yet). Hopefully it was only shipped recently enough that relatively few sites use it, and it is simple enough that they mostly use it ways (only using length variables in length contexts) that are similar to each other and not affected by this proposed change.