diff --git a/layout/style/GeckoBindings.cpp b/layout/style/GeckoBindings.cpp index 9ef1dddce2de..7d1fd0f5c449 100644 --- a/layout/style/GeckoBindings.cpp +++ b/layout/style/GeckoBindings.cpp @@ -696,6 +696,12 @@ bool Gecko_IsDocumentBody(const Element* aElement) { return doc && doc->GetBodyElement() == aElement; } +bool Gecko_IsDarkColorScheme(const Document* aDoc, + const StyleColorScheme* aStyle) { + return LookAndFeel::ColorSchemeForStyle(*aDoc, aStyle->bits) == + ColorScheme::Dark; +} + nscolor Gecko_ComputeSystemColor(StyleSystemColor aColor, const Document* aDoc, const StyleColorScheme* aStyle) { auto colorScheme = LookAndFeel::ColorSchemeForStyle(*aDoc, aStyle->bits); diff --git a/layout/style/GeckoBindings.h b/layout/style/GeckoBindings.h index c6f58518e079..1a7f0ed1b4cc 100644 --- a/layout/style/GeckoBindings.h +++ b/layout/style/GeckoBindings.h @@ -527,6 +527,8 @@ void Gecko_StyleSheet_AddRef(const mozilla::StyleSheet* aSheet); void Gecko_StyleSheet_Release(const mozilla::StyleSheet* aSheet); bool Gecko_IsDocumentBody(const mozilla::dom::Element* element); +bool Gecko_IsDarkColorScheme(const mozilla::dom::Document*, + const mozilla::StyleColorScheme*); nscolor Gecko_ComputeSystemColor(mozilla::StyleSystemColor, const mozilla::dom::Document*, const mozilla::StyleColorScheme*); diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index 4a227e4e4a71..19ab1ee61a56 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -8375,6 +8375,13 @@ mirror: always rust: true +# Is support for light-dark() on content enabled? +- name: layout.css.light-dark.enabled + type: RelaxedAtomicBool + value: false + mirror: always + rust: true + # Is support for color-mix with non-SRGB color spaces on content enabled? - name: layout.css.color-mix.color-spaces.enabled type: RelaxedAtomicBool diff --git a/servo/components/style/gecko/media_queries.rs b/servo/components/style/gecko/media_queries.rs index 2ea4229133a8..c639f6a29182 100644 --- a/servo/components/style/gecko/media_queries.rs +++ b/servo/components/style/gecko/media_queries.rs @@ -487,6 +487,11 @@ impl Device { unsafe { bindings::Gecko_ComputeSystemColor(system_color, self.document(), color_scheme) } } + /// Returns whether the used color-scheme for `color-scheme` should be dark. + pub(crate) fn is_dark_color_scheme(&self, color_scheme: &ColorScheme) -> bool { + unsafe { bindings::Gecko_IsDarkColorScheme(self.document(), color_scheme) } + } + /// Returns the default background color. /// /// This is only for forced-colors/high-contrast, so looking at light colors diff --git a/servo/components/style/values/specified/color.rs b/servo/components/style/values/specified/color.rs index b647711b60ed..3a745d488bd2 100644 --- a/servo/components/style/values/specified/color.rs +++ b/servo/components/style/values/specified/color.rs @@ -63,7 +63,7 @@ impl ColorMix { let mut right_percentage = try_parse_percentage(input); - let right = Color::parse(context, input)?; + let right = Color::parse_internal(context, input, preserve_authored)?; if right_percentage.is_none() { right_percentage = try_parse_percentage(input); @@ -130,11 +130,53 @@ pub enum Color { System(SystemColor), /// A color mix. ColorMix(Box), + /// A light-dark() color. + LightDark(Box), /// Quirksmode-only rule for inheriting color from the body #[cfg(feature = "gecko")] InheritFromBodyQuirk, } +/// A light-dark(, ) function. +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, ToCss)] +#[css(function, comma)] +pub struct LightDark { + light: Color, + dark: Color, +} + +impl LightDark { + fn compute(&self, cx: &Context) -> ComputedColor { + let style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme(); + let dark = cx.device().is_dark_color_scheme(&style_color_scheme); + let used = if dark { + &self.dark + } else { + &self.light + }; + used.to_computed_value(cx) + } + + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + preserve_authored: PreserveAuthored, + ) -> Result> { + let enabled = + context.chrome_rules_enabled() || static_prefs::pref!("layout.css.light-dark.enabled"); + if !enabled { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + input.expect_function_matching("light-dark")?; + input.parse_nested_block(|input| { + let light = Color::parse_internal(context, input, preserve_authored)?; + input.expect_comma()?; + let dark = Color::parse_internal(context, input, preserve_authored)?; + Ok(LightDark { light, dark }) + }) + } +} + impl From for Color { #[inline] fn from(value: AbsoluteColor) -> Self { @@ -376,8 +418,7 @@ impl SystemColor { use crate::gecko::values::convert_nscolor_to_absolute_color; use crate::gecko_bindings::bindings; - // TODO: We should avoid cloning here most likely, though it's - // cheap-ish. + // TODO: We should avoid cloning here most likely, though it's cheap-ish. let style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme(); let color = cx.device().system_nscolor(*self, &style_color_scheme); if color == bindings::NS_SAME_AS_FOREGROUND_COLOR { @@ -574,6 +615,7 @@ impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorParser<'i> for ColorParser<'a, 'b> { /// Whether to preserve authored colors during parsing. That's useful only if we /// plan to serialize the color back. +#[derive(Copy, Clone)] enum PreserveAuthored { No, Yes, @@ -645,6 +687,11 @@ impl Color { return Ok(Color::ColorMix(Box::new(mix))); } + if let Ok(ld) = input.try_parse(|i| LightDark::parse(context, i, preserve_authored)) + { + return Ok(Color::LightDark(Box::new(ld))); + } + match e.kind { ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => { Err(e.location.new_custom_error(StyleParseErrorKind::ValueError( @@ -717,6 +764,7 @@ impl ToCss for Color { Color::CurrentColor => cssparser::ToCss::to_css(&CSSParserColor::CurrentColor, dest), Color::Absolute(ref absolute) => absolute.to_css(dest), Color::ColorMix(ref mix) => mix.to_css(dest), + Color::LightDark(ref ld) => ld.to_css(dest), #[cfg(feature = "gecko")] Color::System(system) => system.to_css(dest), #[cfg(feature = "gecko")] @@ -732,6 +780,10 @@ impl Color { Self::InheritFromBodyQuirk => false, Self::CurrentColor | Color::System(..) => true, Self::Absolute(ref absolute) => allow_transparent && absolute.color.alpha() == 0.0, + Self::LightDark(ref ld) => { + ld.light.honored_in_forced_colors_mode(allow_transparent) && + ld.dark.honored_in_forced_colors_mode(allow_transparent) + }, Self::ColorMix(ref mix) => { mix.left.honored_in_forced_colors_mode(allow_transparent) && mix.right.honored_in_forced_colors_mode(allow_transparent) @@ -854,6 +906,7 @@ impl Color { Some(match *self { Color::CurrentColor => ComputedColor::CurrentColor, Color::Absolute(ref absolute) => ComputedColor::Absolute(absolute.color), + Color::LightDark(ref ld) => ld.compute(context?), Color::ColorMix(ref mix) => { use crate::values::computed::percentage::Percentage; diff --git a/testing/web-platform/mozilla/meta/css/css-color/light-dark.html.ini b/testing/web-platform/mozilla/meta/css/css-color/light-dark.html.ini new file mode 100644 index 000000000000..9f949fecc31e --- /dev/null +++ b/testing/web-platform/mozilla/meta/css/css-color/light-dark.html.ini @@ -0,0 +1,2 @@ +[light-dark.html] +prefs: [layout.css.light-dark.enabled:true] diff --git a/testing/web-platform/mozilla/tests/css/css-color/light-dark.html b/testing/web-platform/mozilla/tests/css/css-color/light-dark.html new file mode 100644 index 000000000000..ee0533223842 --- /dev/null +++ b/testing/web-platform/mozilla/tests/css/css-color/light-dark.html @@ -0,0 +1,26 @@ + +light-dark() color-scheme propagation + + + + +
+
+
+