tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 1975a2d7f60a2fd5cad5662103e305cc15327f50
parent 604a2486ead5757007ccb33c160e16a14fc21e19
Author: Jonathan Kew <jkew@mozilla.com>
Date:   Tue, 28 Oct 2025 14:56:47 +0000

Bug 1682439 - Implement CSS contrast-color() function. r=firefox-style-system-reviewers,emilio,tlouw

This implements the contrast-color() function from CSS Color 5
(https://drafts.csswg.org/css-color-5/#contrast-color).

The extended version proposed in CSS Color 6 is not sufficiently well spec'd
to consider implementing at this time.

Differential Revision: https://phabricator.services.mozilla.com/D269883

Diffstat:
Mlayout/inspector/tests/test_bug877690.html | 1+
Mmodules/libpref/init/StaticPrefList.yaml | 7+++++++
Mservo/components/style/values/computed/color.rs | 41+++++++++++++++++++++++++++++++++++++++++
Mservo/components/style/values/generics/color.rs | 2++
Mservo/components/style/values/specified/color.rs | 26++++++++++++++++++++++++++
Mtesting/web-platform/meta/css/css-color/parsing/color-computed-contrast-color-function.html.ini | 27---------------------------
Mtesting/web-platform/meta/css/css-color/parsing/color-valid-contrast-color-function.html.ini | 27---------------------------
7 files changed, 77 insertions(+), 54 deletions(-)

diff --git a/layout/inspector/tests/test_bug877690.html b/layout/inspector/tests/test_bug877690.html @@ -46,6 +46,7 @@ function do_test() { "hwb", "color-mix", "color", + "contrast-color", "lab", "lch", "light-dark", diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml @@ -9969,6 +9969,13 @@ mirror: always rust: true +# Is support for the contrast-color() function enabled? +- name: layout.css.contrast-color.enabled + type: RelaxedAtomicBool + value: true + mirror: always + rust: true + # Whether alt text in content is enabled. - name: layout.css.content.alt-text.enabled type: RelaxedAtomicBool diff --git a/servo/components/style/values/computed/color.rs b/servo/components/style/values/computed/color.rs @@ -34,6 +34,11 @@ impl ToCss for Color { Self::ColorFunction(ref color_function) => color_function.to_css(dest), Self::CurrentColor => dest.write_str("currentcolor"), Self::ColorMix(ref m) => m.to_css(dest), + Self::ContrastColor(ref c) => { + dest.write_str("contrast-color(")?; + c.to_css(dest)?; + dest.write_char(')') + }, } } } @@ -81,6 +86,42 @@ impl Color { mix.flags, ) }, + Self::ContrastColor(ref c) => { + let bg_color = c.resolve_to_absolute(current_color); + if Self::contrast_ratio(&bg_color, &AbsoluteColor::BLACK) + > Self::contrast_ratio(&bg_color, &AbsoluteColor::WHITE) + { + AbsoluteColor::BLACK + } else { + AbsoluteColor::WHITE + } + }, + } + } + + fn contrast_ratio(a: &AbsoluteColor, b: &AbsoluteColor) -> f32 { + // TODO: This just implements the WCAG 2.1 algorithm, + // https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio + // Consider using a more sophisticated contrast algorithm, e.g. see + // https://apcacontrast.com + let compute = |c| -> f32 { + if c <= 0.04045 { + c / 12.92 + } else { + f32::powf((c + 0.055) / 1.055, 2.4) + } + }; + let luminance = |r, g, b| -> f32 { 0.2126 * r + 0.7152 * g + 0.0722 * b }; + let a = a.into_srgb_legacy(); + let b = b.into_srgb_legacy(); + let a = a.raw_components(); + let b = b.raw_components(); + let la = luminance(compute(a[0]), compute(a[1]), compute(a[2])) + 0.05; + let lb = luminance(compute(b[0]), compute(b[1]), compute(b[2])) + 0.05; + if la > lb { + la / lb + } else { + lb / la } } } diff --git a/servo/components/style/values/generics/color.rs b/servo/components/style/values/generics/color.rs @@ -24,6 +24,8 @@ pub enum GenericColor<Percentage> { CurrentColor, /// The color-mix() function. ColorMix(Box<GenericColorMix<Self, Percentage>>), + /// The contrast-color() function. + ContrastColor(Box<Self>), } /// Flags used to modify the calculation of a color mix result. diff --git a/servo/components/style/values/specified/color.rs b/servo/components/style/values/specified/color.rs @@ -125,6 +125,8 @@ pub enum Color { ColorMix(Box<ColorMix>), /// A light-dark() color. LightDark(Box<GenericLightDark<Self>>), + /// The contrast-color function. + ContrastColor(Box<Color>), /// Quirksmode-only rule for inheriting color from the body #[cfg(feature = "gecko")] InheritFromBodyQuirk, @@ -455,6 +457,17 @@ impl Color { return Ok(Color::LightDark(Box::new(ld))); } + if static_prefs::pref!("layout.css.contrast-color.enabled") { + if let Ok(c) = input.try_parse(|i| { + i.expect_function_matching("contrast-color")?; + i.parse_nested_block(|i| { + Self::parse_internal(context, i, preserve_authored) + }) + }) { + return Ok(Color::ContrastColor(Box::new(c))); + } + } + match e.kind { ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => { Err(e.location.new_custom_error(StyleParseErrorKind::ValueError( @@ -529,6 +542,11 @@ impl ToCss for Color { Color::ColorFunction(ref color_function) => color_function.to_css(dest), Color::ColorMix(ref mix) => mix.to_css(dest), Color::LightDark(ref ld) => ld.to_css(dest), + Color::ContrastColor(ref c) => { + dest.write_str("contrast-color(")?; + c.to_css(dest)?; + dest.write_char(')') + }, #[cfg(feature = "gecko")] Color::System(system) => system.to_css(dest), #[cfg(feature = "gecko")] @@ -563,6 +581,7 @@ impl Color { mix.left.honored_in_forced_colors_mode(allow_transparent) && mix.right.honored_in_forced_colors_mode(allow_transparent) }, + Self::ContrastColor(ref c) => c.honored_in_forced_colors_mode(allow_transparent), } } @@ -768,6 +787,9 @@ impl Color { flags: mix.flags, }) }, + Color::ContrastColor(ref c) => { + ComputedColor::ContrastColor(Box::new(c.to_computed_color(context)?)) + }, #[cfg(feature = "gecko")] Color::System(system) => system.compute(context?), #[cfg(feature = "gecko")] @@ -803,6 +825,9 @@ impl ToComputedValue for Color { ComputedColor::ColorMix(ref mix) => { Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix))) }, + ComputedColor::ContrastColor(ref c) => { + Self::ContrastColor(Box::new(ToComputedValue::from_computed_value(&**c))) + }, } } } @@ -830,6 +855,7 @@ impl SpecifiedValueInfo for Color { "oklab", "oklch", "color-mix", + "contrast-color", "light-dark", ]); } diff --git a/testing/web-platform/meta/css/css-color/parsing/color-computed-contrast-color-function.html.ini b/testing/web-platform/meta/css/css-color/parsing/color-computed-contrast-color-function.html.ini @@ -1,30 +1,3 @@ [color-computed-contrast-color-function.html] - [Property background-color value 'contrast-color(white)'] - expected: FAIL - - [Property background-color value 'contrast-color(black)'] - expected: FAIL - - [Property background-color value 'contrast-color(pink)'] - expected: FAIL - - [Property background-color value 'contrast-color(color(srgb 1 0 1 / 0.5))'] - expected: FAIL - - [Property background-color value 'contrast-color(lab(0.2 0.5 0.2))'] - expected: FAIL - - [Property background-color value 'contrast-color(color(srgb 10 10 10))'] - expected: FAIL - - [Property background-color value 'contrast-color(color(srgb -10 -10 -10))'] - expected: FAIL - - [Property background-color value 'contrast-color(contrast-color(pink))'] - expected: FAIL - - [Property background-color value 'contrast-color(currentcolor)'] - expected: FAIL - [Property background-color value 'contrast-color(color(srgb calc(1 + (sign(20cqw - 10px) * 1)) calc(1 + (sign(20cqw - 10px) * 1)) calc(1 + (sign(20cqw - 10px) * 1))))'] expected: FAIL diff --git a/testing/web-platform/meta/css/css-color/parsing/color-valid-contrast-color-function.html.ini b/testing/web-platform/meta/css/css-color/parsing/color-valid-contrast-color-function.html.ini @@ -1,30 +1,3 @@ [color-valid-contrast-color-function.html] - [e.style['background-color'\] = "contrast-color(white)" should set the property value] - expected: FAIL - - [e.style['background-color'\] = "contrast-color(black)" should set the property value] - expected: FAIL - - [e.style['background-color'\] = "contrast-color(pink)" should set the property value] - expected: FAIL - - [e.style['background-color'\] = "contrast-color(color(srgb 1 0 1 / 0.5))" should set the property value] - expected: FAIL - - [e.style['background-color'\] = "contrast-color(lab(0.2 0.5 0.2))" should set the property value] - expected: FAIL - - [e.style['background-color'\] = "contrast-color(color(srgb 10 10 10))" should set the property value] - expected: FAIL - - [e.style['background-color'\] = "contrast-color(color(srgb -10 -10 -10))" should set the property value] - expected: FAIL - - [e.style['background-color'\] = "contrast-color(contrast-color(pink))" should set the property value] - expected: FAIL - - [e.style['background-color'\] = "contrast-color(currentcolor)" should set the property value] - expected: FAIL - [e.style['background-color'\] = "contrast-color(color(srgb calc(0.5) calc(1 + (sign(20cqw - 10px) * 0.5)) 1 / .5))" should set the property value] expected: FAIL