Title: [273244] trunk
Revision
273244
Author
wei...@apple.com
Date
2021-02-22 09:52:13 -0800 (Mon, 22 Feb 2021)

Log Message

Add experimental support for CSS Color 5 color-mix()
https://bugs.webkit.org/show_bug.cgi?id=222258

Reviewed by Antti Koivisto.

Source/WebCore:

Adds initial support for CSS Color 5 color-mix() - https://drafts.csswg.org/css-color-5/#color-mix

This feature is off by default and can be enabled via the CSSColorMixEnabled
experimental preference flag.

This implementation has the same restriction on it that the recently landed
Relative Color Syntax does in that it does support system colors or currentColor
as input, since those can't be resolved at parse time. Ultimately, we will need
to add a late binding version of this for those cases.

Test: fast/css/parsing-color-mix.html

* css/CSSValueKeywords.in:
Add new keywords needed for color-mix().

* css/parser/CSSParserContext.cpp:
(WebCore::operator==):
* css/parser/CSSParserContext.h:
(WebCore::CSSParserContextHash::hash):
Add new setting for color-mix().

* css/parser/CSSPropertyParserHelpers.cpp:
(WebCore::CSSPropertyParserHelpers::HueColorAdjuster::fixupAnglesForInterpolation):
(WebCore::CSSPropertyParserHelpers::HueColorAdjuster::HueColorAdjuster):
(WebCore::CSSPropertyParserHelpers::ColorAdjuster::ColorAdjuster):
(WebCore::CSSPropertyParserHelpers::consumeAdjuster):
(WebCore::CSSPropertyParserHelpers::consumeAndUpdateAdjusterAtIndex):
(WebCore::CSSPropertyParserHelpers::consumeAndUpdateAdjuster):
(WebCore::CSSPropertyParserHelpers::consumeAdjusters):
(WebCore::CSSPropertyParserHelpers::consumeMixComponents):
(WebCore::CSSPropertyParserHelpers::normalizeAdjusterValues):
(WebCore::CSSPropertyParserHelpers::remainingAdjustment):
(WebCore::CSSPropertyParserHelpers::mixComponent):
(WebCore::CSSPropertyParserHelpers::mixComponentAtIndex):
(WebCore::CSSPropertyParserHelpers::makeColorTypeByNormalizingComponentsAfterMix):
(WebCore::CSSPropertyParserHelpers::makeColorTypeByNormalizingComponentsAfterMix<HWBA<float>>):
(WebCore::CSSPropertyParserHelpers::mix):
(WebCore::CSSPropertyParserHelpers::parseColorMixFunctionParametersUsingAdjusters):
(WebCore::CSSPropertyParserHelpers::parseColorMixFunctionParameters):
(WebCore::CSSPropertyParserHelpers::parseColorFunction):
The implementation uses a templatized ColorAdjuster struct to declartively map the
various mixing color spaces to their allowed adjusters and what type those adjusters
operate on (either double or the hue specific HueColorAdjuster). For example, for
LCHA we have:

    using LCHColorAdjuster = ColorAdjuster<LCHA<float>, CSSValueLightness, double, CSSValueChroma, HueColorAdjuster, CSSValueHue, double, CSSValueAlpha, double>;

which indicates:

    - it creates a LCHA<float> and will operate on LCHA<float> values
    - its first channel is called "lightness" and is a double
    - its second channel is called "chroma" and is a double
    - its third channel is called "hue" and is a HueColorAdjuster
    - its fourth channel is called "alpha" and is a double

This data is then used by the parsing and mixing functions to implement mixing without
having to write specific implementations for each mixing color space and can be expanded
to more spaces if needed.

* platform/graphics/Color.h:
(WebCore::Color::Color):
Add new overloaded constructor for a generic Optional<ColorType<float>> which parallels the existing
Optional<SRGBA<uint8_t>> allowing callers to convert WTF::nullopt to an invalid Color without checking
for nullopt themselves.

Source/WTF:

* Scripts/Preferences/WebPreferencesExperimental.yaml:
Add new experimental preference for CSS Color 5 color-mix()
which is off by default.

LayoutTests:

* fast/css/parsing-color-mix-expected.txt: Added.
* fast/css/parsing-color-mix.html: Added.
Add parsing and computed style computation tests for color-mix().

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (273243 => 273244)


--- trunk/LayoutTests/ChangeLog	2021-02-22 17:33:21 UTC (rev 273243)
+++ trunk/LayoutTests/ChangeLog	2021-02-22 17:52:13 UTC (rev 273244)
@@ -1,5 +1,16 @@
 2021-02-22  Sam Weinig  <wei...@apple.com>
 
+        Add experimental support for CSS Color 5 color-mix()
+        https://bugs.webkit.org/show_bug.cgi?id=222258
+
+        Reviewed by Antti Koivisto.
+
+        * fast/css/parsing-color-mix-expected.txt: Added.
+        * fast/css/parsing-color-mix.html: Added.
+        Add parsing and computed style computation tests for color-mix().
+
+2021-02-22  Sam Weinig  <wei...@apple.com>
+
         Update web-platform-tests/css/css-color
         https://bugs.webkit.org/show_bug.cgi?id=222235
 

Added: trunk/LayoutTests/fast/css/parsing-color-mix-expected.txt (0 => 273244)


