From 028f2f95d2499e4ddbb8ce1e1200d19f73fb5850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Mon, 22 May 2023 11:56:02 +0200 Subject: [PATCH] style: Initial support for the color-scheme CSS property Add initial support for the color-scheme CSS property, allowing pages to choose between light and dark system colors per-element, and such. Things that are left to do so that this can be enabled by default: * Dark system colors on Windows / Android / Standins. * Dark Canvas/CanvasText/Link visited colors (which right now are set via PreferenceSheet). * Dark form controls in nsNativeBasicTheme. * Processing the color-scheme meta tag to fill-in Document::mColorSchemeBits. But this seems like enough progress to be landable on its own. Differential Revision: https://phabricator.services.mozilla.com/D120843 --- .../properties/longhands/inherited_ui.mako.rs | 11 ++ .../style/properties/properties.mako.rs | 3 + components/style/values/computed/color.rs | 2 + components/style/values/computed/mod.rs | 2 +- components/style/values/specified/color.rs | 126 +++++++++++++++++- components/style/values/specified/mod.rs | 2 +- 6 files changed, 143 insertions(+), 3 deletions(-) diff --git a/components/style/properties/longhands/inherited_ui.mako.rs b/components/style/properties/longhands/inherited_ui.mako.rs index f08abbc6235..badcc3f2012 100644 --- a/components/style/properties/longhands/inherited_ui.mako.rs +++ b/components/style/properties/longhands/inherited_ui.mako.rs @@ -95,6 +95,17 @@ ${helpers.predefined_type( has_effect_on_gecko_scrollbars=False, )} +${helpers.predefined_type( + "color-scheme", + "ColorScheme", + "specified::color::ColorScheme::normal()", + engines="gecko", + spec="https://drafts.csswg.org/css-color-adjust/#color-scheme-prop", + gecko_pref="layout.css.color-scheme.enabled", + animation_value_type="discrete", + has_effect_on_gecko_scrollbars=False, +)} + ${helpers.predefined_type( "scrollbar-color", "ui::ScrollbarColor", diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 7b69f23db9e..ae5e4b6afb1 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -1376,6 +1376,9 @@ impl LonghandId { LonghandId::FontStyle | LonghandId::FontFamily | + // color-scheme affects how system colors resolve. + LonghandId::ColorScheme | + // Needed to properly compute the writing mode, to resolve logical // properties, and similar stuff. LonghandId::WritingMode | diff --git a/components/style/values/computed/color.rs b/components/style/values/computed/color.rs index 664fed95886..36cd1db4a42 100644 --- a/components/style/values/computed/color.rs +++ b/components/style/values/computed/color.rs @@ -11,6 +11,8 @@ use cssparser::{Color as CSSParserColor, RGBA}; use std::fmt; use style_traits::{CssWriter, ToCss}; +pub use crate::values::specified::color::ColorScheme; + /// The computed value of the `color` property. pub type ColorPropertyValue = RGBA; diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 1098bae1209..c87142ac1b5 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -50,7 +50,7 @@ pub use self::box_::{Display, Overflow, OverflowAnchor, TransitionProperty}; pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize}; pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType}; pub use self::box_::{TouchAction, VerticalAlign, WillChange}; -pub use self::color::{Color, ColorOrAuto, ColorPropertyValue}; +pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme}; pub use self::column::ColumnCount; pub use self::counters::{Content, ContentItem, CounterIncrement, CounterSetOrReset}; pub use self::easing::TimingFunction; diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 408dfc99e9a..e4b73f6c0da 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -12,6 +12,7 @@ use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue}; use crate::values::generics::color::{GenericColorOrAuto, GenericCaretColor}; use crate::values::specified::calc::CalcNode; use crate::values::specified::Percentage; +use crate::values::CustomIdent; use cssparser::{AngleOrNumber, Color as CSSParserColor, Parser, Token, RGBA}; use cssparser::{BasicParseErrorKind, NumberOrPercentage, ParseErrorKind}; use itoa; @@ -409,7 +410,10 @@ impl SystemColor { use crate::gecko_bindings::bindings; let colors = &cx.device().pref_sheet_prefs().mColors; + let style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme(); + // TODO: At least Canvas / CanvasText should be color-scheme aware + // (probably the link colors too). convert_nscolor_to_computedcolor(match *self { SystemColor::Canvastext => colors.mDefault, SystemColor::Canvas => colors.mDefaultBackground, @@ -419,7 +423,7 @@ impl SystemColor { _ => { let color = unsafe { - bindings::Gecko_GetLookAndFeelSystemColor(*self as i32, cx.device().document(), scheme) + bindings::Gecko_GetLookAndFeelSystemColor(*self as i32, cx.device().document(), scheme, &style_color_scheme) }; if color == bindings::NS_SAME_AS_FOREGROUND_COLOR { return ComputedColor::currentcolor(); @@ -876,3 +880,123 @@ impl Parse for CaretColor { ColorOrAuto::parse(context, input).map(GenericCaretColor) } } + +bitflags! { + /// Various flags to represent the color-scheme property in an efficient + /// way. + #[derive(Default, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] + #[repr(C)] + #[value_info(other_values = "light,dark,only")] + pub struct ColorSchemeFlags: u8 { + /// Whether the author specified `light`. + const LIGHT = 1 << 0; + /// Whether the author specified `dark`. + const DARK = 1 << 1; + /// Whether the author specified `only`. + const ONLY = 1 << 2; + } +} + +/// +#[derive( + Clone, + Debug, + Default, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +#[value_info(other_values = "normal")] +pub struct ColorScheme { + #[ignore_malloc_size_of = "Arc"] + idents: crate::ArcSlice, + bits: ColorSchemeFlags, +} + +impl ColorScheme { + /// Returns the `normal` value. + pub fn normal() -> Self { + Self { + idents: Default::default(), + bits: ColorSchemeFlags::empty(), + } + } +} + +impl Parse for ColorScheme { + fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { + let mut idents = vec![]; + let mut bits = ColorSchemeFlags::empty(); + + let mut location = input.current_source_location(); + while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) { + let mut is_only = false; + match_ignore_ascii_case! { &ident, + "normal" => { + if idents.is_empty() && bits.is_empty() { + return Ok(Self::normal()); + } + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + }, + "light" => bits.insert(ColorSchemeFlags::LIGHT), + "dark" => bits.insert(ColorSchemeFlags::DARK), + "only" => { + if bits.intersects(ColorSchemeFlags::ONLY) { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + bits.insert(ColorSchemeFlags::ONLY); + is_only = true; + }, + _ => {}, + }; + + if is_only { + if !idents.is_empty() { + // Only is allowed either at the beginning or at the end, + // but not in the middle. + break; + } + } else { + idents.push(CustomIdent::from_ident(location, &ident, &[])?); + } + location = input.current_source_location(); + } + + if idents.is_empty() { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + Ok(Self { + idents: crate::ArcSlice::from_iter(idents.into_iter()), + bits, + }) + } +} + +impl ToCss for ColorScheme { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + if self.idents.is_empty() { + debug_assert!(self.bits.is_empty()); + return dest.write_str("normal"); + } + let mut first = true; + for ident in self.idents.iter() { + if !first { + dest.write_char(' ')?; + } + first = false; + ident.to_css(dest)?; + } + if self.bits.intersects(ColorSchemeFlags::ONLY) { + dest.write_str(" only")?; + } + Ok(()) + } +} diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 11498337a7f..6cd952cd82f 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -42,7 +42,7 @@ pub use self::box_::{Clear, Float, Overflow, OverflowAnchor}; pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize}; pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType}; pub use self::box_::{TouchAction, TransitionProperty, VerticalAlign, WillChange}; -pub use self::color::{Color, ColorOrAuto, ColorPropertyValue}; +pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme}; pub use self::column::ColumnCount; pub use self::counters::{Content, ContentItem, CounterIncrement, CounterSetOrReset}; pub use self::easing::TimingFunction;