Bug 1462829: Refactor vector types. r=hiro,xidorn
authorEmilio Cobos Álvarez <emilio@crisal.io>
Fri, 18 May 2018 22:06:25 +0200
changeset 419159 f6141abe76bf0408172099006ae8e8d39a2454a9
parent 419158 b1cccafd336d8d49669fba317f1284eafac11cd0
child 419160 bead6c2d07ca623c19edc88219d3a9315ebe6b51
push id34029
push usershindli@mozilla.com
push dateMon, 21 May 2018 21:30:22 +0000
treeherdermozilla-central@51f2535c7974 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershiro, xidorn
bugs1462829
milestone62.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
Bug 1462829: Refactor vector types. r=hiro,xidorn This fixes clamping of mask-size and moves it out of mako while at it. MozReview-Commit-ID: 9hiTe63odna
servo/components/style/properties/data.py
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/helpers.mako.rs
servo/components/style/properties/helpers/animated_properties.mako.rs
servo/components/style/properties/longhand/background.mako.rs
servo/components/style/properties/longhand/effects.mako.rs
servo/components/style/properties/longhand/inherited_text.mako.rs
servo/components/style/properties/longhand/svg.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/properties/shorthand/mask.mako.rs
servo/components/style/values/animated/effects.rs
servo/components/style/values/animated/mod.rs
servo/components/style/values/computed/background.rs
servo/components/style/values/computed/length.rs
servo/components/style/values/computed/percentage.rs
servo/components/style/values/generics/background.rs
servo/components/style/values/generics/svg.rs
servo/components/style/values/specified/background.rs
--- a/servo/components/style/properties/data.py
+++ b/servo/components/style/properties/data.py
@@ -159,17 +159,17 @@ def parse_property_aliases(alias_list):
 class Longhand(object):
     def __init__(self, style_struct, name, spec=None, animation_value_type=None, keyword=None,
                  predefined_type=None, servo_pref=None, gecko_pref=None,
                  enabled_in="content", need_index=False,
                  gecko_ffi_name=None,
                  allowed_in_keyframe_block=True, cast_type='u8',
                  logical=False, alias=None, extra_prefixes=None, boxed=False,
                  flags=None, allowed_in_page_rule=False, allow_quirks=False, ignored_when_colors_disabled=False,
-                 vector=False, need_animatable=False, servo_restyle_damage="repaint"):
+                 vector=False, servo_restyle_damage="repaint"):
         self.name = name
         if not spec:
             raise TypeError("Spec should be specified for %s" % name)
         self.spec = spec
         self.keyword = keyword
         self.predefined_type = predefined_type
         self.ident = to_rust_ident(name)
         self.camel_case = to_camel_case(self.ident)
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -3243,17 +3243,17 @@ fn static_assert() {
     }
 
     pub fn reset_scroll_snap_coordinate(&mut self, other: &Self) {
         self.copy_scroll_snap_coordinate_from(other)
     }
 
     pub fn clone_scroll_snap_coordinate(&self) -> longhands::scroll_snap_coordinate::computed_value::T {
         let vec = self.gecko.mScrollSnapCoordinate.iter().map(|f| f.into()).collect();
-        longhands::scroll_snap_coordinate::computed_value::T(vec)
+        longhands::scroll_snap_coordinate::computed_value::List(vec)
     }
 
     ${impl_css_url('_moz_binding', 'mBinding')}
 
     ${impl_transition_time_value('delay', 'Delay')}
     ${impl_transition_time_value('duration', 'Duration')}
     ${impl_transition_timing_function()}
 
