diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index 7ab6c7f78b7..9aa9301eef8 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -1339,7 +1339,7 @@ impl FragmentDisplayListBuilding for Fragment { gradient: gradient, }) } - GradientKind::Radial(ref shape, ref center) => { + GradientKind::Radial(ref shape, ref center, _angle) => { let gradient = self.convert_radial_gradient(&bounds, &gradient.items[..], shape, @@ -1484,7 +1484,7 @@ impl FragmentDisplayListBuilding for Fragment { }), })); } - GradientKind::Radial(ref shape, ref center) => { + GradientKind::Radial(ref shape, ref center, _angle) => { let grad = self.convert_radial_gradient(&bounds, &gradient.items[..], shape, diff --git a/components/style/gecko/conversions.rs b/components/style/gecko/conversions.rs index 95f4563b538..9b56f732835 100644 --- a/components/style/gecko/conversions.rs +++ b/components/style/gecko/conversions.rs @@ -256,7 +256,7 @@ impl nsStyleImage { } 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, @@ -297,9 +297,14 @@ impl nsStyleImage { 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); } diff --git a/components/style/values/computed/image.rs b/components/style/values/computed/image.rs index bacb912b567..1e2b694d51d 100644 --- a/components/style/values/computed/image.rs +++ b/components/style/values/computed/image.rs @@ -18,7 +18,6 @@ use values::generics::image::{CompatMode, ColorStop as GenericColorStop, EndingS use values::generics::image::{Gradient as GenericGradient, GradientItem as GenericGradientItem}; use values::generics::image::{Image as GenericImage, GradientKind as GenericGradientKind}; use values::generics::image::{ImageRect as GenericImageRect, LineDirection as GenericLineDirection}; -use values::generics::position::Position as GenericPosition; use values::specified::image::{Gradient as SpecifiedGradient, LineDirection as SpecifiedLineDirection}; use values::specified::image::{GradientKind as SpecifiedGradientKind}; use values::specified::position::{X, Y}; @@ -38,6 +37,7 @@ pub type Gradient = GenericGradient< LengthOrPercentage, Position, RGBA, + Angle, >; /// A computed gradient kind. @@ -46,6 +46,7 @@ pub type GradientKind = GenericGradientKind< Length, LengthOrPercentage, Position, + Angle, >; /// A computed gradient line direction. @@ -205,9 +206,10 @@ impl SpecifiedGradientKind { &GenericGradientKind::Linear(ref line_direction) => { GenericGradientKind::Linear(line_direction.to_computed_value(context, compat_mode)) }, - &GenericGradientKind::Radial(ref ending_shape, ref position) => { + &GenericGradientKind::Radial(ref ending_shape, ref position, ref angle) => { GenericGradientKind::Radial(ending_shape.to_computed_value(context), - position.to_computed_value(context)) + position.to_computed_value(context), + angle.map(|angle| angle.to_computed_value(context))) } } } @@ -218,9 +220,10 @@ impl SpecifiedGradientKind { GenericGradientKind::Linear(line_direction) => { GenericGradientKind::Linear(SpecifiedLineDirection::from_computed_value(&line_direction)) }, - GenericGradientKind::Radial(ending_shape, position) => { - GenericGradientKind::Radial(GenericEndingShape::from_computed_value(&ending_shape), - GenericPosition::from_computed_value(&position)) + GenericGradientKind::Radial(ending_shape, position, angle) => { + GenericGradientKind::Radial(ToComputedValue::from_computed_value(&ending_shape), + ToComputedValue::from_computed_value(&position), + angle.map(|angle| ToComputedValue::from_computed_value(&angle))) } } } diff --git a/components/style/values/generics/image.rs b/components/style/values/generics/image.rs index 59128b057ec..78d011beac0 100644 --- a/components/style/values/generics/image.rs +++ b/components/style/values/generics/image.rs @@ -37,9 +37,9 @@ pub enum Image { /// https://drafts.csswg.org/css-images/#gradients #[derive(Clone, Debug, HasViewportPercentage, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -pub struct Gradient { +pub struct Gradient { /// Gradients can be linear or radial. - pub kind: GradientKind, + pub kind: GradientKind, /// The color stops and interpolation hints. pub items: Vec>, /// True if this is a repeating gradient. @@ -63,11 +63,11 @@ pub enum CompatMode { /// A gradient kind. #[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -pub enum GradientKind { +pub enum GradientKind { /// A linear gradient. Linear(LineDirection), /// A radial gradient. - Radial(EndingShape, Position), + Radial(EndingShape, Position, Option), } /// A radial gradient's ending shape. @@ -214,8 +214,8 @@ impl HasViewportPercentage for Image } } -impl ToCss for Gradient - where D: LineDirection, L: ToCss, LoP: ToCss, P: ToCss, C: ToCss, +impl ToCss for Gradient + where D: LineDirection, L: ToCss, LoP: ToCss, P: ToCss, C: ToCss, A: ToCss { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match self.compat_mode { @@ -235,7 +235,7 @@ impl ToCss for Gradient 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)) => { @@ -252,6 +252,10 @@ impl ToCss for Gradient 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)?; @@ -271,7 +275,7 @@ impl ToCss for Gradient } } -impl GradientKind { +impl GradientKind { fn label(&self) -> &str { match *self { GradientKind::Linear(..) => "linear", diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index 56d09f68317..a117d38f2e3 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -46,6 +46,7 @@ pub type Gradient = GenericGradient< LengthOrPercentage, Position, RGBAColor, + Angle, >; /// A specified gradient kind. @@ -54,6 +55,7 @@ pub type GradientKind = GenericGradientKind< Length, LengthOrPercentage, Position, + Angle, >; /// A specified gradient line direction. @@ -185,6 +187,10 @@ impl Parse for Gradient { }, "-webkit-radial-gradient" => { Some((Shape::Radial, false, CompatMode::WebKit)) + } + #[cfg(feature = "gecko")] + "-moz-radial-gradient" => { + Some((Shape::Radial, false, CompatMode::Moz)) }, "repeating-radial-gradient" => { Some((Shape::Radial, true, CompatMode::Modern)) @@ -192,6 +198,10 @@ impl Parse for Gradient { "-webkit-repeating-radial-gradient" => { Some((Shape::Radial, true, CompatMode::WebKit)) }, + #[cfg(feature = "gecko")] + "-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)); }, @@ -206,7 +216,7 @@ impl Parse for Gradient { 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)) @@ -386,7 +396,7 @@ impl Gradient { 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) }, @@ -492,27 +502,56 @@ impl GradientKind { fn parse_radial<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>, - compat_mode: CompatMode) + compat_mode: &mut CompatMode) -> Result> { - 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( + // [ [ || ]? [ ellipse | [ | ]{2} ] , | + // [ || ]? [ [ circle | ellipse ] | ] , | + // ]? + // [ , ]+ + // ) + // where = closest-corner | closest-side | farthest-corner | farthest-side | + // cover | contain + // and = [ | ]? + 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()?; } @@ -520,7 +559,11 @@ impl GradientKind { 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)) } } @@ -681,24 +724,29 @@ impl EndingShape { } 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 . + 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)?; @@ -723,8 +771,11 @@ impl ShapeExtent { compat_mode: CompatMode) -> Result> { 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), } }