diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index 07c212f30aca..c3b4eafb3c77 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -257,6 +257,91 @@ var validGradientAndElementValues = [ "radial-gradient(at calc(100px + -25px) top, red, blue)", "radial-gradient(at left calc(100px + -25px), red, blue)", + ...(IsCSSPropertyPrefEnabled("layout.css.conic-gradient.enabled") + ? [ + // Conic gradient + "conic-gradient(red, blue)", + "conic-gradient(red,blue,yellow)", + "conic-gradient( red , blue, yellow)", + "conic-gradient(red 0, blue 50deg)", + "conic-gradient(red 10%, blue 50%)", + "conic-gradient(red -50deg, blue 50deg)", + "conic-gradient(red 50deg, blue 0.3turn, yellow 200grad, orange 60%, 5rad)", + + "conic-gradient(red 0 100%)", + "conic-gradient(red 0 50%, blue 50%)", + "conic-gradient(red 0 50deg, blue 50% 100%)", + "conic-gradient(red 0 50%, 0deg, blue 50%)", + "conic-gradient(red 0deg 50%, 0%, blue 50% 100%)", + + "conic-gradient(from 0, red, blue)", + "conic-gradient(from 40deg, red, blue)", + "conic-gradient(from 0.4turn, red, blue)", + "conic-gradient(from 200grad, red, blue)", + "conic-gradient(from 5rad, red, blue)", + + "conic-gradient(at top, red, blue)", + "conic-gradient(at top left, red, blue)", + "conic-gradient(at left top, red, blue)", + "conic-gradient(at center center, red, blue)", + "conic-gradient(at 20% bottom, red, blue)", + "conic-gradient(at center 20%, red, blue)", + "conic-gradient(at left 35px, red, blue)", + "conic-gradient(at 10% 10em, red, blue)", + "conic-gradient(at 44px top, red, blue)", + "conic-gradient(at 0 0, red, blue)", + "conic-gradient(at 10px, red, blue)", + + "conic-gradient(at calc(25%) top, red, blue)", + "conic-gradient(at left calc(25%), red, blue)", + "conic-gradient(at calc(25px) top, red, blue)", + "conic-gradient(at left calc(25px), red, blue)", + "conic-gradient(at calc(-25%) top, red, blue)", + "conic-gradient(at left calc(-25%), red, blue)", + "conic-gradient(at calc(-25px) top, red, blue)", + "conic-gradient(at left calc(-25px), red, blue)", + "conic-gradient(at calc(100px + -25%) top, red, blue)", + "conic-gradient(at left calc(100px + -25%), red, blue)", + "conic-gradient(at calc(100px + -25px) top, red, blue)", + "conic-gradient(at left calc(100px + -25px), red, blue)", + + "conic-gradient(from 0 at 0 0, red, blue)", + "conic-gradient(from 40deg at 50%, red, blue)", + "conic-gradient(from 0.4turn at left 30%, red, blue)", + "conic-gradient(from 200grad at calc(100px + -25%) top, red, blue)", + "conic-gradient(from 5rad at 10px, red, blue)", + + "repeating-conic-gradient(red, blue)", + "repeating-conic-gradient(red, yellow, blue)", + "repeating-conic-gradient(red 1deg, yellow 20%, blue 24em, green)", + "repeating-conic-gradient(red, yellow, green, blue 50%)", + "repeating-conic-gradient(red -50%, yellow -25%, green, blue)", + "repeating-conic-gradient(red -99deg, yellow, green, blue 120%)", + "repeating-conic-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))", + "repeating-conic-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)", + + "repeating-conic-gradient(from 0, red, blue)", + "repeating-conic-gradient(from 40deg, red, blue)", + "repeating-conic-gradient(from 0.4turn, red, blue)", + "repeating-conic-gradient(from 200grad, red, blue)", + "repeating-conic-gradient(from 5rad, red, blue)", + + "repeating-conic-gradient(at top left, red, blue)", + "repeating-conic-gradient(at 0 0, red, blue)", + "repeating-conic-gradient(at 20% bottom, red, blue)", + "repeating-conic-gradient(at center 20%, red, blue)", + "repeating-conic-gradient(at left 35px, red, blue)", + "repeating-conic-gradient(at 10% 10em, red, blue)", + "repeating-conic-gradient(at 44px top, red, blue)", + + "repeating-conic-gradient(from 0 at 0 0, red, blue)", + "repeating-conic-gradient(from 40deg at 50%, red, blue)", + "repeating-conic-gradient(from 0.4turn at left 30%, red, blue)", + "repeating-conic-gradient(from 200grad at calc(100px + -25%) top, red, blue)", + "repeating-conic-gradient(from 5rad at 10px, red, blue)", + ] + : []), + // 2008 GRADIENTS: -webkit-gradient() // ---------------------------------- // linear w/ no color stops (valid) and a variety of position values: @@ -428,6 +513,19 @@ var invalidGradientAndElementValues = [ "radial-gradient(circle 50%, red, blue)", "radial-gradient(50% circle, red, blue)", + /* Invalid units */ + "conic-gradient(red, blue 50px, yellow 30px)", + "conic-gradient(from 0%, black, white)", + "conic-gradient(from 60%, black, white)", + "conic-gradient(from 40px, black, white)", + "conic-gradient(from 50, black, white)", + "conic-gradient(at 50deg, black, white)", + "conic-gradient(from 40deg at 50deg, black, white)", + "conic-gradient(from 40deg at 50deg 60deg, black, white)", + /* Invalid keywords (or ordering) */ + "conic-gradient(at 40% from 50deg, black, white)", + "conic-gradient(to 50deg, black, white)", + /* Used to be valid only when prefixed */ "linear-gradient(top left, red, blue)", "linear-gradient(0 0, red, blue)", @@ -501,6 +599,10 @@ var invalidGradientAndElementValues = [ "radial-gradient(red 0% 50% 75%, blue 75%)", "radial-gradient(center, red 0% 50% 100%)", "radial-gradient(center, red 0% 50% 75%, blue 75%)", + "conic-gradient(red 0% 50% 100%)", + "conic-gradient(red 0% 50% 75%, blue 75%)", + "conic-gradient(center, red 0% 50% 100%)", + "conic-gradient(center, red 0% 50% 75%, blue 75%)", "-moz-linear-gradient(unset, 10px 10px, from(blue))", "-moz-linear-gradient(unset, 10px 10px, blue 0)", @@ -664,6 +766,13 @@ var invalidGradientAndElementValues = [ "-webkit-radial-gradient(top 30deg, red, blue)", "-webkit-radial-gradient(left top 30deg, red, blue)", "-webkit-radial-gradient(10px 20px 30deg, red, blue)", + + // Conic gradients should not support prefixed syntax + "-webkit-gradient(conic, 1 2, 3 4, color-stop(0, lime))", + "-webkit-conic-gradient(red, blue)", + "-moz-conic-gradient(red, blue)", + "-webkit-repeating-conic-gradient(red, blue)", + "-moz-repeating-conic-gradient(red, blue)", ]; var unbalancedGradientAndElementValues = ["-moz-element(#a()"]; diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index 575d12fc1ca1..2b573a507b2e 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -5185,6 +5185,13 @@ value: false mirror: always +# Is support for CSS conic-gradient enabled? +- name: layout.css.conic-gradient.enabled + type: RelaxedAtomicBool + value: false + mirror: always + rust: true + # Is support for CSS contain enabled? - name: layout.css.contain.enabled type: bool diff --git a/servo/components/style/values/computed/image.rs b/servo/components/style/values/computed/image.rs index dc4bbe464a73..4e16c2311d82 100644 --- a/servo/components/style/values/computed/image.rs +++ b/servo/components/style/values/computed/image.rs @@ -13,7 +13,7 @@ use crate::values::computed::url::ComputedImageUrl; use crate::values::computed::NumberOrPercentage; use crate::values::computed::{Angle, Color, Context}; use crate::values::computed::{ - LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage, ToComputedValue, + AngleOrPercentage, LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage, ToComputedValue, }; use crate::values::generics::image::{self as generic, GradientCompatMode}; use crate::values::specified::image::LineDirection as SpecifiedLineDirection; @@ -34,6 +34,8 @@ pub type Gradient = generic::GenericGradient< NonNegativeLength, NonNegativeLengthPercentage, Position, + Angle, + AngleOrPercentage, Color, >; diff --git a/servo/components/style/values/generics/image.rs b/servo/components/style/values/generics/image.rs index 56e3d183d83b..dc64063ba087 100644 --- a/servo/components/style/values/generics/image.rs +++ b/servo/components/style/values/generics/image.rs @@ -9,6 +9,7 @@ use crate::custom_properties; use crate::values::serialize_atom_identifier; use crate::Atom; +use crate::Zero; use servo_arc::Arc; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; @@ -57,6 +58,8 @@ pub enum GenericGradient< NonNegativeLength, NonNegativeLengthPercentage, Position, + Angle, + AngleOrPercentage, Color, > { /// A linear gradient. @@ -83,6 +86,17 @@ pub enum GenericGradient< /// Compatibility mode. compat_mode: GradientCompatMode, }, + /// A conic gradient. + Conic { + /// Start angle of gradient + angle: Angle, + /// Center of gradient + position: Position, + /// The color stops and interpolation hints. + items: crate::OwnedSlice>, + /// True if this is a repeating gradient. + repeating: bool, + }, } pub use self::GenericGradient as Gradient; @@ -308,13 +322,15 @@ where } } -impl ToCss for Gradient +impl ToCss for Gradient where D: LineDirection, LP: ToCss, NL: ToCss, NLP: ToCss, P: ToCss, + A: ToCss, + AoP: ToCss, C: ToCss, { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result @@ -324,6 +340,7 @@ where let (compat_mode, repeating) = match *self { Gradient::Linear { compat_mode, repeating, .. } => (compat_mode, repeating), Gradient::Radial { compat_mode, repeating, .. } => (compat_mode, repeating), + Gradient::Conic { repeating, .. } => (GradientCompatMode::Modern, repeating), }; match compat_mode { @@ -336,17 +353,24 @@ where dest.write_str("repeating-")?; } - let (items, mut skip_comma) = match *self { - Gradient::Linear { ref direction, compat_mode, ref items, .. } => { + match *self { + Gradient::Linear { ref direction, ref items, compat_mode, .. } => { dest.write_str("linear-gradient(")?; - if !direction.points_downwards(compat_mode) { + let mut skip_comma = if !direction.points_downwards(compat_mode) { direction.to_css(dest, compat_mode)?; - (items, false) + false } else { - (items, true) + true + }; + for item in &**items { + if !skip_comma { + dest.write_str(", ")?; + } + skip_comma = false; + item.to_css(dest)?; } }, - Gradient::Radial { ref shape, ref position, compat_mode, ref items, .. } => { + Gradient::Radial { ref shape, ref position, ref items, compat_mode, .. } => { dest.write_str("radial-gradient(")?; let omit_shape = match *shape { EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::Cover)) | @@ -367,15 +391,25 @@ where shape.to_css(dest)?; } } - (items, false) + for item in &**items { + dest.write_str(", ")?; + item.to_css(dest)?; + } + }, + Gradient::Conic { ref angle, ref position, ref items, .. } => { + dest.write_str("conic-gradient(")?; + if !angle.is_zero() { + dest.write_str("from ")?; + angle.to_css(dest)?; + dest.write_str(" ")?; + } + dest.write_str("at ")?; + position.to_css(dest)?; + for item in &**items { + dest.write_str(", ")?; + item.to_css(dest)?; + } }, - }; - for item in &**items { - if !skip_comma { - dest.write_str(", ")?; - } - skip_comma = false; - item.to_css(dest)?; } dest.write_str(")") } diff --git a/servo/components/style/values/specified/image.rs b/servo/components/style/values/specified/image.rs index fcd6210c6b98..66c1c68eb2c2 100644 --- a/servo/components/style/values/specified/image.rs +++ b/servo/components/style/values/specified/image.rs @@ -19,7 +19,7 @@ use crate::values::specified::position::{HorizontalPositionKeyword, VerticalPosi use crate::values::specified::position::{Position, PositionComponent, Side}; use crate::values::specified::url::SpecifiedImageUrl; use crate::values::specified::{ - Angle, Color, Length, LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage, + Angle, AngleOrPercentage, Color, Length, LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage, }; use crate::values::specified::{Number, NumberOrPercentage, Percentage}; use crate::Atom; @@ -44,6 +44,8 @@ pub type Gradient = generic::Gradient< NonNegativeLength, NonNegativeLengthPercentage, Position, + Angle, + AngleOrPercentage, Color, >; @@ -67,6 +69,8 @@ impl SpecifiedValueInfo for Gradient { "repeating-radial-gradient", "-webkit-repeating-radial-gradient", "-moz-repeating-radial-gradient", + "conic-gradient", + "repeating-conic-gradient", "-webkit-gradient", ]); } @@ -188,6 +192,7 @@ impl Parse for Gradient { enum Shape { Linear, Radial, + Conic, } let func = input.expect_function()?; @@ -232,6 +237,12 @@ impl Parse for Gradient { "-moz-repeating-radial-gradient" => { (Shape::Radial, true, GradientCompatMode::Moz) }, + "conic-gradient" if static_prefs::pref!("layout.css.conic-gradient.enabled") => { + (Shape::Conic, false, GradientCompatMode::Modern) + }, + "repeating-conic-gradient" if static_prefs::pref!("layout.css.conic-gradient.enabled") => { + (Shape::Conic, true, GradientCompatMode::Modern) + }, "-webkit-gradient" => { return input.parse_nested_block(|i| { Self::parse_webkit_gradient_argument(context, i) @@ -247,6 +258,7 @@ impl Parse for Gradient { Ok(match shape { Shape::Linear => Self::parse_linear(context, i, repeating, compat_mode)?, Shape::Radial => Self::parse_radial(context, i, repeating, compat_mode)?, + Shape::Conic => Self::parse_conic(context, i, repeating)?, }) })?) } @@ -505,12 +517,12 @@ impl Gradient { Ok(items.into()) } - /// Not used for -webkit-gradient syntax. + /// Not used for -webkit-gradient syntax and conic-gradient fn parse_stops<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { - let items = generic::GradientItem::parse_comma_separated(context, input)?; + let items = generic::GradientItem::parse_comma_separated(context, input, LengthPercentage::parse)?; if items.len() < 2 { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } @@ -595,6 +607,40 @@ impl Gradient { compat_mode, }) } + fn parse_conic<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + repeating: bool, + ) -> Result> { + let angle = input.try(|i| { + i.expect_ident_matching("from")?; + // Spec allows unitless zero start angles + // https://drafts.csswg.org/css-images-4/#valdef-conic-gradient-angle + Angle::parse_with_unitless(context, i) + }); + let position = input.try(|i| { + i.expect_ident_matching("at")?; + Position::parse(context, i) + }); + if angle.is_ok() || position.is_ok() { + input.expect_comma()?; + } + + let angle = angle.unwrap_or(Angle::zero()); + let position = position.unwrap_or(Position::center()); + let items = generic::GradientItem::parse_comma_separated(context, input, AngleOrPercentage::parse_with_unitless)?; + + if items.len() < 2 { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + Ok(Gradient::Conic { + angle, + position, + items, + repeating, + }) + } } impl generic::LineDirection for LineDirection { @@ -798,13 +844,11 @@ impl ShapeExtent { } } -impl generic::GradientItem -where - T: Parse, -{ +impl generic::GradientItem { fn parse_comma_separated<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + parse_position: impl for<'i1, 't1> Fn(&ParserContext, &mut Parser<'i1, 't1>) -> Result> + Copy, ) -> Result, ParseError<'i>> { let mut items = Vec::new(); let mut seen_stop = false; @@ -812,16 +856,16 @@ where loop { input.parse_until_before(Delimiter::Comma, |input| { if seen_stop { - if let Ok(hint) = input.try(|i| T::parse(context, i)) { + if let Ok(hint) = input.try(|i| parse_position(context, i)) { seen_stop = false; items.push(generic::GradientItem::InterpolationHint(hint)); return Ok(()); } } - let stop = generic::ColorStop::parse(context, input)?; + let stop = generic::ColorStop::parse(context, input, parse_position)?; - if let Ok(multi_position) = input.try(|i| T::parse(context, i)) { + if let Ok(multi_position) = input.try(|i| parse_position(context, i)) { let stop_color = stop.color.clone(); items.push(stop.into_item()); items.push( @@ -853,17 +897,15 @@ where } } -impl Parse for generic::ColorStop -where - T: Parse, -{ +impl generic::ColorStop { fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + parse_position: impl for<'i1, 't1> Fn(&ParserContext, &mut Parser<'i1, 't1>) -> Result>, ) -> Result> { Ok(generic::ColorStop { color: Color::parse(context, input)?, - position: input.try(|i| T::parse(context, i)).ok(), + position: input.try(|i| parse_position(context, i)).ok(), }) } }