Closed emilio closed 2 years ago
err, s/at-futhark/@lilles :)
I'll defer to @tabatkins as he raised the original issue.
Adding @alisonmaher as I think this is even more interesting for forced-color-adjust. preserve-parent-color seems relevant here?
@emilio said:
Can you explain what's going on here?
I don't believe that Chromium has shipped the system color computing to itself behavior, which is likely why you're seeing the behavior you are.
There is a part of the Forced Colors Mode spec that I think relies on the decision in #3847:
if its computed value is a color other than a system color, its used value is instead forced to a system color
In other words, we force the colors at used value time, but do not force the color if it is a system color already. If system colors no longer compute to themselves, this won't be as clean to do (although in Chromium, we currently have a workaround for this, so we can continue to work around this if the decision is overturned).
I think this is even more interesting for forced-color-adjust. preserve-parent-color seems relevant here?
Because preserve-parent-color
forces the computed color
to the parent's used color
value, in general, it shouldn't matter if the system color computes to itself since we are only looking at the used value. But perhaps you are specifically referring to the interaction between system colors, color-scheme
and forced-color-adjust
, and that does seem pretty interesting.
In Chromium, we are currently ignoring color-scheme
when resolving system colors in Forced Colors Mode, even if forced-color-adjust
is none
/preserve-parent-color
. In other words, we always resolve the system colors to the OS defined system colors in Forced Colors Mode, no matter what color-scheme
is set to (which may not be the expected behavior).
However, if we do take color-scheme
into account, we could run into some pretty interesting use cases. For example:
<!doctype html>
<style>
:root { color-scheme: dark; }
span { forced-color-adjust: preserve-parent-color; color-scheme: light; }
</style>
I'm CanvasText in Forced Colors Mode <span>I should be light?</span>
In this case, the root's colors-scheme
will be overridden to light dark
. The span would set its color
value to the used value of its parent (i.e. the resolved value of CanvasText), which may not match the expected light color-scheme
of the span. We could run into similarly interesting cases with forced-color-adjust: none
.
@emilio said:
Can you explain what's going on here?
I don't believe that Chromium has shipped the system color computing to itself behavior, which is likely why you're seeing the behavior you are.
I'm testing on Chrome Dev with experimental features enabled (and I confirmed that https://wpt.live/css/css-color/system-color-compute.html passes on this browser, yet my test-case renders differently from what I'd expect). So it seems there's something else going on.
@emilio said:
Can you explain what's going on here?
I don't believe that Chromium has shipped the system color computing to itself behavior, which is likely why you're seeing the behavior you are.
I'm testing on Chrome Dev with experimental features enabled (and I confirmed that https://wpt.live/css/css-color/system-color-compute.html passes on this browser, yet my test-case renders differently from what I'd expect). So it seems there's something else going on.
Ah I see, so it only works when you explicitly set color
to CanvasText
, but not in the default case. In Chromium, the default for color
is still black
and hasn't been updated to CanvasText
, so that would explain why your test-case is behaving as it is.
Ugh, alright. So :root { color-scheme: dark }
does change the computed text style to light but it behaves differently from :root { color: CanvasText }
? That seems a bit broken, how do you implement the initial color switching for the root?
Anyways that explains what's going on to some extent, I guess.
I'm guessing this change has the details for how the initial color switching happens at the root https://chromium-review.googlesource.com/c/chromium/src/+/2224222, although @lilles or @andruud may be able to provide more details on that.
The CSS Working Group just discussed [css-color] [css-color-adjust] Consider reversing the resolution of #3847
.
Also in #3847 @upsuper said:
What I meant to say in #3847 (comment) is that Firefox's approach is not scalable to more keywords. It was done under the assumption that only currentcolor would behave that way. It's not touching serialization / Typed OM, but about the fact that animating with multiple parts which can change during animation could be challenging.
But I might be wrong because most complexity of animating currentcolor comes from the fact that color itself can animate as well, so there might be some smart way to handle all the system color keywords which can only discretely switch (AFAIU) without adding too much complexity to implementation, but I'm not sure.
Serialization / Typed OM is indeed something we need to consider, but I don't think that has or would introduce inherent complexity as far as we allow only linear combination of colors and keywords. But animation may have such complexity by itself.
As a reminder of the problem identified last time this was discussed:
emilio: always need to set in pairs, but even if you do, but if you change color-scheme without setting any color then behavior is broken if they compute to themselves emilio: because changing only foreground color and bg color doesn't change because not inherited
Also a reminder that this doesn't just affect currentColor
but also the (undeprecated) system colors, which leads to the css-color-5 issue on color-mix()
where the result is, in the worst case,
The "worst case" isn't unusual; you get the exact same effect for specified lengths, where you can only merge the absolute units.
The inheritance issue is indeed problematic tho, hm.
The CSS Working Group just discussed [css-color] [css-color-adjust] Consider reversing the resolution of #3847
.
So, regarding the Forced Colors edits: currently, Forced Colors Mode will leave a color alone if it's a system color, and only actually "force" them (into a predefined system color) otherwise. If we want to preserve this ability (so someone can, for example, purposely invert colors for an element), we'll need to specify that, while system colors compute to absolutes, they retain the fact that they were system colors (in an invisible-to-authors fashion, so no-op setting a color property to itself would clear this info).
Is there more issues to think about? @alisonmaher ?
@tabatkins That's correct. And since I don't think this has been mentioned in the thread directly, to summarize, the proposal is to reverse the resolution in both #3847 and #4915.
(And I'm guessing there might be resolutions related to color-scheme
and the default color
value that would need to be reverted, as well?)
The reasons why forcing colors at used value time was desirable is well summarized by two of @fantasai's comments: https://github.com/w3c/csswg-drafts/issues/4915#issuecomment-701674628 and https://github.com/w3c/csswg-drafts/issues/4915#issuecomment-707969753.
One point of which is how inheritance works when forced-color-adjust:none
is set (i.e. we would inherit the parent's forced color styles rather than the non-forced color values). This would be a behavior change from what is currently shipped in Chromium.
As a result of this behavior change, though, we would no longer need to add preserve-parent-color
to address the issue raised in #6310.
And reverting back to forcing colors at computed value time would likely re-raise the following issue: https://github.com/w3c/csswg-drafts/issues/5419#issuecomment-724269096.
while system colors compute to absolutes, they retain the fact that they were system colors (in an invisible-to-authors fashion, so no-op setting a color property to itself would clear this info).
A single bit to indicate was_system_color
or an indication of which system color it was? (Some of them compute to the same color)
Single bit for provenance; all we need to know is if they were originally system colors so we know not to force them.
Can we re-title this issue? The indirection makes it hard to remember what it's about, every time.
I see this is second on the agenda tomorrow. @emilio @alisonmaher @tabatkins @smfr are we ready to resolve on this on the call?
I think I would be ok resolving to revert both https://github.com/w3c/csswg-drafts/issues/3847# and https://github.com/w3c/csswg-drafts/issues/4915# as long as the following are met:
Sounds good to me, let's suggest that plan on the call.
The CSS Working Group just discussed Making system colors fully resolve
.
So, in 4.7.5. Resolving other colors, instead of
Each
<system-color>
keyword computes to itself. Its used value is the corresponding color in its color space.
we would have
Each
<system-color>
keyword computes to the corresponding color in its color space. However, implementations which also implement [forced color mode]() must maintain an internal flag for such colors, to preserve the fact that they were specified as system colors and must not be forced.
(I would link to the specs but drafts are down again)
We would also need a change to 4.8.6. Serializing other colors because currently, <system-color>
keywords serialize as the lowercased name. They would now serialize as rgb(nn, nn, nn)
same as named colors, right?
OK, I reread the entire conversation.
Arguments for resolving system colors at computed value time:
color-scheme
shouldn't cause system colors to change when they inherit through. I disagree with this argument. I don't think the proposed behavior is more reasonable, why would you change color-scheme
if you are not wanting the color scheme to actually change and are prepared for that?Arguments for resolving system colors at used value time:
forced-color-adjust: none
should actually turn off the effect of forced colors within that element. Giving a subtree an unexpected mix of forced and unforced colors can cause contrast problems.What it boils down to for authors is:
forced-color-adjust: none
have to explicitly set every inherited color property on that element to their own chosen color, or the element could have unreadable contrast. They have to know that forced colors mode on an ancestor will break color inheritance into this element, and compensate for that by redeclaring any such colors on the element using forced-color-adjust: none
.color-scheme
unless they're actually changing their color scheme. Which seems... obvious?The CSS Working Group just discussed [css-color] [css-color-adjust] Make system colors fully resolve (but flag they were system colors) thus reversing the resolution of #3847
.
Overview
The key question here is whether system color keywords compute to an absolute color, and inherit as such, or whether they remain keywords and resolve at used-value time. My takeaway from the WG discussion was that the hangup here is, “which one results in more authoring mistakes?”
The two examples to think about are how this is impacted by color-scheme
and forced-color-adjust
changes specified by an author on a descendant of the root.
Color Scheme Example
Emilio's example in the OP, showing the effects of author-specified color-scheme
changes:
<!doctype html>
<style>
:root { color-scheme: dark } /* light text, dark background */
span { color-scheme: light } /* dark text, light background */
</style>
I'm dark, and <span>I'm light</span>.
In this case the body text is light on a dark background. If system colors compute to absolute before inheriting, then “I'm light” will be light text. Is this expected or unexpected, and will it cause more or fewer mistakes than if we resolved after inheritance (yielding dark text for the element with color-scheme: light
)?
Forced Colors Example
The other example is about forced-color-adjust
, and was brought up when we originally decided to make them resolve at used-value time:
<!doctype html>
<style>
:root { color: black; background: white; }
.special { forced-color-adjust: none; background: yellow; }
</style>
Author says I'm black on white.
<div class=".special">
What color am I?
</div>
In this case, the text is black on white. If forced-colors mode is on, however, the text might be forced to be, e.g. white on black. But inside the “special” div, we've turned off forced-colors mode.
If system colors compute to absolute before inheriting, then in forced white-on-black mode “What color am I?” will be white. Is this expected or unexpected, and will it cause more or fewer readability problems than if we resolved after inheritance (yielding the original color, black)?
Issue
The proposed change is to make the system colors compute to absolute before inheriting. The existing spec is to have them resolve on the element to which they apply.
I have been discussing this issue with @smfr, and WebKit believes that system colors should resolve at used-value time, after inheritance. Our reasoning is as follows:
color-scheme
boundary implies authors want to further customize the appearance of their content. We would expect authors to test accordingly and observe the consequences of intentional color-scheme
changes, limiting the occurrence of legibility issues.color-scheme
boundaries, without any other author style changes. Authors may expect system colors to match form controls. In this scenario, we believe that authors should not need to redeclare color
in order to match the current color-scheme
.color-scheme
boundary changes to be a rare use case. Additional data is needed to back this claim.I haven't seen much discussion on this point:
There's an argument that managing computed values that are linear combinations of, potentially, 18 different channels is unweildy. I take this argument as valid.
Are implementers all on board with that (rare, but certainly possible) complexity?
There's an argument that managing computed values that are linear combinations of, potentially, 18 different channels is unweildy. I take this argument as valid.
Are implementers all on board with that (rare, but certainly possible) complexity?
I don't have enough context on this particular argument to comment, but based on the discussion in https://github.com/w3c/csswg-drafts/issues/5780#issuecomment-1019490769, it isn't clear to me in what case we would have 18 different combinations. Could you provide an example?
Regarding this issue as a whole, I see arguments on both side and could likely come up with use cases to support either decision, so I personally could live with either path forward (i.e. reverting the relevant issues or keeping things as-is). However, this issue is currently blocking us from shipping forced-color-adjust: preserve-parent-color
in Chromium, which will help solve a use-case related to SVGs and currentColor
that is a developer pain point. So it would be useful to come to a consensus on this either way.
One thing that I will say is that I do think the use cases provided by @fantasai are valid. Particularly related to Forced Colors Mode. If we revert back to computed value time, authors won't have the option to inherit through the original colors when forced-color-adjust: none
is set. Whereas with the current approach, developers can choose between three forced-color-adjust
values to get varying behavior.
The current use cases to support having system colors compute to themselves aren't as convincing to me, so perhaps a less controversial path forward here would be to leave Forced Colors Mode alone (i.e. continue to force colors at used value time), and only revert https://github.com/w3c/csswg-drafts/issues/3847. This would require us to add a bit to tell if a color was a system color, as @tabatkins mentioned, but I don't think this should be a problem. Thoughts?
- Form controls already change their appearance across
color-scheme
boundaries, without any other author style changes. Authors may expect system colors to match form controls. In this scenario, we believe that authors should not need to redeclarecolor
in order to match the currentcolor-scheme
.
When would that happen? Form controls already specify color
and background-color
(and borders and so on) in the UA stylesheet, e.g. here for WebKit. Do you have an example that behaves suboptimally if we resolve at computed-value time?
I don't have enough context on this particular argument to comment, but based on the discussion in https://github.com/w3c/csswg-drafts/issues/5780#issuecomment-1019490769, it isn't clear to me in what case we would have 18 different combinations. Could you provide an example?
When you interpolate system colors, if they resolve at used value time, in order to interpolate correctly you need to keep N channels (where N is the number of system colors), plus currentColor, plus potentially others.
I guess you can express it as a color-mix()
, but I don't recall whether color-mix()
resolved at computed or used-value time either per spec (in Gecko it resolves at computed-value time).
- Form controls already change their appearance across
color-scheme
boundaries, without any other author style changes. Authors may expect system colors to match form controls. In this scenario, we believe that authors should not need to redeclarecolor
in order to match the currentcolor-scheme
.
Err, upon re-reading I think I may have misread this, and now I'm not sure about the argument you're trying to make.
I guess the argument you're making is that you want every element to react to color-scheme changes if it inherits a system color for consistency with form controls? If so... I don't think that's great behavior because color
would react to the change but backgrounds won't. Do you expect authors to re-declare backgrounds but not need to re-declare colors? (Sorry if that's not the argument you're making though).
In any case, I think the behavior of resolving at computed-value time is generally what authors would expect / the less error-prone behavior. The fact that we needed to add this behavior to force-color-adjust
by default is kind of telling, IMO (https://github.com/w3c/csswg-drafts/issues/6310), and as @alisonmaher says the use-cases for this for forced colors can be achieved in other ways if necessary.
I don't recall whether color-mix() resolved at computed or used-value time either per spec (in Gecko it resolves at computed-value time).
There is a reason you don't recall :) In CSS Color 5, Resolving <color>
Values doesn't have a section on resolving color-mix()
. It should.
It does have Serializing color-mix(), which serializes the result as a single color in the specified colorspace used for mixing.
For comparison, color-contrast()
resolves at computed value time
You mean at used value time right? in that example, color-contrast()
is preserved in the computed value (which means that it resolves at used value time).
Yes the individual color values are resolved at computed value time, and at used value time the whole function is evaluated and the winning result is the used value.
The fact that we needed to add this behavior to force-color-adjust by default is kind of telling, IMO (https://github.com/w3c/csswg-drafts/issues/6310), and as @alisonmaher says the use-cases for this for forced colors can be achieved in other ways if necessary.
@emilio My comment in https://github.com/w3c/csswg-drafts/issues/6310 was referring to the fact that authors can currently achieve the results they want by setting forced-color-adjust: auto
for SVG icons given that things happen at used value time. However, I do think that moving forced colors to computed value time will give authors more limited options in achieving the results they want.
Here's an example that I found while searching around: https://codepen.io/AmeliaBR/pen/xxJBgp. In this example, Chromium and Gecko produce different results in Forced Colors Mode due to the difference in inheritance (see screenshots below). I think Chromium's behavior is more likely to be what an author would expect in this case. Would an author have an option to get this case working in Gecko without re-declaring the original (non-forced) styles that would have been inherited previously?
Here's an example that I found while searching around: https://codepen.io/AmeliaBR/pen/xxJBgp.
I see the same rendering between Chrome and Firefox on this example. I also don't see how it should differ based on used-value time vs. computed-value time. Seemingly it is only about how SVG use
works.
I think Alison's screenshot is with HCM enabled (in firefox: about:preferences > colors > set dropdown to "always"). I can reproduce her results on macOS.
@emilio My comment in #6310 was referring to the fact that authors can currently achieve the results they want by setting
forced-color-adjust: auto
for SVG icons given that things happen at used value time. However, I do think that moving forced colors to computed value time will give authors more limited options in achieving the results they want.
That is true.
Here's an example that I found while searching around: https://codepen.io/AmeliaBR/pen/xxJBgp. In this example, Chromium and Gecko produce different results in Forced Colors Mode due to the difference in inheritance (see screenshots below). I think Chromium's behavior is more likely to be what an author would expect in this case.
Is it though? If you use currentColor, you most likely want stuff to fit with the surrounding text, which is Gecko's behavior.
In any case guessing author intent is hard, and you're right that forcing colors at computed value time the original author-specified colors are lost down the tree (though that's different from the question of whether system colors should themselves resolve at computed or used value time).
Is it though? If you use currentColor, you most likely want stuff to fit with the surrounding text, which is Gecko's behavior.
Yeah, I think that is a more common scenario, so having that be the default behavior for SVGs makes sense. But I do think having the option to inherit through the original values can be valuable if that's what the author does end up wanting in certain situations.
In any case guessing author intent is hard, and you're right that forcing colors at computed value time the original author-specified colors are lost down the tree (though that's different from the question of whether system colors should themselves resolve at computed or used value time).
Agreed, I think these would be two independent resolutions. And the arguments for resolving system colors at computed value time do seem valid to me.
Agreed, I think these would be two independent resolutions. And the arguments for resolving system colors at computed value time do seem valid to me.
The only thing stopping me pushing hard for fully resolving system colors at computed value time, is concern on the knock-on impact for forced colors mode (the arguments around which I am not sure I fully understand).
I'm not arguing for fully resolving currentColor, btw. Handling that one special case seems necessary, and tractable.
The only thing stopping me pushing hard for fully resolving system colors at computed value time, is concern on the knock-on impact for forced colors mode (the arguments around which I am not sure I fully understand).
Are you referring to the bit that would be needed to tell if something was a system color (which would be needed if forced colors mode happens at used value time while system colors resolve at computed value time)? Fwiw we are currently doing something similar to this in Chromium today.
I was assuming we had consensus that the bit would be needed, per the adjusted title of this issue.
Gotcha, I think we can continue to force colors at used value time while system colors resolve at computed value time (as long as we have that bit). Was there some other impact on forced colors mode you were referring to in your previous comment?
Perhaps you were referring to some of the impacts noted in https://github.com/w3c/csswg-drafts/issues/6773#issuecomment-1062008116? These points were assuming we would also move forced colors mode to computed value time, but if we decide to leave forced colors mode alone, then those impacts should no longer be an issue.
Thanks for the clarification @alisonmaher in that case I think we could propose to adopt the resolution in the issue title.
As proposed earlier:
So, in 4.7.5. Resolving other colors, instead of
Each
<system-color>
keyword computes to itself. Its used value is the corresponding color in its color space.
we would have
Each
<system-color>
keyword computes to the corresponding color in its color space. However, implementations which also implement [forced color mode]() must maintain an internal flag for such colors, to preserve the fact that they were specified as system colors and must not be forced.
We would also need a change to 4.8.6. Serializing other colors because currently, <system-color>
keywords serialize as the lowercased name. They would now serialize as rgb(nn, nn, nn)
same as named colors, right?
This will also require changes to WPT system-color-compute.html
That looks correct to me. There may be some updates needed around color-scheme, as well. (i.e. to clarify that it affects the computed value of system colors)
The CSS Working Group just discussed 5. [css-color] [css-color-adjust] Make system colors fully resolve (but flag they were system colors) thus reversing the resolution of #3847
, and agreed to the following:
RESOLVEDS: System colors compute to absolute colors, but must not be affected by forced-colors mode.
RESOLVED: Republish css-color-4 and css-color-5
In https://github.com/w3c/csswg-drafts/issues/3847 it was resolved that system colors would compute to themselves. The argument for that seems sensible (which is that color-scheme would be automatically honored for them while inheriting).
However I'm not so sure that's great behavior (plus there are still open issues from that change like https://github.com/w3c/csswg-drafts/issues/5780).
In particular, in order to guarantee contrast, you need to use system color pairs (the foreground and the background), such as:
If color-scheme changes, but the author doesn't specify a background, making system colors compute to themselves at computed-value time breaks contrast (rendered):
The
span
should be dark text over dark background per spec, since it inherits the initial color which iscanvastext
, which is undesirable.That's clearly not how browsers are working today, which confuses me because I thought Chrome implemented this change.
I'd expect this test-case to render per spec the same as the following (rendered):
(which is clearly undesirable, and not what's going on).
Can you explain what's going on here @futhark / @andruud / @kbabbitt / @tabatkins?
cc @smfr