servo: Merge #17530 - Improve sequence values in style (from servo:derive-all-the-things); r=emilio
authorAnthony Ramine <n.oxyde@gmail.com>
Tue, 27 Jun 2017 15:34:15 -0700
changeset 366375 b79b4644a3fe8fc168ed023d3de5479437063b2c
parent 366374 d1382e1bc996b196ef7b449f9f8da12b3f898337
child 366376 74b19063d4e2590cc59693d8f80dd231e7814e93
push id91947
push usercbook@mozilla.com
push dateWed, 28 Jun 2017 11:46:07 +0000
treeherdermozilla-inbound@e9c0fbb55f86 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
milestone56.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 #17530 - Improve sequence values in style (from servo:derive-all-the-things); r=emilio Source-Repo: https://github.com/servo/servo Source-Revision: de0ee6cebfcaad720cd3568b19d2992349c8825c
servo/components/gfx/display_list/mod.rs
servo/components/layout/display_list_builder.rs
servo/components/layout/webrender_helpers.rs
servo/components/style/counter_style/mod.rs
servo/components/style/font_face.rs
servo/components/style/parser.rs
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/effects.mako.rs
servo/components/style/properties/longhand/inherited_svg.mako.rs
servo/components/style/values/animated/effects.rs
servo/components/style/values/computed/effects.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/generics/effects.rs
servo/components/style/values/generics/mod.rs
servo/components/style/values/mod.rs
servo/components/style/values/specified/effects.rs
servo/components/style/values/specified/mod.rs
servo/components/style_traits/lib.rs
servo/components/style_traits/values.rs
--- a/servo/components/gfx/display_list/mod.rs
+++ b/servo/components/gfx/display_list/mod.rs
@@ -23,17 +23,18 @@ use ipc_channel::ipc::IpcSharedMemory;
 use msg::constellation_msg::PipelineId;
 use net_traits::image::base::{Image, PixelFormat};
 use range::Range;
 use servo_geometry::max_rect;
 use std::cmp::{self, Ordering};
 use std::collections::HashMap;
 use std::fmt;
 use std::sync::Arc;
-use style::computed_values::{border_style, filter, image_rendering};
+use style::computed_values::{border_style, image_rendering};
+use style::values::computed::Filter;
 use style_traits::cursor::Cursor;
 use text::TextRun;
 use text::glyph::ByteIndex;
 use webrender_traits::{self, ClipId, ColorF, GradientStop, MixBlendMode, ScrollPolicy, TransformStyle, WebGLContextId};
 
 pub use style::dom::OpaqueNode;
 
 /// The factor that we multiply the blur radius by in order to inflate the boundaries of display