@@ -3749,18 +3749,19 @@ fn static_assert() {
         style_struct = next(x for x in data.style_structs if x.name == struct_name)
         longhand = next(x for x in style_struct.longhands if x.ident == ident)
         keyword = longhand.keyword
     %>
 
     <% copy_simple_image_array_property(name, shorthand, layer_field_name, field_name) %>
 
     pub fn set_${ident}<I>(&mut self, v: I)
-        where I: IntoIterator<Item=longhands::${ident}::computed_value::single_value::T>,
-              I::IntoIter: ExactSizeIterator
+    where
+        I: IntoIterator<Item=longhands::${ident}::computed_value::single_value::T>,
+        I::IntoIter: ExactSizeIterator,
     {
         use properties::longhands::${ident}::single_value::computed_value::T as Keyword;
         use gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType;
 
         let v = v.into_iter();
 
         unsafe {
           Gecko_EnsureImageLayersLength(&mut self.gecko.${layer_field_name}, v.len(),
@@ -3785,17 +3786,17 @@ fn static_assert() {
 
         % if keyword.needs_cast():
         % for value in keyword.values_for('gecko'):
         const ${keyword.casted_constant_name(value, "u8")} : u8 =
             structs::${keyword.gecko_constant(value)} as u8;
         % endfor
         % endif
 
-        longhands::${ident}::computed_value::T (
+        longhands::${ident}::computed_value::List(
             self.gecko.${layer_field_name}.mLayers.iter()
                 .take(self.gecko.${layer_field_name}.${field_name}Count as usize)
                 .map(|ref layer| {
                     match layer.${field_name} {
                         % for value in longhand.keyword.values_for("gecko"):
                         % if keyword.needs_cast():
                         ${keyword.casted_constant_name(value, "u8")}
                         % else:
@@ -3852,17 +3853,17 @@ fn static_assert() {
                 StyleImageLayerRepeat::Repeat => BackgroundRepeatKeyword::Repeat,
                 StyleImageLayerRepeat::Space => BackgroundRepeatKeyword::Space,
                 StyleImageLayerRepeat::Round => BackgroundRepeatKeyword::Round,
                 StyleImageLayerRepeat::NoRepeat => BackgroundRepeatKeyword::NoRepeat,
                 _ => panic!("Found unexpected value in style struct for ${shorthand}_repeat property"),
             }
         }
 
-        longhands::${shorthand}_repeat::computed_value::T (
+        longhands::${shorthand}_repeat::computed_value::List(
             self.gecko.${image_layers_field}.mLayers.iter()
                 .take(self.gecko.${image_layers_field}.mRepeatCount as usize)
                 .map(|ref layer| {
                     T(to_servo(layer.mRepeat.mXRepeat), to_servo(layer.mRepeat.mYRepeat))
                 }).collect()
         )
     }
 
@@ -3891,17 +3892,17 @@ fn static_assert() {
     }
 
     pub fn reset_${shorthand}_position_${orientation}(&mut self, other: &Self) {
         self.copy_${shorthand}_position_${orientation}_from(other)
     }
 
     pub fn clone_${shorthand}_position_${orientation}(&self)
         -> longhands::${shorthand}_position_${orientation}::computed_value::T {
-        longhands::${shorthand}_position_${orientation}::computed_value::T(
+        longhands::${shorthand}_position_${orientation}::computed_value::List(
             self.gecko.${image_layers_field}.mLayers.iter()
                 .take(self.gecko.${image_layers_field}.mPosition${orientation.upper()}Count as usize)
                 .map(|position| position.mPosition.m${orientation.upper()}Position.into())
                 .collect()
         )
     }
 
     pub fn set_${shorthand}_position_${orientation[0]}<I>(&mut self,
@@ -3935,21 +3936,21 @@ fn static_assert() {
 
         let mut width = nsStyleCoord_CalcValue::new();
         let mut height = nsStyleCoord_CalcValue::new();
 
         let (w_type, h_type) = match servo {
             BackgroundSize::Explicit { width: explicit_width, height: explicit_height } => {
                 let mut w_type = nsStyleImageLayers_Size_DimensionType::eAuto;
                 let mut h_type = nsStyleImageLayers_Size_DimensionType::eAuto;
-                if let Some(w) = explicit_width.to_calc_value() {
+                if let Some(w) = explicit_width.0.to_calc_value() {
                     width = w;
                     w_type = nsStyleImageLayers_Size_DimensionType::eLengthPercentage;
                 }
-                if let Some(h) = explicit_height.to_calc_value() {
+                if let Some(h) = explicit_height.0.to_calc_value() {
                     height = h;
                     h_type = nsStyleImageLayers_Size_DimensionType::eLengthPercentage;
                 }
                 (w_type, h_type)
             }
             BackgroundSize::Cover => {
                 (
                     nsStyleImageLayers_Size_DimensionType::eCover,
@@ -3967,32 +3968,33 @@ fn static_assert() {
         nsStyleImageLayers_Size {
             mWidth: nsStyleImageLayers_Size_Dimension { _base: width },
             mHeight: nsStyleImageLayers_Size_Dimension { _base: height },
             mWidthType: w_type as u8,
             mHeightType: h_type as u8,
         }
     </%self:simple_image_array_property>
 
-    pub fn clone_${shorthand}_size(&self) -> longhands::background_size::computed_value::T {
+    pub fn clone_${shorthand}_size(&self) -> longhands::${shorthand}_size::computed_value::T {
         use gecko_bindings::structs::nsStyleCoord_CalcValue as CalcValue;
         use gecko_bindings::structs::nsStyleImageLayers_Size_DimensionType as DimensionType;
-        use values::computed::LengthOrPercentageOrAuto;
+        use values::generics::NonNegative;
+        use values::computed::NonNegativeLengthOrPercentageOrAuto;
         use values::generics::background::BackgroundSize;
 
-        fn to_servo(value: CalcValue, ty: u8) -> LengthOrPercentageOrAuto {
+        fn to_servo(value: CalcValue, ty: u8) -> NonNegativeLengthOrPercentageOrAuto {
             if ty == DimensionType::eAuto as u8 {
-                LengthOrPercentageOrAuto::Auto
+                NonNegativeLengthOrPercentageOrAuto::auto()
             } else {
                 debug_assert_eq!(ty, DimensionType::eLengthPercentage as u8);
-                value.into()
+                NonNegative(value.into())
             }
         }
 
-        longhands::background_size::computed_value::T(
+        longhands::${shorthand}_size::computed_value::List(
             self.gecko.${image_layers_field}.mLayers.iter().map(|ref layer| {
                 if DimensionType::eCover as u8 == layer.mSize.mWidthType {
                     debug_assert_eq!(layer.mSize.mHeightType, DimensionType::eCover as u8);
                     return BackgroundSize::Cover
                 }
                 if DimensionType::eContain as u8 == layer.mSize.mWidthType {
                     debug_assert_eq!(layer.mSize.mHeightType, DimensionType::eContain as u8);
                     return BackgroundSize::Contain
@@ -4053,17 +4055,17 @@ fn static_assert() {
                 geckoimage.mImage.set(image)
             }
         }
     }
 
     pub fn clone_${shorthand}_image(&self) -> longhands::${shorthand}_image::computed_value::T {
         use values::None_;
 
-        longhands::${shorthand}_image::computed_value::T(
+        longhands::${shorthand}_image::computed_value::List(
             self.gecko.${image_layers_field}.mLayers.iter()
                 .take(self.gecko.${image_layers_field}.mImageCount as usize)
                 .map(|ref layer| {
                     match unsafe { layer.mImage.into_image() } {
                         Some(image) => Either::Second(image),
                         None => Either::First(None_),
                     }
             }).collect()
@@ -4309,17 +4311,17 @@ fn static_assert() {
     }
 
     pub fn reset_box_shadow(&mut self, other: &Self) {
         self.copy_box_shadow_from(other)
     }
 
     pub fn clone_box_shadow(&self) -> longhands::box_shadow::computed_value::T {
         let buf = self.gecko.mBoxShadow.iter().map(|v| v.to_box_shadow()).collect();
-        longhands::box_shadow::computed_value::T(buf)
+        longhands::box_shadow::computed_value::List(buf)
     }
 
     pub fn set_clip(&mut self, v: longhands::clip::computed_value::T) {
         use gecko_bindings::structs::NS_STYLE_CLIP_AUTO;
         use gecko_bindings::structs::NS_STYLE_CLIP_RECT;
         use gecko_bindings::structs::NS_STYLE_CLIP_LEFT_AUTO;
         use gecko_bindings::structs::NS_STYLE_CLIP_TOP_AUTO;
         use gecko_bindings::structs::NS_STYLE_CLIP_RIGHT_AUTO;
@@ -4554,17 +4556,17 @@ fn static_assert() {
                     filters.push(Filter::Url(unsafe {
                         let url = RefPtr::new(*filter.__bindgen_anon_1.mURL.as_ref());
                         ComputedUrl::from_url_value(url)
                     }));
                 }
                 _ => {},
             }
         }
-        longhands::filter::computed_value::T(filters)
+        longhands::filter::computed_value::List(filters)
     }
 
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="InheritedBox"
                   skip_longhands="image-orientation">
     // FIXME: Gecko uses a tricky way to store computed value of image-orientation
     //        within an u8. We could inline following glue codes by implementing all
@@ -4675,17 +4677,17 @@ fn static_assert() {
     }
 
     pub fn reset_text_shadow(&mut self, other: &Self) {
         self.copy_text_shadow_from(other)
     }
 
     pub fn clone_text_shadow(&self) -> longhands::text_shadow::computed_value::T {
         let buf = self.gecko.mTextShadow.iter().map(|v| v.to_simple_shadow()).collect();
-        longhands::text_shadow::computed_value::T(buf)
+        longhands::text_shadow::computed_value::List(buf)
     }
 
     pub fn set_line_height(&mut self, v: longhands::line_height::computed_value::T) {
         use values::generics::text::LineHeight;
         // FIXME: Align binary representations and ditch |match| for cast + static_asserts
         let en = match v {
             LineHeight::Normal => CoordDataValue::Normal,
             LineHeight::Length(val) => CoordDataValue::Coord(val.0.to_i32_au()),
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -3,17 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 <%!
     from data import Keyword, to_rust_ident, to_camel_case
     from data import LOGICAL_SIDES, PHYSICAL_SIDES, LOGICAL_SIZES, SYSTEM_FONT_LONGHANDS
 %>
 
 <%def name="predefined_type(name, type, initial_value, parse_method='parse',
-            needs_context=True, vector=False, computed_type=None, initial_specified_value=None,
+            needs_context=True, vector=False,
+            computed_type=None, initial_specified_value=None,
             allow_quirks=False, allow_empty=False, **kwargs)">
     <%def name="predefined_type_inner(name, type, initial_value, parse_method)">
         #[allow(unused_imports)]
         use app_units::Au;
         #[allow(unused_imports)]
         use cssparser::{Color as CSSParserColor, RGBA};
         #[allow(unused_imports)]
         use values::specified::AllowQuirks;
@@ -72,20 +73,22 @@
 <%doc>
     To be used in cases where we have a grammar like "<thing> [ , <thing> ]*".
 
     Setting allow_empty to False allows for cases where the vector
     is empty. The grammar for these is usually "none | <thing> [ , <thing> ]*".
     We assume that the default/initial value is an empty vector for these.
     `initial_value` need not be defined for these.
 </%doc>
-<%def name="vector_longhand(name, animation_value_type=None, allow_empty=False, separator='Comma',
-                            need_animatable=False, **kwargs)">
+<%def name="vector_longhand(name, animation_value_type=None,
+                            vector_animation_type=None, allow_empty=False,
+                            separator='Comma',
+                            **kwargs)">
     <%call expr="longhand(name, animation_value_type=animation_value_type, vector=True,
-                          need_animatable=need_animatable, **kwargs)">
+                          **kwargs)">
         #[allow(unused_imports)]
         use smallvec::SmallVec;
 
         pub mod single_value {
             #[allow(unused_imports)]
             use cssparser::{Parser, BasicParseError};
             #[allow(unused_imports)]
             use parser::{Parse, ParserContext};
@@ -110,46 +113,78 @@
             pub use self::single_value::T as SingleComputedValue;
             % if allow_empty and allow_empty != "NotInitial":
             use std::vec::IntoIter;
             % else:
             use smallvec::{IntoIter, SmallVec};
             % endif
             use values::computed::ComputedVecIter;
 
-            /// The computed value, effectively a list of single values.
+            /// The generic type defining the value for this property.
             % if separator == "Comma":
             #[css(comma)]
             % endif
-            #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
-            % if need_animatable or animation_value_type == "ComputedValue":
-            #[derive(Animate, ComputeSquaredDistance)]
-            % endif
-            pub struct T(
+            #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue,
+                     ToCss)]
+            pub struct List<T>(
                 % if not allow_empty:
                 #[css(iterable)]
                 % else:
                 #[css(if_empty = "none", iterable)]
                 % endif
                 % if allow_empty and allow_empty != "NotInitial":
-                pub Vec<single_value::T>,
+                pub Vec<T>,
                 % else:
-                pub SmallVec<[single_value::T; 1]>,
+                pub SmallVec<[T; 1]>,
                 % endif
             );
 
-            % if need_animatable or animation_value_type == "ComputedValue":
-                use values::animated::{ToAnimatedZero};
+
+            /// The computed value, effectively a list of single values.
+            % if vector_animation_type:
+            % if not animation_value_type:
+                Sorry, this is stupid but needed for now.
+            % endif
+
+            use properties::animated_properties::ListAnimation;
+            use values::animated::{Animate, ToAnimatedValue, ToAnimatedZero, Procedure};
+            use values::distance::{SquaredDistance, ComputeSquaredDistance};
+
+            // FIXME(emilio): For some reason rust thinks that this alias is
+            // unused, even though it's clearly used below?
+            #[allow(unused)]
+            type AnimatedList = <List<single_value::T> as ToAnimatedValue>::AnimatedValue;
+
+            impl ToAnimatedZero for AnimatedList {
+                fn to_animated_zero(&self) -> Result<Self, ()> { Err(()) }
+            }
 
-                impl ToAnimatedZero for T {
-                    #[inline]
-                    fn to_animated_zero(&self) -> Result<Self, ()> { Err(()) }
+            impl Animate for AnimatedList {
+                fn animate(
+                    &self,
+                    other: &Self,
+                    procedure: Procedure,
+                ) -> Result<Self, ()> {
+                    Ok(List(
+                        self.0.animate_${vector_animation_type}(&other.0, procedure)?
+                    ))
                 }
+            }
+            impl ComputeSquaredDistance for AnimatedList {
+                fn compute_squared_distance(
+                    &self,
+                    other: &Self,
+                ) -> Result<SquaredDistance, ()> {
+                    self.0.squared_distance_${vector_animation_type}(&other.0)
+                }
+            }
             % endif
 
+            pub type T = List<single_value::T>;
+
             pub type Iter<'a, 'cx, 'cx_a> = ComputedVecIter<'a, 'cx, 'cx_a, super::single_value::SpecifiedValue>;
 
             impl IntoIterator for T {
                 type Item = single_value::T;
                 % if allow_empty and allow_empty != "NotInitial":
                 type IntoIter = IntoIter<single_value::T>;
                 % else:
                 type IntoIter = IntoIter<[single_value::T; 1]>;
@@ -171,54 +206,58 @@
             % else:
             #[css(if_empty = "none", iterable)]
             % endif
             pub Vec<single_value::SpecifiedValue>,
         );
 
         pub fn get_initial_value() -> computed_value::T {
             % if allow_empty and allow_empty != "NotInitial":
-                computed_value::T(vec![])
+                computed_value::List(vec![])
             % else:
                 let mut v = SmallVec::new();
                 v.push(single_value::get_initial_value());
-                computed_value::T(v)
+                computed_value::List(v)
             % endif
         }
 
-        pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
-                                -> Result<SpecifiedValue, ParseError<'i>> {
+        pub fn parse<'i, 't>(
+            context: &ParserContext,
+            input: &mut Parser<'i, 't>,
+        ) -> Result<SpecifiedValue, ParseError<'i>> {
             use style_traits::Separator;
 
             % if allow_empty:
                 if input.try(|input| input.expect_ident_matching("none")).is_ok() {
                     return Ok(SpecifiedValue(Vec::new()))
                 }
             % endif
 
             ::style_traits::${separator}::parse(input, |parser| {
                 single_value::parse(context, parser)
             }).map(SpecifiedValue)
         }
 
         pub use self::single_value::SpecifiedValue as SingleSpecifiedValue;
 
         impl SpecifiedValue {
-            pub fn compute_iter<'a, 'cx, 'cx_a>(&'a self, context: &'cx Context<'cx_a>)
-                -> computed_value::Iter<'a, 'cx, 'cx_a> {
+            pub fn compute_iter<'a, 'cx, 'cx_a>(
+                &'a self,
+                context: &'cx Context<'cx_a>,
+            ) -> computed_value::Iter<'a, 'cx, 'cx_a> {
                 computed_value::Iter::new(context, &self.0)
             }
         }
 
         impl ToComputedValue for SpecifiedValue {
             type ComputedValue = computed_value::T;
 
             #[inline]
             fn to_computed_value(&self, context: &Context) -> computed_value::T {
-                computed_value::T(self.compute_iter(context).collect())
+                computed_value::List(self.compute_iter(context).collect())
             }
 
             #[inline]
             fn from_computed_value(computed: &computed_value::T) -> Self {
                 SpecifiedValue(computed.0.iter()
                                     .map(ToComputedValue::from_computed_value)
                                     .collect())
             }
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -24,21 +24,20 @@ use properties::PropertyId;
 use properties::{LonghandId, ShorthandId};
 use servo_arc::Arc;
 use smallvec::SmallVec;
 use std::{cmp, ptr};
 use std::mem::{self, ManuallyDrop};
 #[cfg(feature = "gecko")] use hash::FnvHashMap;
 use style_traits::{KeywordsCollectFn, ParseError, SpecifiedValueInfo};
 use super::ComputedValues;
-use values::{CSSFloat, CustomIdent, Either};
+use values::{CSSFloat, CustomIdent};
 use values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero};
 use values::animated::color::RGBA as AnimatedRGBA;
 use values::animated::effects::Filter as AnimatedFilter;
-use values::animated::effects::FilterList as AnimatedFilterList;
 use values::computed::{Angle, CalcLengthOrPercentage};
 use values::computed::{ClipRect, Context};
 use values::computed::{Length, LengthOrPercentage, LengthOrPercentageOrAuto};
 use values::computed::{LengthOrPercentageOrNone, MaxLength};
 use values::computed::{NonNegativeNumber, Number, NumberOrPercentage, Percentage};
 use values::computed::length::NonNegativeLengthOrPercentage;
 use values::computed::ToComputedValue;
 use values::computed::transform::{DirectionVector, Matrix, Matrix3D};
@@ -48,23 +47,20 @@ use values::computed::transform::Rotate 
 use values::computed::transform::Translate as ComputedTranslate;
 use values::computed::transform::Scale as ComputedScale;
 use values::computed::url::ComputedUrl;
 use values::generics::transform::{self, Rotate, Translate, Scale, Transform, TransformOperation};
 use values::distance::{ComputeSquaredDistance, SquaredDistance};
 use values::generics::font::{FontSettings as GenericFontSettings, FontTag, VariationValue};
 use values::computed::font::FontVariationSettings;
 use values::generics::effects::Filter;
-use values::generics::position as generic_position;
 use values::generics::svg::{SVGLength,  SvgLengthOrPercentageOrNumber, SVGPaint};
 use values::generics::svg::{SVGPaintKind, SVGStrokeDashArray, SVGOpacity};
 use void::{self, Void};
 
-/// <https://drafts.csswg.org/css-transitions/#animtype-repeatable-list>
-pub trait RepeatableListAnimatable: Animate {}
 
 /// Returns true if this nsCSSPropertyID is one of the animatable properties.
 #[cfg(feature = "gecko")]
 pub fn nscsspropertyid_is_animatable(property: nsCSSPropertyID) -> bool {
     match property {
         % for prop in data.longhands + data.shorthands_except_all():
             % if prop.animatable:
                 ${prop.nscsspropertyid()} => true,
@@ -750,60 +746,135 @@ impl ToAnimatedZero for AnimationValue {
             },
             % endif
             % endfor
             _ => Err(()),
         }
     }
 }
 
-impl RepeatableListAnimatable for LengthOrPercentage {}
-impl RepeatableListAnimatable for Either<f32, LengthOrPercentage> {}
-impl RepeatableListAnimatable for Either<NonNegativeNumber, NonNegativeLengthOrPercentage> {}
-impl RepeatableListAnimatable for SvgLengthOrPercentageOrNumber<LengthOrPercentage, Number> {}
+/// A trait to abstract away the different kind of animations over a list that
+/// there may be.
+pub trait ListAnimation<T> : Sized {
+    /// <https://drafts.csswg.org/css-transitions/#animtype-repeatable-list>
+    fn animate_repeatable_list(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>
+    where
+        T: Animate;
+
+    /// <https://drafts.csswg.org/css-transitions/#animtype-repeatable-list>
+    fn squared_distance_repeatable_list(&self, other: &Self) -> Result<SquaredDistance, ()>
+    where
+        T: ComputeSquaredDistance;
+
+    /// This is the animation used for some of the types like shadows and
+    /// filters, where the interpolation happens with the zero value if one of
+    /// the sides is not present.
+    fn animate_with_zero(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>
+    where
+        T: Animate + Clone + ToAnimatedZero;
 
-macro_rules! repeated_vec_impl {
-    ($($ty:ty),*) => {
-        $(impl<T> Animate for $ty
-        where
-            T: RepeatableListAnimatable,
-        {
-            fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
+    /// This is the animation used for some of the types like shadows and
+    /// filters, where the interpolation happens with the zero value if one of
+    /// the sides is not present.
+    fn squared_distance_with_zero(&self, other: &Self) -> Result<SquaredDistance, ()>
+    where
+        T: ToAnimatedZero + ComputeSquaredDistance;
+}
+
+macro_rules! animated_list_impl {
+    (<$t:ident> for $ty:ty) => {
+        impl<$t> ListAnimation<$t> for $ty {
+            fn animate_repeatable_list(
+                &self,
+                other: &Self,
+                procedure: Procedure,
+            ) -> Result<Self, ()>
+            where
+                T: Animate,
+            {
                 // If the length of either list is zero, the least common multiple is undefined.
                 if self.is_empty() || other.is_empty() {
                     return Err(());
                 }
                 use num_integer::lcm;
                 let len = lcm(self.len(), other.len());
                 self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(this, other)| {
                     this.animate(other, procedure)
                 }).collect()
             }
-        }
 
-        impl<T> ComputeSquaredDistance for $ty
-        where
-            T: ComputeSquaredDistance + RepeatableListAnimatable,
-        {
-            #[inline]
-            fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
+            fn squared_distance_repeatable_list(
+                &self,
+                other: &Self,
+            ) -> Result<SquaredDistance, ()>
+            where
+                T: ComputeSquaredDistance,
+            {
                 if self.is_empty() || other.is_empty() {
                     return Err(());
                 }
                 use num_integer::lcm;
                 let len = lcm(self.len(), other.len());
                 self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(this, other)| {
                     this.compute_squared_distance(other)
                 }).sum()
             }
-        })*
-    };
+
+            fn animate_with_zero(
+                &self,
+                other: &Self,
+                procedure: Procedure,
+            ) -> Result<Self, ()>
+            where
+                T: Animate + Clone + ToAnimatedZero
+            {
+                if procedure == Procedure::Add {
+                    return Ok(
+                        self.iter().chain(other.iter()).cloned().collect()
+                    );
+                }
+                self.iter().zip_longest(other.iter()).map(|it| {
+                    match it {
+                        EitherOrBoth::Both(this, other) => {
+                            this.animate(other, procedure)
+                        },
+                        EitherOrBoth::Left(this) => {
+                            this.animate(&this.to_animated_zero()?, procedure)
+                        },
+                        EitherOrBoth::Right(other) => {
+                            other.to_animated_zero()?.animate(other, procedure)
+                        }
+                    }
+                }).collect()
+            }
+
+            fn squared_distance_with_zero(
+                &self,
+                other: &Self,
+            ) -> Result<SquaredDistance, ()>
+            where
+                T: ToAnimatedZero + ComputeSquaredDistance
+            {
+                self.iter().zip_longest(other.iter()).map(|it| {
+                    match it {
+                        EitherOrBoth::Both(this, other) => {
+                            this.compute_squared_distance(other)
+                        },
+                        EitherOrBoth::Left(list) | EitherOrBoth::Right(list) => {
+                            list.to_animated_zero()?.compute_squared_distance(list)
+                        },
+                    }
+                }).sum()
+            }
+        }
+    }
 }
 
-repeated_vec_impl!(SmallVec<[T; 1]>, Vec<T>);
+animated_list_impl!(<T> for SmallVec<[T; 1]>);
+animated_list_impl!(<T> for Vec<T>);
 
 /// <https://drafts.csswg.org/css-transitions/#animtype-visibility>
 impl Animate for Visibility {
     #[inline]
     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
         let (this_weight, other_weight) = procedure.weights();
         match (*self, *other) {
             (Visibility::Visible, _) => {
@@ -1022,19 +1093,16 @@ impl<'a> Iterator for FontSettingTagIter
         ) {
             (Some(at), Some(bt)) if at.tag == bt.tag => Some(Ok((at, bt))),
             (None, None) => None,
             _ => Some(Err(())), // Mismatch number of unique tags or tag names.
         }
     }
 }
 
-impl<H, V> RepeatableListAnimatable for generic_position::Position<H, V>
-    where H: RepeatableListAnimatable, V: RepeatableListAnimatable {}
-
 /// <https://drafts.csswg.org/css-transitions/#animtype-rect>
 impl Animate for ClipRect {
     #[inline]
     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
         use values::computed::Length;
         let animate_component = |this: &Option<Length>, other: &Option<Length>| {
             match (this.animate(other, procedure)?, procedure) {
                 (None, Procedure::Interpolate { .. }) => Ok(None),
@@ -2663,37 +2731,26 @@ impl ComputeSquaredDistance for Computed
             _ => Err(()),
         }
     }
 }
 
 impl ComputeSquaredDistance for ComputedTransform {
     #[inline]
     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
-        let list1 = &self.0;
-        let list2 = &other.0;
+        let squared_dist = self.0.squared_distance_with_zero(&other.0);
 
-        let squared_dist: Result<SquaredDistance, _> = list1.iter().zip_longest(list2).map(|it| {
-            match it {
-                EitherOrBoth::Both(this, other) => {
-                    this.compute_squared_distance(other)
-                },
-                EitherOrBoth::Left(list) | EitherOrBoth::Right(list) => {
-                    list.to_animated_zero()?.compute_squared_distance(list)
-                },
-            }
-        }).sum();
-
-        // Roll back to matrix interpolation if there is any Err(()) in the transform lists, such
-        // as mismatched transform functions.
-        if let Err(_) = squared_dist {
+        // Roll back to matrix interpolation if there is any Err(()) in the
+        // transform lists, such as mismatched transform functions.
+        if squared_dist.is_err() {
             let matrix1: Matrix3D = self.to_transform_3d_matrix(None)?.0.into();
             let matrix2: Matrix3D = other.to_transform_3d_matrix(None)?.0.into();
             return matrix1.compute_squared_distance(&matrix2);
         }
+
         squared_dist
     }
 }
 
 /// Animated SVGPaint
 pub type IntermediateSVGPaint = SVGPaint<AnimatedRGBA, ComputedUrl>;
 
 /// Animated SVGPaintKind
@@ -2823,27 +2880,42 @@ where
             _ => Err(()),
         }
     }
 }
 
 /// <https://www.w3.org/TR/SVG11/painting.html#StrokeDasharrayProperty>
 impl<L> Animate for SVGStrokeDashArray<L>
 where
-    L: Clone + RepeatableListAnimatable,
+    L: Clone + Animate,
 {
     #[inline]
     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
         if matches!(procedure, Procedure::Add | Procedure::Accumulate { .. }) {
             // Non-additive.
             return Err(());
         }
         match (self, other) {
             (&SVGStrokeDashArray::Values(ref this), &SVGStrokeDashArray::Values(ref other)) => {
-                Ok(SVGStrokeDashArray::Values(this.animate(other, procedure)?))
+                Ok(SVGStrokeDashArray::Values(this.animate_repeatable_list(other, procedure)?))
+            },
+            _ => Err(()),
+        }
+    }
+}
+
+impl<L> ComputeSquaredDistance for SVGStrokeDashArray<L>
+where
+    L: ComputeSquaredDistance,
+{
+    #[inline]
+    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
+        match (self, other) {
+            (&SVGStrokeDashArray::Values(ref this), &SVGStrokeDashArray::Values(ref other)) => {
+                this.squared_distance_repeatable_list(other)
             },
             _ => Err(()),
         }
     }
 }
 
 impl<L> ToAnimatedZero for SVGStrokeDashArray<L>
 where
@@ -2924,60 +2996,16 @@ impl ToAnimatedZero for AnimatedFilter {
             % if product == "gecko":
             Filter::DropShadow(ref this) => Ok(Filter::DropShadow(this.to_animated_zero()?)),
             % endif
             _ => Err(()),
         }
     }
 }
 
-impl Animate for AnimatedFilterList {
-    #[inline]
-    fn animate(
-        &self,
-        other: &Self,
-        procedure: Procedure,
-    ) -> Result<Self, ()> {
-        if procedure == Procedure::Add {
-            return Ok(AnimatedFilterList(
-                self.0.iter().chain(other.0.iter()).cloned().collect(),
-            ));
-        }
-        Ok(AnimatedFilterList(self.0.iter().zip_longest(other.0.iter()).map(|it| {
-            match it {
-                EitherOrBoth::Both(this, other) => {
-                    this.animate(other, procedure)
-                },
-                EitherOrBoth::Left(this) => {
-                    this.animate(&this.to_animated_zero()?, procedure)
-                },
-                EitherOrBoth::Right(other) => {
-                    other.to_animated_zero()?.animate(other, procedure)
-                },
-            }
-        }).collect::<Result<Vec<_>, _>>()?))
-    }
-}
-
-impl ComputeSquaredDistance for AnimatedFilterList {
-    #[inline]
-    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
-        self.0.iter().zip_longest(other.0.iter()).map(|it| {
-            match it {
-                EitherOrBoth::Both(this, other) => {
-                    this.compute_squared_distance(other)
-                },
-                EitherOrBoth::Left(list) | EitherOrBoth::Right(list) => {
-                    list.to_animated_zero()?.compute_squared_distance(list)
-                },
-            }
-        }).sum()
-    }
-}
-
 /// A comparator to sort PropertyIds such that longhands are sorted before shorthands,
 /// shorthands with fewer components are sorted before shorthands with more components,
 /// and otherwise shorthands are sorted by IDL name as defined by [Web Animations][property-order].
 ///
 /// Using this allows us to prioritize values specified by longhands (or smaller
 /// shorthand subsets) when longhands and shorthands are both specified on the one keyframe.
 ///
 /// Example orderings that result from this:
--- a/servo/components/style/properties/longhand/background.mako.rs
+++ b/servo/components/style/properties/longhand/background.mako.rs
@@ -31,16 +31,17 @@
     ${helpers.predefined_type(
         "background-position-" + axis,
         "position::" + direction + "Position",
         initial_value="computed::LengthOrPercentage::zero()",
         initial_specified_value="SpecifiedValue::initial_specified_value()",
         spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-" + axis,
         animation_value_type="ComputedValue",
         vector=True,
+        vector_animation_type="repeatable_list",
         flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
     )}
 % endfor
 
 ${helpers.predefined_type(
     "background-repeat",
     "BackgroundRepeat",
     "computed::BackgroundRepeat::repeat()",
@@ -71,23 +72,25 @@
 ${helpers.single_keyword("background-origin",
                          "padding-box border-box content-box",
                          vector=True, extra_prefixes="webkit",
                          gecko_enum_prefix="StyleGeometryBox",
                          spec="https://drafts.csswg.org/css-backgrounds/#the-background-origin",
                          animation_value_type="discrete",
                          flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER")}
 
-${helpers.predefined_type("background-size", "BackgroundSize",
+${helpers.predefined_type(
+    "background-size",
+    "BackgroundSize",
     initial_value="computed::BackgroundSize::auto()",
     initial_specified_value="specified::BackgroundSize::auto()",
     spec="https://drafts.csswg.org/css-backgrounds/#the-background-size",
     vector=True,
+    vector_animation_type="repeatable_list",
     animation_value_type="BackgroundSizeList",
-    need_animatable=True,
     flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
     extra_prefixes="webkit")}
 
 // https://drafts.fxtf.org/compositing/#background-blend-mode
 ${helpers.single_keyword("background-blend-mode",
                          """normal multiply screen overlay darken lighten color-dodge
                             color-burn hard-light soft-light difference exclusion hue
                             saturation color luminosity""",
--- a/servo/components/style/properties/longhand/effects.mako.rs
+++ b/servo/components/style/properties/longhand/effects.mako.rs
@@ -19,16 +19,17 @@
 )}
 
 ${helpers.predefined_type(
     "box-shadow",
     "BoxShadow",
     None,
     vector=True,
     animation_value_type="AnimatedBoxShadowList",
+    vector_animation_type="with_zero",
     extra_prefixes="webkit",
     ignored_when_colors_disabled=True,
     flags="APPLIES_TO_FIRST_LETTER",
     spec="https://drafts.csswg.org/css-backgrounds/#box-shadow",
 )}
 
 ${helpers.predefined_type("clip",
                           "ClipRectOrAuto",
@@ -40,16 +41,17 @@
 
 ${helpers.predefined_type(
     "filter",
     "Filter",
     None,
     vector=True,
     separator="Space",
     animation_value_type="AnimatedFilterList",
+    vector_animation_type="with_zero",
     extra_prefixes="webkit",
     flags="CREATES_STACKING_CONTEXT FIXPOS_CB",
     spec="https://drafts.fxtf.org/filters/#propdef-filter",
 )}
 
 ${helpers.single_keyword("mix-blend-mode",
                          """normal multiply screen overlay darken lighten color-dodge
                             color-burn hard-light soft-light difference exclusion hue
--- a/servo/components/style/properties/longhand/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_text.mako.rs
@@ -187,16 +187,17 @@
     % endif
 </%helpers:single_keyword>
 
 ${helpers.predefined_type(
     "text-shadow",
     "SimpleShadow",
     None,
     vector=True,
+    vector_animation_type="with_zero",
     animation_value_type="AnimatedTextShadowList",
     ignored_when_colors_disabled=True,
     flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
     spec="https://drafts.csswg.org/css-text-decor-3/#text-shadow-property",
 )}
 
 ${helpers.predefined_type(
     "text-emphasis-style",
--- a/servo/components/style/properties/longhand/svg.mako.rs
+++ b/servo/components/style/properties/longhand/svg.mako.rs
@@ -98,16 +98,17 @@
         "mask-position-" + axis,
         "position::" + direction + "Position",
         products="gecko",
         extra_prefixes="webkit",
         initial_value="computed::LengthOrPercentage::zero()",
         initial_specified_value="specified::PositionComponent::Center",
         spec="https://drafts.fxtf.org/css-masking/#propdef-mask-position",
         animation_value_type="ComputedValue",
+        vector_animation_type="repeatable_list",
         vector=True,
     )}
 % endfor
 
 ${helpers.single_keyword("mask-clip",
                          "border-box content-box padding-box",
                          extra_gecko_values="fill-box stroke-box view-box no-clip",
                          vector=True,
@@ -121,36 +122,28 @@
                          "border-box content-box padding-box",
                          extra_gecko_values="fill-box stroke-box view-box",
                          vector=True,
                          products="gecko",
                          extra_prefixes="webkit",
                          gecko_enum_prefix="StyleGeometryBox",
                          animation_value_type="discrete",
                          spec="https://drafts.fxtf.org/css-masking/#propdef-mask-origin")}
-
-<%helpers:longhand name="mask-size" products="gecko" animation_value_type="ComputedValue" extra_prefixes="webkit"
-                   spec="https://drafts.fxtf.org/css-masking/#propdef-mask-size">
-    use properties::longhands::background_size;
-    pub use ::properties::longhands::background_size::SpecifiedValue;
-    pub use ::properties::longhands::background_size::single_value as single_value;
-    pub use ::properties::longhands::background_size::computed_value as computed_value;
-
-    #[inline]
-    pub fn get_initial_value() -> computed_value::T {
-        background_size::get_initial_value()
-    }
-
-    pub fn parse<'i, 't>(
-        context: &ParserContext,
-        input: &mut Parser<'i, 't>,
-    ) -> Result<SpecifiedValue, ParseError<'i>> {
-        background_size::parse(context, input)
-    }
-</%helpers:longhand>
+${helpers.predefined_type(
+    "mask-size",
+    "background::BackgroundSize",
+    "computed::BackgroundSize::auto()",
+    initial_specified_value="specified::BackgroundSize::auto()",
+    products="gecko",
+    extra_prefixes="webkit",
+    spec="https://drafts.fxtf.org/css-masking/#propdef-mask-size",
+    animation_value_type="MaskSizeList",
+    vector=True,
+    vector_animation_type="repeatable_list",
+)}
 
 ${helpers.single_keyword("mask-composite",
                          "add subtract intersect exclude",
                          vector=True,
                          products="gecko",
                          extra_prefixes="webkit",
                          animation_value_type="discrete",
                          spec="https://drafts.fxtf.org/css-masking/#propdef-mask-composite")}
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -2211,22 +2211,23 @@ pub mod style_structs {
                 % if longhand.logical:
                     ${helpers.logical_setter(name=longhand.name)}
                 % else:
                     % if longhand.is_vector:
                         /// Set ${longhand.name}.
                         #[allow(non_snake_case)]
                         #[inline]
                         pub fn set_${longhand.ident}<I>(&mut self, v: I)
-                            where I: IntoIterator<Item = longhands::${longhand.ident}
-                                                                  ::computed_value::single_value::T>,
-                                  I::IntoIter: ExactSizeIterator
+                        where
+                            I: IntoIterator<Item = longhands::${longhand.ident}
+                                                              ::computed_value::single_value::T>,
+                            I::IntoIter: ExactSizeIterator
                         {
                             self.${longhand.ident} = longhands::${longhand.ident}::computed_value
-                                                              ::T(v.into_iter().collect());
+                                                              ::List(v.into_iter().collect());
                         }
                     % elif longhand.ident == "display":
                         /// Set `display`.
                         ///
                         /// We need to keep track of the original display for hypothetical boxes,
                         /// so we need to special-case this.
                         #[allow(non_snake_case)]
                         #[inline]
@@ -2399,17 +2400,17 @@ pub mod style_structs {
 
                 /// Clone the computed value for the property.
                 #[allow(non_snake_case)]
                 #[inline]
                 #[cfg(feature = "gecko")]
                 pub fn clone_${longhand.ident}(
                     &self,
                 ) -> longhands::${longhand.ident}::computed_value::T {
-                    longhands::${longhand.ident}::computed_value::T(
+                    longhands::${longhand.ident}::computed_value::List(
                         self.${longhand.ident}_iter().collect()
                     )
                 }
             % endif
         % endfor
 
         % if style_struct.name == "Box":
             /// Returns whether there is any animation specified with
--- a/servo/components/style/properties/shorthand/mask.mako.rs
+++ b/servo/components/style/properties/shorthand/mask.mako.rs
@@ -183,34 +183,37 @@
 
 <%helpers:shorthand name="mask-position" products="gecko" extra_prefixes="webkit"
                     sub_properties="mask-position-x mask-position-y"
                     spec="https://drafts.csswg.org/css-masks-4/#the-mask-position">
     use properties::longhands::{mask_position_x,mask_position_y};
     use values::specified::position::Position;
     use parser::Parse;
 
-    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
-                               -> Result<Longhands, ParseError<'i>> {
+    pub fn parse_value<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Longhands, ParseError<'i>> {
         // Vec grows from 0 to 4 by default on first push().  So allocate with
         // capacity 1, so in the common case of only one item we don't way
         // overallocate.  Note that we always push at least one item if parsing
         // succeeds.
         let mut position_x = mask_position_x::SpecifiedValue(Vec::with_capacity(1));
         let mut position_y = mask_position_y::SpecifiedValue(Vec::with_capacity(1));
         let mut any = false;
 
         input.parse_comma_separated(|input| {
             let value = Position::parse(context, input)?;
             position_x.0.push(value.horizontal);
             position_y.0.push(value.vertical);
             any = true;
             Ok(())
         })?;
-        if any == false {
+
+        if !any {
             return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
         }
 
         Ok(expanded! {
             mask_position_x: position_x,
             mask_position_y: position_y,
         })
     }
--- a/servo/components/style/values/animated/effects.rs
+++ b/servo/components/style/values/animated/effects.rs
@@ -1,166 +1,42 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Animated types for CSS values related to effects.
 
-use properties::longhands::box_shadow::computed_value::T as ComputedBoxShadowList;
-use properties::longhands::filter::computed_value::T as ComputedFilterList;
-use properties::longhands::text_shadow::computed_value::T as ComputedTextShadowList;
-use std::cmp;
 #[cfg(not(feature = "gecko"))]
 use values::Impossible;
-use values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero};
 use values::animated::color::RGBA;
 use values::computed::{Angle, Number};
 use values::computed::length::Length;
 #[cfg(feature = "gecko")]
 use values::computed::url::ComputedUrl;
 use values::distance::{ComputeSquaredDistance, SquaredDistance};
 use values::generics::effects::BoxShadow as GenericBoxShadow;
 use values::generics::effects::Filter as GenericFilter;
 use values::generics::effects::SimpleShadow as GenericSimpleShadow;
 
-/// An animated value for the `box-shadow` property.
-pub type BoxShadowList = ShadowList<BoxShadow>;
-
-/// An animated value for the `text-shadow` property.
-pub type TextShadowList = ShadowList<SimpleShadow>;
-
-/// An animated value for shadow lists.
-///
-/// <https://drafts.csswg.org/css-transitions/#animtype-shadow-list>
-#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
-#[derive(Clone, Debug, PartialEq)]
-pub struct ShadowList<Shadow>(Vec<Shadow>);
-
 /// An animated value for a single `box-shadow`.
 pub type BoxShadow = GenericBoxShadow<Option<RGBA>, Length, Length, Length>;
 
-/// An animated value for the `filter` property.
-#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
-#[derive(Clone, Debug, PartialEq)]
-pub struct FilterList(pub Vec<Filter>);
-
 /// An animated value for a single `filter`.
 #[cfg(feature = "gecko")]
 pub type Filter = GenericFilter<Angle, Number, Length, SimpleShadow, ComputedUrl>;
 
 /// An animated value for a single `filter`.
 #[cfg(not(feature = "gecko"))]
 pub type Filter = GenericFilter<Angle, Number, Length, Impossible, Impossible>;
 
 /// An animated value for the `drop-shadow()` filter.
 pub type SimpleShadow = GenericSimpleShadow<Option<RGBA>, Length, Length>;
 
-impl ToAnimatedValue for ComputedBoxShadowList {
-    type AnimatedValue = BoxShadowList;
-
-    #[inline]
-    fn to_animated_value(self) -> Self::AnimatedValue {
-        ShadowList(self.0.to_animated_value())
-    }
-
-    #[inline]
-    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
-        ComputedBoxShadowList(ToAnimatedValue::from_animated_value(animated.0))
-    }
-}
-
-impl<S> Animate for ShadowList<S>
-where
-    S: Animate + Clone + ToAnimatedZero,
-{
-    #[inline]
-    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
-        if procedure == Procedure::Add {
-            return Ok(ShadowList(self.0.iter().chain(&other.0).cloned().collect()));
-        }
-        // FIXME(nox): Use itertools here, to avoid the need for `unreachable!`.
-        let max_len = cmp::max(self.0.len(), other.0.len());
-        let mut shadows = Vec::with_capacity(max_len);
-        for i in 0..max_len {
-            shadows.push(match (self.0.get(i), other.0.get(i)) {
-                (Some(shadow), Some(other)) => shadow.animate(other, procedure)?,
-                (Some(shadow), None) => shadow.animate(&shadow.to_animated_zero()?, procedure)?,
-                (None, Some(shadow)) => shadow.to_animated_zero()?.animate(shadow, procedure)?,
-                (None, None) => unreachable!(),
-            });
-        }
-        Ok(ShadowList(shadows))
-    }
-}
-
-impl<S> ComputeSquaredDistance for ShadowList<S>
-where
-    S: ComputeSquaredDistance + ToAnimatedZero,
-{
-    #[inline]
-    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
-        use itertools::{EitherOrBoth, Itertools};
-
-        self.0
-            .iter()
-            .zip_longest(other.0.iter())
-            .map(|it| match it {
-                EitherOrBoth::Both(from, to) => from.compute_squared_distance(to),
-                EitherOrBoth::Left(list) | EitherOrBoth::Right(list) => {
-                    list.compute_squared_distance(&list.to_animated_zero()?)
-                },
-            })
-            .sum()
-    }
-}
-
-impl<S> ToAnimatedZero for ShadowList<S> {
-    #[inline]
-    fn to_animated_zero(&self) -> Result<Self, ()> {
-        Ok(ShadowList(vec![]))
-    }
-}
-
-impl ToAnimatedValue for ComputedTextShadowList {
-    type AnimatedValue = TextShadowList;
-
-    #[inline]
-    fn to_animated_value(self) -> Self::AnimatedValue {
-        ShadowList(self.0.to_animated_value())
-    }
-
-    #[inline]
-    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
-        ComputedTextShadowList(ToAnimatedValue::from_animated_value(animated.0))
-    }
-}
-
 impl ComputeSquaredDistance for BoxShadow {
     #[inline]
     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
         if self.inset != other.inset {
             return Err(());
         }
         Ok(self.base.compute_squared_distance(&other.base)? +
             self.spread.compute_squared_distance(&other.spread)?)
     }
 }
-
-impl ToAnimatedValue for ComputedFilterList {
-    type AnimatedValue = FilterList;
-
-    #[inline]
-    fn to_animated_value(self) -> Self::AnimatedValue {
-        FilterList(self.0.to_animated_value())
-    }
-
-    #[inline]
-    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
-        ComputedFilterList(ToAnimatedValue::from_animated_value(animated.0))
-    }
-}
-
-impl ToAnimatedZero for FilterList {
-    #[inline]
-    fn to_animated_zero(&self) -> Result<Self, ()> {
-        Ok(FilterList(vec![]))
-    }
-}
--- a/servo/components/style/values/animated/mod.rs
+++ b/servo/components/style/values/animated/mod.rs
@@ -6,16 +6,17 @@
 //!
 //! Some values, notably colors, cannot be interpolated directly with their
 //! computed values and need yet another intermediate representation. This
 //! module's raison d'ĂȘtre is to ultimately contain all these types.
 
 use app_units::Au;
 use euclid::{Point2D, Size2D};
 use smallvec::SmallVec;
+use values::computed::length::CalcLengthOrPercentage;
 use values::computed::Angle as ComputedAngle;
 use values::computed::BorderCornerRadius as ComputedBorderCornerRadius;
 use values::computed::MaxLength as ComputedMaxLength;
 use values::computed::MozLength as ComputedMozLength;
 use values::computed::url::ComputedUrl;
 
 pub mod color;
 pub mod effects;
@@ -252,16 +253,17 @@ macro_rules! trivial_to_animated_value {
             fn from_animated_value(animated: Self::AnimatedValue) -> Self {
                 animated
             }
         }
     };
 }
 
 trivial_to_animated_value!(Au);
+trivial_to_animated_value!(CalcLengthOrPercentage);
 trivial_to_animated_value!(ComputedAngle);
 trivial_to_animated_value!(ComputedUrl);
 trivial_to_animated_value!(bool);
 trivial_to_animated_value!(f32);
 
 impl ToAnimatedValue for ComputedBorderCornerRadius {
     type AnimatedValue = Self;
 
--- a/servo/components/style/values/computed/background.rs
+++ b/servo/components/style/values/computed/background.rs
@@ -1,93 +1,35 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Computed types for CSS values related to backgrounds.
 
-use properties::animated_properties::RepeatableListAnimatable;
-use properties::longhands::background_size::computed_value::T as BackgroundSizeList;
 use std::fmt::{self, Write};
 use style_traits::{CssWriter, ToCss};
-use values::animated::{ToAnimatedValue, ToAnimatedZero};
 use values::computed::{Context, ToComputedValue};
-use values::computed::length::LengthOrPercentageOrAuto;
+use values::computed::length::NonNegativeLengthOrPercentageOrAuto;
 use values::generics::background::BackgroundSize as GenericBackgroundSize;
 use values::specified::background::BackgroundRepeat as SpecifiedBackgroundRepeat;
 use values::specified::background::BackgroundRepeatKeyword;
 
 /// A computed value for the `background-size` property.
-pub type BackgroundSize = GenericBackgroundSize<LengthOrPercentageOrAuto>;
+pub type BackgroundSize = GenericBackgroundSize<NonNegativeLengthOrPercentageOrAuto>;
 
 impl BackgroundSize {
     /// Returns `auto auto`.
     pub fn auto() -> Self {
         GenericBackgroundSize::Explicit {
-            width: LengthOrPercentageOrAuto::Auto,
-            height: LengthOrPercentageOrAuto::Auto,
+            width: NonNegativeLengthOrPercentageOrAuto::auto(),
+            height: NonNegativeLengthOrPercentageOrAuto::auto(),
         }
     }
 }
 
-impl RepeatableListAnimatable for BackgroundSize {}
-
-impl ToAnimatedZero for BackgroundSize {
-    #[inline]
-    fn to_animated_zero(&self) -> Result<Self, ()> {
-        Err(())
-    }
-}
-
-impl ToAnimatedValue for BackgroundSize {
-    type AnimatedValue = Self;
-
-    #[inline]
-    fn to_animated_value(self) -> Self {
-        self
-    }
-
-    #[inline]
-    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
-        use values::computed::{Length, Percentage};
-        let clamp_animated_value = |value: LengthOrPercentageOrAuto| -> LengthOrPercentageOrAuto {
-            match value {
-                LengthOrPercentageOrAuto::Length(len) => {
-                    LengthOrPercentageOrAuto::Length(Length::new(len.px().max(0.)))
-                },
-                LengthOrPercentageOrAuto::Percentage(percent) => {
-                    LengthOrPercentageOrAuto::Percentage(Percentage(percent.0.max(0.)))
-                },
-                _ => value,
-            }
-        };
-        match animated {
-            GenericBackgroundSize::Explicit { width, height } => GenericBackgroundSize::Explicit {
-                width: clamp_animated_value(width),
-                height: clamp_animated_value(height),
-            },
-            _ => animated,
-        }
-    }
-}
-
-impl ToAnimatedValue for BackgroundSizeList {
-    type AnimatedValue = Self;
-
-    #[inline]
-    fn to_animated_value(self) -> Self {
-        self
-    }
-
-    #[inline]
-    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
-        BackgroundSizeList(ToAnimatedValue::from_animated_value(animated.0))
-    }
-}
-
 /// The computed value of the `background-repeat` property:
 ///
 /// https://drafts.csswg.org/css-backgrounds/#the-background-repeat
 #[derive(Clone, Debug, MallocSizeOf, PartialEq)]
 pub struct BackgroundRepeat(pub BackgroundRepeatKeyword, pub BackgroundRepeatKeyword);
 
 impl BackgroundRepeat {
     /// Returns the `repeat repeat` value.
--- a/servo/components/style/values/computed/length.rs
+++ b/servo/components/style/values/computed/length.rs
@@ -319,18 +319,18 @@ impl ToComputedValue for specified::Calc
             ..Default::default()
         }
     }
 }
 
 #[allow(missing_docs)]
 #[animate(fallback = "Self::animate_fallback")]
 #[css(derive_debug)]
-#[derive(Animate, Clone, ComputeSquaredDistance, Copy, MallocSizeOf, PartialEq, ToAnimatedZero,
-         ToCss)]
+#[derive(Animate, Clone, ComputeSquaredDistance, Copy, MallocSizeOf, PartialEq,
+         ToAnimatedValue, ToAnimatedZero, ToCss)]
 #[distance(fallback = "Self::compute_squared_distance_fallback")]
 pub enum LengthOrPercentage {
     Length(Length),
     Percentage(Percentage),
     Calc(CalcLengthOrPercentage),
 }
 
 impl LengthOrPercentage {
--- a/servo/components/style/values/computed/percentage.rs
+++ b/servo/components/style/values/computed/percentage.rs
@@ -9,17 +9,17 @@ use style_traits::{CssWriter, ToCss};
 use values::{serialize_percentage, CSSFloat};
 use values::animated::ToAnimatedValue;
 use values::generics::NonNegative;
 
 /// A computed percentage.
 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
 #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, Default,
          MallocSizeOf, PartialEq, PartialOrd, SpecifiedValueInfo,
-         ToAnimatedZero, ToComputedValue)]
+         ToAnimatedValue, ToAnimatedZero, ToComputedValue)]
 pub struct Percentage(pub CSSFloat);
 
 impl Percentage {
     /// 0%
     #[inline]
     pub fn zero() -> Self {
         Percentage(0.)
     }
--- a/servo/components/style/values/generics/background.rs
+++ b/servo/components/style/values/generics/background.rs
@@ -1,17 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Generic types for CSS values related to backgrounds.
 
 /// A generic value for the `background-size` property.
 #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf,
-         PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss)]
+         PartialEq, SpecifiedValueInfo, ToAnimatedValue, ToAnimatedZero,
+         ToComputedValue, ToCss)]
 pub enum BackgroundSize<LengthOrPercentageOrAuto> {
     /// `<width> <height>`
     Explicit {
         /// Explicit width.
         width: LengthOrPercentageOrAuto,
         /// Explicit height.
         height: LengthOrPercentageOrAuto,
     },
--- a/servo/components/style/values/generics/svg.rs
+++ b/servo/components/style/values/generics/svg.rs
@@ -197,24 +197,23 @@ impl<LengthOrPercentageType: Parse, Numb
 pub enum SVGLength<LengthType> {
     /// `<length> | <percentage> | <number>`
     Length(LengthType),
     /// `context-value`
     ContextValue,
 }
 
 /// Generic value for stroke-dasharray.
-#[derive(Clone, ComputeSquaredDistance, Debug, MallocSizeOf, PartialEq,
+#[derive(Clone, Debug, MallocSizeOf, PartialEq,
          SpecifiedValueInfo, ToAnimatedValue, ToComputedValue, ToCss)]
 pub enum SVGStrokeDashArray<LengthType> {
     /// `[ <length> | <percentage> | <number> ]#`
     #[css(comma)]
     Values(
         #[css(if_empty = "none", iterable)]
-        #[distance(field_bound)]
         Vec<LengthType>,
     ),
     /// `context-value`
     ContextValue,
 }
 
 /// An SVG opacity value accepts `context-{fill,stroke}-opacity` in
 /// addition to opacity value.
--- a/servo/components/style/values/specified/background.rs
+++ b/servo/components/style/values/specified/background.rs
@@ -4,50 +4,45 @@
 
 //! Specified types for CSS values related to backgrounds.
 
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
 use selectors::parser::SelectorParseErrorKind;
 use style_traits::ParseError;
 use values::generics::background::BackgroundSize as GenericBackgroundSize;
-use values::specified::length::LengthOrPercentageOrAuto;
+use values::specified::length::NonNegativeLengthOrPercentageOrAuto;
 
 /// A specified value for the `background-size` property.
-pub type BackgroundSize = GenericBackgroundSize<LengthOrPercentageOrAuto>;
+pub type BackgroundSize = GenericBackgroundSize<NonNegativeLengthOrPercentageOrAuto>;
 
 impl Parse for BackgroundSize {
     fn parse<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
     ) -> Result<Self, ParseError<'i>> {
-        if let Ok(width) = input.try(|i| LengthOrPercentageOrAuto::parse_non_negative(context, i)) {
+        if let Ok(width) = input.try(|i| NonNegativeLengthOrPercentageOrAuto::parse(context, i)) {
             let height = input
-                .try(|i| LengthOrPercentageOrAuto::parse_non_negative(context, i))
-                .unwrap_or(LengthOrPercentageOrAuto::Auto);
+                .try(|i| NonNegativeLengthOrPercentageOrAuto::parse(context, i))
+                .unwrap_or(NonNegativeLengthOrPercentageOrAuto::auto());
             return Ok(GenericBackgroundSize::Explicit { width, height });
         }
-        let location = input.current_source_location();
-        let ident = input.expect_ident()?;
-        (match_ignore_ascii_case! { &ident,
-            "cover" => Ok(GenericBackgroundSize::Cover),
-            "contain" => Ok(GenericBackgroundSize::Contain),
-            _ => Err(()),
-        }).map_err(|()| {
-            location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))
+        Ok(try_match_ident_ignore_ascii_case! { input,
+            "cover" => GenericBackgroundSize::Cover,
+            "contain" => GenericBackgroundSize::Contain,
         })
     }
 }
 
 impl BackgroundSize {
     /// Returns `auto auto`.
     pub fn auto() -> Self {
         GenericBackgroundSize::Explicit {
-            width: LengthOrPercentageOrAuto::Auto,
-            height: LengthOrPercentageOrAuto::Auto,
+            width: NonNegativeLengthOrPercentageOrAuto::auto(),
+            height: NonNegativeLengthOrPercentageOrAuto::auto(),
         }
     }
 }
 
 /// One of the keywords for `background-repeat`.
 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq,
          SpecifiedValueInfo, ToComputedValue, ToCss)]
 #[allow(missing_docs)]