--- trunk/LayoutTests/fast/css/parsing-color-mix-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/css/parsing-color-mix-expected.txt	2021-02-22 17:52:13 UTC (rev 273244)
@@ -0,0 +1,204 @@
+Test the parsing of CSS Color 5 color-mix().
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+color-mix(hsl, ...)
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%), hsl(30deg 30% 40%))") is "rgb(84, 92, 61)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) 25%, hsl(30deg 30% 40%))") is "rgb(112, 106, 67)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%), hsl(30deg 30% 40%) 25%)") is "rgb(61, 73, 54)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) 25%, hsl(30deg 30% 40%) 75%)") is "rgb(112, 106, 67)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) 50%, hsl(30deg 30% 40%) 150%)") is "rgb(112, 106, 67)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) 12.5%, hsl(30deg 30% 40%) 37.5%)") is "rgb(112, 106, 67)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) 0%, hsl(30deg 30% 40%))") is "rgb(133, 102, 71)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 50%, hsl(30deg 30% 40%))") is "rgb(53, 56, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%), hsl(30deg 30% 40%) hue 50%)") is "rgb(53, 56, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 50%, hsl(30deg 30% 40%) hue 50%)") is "rgb(53, 56, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 100%, hsl(30deg 30% 40%))") is "rgb(46, 56, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 50%, hsl(30deg 30% 40%))") is "rgb(53, 56, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 50% saturation 100%, hsl(30deg 30% 40%))") is "rgb(53, 56, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 50% saturation 100% lightness 100%, hsl(30deg 30% 40%))") is "rgb(53, 56, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 50% saturation 100% lightness 100% alpha 100%, hsl(30deg 30% 40%))") is "rgb(53, 56, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 50%, hsl(30deg 30% 40%) saturation 0%)") is "rgb(53, 56, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 50%, hsl(30deg 30% 40%) saturation 0% lightness 0%)") is "rgb(53, 56, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 50%, hsl(30deg 30% 40%) saturation 0% lightness 0% alpha 0%)") is "rgb(53, 56, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 50% saturation 75%, hsl(30deg 30% 40%))") is "rgb(55, 59, 43)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 50% saturation 75% lightness 15%, hsl(30deg 30% 40%))") is "rgb(101, 108, 80)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 50% saturation 75% lightness 15% alpha 10%, hsl(30deg 30% 40%))") is "rgb(101, 108, 80)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 60%, hsl(30deg 30% 40%))") is "rgb(52, 56, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%), hsl(30deg 30% 40%) hue 40%)") is "rgb(52, 56, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 60%, hsl(30deg 30% 40%) hue 40%)") is "rgb(52, 56, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 60% hue 40%, hsl(30deg 30% 40%))") is "rgb(55, 56, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 40%, hsl(30deg 30% 40%))") is "rgb(55, 56, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue, hsl(30deg 30% 40%))") is "rgb(56, 51, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 0%, hsl(30deg 30% 40%))") is "rgb(56, 51, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) -10%, hsl(30deg 30% 40%))") is "rgb(133, 102, 71)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) 0%, hsl(30deg 30% 40%))") is "rgb(133, 102, 71)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue -10%, hsl(30deg 30% 40%))") is "rgb(56, 51, 46)"
+PASS computedStyle("background-color", "color-mix(hsl, hsl(120deg 10% 20%) hue 0%, hsl(30deg 30% 40%))") is "rgb(56, 51, 46)"
+
+color-mix(hwb, ...)
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%), hwb(30deg 30% 40%))") is "rgb(147, 179, 52)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) 25%, hwb(30deg 30% 40%))") is "rgb(166, 153, 64)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%), hwb(30deg 30% 40%) 25%)") is "rgb(96, 191, 39)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) 25%, hwb(30deg 30% 40%) 75%)") is "rgb(166, 153, 64)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) 50%, hwb(30deg 30% 40%) 150%)") is "rgb(166, 153, 64)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) 12.5%, hwb(30deg 30% 40%) 37.5%)") is "rgb(166, 153, 64)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) 0%, hwb(30deg 30% 40%))") is "rgb(153, 115, 77)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 50%, hwb(30deg 30% 40%))") is "rgb(160, 204, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%), hwb(30deg 30% 40%) hue 50%)") is "rgb(160, 204, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 50%, hwb(30deg 30% 40%) hue 50%)") is "rgb(160, 204, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 100%, hwb(30deg 30% 40%))") is "rgb(26, 204, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 50%, hwb(30deg 30% 40%))") is "rgb(160, 204, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 50% whiteness 100%, hwb(30deg 30% 40%))") is "rgb(160, 204, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 50% whiteness 100% blackness 100%, hwb(30deg 30% 40%))") is "rgb(160, 204, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 50% whiteness 100% blackness 100% alpha 100%, hwb(30deg 30% 40%))") is "rgb(160, 204, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 50%, hwb(30deg 30% 40%) whiteness 0%)") is "rgb(160, 204, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 50%, hwb(30deg 30% 40%) whiteness 0% blackness 0%)") is "rgb(160, 204, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 50%, hwb(30deg 30% 40%) whiteness 0% blackness 0% alpha 0%)") is "rgb(160, 204, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 50% whiteness 75%, hwb(30deg 30% 40%))") is "rgb(163, 204, 39)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 50% whiteness 75% blackness 15%, hwb(30deg 30% 40%))") is "rgb(130, 161, 39)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 50% whiteness 75% blackness 15% alpha 10%, hwb(30deg 30% 40%))") is "rgb(130, 161, 39)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 60%, hwb(30deg 30% 40%))") is "rgb(133, 204, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%), hwb(30deg 30% 40%) hue 40%)") is "rgb(133, 204, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 60%, hwb(30deg 30% 40%) hue 40%)") is "rgb(133, 204, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 75% 25%) whiteness 100%, hwb(120deg 25% 75%) blackness 100%)") is "rgb(128, 128, 128)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 60% hue 40%, hwb(30deg 30% 40%))") is "rgb(186, 204, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 40%, hwb(30deg 30% 40%))") is "rgb(186, 204, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue, hwb(30deg 30% 40%))") is "rgb(204, 115, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 0%, hwb(30deg 30% 40%))") is "rgb(204, 115, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) -10%, hwb(30deg 30% 40%))") is "rgb(153, 115, 77)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) 0%, hwb(30deg 30% 40%))") is "rgb(153, 115, 77)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue -10%, hwb(30deg 30% 40%))") is "rgb(204, 115, 26)"
+PASS computedStyle("background-color", "color-mix(hwb, hwb(120deg 10% 20%) hue 0%, hwb(30deg 30% 40%))") is "rgb(204, 115, 26)"
+
+color-mix(lch, ...)
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4), lch(50% 60 70deg / .8))") is "lch(30% 40 50 / 0.6)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) 25%, lch(50% 60 70deg / .8))") is "lch(40% 50 60 / 0.7)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4), lch(50% 60 70deg / .8) 25%)") is "lch(20% 30 40 / 0.5)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) 25%, lch(50% 60 70deg / .8) 75%)") is "lch(40% 50 60 / 0.7)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) 50%, lch(50% 60 70deg / .8) 150%)") is "lch(40% 50 60 / 0.7)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) 12.5%, lch(50% 60 70deg / .8) 37.5%)") is "lch(40% 50 60 / 0.7)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) 0%, lch(50% 60 70deg / .8))") is "lch(50% 60 70 / 0.8)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 50%, lch(50% 60 70deg / .8))") is "lch(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4), lch(50% 60 70deg / .8) lightness 50%)") is "lch(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 50%, lch(50% 60 70deg / .8) lightness 50%)") is "lch(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 100%, lch(50% 60 70deg / .8))") is "lch(10% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 50%, lch(50% 60 70deg / .8))") is "lch(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 50% chroma 100%, lch(50% 60 70deg / .8))") is "lch(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 50% chroma 100% hue 100%, lch(50% 60 70deg / .8))") is "lch(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 50% chroma 100% hue 100% alpha 100%, lch(50% 60 70deg / .8))") is "lch(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 50%, lch(50% 60 70deg / .8) chroma 0%)") is "lch(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 50%, lch(50% 60 70deg / .8) chroma 0% hue 0%)") is "lch(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 50%, lch(50% 60 70deg / .8) chroma 0% hue 0% alpha 0%)") is "lch(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 50% chroma 75%, lch(50% 60 70deg / .8))") is "lch(30% 30 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 50% chroma 75% hue 15%, lch(50% 60 70deg / .8))") is "lch(30% 30 64 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 50% chroma 75% hue 15% alpha 10%, lch(50% 60 70deg / .8))") is "lch(30% 30 64 / 0.76)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 60%, lch(50% 60 70deg / .8))") is "lch(26% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4), lch(50% 60 70deg / .8) lightness 40%)") is "lch(26% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 60%, lch(50% 60 70deg / .8) lightness 40%)") is "lch(26% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 60% lightness 40%, lch(50% 60 70deg / .8))") is "lch(34% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 40%, lch(50% 60 70deg / .8))") is "lch(34% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness, lch(50% 60 70deg / .8))") is "lch(50% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 0%, lch(50% 60 70deg / .8))") is "lch(50% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) -10%, lch(50% 60 70deg / .8))") is "lch(50% 60 70 / 0.8)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) 0%, lch(50% 60 70deg / .8))") is "lch(50% 60 70 / 0.8)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness -10%, lch(50% 60 70deg / .8))") is "lch(50% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lch, lch(10% 20 30deg / .4) lightness 0%, lch(50% 60 70deg / .8))") is "lch(50% 20 30 / 0.4)"
+
+color-mix(lab, ...)
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4), lab(50% 60 70 / .8))") is "lab(30% 40 50 / 0.6)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) 25%, lab(50% 60 70 / .8))") is "lab(40% 50 60 / 0.7)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4), lab(50% 60 70 / .8) 25%)") is "lab(20% 30 40 / 0.5)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) 25%, lab(50% 60 70 / .8) 75%)") is "lab(40% 50 60 / 0.7)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) 50%, lab(50% 60 70 / .8) 150%)") is "lab(40% 50 60 / 0.7)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) 12.5%, lab(50% 60 70 / .8) 37.5%)") is "lab(40% 50 60 / 0.7)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) 0%, lab(50% 60 70 / .8))") is "lab(50% 60 70 / 0.8)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness 50%, lab(50% 60 70 / .8))") is "lab(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4), lab(50% 60 70 / .8) lightness 50%)") is "lab(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness 50%, lab(50% 60 70 / .8) lightness 50%)") is "lab(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness 100%, lab(50% 60 70 / .8))") is "lab(10% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness 50%, lab(50% 60 70 / .8))") is "lab(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness 50% a 100%, lab(50% 60 70 / .8))") is "lab(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness 50% a 100% b 100%, lab(50% 60 70 / .8))") is "lab(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness 50% a 100% b 100% alpha 100%, lab(50% 60 70 / .8))") is "lab(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness 50%, lab(50% 60 70 / .8) a 0%)") is "lab(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness 50%, lab(50% 60 70 / .8) a 0% b 0%)") is "lab(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness 50%, lab(50% 60 70 / .8) a 0% b 0% alpha 0%)") is "lab(30% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness 50% a 75%, lab(50% 60 70 / .8))") is "lab(30% 30 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness 50% a 75% b 15%, lab(50% 60 70 / .8))") is "lab(30% 30 64 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness 50% a 75% b 15% alpha 10%, lab(50% 60 70 / .8))") is "lab(30% 30 64 / 0.76)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness 60%, lab(50% 60 70 / .8))") is "lab(26% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4), lab(50% 60 70 / .8) lightness 40%)") is "lab(26% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness 60%, lab(50% 60 70 / .8) lightness 40%)") is "lab(26% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness 60% lightness 40%, lab(50% 60 70 / .8))") is "lab(34% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness, lab(50% 60 70 / .8))") is "lab(50% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness 0%, lab(50% 60 70 / .8))") is "lab(50% 20 30 / 0.4)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) -10%, lab(50% 60 70 / .8))") is "lab(50% 60 70 / 0.8)"
+PASS computedStyle("background-color", "color-mix(lab, lab(10% 20 30 / .4) lightness -10%, lab(50% 60 70 / .8))") is "lab(50% 20 30 / 0.4)"
+
+color-mix(srgb, ...)
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4), color(srgb .5 .6 .7 / .8))") is "color(srgb 0.3 0.4 0.5 / 0.6)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) 25%, color(srgb .5 .6 .7 / .8))") is "color(srgb 0.4 0.5 0.6 / 0.7)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4), color(srgb .5 .6 .7 / .8) 25%)") is "color(srgb 0.2 0.3 0.4 / 0.5)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) 25%, color(srgb .5 .6 .7 / .8) 75%)") is "color(srgb 0.4 0.5 0.6 / 0.7)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) 50%, color(srgb .5 .6 .7 / .8) 150%)") is "color(srgb 0.4 0.5 0.6 / 0.7)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) 12.5%, color(srgb .5 .6 .7 / .8) 37.5%)") is "color(srgb 0.4 0.5 0.6 / 0.7)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) 0%, color(srgb .5 .6 .7 / .8))") is "color(srgb 0.5 0.6 0.7 / 0.8)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50%, color(srgb .5 .6 .7 / .8))") is "color(srgb 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4), color(srgb .5 .6 .7 / .8) red 50%)") is "color(srgb 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50%, color(srgb .5 .6 .7 / .8) red 50%)") is "color(srgb 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red 100%, color(srgb .5 .6 .7 / .8))") is "color(srgb 0.1 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50%, color(srgb .5 .6 .7 / .8))") is "color(srgb 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50% green 100%, color(srgb .5 .6 .7 / .8))") is "color(srgb 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50% green 100% blue 100%, color(srgb .5 .6 .7 / .8))") is "color(srgb 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50% green 100% blue 100% alpha 100%, color(srgb .5 .6 .7 / .8))") is "color(srgb 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50%, color(srgb .5 .6 .7 / .8) green 0%)") is "color(srgb 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50%, color(srgb .5 .6 .7 / .8) green 0% blue 0%)") is "color(srgb 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50%, color(srgb .5 .6 .7 / .8) green 0% blue 0% alpha 0%)") is "color(srgb 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50% green 75%, color(srgb .5 .6 .7 / .8))") is "color(srgb 0.3 0.3 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50% green 75% blue 15%, color(srgb .5 .6 .7 / .8))") is "color(srgb 0.3 0.3 0.64 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50% green 75% blue 15% alpha 10%, color(srgb .5 .6 .7 / .8))") is "color(srgb 0.3 0.3 0.64 / 0.76)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red 60%, color(srgb .5 .6 .7 / .8))") is "color(srgb 0.26 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4), color(srgb .5 .6 .7 / .8) red 40%)") is "color(srgb 0.26 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red 60%, color(srgb .5 .6 .7 / .8) red 40%)") is "color(srgb 0.26 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red 60% red 40%, color(srgb .5 .6 .7 / .8))") is "color(srgb 0.34 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red, color(srgb .5 .6 .7 / .8))") is "color(srgb 0.5 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red 0%, color(srgb .5 .6 .7 / .8))") is "color(srgb 0.5 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) -10%, color(srgb .5 .6 .7 / .8))") is "color(srgb 0.5 0.6 0.7 / 0.8)"
+PASS computedStyle("background-color", "color-mix(srgb, color(srgb .1 .2 .3 / .4) red -10%, color(srgb .5 .6 .7 / .8))") is "color(srgb 0.5 0.2 0.3 / 0.4)"
+
+color-mix(xyz, ...)
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4), color(xyz .5 .6 .7 / .8))") is "color(xyz 0.3 0.4 0.5 / 0.6)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) 25%, color(xyz .5 .6 .7 / .8))") is "color(xyz 0.4 0.5 0.6 / 0.7)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4), color(xyz .5 .6 .7 / .8) 25%)") is "color(xyz 0.2 0.3 0.4 / 0.5)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) 25%, color(xyz .5 .6 .7 / .8) 75%)") is "color(xyz 0.4 0.5 0.6 / 0.7)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) 50%, color(xyz .5 .6 .7 / .8) 150%)") is "color(xyz 0.4 0.5 0.6 / 0.7)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) 12.5%, color(xyz .5 .6 .7 / .8) 37.5%)") is "color(xyz 0.4 0.5 0.6 / 0.7)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) 0%, color(xyz .5 .6 .7 / .8))") is "color(xyz 0.5 0.6 0.7 / 0.8)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50%, color(xyz .5 .6 .7 / .8))") is "color(xyz 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4), color(xyz .5 .6 .7 / .8) x 50%)") is "color(xyz 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50%, color(xyz .5 .6 .7 / .8) x 50%)") is "color(xyz 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x 100%, color(xyz .5 .6 .7 / .8))") is "color(xyz 0.1 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50%, color(xyz .5 .6 .7 / .8))") is "color(xyz 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50% y 100%, color(xyz .5 .6 .7 / .8))") is "color(xyz 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50% y 100% z 100%, color(xyz .5 .6 .7 / .8))") is "color(xyz 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50% y 100% z 100% alpha 100%, color(xyz .5 .6 .7 / .8))") is "color(xyz 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50%, color(xyz .5 .6 .7 / .8) y 0%)") is "color(xyz 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50%, color(xyz .5 .6 .7 / .8) y 0% z 0%)") is "color(xyz 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50%, color(xyz .5 .6 .7 / .8) y 0% z 0% alpha 0%)") is "color(xyz 0.3 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50% y 75%, color(xyz .5 .6 .7 / .8))") is "color(xyz 0.3 0.3 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50% y 75% z 15%, color(xyz .5 .6 .7 / .8))") is "color(xyz 0.3 0.3 0.64 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50% y 75% z 15% alpha 10%, color(xyz .5 .6 .7 / .8))") is "color(xyz 0.3 0.3 0.64 / 0.76)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x 60%, color(xyz .5 .6 .7 / .8))") is "color(xyz 0.26 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4), color(xyz .5 .6 .7 / .8) x 40%)") is "color(xyz 0.26 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x 60%, color(xyz .5 .6 .7 / .8) x 40%)") is "color(xyz 0.26 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x 60% x 40%, color(xyz .5 .6 .7 / .8))") is "color(xyz 0.34 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x, color(xyz .5 .6 .7 / .8))") is "color(xyz 0.5 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x 0%, color(xyz .5 .6 .7 / .8))") is "color(xyz 0.5 0.2 0.3 / 0.4)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) -10%, color(xyz .5 .6 .7 / .8))") is "color(xyz 0.5 0.6 0.7 / 0.8)"
+PASS computedStyle("background-color", "color-mix(xyz, color(xyz .1 .2 .3 / .4) x -10%, color(xyz .5 .6 .7 / .8))") is "color(xyz 0.5 0.2 0.3 / 0.4)"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/fast/css/parsing-color-mix.html (0 => 273244)


