Bug 1366544 - stylo: Implement -moz-prefixed radial gradients r?Manishearth draft
authorNazım Can Altınova <canaltinova@gmail.com>
Tue, 20 Jun 2017 17:27:43 -0700
changeset 598622 0d1a8c6d5cfeb4bc325b872393da0e1c7ce10cac
parent 597814 feb0dbb64d665070f4b015d9ad694c2ac04b6107
child 598623 f4a9ff67a5465c12ca9822c414d6f436e3ec4463
push id65257
push userbmo:canaltinova@gmail.com
push dateWed, 21 Jun 2017 22:40:27 +0000
reviewersManishearth
bugs1366544
milestone56.0a1
Bug 1366544 - stylo: Implement -moz-prefixed radial gradients r?Manishearth MozReview-Commit-ID: JdssRGsytxU
servo/components/style/gecko/conversions.rs
servo/components/style/values/computed/image.rs
servo/components/style/values/generics/image.rs
servo/components/style/values/specified/image.rs
--- a/servo/components/style/gecko/conversions.rs
+++ b/servo/components/style/gecko/conversions.rs
@@ -250,17 +250,17 @@ impl nsStyleImage {
                             } else {
                                 (*gecko_gradient).mAngle.set_value(CoordDataValue::None);
                             }
                         }
                     },
                 }
                 gecko_gradient
             },
-            GradientKind::Radial(shape, position) => {
+            GradientKind::Radial(shape, position, angle) => {
                 let keyword_to_gecko_size = |keyword| {
                     match keyword {
                         ShapeExtent::ClosestSide => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE,
                         ShapeExtent::FarthestSide => NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE,
                         ShapeExtent::ClosestCorner => NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER,
                         ShapeExtent::FarthestCorner => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER,
                         ShapeExtent::Contain => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE,
                         ShapeExtent::Cover => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER,
@@ -291,19 +291,24 @@ impl nsStyleImage {
                     Gecko_CreateGradient(gecko_shape,
                                          gecko_size,
                                          gradient.repeating,
                                          gradient.compat_mode != CompatMode::Modern,
                                          gradient.compat_mode == CompatMode::Moz,
                                          stop_count as u32)
                 };
 
-                // Clear mAngle and mBgPos fields
+                // Clear mBgPos field and set mAngle if angle is set. Otherwise clear it.
                 unsafe {
-                    (*gecko_gradient).mAngle.set_value(CoordDataValue::None);
+                    if let Some(angle) = angle {
+                        (*gecko_gradient).mAngle.set(angle);
+                    } else {
+                        (*gecko_gradient).mAngle.set_value(CoordDataValue::None);
+                    }
+
                     (*gecko_gradient).mBgPosX.set_value(CoordDataValue::None);
                     (*gecko_gradient).mBgPosY.set_value(CoordDataValue::None);
                 }
 
                 // Setting radius values depending shape
                 match shape {
                     EndingShape::Circle(Circle::Radius(length)) => {
                         unsafe {
--- a/servo/components/style/values/computed/image.rs
+++ b/servo/components/style/values/computed/image.rs
@@ -31,24 +31,26 @@ pub type Image = GenericImage<Gradient, 
 /// Computed values for a CSS gradient.
 /// https://drafts.csswg.org/css-images/#gradients
 pub type Gradient = GenericGradient<
     LineDirection,
     Length,
     LengthOrPercentage,
     Position,
     RGBA,
+    Angle,
 >;
 
 /// A computed gradient kind.
 pub type GradientKind = GenericGradientKind<
     LineDirection,
     Length,
     LengthOrPercentage,
     Position,
+    Angle,
 >;
 
 /// A computed gradient line direction.
 #[derive(Clone, Copy, Debug, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum LineDirection {
     /// An angle.
     Angle(Angle),
--- a/servo/components/style/values/generics/image.rs
+++ b/servo/components/style/values/generics/image.rs
@@ -32,19 +32,19 @@ pub enum Image<Gradient, ImageRect> {
     #[cfg(feature = "servo")]
     PaintWorklet(PaintWorklet),
 }
 
 /// A CSS gradient.
 /// https://drafts.csswg.org/css-images/#gradients
 #[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToComputedValue)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub struct Gradient<LineDirection, Length, LengthOrPercentage, Position, Color> {
+pub struct Gradient<LineDirection, Length, LengthOrPercentage, Position, Color, Angle> {
     /// Gradients can be linear or radial.
-    pub kind: GradientKind<LineDirection, Length, LengthOrPercentage, Position>,
+    pub kind: GradientKind<LineDirection, Length, LengthOrPercentage, Position, Angle>,
     /// The color stops and interpolation hints.
     pub items: Vec<GradientItem<Color, LengthOrPercentage>>,
     /// True if this is a repeating gradient.
     pub repeating: bool,
     /// Compatibility mode.
     pub compat_mode: CompatMode,
 }
 
@@ -58,21 +58,21 @@ pub enum CompatMode {
     WebKit,
     /// `-moz` prefix
     Moz,
 }
 
 /// A gradient kind.
 #[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub enum GradientKind<LineDirection, Length, LengthOrPercentage, Position> {
+pub enum GradientKind<LineDirection, Length, LengthOrPercentage, Position, Angle> {
     /// A linear gradient.
     Linear(LineDirection),
     /// A radial gradient.
-    Radial(EndingShape<Length, LengthOrPercentage>, Position),
+    Radial(EndingShape<Length, LengthOrPercentage>, Position, Option<Angle>),
 }
 
 /// A radial gradient's ending shape.
 #[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue, ToCss)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum EndingShape<Length, LengthOrPercentage> {
     /// A circular gradient.
     Circle(Circle<Length>),
@@ -209,18 +209,18 @@ impl<G, R> HasViewportPercentage for Ima
     fn has_viewport_percentage(&self) -> bool {
         match *self {
             Image::Gradient(ref gradient) => gradient.has_viewport_percentage(),
             _ => false,
         }
     }
 }
 
-impl<D, L, LoP, P, C> ToCss for Gradient<D, L, LoP, P, C>
-    where D: LineDirection, L: ToCss, LoP: ToCss, P: ToCss, C: ToCss,
+impl<D, L, LoP, P, C, A> ToCss for Gradient<D, L, LoP, P, C, A>
+    where D: LineDirection, L: ToCss, LoP: ToCss, P: ToCss, C: ToCss, A: ToCss
 {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         match self.compat_mode {
             CompatMode::WebKit => dest.write_str("-webkit-")?,
             CompatMode::Moz => dest.write_str("-moz-")?,
             _ => {},
         }
 
@@ -230,33 +230,37 @@ impl<D, L, LoP, P, C> ToCss for Gradient
         dest.write_str(self.kind.label())?;
         dest.write_str("-gradient(")?;
         let mut skip_comma = match self.kind {
             GradientKind::Linear(ref direction) if direction.points_downwards() => true,
             GradientKind::Linear(ref direction) => {
                 direction.to_css(dest, self.compat_mode)?;
                 false
             },
-            GradientKind::Radial(ref shape, ref position) => {
+            GradientKind::Radial(ref shape, ref position, ref angle) => {
                 let omit_shape = match *shape {
                     EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::Cover)) |
                     EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)) => {
                         true
                     },
                     _ => false,
                 };
                 if self.compat_mode == CompatMode::Modern {
                     if !omit_shape {
                         shape.to_css(dest)?;
                         dest.write_str(" ")?;
                     }
                     dest.write_str("at ")?;
                     position.to_css(dest)?;
                 } else {
                     position.to_css(dest)?;
+                    if let Some(ref a) = *angle {
+                        dest.write_str(" ")?;
+                        a.to_css(dest)?;
+                    }
                     if !omit_shape {
                         dest.write_str(", ")?;
                         shape.to_css(dest)?;
                     }
                 }
                 false
             },
         };
@@ -266,17 +270,17 @@ impl<D, L, LoP, P, C> ToCss for Gradient
             }
             skip_comma = false;
             item.to_css(dest)?;
         }
         dest.write_str(")")
     }
 }
 