@@ -414,17 +415,17 @@ pub struct StackingContext {
 
     /// The overflow rect for this stacking context in its coordinate system.
     pub overflow: Rect<Au>,
 
     /// The `z-index` for this stacking context.
     pub z_index: i32,
 
     /// CSS filters to be applied to this stacking context (including opacity).
-    pub filters: filter::T,
+    pub filters: Vec<Filter>,
 
     /// The blend mode with which this stacking context blends with its backdrop.
     pub mix_blend_mode: MixBlendMode,
 
     /// A transform to be applied to this stacking context.
     pub transform: Option<Transform3D<f32>>,
 
     /// The transform style of this stacking context.
@@ -443,17 +444,17 @@ pub struct StackingContext {
 impl StackingContext {
     /// Creates a new stacking context.
     #[inline]
     pub fn new(id: StackingContextId,
                context_type: StackingContextType,
                bounds: &Rect<Au>,
                overflow: &Rect<Au>,
                z_index: i32,
-               filters: filter::T,
+               filters: Vec<Filter>,
                mix_blend_mode: MixBlendMode,
                transform: Option<Transform3D<f32>>,
                transform_style: TransformStyle,
                perspective: Option<Transform3D<f32>>,
                scroll_policy: ScrollPolicy,
                parent_scroll_id: ClipId)
                -> StackingContext {
         StackingContext {
@@ -474,17 +475,17 @@ impl StackingContext {
 
     #[inline]
     pub fn root(pipeline_id: PipelineId) -> StackingContext {
         StackingContext::new(StackingContextId::root(),
                              StackingContextType::Real,
                              &Rect::zero(),
                              &Rect::zero(),
                              0,
-                             filter::T::none(),
+                             vec![],
                              MixBlendMode::Normal,
                              None,
                              TransformStyle::Flat,
                              None,
                              ScrollPolicy::Scrollable,
                              pipeline_id.root_scroll_node())
     }
 
--- a/servo/components/layout/display_list_builder.rs
+++ b/servo/components/layout/display_list_builder.rs
@@ -2005,17 +2005,17 @@ impl FragmentDisplayListBuilding for Fra
         // from our flow origin, since that is what `BaseFlow::overflow` is relative to.
         let border_box_offset =
             border_box.translate(&-base_flow.stacking_relative_position).origin;
         // Then, using that, compute our overflow region relative to our border box.
         let overflow = base_flow.overflow.paint.translate(&-border_box_offset.to_vector());
 
         // Create the filter pipeline.
         let effects = self.style().get_effects();
-        let mut filters = effects.filter.clone().0.into_vec();
+        let mut filters = effects.filter.0.clone();
         if effects.opacity != 1.0 {
             filters.push(Filter::Opacity(effects.opacity))
         }
 
         let context_type = match mode {
             StackingContextCreationMode::PseudoFloat => StackingContextType::PseudoFloat,
             StackingContextCreationMode::PseudoPositioned => StackingContextType::PseudoPositioned,
             _ => StackingContextType::Real,
--- a/servo/components/layout/webrender_helpers.rs
+++ b/servo/components/layout/webrender_helpers.rs
@@ -8,19 +8,18 @@
 //           completely converting layout to directly generate WebRender display lists, for example.
 
 use app_units::Au;
 use euclid::{Point2D, Vector2D, Rect, SideOffsets2D, Size2D};
 use gfx::display_list::{BorderDetails, BorderRadii, BoxShadowClipMode, ClippingRegion};
 use gfx::display_list::{DisplayItem, DisplayList, DisplayListTraversal, StackingContextType};
 use msg::constellation_msg::PipelineId;
 use style::computed_values::{image_rendering, mix_blend_mode, transform_style};
-use style::computed_values::filter;
-use style::values::computed::BorderStyle;
-use style::values::generics::effects::Filter;
+use style::values::computed::{BorderStyle, Filter};
+use style::values::generics::effects::Filter as GenericFilter;
 use webrender_traits::{self, DisplayListBuilder, ExtendMode};
 use webrender_traits::{LayoutTransform, ClipId, ClipRegionToken};
 
 pub trait WebRenderDisplayListConverter {
     fn convert_to_webrender(&self, pipeline_id: PipelineId) -> DisplayListBuilder;
 }
 
 trait WebRenderDisplayItemConverter {
@@ -197,31 +196,31 @@ impl ToImageRendering for image_renderin
         }
     }
 }
 
 trait ToFilterOps {
     fn to_filter_ops(&self) -> Vec<webrender_traits::FilterOp>;
 }
 
-impl ToFilterOps for filter::T {
+impl ToFilterOps for Vec<Filter> {
     fn to_filter_ops(&self) -> Vec<webrender_traits::FilterOp> {
-        let mut result = Vec::with_capacity(self.0.len());
-        for filter in self.0.iter() {
+        let mut result = Vec::with_capacity(self.len());
+        for filter in self.iter() {
             match *filter {
-                Filter::Blur(radius) => result.push(webrender_traits::FilterOp::Blur(radius)),
-                Filter::Brightness(amount) => result.push(webrender_traits::FilterOp::Brightness(amount)),
-                Filter::Contrast(amount) => result.push(webrender_traits::FilterOp::Contrast(amount)),
-                Filter::Grayscale(amount) => result.push(webrender_traits::FilterOp::Grayscale(amount)),
-                Filter::HueRotate(angle) => result.push(webrender_traits::FilterOp::HueRotate(angle.radians())),
-                Filter::Invert(amount) => result.push(webrender_traits::FilterOp::Invert(amount)),
-                Filter::Opacity(amount) => result.push(webrender_traits::FilterOp::Opacity(amount.into())),
-                Filter::Saturate(amount) => result.push(webrender_traits::FilterOp::Saturate(amount)),
-                Filter::Sepia(amount) => result.push(webrender_traits::FilterOp::Sepia(amount)),
-                Filter::DropShadow(ref shadow) => match *shadow {},
+                GenericFilter::Blur(radius) => result.push(webrender_traits::FilterOp::Blur(radius)),
+                GenericFilter::Brightness(amount) => result.push(webrender_traits::FilterOp::Brightness(amount)),
+                GenericFilter::Contrast(amount) => result.push(webrender_traits::FilterOp::Contrast(amount)),
+                GenericFilter::Grayscale(amount) => result.push(webrender_traits::FilterOp::Grayscale(amount)),
+                GenericFilter::HueRotate(angle) => result.push(webrender_traits::FilterOp::HueRotate(angle.radians())),
+                GenericFilter::Invert(amount) => result.push(webrender_traits::FilterOp::Invert(amount)),
+                GenericFilter::Opacity(amount) => result.push(webrender_traits::FilterOp::Opacity(amount.into())),
+                GenericFilter::Saturate(amount) => result.push(webrender_traits::FilterOp::Saturate(amount)),
+                GenericFilter::Sepia(amount) => result.push(webrender_traits::FilterOp::Sepia(amount)),
+                GenericFilter::DropShadow(ref shadow) => match *shadow {},
             }
         }
         result
     }
 }
 
 pub trait ToTransformStyle {
     fn to_transform_style(&self) -> webrender_traits::TransformStyle;
--- a/servo/components/style/counter_style/mod.rs
+++ b/servo/components/style/counter_style/mod.rs
@@ -14,17 +14,17 @@ use error_reporting::ContextualParseErro
 #[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSCounterDesc;
 use parser::{ParserContext, log_css_error, Parse};
 use selectors::parser::SelectorParseError;
 use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
 use std::ascii::AsciiExt;
 use std::borrow::Cow;
 use std::fmt;
 use std::ops::Range;
-use style_traits::{ToCss, OneOrMoreSeparated, CommaSeparator, ParseError, StyleParseError};
+use style_traits::{Comma, OneOrMoreSeparated, ParseError, StyleParseError, ToCss};
 use values::CustomIdent;
 
 /// Parse the prelude of an @counter-style rule
 pub fn parse_counter_style_name<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CustomIdent, ParseError<'i>> {
     macro_rules! predefined {
         ($($name: expr,)+) => {
             {
                 ascii_case_insensitive_phf_map! {
@@ -548,17 +548,17 @@ impl Parse for AdditiveSymbols {
 pub struct AdditiveTuple {
     /// <integer>
     pub weight: u32,
     /// <symbol>
     pub symbol: Symbol,
 }
 
 impl OneOrMoreSeparated for AdditiveTuple {
-    type S = CommaSeparator;
+    type S = Comma;
 }
 
 impl Parse for AdditiveTuple {
     fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         let symbol = input.try(|input| Symbol::parse(context, input));
         let weight = input.expect_integer()?;
         if weight < 0 {
             return Err(StyleParseError::UnspecifiedError.into())
--- a/servo/components/style/font_face.rs
+++ b/servo/components/style/font_face.rs
@@ -17,32 +17,32 @@ use error_reporting::ContextualParseErro
 #[cfg(feature = "gecko")] use gecko_bindings::structs::CSSFontFaceDescriptors;
 #[cfg(feature = "gecko")] use cssparser::UnicodeRange;
 use parser::{ParserContext, log_css_error, Parse};
 #[cfg(feature = "gecko")]
 use properties::longhands::font_language_override;
 use selectors::parser::SelectorParseError;
 use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
 use std::fmt;
-use style_traits::{ToCss, OneOrMoreSeparated, CommaSeparator, ParseError, StyleParseError};
+use style_traits::{Comma, OneOrMoreSeparated, ParseError, StyleParseError, ToCss};
 use values::specified::url::SpecifiedUrl;
 
 /// A source for a font-face rule.
 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
 #[derive(Clone, Debug, Eq, PartialEq, ToCss)]
 pub enum Source {
     /// A `url()` source.
     Url(UrlSource),
     /// A `local()` source.
     #[css(function)]
     Local(FamilyName),
 }
 
 impl OneOrMoreSeparated for Source {
-    type S = CommaSeparator;
+    type S = Comma;
 }
 
 /// A `UrlSource` represents a font-face source that has been specified with a
 /// `url()` function.
 ///
 /// https://drafts.csswg.org/css-fonts/#src-desc
 #[derive(Clone, Debug, PartialEq, Eq)]
 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
--- a/servo/components/style/parser.rs
+++ b/servo/components/style/parser.rs
@@ -2,17 +2,17 @@
  * 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/. */
 
 //! The context within which CSS code is parsed.
 
 use context::QuirksMode;
 use cssparser::{Parser, SourcePosition, UnicodeRange};
 use error_reporting::{ParseErrorReporter, ContextualParseError};
-use style_traits::{OneOrMoreSeparated, IsCommaSeparator, ParseError, ParsingMode};
+use style_traits::{OneOrMoreSeparated, ParseError, ParsingMode, Separator};
 #[cfg(feature = "gecko")]
 use style_traits::{PARSING_MODE_DEFAULT, PARSING_MODE_ALLOW_UNITLESS_LENGTH, PARSING_MODE_ALLOW_ALL_NUMERIC_VALUES};
 use stylesheets::{CssRuleType, Origin, UrlExtraData, Namespaces};
 
 /// Asserts that all ParsingMode flags have a matching ParsingMode value in gecko.
 #[cfg(feature = "gecko")]
 #[inline]
 pub fn assert_parsing_mode_match() {
@@ -156,39 +156,25 @@ pub fn log_css_error<'a>(input: &mut Par
 pub trait Parse : Sized {
     /// Parse a value of this type.
     ///
     /// Returns an error on failure.
     fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                      -> Result<Self, ParseError<'i>>;
 }
 
-impl<T> Parse for Vec<T> where T: Parse + OneOrMoreSeparated,
-                               <T as OneOrMoreSeparated>::S: IsCommaSeparator
+impl<T> Parse for Vec<T>
+where
+    T: Parse + OneOrMoreSeparated,
+    <T as OneOrMoreSeparated>::S: Separator,
 {
     fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                      -> Result<Self, ParseError<'i>> {
-        input.parse_comma_separated(|input| T::parse(context, input))
+        <T as OneOrMoreSeparated>::S::parse(input, |i| T::parse(context, i))
     }
 }
 
-/// Parse a non-empty space-separated or comma-separated list of objects parsed by parse_one
-pub fn parse_space_or_comma_separated<'i, 't, F, T>(input: &mut Parser<'i, 't>, mut parse_one: F)
-        -> Result<Vec<T>, ParseError<'i>>
-        where F: for<'ii, 'tt> FnMut(&mut Parser<'ii, 'tt>) -> Result<T, ParseError<'ii>> {
-    let first = parse_one(input)?;
-    let mut vec = vec![first];
-    loop {
-        let _ = input.try(|i| i.expect_comma());
-        if let Ok(val) = input.try(|i| parse_one(i)) {
-            vec.push(val)
-        } else {
-            break
-        }
-    }
-    Ok(vec)
-}
 impl Parse for UnicodeRange {
     fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
                      -> Result<Self, ParseError<'i>> {
         UnicodeRange::parse(input).map_err(|e| e.into())
     }
 }
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -55,17 +55,17 @@ use media_queries::Device;
 use properties::animated_properties::TransitionProperty;
 use properties::{longhands, ComputedValues, LonghandId, PropertyDeclarationId};
 use std::fmt::{self, Debug};
 use std::mem::{forget, transmute, zeroed};
 use std::ptr;
 use stylearc::Arc;
 use std::cmp;
 use values::{Auto, CustomIdent, Either, KeyframesName};
-use values::computed::Shadow;
+use values::computed::{Filter, Shadow};
 use values::specified::length::Percentage;
 use computed_values::border_style;
 
 pub mod style_structs {
     % for style_struct in data.style_structs:
     pub use super::${style_struct.gecko_struct_name} as ${style_struct.name};
     % endfor
 }
@@ -3295,17 +3295,21 @@ fn static_assert() {
     # This array is several filter function which has percentage or
     # number value for function of clone / set.
     # The setting / cloning process of other function(e.g. Blur / HueRotate) is
     # different from these function. So this array don't include such function.
     FILTER_FUNCTIONS = [ 'Brightness', 'Contrast', 'Grayscale', 'Invert',
                          'Opacity', 'Saturate', 'Sepia' ]
      %>
 
-    pub fn set_filter(&mut self, v: longhands::filter::computed_value::T) {
+    pub fn set_filter<I>(&mut self, v: I)
+    where
+        I: IntoIterator<Item = Filter>,
+        I::IntoIter: ExactSizeIterator,
+    {
         use values::generics::effects::Filter::*;
         use gecko_bindings::structs::nsCSSShadowArray;
         use gecko_bindings::structs::nsStyleFilter;
         use gecko_bindings::structs::NS_STYLE_FILTER_BLUR;
         use gecko_bindings::structs::NS_STYLE_FILTER_BRIGHTNESS;
         use gecko_bindings::structs::NS_STYLE_FILTER_CONTRAST;
         use gecko_bindings::structs::NS_STYLE_FILTER_GRAYSCALE;
         use gecko_bindings::structs::NS_STYLE_FILTER_INVERT;
@@ -3315,22 +3319,23 @@ fn static_assert() {
         use gecko_bindings::structs::NS_STYLE_FILTER_HUE_ROTATE;
         use gecko_bindings::structs::NS_STYLE_FILTER_DROP_SHADOW;
 
         fn fill_filter(m_type: u32, value: CoordDataValue, gecko_filter: &mut nsStyleFilter){
             gecko_filter.mType = m_type;
             gecko_filter.mFilterParameter.set_value(value);
         }
 
+        let v = v.into_iter();
         unsafe {
-            Gecko_ResetFilters(&mut self.gecko, v.0.len());
+            Gecko_ResetFilters(&mut self.gecko, v.len());
         }
-        debug_assert!(v.0.len() == self.gecko.mFilters.len());
-
-        for (servo, gecko_filter) in v.0.into_vec().into_iter().zip(self.gecko.mFilters.iter_mut()) {
+        debug_assert_eq!(v.len(), self.gecko.mFilters.len());
+
+        for (servo, gecko_filter) in v.zip(self.gecko.mFilters.iter_mut()) {
             match servo {
                 % for func in FILTER_FUNCTIONS:
                 ${func}(factor) => fill_filter(NS_STYLE_FILTER_${func.upper()},
                                                CoordDataValue::Factor(factor),
                                                gecko_filter),
                 % endfor
                 Blur(length) => fill_filter(NS_STYLE_FILTER_BLUR,
                                             CoordDataValue::Coord(length.0),
@@ -3367,17 +3372,17 @@ fn static_assert() {
 
     pub fn copy_filter_from(&mut self, other: &Self) {
         unsafe {
             Gecko_CopyFiltersFrom(&other.gecko as *const _ as *mut _, &mut self.gecko);
         }
     }
 
     pub fn clone_filter(&self) -> longhands::filter::computed_value::T {
-        use values::generics::effects::{Filter, FilterList};
+        use values::generics::effects::Filter;
         use values::specified::url::SpecifiedUrl;
         use gecko_bindings::structs::NS_STYLE_FILTER_BLUR;
         use gecko_bindings::structs::NS_STYLE_FILTER_BRIGHTNESS;
         use gecko_bindings::structs::NS_STYLE_FILTER_CONTRAST;
         use gecko_bindings::structs::NS_STYLE_FILTER_GRAYSCALE;
         use gecko_bindings::structs::NS_STYLE_FILTER_INVERT;
         use gecko_bindings::structs::NS_STYLE_FILTER_OPACITY;
         use gecko_bindings::structs::NS_STYLE_FILTER_SATURATE;
@@ -3417,17 +3422,17 @@ fn static_assert() {
                         Filter::Url(
                             SpecifiedUrl::from_url_value_data(&(**filter.__bindgen_anon_1.mURL.as_ref())._base).unwrap()
                         )
                     });
                 }
                 _ => {},
             }
         }
-        FilterList(filters.into_boxed_slice())
+        longhands::filter::computed_value::T(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
@@ -3937,20 +3942,19 @@ clip-path
 
     pub fn copy_stroke_dasharray_from(&mut self, other: &Self) {
         unsafe {
             bindings::Gecko_nsStyleSVG_CopyDashArray(&mut self.gecko, &other.gecko);
         }
     }
 
     pub fn clone_stroke_dasharray(&self) -> longhands::stroke_dasharray::computed_value::T {
-        use smallvec::SmallVec;
         use values::computed::LengthOrPercentage;
 
-        let mut vec = SmallVec::new();
+        let mut vec = vec![];
         for gecko in self.gecko.mStrokeDasharray.iter() {
             match gecko.as_value() {
                 CoordDataValue::Factor(number) => vec.push(Either::First(number)),
                 CoordDataValue::Coord(coord) =>
                     vec.push(Either::Second(LengthOrPercentage::Length(Au(coord)))),
                 CoordDataValue::Percent(p) =>
                     vec.push(Either::Second(LengthOrPercentage::Percentage(Percentage(p)))),
                 CoordDataValue::Calc(calc) =>
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -75,24 +75,25 @@
     and Stylo supports vector values.
 
     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, gecko_only=False, allow_empty=False,
-            delegate_animate=False, space_separated_allowed=False, **kwargs)">
+            delegate_animate=False, separator='Comma', **kwargs)">
     <%call expr="longhand(name, vector=True, **kwargs)">
         % if not gecko_only:
+            #[allow(unused_imports)]
             use smallvec::SmallVec;
             use std::fmt;
             #[allow(unused_imports)]
             use style_traits::HasViewportPercentage;
-            use style_traits::ToCss;
+            use style_traits::{Separator, ToCss};
 
             pub mod single_value {
                 #[allow(unused_imports)]
                 use cssparser::{Parser, BasicParseError};
                 #[allow(unused_imports)]
                 use parser::{Parse, ParserContext};
                 #[allow(unused_imports)]
                 use properties::ShorthandId;
@@ -108,23 +109,33 @@
                 use values::{Auto, Either, None_, Normal};
                 ${caller.body()}
             }
 
             /// The definition of the computed value for ${name}.
             pub mod computed_value {
                 pub use super::single_value::computed_value as single_value;
                 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.
                 #[derive(Debug, Clone, PartialEq)]
                 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-                pub struct T(pub SmallVec<[single_value::T; 1]>);
+                pub struct T(
+                    % if allow_empty and allow_empty != "NotInitial":
+                    pub Vec<single_value::T>,
+                    % else:
+                    pub SmallVec<[single_value::T; 1]>,
+                    % endif
+                );
 
                 % if delegate_animate:
                     use properties::animated_properties::Animatable;
                     impl Animatable for T {
                         fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64)
                             -> Result<Self, ()> {
                             self.0.add_weighted(&other.0, self_portion, other_portion).map(T)
                         }
@@ -144,17 +155,21 @@
                         }
                     }
                 % endif
 
                 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]>;
+                    % endif
                     fn into_iter(self) -> Self::IntoIter {
                         self.0.into_iter()
                     }
                 }
             }
 
             impl ToCss for computed_value::T {
                 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
@@ -166,17 +181,17 @@
                     } else {
                         % if allow_empty:
                             dest.write_str("none")?;
                         % else:
                             warn!("Found empty value for property ${name}");
                         % endif
                     }
                     for i in iter {
-                        dest.write_str(", ")?;
+                        dest.write_str(::style_traits::${separator}::separator())?;
                         i.to_css(dest)?;
                     }
                     Ok(())
                 }
             }
 
             /// The specified value of ${name}.
             #[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
@@ -193,51 +208,44 @@
                     } else {
                         % if allow_empty:
                             dest.write_str("none")?;
                         % else:
                             warn!("Found empty value for property ${name}");
                         % endif
                     }
                     for i in iter {
-                        dest.write_str(", ")?;
+                        dest.write_str(::style_traits::${separator}::separator())?;
                         i.to_css(dest)?;
                     }
                     Ok(())
                 }
             }
 
             pub fn get_initial_value() -> computed_value::T {
                 % if allow_empty and allow_empty != "NotInitial":
-                    computed_value::T(SmallVec::new())
+                    computed_value::T(vec![])
                 % else:
                     let mut v = SmallVec::new();
                     v.push(single_value::get_initial_value());
                     computed_value::T(v)
                 % endif
             }
 
             pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                                  -> Result<SpecifiedValue, ParseError<'i>> {
-                #[allow(unused_imports)]
-                use parser::parse_space_or_comma_separated;
-
-                <%
-                    parse_func = "Parser::parse_comma_separated"
-                    if space_separated_allowed:
-                        parse_func = "parse_space_or_comma_separated"
-                %>
+                use style_traits::Separator;
 
                 % if allow_empty:
                     if input.try(|input| input.expect_ident_matching("none")).is_ok() {
                         return Ok(SpecifiedValue(Vec::new()))
                     }
                 % endif
 
-                ${parse_func}(input, |parser| {
+                ::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>)
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -803,41 +803,47 @@ pub trait Animatable: Sized {
 }
 
 /// https://drafts.csswg.org/css-transitions/#animtype-repeatable-list
 pub trait RepeatableListAnimatable: Animatable {}
 
 impl RepeatableListAnimatable for LengthOrPercentage {}
 impl RepeatableListAnimatable for Either<f32, LengthOrPercentage> {}
 
-impl<T: RepeatableListAnimatable> Animatable for SmallVec<[T; 1]> {
-    fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64)
-        -> Result<Self, ()> {
-        use num_integer::lcm;
-        let len = lcm(self.len(), other.len());
-        self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(me, you)| {
-            me.add_weighted(you, self_portion, other_portion)
-        }).collect()
-    }
-
-    #[inline]
-    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
-        self.compute_squared_distance(other).map(|sd| sd.sqrt())
-    }
-
-    #[inline]
-    fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
-        use num_integer::lcm;
-        let len = lcm(self.len(), other.len());
-        self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(me, you)| {
-            me.compute_squared_distance(you)
-        }).collect::<Result<Vec<_>, _>>().map(|d| d.iter().sum())
-    }
+macro_rules! repeated_vec_impl {
+    ($($ty:ty),*) => {
+        $(impl<T: RepeatableListAnimatable> Animatable for $ty {
+            fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64)
+                -> Result<Self, ()> {
+                use num_integer::lcm;
+                let len = lcm(self.len(), other.len());
+                self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(me, you)| {
+                    me.add_weighted(you, self_portion, other_portion)
+                }).collect()
+            }
+
+            #[inline]
+            fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+                self.compute_squared_distance(other).map(|sd| sd.sqrt())
+            }
+
+            #[inline]
+            fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
+                use num_integer::lcm;
+                let len = lcm(self.len(), other.len());
+                self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(me, you)| {
+                    me.compute_squared_distance(you)
+                }).sum()
+            }
+        })*
+    };
 }
 
+repeated_vec_impl!(SmallVec<[T; 1]>, Vec<T>);
+
 /// https://drafts.csswg.org/css-transitions/#animtype-number
 impl Animatable for Au {
     #[inline]
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         Ok(Au((self.0 as f64 * self_portion + other.0 as f64 * other_portion).round() as i32))
     }
 
     #[inline]
@@ -3048,19 +3054,19 @@ pub struct IntermediateShadow {
     pub color: IntermediateColor,
     pub inset: bool,
 }
 
 #[derive(Clone, Debug, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 /// Intermediate type for box-shadow list and text-shadow list.
-pub struct IntermediateShadowList(pub SmallVec<[IntermediateShadow; 1]>);
-
-type ShadowList = SmallVec<[Shadow; 1]>;
+pub struct IntermediateShadowList(pub Vec<IntermediateShadow>);
+
+type ShadowList = Vec<Shadow>;
 
 impl From<IntermediateShadowList> for ShadowList {
     fn from(shadow_list: IntermediateShadowList) -> Self {
         shadow_list.0.into_iter().map(|s| s.into()).collect()
     }
 }
 
 impl From<ShadowList> for IntermediateShadowList {
@@ -3167,21 +3173,17 @@ impl Animatable for IntermediateShadowLi
             blur_radius: Au(0),
             spread_radius: Au(0),
             color: IntermediateColor::transparent(),
             inset: false,
         };
 
         let max_len = cmp::max(self.0.len(), other.0.len());
 
-        let mut result = if max_len > 1 {
-            SmallVec::from_vec(Vec::with_capacity(max_len))
-        } else {
-            SmallVec::new()
-        };
+        let mut result = Vec::with_capacity(max_len);
 
         for i in 0..max_len {
             let shadow = match (self.0.get(i), other.0.get(i)) {
                 (Some(shadow), Some(other)) => {
                     shadow.add_weighted(other, self_portion, other_portion)?
                 }
                 (Some(shadow), None) => {
                         zero.inset = shadow.inset;
@@ -3197,21 +3199,17 @@ impl Animatable for IntermediateShadowLi
         }
 
         Ok(IntermediateShadowList(result))
     }
 
     fn add(&self, other: &Self) -> Result<Self, ()> {
         let len = self.0.len() + other.0.len();
 
-        let mut result = if len > 1 {
-            SmallVec::from_vec(Vec::with_capacity(len))
-        } else {
-            SmallVec::new()
-        };
+        let mut result = Vec::with_capacity(len);
 
         result.extend(self.0.iter().cloned());
         result.extend(other.0.iter().cloned());
 
         Ok(IntermediateShadowList(result))
     }
 }
 
@@ -3337,21 +3335,21 @@ impl Animatable for AnimatedFilterList {
             if from.is_some() {
                 from = from_iter.next();
             }
             if to.is_some() {
                 to = to_iter.next();
             }
         }
 
-        Ok(filters.into())
+        Ok(AnimatedFilterList(filters))
     }
 
     fn add(&self, other: &Self) -> Result<Self, ()> {
-        Ok(self.0.iter().chain(other.0.iter()).cloned().collect::<Vec<_>>().into())
+        Ok(AnimatedFilterList(self.0.iter().chain(other.0.iter()).cloned().collect()))
     }
 
     #[inline]
     fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
         self.compute_squared_distance(other).map(|sd| sd.sqrt())
     }
 
     #[inline]
--- a/servo/components/style/properties/longhand/effects.mako.rs
+++ b/servo/components/style/properties/longhand/effects.mako.rs
@@ -38,18 +38,20 @@
                           "computed::ClipRectOrAuto::auto()",
                           animation_value_type="ComputedValue",
                           boxed="True",
                           allow_quirks=True,
                           spec="https://drafts.fxtf.org/css-masking/#clip-property")}
 
 ${helpers.predefined_type(
     "filter",
-    "FilterList",
-    "computed::FilterList::none()",
+    "Filter",
+    None,
+    vector=True,
+    separator="Space",
     animation_value_type="AnimatedFilterList",
     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
--- a/servo/components/style/properties/longhand/inherited_svg.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_svg.mako.rs
@@ -92,17 +92,17 @@
     "stroke-dasharray",
     "LengthOrPercentageOrNumber",
     None,
     "parse_non_negative",
     vector=True,
     delegate_animate=True,
     products="gecko",
     animation_value_type="ComputedValue",
-    space_separated_allowed="True",
+    separator="CommaWithSpace",
     spec="https://www.w3.org/TR/SVG2/painting.html#StrokeDashing",
 )}
 
 ${helpers.predefined_type(
     "stroke-dashoffset", "LengthOrPercentageOrNumber",
     "Either::First(0.0)",
     products="gecko",
     animation_value_type="ComputedValue",
--- a/servo/components/style/values/animated/effects.rs
+++ b/servo/components/style/values/animated/effects.rs
@@ -1,55 +1,67 @@
 /* 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::animated_properties::{Animatable, IntermediateColor};
+use properties::longhands::filter::computed_value::T as ComputedFilterList;
 #[cfg(not(feature = "gecko"))]
 use values::Impossible;
 use values::computed::{Angle, Number};
 #[cfg(feature = "gecko")]
 use values::computed::effects::Filter as ComputedFilter;
-#[cfg(feature = "gecko")]
-use values::computed::effects::FilterList as ComputedFilterList;
 use values::computed::effects::SimpleShadow as ComputedSimpleShadow;
 use values::computed::length::Length;
 use values::generics::effects::Filter as GenericFilter;
-use values::generics::effects::FilterList as GenericFilterList;
 use values::generics::effects::SimpleShadow as GenericSimpleShadow;
 
 /// An animated value for the `filter` property.
-pub type FilterList = GenericFilterList<Filter>;
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[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>;
 
 /// An animated value for a single `filter`.
 #[cfg(not(feature = "gecko"))]
 pub type Filter = GenericFilter<Angle, Number, Length, Impossible>;
 
 /// An animated value for the `drop-shadow()` filter.
 pub type SimpleShadow = GenericSimpleShadow<IntermediateColor, Length, Length>;
 
-#[cfg(feature = "gecko")]
 impl From<ComputedFilterList> for FilterList {
+    #[cfg(not(feature = "gecko"))]
     #[inline]
     fn from(filters: ComputedFilterList) -> Self {
-        filters.0.into_vec().into_iter().map(|f| f.into()).collect::<Vec<_>>().into()
+        FilterList(filters.0)
+    }
+
+    #[cfg(feature = "gecko")]
+    #[inline]
+    fn from(filters: ComputedFilterList) -> Self {
+        FilterList(filters.0.into_iter().map(|f| f.into()).collect())
     }
 }
 
-#[cfg(feature = "gecko")]
 impl From<FilterList> for ComputedFilterList {
+    #[cfg(not(feature = "gecko"))]
     #[inline]
     fn from(filters: FilterList) -> Self {
-        filters.0.into_vec().into_iter().map(|f| f.into()).collect::<Vec<_>>().into()
+        ComputedFilterList(filters.0)
+    }
+
+    #[cfg(feature = "gecko")]
+    #[inline]
+    fn from(filters: FilterList) -> Self {
+        ComputedFilterList(filters.0.into_iter().map(|f| f.into()).collect())
     }
 }
 
 #[cfg(feature = "gecko")]
 impl From<ComputedFilter> for Filter {
     #[inline]
     fn from(filter: ComputedFilter) -> Self {
         match filter {
--- a/servo/components/style/values/computed/effects.rs
+++ b/servo/components/style/values/computed/effects.rs
@@ -5,37 +5,20 @@
 //! Computed types for CSS values related to effects.
 
 #[cfg(not(feature = "gecko"))]
 use values::Impossible;
 use values::computed::{Angle, Number};
 use values::computed::color::Color;
 use values::computed::length::Length;
 use values::generics::effects::Filter as GenericFilter;
-use values::generics::effects::FilterList as GenericFilterList;
 use values::generics::effects::SimpleShadow as GenericSimpleShadow;
 
-/// A computed value for the `filter` property.
-pub type FilterList = GenericFilterList<Filter>;
-
 /// A computed value for a single `filter`.
 #[cfg(feature = "gecko")]
 pub type Filter = GenericFilter<Angle, Number, Length, SimpleShadow>;
 
 /// A computed value for a single `filter`.
 #[cfg(not(feature = "gecko"))]
 pub type Filter = GenericFilter<Angle, Number, Length, Impossible>;
 
 /// A computed value for the `drop-shadow()` filter.
 pub type SimpleShadow = GenericSimpleShadow<Color, Length, Length>;
-
-impl FilterList {
-    /// Returns the resulting opacity of this filter pipeline.
-    pub fn opacity(&self) -> Number {
-        let mut opacity = 0.;
-        for filter in &*self.0 {
-            if let GenericFilter::Opacity(factor) = *filter {
-                opacity *= factor
-            }
-        }
-        opacity
-    }
-}
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -23,17 +23,17 @@ use super::generics::grid::TrackList as 
 use super::specified;
 
 pub use app_units::Au;
 pub use properties::animated_properties::TransitionProperty;
 pub use self::background::BackgroundSize;
 pub use self::border::{BorderImageSlice, BorderImageWidth, BorderImageSideWidth};
 pub use self::border::{BorderRadius, BorderCornerRadius};
 pub use self::color::{Color, RGBAColor};
-pub use self::effects::FilterList;
+pub use self::effects::Filter;
 pub use self::flex::FlexBasis;
 pub use self::image::{Gradient, GradientItem, ImageLayer, LineDirection, Image, ImageRect};
 #[cfg(feature = "gecko")]
 pub use self::gecko::ScrollSnapPoint;
 pub use self::rect::LengthOrNumberRect;
 pub use super::{Auto, Either, None_};
 #[cfg(feature = "gecko")]
 pub use super::specified::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
--- a/servo/components/style/values/generics/effects.rs
+++ b/servo/components/style/values/generics/effects.rs
@@ -1,26 +1,17 @@
 /* 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 effects.
 
-use std::fmt;
-use style_traits::ToCss;
 #[cfg(feature = "gecko")]
 use values::specified::url::SpecifiedUrl;
 
-/// A generic value for the `filter` property.
-///
-/// Keyword `none` is represented by an empty slice.
-#[cfg_attr(feature = "servo", derive(Deserialize, HeapSizeOf, Serialize))]
-#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToComputedValue)]
-pub struct FilterList<Filter>(pub Box<[Filter]>);
-
 /// A generic value for a single `filter`.
 #[cfg_attr(feature = "servo", derive(Deserialize, HeapSizeOf, Serialize))]
 #[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToComputedValue, ToCss)]
 pub enum Filter<Angle, Factor, Length, DropShadow> {
     /// `blur(<length>)`
     #[css(function)]
     Blur(Length),
     /// `brightness(<factor>)`
@@ -66,44 +57,8 @@ pub struct SimpleShadow<Color, SizeLengt
     pub color: Color,
     /// Horizontal radius.
     pub horizontal: SizeLength,
     /// Vertical radius.
     pub vertical: SizeLength,
     /// Blur radius.
     pub blur: ShapeLength,
 }
-
-impl<F> FilterList<F> {
-    /// Returns `none`.
-    #[inline]
-    pub fn none() -> Self {
-        FilterList(vec![].into_boxed_slice())
-    }
-}
-
-impl<F> From<Vec<F>> for FilterList<F> {
-    #[inline]
-    fn from(vec: Vec<F>) -> Self {
-        FilterList(vec.into_boxed_slice())
-    }
-}
-
-impl<F> ToCss for FilterList<F>
-where
-    F: ToCss,
-{
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
-    where
-        W: fmt::Write
-    {
-        if let Some((first, rest)) = self.0.split_first() {
-            first.to_css(dest)?;
-            for filter in rest {
-                dest.write_str(" ")?;
-                filter.to_css(dest)?;
-            }
-            Ok(())
-        } else {
-            dest.write_str("none")
-        }
-    }
-}
--- a/servo/components/style/values/generics/mod.rs
+++ b/servo/components/style/values/generics/mod.rs
@@ -4,17 +4,17 @@
 
 //! Generic types that share their serialization implementations
 //! for both specified and computed values.
 
 use counter_style::{Symbols, parse_counter_style_name};
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
 use std::fmt;
-use style_traits::{OneOrMoreSeparated, CommaSeparator, ToCss, ParseError, StyleParseError};
+use style_traits::{Comma, OneOrMoreSeparated, ParseError, StyleParseError, ToCss};
 use super::CustomIdent;
 use values::specified::url::SpecifiedUrl;
 
 pub mod background;
 pub mod basic_shape;
 pub mod border;
 pub mod effects;
 pub mod flex;
@@ -120,17 +120,17 @@ impl Parse for CounterStyleOrNone {
 pub struct FontSettingTag<T> {
     /// A four-character tag, packed into a u32 (one byte per character)
     pub tag: u32,
     /// The value
     pub value: T,
 }
 
 impl<T> OneOrMoreSeparated for FontSettingTag<T> {
-    type S = CommaSeparator;
+    type S = Comma;
 }
 
 impl<T: ToCss> ToCss for FontSettingTag<T> {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         use byteorder::{BigEndian, ByteOrder};
         use std::str;
 
         let mut raw = [0u8; 4];
--- a/servo/components/style/values/mod.rs
+++ b/servo/components/style/values/mod.rs
@@ -47,17 +47,17 @@ impl Parse for Impossible {
         _input: &mut Parser<'i, 't>)
     -> Result<Self, ParseError<'i>> {
         Err(StyleParseError::UnspecifiedError.into())
     }
 }
 
 /// A struct representing one of two kinds of values.
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[derive(Clone, Copy, HasViewportPercentage, PartialEq, ToCss)]
+#[derive(Clone, Copy, HasViewportPercentage, PartialEq, ToComputedValue, ToCss)]
 pub enum Either<A, B> {
     /// The first value.
     First(A),
     /// The second kind of value.
     Second(B),
 }
 
 impl<A: Debug, B: Debug> Debug for Either<A, B> {
@@ -75,37 +75,16 @@ impl<A: Parse, B: Parse> Parse for Eithe
         if let Ok(v) = input.try(|i| A::parse(context, i)) {
             Ok(Either::First(v))
         } else {
             B::parse(context, input).map(Either::Second)
         }
     }
 }
 
-use self::computed::{Context, ToComputedValue};
-
-impl<A: ToComputedValue, B: ToComputedValue> ToComputedValue for Either<A, B> {
-    type ComputedValue = Either<A::ComputedValue, B::ComputedValue>;
-
-    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
-        match *self {
-            Either::First(ref a) => Either::First(a.to_computed_value(context)),
-            Either::Second(ref a) => Either::Second(a.to_computed_value(context)),
-        }
-    }
-
-    #[inline]
-    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
-        match *computed {
-            Either::First(ref a) => Either::First(ToComputedValue::from_computed_value(a)),
-            Either::Second(ref a) => Either::Second(ToComputedValue::from_computed_value(a)),
-        }
-    }
-}
-
 /// https://drafts.csswg.org/css-values-4/#custom-idents
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct CustomIdent(pub Atom);
 
 impl CustomIdent {
     /// Parse an already-tokenizer identifier
     pub fn from_ident<'i>(ident: CompactCowStr<'i>, excluding: &[&str]) -> Result<Self, ParseError<'i>> {
--- a/servo/components/style/values/specified/effects.rs
+++ b/servo/components/style/values/specified/effects.rs
@@ -7,27 +7,23 @@
 use cssparser::{BasicParseError, Parser, Token};
 use parser::{Parse, ParserContext};
 use style_traits::ParseError;
 #[cfg(not(feature = "gecko"))]
 use values::Impossible;
 use values::computed::{Context, Number as ComputedNumber, ToComputedValue};
 use values::computed::effects::SimpleShadow as ComputedSimpleShadow;
 use values::generics::effects::Filter as GenericFilter;
-use values::generics::effects::FilterList as GenericFilterList;
 use values::generics::effects::SimpleShadow as GenericSimpleShadow;
 use values::specified::{Angle, Percentage};
 use values::specified::color::Color;
 use values::specified::length::Length;
 #[cfg(feature = "gecko")]
 use values::specified::url::SpecifiedUrl;
 
-/// A specified value for the `filter` property.
-pub type FilterList = GenericFilterList<Filter>;
-
 /// A specified value for a single `filter`.
 #[cfg(feature = "gecko")]
 pub type Filter = GenericFilter<Angle, Factor, Length, SimpleShadow>;
 
 /// A specified value for a single `filter`.
 #[cfg(not(feature = "gecko"))]
 pub type Filter = GenericFilter<Angle, Factor, Length, Impossible>;
 
@@ -41,33 +37,16 @@ pub enum Factor {
     Number(ComputedNumber),
     /// Literal percentage.
     Percentage(Percentage),
 }
 
 /// A specified value for the `drop-shadow()` filter.
 pub type SimpleShadow = GenericSimpleShadow<Option<Color>, Length, Option<Length>>;
 
-impl Parse for FilterList {
-    #[inline]
-    fn parse<'i, 't>(
-        context: &ParserContext,
-        input: &mut Parser<'i, 't>
-    ) -> Result<Self, ParseError<'i>> {
-        let mut filters = vec![];
-        while let Ok(filter) = input.try(|i| Filter::parse(context, i)) {
-            filters.push(filter);
-        }
-        if filters.is_empty() {
-            input.expect_ident_matching("none")?;
-        }
-        Ok(GenericFilterList(filters.into_boxed_slice()))
-    }
-}
-
 impl Parse for Filter {
     #[inline]
     fn parse<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>
     ) -> Result<Self, ParseError<'i>> {
         #[cfg(feature = "gecko")]
         {
@@ -108,17 +87,17 @@ impl Parse for Factor {
             },
             other => Err(BasicParseError::UnexpectedToken(other).into()),
         }
     }
 }
 
 impl ToComputedValue for Factor {
     /// This should actually be `ComputedNumberOrPercentage`, but layout uses
-    /// `computed::effects::FilterList` directly in `StackingContext`.
+    /// `computed::effects::Filter` directly in `StackingContext`.
     type ComputedValue = ComputedNumber;
 
     #[inline]
     fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
         match *self {
             Factor::Number(number) => number,
             Factor::Percentage(percentage) => percentage.0,
         }
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -28,17 +28,17 @@ use values::specified::calc::CalcNode;
 
 pub use properties::animated_properties::TransitionProperty;
 #[cfg(feature = "gecko")]
 pub use self::align::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
 pub use self::background::BackgroundSize;
 pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth};
 pub use self::border::{BorderImageSideWidth, BorderRadius, BorderSideWidth};
 pub use self::color::{Color, RGBAColor};
-pub use self::effects::FilterList;
+pub use self::effects::Filter;
 pub use self::flex::FlexBasis;
 #[cfg(feature = "gecko")]
 pub use self::gecko::ScrollSnapPoint;
 pub use self::image::{ColorStop, EndingShape as GradientEndingShape, Gradient};
 pub use self::image::{GradientItem, GradientKind, Image, ImageRect, ImageLayer};
 pub use self::length::{AbsoluteLength, CalcLengthOrPercentage, CharacterWidth};
 pub use self::length::{FontRelativeLength, Length, LengthOrNone, LengthOrNumber};
 pub use self::length::{LengthOrPercentage, LengthOrPercentageOrAuto};
--- a/servo/components/style_traits/lib.rs
+++ b/servo/components/style_traits/lib.rs
@@ -66,17 +66,17 @@ pub enum CSSPixel {}
 //     / desktop_zoom => CSSPixel
 
 pub mod cursor;
 #[macro_use]
 pub mod values;
 #[macro_use]
 pub mod viewport;
 
-pub use values::{ToCss, OneOrMoreSeparated, CommaSeparator, SpaceSeparator, IsCommaSeparator};
+pub use values::{Comma, CommaWithSpace, OneOrMoreSeparated, Separator, Space, ToCss};
 pub use viewport::HasViewportPercentage;
 
 /// The error type for all CSS parsing routines.
 pub type ParseError<'i> = cssparser::ParseError<'i, SelectorParseError<'i, StyleParseError<'i>>>;
 
 #[derive(Clone, Debug, PartialEq)]
 /// Errors that can be encountered while parsing CSS values.
 pub enum StyleParseError<'i> {
--- a/servo/components/style_traits/values.rs
+++ b/servo/components/style_traits/values.rs
@@ -1,16 +1,16 @@
 /* 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/. */
 
 //! Helper types and traits for the handling of CSS values.
 
 use app_units::Au;
-use cssparser::{UnicodeRange, serialize_string};
+use cssparser::{BasicParseError, ParseError, Parser, Token, UnicodeRange, serialize_string};
 use std::fmt::{self, Write};
 
 /// Serialises a value according to its CSS representation.
 ///
 /// This trait is implemented for `str` and its friends, serialising the string
 /// contents as a CSS quoted string.
 ///
 /// This trait is derivable with `#[derive(ToCss)]`, with the following behaviour:
@@ -179,58 +179,122 @@ where
         }
         self.writer.write_char(c)
     }
 }
 
 /// Type used as the associated type in the `OneOrMoreSeparated` trait on a
 /// type to indicate that a serialized list of elements of this type is
 /// separated by commas.