--- trunk/LayoutTests/fast/css/parsing-color-mix.html	                        (rev 0)
+++ trunk/LayoutTests/fast/css/parsing-color-mix.html	2021-02-22 17:52:13 UTC (rev 273244)
@@ -0,0 +1,330 @@
+<!DOCTYPE html><!-- webkit-test-runner [ CSSColorMixEnabled=true ] -->
+<html>
+    <script src=""
+</head>
+<body>
+<script>
+    description("Test the parsing of CSS Color 5 color-mix().");
+
+    function computedStyle(property, value)
+    {
+        var div = document.createElement("div");
+        document.body.appendChild(div);
+        div.style.setProperty(property, value);
+        var computedValue = getComputedStyle(div).getPropertyValue(property);
+        document.body.removeChild(div);
+        return computedValue;
+    }
+
+    function testComputedProperty(property, value, expected)
+    {
+        shouldBeEqualToString('computedStyle("' + property + '", "' + value + '")', expected);
+    }
+
+    function testComputed(value, expected)
+    {
+        testComputedProperty("background-color", value, expected);
+    }
+
+    debug('color-mix(hsl, ...)');
+
+    // Special case no adjusters or percentage is split 50-50.
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%), hsl(30deg 30% 40%))`, `rgb(84, 92, 61)`); // hsl(75deg 20% 30%)
+
+    // Test precentage without adjusters.
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) 25%, hsl(30deg 30% 40%))`, `rgb(112, 106, 67)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%), hsl(30deg 30% 40%) 25%)`, `rgb(61, 73, 54)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) 25%, hsl(30deg 30% 40%) 75%)`, `rgb(112, 106, 67)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) 50%, hsl(30deg 30% 40%) 150%)`, `rgb(112, 106, 67)`); // Scale down > 100% sum.
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) 12.5%, hsl(30deg 30% 40%) 37.5%)`, `rgb(112, 106, 67)`); // Scale up < 100% sum.
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) 0%, hsl(30deg 30% 40%))`, `rgb(133, 102, 71)`);
+
+    // Test per-channel adjusters.
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 50%, hsl(30deg 30% 40%))`, `rgb(53, 56, 46)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%), hsl(30deg 30% 40%) hue 50%)`, `rgb(53, 56, 46)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 50%, hsl(30deg 30% 40%) hue 50%)`, `rgb(53, 56, 46)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 100%, hsl(30deg 30% 40%))`, `rgb(46, 56, 46)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 50%, hsl(30deg 30% 40%))`, `rgb(53, 56, 46)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 50% saturation 100%, hsl(30deg 30% 40%))`, `rgb(53, 56, 46)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 50% saturation 100% lightness 100%, hsl(30deg 30% 40%))`, `rgb(53, 56, 46)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 50% saturation 100% lightness 100% alpha 100%, hsl(30deg 30% 40%))`, `rgb(53, 56, 46)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 50%, hsl(30deg 30% 40%) saturation 0%)`, `rgb(53, 56, 46)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 50%, hsl(30deg 30% 40%) saturation 0% lightness 0%)`, `rgb(53, 56, 46)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 50%, hsl(30deg 30% 40%) saturation 0% lightness 0% alpha 0%)`, `rgb(53, 56, 46)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 50% saturation 75%, hsl(30deg 30% 40%))`, `rgb(55, 59, 43)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 50% saturation 75% lightness 15%, hsl(30deg 30% 40%))`, `rgb(101, 108, 80)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 50% saturation 75% lightness 15% alpha 10%, hsl(30deg 30% 40%))`, `rgb(101, 108, 80)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 60%, hsl(30deg 30% 40%))`, `rgb(52, 56, 46)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%), hsl(30deg 30% 40%) hue 40%)`, `rgb(52, 56, 46)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 60%, hsl(30deg 30% 40%) hue 40%)`, `rgb(52, 56, 46)`);
+
+    // FIXME: Test hue mixes with modifiers.
+
+    // Open questions.
+
+    // What should happen if you provide the same adjuster more than once? Currently, we do last one wins.
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 60% hue 40%, hsl(30deg 30% 40%))`, `rgb(55, 56, 46)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 40%, hsl(30deg 30% 40%))`, `rgb(55, 56, 46)`);
+
+    // What should happen if you provide an adjuster without a paihue percent? Currently, we treat that like having 0% for that adjuster.
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue, hsl(30deg 30% 40%))`, `rgb(56, 51, 46)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 0%, hsl(30deg 30% 40%))`, `rgb(56, 51, 46)`);
+
+    // What should happen if you provide an adjuster without a negative percent? Currently, we treat that like having 0% for that adjuster.
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) -10%, hsl(30deg 30% 40%))`, `rgb(133, 102, 71)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) 0%, hsl(30deg 30% 40%))`, `rgb(133, 102, 71)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue -10%, hsl(30deg 30% 40%))`, `rgb(56, 51, 46)`);
+    testComputed(`color-mix(hsl, hsl(120deg 10% 20%) hue 0%, hsl(30deg 30% 40%))`, `rgb(56, 51, 46)`);
+
+
+    debug('');
+    debug('color-mix(hwb, ...)');
+
+    // Special case no adjusters or percentage is split 50-50.
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%), hwb(30deg 30% 40%))`, `rgb(147, 179, 52)`); // hwb(75deg 20% 30%)
+
+    // Test precentage without adjusters.
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) 25%, hwb(30deg 30% 40%))`, `rgb(166, 153, 64)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%), hwb(30deg 30% 40%) 25%)`, `rgb(96, 191, 39)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) 25%, hwb(30deg 30% 40%) 75%)`, `rgb(166, 153, 64)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) 50%, hwb(30deg 30% 40%) 150%)`, `rgb(166, 153, 64)`); // Scale down > 100% sum.
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) 12.5%, hwb(30deg 30% 40%) 37.5%)`, `rgb(166, 153, 64)`); // Scale up < 100% sum.
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) 0%, hwb(30deg 30% 40%))`, `rgb(153, 115, 77)`);
+
+    // Test per-channel adjusters.
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 50%, hwb(30deg 30% 40%))`, `rgb(160, 204, 26)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%), hwb(30deg 30% 40%) hue 50%)`, `rgb(160, 204, 26)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 50%, hwb(30deg 30% 40%) hue 50%)`, `rgb(160, 204, 26)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 100%, hwb(30deg 30% 40%))`, `rgb(26, 204, 26)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 50%, hwb(30deg 30% 40%))`, `rgb(160, 204, 26)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 50% whiteness 100%, hwb(30deg 30% 40%))`, `rgb(160, 204, 26)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 50% whiteness 100% blackness 100%, hwb(30deg 30% 40%))`, `rgb(160, 204, 26)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 50% whiteness 100% blackness 100% alpha 100%, hwb(30deg 30% 40%))`, `rgb(160, 204, 26)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 50%, hwb(30deg 30% 40%) whiteness 0%)`, `rgb(160, 204, 26)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 50%, hwb(30deg 30% 40%) whiteness 0% blackness 0%)`, `rgb(160, 204, 26)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 50%, hwb(30deg 30% 40%) whiteness 0% blackness 0% alpha 0%)`, `rgb(160, 204, 26)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 50% whiteness 75%, hwb(30deg 30% 40%))`, `rgb(163, 204, 39)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 50% whiteness 75% blackness 15%, hwb(30deg 30% 40%))`, `rgb(130, 161, 39)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 50% whiteness 75% blackness 15% alpha 10%, hwb(30deg 30% 40%))`, `rgb(130, 161, 39)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 60%, hwb(30deg 30% 40%))`, `rgb(133, 204, 26)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%), hwb(30deg 30% 40%) hue 40%)`, `rgb(133, 204, 26)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 60%, hwb(30deg 30% 40%) hue 40%)`, `rgb(133, 204, 26)`);
+
+    // Test mix that creates whiteness + blackness > 100%.
+    testComputed(`color-mix(hwb, hwb(120deg 75% 25%) whiteness 100%, hwb(120deg 25% 75%) blackness 100%)`, `rgb(128, 128, 128)`);
+
+    // FIXME: Test hue mixes with modifiers.
+
+
+    // Open questions.
+
+    // What should happen if you provide the same adjuster more than once? Currently, we do last one wins.
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 60% hue 40%, hwb(30deg 30% 40%))`, `rgb(186, 204, 26)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 40%, hwb(30deg 30% 40%))`, `rgb(186, 204, 26)`);
+
+    // What should happen if you provide an adjuster without a paihue percent? Currently, we treat that like having 0% for that adjuster.
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue, hwb(30deg 30% 40%))`, `rgb(204, 115, 26)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 0%, hwb(30deg 30% 40%))`, `rgb(204, 115, 26)`);
+
+    // What should happen if you provide an adjuster without a negative percent? Currently, we treat that like having 0% for that adjuster.
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) -10%, hwb(30deg 30% 40%))`, `rgb(153, 115, 77)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) 0%, hwb(30deg 30% 40%))`, `rgb(153, 115, 77)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue -10%, hwb(30deg 30% 40%))`, `rgb(204, 115, 26)`);
+    testComputed(`color-mix(hwb, hwb(120deg 10% 20%) hue 0%, hwb(30deg 30% 40%))`, `rgb(204, 115, 26)`);
+    
+
+    debug('');
+    debug('color-mix(lch, ...)');
+
+    // Special case no adjusters or percentage is split 50-50.
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4), lch(50% 60 70deg / .8))`, `lch(30% 40 50 / 0.6)`);
+
+    // Test precentage without adjusters.
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) 25%, lch(50% 60 70deg / .8))`, `lch(40% 50 60 / 0.7)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4), lch(50% 60 70deg / .8) 25%)`, `lch(20% 30 40 / 0.5)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) 25%, lch(50% 60 70deg / .8) 75%)`, `lch(40% 50 60 / 0.7)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) 50%, lch(50% 60 70deg / .8) 150%)`, `lch(40% 50 60 / 0.7)`); // Scale down > 100% sum.
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) 12.5%, lch(50% 60 70deg / .8) 37.5%)`, `lch(40% 50 60 / 0.7)`); // Scale up < 100% sum.
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) 0%, lch(50% 60 70deg / .8))`, `lch(50% 60 70 / 0.8)`);
+
+    // Test per-channel adjusters.
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 50%, lch(50% 60 70deg / .8))`, `lch(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4), lch(50% 60 70deg / .8) lightness 50%)`, `lch(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 50%, lch(50% 60 70deg / .8) lightness 50%)`, `lch(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 100%, lch(50% 60 70deg / .8))`, `lch(10% 20 30 / 0.4)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 50%, lch(50% 60 70deg / .8))`, `lch(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 50% chroma 100%, lch(50% 60 70deg / .8))`, `lch(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 50% chroma 100% hue 100%, lch(50% 60 70deg / .8))`, `lch(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 50% chroma 100% hue 100% alpha 100%, lch(50% 60 70deg / .8))`, `lch(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 50%, lch(50% 60 70deg / .8) chroma 0%)`, `lch(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 50%, lch(50% 60 70deg / .8) chroma 0% hue 0%)`, `lch(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 50%, lch(50% 60 70deg / .8) chroma 0% hue 0% alpha 0%)`, `lch(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 50% chroma 75%, lch(50% 60 70deg / .8))`, `lch(30% 30 30 / 0.4)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 50% chroma 75% hue 15%, lch(50% 60 70deg / .8))`, `lch(30% 30 64 / 0.4)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 50% chroma 75% hue 15% alpha 10%, lch(50% 60 70deg / .8))`, `lch(30% 30 64 / 0.76)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 60%, lch(50% 60 70deg / .8))`, `lch(26% 20 30 / 0.4)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4), lch(50% 60 70deg / .8) lightness 40%)`, `lch(26% 20 30 / 0.4)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 60%, lch(50% 60 70deg / .8) lightness 40%)`, `lch(26% 20 30 / 0.4)`);
+
+    // FIXME: Test hue mixes with modifiers.
+
+    // Open questions.
+
+    // What should happen if you provide the same adjuster more than once? Currently, we do last one wins.
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 60% lightness 40%, lch(50% 60 70deg / .8))`, `lch(34% 20 30 / 0.4)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 40%, lch(50% 60 70deg / .8))`, `lch(34% 20 30 / 0.4)`);
+
+    // What should happen if you provide an adjuster without a pailightness percent? Currently, we treat that like having 0% for that adjuster.
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness, lch(50% 60 70deg / .8))`, `lch(50% 20 30 / 0.4)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 0%, lch(50% 60 70deg / .8))`, `lch(50% 20 30 / 0.4)`);
+
+    // What should happen if you provide an adjuster without a negative percent? Currently, we treat that like having 0% for that adjuster.
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) -10%, lch(50% 60 70deg / .8))`, `lch(50% 60 70 / 0.8)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) 0%, lch(50% 60 70deg / .8))`, `lch(50% 60 70 / 0.8)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness -10%, lch(50% 60 70deg / .8))`, `lch(50% 20 30 / 0.4)`);
+    testComputed(`color-mix(lch, lch(10% 20 30deg / .4) lightness 0%, lch(50% 60 70deg / .8))`, `lch(50% 20 30 / 0.4)`);
+
+
+    debug('');
+    debug('color-mix(lab, ...)');
+
+    // Special case no adjusters or percentage is split 50-50.
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4), lab(50% 60 70 / .8))`, `lab(30% 40 50 / 0.6)`);
+
+    // Test precentage without adjusters.
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) 25%, lab(50% 60 70 / .8))`, `lab(40% 50 60 / 0.7)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4), lab(50% 60 70 / .8) 25%)`, `lab(20% 30 40 / 0.5)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) 25%, lab(50% 60 70 / .8) 75%)`, `lab(40% 50 60 / 0.7)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) 50%, lab(50% 60 70 / .8) 150%)`, `lab(40% 50 60 / 0.7)`); // Scale down > 100% sum.
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) 12.5%, lab(50% 60 70 / .8) 37.5%)`, `lab(40% 50 60 / 0.7)`); // Scale up < 100% sum.
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) 0%, lab(50% 60 70 / .8))`, `lab(50% 60 70 / 0.8)`);
+
+    // Test per-channel adjusters.
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness 50%, lab(50% 60 70 / .8))`, `lab(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4), lab(50% 60 70 / .8) lightness 50%)`, `lab(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness 50%, lab(50% 60 70 / .8) lightness 50%)`, `lab(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness 100%, lab(50% 60 70 / .8))`, `lab(10% 20 30 / 0.4)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness 50%, lab(50% 60 70 / .8))`, `lab(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness 50% a 100%, lab(50% 60 70 / .8))`, `lab(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness 50% a 100% b 100%, lab(50% 60 70 / .8))`, `lab(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness 50% a 100% b 100% alpha 100%, lab(50% 60 70 / .8))`, `lab(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness 50%, lab(50% 60 70 / .8) a 0%)`, `lab(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness 50%, lab(50% 60 70 / .8) a 0% b 0%)`, `lab(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness 50%, lab(50% 60 70 / .8) a 0% b 0% alpha 0%)`, `lab(30% 20 30 / 0.4)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness 50% a 75%, lab(50% 60 70 / .8))`, `lab(30% 30 30 / 0.4)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness 50% a 75% b 15%, lab(50% 60 70 / .8))`, `lab(30% 30 64 / 0.4)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness 50% a 75% b 15% alpha 10%, lab(50% 60 70 / .8))`, `lab(30% 30 64 / 0.76)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness 60%, lab(50% 60 70 / .8))`, `lab(26% 20 30 / 0.4)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4), lab(50% 60 70 / .8) lightness 40%)`, `lab(26% 20 30 / 0.4)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness 60%, lab(50% 60 70 / .8) lightness 40%)`, `lab(26% 20 30 / 0.4)`);
+
+    // Open questions.
+
+    // What should happen if you provide the same adjuster more than once? Currently, we do last one wins.
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness 60% lightness 40%, lab(50% 60 70 / .8))`, `lab(34% 20 30 / 0.4)`);
+
+    // What should happen if you provide an adjuster without a pailightness percent? Currently, we treat that like having 0% for that adjuster.
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness, lab(50% 60 70 / .8))`, `lab(50% 20 30 / 0.4)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness 0%, lab(50% 60 70 / .8))`, `lab(50% 20 30 / 0.4)`);
+
+    // What should happen if you provide an adjuster without a negative percent? Currently, we treat that like having 0% for that adjuster.
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) -10%, lab(50% 60 70 / .8))`, `lab(50% 60 70 / 0.8)`);
+    testComputed(`color-mix(lab, lab(10% 20 30 / .4) lightness -10%, lab(50% 60 70 / .8))`, `lab(50% 20 30 / 0.4)`);
+ 
+    debug('');
+    debug('color-mix(srgb, ...)');
+
+    // Special case no adjusters or percentage is split 50-50.
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4), color(srgb .5 .6 .7 / .8))`, `color(srgb 0.3 0.4 0.5 / 0.6)`);
+
+    // Test precentage without adjusters.
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) 25%, color(srgb .5 .6 .7 / .8))`, `color(srgb 0.4 0.5 0.6 / 0.7)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4), color(srgb .5 .6 .7 / .8) 25%)`, `color(srgb 0.2 0.3 0.4 / 0.5)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) 25%, color(srgb .5 .6 .7 / .8) 75%)`, `color(srgb 0.4 0.5 0.6 / 0.7)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) 50%, color(srgb .5 .6 .7 / .8) 150%)`, `color(srgb 0.4 0.5 0.6 / 0.7)`); // Scale down > 100% sum.
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) 12.5%, color(srgb .5 .6 .7 / .8) 37.5%)`, `color(srgb 0.4 0.5 0.6 / 0.7)`); // Scale up < 100% sum.
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) 0%, color(srgb .5 .6 .7 / .8))`, `color(srgb 0.5 0.6 0.7 / 0.8)`);
+
+    // Test per-channel adjusters.
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50%, color(srgb .5 .6 .7 / .8))`, `color(srgb 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4), color(srgb .5 .6 .7 / .8) red 50%)`, `color(srgb 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50%, color(srgb .5 .6 .7 / .8) red 50%)`, `color(srgb 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red 100%, color(srgb .5 .6 .7 / .8))`, `color(srgb 0.1 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50%, color(srgb .5 .6 .7 / .8))`, `color(srgb 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50% green 100%, color(srgb .5 .6 .7 / .8))`, `color(srgb 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50% green 100% blue 100%, color(srgb .5 .6 .7 / .8))`, `color(srgb 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50% green 100% blue 100% alpha 100%, color(srgb .5 .6 .7 / .8))`, `color(srgb 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50%, color(srgb .5 .6 .7 / .8) green 0%)`, `color(srgb 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50%, color(srgb .5 .6 .7 / .8) green 0% blue 0%)`, `color(srgb 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50%, color(srgb .5 .6 .7 / .8) green 0% blue 0% alpha 0%)`, `color(srgb 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50% green 75%, color(srgb .5 .6 .7 / .8))`, `color(srgb 0.3 0.3 0.3 / 0.4)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50% green 75% blue 15%, color(srgb .5 .6 .7 / .8))`, `color(srgb 0.3 0.3 0.64 / 0.4)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red 50% green 75% blue 15% alpha 10%, color(srgb .5 .6 .7 / .8))`, `color(srgb 0.3 0.3 0.64 / 0.76)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red 60%, color(srgb .5 .6 .7 / .8))`, `color(srgb 0.26 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4), color(srgb .5 .6 .7 / .8) red 40%)`, `color(srgb 0.26 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red 60%, color(srgb .5 .6 .7 / .8) red 40%)`, `color(srgb 0.26 0.2 0.3 / 0.4)`);
+
+    // Open questions.
+
+    // What should happen if you provide the same adjuster more than once? Currently, we do last one wins.
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red 60% red 40%, color(srgb .5 .6 .7 / .8))`, `color(srgb 0.34 0.2 0.3 / 0.4)`);
+
+    // What should happen if you provide an adjuster without a paired percent? Currently, we treat that like having 0% for that adjuster.
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red, color(srgb .5 .6 .7 / .8))`, `color(srgb 0.5 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red 0%, color(srgb .5 .6 .7 / .8))`, `color(srgb 0.5 0.2 0.3 / 0.4)`);
+
+    // What should happen if you provide an adjuster without a negative percent? Currently, we treat that like having 0% for that adjuster.
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) -10%, color(srgb .5 .6 .7 / .8))`, `color(srgb 0.5 0.6 0.7 / 0.8)`);
+    testComputed(`color-mix(srgb, color(srgb .1 .2 .3 / .4) red -10%, color(srgb .5 .6 .7 / .8))`, `color(srgb 0.5 0.2 0.3 / 0.4)`);
+
+
+    debug('');
+    debug('color-mix(xyz, ...)');
+
+    // Special case no adjusters or percentage is split 50-50.
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4), color(xyz .5 .6 .7 / .8))`, `color(xyz 0.3 0.4 0.5 / 0.6)`);
+
+    // Test precentage without adjusters.
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) 25%, color(xyz .5 .6 .7 / .8))`, `color(xyz 0.4 0.5 0.6 / 0.7)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4), color(xyz .5 .6 .7 / .8) 25%)`, `color(xyz 0.2 0.3 0.4 / 0.5)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) 25%, color(xyz .5 .6 .7 / .8) 75%)`, `color(xyz 0.4 0.5 0.6 / 0.7)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) 50%, color(xyz .5 .6 .7 / .8) 150%)`, `color(xyz 0.4 0.5 0.6 / 0.7)`); // Scale down > 100% sum.
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) 12.5%, color(xyz .5 .6 .7 / .8) 37.5%)`, `color(xyz 0.4 0.5 0.6 / 0.7)`); // Scale up < 100% sum.
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) 0%, color(xyz .5 .6 .7 / .8))`, `color(xyz 0.5 0.6 0.7 / 0.8)`);
+
+    // Test per-channel adjusters.
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50%, color(xyz .5 .6 .7 / .8))`, `color(xyz 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4), color(xyz .5 .6 .7 / .8) x 50%)`, `color(xyz 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50%, color(xyz .5 .6 .7 / .8) x 50%)`, `color(xyz 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x 100%, color(xyz .5 .6 .7 / .8))`, `color(xyz 0.1 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50%, color(xyz .5 .6 .7 / .8))`, `color(xyz 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50% y 100%, color(xyz .5 .6 .7 / .8))`, `color(xyz 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50% y 100% z 100%, color(xyz .5 .6 .7 / .8))`, `color(xyz 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50% y 100% z 100% alpha 100%, color(xyz .5 .6 .7 / .8))`, `color(xyz 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50%, color(xyz .5 .6 .7 / .8) y 0%)`, `color(xyz 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50%, color(xyz .5 .6 .7 / .8) y 0% z 0%)`, `color(xyz 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50%, color(xyz .5 .6 .7 / .8) y 0% z 0% alpha 0%)`, `color(xyz 0.3 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50% y 75%, color(xyz .5 .6 .7 / .8))`, `color(xyz 0.3 0.3 0.3 / 0.4)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50% y 75% z 15%, color(xyz .5 .6 .7 / .8))`, `color(xyz 0.3 0.3 0.64 / 0.4)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x 50% y 75% z 15% alpha 10%, color(xyz .5 .6 .7 / .8))`, `color(xyz 0.3 0.3 0.64 / 0.76)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x 60%, color(xyz .5 .6 .7 / .8))`, `color(xyz 0.26 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4), color(xyz .5 .6 .7 / .8) x 40%)`, `color(xyz 0.26 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x 60%, color(xyz .5 .6 .7 / .8) x 40%)`, `color(xyz 0.26 0.2 0.3 / 0.4)`);
+
+    // Open questions.
+
+    // What should happen if you provide the same adjuster more than once? Currently, we do last one wins.
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x 60% x 40%, color(xyz .5 .6 .7 / .8))`, `color(xyz 0.34 0.2 0.3 / 0.4)`);
+
+    // What should happen if you provide an adjuster without a paix percent? Currently, we treat that like having 0% for that adjuster.
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x, color(xyz .5 .6 .7 / .8))`, `color(xyz 0.5 0.2 0.3 / 0.4)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x 0%, color(xyz .5 .6 .7 / .8))`, `color(xyz 0.5 0.2 0.3 / 0.4)`);
+
+    // What should happen if you provide an adjuster without a negative percent? Currently, we treat that like having 0% for that adjuster.
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) -10%, color(xyz .5 .6 .7 / .8))`, `color(xyz 0.5 0.6 0.7 / 0.8)`);
+    testComputed(`color-mix(xyz, color(xyz .1 .2 .3 / .4) x -10%, color(xyz .5 .6 .7 / .8))`, `color(xyz 0.5 0.2 0.3 / 0.4)`);
+</script>
+    
+<script src=""
+</body>
+</html>

