servo: Merge #16628 - Parse interpolation hints (fixes #15166) (from nox:INTERPOL); r=emilio
authorAnthony Ramine <n.oxyde@gmail.com>
Thu, 27 Apr 2017 10:41:04 -0500
changeset 355428 8d6170ea1200fbef983b7bf80bbd897c2ff52775
parent 355383 87bba05a5669aadbc0e4bb23fb7157c2ebf75877
child 355429 4be445fb462f114413c6a63a87a22c359546ed57
push id89678
push usercbook@mozilla.com
push dateFri, 28 Apr 2017 08:45:40 +0000
treeherdermozilla-inbound@a77813ea2be4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #16628 - Parse interpolation hints (fixes #15166) (from nox:INTERPOL); r=emilio Source-Repo: https://github.com/servo/servo Source-Revision: f598adc3ad6f25ce684127ce2335e783ff49e00a
servo/components/layout/display_list_builder.rs
servo/components/style/gecko/conversions.rs
servo/components/style/values/computed/image.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/specified/image.rs
servo/components/style/values/specified/mod.rs
--- a/servo/components/layout/display_list_builder.rs
+++ b/servo/components/layout/display_list_builder.rs
@@ -53,18 +53,18 @@ use style::computed_values::{image_rende
 use style::computed_values::filter::Filter;
 use style::computed_values::text_shadow::TextShadow;
 use style::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode};
 use style::properties::{self, ServoComputedValues};
 use style::properties::longhands::border_image_repeat::computed_value::RepeatKeyword;
 use style::properties::style_structs;
 use style::servo::restyle_damage::REPAINT;
 use style::values::{Either, RGBA, computed};