-pub struct CommaSeparator;
+pub struct Comma;
 
 /// Type used as the associated type in the `OneOrMoreSeparated` trait on a
 /// type to indicate that a serialized list of elements of this type is
 /// separated by spaces.
-pub struct SpaceSeparator;
+pub struct Space;
+
+/// Type used as the associated type in the `OneOrMoreSeparated` trait on a
+/// type to indicate that a serialized list of elements of this type is
+/// separated by commas, but spaces without commas are also allowed when
+/// parsing.
+pub struct CommaWithSpace;
 
 /// A trait satisfied by the types corresponding to separators.
 pub trait Separator {
     /// The separator string that the satisfying separator type corresponds to.
     fn separator() -> &'static str;
+
+    /// Parses a sequence of values separated by this separator.
+    ///
+    /// The given closure is called repeatedly for each item in the sequence.
+    ///
+    /// Successful results are accumulated in a vector.
+    ///
+    /// This method returns `Err(_)` the first time a closure does or if
+    /// the separators aren't correct.
+    fn parse<'i, 't, F, T, E>(
+        parser: &mut Parser<'i, 't>,
+        parse_one: F,
+    ) -> Result<Vec<T>, ParseError<'i, E>>
+    where
+        F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>;
 }
 
-impl Separator for CommaSeparator {
+impl Separator for Comma {
     fn separator() -> &'static str {
         ", "
     }
+
+    fn parse<'i, 't, F, T, E>(
+        input: &mut Parser<'i, 't>,
+        parse_one: F,
+    ) -> Result<Vec<T>, ParseError<'i, E>>
+    where
+        F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>
+    {
+        input.parse_comma_separated(parse_one)
+    }
 }
 