Modified: trunk/Source/WTF/ChangeLog (273243 => 273244)


--- trunk/Source/WTF/ChangeLog	2021-02-22 17:33:21 UTC (rev 273243)
+++ trunk/Source/WTF/ChangeLog	2021-02-22 17:52:13 UTC (rev 273244)
@@ -1,3 +1,14 @@
+2021-02-22  Sam Weinig  <wei...@apple.com>
+
+        Add experimental support for CSS Color 5 color-mix()
+        https://bugs.webkit.org/show_bug.cgi?id=222258
+
+        Reviewed by Antti Koivisto.
+
+        * Scripts/Preferences/WebPreferencesExperimental.yaml:
+        Add new experimental preference for CSS Color 5 color-mix()
+        which is off by default.
+
 2021-02-22  Carlos Garcia Campos  <cgar...@igalia.com>
 
         [SOUP] Add support for libsoup3

Modified: trunk/Source/WTF/Scripts/Preferences/WebPreferencesExperimental.yaml (273243 => 273244)


--- trunk/Source/WTF/Scripts/Preferences/WebPreferencesExperimental.yaml	2021-02-22 17:33:21 UTC (rev 273243)
+++ trunk/Source/WTF/Scripts/Preferences/WebPreferencesExperimental.yaml	2021-02-22 17:52:13 UTC (rev 273244)
@@ -113,6 +113,18 @@
     WebKit:
       default: false
 