-use style::values::computed::{AngleOrCorner, Gradient, GradientKind, LengthOrPercentage};
-use style::values::computed::{LengthOrPercentageOrAuto, NumberOrPercentage};
+use style::values::computed::{AngleOrCorner, Gradient, GradientItem, GradientKind};
+use style::values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto, NumberOrPercentage};
 use style::values::specified::{HorizontalDirection, VerticalDirection};
 use style_traits::CSSPixel;
 use style_traits::cursor::Cursor;
 use table_cell::CollapsedBordersForCell;
 use webrender_traits::{ColorF, ClipId, GradientStop, RepeatMode, ScrollPolicy};
 
 trait ResolvePercentage {
     fn resolve(&self, length: u32) -> u32;
@@ -943,36 +943,42 @@ impl FragmentDisplayListBuilding for Fra
 
         // This is the length of the gradient line.
         let length = Au::from_f32_px(
             (delta.x.to_f32_px() * 2.0).hypot(delta.y.to_f32_px() * 2.0));
 
         // Determine the position of each stop per CSS-IMAGES ยง 3.4.
         //
         // FIXME(#3908, pcwalton): Make sure later stops can't be behind earlier stops.
-        let mut stops = Vec::with_capacity(gradient.stops.len());
+        let stop_items = gradient.items.iter().filter_map(|item| {
+            match *item {
+                GradientItem::ColorStop(ref stop) => Some(stop),
+                _ => None,
+            }
+        }).collect::<Vec<_>>();
+        let mut stops = Vec::with_capacity(stop_items.len());
         let mut stop_run = None;
-        for (i, stop) in gradient.stops.iter().enumerate() {
+        for (i, stop) in stop_items.iter().enumerate() {
             let offset = match stop.position {
                 None => {
                     if stop_run.is_none() {
                         // Initialize a new stop run.
                         let start_offset = if i == 0 {
                             0.0
                         } else {
                             // `unwrap()` here should never fail because this is the beginning of
                             // a stop run, which is always bounded by a length or percentage.
-                            position_to_offset(gradient.stops[i - 1].position.unwrap(), length)
+                            position_to_offset(stop_items[i - 1].position.unwrap(), length)
                         };
                         let (end_index, end_offset) =
-                            match gradient.stops[i..]
+                            match stop_items[i..]
                                           .iter()
                                           .enumerate()
                                           .find(|&(_, ref stop)| stop.position.is_some()) {
-                                None => (gradient.stops.len() - 1, 1.0),
+                                None => (stop_items.len() - 1, 1.0),
                                 Some((end_index, end_stop)) => {
                                     // `unwrap()` here should never fail because this is the end of
                                     // a stop run, which is always bounded by a length or
                                     // percentage.
                                     (end_index,
                                      position_to_offset(end_stop.position.unwrap(), length))
                                 }
                             };
--- a/servo/components/style/gecko/conversions.rs
+++ b/servo/components/style/gecko/conversions.rs
@@ -11,17 +11,18 @@
 use app_units::Au;
 use gecko::values::{convert_rgba_to_nscolor, GeckoStyleCoordConvertible};
 use gecko_bindings::bindings::{Gecko_CreateGradient, Gecko_SetGradientImageValue, Gecko_SetUrlImageValue};
 use gecko_bindings::bindings::{Gecko_InitializeImageCropRect, Gecko_SetImageElement};
 use gecko_bindings::structs::{nsStyleCoord_CalcValue, nsStyleImage};
 use gecko_bindings::structs::{nsresult, SheetType};
 use gecko_bindings::sugar::ns_style_coord::{CoordDataValue, CoordDataMut};
 use stylesheets::{Origin, RulesMutateError};
-use values::computed::{CalcLengthOrPercentage, Gradient, Image, LengthOrPercentage, LengthOrPercentageOrAuto};
+use values::computed::{CalcLengthOrPercentage, Gradient, GradientItem, Image};
+use values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto};
 
 impl From<CalcLengthOrPercentage> for nsStyleCoord_CalcValue {
     fn from(other: CalcLengthOrPercentage) -> nsStyleCoord_CalcValue {
         let has_percentage = other.percentage.is_some();
         nsStyleCoord_CalcValue {
             mLength: other.length.0,
             mPercent: other.percentage.unwrap_or(0.0),
             mHasPercent: has_percentage,
@@ -155,17 +156,17 @@ impl nsStyleImage {
         use gecko_bindings::structs::{NS_STYLE_GRADIENT_SHAPE_LINEAR, NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER};
         use gecko_bindings::structs::{NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE, NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE};
         use gecko_bindings::structs::{NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER, NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE};
         use gecko_bindings::structs::nsStyleCoord;
         use values::computed::{AngleOrCorner, GradientKind, GradientShape, LengthOrKeyword};
         use values::computed::LengthOrPercentageOrKeyword;
         use values::specified::{HorizontalDirection, SizeKeyword, VerticalDirection};
 
-        let stop_count = gradient.stops.len();
+        let stop_count = gradient.items.len();
         if stop_count >= ::std::u32::MAX as usize {
             warn!("stylo: Prevented overflow due to too many gradient stops");
             return;
         }
 
         let gecko_gradient = match gradient.gradient_kind {
             GradientKind::Linear(angle_or_corner) => {
                 let gecko_gradient = unsafe {
@@ -275,42 +276,50 @@ impl nsStyleImage {
                     (*gecko_gradient).mBgPosX.set(position.horizontal.0);
                     (*gecko_gradient).mBgPosY.set(position.vertical.0);
                 }
 
                 gecko_gradient
             },
         };
 
-        for (index, stop) in gradient.stops.iter().enumerate() {
+        for (index, item) in gradient.items.iter().enumerate() {
             // NB: stops are guaranteed to be none in the gecko side by
             // default.
-            let mut coord: nsStyleCoord = nsStyleCoord::null();
-            coord.set(stop.position);
-            let color = match stop.color {
-                CSSColor::CurrentColor => {
-                    // TODO(emilio): gecko just stores an nscolor,
-                    // and it doesn't seem to support currentColor
-                    // as value in a gradient.
-                    //
-                    // Double-check it and either remove
-                    // currentColor for servo or see how gecko
-                    // handles this.
-                    0
-                },
-                CSSColor::RGBA(ref rgba) => convert_rgba_to_nscolor(rgba),
-            };
 
-            let mut stop = unsafe {
+            let mut gecko_stop = unsafe {
                 &mut (*gecko_gradient).mStops[index]
             };
+            let mut coord = nsStyleCoord::null();
 
-            stop.mColor = color;
-            stop.mIsInterpolationHint = false;
-            stop.mLocation.move_from(coord);
+            match *item {
+                GradientItem::ColorStop(ref stop) => {
+                    gecko_stop.mColor = match stop.color {
+                        CSSColor::CurrentColor => {
+                            // TODO(emilio): gecko just stores an nscolor,
+                            // and it doesn't seem to support currentColor
+                            // as value in a gradient.
+                            //
+                            // Double-check it and either remove
+                            // currentColor for servo or see how gecko
+                            // handles this.
+                            0
+                        },
+                        CSSColor::RGBA(ref rgba) => convert_rgba_to_nscolor(rgba),
+                    };
+                    gecko_stop.mIsInterpolationHint = false;
+                    coord.set(stop.position);
+                },
+                GradientItem::InterpolationHint(hint) => {
+                    gecko_stop.mIsInterpolationHint = true;
+                    coord.set(Some(hint));
+                }
+            }
+
+            gecko_stop.mLocation.move_from(coord);
         }
 
         unsafe {
             Gecko_SetGradientImageValue(self, gecko_gradient);
         }
     }
 }
 
--- a/servo/components/style/values/computed/image.rs
+++ b/servo/components/style/values/computed/image.rs
@@ -115,17 +115,17 @@ impl ToCss for Image {
 }
 
 /// Computed values for a CSS gradient.
 /// https://drafts.csswg.org/css-images/#gradients
 #[derive(Clone, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct Gradient {
     /// The color stops.
-    pub stops: Vec<ColorStop>,
+    pub items: Vec<GradientItem>,
     /// True if this is a repeating gradient.
     pub repeating: bool,
     /// Gradient kind can be linear or radial.
     pub gradient_kind: GradientKind,
     /// Compatibility mode.
     pub compat_mode: CompatMode,
 }
 
@@ -144,19 +144,19 @@ impl ToCss for Gradient {
             },
             GradientKind::Radial(ref shape, position) => {
                 try!(dest.write_str("radial-gradient("));
                 try!(shape.to_css(dest));
                 try!(dest.write_str(" at "));
                 try!(position.to_css(dest));
             },
         }
-        for stop in &self.stops {
+        for item in &self.items {
             try!(dest.write_str(", "));
-            try!(stop.to_css(dest));
+            try!(item.to_css(dest));
         }
         try!(dest.write_str(")"));
         Ok(())
     }
 }
 
 impl fmt::Debug for Gradient {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -164,52 +164,52 @@ impl fmt::Debug for Gradient {
             GradientKind::Linear(angle_or_corner) => {
                 let _ = write!(f, "{:?}", angle_or_corner);
             },
             GradientKind::Radial(ref shape, position) => {
                 let _ = write!(f, "{:?} at {:?}", shape, position);
             },
         }
 
-        for stop in &self.stops {
-            let _ = write!(f, ", {:?}", stop);
+        for item in &self.items {
+            let _ = write!(f, ", {:?}", item);
         }
         Ok(())
     }
 }
 
 impl ToComputedValue for specified::Gradient {
     type ComputedValue = Gradient;
 
     #[inline]
     fn to_computed_value(&self, context: &Context) -> Gradient {
         let specified::Gradient {
-            ref stops,
+            ref items,
             repeating,
             ref gradient_kind,
             compat_mode,
         } = *self;
         Gradient {
-            stops: stops.iter().map(|s| s.to_computed_value(context)).collect(),
+            items: items.iter().map(|s| s.to_computed_value(context)).collect(),
             repeating: repeating,
             gradient_kind: gradient_kind.to_computed_value(context),
             compat_mode: compat_mode,
         }
     }
 
     #[inline]
     fn from_computed_value(computed: &Gradient) -> Self {
         let Gradient {
-            ref stops,
+            ref items,
             repeating,
             ref gradient_kind,
             compat_mode,
         } = *computed;
         specified::Gradient {
-            stops: stops.iter().map(ToComputedValue::from_computed_value).collect(),
+            items: items.iter().map(ToComputedValue::from_computed_value).collect(),
             repeating: repeating,
             gradient_kind: ToComputedValue::from_computed_value(gradient_kind),
             compat_mode: compat_mode,
         }
     }
 }
 
 /// Computed values for CSS linear or radial gradients.
@@ -246,16 +246,63 @@ impl ToComputedValue for specified::Grad
             GradientKind::Radial(ref shape, position) => {
                 specified::GradientKind::Radial(ToComputedValue::from_computed_value(shape),
                                                 ToComputedValue::from_computed_value(&position))
             },
         }
     }
 }
 
+/// Specified values for color stops and interpolation hints.
+#[derive(Clone, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub enum GradientItem {
+    /// A color stop.
+    ColorStop(ColorStop),
+    /// An interpolation hint.
+    InterpolationHint(LengthOrPercentage),
+}
+
+impl ToCss for GradientItem {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            GradientItem::ColorStop(stop) => stop.to_css(dest),
+            GradientItem::InterpolationHint(hint) => hint.to_css(dest),
+        }
+    }
+}
+
+impl ToComputedValue for specified::GradientItem {
+    type ComputedValue = GradientItem;
+
+    #[inline]
+    fn to_computed_value(&self, context: &Context) -> GradientItem {
+        match *self {
+            specified::GradientItem::ColorStop(ref stop) => {
+                GradientItem::ColorStop(stop.to_computed_value(context))
+            },
+            specified::GradientItem::InterpolationHint(ref hint) => {
+                GradientItem::InterpolationHint(hint.to_computed_value(context))
+            },
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &GradientItem) -> Self {
+        match *computed {
+            GradientItem::ColorStop(ref stop) => {
+                specified::GradientItem::ColorStop(ToComputedValue::from_computed_value(stop))
+            },
+            GradientItem::InterpolationHint(ref hint) => {
+                specified::GradientItem::InterpolationHint(ToComputedValue::from_computed_value(hint))
+            },
+        }
+    }
+}
+
 /// Computed values for one color stop in a linear gradient.
 /// https://drafts.csswg.org/css-images/#typedef-color-stop-list
 #[derive(Clone, PartialEq, Copy)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct ColorStop {
     /// The color of this stop.
     pub color: CSSColor,
 
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -12,18 +12,18 @@ use std::fmt;
 use style_traits::ToCss;
 use super::{CSSFloat, CSSInteger, RGBA};
 use super::generics::BorderRadiusSize as GenericBorderRadiusSize;
 use super::specified;
 use super::specified::grid::{TrackBreadth as GenericTrackBreadth, TrackSize as GenericTrackSize};
 
 pub use app_units::Au;
 pub use cssparser::Color as CSSColor;
-pub use self::image::{AngleOrCorner, EndingShape as GradientShape, Gradient, GradientKind, Image, ImageRect};
-pub use self::image::{LengthOrKeyword, LengthOrPercentageOrKeyword};
+pub use self::image::{AngleOrCorner, EndingShape as GradientShape, Gradient, GradientItem};
+pub use self::image::{GradientKind, Image, ImageRect, LengthOrKeyword, LengthOrPercentageOrKeyword};
 pub use super::{Auto, Either, None_};
 #[cfg(feature = "gecko")]
 pub use super::specified::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
 pub use super::specified::{BorderStyle, GridLine, Percentage, UrlOrNone};
 pub use super::specified::url::SpecifiedUrl;
 pub use self::length::{CalcLengthOrPercentage, Length, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
 pub use self::length::{LengthOrPercentageOrAutoOrContent, LengthOrPercentageOrNone, LengthOrNone};
 pub use self::length::{MaxLength, MinLength};
--- a/servo/components/style/values/specified/image.rs
+++ b/servo/components/style/values/specified/image.rs
@@ -87,18 +87,18 @@ impl Image {
     }
 }
 
 /// Specified values for a CSS gradient.
 /// https://drafts.csswg.org/css-images/#gradients
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct Gradient {
-    /// The color stops.
-    pub stops: Vec<ColorStop>,
+    /// The color stops and interpolation hints.
+    pub items: Vec<GradientItem>,
     /// True if this is a repeating gradient.
     pub repeating: bool,
     /// Gradients can be linear or radial.
     pub gradient_kind: GradientKind,
     /// Compatibility mode.
     pub compat_mode: CompatMode,
 }
 
@@ -127,44 +127,44 @@ impl ToCss for Gradient {
                     try!(position.to_css(dest));
                 } else {
                     try!(position.to_css(dest));
                     try!(dest.write_str(", "));
                     try!(shape.to_css(dest));
                 }
             },
         }