-impl Separator for SpaceSeparator {
+impl Separator for Space {
     fn separator() -> &'static str {
         " "
     }
+
+    fn parse<'i, 't, F, T, E>(
+        input: &mut Parser<'i, 't>,
+        mut parse_one: F,
+    ) -> Result<Vec<T>, ParseError<'i, E>>
+    where
+        F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>
+    {
+        let mut results = vec![parse_one(input)?];
+        while let Ok(item) = input.try(&mut parse_one) {
+            results.push(item);
+        }
+        Ok(results)
+    }
 }
 
-/// Trait that indicates that satisfying separator types are comma separators.
-/// This seems kind of redundant, but we aren't able to express type equality
-/// constraints yet.
-/// https://github.com/rust-lang/rust/issues/20041
-pub trait IsCommaSeparator {}
+impl Separator for CommaWithSpace {
+    fn separator() -> &'static str {
+        ", "
+    }
 
-impl IsCommaSeparator for CommaSeparator {}
+    fn parse<'i, 't, F, T, E>(
+        input: &mut Parser<'i, 't>,
+        mut parse_one: F,
+    ) -> Result<Vec<T>, ParseError<'i, E>>
+    where
+        F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>
+    {
+        let mut results = vec![parse_one(input)?];
+        loop {
+            let comma = input.try(|i| i.expect_comma()).is_ok();
+            if let Ok(item) = input.try(&mut parse_one) {
+                results.push(item);
+            } else if comma {
+                return Err(BasicParseError::UnexpectedToken(Token::Comma).into());
+            } else {
+                break;
+            }
+        }
+        Ok(results)
+    }
+}
 
 /// Marker trait on T to automatically implement ToCss for Vec<T> when T's are
 /// separated by some delimiter `delim`.
 pub trait OneOrMoreSeparated {
     /// Associated type indicating which separator is used.
     type S: Separator;
 }
 
 impl OneOrMoreSeparated for UnicodeRange {
-    type S = CommaSeparator;
+    type S = Comma;
 }
 
 impl<T> ToCss for Vec<T> where T: ToCss + OneOrMoreSeparated {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: Write {
         let mut iter = self.iter();
         iter.next().unwrap().to_css(dest)?;
         for item in iter {
             dest.write_str(<T as OneOrMoreSeparated>::S::separator())?;