Backed out changeset eb313b2b8b12 (bug 1890724) for causing wpt failures on color-computed-relative-color.html. CLOSED TREE

This commit is contained in:
Aron Cseh 2024-04-15 19:41:48 +03:00
parent 4f2531fe2c
commit a56e183ac5
7 changed files with 3552 additions and 206 deletions

View file

@ -4,7 +4,6 @@
//! Parse/serialize and resolve a single color component. //! Parse/serialize and resolve a single color component.
use super::AbsoluteColor;
use crate::{ use crate::{
parser::ParserContext, parser::ParserContext,
values::{ values::{
@ -61,12 +60,6 @@ impl<ValueType> ColorComponent<ValueType> {
/// An utility trait that allows the construction of [ColorComponent] /// An utility trait that allows the construction of [ColorComponent]
/// `ValueType`'s after parsing a color component. /// `ValueType`'s after parsing a color component.
pub trait ColorComponentType: Sized { pub trait ColorComponentType: Sized {
// TODO(tlouw): This function should be named according to the rules in the spec
// stating that all the values coming from color components are
// numbers and that each has their own rules dependeing on types.
/// Construct a new component from a single value.
fn from_value(value: f32) -> Self;
/// Return the [CalcUnits] flags that the impl can handle. /// Return the [CalcUnits] flags that the impl can handle.
fn units() -> CalcUnits; fn units() -> CalcUnits;
@ -84,7 +77,6 @@ impl<ValueType: ColorComponentType> ColorComponent<ValueType> {
context: &ParserContext, context: &ParserContext,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
allow_none: bool, allow_none: bool,
origin_color: Option<&AbsoluteColor>,
) -> Result<Self, ParseError<'i>> { ) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location(); let location = input.current_source_location();
@ -92,15 +84,6 @@ impl<ValueType: ColorComponentType> ColorComponent<ValueType> {
Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => { Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => {
Ok(ColorComponent::None) Ok(ColorComponent::None)
}, },
ref t @ Token::Ident(ref ident) if origin_color.is_some() => {
match origin_color
.unwrap()
.get_component_by_channel_keyword(ident)
{
Ok(Some(value)) => Ok(Self::Value(ValueType::from_value(value))),
_ => Err(location.new_unexpected_token_error(t.clone())),
}
},
Token::Function(ref name) => { Token::Function(ref name) => {
let function = SpecifiedCalcNode::math_function(context, name, location)?; let function = SpecifiedCalcNode::math_function(context, name, location)?;
let node = SpecifiedCalcNode::parse(context, input, function, ValueType::units())?; let node = SpecifiedCalcNode::parse(context, input, function, ValueType::units())?;

View file

@ -449,62 +449,6 @@ impl AbsoluteColor {
} }
} }
/// Return the value of a component by its channel keyword.
pub fn get_component_by_channel_keyword(&self, name: &str) -> Result<Option<f32>, ()> {
if name.eq_ignore_ascii_case("alpha") {
return Ok(self.alpha());
}
Ok(match self.color_space {
ColorSpace::Srgb => match_ignore_ascii_case! { name,
"r" => self.c0(),
"g" => self.c1(),
"b" => self.c2(),
_ => return Err(()),
},
ColorSpace::Hsl => match_ignore_ascii_case! { name,
"h" => self.c0(),
"s" => self.c1(),
"l" => self.c2(),
_ => return Err(()),
},
ColorSpace::Hwb => match_ignore_ascii_case! { name,
"h" => self.c0(),
"w" => self.c1(),
"b" => self.c2(),
_ => return Err(()),
},
ColorSpace::Lab | ColorSpace::Oklab => match_ignore_ascii_case! { name,
"l" => self.c0(),
"a" => self.c1(),
"b" => self.c2(),
_ => return Err(()),
},
ColorSpace::Lch | ColorSpace::Oklch => match_ignore_ascii_case! { name,
"l" => self.c0(),
"c" => self.c1(),
"h" => self.c2(),
_ => return Err(()),
},
ColorSpace::SrgbLinear |
ColorSpace::DisplayP3 |
ColorSpace::A98Rgb |
ColorSpace::ProphotoRgb |
ColorSpace::Rec2020 => match_ignore_ascii_case! { name,
"r" => self.c0(),
"g" => self.c1(),
"b" => self.c2(),
_ => return Err(()),
},
ColorSpace::XyzD50 | ColorSpace::XyzD65 => match_ignore_ascii_case! { name,
"x" => self.c0(),
"y" => self.c1(),
"z" => self.c2(),
_ => return Err(()),
},
})
}
/// Convert this color to the specified color space. /// Convert this color to the specified color space.
pub fn to_color_space(&self, color_space: ColorSpace) -> Self { pub fn to_color_space(&self, color_space: ColorSpace) -> Self {
use ColorSpace::*; use ColorSpace::*;

View file

@ -11,7 +11,7 @@
use super::{ use super::{
color_function::ColorFunction, color_function::ColorFunction,
component::{ColorComponent, ColorComponentType}, component::{ColorComponent, ColorComponentType},
AbsoluteColor, ColorSpace, AbsoluteColor,
}; };
use crate::{ use crate::{
parser::ParserContext, parser::ParserContext,
@ -96,21 +96,15 @@ fn parse_color_function<'i, 't>(
name: CowRcStr<'i>, name: CowRcStr<'i>,
arguments: &mut Parser<'i, 't>, arguments: &mut Parser<'i, 't>,
) -> Result<ColorFunction, ParseError<'i>> { ) -> Result<ColorFunction, ParseError<'i>> {
let origin_color = parse_origin_color(color_parser, arguments)?;
let color_parser = ColorParser {
context: color_parser.context,
origin_color: origin_color.as_ref(),
};
let color = match_ignore_ascii_case! { &name, let color = match_ignore_ascii_case! { &name,
"rgb" | "rgba" => parse_rgb(&color_parser, arguments), "rgb" | "rgba" => parse_rgb(color_parser, arguments),
"hsl" | "hsla" => parse_hsl(&color_parser, arguments), "hsl" | "hsla" => parse_hsl(color_parser, arguments),
"hwb" => parse_hwb(&color_parser, arguments), "hwb" => parse_hwb(color_parser, arguments),
"lab" => parse_lab_like(&color_parser, arguments, ColorSpace::Lab, ColorFunction::Lab), "lab" => parse_lab_like(color_parser, arguments, ColorFunction::Lab),
"lch" => parse_lch_like(&color_parser, arguments, ColorSpace::Lch, ColorFunction::Lch), "lch" => parse_lch_like(color_parser, arguments, ColorFunction::Lch),
"oklab" => parse_lab_like(&color_parser, arguments, ColorSpace::Oklab, ColorFunction::Oklab), "oklab" => parse_lab_like(color_parser, arguments, ColorFunction::Oklab),
"oklch" => parse_lch_like(&color_parser, arguments, ColorSpace::Oklch, ColorFunction::Oklch), "oklch" => parse_lch_like(color_parser, arguments, ColorFunction::Oklch),
"color" => parse_color_with_color_space(&color_parser, arguments), "color" => parse_color_with_color_space(color_parser, arguments),
_ => return Err(arguments.new_unexpected_token_error(Token::Ident(name))), _ => return Err(arguments.new_unexpected_token_error(Token::Ident(name))),
}?; }?;
@ -161,7 +155,7 @@ impl ColorComponent<NumberOrPercentage> {
fn parse_origin_color<'i, 't>( fn parse_origin_color<'i, 't>(
color_parser: &ColorParser<'_, '_>, color_parser: &ColorParser<'_, '_>,
arguments: &mut Parser<'i, 't>, arguments: &mut Parser<'i, 't>,
) -> Result<Option<AbsoluteColor>, ParseError<'i>> { ) -> Result<Option<SpecifiedColor>, ParseError<'i>> {
if !rcs_enabled() { if !rcs_enabled() {
return Ok(None); return Ok(None);
} }
@ -175,18 +169,8 @@ fn parse_origin_color<'i, 't>(
return Ok(None); return Ok(None);
} }
let location = arguments.current_source_location();
// We still fail if we can't parse the origin color. // We still fail if we can't parse the origin color.
let origin_color = parse_color_with(color_parser, arguments)?; parse_color_with(color_parser, arguments).map(|color| Some(color))
// Right now we only handle absolute colors.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1890972
let SpecifiedColor::Absolute(absolute) = origin_color else {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
};
Ok(Some(absolute.color))
} }
#[inline] #[inline]
@ -194,13 +178,7 @@ fn parse_rgb<'i, 't>(
color_parser: &ColorParser<'_, '_>, color_parser: &ColorParser<'_, '_>,
arguments: &mut Parser<'i, 't>, arguments: &mut Parser<'i, 't>,
) -> Result<ColorFunction, ParseError<'i>> { ) -> Result<ColorFunction, ParseError<'i>> {
let origin_color = color_parser let origin_color = parse_origin_color(color_parser, arguments)?;
.origin_color
.map(|c| c.to_color_space(ColorSpace::Srgb));
let color_parser = ColorParser {
context: color_parser.context,
origin_color: origin_color.as_ref(),
};
let location = arguments.current_source_location(); let location = arguments.current_source_location();
@ -209,11 +187,11 @@ fn parse_rgb<'i, 't>(
// If the first component is not "none" and is followed by a comma, then we // If the first component is not "none" and is followed by a comma, then we
// are parsing the legacy syntax. Legacy syntax also doesn't support an // are parsing the legacy syntax. Legacy syntax also doesn't support an
// origin color. // origin color.
let is_legacy_syntax = color_parser.origin_color.is_none() && let is_legacy_syntax = origin_color.is_none() &&
!maybe_red.is_none() && !maybe_red.is_none() &&
arguments.try_parse(|p| p.expect_comma()).is_ok(); arguments.try_parse(|p| p.expect_comma()).is_ok();
Ok(if is_legacy_syntax { let (red, green, blue, alpha) = if is_legacy_syntax {
let Ok(is_percentage) = maybe_red.is_percentage() else { let Ok(is_percentage) = maybe_red.is_percentage() else {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}; };
@ -239,30 +217,24 @@ fn parse_rgb<'i, 't>(
(red, green, blue) (red, green, blue)
}; };
let alpha = parse_legacy_alpha(&color_parser, arguments)?; let alpha = parse_legacy_alpha(color_parser, arguments)?;
ColorFunction::Rgb(red, green, blue, alpha) (red, green, blue, alpha)
} else { } else {
let green = color_parser.parse_number_or_percentage(arguments, true)?; let red = maybe_red.map_value(|v| clamp_floor_256_f32(v.to_number(255.0)));
let blue = color_parser.parse_number_or_percentage(arguments, true)?; let green = color_parser
let alpha = parse_modern_alpha(&color_parser, arguments)?; .parse_number_or_percentage(arguments, true)?
.map_value(|v| clamp_floor_256_f32(v.to_number(255.0)));
let blue = color_parser
.parse_number_or_percentage(arguments, true)?
.map_value(|v| clamp_floor_256_f32(v.to_number(255.0)));
// When using the relative color syntax (having an origin color), the let alpha = parse_modern_alpha(color_parser, arguments)?;
// resulting color is always in the modern syntax.
if color_parser.origin_color.is_some() {
ColorFunction::Color(PredefinedColorSpace::Srgb, maybe_red, green, blue, alpha)
} else {
fn clamp(v: NumberOrPercentage) -> u8 {
clamp_floor_256_f32(v.to_number(255.0))
}
let red = maybe_red.map_value(clamp); (red, green, blue, alpha)
let green = green.map_value(clamp); };
let blue = blue.map_value(clamp);
ColorFunction::Rgb(red, green, blue, alpha) Ok(ColorFunction::Rgb(red, green, blue, alpha))
}
})
} }
/// Parses hsl syntax. /// Parses hsl syntax.
@ -273,19 +245,13 @@ fn parse_hsl<'i, 't>(
color_parser: &ColorParser<'_, '_>, color_parser: &ColorParser<'_, '_>,
arguments: &mut Parser<'i, 't>, arguments: &mut Parser<'i, 't>,
) -> Result<ColorFunction, ParseError<'i>> { ) -> Result<ColorFunction, ParseError<'i>> {
let origin_color = color_parser let origin_color = parse_origin_color(color_parser, arguments)?;
.origin_color
.map(|c| c.to_color_space(ColorSpace::Hsl));
let color_parser = ColorParser {
context: color_parser.context,
origin_color: origin_color.as_ref(),
};
let hue = color_parser.parse_number_or_angle(arguments, true)?; let hue = color_parser.parse_number_or_angle(arguments, true)?;
// If the hue is not "none" and is followed by a comma, then we are parsing // If the hue is not "none" and is followed by a comma, then we are parsing
// the legacy syntax. Legacy syntax also doesn't support an origin color. // the legacy syntax. Legacy syntax also doesn't support an origin color.
let is_legacy_syntax = color_parser.origin_color.is_none() && let is_legacy_syntax = origin_color.is_none() &&
!hue.is_none() && !hue.is_none() &&
arguments.try_parse(|p| p.expect_comma()).is_ok(); arguments.try_parse(|p| p.expect_comma()).is_ok();
@ -300,7 +266,7 @@ fn parse_hsl<'i, 't>(
( (
saturation, saturation,
lightness, lightness,
parse_legacy_alpha(&color_parser, arguments)?, parse_legacy_alpha(color_parser, arguments)?,
) )
} else { } else {
let saturation = color_parser.parse_number_or_percentage(arguments, true)?; let saturation = color_parser.parse_number_or_percentage(arguments, true)?;
@ -308,7 +274,7 @@ fn parse_hsl<'i, 't>(
( (
saturation, saturation,
lightness, lightness,
parse_modern_alpha(&color_parser, arguments)?, parse_modern_alpha(color_parser, arguments)?,
) )
}; };
@ -323,16 +289,10 @@ fn parse_hwb<'i, 't>(
color_parser: &ColorParser<'_, '_>, color_parser: &ColorParser<'_, '_>,
arguments: &mut Parser<'i, 't>, arguments: &mut Parser<'i, 't>,
) -> Result<ColorFunction, ParseError<'i>> { ) -> Result<ColorFunction, ParseError<'i>> {
let origin_color = color_parser let _origin_color = parse_origin_color(color_parser, arguments)?;
.origin_color
.map(|c| c.to_color_space(ColorSpace::Hwb));
let color_parser = ColorParser {
context: color_parser.context,
origin_color: origin_color.as_ref(),
};
let (hue, whiteness, blackness, alpha) = parse_components( let (hue, whiteness, blackness, alpha) = parse_components(
&color_parser, color_parser,
arguments, arguments,
ColorParser::parse_number_or_angle, ColorParser::parse_number_or_angle,
ColorParser::parse_number_or_percentage, ColorParser::parse_number_or_percentage,
@ -353,19 +313,12 @@ type IntoLabFn<Output> = fn(
fn parse_lab_like<'i, 't>( fn parse_lab_like<'i, 't>(
color_parser: &ColorParser<'_, '_>, color_parser: &ColorParser<'_, '_>,
arguments: &mut Parser<'i, 't>, arguments: &mut Parser<'i, 't>,
color_space: ColorSpace,
into_color: IntoLabFn<ColorFunction>, into_color: IntoLabFn<ColorFunction>,
) -> Result<ColorFunction, ParseError<'i>> { ) -> Result<ColorFunction, ParseError<'i>> {
let origin_color = color_parser let _origin_color = parse_origin_color(color_parser, arguments)?;
.origin_color
.map(|c| c.to_color_space(color_space));
let color_parser = ColorParser {
context: color_parser.context,
origin_color: origin_color.as_ref(),
};
let (lightness, a, b, alpha) = parse_components( let (lightness, a, b, alpha) = parse_components(
&color_parser, color_parser,
arguments, arguments,
ColorParser::parse_number_or_percentage, ColorParser::parse_number_or_percentage,
ColorParser::parse_number_or_percentage, ColorParser::parse_number_or_percentage,
@ -386,19 +339,12 @@ type IntoLchFn<Output> = fn(
fn parse_lch_like<'i, 't>( fn parse_lch_like<'i, 't>(
color_parser: &ColorParser<'_, '_>, color_parser: &ColorParser<'_, '_>,
arguments: &mut Parser<'i, 't>, arguments: &mut Parser<'i, 't>,
color_space: ColorSpace,
into_color: IntoLchFn<ColorFunction>, into_color: IntoLchFn<ColorFunction>,
) -> Result<ColorFunction, ParseError<'i>> { ) -> Result<ColorFunction, ParseError<'i>> {
let origin_color = color_parser let _origin_color = parse_origin_color(color_parser, arguments)?;
.origin_color
.map(|c| c.to_color_space(color_space));
let color_parser = ColorParser {
context: color_parser.context,
origin_color: origin_color.as_ref(),
};
let (lightness, chroma, hue, alpha) = parse_components( let (lightness, chroma, hue, alpha) = parse_components(
&color_parser, color_parser,
arguments, arguments,
ColorParser::parse_number_or_percentage, ColorParser::parse_number_or_percentage,
ColorParser::parse_number_or_percentage, ColorParser::parse_number_or_percentage,
@ -414,6 +360,8 @@ fn parse_color_with_color_space<'i, 't>(
color_parser: &ColorParser<'_, '_>, color_parser: &ColorParser<'_, '_>,
arguments: &mut Parser<'i, 't>, arguments: &mut Parser<'i, 't>,
) -> Result<ColorFunction, ParseError<'i>> { ) -> Result<ColorFunction, ParseError<'i>> {
let _origin_color = parse_origin_color(color_parser, arguments)?;
let color_space = { let color_space = {
let location = arguments.current_source_location(); let location = arguments.current_source_location();
@ -422,16 +370,8 @@ fn parse_color_with_color_space<'i, 't>(
.map_err(|_| location.new_unexpected_token_error(Token::Ident(ident.clone())))? .map_err(|_| location.new_unexpected_token_error(Token::Ident(ident.clone())))?
}; };
let origin_color = color_parser
.origin_color
.map(|c| c.to_color_space(color_space.into()));
let color_parser = ColorParser {
context: color_parser.context,
origin_color: origin_color.as_ref(),
};
let (c1, c2, c3, alpha) = parse_components( let (c1, c2, c3, alpha) = parse_components(
&color_parser, color_parser,
arguments, arguments,
ColorParser::parse_number_or_percentage, ColorParser::parse_number_or_percentage,
ColorParser::parse_number_or_percentage, ColorParser::parse_number_or_percentage,
@ -513,10 +453,6 @@ impl NumberOrPercentage {
} }
impl ColorComponentType for NumberOrPercentage { impl ColorComponentType for NumberOrPercentage {
fn from_value(value: f32) -> Self {
Self::Number { value }
}
fn units() -> CalcUnits { fn units() -> CalcUnits {
CalcUnits::PERCENTAGE CalcUnits::PERCENTAGE
} }
@ -567,10 +503,6 @@ impl NumberOrAngle {
} }
impl ColorComponentType for NumberOrAngle { impl ColorComponentType for NumberOrAngle {
fn from_value(value: f32) -> Self {
Self::Number { value }
}
fn units() -> CalcUnits { fn units() -> CalcUnits {
CalcUnits::ANGLE CalcUnits::ANGLE
} }
@ -606,10 +538,6 @@ impl ColorComponentType for NumberOrAngle {
/// The raw f32 here is for <number>. /// The raw f32 here is for <number>.
impl ColorComponentType for f32 { impl ColorComponentType for f32 {
fn from_value(value: f32) -> Self {
value
}
fn units() -> CalcUnits { fn units() -> CalcUnits {
CalcUnits::empty() CalcUnits::empty()
} }
@ -632,30 +560,19 @@ impl ColorComponentType for f32 {
} }
/// Used to parse the components of a color. /// Used to parse the components of a color.
#[derive(Clone)]
pub struct ColorParser<'a, 'b: 'a> { pub struct ColorParser<'a, 'b: 'a> {
/// Parser context used for parsing the colors. /// Parser context used for parsing the colors.
pub context: &'a ParserContext<'b>, pub context: &'a ParserContext<'b>,
/// A parsed origin color if one is available.
pub origin_color: Option<&'a AbsoluteColor>,
} }
impl<'a, 'b: 'a> ColorParser<'a, 'b> { impl<'a, 'b: 'a> ColorParser<'a, 'b> {
/// Create a new [ColorParser] with the given context.
pub fn new(context: &'a ParserContext<'b>) -> Self {
Self {
context,
origin_color: None,
}
}
/// Parse an `<number>` or `<angle>` value. /// Parse an `<number>` or `<angle>` value.
fn parse_number_or_angle<'i, 't>( fn parse_number_or_angle<'i, 't>(
&self, &self,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
allow_none: bool, allow_none: bool,
) -> Result<ColorComponent<NumberOrAngle>, ParseError<'i>> { ) -> Result<ColorComponent<NumberOrAngle>, ParseError<'i>> {
ColorComponent::parse(self.context, input, allow_none, self.origin_color) ColorComponent::parse(self.context, input, allow_none)
} }
/// Parse a `<percentage>` value. /// Parse a `<percentage>` value.
@ -670,12 +587,7 @@ impl<'a, 'b: 'a> ColorParser<'a, 'b> {
// doesn't have any more overhead than just parsing a percentage on its // doesn't have any more overhead than just parsing a percentage on its
// own. // own.
Ok( Ok(
match ColorComponent::<NumberOrPercentage>::parse( match ColorComponent::<NumberOrPercentage>::parse(self.context, input, allow_none)? {
self.context,
input,
allow_none,
self.origin_color,
)? {
ColorComponent::None => ColorComponent::None, ColorComponent::None => ColorComponent::None,
ColorComponent::Value(NumberOrPercentage::Percentage { unit_value }) => { ColorComponent::Value(NumberOrPercentage::Percentage { unit_value }) => {
ColorComponent::Value(unit_value) ColorComponent::Value(unit_value)
@ -691,7 +603,7 @@ impl<'a, 'b: 'a> ColorParser<'a, 'b> {
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
allow_none: bool, allow_none: bool,
) -> Result<ColorComponent<f32>, ParseError<'i>> { ) -> Result<ColorComponent<f32>, ParseError<'i>> {
ColorComponent::parse(self.context, input, allow_none, self.origin_color) ColorComponent::parse(self.context, input, allow_none)
} }
/// Parse a `<number>` or `<percentage>` value. /// Parse a `<number>` or `<percentage>` value.
@ -700,7 +612,7 @@ impl<'a, 'b: 'a> ColorParser<'a, 'b> {
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
allow_none: bool, allow_none: bool,
) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> { ) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
ColorComponent::parse(self.context, input, allow_none, self.origin_color) ColorComponent::parse(self.context, input, allow_none)
} }
} }

View file

@ -658,7 +658,7 @@ impl Color {
}, },
}; };
let color_parser = ColorParser::new(&context); let color_parser = ColorParser { context: &context };
match input.try_parse(|i| parsing::parse_color_with(&color_parser, i)) { match input.try_parse(|i| parsing::parse_color_with(&color_parser, i)) {
Ok(mut color) => { Ok(mut color) => {
if let Color::Absolute(ref mut absolute) = color { if let Color::Absolute(ref mut absolute) = color {

View file

@ -1,4 +1,31 @@
[relative-color-out-of-gamut.html] [relative-color-out-of-gamut.html]
[Property color value 'rgb(from color(display-p3 0 1 0) r g b / alpha)']
expected: FAIL
[Property color value 'rgb(from lab(100 104.3 -50.9) r g b)']
expected: FAIL
[Property color value 'rgb(from lab(0 104.3 -50.9) r g b)']
expected: FAIL
[Property color value 'rgb(from lch(100 116 334) r g b)']
expected: FAIL
[Property color value 'rgb(from lch(0 116 334) r g b)']
expected: FAIL
[Property color value 'rgb(from oklab(1 0.365 -0.16) r g b)']
expected: FAIL
[Property color value 'rgb(from oklab(0 0.365 -0.16) r g b)']
expected: FAIL
[Property color value 'rgb(from oklch(1 0.399 336.3) r g b)']
expected: FAIL
[Property color value 'rgb(from oklch(0 0.399 336.3) r g b)']
expected: FAIL
[Property color value 'hsl(from color(display-p3 0 1 0) h s l / alpha)'] [Property color value 'hsl(from color(display-p3 0 1 0) h s l / alpha)']
expected: FAIL expected: FAIL