Follow-up to https://github.com/w3c/csswg-drafts/issues/4175
We are now computing background-color based on the computed 'color' value. (Specifically, we're keying off the computed color to find a color, and forcing all but the author’s specified alpha channel to match that color.)
Is this computation at computed-value time or used-value time for background-color?
It has to be at used-value time, due to the system color keywords not resolving until then, right?
If the adjusted background-color exists computed-value time, it means there are two right answers (/two wrong answers?) for what the computed-value is for background-color. The author alpha doesn't exist separately from background-color, hence the computed value of background-color must be computed to know the author alpha. We shouldn't then suddenly redefine the computed value after it has already been determined the first time. So used-value time seems like a better choice.
It would be a little weird if all the other forced-color effects are available at computed time, but background-color is naively reported without adjustments.
It has to be at used-value time, due to the system color keywords not resolving until then
Yes, but the other colors at least compute correctly to a system color.
Ideally, we would define the computed value of background-color using one of the Colors Level 5 mixing functions: this system color, but adjusted to use that alpha.
the computed value of background-color must be computed to know the author alpha. We shouldn't then suddenly redefine the computed value after it has already been determined
This is also true, and trickier to work around.
Logically, we want to treat background-color as if it is a shorthand for background-color-color and background-color-alpha. The author computed background-color sets both of these hypothetical longhands, and then the forced user agent style overrides the first but not the second. Using that shorthand/longhand model, the overriding would happen at computed value time.
A comparison would be font: caption; font-size: 16px. There is no way to serialize a font shorthand representing the combined result without first substituting the expanded value of the system font keyword.
But of course, the difference is that system fonts _are_ substituted at computed time, and they _do_ have longhand properties that can inherit only part of that value. So, I'm not sure we can reuse that model without a serious re-think to all this.
The CSS Working Group just discussed Is forced background-color computed or used value?, and agreed to the following:
RESOLVED: The color computed values are defined as used values using the color mix functionThe full IRC log of that discussion
<dael> Topic: Is forced background-color computed or used value?
<dael> github: https://github.com/w3c/csswg-drafts/issues/4915
<dael> Rossen_: This is in the context of forced-colors-adjust.
<dael> Rossen_: Short summary is in the previous impl of what used to be ms-high-contrast and is now forced-colors we were reverting colors incl background and override with system color
<dael> Rossen_: At the time at MS we didn't consider taking in the alpha channel of the color value.
<dael> Rossen_: That meant simply all overrides were stable during computed and alpha was always opauqe
<dael> Rossen_: When working on focred-colors one addition to the feature is addition of considering alpha channel of the color. For background-color it means we now have to compute the final color value during used time
<dael> Rossen_: That's how issue came about. Is it supposed to be computed or used.
<dael> Rossen_: Is amelia_br or fantasai on?
<dael> TabAtkins: We have me and I commented
<dael> TabAtkins: Seems clear this needs to be used value time transformation
<dael> TabAtkins: because we can't do anything with a system color until we resolve it which is at used value time b/c need to know color scheme
<dael> TabAtkins: This is to make it reasonably work. Amelia's comment brings up the very good point that color mix function would be useful for this.
<dael> TabAtkins: If you want to define a computed value that's this system color but with the other alpha you can do it simply with color mix function.
<dael> TabAtkins: We could and should define computed value of color becomes a color mix function with alpha from previous color and mix with system color
<dael> Rossen_: What do we serialize in OM? used value?
<dael> TabAtkins: All current properties do used
<dael> Rossen_: Okay, that continues to work
<dael> TabAtkins: Not 100% clear/remember what happens in image properties. But plain colors do used value.
<dael> Rossen_: The ones covered by color-adjust are covered.
<dael> TabAtkins: Yeah
<dael> emilio: Not quite used value because :visited and so on
<dael> Rossen_: sure
<dael> TabAtkins: Ignoring :visited hacks they are always used values
<dael> Rossen_: Sounds like a good path forward for this behavior
<dael> Rossen_: Any other considerations or proposals to consider?
<dael> TabAtkins: If we go with this is means impl should prioritize color mix.
<dael> TabAtkins: Also solves that you can't transition system colors since you can use color mix for that.
<dael> Rossen_: Sounds good
<dael> Rossen_: Looking for your summary
<TabAtkins> color-mix(appropriate-system-color, specified-color a(100%))
<dael> Rossen_: Prop: The color computed values are defined as used values using the color mix function
<dael> Rossen_: Objections?
<dael> RESOLVED: The color computed values are defined as used values using the color mix function
@tabatkins wrote:
color-mix(appropriate-system-color, specified-color a(100%))
As specified, that will do the [color interpolation(https://drafts.csswg.org/css-color-5/#interpolation) in the LCH colorspace, which is the default. That might not be what is wanted however. I can think of two other options:
color-mix(appropriate-system-color, specified-color a(100%) srgb)
That will mix in the (gamma corrected, i.e. non-linear-light) sRGB colorspace, which is what the current implementations do when mixing colors. It gives the wrong (too dark) result but is compatible with lots of software which also does it the wrong way :) It is also constrained to the sRGB gamut, which is temporarily fine but won't be over time as some system colors may be specified in, for example, display-p3.
color-mix(appropriate-system-color, specified-color a(100%) xyz)
That will get you linear-light mixing, _ie_ the correct result for the physical mixture of two lights, and avoids gamut mapping. It gives the same result as mixing in linear-light sRGB, except without the gamut limitation.
This might be moot anyway (if it's specified as happening at used-value time, from other issues), but if we do keep with "forced-colors mode causes the computed value to be a color-mix() function", it's only mixing alpha (and "mixing" only in a degenerate sense; it's just taking the source color's alpha entirely); the rest of the channels are taken solely from the system color. So the colorspace we mix in is moot.
Given the resolutions to adopt @FremyCompany's proposals in #4915, I would like to re-open this issue (see https://github.com/w3c/csswg-drafts/issues/4178#issuecomment-700481333).
The concept we adopted in #4178 was to treat non-system colors as “out of gamut”, effectively. Mapping out-of-gamut colors is a used-value time operation, not a computed-value time operation. There's an added benefit to making this a used-value time operation: it also makes the question of transitions and interpolation (see #5419) go away. So my proposal is that we go with making this a used-value time operation rather than a computed-value time operation.
Next question is, should we do the same for the other affected properties (which are not color properties) and force their used values to the initial value, instead of their computed values?
Another (important, imho) benefit of forcing colors at used-value time is that they will inherit as the author-specified value, so if a subtree opts out of forced colors, it will use the author-specified colors, not a mix of author-specified and forced colors.
I wanted to +1 @fantasai's point in the above comment.
In particular, forcing at used-value time will likely be helpful for the following case:
<style>
body {
color: red;
}
</style>
<body>
<svg>
<rect width="100" height="100" fill="white"/>
<text x="0" y="15" fill="currentColor">Test</text>
</svg>
</body>
We set forced-color-adjust: none on SVG elements in Forced Colors Mode. Thus, in the above example, when Forced Colors Mode is enabled to the white-on-black theme, the SVG rectangle will remain filled with white, but since the text fill is set to currentColor, it will become unreadable since it will inherit a white forced color from its parent.
If we were to force colors at used-value time, instead, the text fill would pick up red and would remain readable in Forced Colors Mode.
So although changing this to used-value time may complicate the implementation a bit, I do think that there is value in changing this to a used-value time operation.
The CSS Working Group just discussed [css-color-adjust-1] Is forced color computed or used value?, and agreed to the following:
RESOLVED: Forced colors happens at used value time. Add a note to the spec about implementationThe full IRC log of that discussion
<emilio> topic: [css-color-adjust-1] Is forced color computed or used value?
<fantasai> github: https://github.com/w3c/csswg-drafts/issues/4915#issuecomment-701674628
<emilio> fantasai: I reopened in light on the resolutions we took to adopt fremy's proposal in the previous issue
<emilio> ... we decided than rather than a cascade-time revert and consider them out-of-gamut for non-system colors
<emilio> ... which is a different technique
<emilio> ... but out-of-gamut mapping is a used-time op, not computed-time
<emilio> ... by doing it at computed-value time we got weird behavior wrt ??
<emilio> s/??/inheritance
<emilio> fantasai: when you do it at computed value then it'll change it to system color and then inherit
<emilio> ... if you have a forced-color-adjust: none section down the page
<emilio> ... expecting a particular inherited color
<emilio> ... but instead you get a random system color which might be unreadable
<emilio> ... if we do this mapping at used value time
<florian> q+
<emilio> ... then it inherits the color properly
<fremy> q+
<emilio> ... but the ancestor with forced colors will get the system color at used value time
<emilio> q+
<emilio> TabAtkins: I think I agree on this, makes much more sense
<TabAtkins> s/think I/strongly/
<emilio> Rossen_: it also avoids the dependency with color-mix()
<florian> q-
<emilio> fantasai: yeah prevents all the interpolation / transitions issues
<Rossen_> ack fremy
<emilio> fremy: I think it does make a lot of sense but I want to make sure something is recorded
<emilio> ... if you have a disabled button, the default style will have some color
<emilio> ... let's say text -> disabledtext
<emilio> ... so it can be made at used value time
<emilio> ... but for children you need to walk the parent chain to know which color to reset
<emilio> ... basically what you map to depends on the UA stylesheets
<emilio> ... so you kinda need to remember this in a way
<emilio> florian: [restates the issue]
<emilio> TabAtkins: it is indeed a separate concern
<emilio> fremy: I wanted to make sure it's mentioned because it's an important impl detail
<emilio> fantasai: Yeah we should note that in the spec
<emilio> florian: if we were doing it at computed value time you wouldn't have to do it so it's the same issue, not separate
<Rossen_> q
<Rossen_> ack emilio
<fantasai> emilio: Wanted to mention related point
<fantasai> emilio: this is a problem, even if you don't account for children
<fantasai> emilio: if you specify color on the button
<fantasai> emilio: in order to know what to revert to, need to do the cascade again
<fantasai> emilio: to find the original value to revert to
<fantasai> emilio: that would be my main concern
<fantasai> emilio: I think it could be addressed with something like an internal CSS property
<fantasai> emilio: but different from what browsers currently do
<fantasai> fremy: similar to :visited
<fantasai> emilio: similar, but different
<fantasai> fremy: this is what Edge did, we tracked a separate value
<fantasai> s/value/value for :visited
<fantasai> Rossen_: We had a cascaded value alongside, for anything that was overridden, so that we didn't have to go and recascade
<fantasai> Rossen_: we would have it at hand, ready to use as needed.
<fantasai> Rossen_: Added a little bit of memory, but relatively insignificant
<fantasai> Rossen_: so from impl pov, this is very doable, not much additional context needed
<fantasai> Rossen_: but fremy's point that it's not automatic is relevant
<emilio> RESOLVED: Forced colors happens at used value time. Add a note to the spec about implementation
Most helpful comment
@tabatkins wrote:
As specified, that will do the [color interpolation(https://drafts.csswg.org/css-color-5/#interpolation) in the LCH colorspace, which is the default. That might not be what is wanted however. I can think of two other options:
color-mix(appropriate-system-color, specified-color a(100%) srgb)That will mix in the (gamma corrected, i.e. non-linear-light) sRGB colorspace, which is what the current implementations do when mixing colors. It gives the wrong (too dark) result but is compatible with lots of software which also does it the wrong way :) It is also constrained to the sRGB gamut, which is temporarily fine but won't be over time as some system colors may be specified in, for example,
display-p3.color-mix(appropriate-system-color, specified-color a(100%) xyz)That will get you linear-light mixing, _ie_ the correct result for the physical mixture of two lights, and avoids gamut mapping. It gives the same result as mixing in linear-light sRGB, except without the gamut limitation.