diff --git a/components/style/gecko/conversions.rs b/components/style/gecko/conversions.rs index ac87b180953..95f4563b538 100644 --- a/components/style/gecko/conversions.rs +++ b/components/style/gecko/conversions.rs @@ -205,7 +205,8 @@ impl nsStyleImage { Gecko_CreateGradient(NS_STYLE_GRADIENT_SHAPE_LINEAR as u8, NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER as u8, gradient.repeating, - gradient.compat_mode == CompatMode::WebKit, + gradient.compat_mode != CompatMode::Modern, + gradient.compat_mode == CompatMode::Moz, stop_count as u32) }; @@ -234,7 +235,24 @@ impl nsStyleImage { (*gecko_gradient).mBgPosY .set_value(CoordDataValue::Percent(percent_y)); } - } + }, + #[cfg(feature = "gecko")] + LineDirection::MozPosition(position, angle) => { + unsafe { + if let Some(position) = position { + (*gecko_gradient).mBgPosX.set(position.horizontal); + (*gecko_gradient).mBgPosY.set(position.vertical); + } else { + (*gecko_gradient).mBgPosX.set_value(CoordDataValue::None); + (*gecko_gradient).mBgPosY.set_value(CoordDataValue::None); + } + if let Some(angle) = angle { + (*gecko_gradient).mAngle.set(angle); + } else { + (*gecko_gradient).mAngle.set_value(CoordDataValue::None); + } + } + }, } gecko_gradient }, @@ -274,7 +292,8 @@ impl nsStyleImage { Gecko_CreateGradient(gecko_shape, gecko_size, gradient.repeating, - gradient.compat_mode == CompatMode::WebKit, + gradient.compat_mode != CompatMode::Modern, + gradient.compat_mode == CompatMode::Moz, stop_count as u32) }; diff --git a/components/style/gecko/generated/bindings.rs b/components/style/gecko/generated/bindings.rs index 006f2a8ddd3..6db35db4954 100644 --- a/components/style/gecko/generated/bindings.rs +++ b/components/style/gecko/generated/bindings.rs @@ -952,8 +952,8 @@ extern "C" { } extern "C" { pub fn Gecko_CreateGradient(shape: u8, size: u8, repeating: bool, - legacy_syntax: bool, stops: u32) - -> *mut nsStyleGradient; + legacy_syntax: bool, moz_legacy_syntax: bool, + stops: u32) -> *mut nsStyleGradient; } extern "C" { pub fn Gecko_SetListStyleImageNone(style_struct: *mut nsStyleList); diff --git a/components/style/properties/shorthand/background.mako.rs b/components/style/properties/shorthand/background.mako.rs index 144dc829e77..9c4cfaf762d 100644 --- a/components/style/properties/shorthand/background.mako.rs +++ b/components/style/properties/shorthand/background.mako.rs @@ -193,7 +193,7 @@ spec="https://drafts.csswg.org/css-backgrounds-4/#the-background-position"> use properties::longhands::{background_position_x, background_position_y}; use values::specified::AllowQuirks; - use values::specified::position::Position; + use values::specified::position::{Position, PositionSyntax}; pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { @@ -202,7 +202,7 @@ let mut any = false; input.parse_comma_separated(|input| { - let value = Position::parse_quirky(context, input, AllowQuirks::Yes)?; + let value = Position::parse_quirky(context, input, AllowQuirks::Yes, PositionSyntax::Modern)?; position_x.0.push(value.horizontal); position_y.0.push(value.vertical); any = true; diff --git a/components/style/values/computed/image.rs b/components/style/values/computed/image.rs index 05640c94f6f..bacb912b567 100644 --- a/components/style/values/computed/image.rs +++ b/components/style/values/computed/image.rs @@ -56,6 +56,9 @@ pub enum LineDirection { Angle(Angle), /// A corner. Corner(X, Y), + /// A Position and an Angle for legacy `-moz-` prefixed gradient. + #[cfg(feature = "gecko")] + MozPosition(Option, Option), } /// A computed radial gradient ending shape. @@ -75,6 +78,8 @@ impl GenericLineDirection for LineDirection { match *self { LineDirection::Angle(angle) => angle.radians() == PI, LineDirection::Corner(..) => false, + #[cfg(feature = "gecko")] + LineDirection::MozPosition(_, _) => false, } } @@ -91,6 +96,21 @@ impl GenericLineDirection for LineDirection { dest.write_str(" ")?; y.to_css(dest) }, + #[cfg(feature = "gecko")] + LineDirection::MozPosition(position, angle) => { + let mut need_space = false; + if let Some(position) = position { + position.to_css(dest)?; + need_space = true; + } + if let Some(angle) = angle { + if need_space { + dest.write_str(" ")?; + } + angle.to_css(dest)?; + } + Ok(()) + } } } } @@ -102,8 +122,8 @@ impl SpecifiedLineDirection { #[cfg(feature = "gecko")] { return match _compat_mode { - CompatMode::Modern => modern_angle, - CompatMode::WebKit => -modern_angle + 270. + CompatMode::WebKit => -modern_angle + 270., + _ => modern_angle, } } #[cfg(feature = "servo")] @@ -131,6 +151,11 @@ impl SpecifiedLineDirection { SpecifiedLineDirection::Corner(x, y) => { LineDirection::Corner(x, y) }, + #[cfg(feature = "gecko")] + SpecifiedLineDirection::MozPosition(ref position, ref angle) => { + LineDirection::MozPosition(position.to_computed_value(context), + angle.to_computed_value(context)) + }, } } @@ -142,6 +167,11 @@ impl SpecifiedLineDirection { LineDirection::Corner(x, y) => { SpecifiedLineDirection::Corner(x, y) }, + #[cfg(feature = "gecko")] + LineDirection::MozPosition(ref position, ref angle) => { + SpecifiedLineDirection::MozPosition(ToComputedValue::from_computed_value(position), + ToComputedValue::from_computed_value(angle)) + }, } } } diff --git a/components/style/values/generics/image.rs b/components/style/values/generics/image.rs index 31a4606aa79..59128b057ec 100644 --- a/components/style/values/generics/image.rs +++ b/components/style/values/generics/image.rs @@ -50,12 +50,14 @@ pub struct Gradient #[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -/// Whether we used the modern notation or the compatibility `-webkit` prefix. +/// Whether we used the modern notation or the compatibility `-webkit`, `-moz` prefixes. pub enum CompatMode { /// Modern syntax. Modern, /// `-webkit` prefix. WebKit, + /// `-moz` prefix + Moz, } /// A gradient kind. @@ -216,9 +218,12 @@ impl ToCss for Gradient where D: LineDirection, L: ToCss, LoP: ToCss, P: ToCss, C: ToCss, { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - if self.compat_mode == CompatMode::WebKit { - dest.write_str("-webkit-")?; + match self.compat_mode { + CompatMode::WebKit => dest.write_str("-webkit-")?, + CompatMode::Moz => dest.write_str("-moz-")?, + _ => {}, } + if self.repeating { dest.write_str("repeating-")?; } diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index 7fc73f94733..56d09f68317 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -68,6 +68,12 @@ pub enum LineDirection { Vertical(Y), /// A direction towards a corner of a box. Corner(X, Y), + /// A Position and an Angle for legacy `-moz-` prefixed gradient. + /// `-moz-` prefixed linear gradient can contain both a position and an angle but it + /// uses legacy syntax for position. That means we can't specify both keyword and + /// length for each horizontal/vertical components. + #[cfg(feature = "gecko")] + MozPosition(Option, Option), } /// A specified ending shape. @@ -160,12 +166,20 @@ impl Parse for Gradient { "-webkit-linear-gradient" => { Some((Shape::Linear, false, CompatMode::WebKit)) }, + #[cfg(feature = "gecko")] + "-moz-linear-gradient" => { + Some((Shape::Linear, false, CompatMode::Moz)) + }, "repeating-linear-gradient" => { Some((Shape::Linear, true, CompatMode::Modern)) }, "-webkit-repeating-linear-gradient" => { Some((Shape::Linear, true, CompatMode::WebKit)) }, + #[cfg(feature = "gecko")] + "-moz-repeating-linear-gradient" => { + Some((Shape::Linear, true, CompatMode::Moz)) + }, "radial-gradient" => { Some((Shape::Radial, false, CompatMode::Modern)) }, @@ -184,14 +198,14 @@ impl Parse for Gradient { _ => None, }; - let (shape, repeating, compat_mode) = match result { + 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, compat_mode)?, + Shape::Linear => GradientKind::parse_linear(context, i, &mut compat_mode)?, Shape::Radial => GradientKind::parse_radial(context, i, compat_mode)?, }; let items = GradientItem::parse_comma_separated(context, i)?; @@ -461,9 +475,11 @@ impl Gradient { } impl GradientKind { + /// Parses a linear gradient. + /// CompatMode can change during `-moz-` prefixed gradient parsing if it come across a `to` keyword. fn parse_linear<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>, - compat_mode: CompatMode) + compat_mode: &mut CompatMode) -> Result> { let direction = if let Ok(d) = input.try(|i| LineDirection::parse(context, i, compat_mode)) { input.expect_comma()?; @@ -543,7 +559,22 @@ impl GenericsLineDirection for LineDirection { x.to_css(dest)?; dest.write_str(" ")?; y.to_css(dest) - } + }, + #[cfg(feature = "gecko")] + LineDirection::MozPosition(ref position, ref angle) => { + let mut need_space = false; + if let Some(ref position) = *position { + position.to_css(dest)?; + need_space = true; + } + if let Some(ref angle) = *angle { + if need_space { + dest.write_str(" ")?; + } + angle.to_css(dest)?; + } + Ok(()) + }, } } } @@ -551,15 +582,51 @@ impl GenericsLineDirection for LineDirection { impl LineDirection { fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>, - compat_mode: CompatMode) + compat_mode: &mut CompatMode) -> Result> { - if let Ok(angle) = input.try(|i| Angle::parse_with_unitless(context, i)) { - return Ok(LineDirection::Angle(angle)); - } - input.try(|i| { - if compat_mode == CompatMode::Modern { - i.expect_ident_matching("to")?; + let mut _angle = if *compat_mode == CompatMode::Moz { + input.try(|i| Angle::parse(context, i)).ok() + } else { + if let Ok(angle) = input.try(|i| Angle::parse_with_unitless(context, i)) { + return Ok(LineDirection::Angle(angle)); } + None + }; + + input.try(|i| { + let to_ident = i.try(|i| i.expect_ident_matching("to")); + match *compat_mode { + /// `to` keyword is mandatory in modern syntax. + CompatMode::Modern => to_ident?, + // Fall back to Modern compatibility mode in case there is a `to` keyword. + // According to Gecko, `-moz-linear-gradient(to ...)` should serialize like + // `linear-gradient(to ...)`. + CompatMode::Moz if to_ident.is_ok() => *compat_mode = CompatMode::Modern, + /// There is no `to` keyword in webkit prefixed syntax. If it's consumed, + /// parsing should throw an error. + CompatMode::WebKit if to_ident.is_ok() => { + return Err(SelectorParseError::UnexpectedIdent("to".into()).into()) + }, + _ => {}, + } + + + #[cfg(feature = "gecko")] + { + // `-moz-` prefixed linear gradient can be both Angle and Position. + if *compat_mode == CompatMode::Moz { + let position = i.try(|i| Position::parse_legacy(context, i)).ok(); + if _angle.is_none() { + _angle = i.try(|i| Angle::parse(context, i)).ok(); + }; + + if _angle.is_none() && position.is_none() { + return Err(StyleParseError::UnspecifiedError.into()); + } + return Ok(LineDirection::MozPosition(position, _angle)); + } + } + if let Ok(x) = i.try(X::parse) { if let Ok(y) = i.try(Y::parse) { return Ok(LineDirection::Corner(x, y)); diff --git a/components/style/values/specified/position.rs b/components/style/values/specified/position.rs index 4353a0fafa5..10418b05689 100644 --- a/components/style/values/specified/position.rs +++ b/components/style/values/specified/position.rs @@ -49,25 +49,46 @@ define_css_keyword_enum! { Y: } add_impls_for_keyword_enum!(Y); +/// Modern position syntax supports 3 and 4-value syntax. That means: +/// If three or four values are given, then each or represents an offset +/// and must be preceded by a keyword, which specifies from which edge the offset is given. +/// For example, `bottom 10px right 20px` represents a `10px` vertical +/// offset up from the bottom edge and a `20px` horizontal offset leftward from the right edge. +/// If three values are given, the missing offset is assumed to be zero. +/// But for some historical reasons we need to keep CSS Level 2 syntax which only supports up to +/// 2-value. This enum represents whether position should parse modern syntax or legacy syntax. +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum PositionSyntax { + /// Modern syntax + Modern, + /// Legacy syntax + Legacy, +} + impl Parse for Position { fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { - Self::parse_quirky(context, input, AllowQuirks::No) + Self::parse_quirky(context, input, AllowQuirks::No, PositionSyntax::Modern) } } impl Position { /// Parses a ``, with quirks. + /// Syntax parameter uses PositionSyntax to determine whether the syntax parsing + /// mode is modern or legacy. pub fn parse_quirky<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks) + allow_quirks: AllowQuirks, + syntax: PositionSyntax) -> Result> { - match input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) { + match input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks, syntax)) { Ok(x_pos @ PositionComponent::Center) => { - if let Ok(y_pos) = input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) { + if let Ok(y_pos) = input.try(|i| + PositionComponent::parse_quirky(context, i, allow_quirks, syntax)) { return Ok(Self::new(x_pos, y_pos)); } let x_pos = input - .try(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) + .try(|i| PositionComponent::parse_quirky(context, i, allow_quirks, syntax)) .unwrap_or(x_pos); let y_pos = PositionComponent::Center; return Ok(Self::new(x_pos, y_pos)); @@ -79,12 +100,22 @@ impl Position { return Ok(Self::new(x_pos, y_pos)); } if let Ok(y_keyword) = input.try(Y::parse) { - let y_lop = input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok(); + let y_lop = match syntax { + PositionSyntax::Modern => { + input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok() + }, + PositionSyntax::Legacy => None, + }; let x_pos = PositionComponent::Side(x_keyword, lop); let y_pos = PositionComponent::Side(y_keyword, y_lop); return Ok(Self::new(x_pos, y_pos)); } let x_pos = PositionComponent::Side(x_keyword, None); + if syntax == PositionSyntax::Legacy { + if let Ok(y_lop) = input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)) { + return Ok(Self::new(x_pos, PositionComponent::Length(y_lop))) + } + } let y_pos = lop.map_or(PositionComponent::Center, PositionComponent::Length); return Ok(Self::new(x_pos, y_pos)); }, @@ -105,12 +136,22 @@ impl Position { } let y_keyword = Y::parse(input)?; let lop_and_x_pos: Result<_, ParseError> = input.try(|i| { - let y_lop = i.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok(); + let y_lop = match syntax { + PositionSyntax::Modern => { + i.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok() + }, + PositionSyntax::Legacy => None, + }; if let Ok(x_keyword) = i.try(X::parse) { - let x_lop = i.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok(); + let x_lop = match syntax { + PositionSyntax::Modern => { + i.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok() + }, + PositionSyntax::Legacy => None, + }; let x_pos = PositionComponent::Side(x_keyword, x_lop); return Ok((y_lop, x_pos)); - } + }; i.expect_ident_matching("center")?; let x_pos = PositionComponent::Center; Ok((y_lop, x_pos)) @@ -124,6 +165,13 @@ impl Position { Ok(Self::new(x_pos, y_pos)) } + /// Parses legacy Position syntax. + pub fn parse_legacy<'i, 't>(context: &ParserContext, + input: &mut Parser<'i, 't>) + -> Result> { + Self::parse_quirky(context, input, AllowQuirks::No, PositionSyntax::Legacy) + } + /// `center center` #[inline] pub fn center() -> Self { @@ -168,7 +216,7 @@ impl HasViewportPercentage for PositionComponent { impl Parse for PositionComponent { fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { - Self::parse_quirky(context, input, AllowQuirks::No) + Self::parse_quirky(context, input, AllowQuirks::No, PositionSyntax::Modern) } } @@ -176,7 +224,8 @@ impl PositionComponent { /// Parses a component of a CSS position, with quirks. pub fn parse_quirky<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks) + allow_quirks: AllowQuirks, + syntax: PositionSyntax) -> Result> { if input.try(|i| i.expect_ident_matching("center")).is_ok() { return Ok(PositionComponent::Center); @@ -185,6 +234,9 @@ impl PositionComponent { return Ok(PositionComponent::Length(lop)); } let keyword = S::parse(context, input)?; + if syntax == PositionSyntax::Legacy { + return Ok(PositionComponent::Side(keyword, None)); + } let lop = input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok(); Ok(PositionComponent::Side(keyword, lop)) }