-        for stop in &self.stops {
+        for item in &self.items {
             if !skipcomma {
                 try!(dest.write_str(", "));
             } else {
                 skipcomma = false;
             }
-            try!(stop.to_css(dest));
+            try!(item.to_css(dest));
         }
         dest.write_str(")")
     }
 }
 
 impl Gradient {
     /// Parses a gradient from the given arguments.
     pub fn parse_function(context: &ParserContext, input: &mut Parser) -> Result<Gradient, ()> {
         fn parse<F>(context: &ParserContext, input: &mut Parser, parse_kind: F)
-                    -> Result<(GradientKind, Vec<ColorStop>), ()>
+                    -> Result<(GradientKind, Vec<GradientItem>), ()>
             where F: FnOnce(&ParserContext, &mut Parser) -> Result<GradientKind, ()>
         {
             input.parse_nested_block(|input| {
                 let kind = try!(parse_kind(context, input));
-                let stops = try!(input.parse_comma_separated(|i| ColorStop::parse(context, i)));
-                Ok((kind, stops))
+                let items = try!(Gradient::parse_items(context, input));
+                Ok((kind, items))
             })
         };
         let mut repeating = false;
         let mut compat_mode = CompatMode::Modern;
-        let (gradient_kind, stops) = match_ignore_ascii_case! { &try!(input.expect_function()),
+        let (gradient_kind, items) = match_ignore_ascii_case! { &try!(input.expect_function()),
             "linear-gradient" => {
                 try!(parse(context, input, GradientKind::parse_modern_linear))
             },
             "-webkit-linear-gradient" => {
                 compat_mode = CompatMode::WebKit;
                 try!(parse(context, input, GradientKind::parse_webkit_linear))
             },
             "repeating-linear-gradient" => {
@@ -190,28 +190,41 @@ impl Gradient {
             "-webkit-repeating-radial-gradient" => {
                 repeating = true;
                 compat_mode = CompatMode::WebKit;
                 try!(parse(context, input, GradientKind::parse_webkit_radial))
             },
             _ => { return Err(()); }
         };
 
-        // https://drafts.csswg.org/css-images/#typedef-color-stop-list
-        if stops.len() < 2 {
-            return Err(())
-        }
-
         Ok(Gradient {
-            stops: stops,
+            items: items,
             repeating: repeating,
             gradient_kind: gradient_kind,
             compat_mode: compat_mode,
         })
     }
+
+    fn parse_items(context: &ParserContext, input: &mut Parser) -> Result<Vec<GradientItem>, ()> {
+        let mut seen_stop = false;
+        let items = try!(input.parse_comma_separated(|input| {
+            if seen_stop {
+                if let Ok(hint) = input.try(|i| LengthOrPercentage::parse(context, i)) {
+                    seen_stop = false;
+                    return Ok(GradientItem::InterpolationHint(hint));
+                }
+            }
+            seen_stop = true;
+            ColorStop::parse(context, input).map(GradientItem::ColorStop)
+        }));
+        if !seen_stop || items.len() < 2 {
+            return Err(());
+        }
+        Ok(items)
+    }
 }
 
 /// Specified values for CSS linear or radial gradients.
 /// https://drafts.csswg.org/css-images/#gradients
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum GradientKind {
     /// A `<linear-gradient()>`:
@@ -484,17 +497,37 @@ impl AngleOrCorner {
                     try!(vertical.to_css(dest));
                 }
                 Ok(())
             }
         }
     }
 }
 
