From 3b03cd5b31a27c6d28b534241992356ce4909027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Wed, 21 Sep 2016 01:53:31 +0300 Subject: [PATCH 1/3] Implement parsing for mask shorthand --- .../style/properties/longhand/svg.mako.rs | 14 +- .../style/properties/properties.mako.rs | 1 + .../style/properties/shorthand/mask.mako.rs | 195 ++++++++++++++++++ 3 files changed, 202 insertions(+), 8 deletions(-) create mode 100644 components/style/properties/shorthand/mask.mako.rs diff --git a/components/style/properties/longhand/svg.mako.rs b/components/style/properties/longhand/svg.mako.rs index 2d0321ad7e0..cc9aadf4e02 100644 --- a/components/style/properties/longhand/svg.mako.rs +++ b/components/style/properties/longhand/svg.mako.rs @@ -94,10 +94,9 @@ ${helpers.single_keyword("mask-repeat", <%helpers:longhand name="mask-position" products="gecko" animatable="True"> use properties::longhands::background_position; - pub mod computed_value { - pub type T = ::properties::longhands::background_position::computed_value::T; - } - pub type SpecifiedValue = background_position::SpecifiedValue; + pub use ::properties::longhands::background_position::SpecifiedValue; + pub use ::properties::longhands::background_position::single_value as single_value; + pub use ::properties::longhands::background_position::computed_value as computed_value; #[inline] pub fn get_initial_value() -> computed_value::T { @@ -127,10 +126,9 @@ ${helpers.single_keyword("mask-origin", <%helpers:longhand name="mask-size" products="gecko" animatable="True"> use properties::longhands::background_size; - pub mod computed_value { - pub type T = ::properties::longhands::background_size::computed_value::T; - } - pub type SpecifiedValue = background_size::SpecifiedValue; + pub use ::properties::longhands::background_size::SpecifiedValue; + pub use ::properties::longhands::background_size::single_value as single_value; + pub use ::properties::longhands::background_size::computed_value as computed_value; #[inline] pub fn get_initial_value() -> computed_value::T { diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index e891b051ee3..e55d3d1c06d 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -130,6 +130,7 @@ pub mod shorthands { <%include file="/shorthand/inherited_text.mako.rs" /> <%include file="/shorthand/list.mako.rs" /> <%include file="/shorthand/margin.mako.rs" /> + <%include file="/shorthand/mask.mako.rs" /> <%include file="/shorthand/outline.mako.rs" /> <%include file="/shorthand/padding.mako.rs" /> <%include file="/shorthand/position.mako.rs" /> diff --git a/components/style/properties/shorthand/mask.mako.rs b/components/style/properties/shorthand/mask.mako.rs new file mode 100644 index 00000000000..448dc7ac831 --- /dev/null +++ b/components/style/properties/shorthand/mask.mako.rs @@ -0,0 +1,195 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<%helpers:shorthand name="mask" products="gecko" + sub_properties="mask-mode mask-repeat mask-clip mask-origin mask-composite mask-position + mask-size mask-image"> + use properties::longhands::{mask_mode, mask_repeat, mask_clip, mask_origin, mask_composite, mask_position}; + use properties::longhands::{mask_size, mask_image}; + + pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result { + % for name in "image mode position size repeat origin clip composite".split(): + let mut mask_${name} = mask_${name}::SpecifiedValue(Vec::new()); + % endfor + + try!(input.parse_comma_separated(|input| { + % for name in "image mode position size repeat origin clip composite".split(): + let mut ${name} = None; + % endfor + loop { + if image.is_none() { + if let Ok(value) = input.try(|input| mask_image::single_value + ::parse(context, input)) { + image = Some(value); + + // Parse mask mode, if applicable. + mode = input.try(|input| mask_mode::single_value::parse(context, input)).ok(); + + continue + } + } + if position.is_none() { + if let Ok(value) = input.try(|input| mask_position::single_value + ::parse(context, input)) { + position = Some(value); + + // Parse mask size, if applicable. + size = input.try(|input| { + try!(input.expect_delim('/')); + mask_size::single_value::parse(context, input) + }).ok(); + + continue + } + } + % for name in "repeat origin clip composite".split(): + if ${name}.is_none() { + if let Ok(value) = input.try(|input| mask_${name}::single_value + ::parse(context, input)) { + ${name} = Some(value); + continue + } + } + % endfor + break + } + let mut any = false; + % for name in "image mode position size repeat origin clip composite".split(): + any = any || ${name}.is_some(); + % endfor + if any { + % for name in "image mode position size repeat origin clip composite".split(): + if let Some(m_${name}) = ${name} { + mask_${name}.0.push(m_${name}); + } else { + mask_${name}.0.push(mask_${name}::single_value + ::get_initial_specified_value()); + } + % endfor + Ok(()) + } else { + Err(()) + } + })); + + Ok(Longhands { + % for name in "image mode position size repeat origin clip composite".split(): + mask_${name}: Some(mask_${name}), + % endfor + }) + } + + impl<'a> LonghandsToSerialize<'a> { + fn to_css_declared(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + // mako doesn't like ampersands following `<` + fn extract_value(x: &DeclaredValue) -> Option< &T> { + match *x { + DeclaredValue::Value(ref val) => Some(val), + _ => None, + } + } + use std::cmp; + let mut len = 0; + % for name in "image mode position size repeat origin clip composite".split(): + len = cmp::max(len, extract_value(self.mask_${name}).map(|i| i.0.len()) + .unwrap_or(0)); + % endfor + + // There should be at least one declared value + if len == 0 { + return dest.write_str("") + } + + for i in 0..len { + % for name in "image mode position size repeat origin clip composite".split(): + let ${name} = if let DeclaredValue::Value(ref arr) = *self.mask_${name} { + arr.0.get(i % arr.0.len()) + } else { + None + }; + % endfor + + if let Some(image) = image { + try!(image.to_css(dest)); + } else { + try!(write!(dest, "none")); + } + + try!(write!(dest, " ")); + + if let Some(mode) = mode { + try!(mode.to_css(dest)); + } else { + try!(write!(dest, "match-source")); + } + + try!(write!(dest, " ")); + + try!(position.unwrap_or(&mask_position::single_value + ::get_initial_specified_value()) + .to_css(dest)); + + if let Some(size) = size { + try!(write!(dest, " / ")); + try!(size.to_css(dest)); + } + + try!(write!(dest, " ")); + + if let Some(repeat) = repeat { + try!(repeat.to_css(dest)); + } else { + try!(write!(dest, "repeat")); + } + + match (origin, clip) { + (Some(origin), Some(clip)) => { + use properties::longhands::mask_origin::single_value::computed_value::T as Origin; + use properties::longhands::mask_clip::single_value::computed_value::T as Clip; + + try!(write!(dest, " ")); + + match (origin, clip) { + (&Origin::padding_box, &Clip::padding_box) => { + try!(origin.to_css(dest)); + }, + (&Origin::border_box, &Clip::border_box) => { + try!(origin.to_css(dest)); + }, + (&Origin::content_box, &Clip::content_box) => { + try!(origin.to_css(dest)); + }, + _ => { + try!(origin.to_css(dest)); + try!(write!(dest, " ")); + try!(clip.to_css(dest)); + } + } + }, + (Some(origin), _) => { + try!(write!(dest, " ")); + try!(origin.to_css(dest)); + }, + (_, Some(clip)) => { + try!(write!(dest, " ")); + try!(clip.to_css(dest)); + }, + _ => {} + }; + + try!(write!(dest, " ")); + + if let Some(composite) = composite { + try!(composite.to_css(dest)); + } else { + try!(write!(dest, "add")); + } + } + + Ok(()) + } + } + From 564b00314607d4e03977f6ff274264e61febe71a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Mon, 26 Sep 2016 21:56:47 +0300 Subject: [PATCH 2/3] Add test for mask shorthand --- tests/unit/style/properties/serialization.rs | 138 +++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/tests/unit/style/properties/serialization.rs b/tests/unit/style/properties/serialization.rs index add62581f61..6df1a65278b 100644 --- a/tests/unit/style/properties/serialization.rs +++ b/tests/unit/style/properties/serialization.rs @@ -865,4 +865,142 @@ mod shorthand_serialization { assert_eq!(serialization, "background: rgb(255, 0, 0) none repeat-x scroll 0px 0px;"); } } + + mod mask { + use style::properties::longhands::mask_clip as clip; + use style::properties::longhands::mask_composite as composite; + use style::properties::longhands::mask_image as image; + use style::properties::longhands::mask_mode as mode; + use style::properties::longhands::mask_origin as origin; + use style::properties::longhands::mask_position as position; + use style::properties::longhands::mask_repeat as repeat; + use style::properties::longhands::mask_size as size; + use style::values::specified::Image; + use style::values::specified::position::Position; + use super::*; + + macro_rules! single_vec_value_typedef { + ($name:ident, $path:expr) => { + DeclaredValue::Value($name::SpecifiedValue( + vec![$path] + )) + }; + } + macro_rules! single_vec_keyword_value { + ($name:ident, $kw:ident) => { + DeclaredValue::Value($name::SpecifiedValue( + vec![$name::single_value::SpecifiedValue::$kw] + )) + }; + } + macro_rules! single_vec_variant_value { + ($name:ident, $variant:expr) => { + DeclaredValue::Value($name::SpecifiedValue( + vec![$variant] + )) + }; + } + + #[test] + fn mask_should_serialize_all_available_properties_when_specified() { + let mut properties = Vec::new(); + + let image = single_vec_value_typedef!(image, + image::single_value::SpecifiedValue::Image( + Image::Url(Url::parse("http://servo/test.png").unwrap(), + UrlExtraData {}))); + + let mode = single_vec_keyword_value!(mode, luminance); + + let position = single_vec_value_typedef!(position, + Position { + horiz_keyword: None, + horiz_position: Some(LengthOrPercentage::Length(Length::from_px(7f32))), + vert_keyword: None, + vert_position: Some(LengthOrPercentage::Length(Length::from_px(4f32))) + } + ); + + let size = single_vec_variant_value!(size, + size::single_value::SpecifiedValue::Explicit( + size::single_value::ExplicitSize { + width: LengthOrPercentageOrAuto::Length(Length::from_px(70f32)), + height: LengthOrPercentageOrAuto::Length(Length::from_px(50f32)) + } + ) + ); + + let repeat = single_vec_keyword_value!(repeat, repeat_x); + let origin = single_vec_keyword_value!(origin, padding_box); + let clip = single_vec_keyword_value!(clip, border_box); + let composite = single_vec_keyword_value!(composite, subtract); + + properties.push(PropertyDeclaration::MaskImage(image)); + properties.push(PropertyDeclaration::MaskMode(mode)); + properties.push(PropertyDeclaration::MaskPosition(position)); + properties.push(PropertyDeclaration::MaskSize(size)); + properties.push(PropertyDeclaration::MaskRepeat(repeat)); + properties.push(PropertyDeclaration::MaskOrigin(origin)); + properties.push(PropertyDeclaration::MaskClip(clip)); + properties.push(PropertyDeclaration::MaskComposite(composite)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!( + serialization, + "mask: url(\"http://servo/test.png\") luminance 7px 4px / 70px 50px \ + repeat-x padding-box border-box subtract;" + ); + } + + #[test] + fn mask_should_combine_origin_and_clip_properties_when_equal() { + let mut properties = Vec::new(); + + let image = single_vec_value_typedef!(image, + image::single_value::SpecifiedValue::Image( + Image::Url(Url::parse("http://servo/test.png").unwrap(), + UrlExtraData {}))); + + let mode = single_vec_keyword_value!(mode, luminance); + + let position = single_vec_value_typedef!(position, + Position { + horiz_keyword: None, + horiz_position: Some(LengthOrPercentage::Length(Length::from_px(7f32))), + vert_keyword: None, + vert_position: Some(LengthOrPercentage::Length(Length::from_px(4f32))) + } + ); + + let size = single_vec_variant_value!(size, + size::single_value::SpecifiedValue::Explicit( + size::single_value::ExplicitSize { + width: LengthOrPercentageOrAuto::Length(Length::from_px(70f32)), + height: LengthOrPercentageOrAuto::Length(Length::from_px(50f32)) + } + ) + ); + + let repeat = single_vec_keyword_value!(repeat, repeat_x); + let origin = single_vec_keyword_value!(origin, padding_box); + let clip = single_vec_keyword_value!(clip, padding_box); + let composite = single_vec_keyword_value!(composite, subtract); + + properties.push(PropertyDeclaration::MaskImage(image)); + properties.push(PropertyDeclaration::MaskMode(mode)); + properties.push(PropertyDeclaration::MaskPosition(position)); + properties.push(PropertyDeclaration::MaskSize(size)); + properties.push(PropertyDeclaration::MaskRepeat(repeat)); + properties.push(PropertyDeclaration::MaskOrigin(origin)); + properties.push(PropertyDeclaration::MaskClip(clip)); + properties.push(PropertyDeclaration::MaskComposite(composite)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!( + serialization, + "mask: url(\"http://servo/test.png\") luminance 7px 4px / 70px 50px \ + repeat-x padding-box subtract;" + ); + } + } } From 17d99f6d5e2b37e8b649d0dee2fb42d7b3d119bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Tue, 27 Sep 2016 13:15:55 +0300 Subject: [PATCH 3/3] Add parsing unit tests for mask shorthand --- tests/unit/style/parsing/mask.rs | 122 +++++++++++++++++++++++++++++++ tests/unit/style/parsing/mod.rs | 1 + 2 files changed, 123 insertions(+) create mode 100644 tests/unit/style/parsing/mask.rs diff --git a/tests/unit/style/parsing/mask.rs b/tests/unit/style/parsing/mask.rs new file mode 100644 index 00000000000..a6ee3230248 --- /dev/null +++ b/tests/unit/style/parsing/mask.rs @@ -0,0 +1,122 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use cssparser::Parser; +use media_queries::CSSErrorReporterTest; +use style::parser::ParserContext; +use style::properties::longhands::{mask_clip, mask_composite, mask_image, mask_mode}; +use style::properties::longhands::{mask_origin, mask_position, mask_repeat, mask_size}; +use style::properties::shorthands::mask; +use style::stylesheets::Origin; +use url::Url; + +macro_rules! parse_longhand { + ($name:ident, $s:expr) => {{ + let url = Url::parse("http://localhost").unwrap(); + let context = ParserContext::new(Origin::Author, &url, Box::new(CSSErrorReporterTest)); + $name::parse(&context, &mut Parser::new($s)).unwrap() + }}; +} + +#[test] +fn mask_shorthand_should_parse_all_available_properties_when_specified() { + let url = Url::parse("http://localhost").unwrap(); + let context = ParserContext::new(Origin::Author, &url, Box::new(CSSErrorReporterTest)); + let mut parser = Parser::new("url(\"http://servo/test.png\") luminance 7px 4px / 70px 50px \ + repeat-x padding-box border-box subtract"); + let result = mask::parse_value(&context, &mut parser).unwrap(); + + assert_eq!(result.mask_image.unwrap(), parse_longhand!(mask_image, "url(\"http://servo/test.png\")")); + assert_eq!(result.mask_mode.unwrap(), parse_longhand!(mask_mode, "luminance")); + assert_eq!(result.mask_position.unwrap(), parse_longhand!(mask_position, "7px 4px")); + assert_eq!(result.mask_size.unwrap(), parse_longhand!(mask_size, "70px 50px")); + assert_eq!(result.mask_repeat.unwrap(), parse_longhand!(mask_repeat, "repeat-x")); + assert_eq!(result.mask_origin.unwrap(), parse_longhand!(mask_origin, "padding-box")); + assert_eq!(result.mask_clip.unwrap(), parse_longhand!(mask_clip, "border-box")); + assert_eq!(result.mask_composite.unwrap(), parse_longhand!(mask_composite, "subtract")); +} + +#[test] +fn mask_shorthand_should_parse_when_some_fields_set() { + let url = Url::parse("http://localhost").unwrap(); + let context = ParserContext::new(Origin::Author, &url, Box::new(CSSErrorReporterTest)); + let mut parser = Parser::new("14px 40px repeat-y"); + let result = mask::parse_value(&context, &mut parser).unwrap(); + + assert_eq!(result.mask_position.unwrap(), parse_longhand!(mask_position, "14px 40px")); + assert_eq!(result.mask_repeat.unwrap(), parse_longhand!(mask_repeat, "repeat-y")); + + let mut parser = Parser::new("url(\"http://servo/test.png\") repeat add"); + let result = mask::parse_value(&context, &mut parser).unwrap(); + + assert_eq!(result.mask_image.unwrap(), parse_longhand!(mask_image, "url(\"http://servo/test.png\")")); + assert_eq!(result.mask_repeat.unwrap(), parse_longhand!(mask_repeat, "repeat")); + assert_eq!(result.mask_composite.unwrap(), parse_longhand!(mask_composite, "add")); + + let mut parser = Parser::new("intersect"); + let result = mask::parse_value(&context, &mut parser).unwrap(); + + assert_eq!(result.mask_composite.unwrap(), parse_longhand!(mask_composite, "intersect")); + + let mut parser = Parser::new("url(\"http://servo/test.png\")"); + let result = mask::parse_value(&context, &mut parser).unwrap(); + + assert_eq!(result.mask_image.unwrap(), parse_longhand!(mask_image, "url(\"http://servo/test.png\")")); +} + +#[test] +fn mask_shorthand_should_parse_position_and_size_correctly() { + let url = Url::parse("http://localhost").unwrap(); + let context = ParserContext::new(Origin::Author, &url, Box::new(CSSErrorReporterTest)); + let mut parser = Parser::new("7px 4px"); + let result = mask::parse_value(&context, &mut parser).unwrap(); + + assert_eq!(result.mask_position.unwrap(), parse_longhand!(mask_position, "7px 4px")); + + let mut parser = Parser::new("7px 4px / 30px 20px"); + let result = mask::parse_value(&context, &mut parser).unwrap(); + + assert_eq!(result.mask_position.unwrap(), parse_longhand!(mask_position, "7px 4px")); + assert_eq!(result.mask_size.unwrap(), parse_longhand!(mask_size, "30px 20px")); + + let mut parser = Parser::new("/ 30px 20px"); + assert!(mask::parse_value(&context, &mut parser).is_err()); + + let mut parser = Parser::new("match-source repeat-x / 30px 20px"); + assert!(mask::parse_value(&context, &mut parser).is_err()); +} + +#[test] +fn mask_shorthand_should_parse_origin_and_clip_correctly() { + let url = Url::parse("http://localhost").unwrap(); + let context = ParserContext::new(Origin::Author, &url, Box::new(CSSErrorReporterTest)); + let mut parser = Parser::new("padding-box content-box"); + let result = mask::parse_value(&context, &mut parser).unwrap(); + + assert_eq!(result.mask_origin.unwrap(), parse_longhand!(mask_origin, "padding-box")); + assert_eq!(result.mask_clip.unwrap(), parse_longhand!(mask_clip, "content-box")); + + let mut parser = Parser::new("padding-box padding-box"); + let result = mask::parse_value(&context, &mut parser).unwrap(); + + assert_eq!(result.mask_origin.unwrap(), parse_longhand!(mask_origin, "padding-box")); + assert_eq!(result.mask_clip.unwrap(), parse_longhand!(mask_clip, "padding-box")); + + let mut parser = Parser::new("padding-box"); + let result = mask::parse_value(&context, &mut parser).unwrap(); + + // TODO(#13466): We should fix origin/clip parsing behavior. + assert_eq!(result.mask_origin.unwrap(), parse_longhand!(mask_origin, "padding-box")); +} + +#[test] +fn mask_shorthand_should_not_parse_when_mode_specified_but_image_not() { + let url = Url::parse("http://localhost").unwrap(); + let context = ParserContext::new(Origin::Author, &url, Box::new(CSSErrorReporterTest)); + let mut parser = Parser::new("luminance 7px 4px repeat-x padding"); + assert!(mask::parse_value(&context, &mut parser).is_err()); + + let mut parser = Parser::new("alpha"); + assert!(mask::parse_value(&context, &mut parser).is_err()); +} diff --git a/tests/unit/style/parsing/mod.rs b/tests/unit/style/parsing/mod.rs index d0989d01582..85429ef0ffa 100644 --- a/tests/unit/style/parsing/mod.rs +++ b/tests/unit/style/parsing/mod.rs @@ -33,5 +33,6 @@ macro_rules! assert_roundtrip { mod basic_shape; +mod mask; mod position; mod selectors;