servo: Merge #14189 - Implement border-image shorthand (from canaltinova:border-image-shorthand); r=Manishearth
authorNazım Can Altınova <canaltinova@gmail.com>
Mon, 14 Nov 2016 11:17:54 -0600
changeset 340151 03f507fcd92a18729cf3fd02119851cfa9f8c3f2
parent 340150 e9172f714e6ac1173282adacb3b7633b1106ee6d
child 340152 2bd553ec64624838f952212386bc347b691779cd
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersManishearth
servo: Merge #14189 - Implement border-image shorthand (from canaltinova:border-image-shorthand); r=Manishearth <!-- Please describe your changes on the following line: --> Implementation of border-image shorthand. r? Manishearth --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors <!-- Either: --> - [X] There are tests for these changes <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: e9fa69bb2d2210563f774393b40913154ad43d22
servo/components/style/properties/longhand/border.mako.rs
servo/components/style/properties/shorthand/border.mako.rs
servo/tests/unit/style/parsing/border.rs
servo/tests/unit/style/parsing/mod.rs
--- a/servo/components/style/properties/longhand/border.mako.rs
+++ b/servo/components/style/properties/longhand/border.mako.rs
@@ -102,16 +102,21 @@
         }
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T(None)
     }
 
+    #[inline]
+    pub fn get_initial_specified_value() -> SpecifiedValue {
+        SpecifiedValue(None)
+    }
+
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
 
         #[inline]
         fn to_computed_value(&self, context: &Context) -> computed_value::T {
             match self.0 {
                 Some(ref image) => computed_value::T(Some(image.to_computed_value(context))),
                 None => computed_value::T(None),
@@ -136,17 +141,17 @@
     }
 </%helpers:longhand>
 
 // https://drafts.csswg.org/css-backgrounds-3/#border-image-outset
 <%helpers:longhand name="border-image-outset" products="gecko" animatable="False">
     use std::fmt;
     use style_traits::ToCss;
     use values::HasViewportPercentage;
-    use values::specified::LengthOrNumber;
+    use values::specified::{LengthOrNumber, Number};
 
     impl HasViewportPercentage for SpecifiedValue {
         fn has_viewport_percentage(&self) -> bool {
             let mut viewport_percentage = false;
             for value in self.0.iter() {
                 let vp = value.has_viewport_percentage();
                 viewport_percentage = vp || viewport_percentage;
             }
@@ -191,16 +196,21 @@
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T(computed::LengthOrNumber::Number(0.0),
                           computed::LengthOrNumber::Number(0.0),
                           computed::LengthOrNumber::Number(0.0),
                           computed::LengthOrNumber::Number(0.0))
     }
 
+    #[inline]
+    pub fn get_initial_specified_value() -> SpecifiedValue {
+        SpecifiedValue(vec![LengthOrNumber::Number(Number(0.0))])
+    }
+
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
 
         #[inline]
         fn to_computed_value(&self, context: &Context) -> computed_value::T {
             let length = self.0.len();
             match length {
                 4 => computed_value::T(self.0[0].to_computed_value(context),
@@ -296,16 +306,21 @@
         }
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T(RepeatKeyword::Stretch, RepeatKeyword::Stretch)
     }
 
+    #[inline]
+    pub fn get_initial_specified_value() -> SpecifiedValue {
+        SpecifiedValue(RepeatKeyword::Stretch, None)
+    }
+
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
 
         #[inline]
         fn to_computed_value(&self, _context: &Context) -> computed_value::T {
             computed_value::T(self.0, self.1.unwrap_or(self.0))
         }
         #[inline]
@@ -444,16 +459,21 @@
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T(computed_value::SingleComputedValue::Number(1.0),
                           computed_value::SingleComputedValue::Number(1.0),
                           computed_value::SingleComputedValue::Number(1.0),
                           computed_value::SingleComputedValue::Number(1.0))
     }
 
+    #[inline]
+    pub fn get_initial_specified_value() -> SpecifiedValue {
+        SpecifiedValue(vec![SingleSpecifiedValue::Number(Number(1.0))])
+    }
+
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
 
         #[inline]
         fn to_computed_value(&self, context: &Context) -> computed_value::T {
             let length = self.0.len();
             match length {
                 4 => computed_value::T(self.0[0].to_computed_value(context),
@@ -635,16 +655,24 @@
             corners: vec![computed_value::PercentageOrNumber::Percentage(Percentage(1.0)),
                           computed_value::PercentageOrNumber::Percentage(Percentage(1.0)),
                           computed_value::PercentageOrNumber::Percentage(Percentage(1.0)),
                           computed_value::PercentageOrNumber::Percentage(Percentage(1.0))],
             fill: false,
         }
     }
 
+    #[inline]
+    pub fn get_initial_specified_value() -> SpecifiedValue {
+        SpecifiedValue {
+            corners: vec![PercentageOrNumber::Percentage(Percentage(1.0))],
+            fill: false,
+        }
+    }
+
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
 
         #[inline]
         fn to_computed_value(&self, context: &Context) -> computed_value::T {
             let length = self.corners.len();
             let corners = match length {
                 4 => vec![self.corners[0].to_computed_value(context),
--- a/servo/components/style/properties/shorthand/border.mako.rs
+++ b/servo/components/style/properties/shorthand/border.mako.rs
@@ -176,8 +176,136 @@ pub fn parse_border(context: &ParserCont
 
             try!(self.border_bottom_right_radius.to_css(dest));
             try!(write!(dest, " "));
 
             self.border_bottom_left_radius.to_css(dest)
         }
     }
 </%helpers:shorthand>
+
+// https://drafts.csswg.org/css-backgrounds-3/#border-image
+<%helpers:shorthand name="border-image" products="gecko" sub_properties="border-image-outset
+    border-image-repeat border-image-slice border-image-source border-image-width">
+    use properties::longhands::{border_image_outset, border_image_repeat, border_image_slice};
+    use properties::longhands::{border_image_source, border_image_width};
+
+    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+        % for name in "outset repeat slice source width".split():
+            let mut border_image_${name} = border_image_${name}::get_initial_specified_value();
+        % endfor
+
+        try!(input.try(|input| {
+            % for name in "outset repeat slice source width".split():
+                let mut ${name} = None;
+            % endfor
+            loop {
+                if slice.is_none() {
+                    if let Ok(value) = input.try(|input| border_image_slice::parse(context, input)) {
+                        slice = Some(value);
+                        // Parse border image width and outset, if applicable.
+                        let maybe_width_outset: Result<_, ()> = input.try(|input| {
+                            try!(input.expect_delim('/'));
+
+                            // Parse border image width, if applicable.
+                            let w = input.try(|input|
+                                border_image_width::parse(context, input)).ok();
+
+                            // Parse border image outset if applicable.
+                            let o = input.try(|input| {
+                                try!(input.expect_delim('/'));
+                                border_image_outset::parse(context, input)
+                            }).ok();
+                            Ok((w, o))
+                        });
+                        if let Ok((w, o)) = maybe_width_outset {
+                            width = w;
+                            outset = o;
+                        }
+
+                        continue
+                    }
+                }
+                % for name in "source repeat".split():
+                    if ${name}.is_none() {
+                        if let Ok(value) = input.try(|input| border_image_${name}::parse(context, input)) {
+                            ${name} = Some(value);
+                            continue
+                        }
+                    }
+                % endfor
+                break
+            }
+            let mut any = false;
+            % for name in "outset repeat slice source width".split():
+                any = any || ${name}.is_some();
+            % endfor
+            if any {
+                % for name in "outset repeat slice source width".split():
+                    if let Some(b_${name}) = ${name} {
+                        border_image_${name} = b_${name};
+                    }
+                % endfor
+                Ok(())
+            } else {
+                Err(())
+            }
+        }));
+
+        Ok(Longhands {
+            % for name in "outset repeat slice source width".split():
+                border_image_${name}: Some(border_image_${name}),
+            % endfor
+         })
+    }
+
+    impl<'a> LonghandsToSerialize<'a>  {
+        fn to_css_declared<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+            % for name in "outset repeat slice source width".split():
+                let ${name} = if let DeclaredValue::Value(ref value) = *self.border_image_${name} {
+                    Some(value)
+                } else {
+                    None
+                };
+            % endfor
+
+            if let Some(source) = source {
+                try!(source.to_css(dest));
+            } else {
+                try!(write!(dest, "none"));
+            }
+
+            try!(write!(dest, " "));
+
+            if let Some(slice) = slice {
+                try!(slice.to_css(dest));
+            } else {
+                try!(write!(dest, "100%"));
+            }
+
+            try!(write!(dest, " / "));
+
+            if let Some(width) = width {
+                try!(width.to_css(dest));
+            } else {
+                try!(write!(dest, "1"));
+            }
+
+            try!(write!(dest, " / "));
+
+            if let Some(outset) = outset {
+                try!(outset.to_css(dest));
+            } else {
+                try!(write!(dest, "0"));
+            }
+
+            try!(write!(dest, " "));
+
+            if let Some(repeat) = repeat {
+                try!(repeat.to_css(dest));
+            } else {
+                try!(write!(dest, "stretch"));
+            }
+
+            Ok(())
+        }
+    }
+</%helpers:shorthand>
new file mode 100644
--- /dev/null
+++ b/servo/tests/unit/style/parsing/border.rs
@@ -0,0 +1,88 @@
+/* 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::{border_image_outset, border_image_repeat, border_image_slice};
+use style::properties::longhands::{border_image_source, border_image_width};
+use style::properties::shorthands::border_image;
+use style::stylesheets::Origin;
+use url::Url;
+
+#[test]
+fn border_image_shorhand_should_parse_when_all_properties_specified() {
+    let url = Url::parse("http://localhost").unwrap();
+    let context = ParserContext::new(Origin::Author, &url, Box::new(CSSErrorReporterTest));
+    let mut parser = Parser::new("linear-gradient(red, blue) 30 30% 45 fill / 20px 40px / 10px \
+                                 round stretch");
+    let result = border_image::parse_value(&context, &mut parser).unwrap();
+
+    assert_eq!(result.border_image_source.unwrap(),
+               parse_longhand!(border_image_source, "linear-gradient(red, blue)"));
+    assert_eq!(result.border_image_slice.unwrap(), parse_longhand!(border_image_slice, "30 30% 45 fill"));
+    assert_eq!(result.border_image_width.unwrap(), parse_longhand!(border_image_width, "20px 40px"));
+    assert_eq!(result.border_image_outset.unwrap(), parse_longhand!(border_image_outset, "10px"));
+    assert_eq!(result.border_image_repeat.unwrap(), parse_longhand!(border_image_repeat, "round stretch"));
+}
+
+#[test]
+fn border_image_shorhand_should_parse_without_width() {
+    let url = Url::parse("http://localhost").unwrap();
+    let context = ParserContext::new(Origin::Author, &url, Box::new(CSSErrorReporterTest));
+    let mut parser = Parser::new("linear-gradient(red, blue) 30 30% 45 fill / / 10px round stretch");
+    let result = border_image::parse_value(&context, &mut parser).unwrap();
+
+    assert_eq!(result.border_image_source.unwrap(),
+               parse_longhand!(border_image_source, "linear-gradient(red, blue)"));
+    assert_eq!(result.border_image_slice.unwrap(), parse_longhand!(border_image_slice, "30 30% 45 fill"));
+    assert_eq!(result.border_image_outset.unwrap(), parse_longhand!(border_image_outset, "10px"));
+    assert_eq!(result.border_image_repeat.unwrap(), parse_longhand!(border_image_repeat, "round stretch"));
+    assert_eq!(result.border_image_width.unwrap(), border_image_width::get_initial_specified_value());
+}
+
+#[test]
+fn border_image_shorhand_should_parse_without_outset() {
+    let url = Url::parse("http://localhost").unwrap();
+    let context = ParserContext::new(Origin::Author, &url, Box::new(CSSErrorReporterTest));
+    let mut parser = Parser::new("linear-gradient(red, blue) 30 30% 45 fill / 20px 40px round");
+    let result = border_image::parse_value(&context, &mut parser).unwrap();
+
+    assert_eq!(result.border_image_source.unwrap(),
+               parse_longhand!(border_image_source, "linear-gradient(red, blue)"));
+    assert_eq!(result.border_image_slice.unwrap(), parse_longhand!(border_image_slice, "30 30% 45 fill"));
+    assert_eq!(result.border_image_width.unwrap(), parse_longhand!(border_image_width, "20px 40px"));
+    assert_eq!(result.border_image_repeat.unwrap(), parse_longhand!(border_image_repeat, "round"));
+    assert_eq!(result.border_image_outset.unwrap(), border_image_outset::get_initial_specified_value());
+}
+
+#[test]
+fn border_image_shorhand_should_parse_without_width_or_outset() {
+    let url = Url::parse("http://localhost").unwrap();
+    let context = ParserContext::new(Origin::Author, &url, Box::new(CSSErrorReporterTest));
+    let mut parser = Parser::new("linear-gradient(red, blue) 30 30% 45 fill round");
+    let result = border_image::parse_value(&context, &mut parser).unwrap();
+
+    assert_eq!(result.border_image_source.unwrap(),
+               parse_longhand!(border_image_source, "linear-gradient(red, blue)"));
+    assert_eq!(result.border_image_slice.unwrap(), parse_longhand!(border_image_slice, "30 30% 45 fill"));
+    assert_eq!(result.border_image_repeat.unwrap(), parse_longhand!(border_image_repeat, "round"));
+    assert_eq!(result.border_image_width.unwrap(), border_image_width::get_initial_specified_value());
+    assert_eq!(result.border_image_outset.unwrap(), border_image_outset::get_initial_specified_value());
+}
+
+#[test]
+fn border_image_shorhand_should_parse_with_just_source() {
+    let url = Url::parse("http://localhost").unwrap();
+    let context = ParserContext::new(Origin::Author, &url, Box::new(CSSErrorReporterTest));
+    let mut parser = Parser::new("linear-gradient(red, blue)");
+    let result = border_image::parse_value(&context, &mut parser).unwrap();
+
+    assert_eq!(result.border_image_source.unwrap(),
+               parse_longhand!(border_image_source, "linear-gradient(red, blue)"));
+    assert_eq!(result.border_image_slice.unwrap(), border_image_slice::get_initial_specified_value());
+    assert_eq!(result.border_image_width.unwrap(), border_image_width::get_initial_specified_value());
+    assert_eq!(result.border_image_outset.unwrap(), border_image_outset::get_initial_specified_value());
+    assert_eq!(result.border_image_repeat.unwrap(), border_image_repeat::get_initial_specified_value());
+}
--- a/servo/tests/unit/style/parsing/mod.rs
+++ b/servo/tests/unit/style/parsing/mod.rs
@@ -57,14 +57,15 @@ 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()
     }};
 }
 
 mod basic_shape;
+mod border;
 mod font;
 mod image;
 mod inherited_text;
 mod mask;
 mod position;
 mod selectors;