-impl<D, L, LoP, P> GradientKind<D, L, LoP, P> {
+impl<D, L, LoP, P, A> GradientKind<D, L, LoP, P, A> {
     fn label(&self) -> &str {
         match *self {
             GradientKind::Linear(..) => "linear",
             GradientKind::Radial(..) => "radial",
         }
     }
 }
 
--- a/servo/components/style/values/specified/image.rs
+++ b/servo/components/style/values/specified/image.rs
@@ -41,24 +41,26 @@ pub type Image = GenericImage<Gradient, 
 /// Specified values for a CSS gradient.
 /// https://drafts.csswg.org/css-images/#gradients
 pub type Gradient = GenericGradient<
     LineDirection,
     Length,
     LengthOrPercentage,
     Position,
     RGBAColor,
+    Angle,
 >;
 
 /// A specified gradient kind.
 pub type GradientKind = GenericGradientKind<
     LineDirection,
     Length,
     LengthOrPercentage,
     Position,
+    Angle,
 >;
 
 /// A specified gradient line direction.
 #[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum LineDirection {
     /// An angular direction.
     Angle(Angle),
@@ -178,37 +180,43 @@ impl Parse for Gradient {
                 Some((Shape::Linear, true, CompatMode::Moz))
             },
             "radial-gradient" => {
                 Some((Shape::Radial, false, CompatMode::Modern))
             },
             "-webkit-radial-gradient" => {
                 Some((Shape::Radial, false, CompatMode::WebKit))
             },
+            "-moz-radial-gradient" => {
+                Some((Shape::Radial, false, CompatMode::Moz))
+            },
             "repeating-radial-gradient" => {
                 Some((Shape::Radial, true, CompatMode::Modern))
             },
             "-webkit-repeating-radial-gradient" => {
                 Some((Shape::Radial, true, CompatMode::WebKit))
             },
+            "-moz-repeating-radial-gradient" => {
+                Some((Shape::Radial, true, CompatMode::Moz))
+            },
             "-webkit-gradient" => {
                 return input.parse_nested_block(|i| Self::parse_webkit_gradient_argument(context, i));
             },
             _ => None,
         };
 
         let (shape, repeating, mut compat_mode) = match result {
             Some(result) => result,
             None => return Err(StyleParseError::UnexpectedFunction(func).into()),
         };
 
         let (kind, items) = input.parse_nested_block(|i| {
             let shape = match shape {
                 Shape::Linear => GradientKind::parse_linear(context, i, &mut compat_mode)?,
-                Shape::Radial => GradientKind::parse_radial(context, i, compat_mode)?,
+                Shape::Radial => GradientKind::parse_radial(context, i, &mut compat_mode)?,
             };
             let items = GradientItem::parse_comma_separated(context, i)?;
             Ok((shape, items))
         })?;
 
         if items.len() < 2 {
             return Err(StyleParseError::UnspecifiedError.into());
         }
