--- 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>> {