+CSSColorMixEnabled:
+  type: bool
+  humanReadableName: "CSS color-mix()"
+  humanReadableDescription: "Enable support for CSS color-mix() defined in CSS Color 5"
+  defaultValue:
+    WebKitLegacy:
+      default: false
+    WebKit:
+      default: false
+    WebCore:
+      default: false
+
 CSSCustomPropertiesAndValuesEnabled:
   type: bool
   humanReadableName: "CSS Custom Properties and Values API"

Modified: trunk/Source/WebCore/ChangeLog (273243 => 273244)


--- trunk/Source/WebCore/ChangeLog	2021-02-22 17:33:21 UTC (rev 273243)
+++ trunk/Source/WebCore/ChangeLog	2021-02-22 17:52:13 UTC (rev 273244)
@@ -1,3 +1,75 @@
+2021-02-22  Sam Weinig  <wei...@apple.com>
+
+        Add experimental support for CSS Color 5 color-mix()
+        https://bugs.webkit.org/show_bug.cgi?id=222258
+
+        Reviewed by Antti Koivisto.
+
+        Adds initial support for CSS Color 5 color-mix() - https://drafts.csswg.org/css-color-5/#color-mix 
+
+        This feature is off by default and can be enabled via the CSSColorMixEnabled
+        experimental preference flag.
+
+        This implementation has the same restriction on it that the recently landed
+        Relative Color Syntax does in that it does support system colors or currentColor
+        as input, since those can't be resolved at parse time. Ultimately, we will need
+        to add a late binding version of this for those cases.
+
+        Test: fast/css/parsing-color-mix.html
+
+        * css/CSSValueKeywords.in:
+        Add new keywords needed for color-mix().
+
+        * css/parser/CSSParserContext.cpp:
+        (WebCore::operator==):
+        * css/parser/CSSParserContext.h:
+        (WebCore::CSSParserContextHash::hash):
+        Add new setting for color-mix().
+
+        * css/parser/CSSPropertyParserHelpers.cpp:
+        (WebCore::CSSPropertyParserHelpers::HueColorAdjuster::fixupAnglesForInterpolation):
+        (WebCore::CSSPropertyParserHelpers::HueColorAdjuster::HueColorAdjuster):
+        (WebCore::CSSPropertyParserHelpers::ColorAdjuster::ColorAdjuster):
+        (WebCore::CSSPropertyParserHelpers::consumeAdjuster):
+        (WebCore::CSSPropertyParserHelpers::consumeAndUpdateAdjusterAtIndex):
+        (WebCore::CSSPropertyParserHelpers::consumeAndUpdateAdjuster):
+        (WebCore::CSSPropertyParserHelpers::consumeAdjusters):
+        (WebCore::CSSPropertyParserHelpers::consumeMixComponents):
+        (WebCore::CSSPropertyParserHelpers::normalizeAdjusterValues):
+        (WebCore::CSSPropertyParserHelpers::remainingAdjustment):
+        (WebCore::CSSPropertyParserHelpers::mixComponent):
+        (WebCore::CSSPropertyParserHelpers::mixComponentAtIndex):
+        (WebCore::CSSPropertyParserHelpers::makeColorTypeByNormalizingComponentsAfterMix):
+        (WebCore::CSSPropertyParserHelpers::makeColorTypeByNormalizingComponentsAfterMix<HWBA<float>>):
+        (WebCore::CSSPropertyParserHelpers::mix):
+        (WebCore::CSSPropertyParserHelpers::parseColorMixFunctionParametersUsingAdjusters):
+        (WebCore::CSSPropertyParserHelpers::parseColorMixFunctionParameters):
+        (WebCore::CSSPropertyParserHelpers::parseColorFunction):
+        The implementation uses a templatized ColorAdjuster struct to declartively map the
+        various mixing color spaces to their allowed adjusters and what type those adjusters
+        operate on (either double or the hue specific HueColorAdjuster). For example, for 
+        LCHA we have:
+        
+            using LCHColorAdjuster = ColorAdjuster<LCHA<float>, CSSValueLightness, double, CSSValueChroma, HueColorAdjuster, CSSValueHue, double, CSSValueAlpha, double>;
+
+        which indicates: 
+            
+            - it creates a LCHA<float> and will operate on LCHA<float> values
+            - its first channel is called "lightness" and is a double
+            - its second channel is called "chroma" and is a double
+            - its third channel is called "hue" and is a HueColorAdjuster
+            - its fourth channel is called "alpha" and is a double
+
+        This data is then used by the parsing and mixing functions to implement mixing without
+        having to write specific implementations for each mixing color space and can be expanded
+        to more spaces if needed.
+
+        * platform/graphics/Color.h:
+        (WebCore::Color::Color):
+        Add new overloaded constructor for a generic Optional<ColorType<float>> which parallels the existing
+        Optional<SRGBA<uint8_t>> allowing callers to convert WTF::nullopt to an invalid Color without checking
+        for nullopt themselves.
+
 2021-02-19  Sergio Villar Senin  <svil...@igalia.com>
 
         [css-flex] Refactoring of code retrieving main/cross size lengths from children

