From 23d69ea77ddb26df872ba560fed6c07553576e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 2 Dec 2017 00:07:52 +0100 Subject: [PATCH] style: Support calc() in color functions. --- Cargo.lock | 28 +-- components/style/values/specified/angle.rs | 7 + components/style/values/specified/calc.rs | 42 +++- components/style/values/specified/color.rs | 179 ++++++++++++------ ports/geckolib/glue.rs | 11 +- tests/wpt/metadata/MANIFEST.json | 10 + .../css/css-values/calc-in-color-001.html | 25 +++ 7 files changed, 230 insertions(+), 72 deletions(-) create mode 100644 tests/wpt/web-platform-tests/css/css-values/calc-in-color-001.html diff --git a/Cargo.lock b/Cargo.lock index 4a011eda276..bb282bb3ca5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -281,7 +281,7 @@ dependencies = [ "azure 0.23.0 (git+https://github.com/servo/rust-azure)", "canvas_traits 0.0.1", "compositing 0.0.1", - "cssparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cssparser 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "gleam 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -298,7 +298,7 @@ dependencies = [ name = "canvas_traits" version = "0.0.1" dependencies = [ - "cssparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cssparser 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", "ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -561,7 +561,7 @@ dependencies = [ [[package]] name = "cssparser" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cssparser-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1038,7 +1038,7 @@ name = "geckoservo" version = "0.0.1" dependencies = [ "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cssparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cssparser 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1109,7 +1109,7 @@ dependencies = [ name = "gfx_tests" version = "0.0.1" dependencies = [ - "cssparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cssparser 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)", "gfx 0.0.1", "ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "style 0.0.1", @@ -1696,7 +1696,7 @@ name = "malloc_size_of" version = "0.0.1" dependencies = [ "app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "cssparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cssparser 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", "hashglobe 0.1.0", "mozjs 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2560,7 +2560,7 @@ dependencies = [ "caseless 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "cmake 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "cookie 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cssparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cssparser 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)", "deny_public_fields 0.0.1", "devtools_traits 0.0.1", "dom_struct 0.0.1", @@ -2636,7 +2636,7 @@ dependencies = [ "app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "canvas_traits 0.0.1", - "cssparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cssparser 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", "gfx_traits 0.0.1", "html5ever 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2717,7 +2717,7 @@ name = "selectors" version = "0.19.0" dependencies = [ "bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cssparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cssparser 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "malloc_size_of 0.0.1", @@ -3133,7 +3133,7 @@ dependencies = [ "bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "cssparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cssparser 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)", "encoding_rs 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", "fallible 0.0.1", @@ -3192,7 +3192,7 @@ version = "0.0.1" dependencies = [ "app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cssparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cssparser 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", "html5ever 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3214,7 +3214,7 @@ version = "0.0.1" dependencies = [ "app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cssparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cssparser 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", "malloc_size_of 0.0.1", "malloc_size_of_derive 0.0.1", @@ -3230,7 +3230,7 @@ name = "stylo_tests" version = "0.0.1" dependencies = [ "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cssparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cssparser 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", "geckoservo 0.0.1", @@ -3811,7 +3811,7 @@ dependencies = [ "checksum core-foundation-sys 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bc9fb3d6cb663e6fd7cf1c63f9b144ee2b1e4a78595a0451dd34bff85b9a3387" "checksum core-graphics 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5dc0a78ab2ac23b6ea7b3fe5fe93b227900dc0956979735b8f68032417976dd4" "checksum core-text 8.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bcad23756dd1dc4b47bf6a914ace27aadb8fa68889db5837af2308d018d0467c" -"checksum cssparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)" = "217b0241b46d3b758271d88086d2d935b9f21aafa6112813b5f5f854b826146c" +"checksum cssparser 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)" = "83b169a4a5c49826a93ab1778008771b3b05bca95deea76e6c723c667dbc74b8" "checksum cssparser-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "079adec4af52bb5275eadd004292028c79eb3c5f5b4ee8086a36d4197032f6df" "checksum cubeb-ffi 0.0.2 (git+https://github.com/djg/cubeb-pulse-rs?branch=dev)" = "" "checksum cubeb-pulse 0.0.2 (git+https://github.com/djg/cubeb-pulse-rs?branch=dev)" = "" diff --git a/components/style/values/specified/angle.rs b/components/style/values/specified/angle.rs index 67f3463a474..b776a69db3c 100644 --- a/components/style/values/specified/angle.rs +++ b/components/style/values/specified/angle.rs @@ -81,6 +81,13 @@ impl Angle { self.value.radians() } + /// Returns the amount of degrees this angle represents. + #[inline] + pub fn degrees(self) -> f32 { + use std::f32::consts::PI; + self.radians() * 360. / (2. * PI) + } + /// Returns `0deg`. pub fn zero() -> Self { Self::from_degrees(0.0, false) diff --git a/components/style/values/specified/calc.rs b/components/style/values/specified/calc.rs index c530bb6e2f3..9b9a78db5e4 100644 --- a/components/style/values/specified/calc.rs +++ b/components/style/values/specified/calc.rs @@ -6,7 +6,7 @@ //! //! [calc]: https://drafts.csswg.org/css-values/#calc-notation -use cssparser::{Parser, Token}; +use cssparser::{Parser, Token, NumberOrPercentage, AngleOrNumber}; use parser::ParserContext; #[allow(unused_imports)] use std::ascii::AsciiExt; use std::fmt; @@ -202,9 +202,8 @@ impl CalcNode { fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, - expected_unit: CalcUnit) - -> Result> - { + expected_unit: CalcUnit, + ) -> Result> { let mut root = Self::parse_product(context, input, expected_unit)?; loop { @@ -613,4 +612,39 @@ impl CalcNode { .to_time() .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } + + /// Convenience parsing function for `` or ``. + pub fn parse_number_or_percentage<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't> + ) -> Result> { + let node = Self::parse(context, input, CalcUnit::Percentage)?; + + if let Ok(value) = node.to_number() { + return Ok(NumberOrPercentage::Number { value }) + } + + match node.to_percentage() { + Ok(unit_value) => Ok(NumberOrPercentage::Percentage { unit_value }), + Err(()) => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), + } + } + + /// Convenience parsing function for `` or ``. + pub fn parse_angle_or_number<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't> + ) -> Result> { + let node = Self::parse(context, input, CalcUnit::Angle)?; + + if let Ok(angle) = node.to_angle() { + let degrees = angle.degrees(); + return Ok(AngleOrNumber::Angle { degrees }) + } + + match node.to_number() { + Ok(value) => Ok(AngleOrNumber::Number { value }), + Err(()) => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), + } + } } diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 7fe97f45ccd..87ab21a3c67 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -4,7 +4,8 @@ //! Specified color values. -use cssparser::{Color as CSSParserColor, Parser, RGBA, Token, BasicParseError, BasicParseErrorKind}; +use cssparser::{AngleOrNumber, Color as CSSParserColor, Parser, RGBA, Token}; +use cssparser::{BasicParseErrorKind, NumberOrPercentage, ParseErrorKind}; #[cfg(feature = "gecko")] use gecko_bindings::structs::nscolor; use itoa; @@ -16,6 +17,7 @@ use std::io::Write; use style_traits::{ToCss, ParseError, StyleParseErrorKind, ValueParseErrorKind}; use super::AllowQuirks; use values::computed::{Color as ComputedColor, Context, ToComputedValue}; +use values::specified::calc::CalcNode; /// Specified color value #[derive(Clone, Debug, MallocSizeOf, PartialEq)] @@ -43,7 +45,6 @@ pub enum Color { InheritFromBodyQuirk, } - #[cfg(feature = "gecko")] mod gecko { define_css_keyword_enum! { SpecialColorKeyword: @@ -61,9 +62,127 @@ impl From for Color { } } +struct ColorComponentParser<'a, 'b: 'a>(&'a ParserContext<'b>); +impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorComponentParser<'i> for ColorComponentParser<'a, 'b> { + type Error = StyleParseErrorKind<'i>; + + fn parse_angle_or_number<'t>( + &self, + input: &mut Parser<'i, 't>, + ) -> Result> { + #[allow(unused_imports)] use std::ascii::AsciiExt; + use values::specified::Angle; + + let location = input.current_source_location(); + let token = input.next()?.clone(); + match token { + Token::Dimension { value, ref unit, .. } => { + let angle = Angle::parse_dimension( + value, + unit, + /* from_calc = */ false, + ); + + let degrees = match angle { + Ok(angle) => angle.degrees(), + Err(()) => return Err(location.new_unexpected_token_error(token.clone())), + }; + + Ok(AngleOrNumber::Angle { degrees }) + } + Token::Number { value, .. } => { + Ok(AngleOrNumber::Number { value }) + } + Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { + input.parse_nested_block(|i| CalcNode::parse_angle_or_number(self.0, i)) + } + t => return Err(location.new_unexpected_token_error(t)), + } + } + + fn parse_percentage<'t>( + &self, + input: &mut Parser<'i, 't>, + ) -> Result> { + use values::specified::Percentage; + + Ok(Percentage::parse(self.0, input)?.get()) + } + + fn parse_number<'t>( + &self, + input: &mut Parser<'i, 't>, + ) -> Result> { + use values::specified::Number; + + Ok(Number::parse(self.0, input)?.get()) + } + + fn parse_number_or_percentage<'t>( + &self, + input: &mut Parser<'i, 't>, + ) -> Result> { + #[allow(unused_imports)] use std::ascii::AsciiExt; + + let location = input.current_source_location(); + + match input.next()?.clone() { + Token::Number { value, .. } => Ok(NumberOrPercentage::Number { value }), + Token::Percentage { unit_value, .. } => { + Ok(NumberOrPercentage::Percentage { unit_value }) + }, + Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { + input.parse_nested_block(|i| CalcNode::parse_number_or_percentage(self.0, i)) + } + t => return Err(location.new_unexpected_token_error(t)) + } + } +} + impl Parse for Color { - fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { - Color::parse_color(input) + fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { + #[allow(unused_imports)] use std::ascii::AsciiExt; + + // Currently we only store authored value for color keywords, + // because all browsers serialize those values as keywords for + // specified value. + let start = input.state(); + let authored = input.expect_ident_cloned().ok(); + input.reset(&start); + + let compontent_parser = ColorComponentParser(&*context); + match input.try(|i| CSSParserColor::parse_with(&compontent_parser, i)) { + Ok(value) => { + Ok(match value { + CSSParserColor::CurrentColor => Color::CurrentColor, + CSSParserColor::RGBA(rgba) => Color::Numeric { + parsed: rgba, + authored: authored.map(|s| s.to_ascii_lowercase().into_boxed_str()), + }, + }) + } + Err(e) => { + #[cfg(feature = "gecko")] + { + if let Ok(system) = input.try(SystemColor::parse) { + return Ok(Color::System(system)); + } + + if let Ok(c) = gecko::SpecialColorKeyword::parse(input) { + return Ok(Color::Special(c)); + } + } + + match e.kind { + ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => { + Err(e.location.new_custom_error( + StyleParseErrorKind::ValueError(ValueParseErrorKind::InvalidColor(t)) + )) + } + _ => Err(e) + } + } + } } } @@ -86,9 +205,9 @@ impl ToCss for Color { /// A wrapper of cssparser::Color::parse_hash. /// -/// That function should never return CurrentColor, so it makes no sense -/// to handle a cssparser::Color here. This should really be done in -/// cssparser directly rather than here. +/// That function should never return CurrentColor, so it makes no sense to +/// handle a cssparser::Color here. This should really be done in cssparser +/// directly rather than here. fn parse_hash_color(value: &[u8]) -> Result { CSSParserColor::parse_hash(value).map(|color| { match color { @@ -206,52 +325,6 @@ impl Color { _ => true, } } - - /// Parse a value. - pub fn parse_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { - // Currently we only store authored value for color keywords, - // because all browsers serialize those values as keywords for - // specified value. - let start = input.state(); - let authored = match input.next() { - Ok(&Token::Ident(ref s)) => Some(s.clone()), - _ => None, - }; - input.reset(&start); - match input.try(CSSParserColor::parse) { - Ok(value) => - Ok(match value { - CSSParserColor::CurrentColor => Color::CurrentColor, - CSSParserColor::RGBA(rgba) => { - Color::Numeric { - parsed: rgba, - authored: authored.map(|s| s.to_lowercase().into_boxed_str()), - } - } - }), - Err(e) => { - #[cfg(feature = "gecko")] - { - if let Ok(system) = input.try(SystemColor::parse) { - return Ok(Color::System(system)); - } - - if let Ok(c) = gecko::SpecialColorKeyword::parse(input) { - return Ok(Color::Special(c)); - } - } - - match e { - BasicParseError { kind: BasicParseErrorKind::UnexpectedToken(t), location } => { - Err(location.new_custom_error( - StyleParseErrorKind::ValueError(ValueParseErrorKind::InvalidColor(t)) - )) - } - e => Err(e.into()) - } - } - } - } } #[cfg(feature = "gecko")] diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index a4c12642623..8ccd2895f22 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -4563,8 +4563,17 @@ fn parse_color( ) -> Result { let mut input = ParserInput::new(value); let mut parser = Parser::new(&mut input); + let url_data = unsafe { dummy_url_data() }; + let context = ParserContext::new( + Origin::Author, + url_data, + Some(CssRuleType::Style), + ParsingMode::DEFAULT, + QuirksMode::NoQuirks, + ); + let start_position = parser.position(); - parser.parse_entirely(specified::Color::parse_color).map_err(|err| { + parser.parse_entirely(|i| specified::Color::parse(&context, i)).map_err(|err| { if let Some(error_reporter) = error_reporter { match err.kind { ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) => { diff --git a/tests/wpt/metadata/MANIFEST.json b/tests/wpt/metadata/MANIFEST.json index 68306f7ae97..e58fa12d536 100644 --- a/tests/wpt/metadata/MANIFEST.json +++ b/tests/wpt/metadata/MANIFEST.json @@ -303223,6 +303223,12 @@ {} ] ], + "css/css-values/calc-in-color-001.html": [ + [ + "/css/css-values/calc-in-color-001.html", + {} + ] + ], "css/css-values/calc-serialization.html": [ [ "/css/css-values/calc-serialization.html", @@ -503720,6 +503726,10 @@ "be08a1510714e8b4fbc4d35582db5708924d06b2", "reftest" ], + "css/css-values/calc-in-color-001.html": [ + "32a0face898f08b854ac392c75368282d718be32", + "testharness" + ], "css/css-values/calc-in-media-queries-001.html": [ "f8fdd8373eaca7a03d6a089b4faf71825c8bfaf2", "reftest" diff --git a/tests/wpt/web-platform-tests/css/css-values/calc-in-color-001.html b/tests/wpt/web-platform-tests/css/css-values/calc-in-color-001.html new file mode 100644 index 00000000000..e87719b4fe7 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-values/calc-in-color-001.html @@ -0,0 +1,25 @@ + + +CSS Test: calc() function in <color> + + + + +
+