@@ -378,17 +386,17 @@ impl Gradient {
                 let (reverse_stops, point, radius) = if second_radius.value >= first_radius.value {
                     (false, second_point, second_radius)
                 } else {
                     (true, first_point, first_radius)
                 };
 
                 let shape = GenericEndingShape::Circle(Circle::Radius(Length::from_px(radius.value)));
                 let position = point.into();
-                let kind = GenericGradientKind::Radial(shape, position);
+                let kind = GenericGradientKind::Radial(shape, position, None);
 
                 (kind, reverse_stops)
             },
             _ => return Err(SelectorParseError::UnexpectedIdent(ident.clone()).into()),
         };
 
         let mut items = input.try(|i| {
             i.expect_comma()?;
@@ -484,45 +492,78 @@ impl GradientKind {
         } else {
             LineDirection::Vertical(Y::Bottom)
         };
         Ok(GenericGradientKind::Linear(direction))
     }
 
     fn parse_radial<'i, 't>(context: &ParserContext,
                             input: &mut Parser<'i, 't>,
-                            compat_mode: CompatMode)
+                            compat_mode: &mut CompatMode)
                             -> Result<Self, ParseError<'i>> {
-        let (shape, position) = if compat_mode == CompatMode::Modern {
-            let shape = input.try(|i| EndingShape::parse(context, i, compat_mode));
-            let position = input.try(|i| {
-                i.expect_ident_matching("at")?;
-                Position::parse(context, i)
-            });
-            (shape, position)
-        } else {
-            let position = input.try(|i| Position::parse(context, i));
-            let shape = input.try(|i| {
-                if position.is_ok() {
-                    i.expect_comma()?;
+        let (shape, position, angle) = match *compat_mode {
+            CompatMode::Modern => {
+                let shape = input.try(|i| EndingShape::parse(context, i, *compat_mode));
+                let position = input.try(|i| {
+                    i.expect_ident_matching("at")?;
+                    Position::parse(context, i)
+                });
+                (shape, position, None)
+            },
+            CompatMode::WebKit => {
+                let position = input.try(|i| Position::parse(context, i));
+                let shape = input.try(|i| {
+                    if position.is_ok() {
+                        i.expect_comma()?;
+                    }
+                    EndingShape::parse(context, i, *compat_mode)
+                });
+                (shape, position, None)
+            },
+            // The syntax of `-moz-` prefixed radial gradient is:
+            // -moz-radial-gradient(
+            //   [ [ <position> || <angle> ]?  [ ellipse | [ <length> | <percentage> ]{2} ] , |
+            //     [ <position> || <angle> ]?  [ [ circle | ellipse ] | <extent-keyword> ] , |
+            //   ]?
+            //   <color-stop> [ , <color-stop> ]+
+            // )
+            // where <extent-keyword> = closest-corner | closest-side | farthest-corner | farthest-side |
+            //                          cover | contain
+            // and <color-stop>     = <color> [ <percentage> | <length> ]?
+            CompatMode::Moz => {
+                let mut position = input.try(|i| Position::parse_legacy(context, i));
+                let angle = input.try(|i| Angle::parse(context, i)).ok();
+                if position.is_err() {
+                    position = input.try(|i| Position::parse_legacy(context, i));
                 }
-                EndingShape::parse(context, i, compat_mode)
-            });
-            (shape, position)
+
+                let shape = input.try(|i| {
+                    if position.is_ok() || angle.is_some() {
+                        i.expect_comma()?;
+                    }
+                    EndingShape::parse(context, i, *compat_mode)
+                });
+
+                (shape, position, angle)
+            }
         };
 
-        if shape.is_ok() || position.is_ok() {
+        if shape.is_ok() || position.is_ok() || angle.is_some() {
             input.expect_comma()?;
         }
 
         let shape = shape.unwrap_or({
             GenericEndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner))
         });
         let position = position.unwrap_or(Position::center());
-        Ok(GenericGradientKind::Radial(shape, position))
+        // If this form can be represented in Modern mode, then convert the compat_mode to Modern.
+        if *compat_mode == CompatMode::Moz && angle.is_none() {
+            *compat_mode = CompatMode::Modern;
+        }
+        Ok(GenericGradientKind::Radial(shape, position, angle))
     }
 }
 
 impl GenericsLineDirection for LineDirection {
     fn points_downwards(&self) -> bool {
         match *self {
             LineDirection::Angle(ref angle) => angle.radians() == PI,
             LineDirection::Vertical(Y::Bottom) => true,
@@ -668,34 +709,39 @@ impl EndingShape {
                     Ok((x, y))
                 });
                 if let Ok((x, y)) = pair {
                     return Ok(GenericEndingShape::Ellipse(Ellipse::Radii(x, y)));
                 }
             }
             return Ok(GenericEndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)));
         }