Modified: trunk/Source/WebCore/css/CSSValueKeywords.in (273243 => 273244)


--- trunk/Source/WebCore/css/CSSValueKeywords.in	2021-02-22 17:33:21 UTC (rev 273243)
+++ trunk/Source/WebCore/css/CSSValueKeywords.in	2021-02-22 17:52:13 UTC (rev 273244)
@@ -1434,6 +1434,18 @@
 // sRGB
 xyz
 
+// color-mix()
+color-mix
+shorter
+longer
+increasing
+decreasing
+specified
+lightness
+chroma
+whiteness
+blackness
+
 // prefers-default-appearance
 prefers
 // no-preference

Modified: trunk/Source/WebCore/css/parser/CSSParserContext.cpp (273243 => 273244)


--- trunk/Source/WebCore/css/parser/CSSParserContext.cpp	2021-02-22 17:33:21 UTC (rev 273243)
+++ trunk/Source/WebCore/css/parser/CSSParserContext.cpp	2021-02-22 17:52:13 UTC (rev 273244)
@@ -69,6 +69,7 @@
     , useSystemAppearance { document.page() ? document.page()->useSystemAppearance() : false }
     , aspectRatioEnabled { document.settings().aspectRatioEnabled() }
     , colorFilterEnabled { document.settings().colorFilterEnabled() }
+    , colorMixEnabled { document.settings().cssColorMixEnabled() }
     , constantPropertiesEnabled { document.settings().constantPropertiesEnabled() }
     , deferredCSSParserEnabled { document.settings().deferredCSSParserEnabled() }
     , enforcesCSSMIMETypeInNoQuirksMode { document.settings().enforceCSSMIMETypeInNoQuirksMode() }
@@ -106,6 +107,7 @@
         && a.useSystemAppearance == b.useSystemAppearance
         && a.aspectRatioEnabled == b.aspectRatioEnabled
         && a.colorFilterEnabled == b.colorFilterEnabled
+        && a.colorMixEnabled == b.colorMixEnabled
         && a.constantPropertiesEnabled == b.constantPropertiesEnabled
         && a.deferredCSSParserEnabled == b.deferredCSSParserEnabled
         && a.enforcesCSSMIMETypeInNoQuirksMode == b.enforcesCSSMIMETypeInNoQuirksMode

Modified: trunk/Source/WebCore/css/parser/CSSParserContext.h (273243 => 273244)


--- trunk/Source/WebCore/css/parser/CSSParserContext.h	2021-02-22 17:33:21 UTC (rev 273243)
+++ trunk/Source/WebCore/css/parser/CSSParserContext.h	2021-02-22 17:52:13 UTC (rev 273244)
@@ -59,6 +59,7 @@
     // Settings.
     bool aspectRatioEnabled { false };
     bool colorFilterEnabled { false };
+    bool colorMixEnabled { false };
     bool constantPropertiesEnabled { false };
     bool deferredCSSParserEnabled { false };
     bool enforcesCSSMIMETypeInNoQuirksMode { true };
@@ -102,6 +103,7 @@
             hash ^= WTF::URLHash::hash(key.baseURL);
         if (!key.charset.isEmpty())
             hash ^= StringHash::hash(key.charset);
+        
         unsigned bits = key.isHTMLDocument                  << 0
             & key.hasDocumentSecurityOrigin                 << 1
             & key.isContentOpaque                           << 2
@@ -108,29 +110,30 @@
             & key.useSystemAppearance                       << 3
             & key.aspectRatioEnabled                        << 4
             & key.colorFilterEnabled                        << 5
-            & key.constantPropertiesEnabled                 << 6
-            & key.deferredCSSParserEnabled                  << 7
-            & key.enforcesCSSMIMETypeInNoQuirksMode         << 8
-            & key.individualTransformPropertiesEnabled      << 9
+            & key.colorMixEnabled                           << 6
+            & key.constantPropertiesEnabled                 << 7
+            & key.deferredCSSParserEnabled                  << 8
+            & key.enforcesCSSMIMETypeInNoQuirksMode         << 9
+            & key.individualTransformPropertiesEnabled      << 10
 #if ENABLE(OVERFLOW_SCROLLING_TOUCH)
-            & key.legacyOverflowScrollingTouchEnabled       << 10
+            & key.legacyOverflowScrollingTouchEnabled       << 11
 #endif
-            & key.overscrollBehaviorEnabled                 << 11
-            & key.relativeColorSyntaxEnabled                << 12
-            & key.scrollBehaviorEnabled                     << 13
-            & key.springTimingFunctionEnabled               << 14
+            & key.overscrollBehaviorEnabled                 << 12
+            & key.relativeColorSyntaxEnabled                << 13
+            & key.scrollBehaviorEnabled                     << 14
+            & key.springTimingFunctionEnabled               << 15
 #if ENABLE(TEXT_AUTOSIZING)
-            & key.textAutosizingEnabled                     << 15
+            & key.textAutosizingEnabled                     << 16
 #endif
 #if ENABLE(CSS_TRANSFORM_STYLE_OPTIMIZED_3D)
-            & key.transformStyleOptimized3DEnabled          << 16
+            & key.transformStyleOptimized3DEnabled          << 17
 #endif
