From de1fb76ac1c7c20e734aafab25062f23dab07414 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Thu, 20 Apr 2017 13:11:13 +0200 Subject: [PATCH 1/3] Properly forbid legacy keywords in modern gradient syntax --- components/style/values/specified/image.rs | 24 +++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index 0604b059263..41113d09cb1 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -372,7 +372,7 @@ fn parse_shape(context: &ParserContext, input: &mut Parser, parse_size_keyword: F) -> Result - where F: FnOnce(&mut Parser) -> Result + where F: Fn(&mut Parser) -> Result { if let Ok((first, second)) = input.try(|i| parse_two_length(context, i)) { // Handle ? @@ -382,7 +382,7 @@ fn parse_shape(context: &ParserContext, // Handle ? 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) { + } else if let Ok(keyword) = input.try(&parse_size_keyword) { // Handle ? if input.try(|input| input.expect_ident_matching("circle")).is_ok() { Ok(EndingShape::Circle(LengthOrKeyword::Keyword(keyword))) @@ -394,12 +394,12 @@ fn parse_shape(context: &ParserContext, // https://github.com/rust-lang/rust/issues/41272 if input.try(|input| input.expect_ident_matching("ellipse")).is_ok() { // Handle ? - let length = input.try(|i| LengthOrPercentageOrKeyword::parse(context, i)) + let length = input.try(|i| LengthOrPercentageOrKeyword::parse(context, i, parse_size_keyword)) .unwrap_or(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::FarthestCorner)); Ok(EndingShape::Ellipse(length)) } else if input.try(|input| input.expect_ident_matching("circle")).is_ok() { // Handle ? - let length = input.try(|i| LengthOrKeyword::parse(context, i)) + let length = input.try(|i| LengthOrKeyword::parse(context, i, parse_size_keyword)) .unwrap_or(LengthOrKeyword::Keyword(SizeKeyword::FarthestCorner)); Ok(EndingShape::Circle(length)) } else { @@ -539,9 +539,11 @@ pub enum LengthOrKeyword { Keyword(SizeKeyword), } -impl Parse for LengthOrKeyword { - fn parse(context: &ParserContext, input: &mut Parser) -> Result { - if let Ok(keyword) = input.try(SizeKeyword::parse) { +impl LengthOrKeyword { + fn parse(context: &ParserContext, input: &mut Parser, parse_size_keyword: F) -> Result + where F: Fn(&mut Parser) -> Result + { + if let Ok(keyword) = input.try(parse_size_keyword) { Ok(LengthOrKeyword::Keyword(keyword)) } else { Ok(LengthOrKeyword::Length(try!(Length::parse(context, input)))) @@ -568,9 +570,11 @@ pub enum LengthOrPercentageOrKeyword { } -impl Parse for LengthOrPercentageOrKeyword { - fn parse(context: &ParserContext, input: &mut Parser) -> Result { - if let Ok(keyword) = input.try(SizeKeyword::parse) { +impl LengthOrPercentageOrKeyword { + fn parse(context: &ParserContext, input: &mut Parser, parse_size_keyword: F) -> Result + where F: Fn(&mut Parser) -> Result + { + if let Ok(keyword) = input.try(parse_size_keyword) { Ok(LengthOrPercentageOrKeyword::Keyword(keyword)) } else { Ok(LengthOrPercentageOrKeyword::LengthOrPercentage( From 2ac57a7d8ff4f96c5fdb7c75f624c22c5c29e4b6 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Thu, 20 Apr 2017 22:46:18 +0200 Subject: [PATCH 2/3] Fix parsing of -webkit-radial-gradient() It's either keywords or lengths, but not a mix of two. --- components/style/values/specified/image.rs | 121 ++++++++++++--------- 1 file changed, 70 insertions(+), 51 deletions(-) diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index 41113d09cb1..d2c749f85e9 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -257,17 +257,49 @@ impl GradientKind { pub fn parse_modern_radial(context: &ParserContext, input: &mut Parser) -> Result { 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 "at" + // Handle just (EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::FarthestCorner)), position) - } else if let Ok(shape) = input.try(|i| parse_shape(context, i, SizeKeyword::parse_modern)) { - // Handle ["at" ]? + } else if let Ok((first, second)) = input.try(|i| parse_two_length(context, i)) { + // Handle ? ? + 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 ? ? + 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_modern) { + // Handle ? ? + 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)) + }; (shape, 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)), - Position::center()) + // Handle ? ? + if input.try(|input| input.expect_ident_matching("ellipse")).is_ok() { + // Handle ? ? + let length = input.try(|i| LengthOrPercentageOrKeyword::parse(context, i, SizeKeyword::parse_modern)) + .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 ? ? + let length = input.try(|i| LengthOrKeyword::parse(context, i, SizeKeyword::parse_modern)) + .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 needs_comma { @@ -287,13 +319,40 @@ impl GradientKind { Position::center() }; - let shape = if let Ok(shape) = input.try(|i| parse_shape(context, i, SizeKeyword::parse)) { - try!(input.expect_comma()); - shape + let mut needs_comma = true; + + // Ending shape and position can be in various order. Checks all probabilities. + let shape = if let Ok((first, second)) = input.try(|i| parse_two_length(context, i)) { + EndingShape::Ellipse(LengthOrPercentageOrKeyword::LengthOrPercentage(first, second)) + } else if let Ok(keyword) = input.try(SizeKeyword::parse) { + // Handle ? + 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 { - EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::Cover)) + // Handle ? + if input.try(|input| input.expect_ident_matching("ellipse")).is_ok() { + // Handle ? + let keyword = input.try(SizeKeyword::parse).unwrap_or((SizeKeyword::Cover)); + EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(keyword)) + } else if input.try(|input| input.expect_ident_matching("circle")).is_ok() { + // Handle ? + let keyword = input.try(SizeKeyword::parse).unwrap_or((SizeKeyword::Cover)); + EndingShape::Circle(LengthOrKeyword::Keyword(keyword)) + } else { + // If there is no shape keyword, it should set to default. + needs_comma = false; + EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::FarthestCorner)) + } }; + if needs_comma { + try!(input.expect_comma()); + } + Ok(GradientKind::Radial(shape, position)) } } @@ -368,46 +427,6 @@ fn parse_position(context: &ParserContext, input: &mut Parser) -> Result(context: &ParserContext, - input: &mut Parser, - parse_size_keyword: F) - -> Result - where F: Fn(&mut Parser) -> Result -{ - if let Ok((first, second)) = input.try(|i| parse_two_length(context, i)) { - // Handle ? - 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 ? - 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 ? - 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 ? - let length = input.try(|i| LengthOrPercentageOrKeyword::parse(context, i, parse_size_keyword)) - .unwrap_or(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::FarthestCorner)); - Ok(EndingShape::Ellipse(length)) - } else if input.try(|input| input.expect_ident_matching("circle")).is_ok() { - // Handle ? - let length = input.try(|i| LengthOrKeyword::parse(context, i, parse_size_keyword)) - .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))] From 0854df922db49e462ba21bc35ab4e9b792ea277b Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Fri, 21 Apr 2017 11:08:44 +0200 Subject: [PATCH 3/3] Fix parsing of -webkit-linear-gradient() No code can actually be shared between WebKit and modern gradients. :( --- components/style/values/specified/image.rs | 104 +++++++++++---------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index d2c749f85e9..501ef2948c9 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -147,23 +147,12 @@ impl ToCss for Gradient { impl Gradient { /// Parses a gradient from the given arguments. pub fn parse_function(context: &ParserContext, input: &mut Parser) -> Result { - let parse_linear_gradient = |input: &mut Parser, mode| { + fn parse(context: &ParserContext, input: &mut Parser, parse_kind: F) + -> Result<(GradientKind, Vec), ()> + where F: FnOnce(&ParserContext, &mut Parser) -> Result + { 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_modern_radial_gradient = |input: &mut Parser| { - input.parse_nested_block(|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 kind = try!(parse_kind(context, input)); let stops = try!(input.parse_comma_separated(|i| ColorStop::parse(context, i))); Ok((kind, stops)) }) @@ -172,36 +161,36 @@ impl Gradient { let mut compat_mode = CompatMode::Modern; let (gradient_kind, stops) = match_ignore_ascii_case! { &try!(input.expect_function()), "linear-gradient" => { - try!(parse_linear_gradient(input, compat_mode)) + try!(parse(context, input, GradientKind::parse_modern_linear)) }, "-webkit-linear-gradient" => { compat_mode = CompatMode::WebKit; - try!(parse_linear_gradient(input, compat_mode)) + try!(parse(context, input, GradientKind::parse_webkit_linear)) }, "repeating-linear-gradient" => { repeating = true; - try!(parse_linear_gradient(input, compat_mode)) + try!(parse(context, input, GradientKind::parse_modern_linear)) }, "-webkit-repeating-linear-gradient" => { repeating = true; compat_mode = CompatMode::WebKit; - try!(parse_linear_gradient(input, compat_mode)) + try!(parse(context, input, GradientKind::parse_webkit_linear)) }, "radial-gradient" => { - try!(parse_modern_radial_gradient(input)) + try!(parse(context, input, GradientKind::parse_modern_radial)) }, "-webkit-radial-gradient" => { compat_mode = CompatMode::WebKit; - try!(parse_webkit_radial_gradient(input)) + try!(parse(context, input, GradientKind::parse_webkit_radial)) }, "repeating-radial-gradient" => { repeating = true; - try!(parse_modern_radial_gradient(input)) + try!(parse(context, input, GradientKind::parse_modern_radial)) }, "-webkit-repeating-radial-gradient" => { repeating = true; compat_mode = CompatMode::WebKit; - try!(parse_webkit_radial_gradient(input)) + try!(parse(context, input, GradientKind::parse_webkit_radial)) }, _ => { return Err(()); } }; @@ -248,9 +237,46 @@ 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 { - let angle_or_corner = try!(AngleOrCorner::parse(context, input, mode)); - Ok(GradientKind::Linear(angle_or_corner)) + fn parse_modern_linear(context: &ParserContext, input: &mut Parser) -> Result { + let direction = if let Ok(angle) = input.try(|i| Angle::parse_with_unitless(context, i)) { + try!(input.expect_comma()); + AngleOrCorner::Angle(angle) + } else { + if input.try(|i| i.expect_ident_matching("to")).is_ok() { + let (horizontal, vertical) = + if let Ok(value) = input.try(HorizontalDirection::parse) { + (Some(value), input.try(VerticalDirection::parse).ok()) + } else { + let value = try!(VerticalDirection::parse(input)); + (input.try(HorizontalDirection::parse).ok(), Some(value)) + }; + try!(input.expect_comma()); + AngleOrCorner::Corner(horizontal, vertical) + } else { + AngleOrCorner::None + } + }; + Ok(GradientKind::Linear(direction)) + } + + fn parse_webkit_linear(context: &ParserContext, input: &mut Parser) -> Result { + let direction = if let Ok(angle) = input.try(|i| Angle::parse_with_unitless(context, i)) { + AngleOrCorner::Angle(angle) + } else { + if let Ok(value) = input.try(HorizontalDirection::parse) { + AngleOrCorner::Corner(Some(value), input.try(VerticalDirection::parse).ok()) + } else { + if let Ok(value) = input.try(VerticalDirection::parse) { + AngleOrCorner::Corner(input.try(HorizontalDirection::parse).ok(), Some(value)) + } else { + AngleOrCorner::None + } + } + }; + if direction != AngleOrCorner::None { + try!(input.expect_comma()); + } + Ok(GradientKind::Linear(direction)) } /// Parses a modern radial gradient from the given arguments. @@ -345,7 +371,7 @@ impl GradientKind { } else { // If there is no shape keyword, it should set to default. needs_comma = false; - EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::FarthestCorner)) + EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::Cover)) } }; @@ -463,28 +489,6 @@ impl AngleOrCorner { } } -impl AngleOrCorner { - fn parse(context: &ParserContext, input: &mut Parser, mode: CompatMode) -> Result { - if let Ok(angle) = input.try(|i| Angle::parse_with_unitless(context, i)) { - try!(input.expect_comma()); - return Ok(AngleOrCorner::Angle(angle)) - } - if mode == CompatMode::WebKit || input.try(|input| input.expect_ident_matching("to")).is_ok() { - let (horizontal, vertical) = - if let Ok(value) = input.try(HorizontalDirection::parse) { - (Some(value), input.try(VerticalDirection::parse).ok()) - } else { - let value = try!(VerticalDirection::parse(input)); - (input.try(HorizontalDirection::parse).ok(), Some(value)) - }; - try!(input.expect_comma()); - Ok(AngleOrCorner::Corner(horizontal, vertical)) - } else { - Ok(AngleOrCorner::None) - } - } -} - /// Specified values for one color stop in a linear gradient. /// https://drafts.csswg.org/css-images/#typedef-color-stop-list #[derive(Clone, PartialEq, Debug)]