diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index f7ab9f95fde..275ed7afc38 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -379,13 +379,15 @@ where type DeclarationsToApplyUnlessOverriden = SmallVec<[PropertyDeclaration; 2]>; fn tweak_when_ignoring_colors( - builder: &StyleBuilder, + context: &computed::Context, longhand_id: LonghandId, origin: Origin, declaration: &mut Cow, declarations_to_apply_unless_overriden: &mut DeclarationsToApplyUnlessOverriden, ) { use crate::values::specified::Color; + use crate::values::computed::ToComputedValue; + use cssparser::RGBA; if !longhand_id.ignored_when_document_colors_disabled() { return; @@ -399,34 +401,16 @@ fn tweak_when_ignoring_colors( // Don't override background-color on ::-moz-color-swatch. It is set as an // author style (via the style attribute), but it's pretty important for it // to show up for obvious reasons :) - if builder.pseudo.map_or(false, |p| p.is_color_swatch()) && + if context.builder.pseudo.map_or(false, |p| p.is_color_swatch()) && longhand_id == LonghandId::BackgroundColor { return; } - fn alpha_channel(color: &Color) -> u8 { - match *color { - // Seems safe enough to assume that the default color and system - // colors are opaque in HCM, though maybe we shouldn't asume the - // later? - #[cfg(feature = "gecko")] - Color::InheritFromBodyQuirk | Color::System(..) => 255, - // We don't have the actual color here, but since except for color: - // transparent we force opaque text colors, it seems sane to do - // this. You can technically fool this bit of code with: - // - // color: transparent; background-color: currentcolor; - // - // but this is best-effort, and that seems unlikely to happen in - // practice. - Color::CurrentColor => 255, - // Complex colors are results of interpolation only and probably - // shouldn't show up around here in HCM, but we've always treated - // them as opaque effectively so keep doing it. - Color::Complex { .. } => 255, - Color::Numeric { ref parsed, .. } => parsed.alpha, - } + fn alpha_channel(color: &Color, context: &computed::Context) -> u8 { + // We assume here currentColor is opaque. + let color = color.to_computed_value(context).to_rgba(RGBA::new(0, 0, 0, 255)); + color.alpha } // A few special-cases ahead. @@ -440,9 +424,9 @@ fn tweak_when_ignoring_colors( // should consider not doing that even if it causes some issues like // bug 1625036, or finding a performant way to preserve the original // widget background color's rgb channels but not alpha... - let alpha = alpha_channel(color); + let alpha = alpha_channel(color, context); if alpha != 0 { - let mut color = builder.device.default_background_color(); + let mut color = context.builder.device.default_background_color(); color.alpha = alpha; declarations_to_apply_unless_overriden .push(PropertyDeclaration::BackgroundColor(color.into())) @@ -450,14 +434,14 @@ fn tweak_when_ignoring_colors( }, PropertyDeclaration::Color(ref color) => { // We honor color: transparent, and "revert-or-initial" otherwise. - if alpha_channel(&color.0) == 0 { + if alpha_channel(&color.0, context) == 0 { return; } // If the inherited color would be transparent, but we would // override this with a non-transparent color, then override it with // the default color. Otherwise just let it inherit through. - if builder.get_parent_inherited_text().clone_color().alpha == 0 { - let color = builder.device.default_color(); + if context.builder.get_parent_inherited_text().clone_color().alpha == 0 { + let color = context.builder.device.default_color(); declarations_to_apply_unless_overriden.push(PropertyDeclaration::Color( specified::ColorPropertyValue(color.into()), )) @@ -631,7 +615,7 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { // properties that are marked as ignored in that mode. if ignore_colors { tweak_when_ignoring_colors( - &self.context.builder, + &self.context, longhand_id, origin, &mut declaration, diff --git a/components/style/values/animated/color.rs b/components/style/values/animated/color.rs index 4ef73dda276..77091da01ec 100644 --- a/components/style/values/animated/color.rs +++ b/components/style/values/animated/color.rs @@ -104,6 +104,11 @@ impl Color { } } + /// Mix two colors into one. + pub fn mix(left: &Color, right: &Color, progress: f32) -> Self { + left.animate(right, Procedure::Interpolate { progress: progress as f64 }).unwrap() + } + fn scaled_rgba(&self) -> RGBA { if self.ratios.bg == 0. { return RGBA::transparent(); diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 71b1c5b077c..903b867bf73 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -9,8 +9,9 @@ use super::AllowQuirks; use crate::gecko_bindings::structs::nscolor; use crate::parser::{Parse, ParserContext}; use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue}; -use crate::values::generics::color::{ColorOrAuto as GenericColorOrAuto}; +use crate::values::generics::color::ColorOrAuto as GenericColorOrAuto; use crate::values::specified::calc::CalcNode; +use crate::values::specified::Percentage; use cssparser::{AngleOrNumber, Color as CSSParserColor, Parser, Token, RGBA}; use cssparser::{BasicParseErrorKind, NumberOrPercentage, ParseErrorKind}; use itoa; @@ -19,6 +20,74 @@ use std::io::Write as IoWrite; use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind}; use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind}; +/// A restricted version of the css `color-mix()` function, which only supports +/// percentages and sRGB color-space interpolation. +/// +/// https://drafts.csswg.org/css-color-5/#color-mix +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] +#[allow(missing_docs)] +pub struct ColorMix { + pub left: Color, + pub right: Color, + pub percentage: Percentage, +} + +// NOTE(emilio): Syntax is still a bit in-flux, since [1] doesn't seem +// particularly complete, and disagrees with the examples. +// +// [1]: https://github.com/w3c/csswg-drafts/commit/a4316446112f9e814668c2caff7f826f512f8fed +impl Parse for ColorMix { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let enabled = + context.chrome_rules_enabled() || static_prefs::pref!("layout.css.color-mix.enabled"); + + if !enabled { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + input.expect_function_matching("color-mix")?; + + // NOTE(emilio): This implements the syntax described here for now, + // might need to get updated in the future. + // + // https://github.com/w3c/csswg-drafts/issues/6066#issuecomment-789836765 + input.parse_nested_block(|input| { + input.expect_ident_matching("in")?; + // TODO: support multiple interpolation spaces. + input.expect_ident_matching("srgb")?; + input.expect_comma()?; + let left = Color::parse(context, input)?; + let percentage = input.try_parse(|input| { + Percentage::parse(context, input) + }).unwrap_or_else(|_| Percentage::new(0.5)); + input.expect_comma()?; + let right = Color::parse(context, input)?; + + Ok(ColorMix { left, right, percentage }) + }) + } +} + +impl ToCss for ColorMix { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + dest.write_str("color-mix(in srgb, ")?; + self.left.to_css(dest)?; + if self.percentage.get() != 0.5 || self.percentage.is_calc() { + dest.write_str(" ")?; + self.percentage.to_css(dest)?; + } + dest.write_str(", ")?; + self.right.to_css(dest)?; + dest.write_str(")") + } +} + /// Specified color value #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] pub enum Color { @@ -36,6 +105,8 @@ pub enum Color { /// A system color #[cfg(feature = "gecko")] System(SystemColor), + /// A color mix. + ColorMix(Box), /// Quirksmode-only rule for inheriting color from the body #[cfg(feature = "gecko")] InheritFromBodyQuirk, @@ -338,8 +409,6 @@ impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorComponentParser<'i> for ColorComponen } fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result> { - use crate::values::specified::Percentage; - Ok(Percentage::parse(self.0, input)?.get()) } @@ -398,6 +467,10 @@ impl Parse for Color { } } + if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i)) { + return Ok(Color::ColorMix(Box::new(mix))); + } + match e.kind { ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => { Err(e.location.new_custom_error(StyleParseErrorKind::ValueError( @@ -425,7 +498,9 @@ impl ToCss for Color { Color::Numeric { parsed: ref rgba, .. } => rgba.to_css(dest), + // TODO: Could represent this as a color-mix() instead. Color::Complex(_) => Ok(()), + Color::ColorMix(ref mix) => mix.to_css(dest), #[cfg(feature = "gecko")] Color::System(system) => system.to_css(dest), #[cfg(feature = "gecko")] @@ -562,17 +637,23 @@ impl Color { /// /// If `context` is `None`, and the specified color requires data from /// the context to resolve, then `None` is returned. - pub fn to_computed_color(&self, _context: Option<&Context>) -> Option { + pub fn to_computed_color(&self, context: Option<&Context>) -> Option { Some(match *self { Color::CurrentColor => ComputedColor::currentcolor(), Color::Numeric { ref parsed, .. } => ComputedColor::rgba(*parsed), Color::Complex(ref complex) => *complex, - #[cfg(feature = "gecko")] - Color::System(system) => system.compute(_context?), - #[cfg(feature = "gecko")] - Color::InheritFromBodyQuirk => { - ComputedColor::rgba(_context?.device().body_text_color()) + Color::ColorMix(ref mix) => { + use crate::values::animated::color::Color as AnimatedColor; + use crate::values::animated::ToAnimatedValue; + + let left = mix.left.to_computed_color(context)?.to_animated_value(); + let right = mix.right.to_computed_color(context)?.to_animated_value(); + ToAnimatedValue::from_animated_value(AnimatedColor::mix(&left, &right, mix.percentage.get())) }, + #[cfg(feature = "gecko")] + Color::System(system) => system.compute(context?), + #[cfg(feature = "gecko")] + Color::InheritFromBodyQuirk => ComputedColor::rgba(context?.device().body_text_color()), }) } }