-            & key.useLegacyBackgroundSizeShorthandBehavior  << 17
-            & key.focusVisibleEnabled                       << 18
+            & key.useLegacyBackgroundSizeShorthandBehavior  << 18
+            & key.focusVisibleEnabled                       << 19
 #if ENABLE(ATTACHMENT_ELEMENT)
-            & key.attachmentEnabled                         << 19
+            & key.attachmentEnabled                         << 20
 #endif
-            & key.mode                                      << 20; // Keep this last.
+            & key.mode                                      << 21; // Keep this last.
         hash ^= WTF::intHash(bits);
         return hash;
     }

Modified: trunk/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp (273243 => 273244)


--- trunk/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp	2021-02-22 17:33:21 UTC (rev 273243)
+++ trunk/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp	2021-02-22 17:52:13 UTC (rev 273244)
@@ -1480,6 +1480,380 @@
     return color;
 }
 
+struct HueColorAdjuster {
+    enum class Type {
+        Shorter,
+        Longer,
+        Increasing,
+        Decreasing,
+        Specified
+    };
+
+    static std::pair<double, double> fixupAnglesForInterpolation(double theta1, double theta2, Type type)
+    {
+        ASSERT(theta1 >= 0.0);
+        ASSERT(theta1 <= 360.0);
+        ASSERT(theta2 >= 0.0);
+        ASSERT(theta2 <= 360.0);
+
+        switch (type) {
+        case Type::Shorter: {
+            auto difference = theta2 - theta1;
+            if (difference > 180.0)
+                return { theta1 + 360.0, theta2 };
+            if (difference < -180.0)
+                return { theta1, theta2 + 360.0 };
+            return { theta1, theta2 };
+        }
+        case Type::Longer: {
+            auto difference = theta2 - theta1;
+            if (difference >= 0.0 && difference < 180.0)
+                return { theta1 + 360.0, theta2 };
+            if (difference >= -180.0  && difference < 0)
+                return { theta1, theta2 + 360.0 };
+            return { theta1, theta2 };
+        }
+        case Type::Increasing: {
+            if (theta2 < theta1)
+                return { theta1, theta2 + 360.0 };
+            return { theta1, theta2 };
+        }
+        case Type::Decreasing: {
+            if (theta1 < theta2)
+                return { theta1 + 360.0, theta2 };
+            return { theta1, theta2 };
+        }
+        case Type::Specified:
+            return { theta1, theta2 };
+        }
+    }
+
+    HueColorAdjuster(double value = 0.0, Type type = Type::Shorter)
+        : type { type }
+        , value { value }
+    {
+    }
+
+    Type type;
+    double value;
+};
+
+template<typename C, CSSValueID ID0, typename Channel0, CSSValueID ID1, typename Channel1, CSSValueID ID2, typename Channel2, CSSValueID ID3, typename Channel3>
+struct ColorAdjuster {
+    using ColorType = C;
+    static constexpr auto channelCSSValueIDs = std::make_tuple(ID0, ID1, ID2, ID3);
+
+    ColorAdjuster() = default;
+    explicit ColorAdjuster(double percentage)
+        : channels { percentage, percentage, percentage, percentage }
+    {
+    }
+
+    std::tuple<Optional<Channel0>, Optional<Channel1>, Optional<Channel2>, Optional<Channel3>> channels;
+};
+
+using HSLColorAdjuster = ColorAdjuster<HSLA<float>, CSSValueHue, HueColorAdjuster, CSSValueSaturation, double, CSSValueLightness, double, CSSValueAlpha, double>;
+using HWBColorAdjuster = ColorAdjuster<HWBA<float>, CSSValueHue, HueColorAdjuster, CSSValueWhiteness, double, CSSValueBlackness, double, CSSValueAlpha, double>;
+using LCHColorAdjuster = ColorAdjuster<LCHA<float>, CSSValueLightness, double, CSSValueChroma, HueColorAdjuster, CSSValueHue, double, CSSValueAlpha, double>;
+using LabColorAdjuster = ColorAdjuster<Lab<float>, CSSValueLightness, double, CSSValueA, double, CSSValueB, double, CSSValueAlpha, double>;
+using SRGBColorAdjuster = ColorAdjuster<SRGBA<float>, CSSValueRed, double, CSSValueGreen, double, CSSValueBlue, double, CSSValueAlpha, double>;
+using XYZColorAdjuster = ColorAdjuster<XYZA<float, WhitePoint::D50>, CSSValueX, double, CSSValueY, double, CSSValueZ, double, CSSValueAlpha, double>;
+
+template<typename Adjuster> struct ColorMixComponent {
+    Color color;
+    Adjuster adjuster;
+};
+
+template<CSSValueID Ident, typename T> struct AdjusterConsumer;
+
+template<CSSValueID Ident> struct AdjusterConsumer<Ident, HueColorAdjuster> {
+    static Optional<HueColorAdjuster> consume(CSSParserTokenRange& args)
+    {
+        if (!consumeIdentRaw<Ident>(args))
+            return WTF::nullopt;
+
+        HueColorAdjuster result;
+        if (auto hueAdjustmentType = consumeHueAdjustmentType(args))
+            result.type = *hueAdjustmentType;
+
+        // FIXME: Is clamping to 0 for negative percentages the right thing to do?
+        if (auto percentage = consumePercentRaw(args))
+            result.value = std::max(0.0, *percentage);
+
+        // FIXME: Should an adjuster without a percetange be allowed?
+        //  e.g color-mix(hsl, teal hue, red);
+
+        return result;
+    }
+
+    static Optional<HueColorAdjuster::Type> consumeHueAdjustmentType(CSSParserTokenRange& args)
+    {
+        switch (args.peek().id()) {
+        case CSSValueShorter:
+            consumeIdentRaw(args);
+            return HueColorAdjuster::Type::Shorter;
+        case CSSValueLonger:
+            consumeIdentRaw(args);
+            return HueColorAdjuster::Type::Longer;
+        case CSSValueIncreasing:
+            consumeIdentRaw(args);
+            return HueColorAdjuster::Type::Increasing;
+        case CSSValueDecreasing:
+            consumeIdentRaw(args);
+            return HueColorAdjuster::Type::Decreasing;
+        case CSSValueSpecified:
+            consumeIdentRaw(args);
+            return HueColorAdjuster::Type::Specified;
+        default:
+            return WTF::nullopt;
+        }
+    }
+};
+
+template<CSSValueID Ident> struct AdjusterConsumer<Ident, double> {
+    static Optional<double> consume(CSSParserTokenRange& args)
+    {
+        if (!consumeIdentRaw<Ident>(args))
+            return WTF::nullopt;
+        
+        // FIXME: Is clamping to 0 for negative percentages the right thing to do?
+        if (auto percentage = consumePercentRaw(args))
+            return std::max(0.0, *percentage);
+
+        // FIXME: Should an adjuster without a percetange be allowed?
+        //  e.g color-mix(hsl, teal saturation, red);
+
+        return 0;
+    }
+};
+
+template<CSSValueID Ident, typename T> inline decltype(auto) consumeAdjuster(CSSParserTokenRange& args)
+{
+    return AdjusterConsumer<Ident, T>::consume(args);
+}
+
+template<std::size_t I, typename Adjuster> static bool consumeAndUpdateAdjusterAtIndex(CSSParserTokenRange& args, Adjuster& adjuster)
+{
+    using AdjusterType = std::decay_t<decltype(std::get<I>(adjuster.channels).value())>;
+    static constexpr CSSValueID Ident = std::get<I>(Adjuster::channelCSSValueIDs);
+
+    if (auto adjustment = consumeAdjuster<Ident, AdjusterType>(args)) {
+        std::get<I>(adjuster.channels) = *adjustment;
+        return true;
+    }
+    return false;
+}
+
+template<typename Adjuster> static bool consumeAndUpdateAdjuster(CSSParserTokenRange& args, Adjuster& adjuster)
+{
+    if (consumeAndUpdateAdjusterAtIndex<0>(args, adjuster))
+        return true;
+    if (consumeAndUpdateAdjusterAtIndex<1>(args, adjuster))
+        return true;
+    if (consumeAndUpdateAdjusterAtIndex<2>(args, adjuster))
+        return true;
+    if (consumeAndUpdateAdjusterAtIndex<3>(args, adjuster))
+        return true;
+    return false;
+}
+
+template<typename Adjuster> static Adjuster consumeAdjusters(CSSParserTokenRange& args)
+{
+    Adjuster adjuster;
+    while (consumeAndUpdateAdjuster(args, adjuster)) {
+        // Keep consuming until there are no more adjusters.
+    }
+    
+    return adjuster;
+}
+
+template<typename Adjuster> static Optional<ColorMixComponent<Adjuster>> consumeMixComponents(CSSParserTokenRange& args, const CSSParserContext& context)
+{
+    auto originColor = consumeOriginColor(args, context);
+    if (!originColor.isValid())
+        return WTF::nullopt;
+
+    // FIXME: Is clamping to 0 for negative percentages the right thing to do?
+    if (auto percentage = consumePercentRaw(args))
+        return { { WTFMove(originColor), Adjuster { std::max(0.0, *percentage) } } };
+
+    return { { WTFMove(originColor), consumeAdjusters<Adjuster>(args) } };
+}
+
+static std::pair<HueColorAdjuster, HueColorAdjuster> normalizeAdjusterValues(HueColorAdjuster adjuster1, HueColorAdjuster adjuster2)
+{
+    if (auto sum = adjuster1.value + adjuster2.value; sum != 100.0) {
+        adjuster1.value *= 100.0 / sum;
+        adjuster2.value *= 100.0 / sum;
+    }
+
+    return { adjuster1, adjuster2 };
+}
+
+static std::pair<double, double> normalizeAdjusterValues(double adjuster1, double adjuster2)
+{
+    if (auto sum = adjuster1 + adjuster2; sum != 100.0) {
+        adjuster1 *= 100.0 / sum;
+        adjuster2 *= 100.0 / sum;
+    }
+
+    return { adjuster1, adjuster2 };
+}
+
+static HueColorAdjuster remainingAdjustment(HueColorAdjuster adjuster)
+{
+    return { 100.0 - adjuster.value, adjuster.type };
+}
+
+static double remainingAdjustment(double adjuster)
+{
+    return 100.0 - adjuster;
+}
+
+template<typename AdjusterType> static auto normalizeAdjusterValues(Optional<AdjusterType> adjuster1, Optional<AdjusterType> adjuster2) -> std::pair<AdjusterType, AdjusterType>
+{
+    if (adjuster1 && adjuster2)
+        return normalizeAdjusterValues(*adjuster1, *adjuster2);
+    if (!adjuster1 && adjuster2)
+        return { remainingAdjustment(*adjuster2), *adjuster2 };
+    if (adjuster1 && !adjuster2)
+        return { *adjuster1, remainingAdjustment(*adjuster1) };
+    // When neigher mix component provides an adjuster, the result is the non-modified
+    // channel from from the first color.
+    ASSERT(!adjuster1 && !adjuster2);
+    return { 100.0, 0.0 };
+}
+
+static double mixComponent(float component1, HueColorAdjuster adjustment1, float component2, HueColorAdjuster adjustment2)
+{
+    // FIXME: The spec does not indicate what to do if two different hue types are specified. We always use the first one for now,
+    // though we probably should take into account whether it was actually specified or is the default value. That normalization
+    // should happen in normalizeAdjusterValues().
+
+    auto [fixedUpComponent1, fixedUpComponent2] = HueColorAdjuster::fixupAnglesForInterpolation(component1, component2, adjustment1.type);
+    auto result = (fixedUpComponent1 * (adjustment1.value / 100.0)) + (fixedUpComponent2 * (adjustment2.value / 100.0));
+    // FIXME: Check if this full normalization is needed.
+    return normalizeHue(result);
+}
+
+static double mixComponent(float component1, double adjustment1, float component2, double adjustment2)
+{
+    return (component1 * (adjustment1 / 100.0)) + (component2 * (adjustment2 / 100.0));
+}
+
+template<std::size_t I, typename Adjuster> static double mixComponentAtIndex(const ColorComponents<float>& color1, const Adjuster& adjuster1, const ColorComponents<float>& color2, const Adjuster& adjuster2)
+{
+    auto [normalizedAdjuster1Value, normalizedAdjuster2Value] = normalizeAdjusterValues(std::get<I>(adjuster1.channels), std::get<I>(adjuster2.channels));
+    return mixComponent(color1[I], normalizedAdjuster1Value, color2[I], normalizedAdjuster2Value);
+}
+
+template<typename ColorType> inline ColorType makeColorTypeByNormalizingComponentsAfterMix(double channel0, double channel1, double channel2, double channel3)
+{
+    return { static_cast<float>(channel0), static_cast<float>(channel1), static_cast<float>(channel2), static_cast<float>(channel3) };
+}
+
+template<> inline HWBA<float> makeColorTypeByNormalizingComponentsAfterMix<HWBA<float>>(double hue, double whiteness, double blackness, double alpha)
+{
+    auto [normalizedWhitness, normalizedBlackness] = normalizeWhitenessBlackness(whiteness, blackness);
+    return { static_cast<float>(hue), static_cast<float>(normalizedWhitness), static_cast<float>(normalizedBlackness), static_cast<float>(alpha) };
+}
+
+template<typename Adjuster> static typename Adjuster::ColorType mix(const ColorMixComponent<Adjuster>& mixComponents1, const ColorMixComponent<Adjuster>& mixComponents2)
+{
+    using ColorType = typename Adjuster::ColorType;
+
+    auto color1 = asColorComponents(mixComponents1.color.template toColorTypeLossy<ColorType>());
+    auto color2 = asColorComponents(mixComponents2.color.template toColorTypeLossy<ColorType>());
+
+    auto adjuster1 = mixComponents1.adjuster;
+    auto adjuster2 = mixComponents2.adjuster;
+
+    if (!std::get<0>(adjuster1.channels) && !std::get<1>(adjuster1.channels) && !std::get<2>(adjuster1.channels) && !std::get<3>(adjuster1.channels) && !std::get<0>(adjuster2.channels) && !std::get<1>(adjuster2.channels) && !std::get<2>(adjuster2.channels) && !std::get<3>(adjuster2.channels)) {
+        // No adjusters being specified at all is special cased to mean mix 50-50.
+        adjuster1 = Adjuster { 50.0 };
+        adjuster2 = Adjuster { 50.0 };
+    }
+
+    auto channel0 = mixComponentAtIndex<0>(color1, adjuster1, color2, adjuster2);
+    auto channel1 = mixComponentAtIndex<1>(color1, adjuster1, color2, adjuster2);
+    auto channel2 = mixComponentAtIndex<2>(color1, adjuster1, color2, adjuster2);
+    auto channel3 = mixComponentAtIndex<3>(color1, adjuster1, color2, adjuster2);
+
+    return makeColorTypeByNormalizingComponentsAfterMix<ColorType>(channel0, channel1, channel2, channel3);
+}
+
+template<typename Adjuster> static Optional<typename Adjuster::ColorType> parseColorMixFunctionParametersUsingAdjusters(CSSParserTokenRange& args, const CSSParserContext& context)
+{
+    auto mixComponents1 = consumeMixComponents<Adjuster>(args, context);
+    if (!mixComponents1)
+        return WTF::nullopt;
+
+    // FIXME: This comma is not in the grammar, but is in all the examples.
+    if (!consumeCommaIncludingWhitespace(args))
+        return WTF::nullopt;
+
+    auto mixComponents2 = consumeMixComponents<Adjuster>(args, context);
+    if (!mixComponents2)
+        return WTF::nullopt;
+
+    return mix(*mixComponents1, *mixComponents2);
+}
+
+static Color parseColorMixFunctionParameters(CSSParserTokenRange& range, const CSSParserContext& context)
+{
+    ASSERT(range.peek().functionId() == CSSValueColorMix);
+
+    if (!context.colorMixEnabled)
+        return { };
+
+    auto args = consumeFunction(range);
+
+    auto consumeIdentAndComma = [](CSSParserTokenRange& args) {
+        consumeIdentRaw(args);
+        // FIXME: This comma is not in the grammar, but is in all the examples.
+        return consumeCommaIncludingWhitespace(args);
+    };
+
+    switch (args.peek().id()) {
+    case CSSValueHsl: {
+        if (!consumeIdentAndComma(args))
+            return { };
+        auto hsl = parseColorMixFunctionParametersUsingAdjusters<HSLColorAdjuster>(args, context);
+        if (!hsl)
+            return { };
+        return convertColor<SRGBA<uint8_t>>(*hsl);
+    }
+    case CSSValueHwb: {
+        if (!consumeIdentAndComma(args))
+            return { };
+        auto hwb = parseColorMixFunctionParametersUsingAdjusters<HWBColorAdjuster>(args, context);
+        if (!hwb)
+            return { };
+        return convertColor<SRGBA<uint8_t>>(*hwb);
+    }
+    case CSSValueLch:
+        if (!consumeIdentAndComma(args))
+            return { };
+        return parseColorMixFunctionParametersUsingAdjusters<LCHColorAdjuster>(args, context);
+    case CSSValueLab:
+        if (!consumeIdentAndComma(args))
+            return { };
+        return parseColorMixFunctionParametersUsingAdjusters<LabColorAdjuster>(args, context);
+    case CSSValueXyz:
+        if (!consumeIdentAndComma(args))
+            return { };
+        return parseColorMixFunctionParametersUsingAdjusters<XYZColorAdjuster>(args, context);
+    case CSSValueSRGB:
+        if (!consumeIdentAndComma(args))
+            return { };
+        return parseColorMixFunctionParametersUsingAdjusters<SRGBColorAdjuster>(args, context);
+    default:
+        // Default to using LCH if no color space is provided as per the spec.
+        // FIXME: This behavior is unnecessarily confusing, we should remove the default from the spec.
+        return parseColorMixFunctionParametersUsingAdjusters<LCHColorAdjuster>(args, context);
+    }
+}
+
 static Optional<SRGBA<uint8_t>> parseHexColor(CSSParserTokenRange& range, bool acceptQuirkyColors)
 {
     String string;
@@ -1547,6 +1921,9 @@
     case CSSValueColor:
         color = parseColorFunctionParameters(colorRange);
         break;
+    case CSSValueColorMix:
+        color = parseColorMixFunctionParameters(colorRange, context);
+        break;
     default:
         return { };
     }