-/// Specified values for one color stop in a linear gradient.
+/// Specified values for color stops and interpolation hints.
+/// https://drafts.csswg.org/css-images-4/#color-stop-syntax
+#[derive(Clone, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub enum GradientItem {
+    /// A color stop.
+    ColorStop(ColorStop),
+    /// An interpolation hint.
+    InterpolationHint(LengthOrPercentage),
+}
+
+impl ToCss for GradientItem {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            GradientItem::ColorStop(ref stop) => stop.to_css(dest),
+            GradientItem::InterpolationHint(ref hint) => hint.to_css(dest),
+        }
+    }
+}
+
+/// Specified values for one color stop in a gradient.
 /// https://drafts.csswg.org/css-images/#typedef-color-stop-list
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct ColorStop {
     /// The color of this stop.
     pub color: CSSColor,
 
     /// The position of this stop. If not specified, this stop is placed halfway between the
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -24,18 +24,18 @@ use super::computed::{self, Context};
 use super::computed::{Shadow as ComputedShadow, ToComputedValue};
 use super::generics::BorderRadiusSize as GenericBorderRadiusSize;
 
 #[cfg(feature = "gecko")]
 pub use self::align::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
 pub use self::color::Color;
 pub use self::grid::{GridLine, TrackKeyword};
 pub use self::image::{AngleOrCorner, ColorStop, EndingShape as GradientEndingShape, Gradient};
-pub use self::image::{GradientKind, HorizontalDirection, Image, ImageRect, LengthOrKeyword};
-pub use self::image::{LengthOrPercentageOrKeyword, SizeKeyword, VerticalDirection};
+pub use self::image::{GradientItem, GradientKind, HorizontalDirection, Image, ImageRect};
+pub use self::image::{LengthOrKeyword, LengthOrPercentageOrKeyword, SizeKeyword, VerticalDirection};
 pub use self::length::AbsoluteLength;
 pub use self::length::{FontRelativeLength, ViewportPercentageLength, CharacterWidth, Length, CalcLengthOrPercentage};
 pub use self::length::{Percentage, LengthOrNone, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
 pub use self::length::{LengthOrPercentageOrNone, LengthOrPercentageOrAutoOrContent, NoCalcLength, CalcUnit};
 pub use self::length::{MaxLength, MinLength};
 pub use self::position::{HorizontalPosition, Position, VerticalPosition};
 
 #[cfg(feature = "gecko")]