-        if let Ok(length) = input.try(|i| Length::parse(context, i)) {
-            if let Ok(y) = input.try(|i| LengthOrPercentage::parse(context, i)) {
-                if compat_mode == CompatMode::Modern {
-                    let _ = input.try(|i| i.expect_ident_matching("ellipse"));
-                }
-                return Ok(GenericEndingShape::Ellipse(Ellipse::Radii(length.into(), y)));
-            }
-            if compat_mode == CompatMode::Modern {
-                let y = input.try(|i| {
-                    i.expect_ident_matching("ellipse")?;
-                    LengthOrPercentage::parse(context, i)
-                });
-                if let Ok(y) = y {
+        // -moz- prefixed radial gradient doesn't allow EndingShape's Length or LengthOrPercentage
+        // to come before shape keyword. Otherwise it conflicts with <position>.
+        if compat_mode != CompatMode::Moz {
+            if let Ok(length) = input.try(|i| Length::parse(context, i)) {
+                if let Ok(y) = input.try(|i| LengthOrPercentage::parse(context, i)) {
+                    if compat_mode == CompatMode::Modern {
+                        let _ = input.try(|i| i.expect_ident_matching("ellipse"));
+                    }
                     return Ok(GenericEndingShape::Ellipse(Ellipse::Radii(length.into(), y)));
                 }
-                let _ = input.try(|i| i.expect_ident_matching("circle"));
+                if compat_mode == CompatMode::Modern {
+                    let y = input.try(|i| {
+                        i.expect_ident_matching("ellipse")?;
+                        LengthOrPercentage::parse(context, i)
+                    });
+                    if let Ok(y) = y {
+                        return Ok(GenericEndingShape::Ellipse(Ellipse::Radii(length.into(), y)));
+                    }
+                    let _ = input.try(|i| i.expect_ident_matching("circle"));
+                }
+
+                return Ok(GenericEndingShape::Circle(Circle::Radius(length)));
             }
-            return Ok(GenericEndingShape::Circle(Circle::Radius(length)));
         }
         input.try(|i| {
             let x = Percentage::parse(context, i)?;
             let y = if let Ok(y) = i.try(|i| LengthOrPercentage::parse(context, i)) {
                 if compat_mode == CompatMode::Modern {
                     let _ = i.try(|i| i.expect_ident_matching("ellipse"));
                 }
                 y
@@ -710,18 +756,21 @@ impl EndingShape {
     }
 }
 
 impl ShapeExtent {
     fn parse_with_compat_mode<'i, 't>(input: &mut Parser<'i, 't>,
                                       compat_mode: CompatMode)
                                       -> Result<Self, ParseError<'i>> {
         match Self::parse(input)? {
-            ShapeExtent::Contain | ShapeExtent::Cover if compat_mode == CompatMode::Modern =>
-                Err(StyleParseError::UnspecifiedError.into()),
+            ShapeExtent::Contain | ShapeExtent::Cover if compat_mode == CompatMode::Modern => {
+                Err(StyleParseError::UnspecifiedError.into())
+            },
+            ShapeExtent::Contain => Ok(ShapeExtent::ClosestSide),
+            ShapeExtent::Cover => Ok(ShapeExtent::FarthestCorner),
             keyword => Ok(keyword),
         }
     }
 }
 
 impl GradientItem {
     fn parse_comma_separated<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                                      -> Result<Vec<Self>, ParseError<'i>> {