Modified: trunk/Source/WebCore/platform/graphics/Color.h (273243 => 273244)


--- trunk/Source/WebCore/platform/graphics/Color.h	2021-02-22 17:33:21 UTC (rev 273243)
+++ trunk/Source/WebCore/platform/graphics/Color.h	2021-02-22 17:52:13 UTC (rev 273244)
@@ -73,8 +73,12 @@
     Color(Optional<SRGBA<uint8_t>>, OptionSet<Flags> = { });
 
     Color(ColorComponents<float>, ColorSpace, OptionSet<Flags> = { });
+    
     template<typename ColorType, typename std::enable_if_t<IsColorTypeWithComponentType<ColorType, float>>* = nullptr>
     Color(const ColorType&, OptionSet<Flags> = { });
+    
+    template<typename ColorType, typename std::enable_if_t<IsColorTypeWithComponentType<ColorType, float>>* = nullptr>
+    Color(const Optional<ColorType>&, OptionSet<Flags> = { });
 
     explicit Color(WTF::HashTableEmptyValueType);
     explicit Color(WTF::HashTableDeletedValueType);
@@ -300,6 +304,13 @@
     setExtendedColor(ExtendedColor::create(color), toFlagsIncludingPrivate(flags));
 }
 
+template<typename ColorType, typename std::enable_if_t<IsColorTypeWithComponentType<ColorType, float>>*>
+inline Color::Color(const Optional<ColorType>& color, OptionSet<Flags> flags)
+{
+    if (color)
+        setExtendedColor(ExtendedColor::create(*color), toFlagsIncludingPrivate(flags));
+}
+
 inline Color::Color(Ref<ExtendedColor>&& extendedColor, OptionSet<Flags> flags)
 {
     setExtendedColor(WTFMove(extendedColor), toFlagsIncludingPrivate(flags));
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to