servo: Merge #16511 - Implement -webkit-radial-gradient() (fixes #15441) (from nox:webkit-gradients); r=emilio
authorAnthony Ramine <n.oxyde@gmail.com>
Thu, 20 Apr 2017 04:27:33 -0500
changeset 354048 25944baae7b0b1c5f2bcaa3a4dda0287c6fa778c
parent 354047 e8babe547652de8cfb5aa274487151f848fab093
child 354049 66be5ce5eb0661ba4782c9de162cdf5cd45b54c6
push id31685
push userkwierso@gmail.com
push dateThu, 20 Apr 2017 21:45:29 +0000
treeherdermozilla-central@5e3dc7e1288a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #16511 - Implement -webkit-radial-gradient() (fixes #15441) (from nox:webkit-gradients); r=emilio Source-Repo: https://github.com/servo/servo Source-Revision: 7f825d2119a480a64b103e1d60a2d469af98d3de
servo/components/style/gecko/conversions.rs
servo/components/style/values/specified/image.rs
--- a/servo/components/style/gecko/conversions.rs
+++ b/servo/components/style/gecko/conversions.rs
@@ -202,40 +202,40 @@ impl nsStyleImage {
                             (*gecko_gradient).mBgPosY
                                              .set_value(CoordDataValue::Percent(percent_y));
                         }
                     }
                 }
                 gecko_gradient
             },
             GradientKind::Radial(shape, position) => {
+                let keyword_to_gecko_size = |keyword| {
+                    match keyword {
+                        SizeKeyword::ClosestSide => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE,
+                        SizeKeyword::FarthestSide => NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE,
+                        SizeKeyword::ClosestCorner => NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER,
+                        SizeKeyword::FarthestCorner => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER,
+                        SizeKeyword::Contain => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE,
+                        SizeKeyword::Cover => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER,
+                    }
+                };
                 let (gecko_shape, gecko_size) = match shape {
                     GradientShape::Circle(ref length) => {
                         let size = match *length {
                             LengthOrKeyword::Keyword(keyword) => {
-                                match keyword {
-                                    SizeKeyword::ClosestSide => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE,
-                                    SizeKeyword::FarthestSide => NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE,
-                                    SizeKeyword::ClosestCorner => NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER,
-                                    SizeKeyword::FarthestCorner => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER,
-                                }
+                                keyword_to_gecko_size(keyword)
                             },
                             _ => NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE,
                         };
                         (NS_STYLE_GRADIENT_SHAPE_CIRCULAR as u8, size as u8)
                     },
                     GradientShape::Ellipse(ref length) => {
                         let size = match *length {
                             LengthOrPercentageOrKeyword::Keyword(keyword) => {
-                                match keyword {
-                                    SizeKeyword::ClosestSide => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE,
-                                    SizeKeyword::FarthestSide => NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE,
-                                    SizeKeyword::ClosestCorner => NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER,
-                                    SizeKeyword::FarthestCorner => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER,
-                                }
+                                keyword_to_gecko_size(keyword)
                             },
                             _ => NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE,
                         };
                         (NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL as u8, size as u8)
                     }
                 };
 
                 let gecko_gradient = unsafe {
--- a/servo/components/style/values/specified/image.rs
+++ b/servo/components/style/values/specified/image.rs
@@ -116,19 +116,25 @@ impl ToCss for Gradient {
                 try!(dest.write_str("linear-gradient("));
                 try!(angle_or_corner.to_css(dest, self.compat_mode));
                 if angle_or_corner == AngleOrCorner::None {
                     skipcomma = true;
                 }
             },
             GradientKind::Radial(ref shape, ref position) => {
                 try!(dest.write_str("radial-gradient("));
-                try!(shape.to_css(dest));
-                try!(dest.write_str(" at "));
-                try!(position.to_css(dest));
+                if self.compat_mode == CompatMode::Modern {
+                    try!(shape.to_css(dest));
+                    try!(dest.write_str(" at "));
+                    try!(position.to_css(dest));
+                } else {
+                    try!(position.to_css(dest));
+                    try!(dest.write_str(", "));
+                    try!(shape.to_css(dest));
+                }
             },
         }
         for stop in &self.stops {
             if !skipcomma {
                 try!(dest.write_str(", "));
             } else {
                 skipcomma = false;
             }
@@ -143,19 +149,26 @@ impl Gradient {
     pub fn parse_function(context: &ParserContext, input: &mut Parser) -> Result<Gradient, ()> {
         let parse_linear_gradient = |input: &mut Parser, mode| {
             input.parse_nested_block(|input| {
                 let kind = try!(GradientKind::parse_linear(context, input, mode));
                 let stops = try!(input.parse_comma_separated(|i| ColorStop::parse(context, i)));
                 Ok((kind, stops))
             })
         };
-        let parse_radial_gradient = |input: &mut Parser| {
+        let parse_modern_radial_gradient = |input: &mut Parser| {
             input.parse_nested_block(|input| {
-                let kind = try!(GradientKind::parse_radial(context, input));
+                let kind = try!(GradientKind::parse_modern_radial(context, input));
+                let stops = try!(input.parse_comma_separated(|i| ColorStop::parse(context, i)));
+                Ok((kind, stops))
+            })
+        };
+        let parse_webkit_radial_gradient = |input: &mut Parser| {
+            input.parse_nested_block(|input| {
+                let kind = try!(GradientKind::parse_webkit_radial(context, input));
                 let stops = try!(input.parse_comma_separated(|i| ColorStop::parse(context, i)));
                 Ok((kind, stops))
             })
         };
         let mut repeating = false;
         let mut compat_mode = CompatMode::Modern;
         let (gradient_kind, stops) = match_ignore_ascii_case! { &try!(input.expect_function()),
             "linear-gradient" => {
@@ -170,21 +183,30 @@ impl Gradient {
                 try!(parse_linear_gradient(input, compat_mode))
             },
             "-webkit-repeating-linear-gradient" => {
                 repeating = true;
                 compat_mode = CompatMode::WebKit;
                 try!(parse_linear_gradient(input, compat_mode))
             },
             "radial-gradient" => {
-                try!(parse_radial_gradient(input))
+                try!(parse_modern_radial_gradient(input))
+            },
+            "-webkit-radial-gradient" => {
+                compat_mode = CompatMode::WebKit;
+                try!(parse_webkit_radial_gradient(input))
             },
             "repeating-radial-gradient" => {
                 repeating = true;
-                try!(parse_radial_gradient(input))
+                try!(parse_modern_radial_gradient(input))
+            },
+            "-webkit-repeating-radial-gradient" => {
+                repeating = true;
+                compat_mode = CompatMode::WebKit;
+                try!(parse_webkit_radial_gradient(input))
             },
             _ => { return Err(()); }
         };
 
         // https://drafts.csswg.org/css-images/#typedef-color-stop-list
         if stops.len() < 2 {
             return Err(())
         }
@@ -226,71 +248,59 @@ pub enum CompatMode {
 
 impl GradientKind {
     /// Parses a linear gradient kind from the given arguments.
     fn parse_linear(context: &ParserContext, input: &mut Parser, mode: CompatMode) -> Result<GradientKind, ()> {
         let angle_or_corner = try!(AngleOrCorner::parse(context, input, mode));
         Ok(GradientKind::Linear(angle_or_corner))
     }
 
-    /// Parses a radial gradient from the given arguments.
-    pub fn parse_radial(context: &ParserContext, input: &mut Parser) -> Result<GradientKind, ()> {
+    /// Parses a modern radial gradient from the given arguments.
+    pub fn parse_modern_radial(context: &ParserContext, input: &mut Parser) -> Result<GradientKind, ()> {
         let mut needs_comma = true;
 
-        // Ending shape and position can be in various order. Checks all probabilities.
         let (shape, position) = if let Ok(position) = input.try(|i| parse_position(context, i)) {
-            // Handle just <position>
+            // Handle just "at" <position>
             (EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::FarthestCorner)), position)
-        } else if let Ok((first, second)) = input.try(|i| parse_two_length(context, i)) {
-            // Handle <LengthOrPercentage> <LengthOrPercentage> <shape>? <position>?
-            let _ = input.try(|input| input.expect_ident_matching("ellipse"));
-            (EndingShape::Ellipse(LengthOrPercentageOrKeyword::LengthOrPercentage(first, second)),
-             input.try(|i| parse_position(context, i)).unwrap_or(Position::center()))
-        } else if let Ok(length) = input.try(|i| Length::parse(context, i)) {
-            // Handle <Length> <circle>? <position>?
-            let _ = input.try(|input| input.expect_ident_matching("circle"));
-            (EndingShape::Circle(LengthOrKeyword::Length(length)),
-             input.try(|i| parse_position(context, i)).unwrap_or(Position::center()))
-        } else if let Ok(keyword) = input.try(SizeKeyword::parse) {
-            // Handle <keyword> <shape-keyword>? <position>?
-            let shape = if input.try(|input| input.expect_ident_matching("circle")).is_ok() {
-                EndingShape::Circle(LengthOrKeyword::Keyword(keyword))
-            } else {
-                let _ = input.try(|input| input.expect_ident_matching("ellipse"));
-                EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(keyword))
-            };
+        } else if let Ok(shape) = input.try(|i| parse_shape(context, i, SizeKeyword::parse_modern)) {
+            // Handle <shape> ["at" <position>]?
             (shape, input.try(|i| parse_position(context, i)).unwrap_or(Position::center()))
         } else {
-            // Handle <shape-keyword> <length>? <position>?
-            if input.try(|input| input.expect_ident_matching("ellipse")).is_ok() {
-                // Handle <ellipse> <LengthOrPercentageOrKeyword>? <position>?
-                let length = input.try(|i| LengthOrPercentageOrKeyword::parse(context, i))
-                                  .unwrap_or(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::FarthestCorner));
-                (EndingShape::Ellipse(length),
-                 input.try(|i| parse_position(context, i)).unwrap_or(Position::center()))
-            } else if input.try(|input| input.expect_ident_matching("circle")).is_ok() {
-                // Handle <ellipse> <LengthOrKeyword>? <position>?
-                let length = input.try(|i| LengthOrKeyword::parse(context, i))
-                                  .unwrap_or(LengthOrKeyword::Keyword(SizeKeyword::FarthestCorner));
-                (EndingShape::Circle(length), input.try(|i| parse_position(context, i))
-                                                   .unwrap_or(Position::center()))
-            } else {
-                // If there is no shape keyword, it should set to default.
-                needs_comma = false;
-                (EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::FarthestCorner)),
-                 input.try(|i| parse_position(context, i)).unwrap_or(Position::center()))
-            }
+            // If there is no shape keyword, it should set to default.
+            needs_comma = false;
+            (EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::FarthestCorner)),
+             Position::center())
         };
 
         if needs_comma {
             try!(input.expect_comma());
         }
 
         Ok(GradientKind::Radial(shape, position))
     }
+
+    /// Parses a webkit radial gradient from the given arguments.
+    /// https://compat.spec.whatwg.org/#css-gradients-webkit-radial-gradient
+    pub fn parse_webkit_radial(context: &ParserContext, input: &mut Parser) -> Result<GradientKind, ()> {
+        let position = if let Ok(position) = input.try(|i| Position::parse(context, i)) {
+            try!(input.expect_comma());
+            position
+        } else {
+            Position::center()
+        };
+
+        let shape = if let Ok(shape) = input.try(|i| parse_shape(context, i, SizeKeyword::parse)) {
+            try!(input.expect_comma());
+            shape
+        } else {
+            EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::Cover))
+        };
+
+        Ok(GradientKind::Radial(shape, position))
+    }
 }
 
 /// Specified values for `moz-image-rect`
 /// -moz-image-rect(<uri>, top, right, bottom, left);
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 pub struct ImageRect {
@@ -353,16 +363,56 @@ fn parse_two_length(context: &ParserCont
     Ok((first, second))
 }
 
 fn parse_position(context: &ParserContext, input: &mut Parser) -> Result<Position, ()> {
     try!(input.expect_ident_matching("at"));
     input.try(|i| Position::parse(context, i))
 }
 
+fn parse_shape<F>(context: &ParserContext,
+                  input: &mut Parser,
+                  parse_size_keyword: F)
+                  -> Result<EndingShape, ()>
+    where F: FnOnce(&mut Parser) -> Result<SizeKeyword, ()>
+{
+    if let Ok((first, second)) = input.try(|i| parse_two_length(context, i)) {
+        // Handle <LengthOrPercentage> <LengthOrPercentage> <shape>?
+        let _ = input.try(|input| input.expect_ident_matching("ellipse"));
+        Ok(EndingShape::Ellipse(LengthOrPercentageOrKeyword::LengthOrPercentage(first, second)))
+    } else if let Ok(length) = input.try(|i| Length::parse(context, i)) {
+        // Handle <Length> <circle>?
+        let _ = input.try(|input| input.expect_ident_matching("circle"));
+        Ok(EndingShape::Circle(LengthOrKeyword::Length(length)))
+    } else if let Ok(keyword) = input.try(parse_size_keyword) {
+        // Handle <keyword> <shape-keyword>?
+        if input.try(|input| input.expect_ident_matching("circle")).is_ok() {
+            Ok(EndingShape::Circle(LengthOrKeyword::Keyword(keyword)))
+        } else {
+            let _ = input.try(|input| input.expect_ident_matching("ellipse"));
+            Ok(EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(keyword)))
+        }
+    } else {
+        // https://github.com/rust-lang/rust/issues/41272
+        if input.try(|input| input.expect_ident_matching("ellipse")).is_ok() {
+            // Handle <ellipse> <LengthOrPercentageOrKeyword>?
+            let length = input.try(|i| LengthOrPercentageOrKeyword::parse(context, i))
+                                .unwrap_or(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::FarthestCorner));
+            Ok(EndingShape::Ellipse(length))
+        } else if input.try(|input| input.expect_ident_matching("circle")).is_ok() {
+            // Handle <circle> <LengthOrKeyword>?
+            let length = input.try(|i| LengthOrKeyword::parse(context, i))
+                                .unwrap_or(LengthOrKeyword::Keyword(SizeKeyword::FarthestCorner));
+            Ok(EndingShape::Circle(length))
+        } else {
+            Err(())
+        }
+    }
+}
+
 /// Specified values for an angle or a corner in a linear gradient.
 #[derive(Clone, PartialEq, Copy, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 pub enum AngleOrCorner {
     Angle(Angle),
     Corner(Option<HorizontalDirection>, Option<VerticalDirection>),
     None,
@@ -540,9 +590,19 @@ impl ToCss for LengthOrPercentageOrKeywo
             },
             LengthOrPercentageOrKeyword::Keyword(keyword) => keyword.to_css(dest),
         }
     }
 }
 
 /// https://drafts.csswg.org/css-images/#typedef-extent-keyword
 define_css_keyword_enum!(SizeKeyword: "closest-side" => ClosestSide, "farthest-side" => FarthestSide,
-                         "closest-corner" => ClosestCorner, "farthest-corner" => FarthestCorner);
+                         "closest-corner" => ClosestCorner, "farthest-corner" => FarthestCorner,
+                         "contain" => Contain, "cover" => Cover);
+
+impl SizeKeyword {
+    fn parse_modern(input: &mut Parser) -> Result<Self, ()> {
+        match try!(SizeKeyword::parse(input)) {
+            SizeKeyword::Contain | SizeKeyword::Cover => Err(()),
+            keyword => Ok(keyword),
+        }
+    }
+}