servo: Merge #17543 - Make text-shadow and box-shadow use SimpleShadow (from servo:derive-all-the-things); r=emilio
authorAnthony Ramine <n.oxyde@gmail.com>
Wed, 28 Jun 2017 07:28:45 -0700
changeset 415154 540e811a9a1118ca87569d08a47780c3c07ae402
parent 415153 3b06dbb28dd4b764a650ebc9e87583e6cbb43696
child 415155 16784899765fefccbf0ae64bf0882b4642e8ccff
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [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 #17543 - Make text-shadow and box-shadow use SimpleShadow (from servo:derive-all-the-things); r=emilio Source-Repo: https://github.com/servo/servo Source-Revision: 522d24d1260c93b570882e5bf42f2c9c25ef334c
servo/components/layout/display_list_builder.rs
servo/components/layout/fragment.rs
servo/components/style/gecko_bindings/sugar/ns_css_shadow_item.rs
servo/components/style/properties/gecko.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_text.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/specified/effects.rs
servo/components/style/values/specified/mod.rs
servo/tests/unit/style/properties/serialization.rs
--- a/servo/components/layout/display_list_builder.rs
+++ b/servo/components/layout/display_list_builder.rs
@@ -52,17 +52,18 @@ use style::computed_values::{background_
 use style::computed_values::{image_rendering, overflow_x, pointer_events, position, visibility};
 use style::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode};
 use style::properties::{self, ServoComputedValues};
 use style::properties::longhands::border_image_repeat::computed_value::RepeatKeyword;
 use style::properties::style_structs;
 use style::servo::restyle_damage::REPAINT;
 use style::values::{Either, RGBA};
 use style::values::computed::{Gradient, GradientItem, LengthOrPercentage};
-use style::values::computed::{LengthOrPercentageOrAuto, NumberOrPercentage, Position, Shadow};
+use style::values::computed::{LengthOrPercentageOrAuto, NumberOrPercentage, Position};
+use style::values::computed::effects::SimpleShadow;
 use style::values::computed::image::{EndingShape, LineDirection};
 use style::values::generics::background::BackgroundSize;
 use style::values::generics::effects::Filter;
 use style::values::generics::image::{Circle, Ellipse, EndingShape as GenericEndingShape};
 use style::values::generics::image::{GradientItem as GenericGradientItem, GradientKind};
 use style::values::generics::image::{Image, ShapeExtent};
 use style::values::generics::image::PaintWorklet;
 use style::values::specified::length::Percentage;
@@ -520,26 +521,26 @@ pub trait FragmentDisplayListBuilding {
     /// Creates the text display item for one text fragment. This can be called multiple times for
     /// one fragment if there are text shadows.
     ///
     /// `text_shadow` will be `Some` if this is rendering a shadow.
     fn build_display_list_for_text_fragment(&self,
                                             state: &mut DisplayListBuildState,
                                             text_fragment: &ScannedTextFragmentInfo,
                                             stacking_relative_content_box: &Rect<Au>,
-                                            text_shadow: Option<&Shadow>,
+                                            text_shadow: Option<&SimpleShadow>,
                                             clip: &Rect<Au>);
 
     /// Creates the display item for a text decoration: underline, overline, or line-through.
     fn build_display_list_for_text_decoration(&self,
                                               state: &mut DisplayListBuildState,
                                               color: &RGBA,
                                               stacking_relative_box: &LogicalRect<Au>,
                                               clip: &Rect<Au>,
-                                              blur_radius: Au);
+                                              blur: Au);
 
     /// A helper method that `build_display_list` calls to create per-fragment-type display items.
     fn build_fragment_type_specific_display_items(&mut self,
                                                   state: &mut DisplayListBuildState,
                                                   stacking_relative_border_box: &Rect<Au>,
                                                   clip: &Rect<Au>);
 
     /// Creates a stacking context for associated fragment.
@@ -1346,35 +1347,38 @@ impl FragmentDisplayListBuilding for Fra
     fn build_display_list_for_box_shadow_if_applicable(&self,
                                                        state: &mut DisplayListBuildState,
                                                        style: &ServoComputedValues,
                                                        display_list_section: DisplayListSection,
                                                        absolute_bounds: &Rect<Au>,
                                                        clip: &Rect<Au>) {
         // NB: According to CSS-BACKGROUNDS, box shadows render in *reverse* order (front to back).
         for box_shadow in style.get_effects().box_shadow.0.iter().rev() {
-            let bounds =
-                shadow_bounds(&absolute_bounds.translate(&Vector2D::new(box_shadow.offset_x,
-                                                                        box_shadow.offset_y)),
-                              box_shadow.blur_radius,
-                              box_shadow.spread_radius);
+            let bounds = shadow_bounds(
+                &absolute_bounds.translate(&Vector2D::new(
+                    box_shadow.base.horizontal,
+                    box_shadow.base.vertical,
+                )),
+                box_shadow.base.blur,
+                box_shadow.spread,
+            );
 
             // TODO(pcwalton): Multiple border radii; elliptical border radii.
             let base = state.create_base_display_item(&bounds,
                                                       &ClippingRegion::from_rect(&clip),
                                                       self.node,
                                                       style.get_cursor(Cursor::Default),
                                                       display_list_section);
             state.add_display_item(DisplayItem::BoxShadow(box BoxShadowDisplayItem {
                 base: base,
                 box_bounds: *absolute_bounds,
-                color: style.resolve_color(box_shadow.color).to_gfx_color(),
-                offset: Vector2D::new(box_shadow.offset_x, box_shadow.offset_y),
-                blur_radius: box_shadow.blur_radius,
-                spread_radius: box_shadow.spread_radius,
+                color: style.resolve_color(box_shadow.base.color).to_gfx_color(),
+                offset: Vector2D::new(box_shadow.base.horizontal, box_shadow.base.vertical),
+                blur_radius: box_shadow.base.blur,
+                spread_radius: box_shadow.spread,
                 border_radius: model::specified_border_radius(style.get_border()
                                                                    .border_top_left_radius,
                                                               absolute_bounds.size).width,
                 clip_mode: if box_shadow.inset {
                     BoxShadowClipMode::Inset
                 } else {
                     BoxShadowClipMode::Outset
                 },
@@ -2034,30 +2038,32 @@ impl FragmentDisplayListBuilding for Fra
                              scroll_policy,
                              parent_scroll_id)
     }
 
     fn build_display_list_for_text_fragment(&self,
                                             state: &mut DisplayListBuildState,
                                             text_fragment: &ScannedTextFragmentInfo,
                                             stacking_relative_content_box: &Rect<Au>,
-                                            text_shadow: Option<&Shadow>,
+                                            text_shadow: Option<&SimpleShadow>,
                                             clip: &Rect<Au>) {
         // TODO(emilio): Allow changing more properties by ::selection
         let text_color = if let Some(shadow) = text_shadow {
             // If we're painting a shadow, paint the text the same color as the shadow.
             self.style().resolve_color(shadow.color)
         } else if text_fragment.selected() {
             // Otherwise, paint the text with the color as described in its styling.
             self.selected_style().get_color().color
         } else {
             self.style().get_color().color
         };
-        let offset = text_shadow.map(|s| Vector2D::new(s.offset_x, s.offset_y)).unwrap_or_else(Vector2D::zero);
-        let shadow_blur_radius = text_shadow.map(|s| s.blur_radius).unwrap_or(Au(0));
+        let offset = text_shadow.map_or(Vector2D::zero(), |s| {
+            Vector2D::new(s.horizontal, s.vertical)
+        });
+        let shadow_blur_radius = text_shadow.map(|s| s.blur).unwrap_or(Au(0));
 
         // Determine the orientation and cursor to use.
         let (orientation, cursor) = if self.style.writing_mode.is_vertical() {
             // TODO: Distinguish between 'sideways-lr' and 'sideways-rl' writing modes in CSS
             // Writing Modes Level 4.
             (TextOrientation::SidewaysRight, Cursor::VerticalText)
         } else {
             (TextOrientation::Upright, Cursor::Text)
@@ -2880,18 +2886,18 @@ fn position_to_offset(position: LengthOr
         LengthOrPercentage::Calc(calc) => {
             calc.to_used_value(Some(total_length)).unwrap().0 as f32 / total_length.0 as f32
         },
     }
 }
 
 /// Adjusts `content_rect` as necessary for the given spread, and blur so that the resulting
 /// bounding rect contains all of a shadow's ink.
-fn shadow_bounds(content_rect: &Rect<Au>, blur_radius: Au, spread_radius: Au) -> Rect<Au> {
-    let inflation = spread_radius + blur_radius * BLUR_INFLATION_FACTOR;
+fn shadow_bounds(content_rect: &Rect<Au>, blur: Au, spread: Au) -> Rect<Au> {
+    let inflation = spread + blur * BLUR_INFLATION_FACTOR;
     content_rect.inflate(inflation, inflation)
 }
 
 /// Allows a CSS color to be converted into a graphics color.
 pub trait ToGfxColor {
     /// Converts a CSS color to a graphics color.
     fn to_gfx_color(&self) -> ColorF;
 }
--- a/servo/components/layout/fragment.rs
+++ b/servo/components/layout/fragment.rs
@@ -2546,19 +2546,18 @@ impl Fragment {
         // the time. Can't we handle relative positioning by just adjusting `border_box`?
         let relative_position = self.relative_position(relative_containing_block_size);
         border_box =
             border_box.translate_by_size(&relative_position.to_physical(self.style.writing_mode));
         let mut overflow = Overflow::from_rect(&border_box);
 
         // Box shadows cause us to draw outside our border box.
         for box_shadow in &self.style().get_effects().box_shadow.0 {
-            let offset = Vector2D::new(box_shadow.offset_x, box_shadow.offset_y);
-            let inflation = box_shadow.spread_radius + box_shadow.blur_radius *
-                BLUR_INFLATION_FACTOR;
+            let offset = Vector2D::new(box_shadow.base.horizontal, box_shadow.base.vertical);
+            let inflation = box_shadow.spread + box_shadow.base.blur * BLUR_INFLATION_FACTOR;
             overflow.paint = overflow.paint.union(&border_box.translate(&offset)
                                                              .inflate(inflation, inflation))
         }
 
         // Outlines cause us to draw outside our border box.
         let outline_width = self.style.get_outline().outline_width;
         if outline_width != Au(0) {
             overflow.paint = overflow.paint.union(&border_box.inflate(outline_width,
--- a/servo/components/style/gecko_bindings/sugar/ns_css_shadow_item.rs
+++ b/servo/components/style/gecko_bindings/sugar/ns_css_shadow_item.rs
@@ -2,47 +2,40 @@
  * 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/. */
 
 //! Rust helpers for Gecko's `nsCSSShadowItem`.
 
 use app_units::Au;
 use gecko::values::{convert_rgba_to_nscolor, convert_nscolor_to_rgba};
 use gecko_bindings::structs::nsCSSShadowItem;
-use values::computed::{Color, Shadow};
-use values::computed::effects::SimpleShadow;
+use values::computed::Color;
+use values::computed::effects::{BoxShadow, SimpleShadow};
 
 impl nsCSSShadowItem {
-    /// Set this item to the given shadow value.
-    pub fn set_from_shadow(&mut self, other: Shadow) {
-        self.mXOffset = other.offset_x.0;
-        self.mYOffset = other.offset_y.0;
-        self.mRadius = other.blur_radius.0;
-        self.mSpread = other.spread_radius.0;
-        self.mInset = other.inset;
-        if other.color.is_currentcolor() {
-            // TODO handle currentColor
-            // https://bugzilla.mozilla.org/show_bug.cgi?id=760345
-            self.mHasColor = false;
-            self.mColor = 0;
-        } else {
-            self.mHasColor = true;
-            self.mColor = convert_rgba_to_nscolor(&other.color.color);
-        }
+    /// Sets this item from the given box shadow.
+    #[inline]
+    pub fn set_from_box_shadow(&mut self, shadow: BoxShadow) {
+        self.set_from_simple_shadow(shadow.base);
+        self.mSpread = shadow.spread.0;
+        self.mInset = shadow.inset;
     }
 
-    /// Generate shadow value from this shadow item.
-    pub fn to_shadow(&self) -> Shadow {
-        Shadow {
-            offset_x: Au(self.mXOffset),
-            offset_y: Au(self.mYOffset),
-            blur_radius: Au(self.mRadius),
-            spread_radius: Au(self.mSpread),
+    /// Returns this item as a box shadow.
+    #[inline]
+    pub fn to_box_shadow(&self) -> BoxShadow {
+        BoxShadow {
+            base: SimpleShadow {
+                color: Color::rgba(convert_nscolor_to_rgba(self.mColor)),
+                horizontal: Au(self.mXOffset),
+                vertical: Au(self.mYOffset),
+                blur: Au(self.mRadius),
+            },
+            spread: Au(self.mSpread),
             inset: self.mInset,
-            color: Color::rgba(convert_nscolor_to_rgba(self.mColor)),
         }
     }
 
     /// Sets this item from the given simple shadow.
     #[inline]
     pub fn set_from_simple_shadow(&mut self, shadow: SimpleShadow) {
         self.mXOffset = shadow.horizontal.0;
         self.mYOffset = shadow.vertical.0;
--- 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::{Filter, Shadow};
+use values::computed::effects::{BoxShadow, Filter, SimpleShadow};
 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
 }
@@ -3168,32 +3168,32 @@ fn static_assert() {
     }
 
     ${impl_simple_copy('_x_span', 'mSpan')}
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="Effects"
                   skip_longhands="box-shadow clip filter">
     pub fn set_box_shadow<I>(&mut self, v: I)
-        where I: IntoIterator<Item = Shadow>,
+        where I: IntoIterator<Item = BoxShadow>,
               I::IntoIter: ExactSizeIterator
     {
         let v = v.into_iter();
         self.gecko.mBoxShadow.replace_with_new(v.len() as u32);
         for (servo, gecko_shadow) in v.zip(self.gecko.mBoxShadow.iter_mut()) {
-            gecko_shadow.set_from_shadow(servo);
+            gecko_shadow.set_from_box_shadow(servo);
         }
     }
 
     pub fn copy_box_shadow_from(&mut self, other: &Self) {
         self.gecko.mBoxShadow.copy_from(&other.gecko.mBoxShadow);
     }
 
     pub fn clone_box_shadow(&self) -> longhands::box_shadow::computed_value::T {
-        let buf = self.gecko.mBoxShadow.iter().map(|v| v.to_shadow()).collect();
+        let buf = self.gecko.mBoxShadow.iter().map(|v| v.to_box_shadow()).collect();
         longhands::box_shadow::computed_value::T(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;
@@ -3489,32 +3489,32 @@ fn static_assert() {
 
     <% text_align_keyword = Keyword("text-align",
                                     "start end left right center justify -moz-center -moz-left -moz-right char",
                                     gecko_strip_moz_prefix=False) %>
     ${impl_keyword('text_align', 'mTextAlign', text_align_keyword, need_clone=False)}
     ${impl_keyword_clone('text_align', 'mTextAlign', text_align_keyword)}
 
     pub fn set_text_shadow<I>(&mut self, v: I)
-        where I: IntoIterator<Item = Shadow>,
+        where I: IntoIterator<Item = SimpleShadow>,
               I::IntoIter: ExactSizeIterator
     {
         let v = v.into_iter();
         self.gecko.mTextShadow.replace_with_new(v.len() as u32);
         for (servo, gecko_shadow) in v.zip(self.gecko.mTextShadow.iter_mut()) {
-            gecko_shadow.set_from_shadow(servo);
+            gecko_shadow.set_from_simple_shadow(servo);
         }
     }
 
     pub fn copy_text_shadow_from(&mut self, other: &Self) {
         self.gecko.mTextShadow.copy_from(&other.gecko.mTextShadow);
     }
 
     pub fn clone_text_shadow(&self) -> longhands::text_shadow::computed_value::T {
-        let buf = self.gecko.mTextShadow.iter().map(|v| v.to_shadow()).collect();
+        let buf = self.gecko.mTextShadow.iter().map(|v| v.to_simple_shadow()).collect();
         longhands::text_shadow::computed_value::T(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,
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -14,36 +14,37 @@ use euclid::{Point2D, Size2D};
 #[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSPropertyID;
 #[cfg(feature = "gecko")] use gecko_bindings::sugar::ownership::{HasFFI, HasSimpleFFI};
 #[cfg(feature = "gecko")] use gecko_string_cache::Atom;
 use properties::{CSSWideKeyword, PropertyDeclaration};
 use properties::longhands;
 use properties::longhands::background_size::computed_value::T as BackgroundSizeList;
 use properties::longhands::font_weight::computed_value::T as FontWeight;
 use properties::longhands::font_stretch::computed_value::T as FontStretch;
-use properties::longhands::text_shadow::computed_value::T as TextShadowList;
-use properties::longhands::box_shadow::computed_value::T as BoxShadowList;
 use properties::longhands::transform::computed_value::ComputedMatrix;
 use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation;
 use properties::longhands::transform::computed_value::T as TransformList;
 use properties::longhands::vertical_align::computed_value::T as VerticalAlign;
 use properties::longhands::visibility::computed_value::T as Visibility;
 #[cfg(feature = "gecko")] use properties::{PropertyDeclarationId, LonghandId};
 use selectors::parser::SelectorParseError;
 use smallvec::SmallVec;
 use std::cmp;
 #[cfg(feature = "gecko")] use fnv::FnvHashMap;
 use style_traits::ParseError;
 use super::ComputedValues;
 use values::{Auto, CSSFloat, CustomIdent, Either};
-use values::animated::effects::{Filter as AnimatedFilter, FilterList as AnimatedFilterList};
+use values::animated::effects::BoxShadowList as AnimatedBoxShadowList;
+use values::animated::effects::Filter as AnimatedFilter;
+use values::animated::effects::FilterList as AnimatedFilterList;
+use values::animated::effects::TextShadowList as AnimatedTextShadowList;
 use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone};
 use values::computed::{BorderCornerRadius, ClipRect};
 use values::computed::{CalcLengthOrPercentage, Color, Context, ComputedValueAsSpecified};
-use values::computed::{LengthOrPercentage, MaxLength, MozLength, Shadow, ToComputedValue};
+use values::computed::{LengthOrPercentage, MaxLength, MozLength, ToComputedValue};
 use values::generics::{SVGPaint, SVGPaintKind};
 use values::generics::border::BorderCornerRadius as GenericBorderCornerRadius;
 use values::generics::effects::Filter;
 use values::generics::position as generic_position;
 use values::specified::length::Percentage;
 
 
 /// A longhand property whose animation type is not "none".
@@ -2827,17 +2828,18 @@ pub struct IntermediateColor {
 impl IntermediateColor {
     fn currentcolor() -> Self {
         IntermediateColor {
             color: IntermediateRGBA::transparent(),
             foreground_ratio: 1.,
         }
     }
 
-    fn transparent() -> Self {
+    /// Returns a transparent intermediate color.
+    pub fn transparent() -> Self {
         IntermediateColor {
             color: IntermediateRGBA::transparent(),
             foreground_ratio: 0.,
         }
     }
 
     fn is_currentcolor(&self) -> bool {
         self.foreground_ratio >= 1.
@@ -3035,189 +3037,16 @@ impl Animatable for IntermediateSVGPaint
             &SVGPaintKind::None |
             &SVGPaintKind::ContextFill |
             &SVGPaintKind::ContextStroke =>  Some(self.clone()),
             _ => None,
         }
     }
 }
 
-#[derive(Copy, Clone, Debug, PartialEq)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-/// Intermediate type for box-shadow and text-shadow.
-/// The difference from normal shadow type is that this type uses
-/// IntermediateColor instead of ParserColor.
-pub struct IntermediateShadow {
-    pub offset_x: Au,
-    pub offset_y: Au,
-    pub blur_radius: Au,
-    pub spread_radius: Au,
-    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 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 {
-    fn from(shadow_list: ShadowList) -> IntermediateShadowList {
-        IntermediateShadowList(shadow_list.into_iter().map(|s| s.into()).collect())
-    }
-}
-
-% for ty in "Box Text".split():
-impl From<IntermediateShadowList> for ${ty}ShadowList {
-    #[inline]
-    fn from(shadow_list: IntermediateShadowList) -> Self {
-        ${ty}ShadowList(shadow_list.into())
-    }
-}
-
-impl From<${ty}ShadowList> for IntermediateShadowList {
-    #[inline]
-    fn from(shadow_list: ${ty}ShadowList) -> IntermediateShadowList {
-        shadow_list.0.into()
-    }
-}
-% endfor
-
-impl From<IntermediateShadow> for Shadow {
-    fn from(shadow: IntermediateShadow) -> Shadow {
-        Shadow {
-            offset_x: shadow.offset_x,
-            offset_y: shadow.offset_y,
-            blur_radius: shadow.blur_radius,
-            spread_radius: shadow.spread_radius,
-            color: shadow.color.into(),
-            inset: shadow.inset,
-        }
-    }
-}
-
-impl From<Shadow> for IntermediateShadow {
-    fn from(shadow: Shadow) -> IntermediateShadow {
-        IntermediateShadow {
-            offset_x: shadow.offset_x,
-            offset_y: shadow.offset_y,
-            blur_radius: shadow.blur_radius,
-            spread_radius: shadow.spread_radius,
-            color: shadow.color.into(),
-            inset: shadow.inset,
-        }
-    }
-}
-
-impl Animatable for IntermediateShadow {
-    #[inline]
-    fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
-        // It can't be interpolated if inset does not match.
-        if self.inset != other.inset {
-            return Err(());
-        }
-
-        let x = self.offset_x.add_weighted(&other.offset_x, self_portion, other_portion)?;
-        let y = self.offset_y.add_weighted(&other.offset_y, self_portion, other_portion)?;
-        let color = self.color.add_weighted(&other.color, self_portion, other_portion)?;
-        let blur = self.blur_radius.add_weighted(&other.blur_radius, self_portion, other_portion)?;
-        let spread = self.spread_radius.add_weighted(&other.spread_radius, self_portion, other_portion)?;
-
-        Ok(IntermediateShadow {
-            offset_x: x,
-            offset_y: y,
-            blur_radius: blur,
-            spread_radius: spread,
-            color: color,
-            inset: self.inset,
-        })
-    }
-
-    #[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, ()> {
-        if self.inset != other.inset {
-            return Err(());
-        }
-        let list = [
-            self.offset_x.compute_distance(&other.offset_x)?,
-            self.offset_y.compute_distance(&other.offset_y)?,
-            self.blur_radius.compute_distance(&other.blur_radius)?,
-            self.color.compute_distance(&other.color)?,
-            self.spread_radius.compute_distance(&other.spread_radius)?,
-        ];
-        Ok(list.iter().fold(0.0f64, |sum, diff| sum + diff * diff))
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list
-impl Animatable for IntermediateShadowList {
-    #[inline]
-    fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
-        // The inset value must change
-        let mut zero = IntermediateShadow {
-            offset_x: Au(0),
-            offset_y: Au(0),
-            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 = 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;
-                        shadow.add_weighted(&zero, self_portion, other_portion).unwrap()
-                }
-                (None, Some(shadow)) => {
-                    zero.inset = shadow.inset;
-                    zero.add_weighted(&shadow, self_portion, other_portion).unwrap()
-                }
-                (None, None) => unreachable!(),
-            };
-            result.push(shadow);
-        }
-
-        Ok(IntermediateShadowList(result))
-    }
-
-    fn add(&self, other: &Self) -> Result<Self, ()> {
-        let len = self.0.len() + other.0.len();
-
-        let mut result = Vec::with_capacity(len);
-
-        result.extend(self.0.iter().cloned());
-        result.extend(other.0.iter().cloned());
-
-        Ok(IntermediateShadowList(result))
-    }
-}
-
 <%
     FILTER_FUNCTIONS = [ 'Blur', 'Brightness', 'Contrast', 'Grayscale',
                          'HueRotate', 'Invert', 'Opacity', 'Saturate',
                          'Sepia' ]
 %>
 
 /// https://drafts.fxtf.org/filters/#animation-of-filters
 fn add_weighted_filter_function_impl(from: &AnimatedFilter,
--- a/servo/components/style/properties/longhand/effects.mako.rs
+++ b/servo/components/style/properties/longhand/effects.mako.rs
@@ -9,34 +9,26 @@
 
 ${helpers.predefined_type("opacity",
                           "Opacity",
                           "1.0",
                           animation_value_type="ComputedValue",
                           flags="CREATES_STACKING_CONTEXT",
                           spec="https://drafts.csswg.org/css-color/#opacity")}
 
-<%helpers:vector_longhand name="box-shadow" allow_empty="True"
-                          animation_value_type="IntermediateShadowList"
-                          extra_prefixes="webkit"
-                          ignored_when_colors_disabled="True"
-                          spec="https://drafts.csswg.org/css-backgrounds/#box-shadow">
-    pub type SpecifiedValue = specified::Shadow;
-
-    pub mod computed_value {
-        use values::computed::Shadow;
-
-        pub type T = Shadow;
-    }
-
-    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
-                         -> Result<specified::Shadow, ParseError<'i>> {
-        specified::Shadow::parse(context, input, false)
-    }
-</%helpers:vector_longhand>
+${helpers.predefined_type(
+    "box-shadow",
+    "BoxShadow",
+    None,
+    vector=True,
+    animation_value_type="AnimatedBoxShadowList",
+    extra_prefixes="webkit",
+    ignored_when_colors_disabled=True,
+    spec="https://drafts.csswg.org/css-backgrounds/#box-shadow",
+)}
 
 ${helpers.predefined_type("clip",
                           "ClipRectOrAuto",
                           "computed::ClipRectOrAuto::auto()",
                           animation_value_type="ComputedValue",
                           boxed="True",
                           allow_quirks=True,
                           spec="https://drafts.fxtf.org/css-masking/#clip-property")}
--- a/servo/components/style/properties/longhand/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_text.mako.rs
@@ -402,31 +402,25 @@
                 SpecifiedValue::pre |
                 SpecifiedValue::pre_wrap => true,
             }
         }
     }
     % endif
 </%helpers:single_keyword_computed>
 
-<%helpers:vector_longhand name="text-shadow" allow_empty="True"
-                          animation_value_type="IntermediateShadowList"
-                          ignored_when_colors_disabled="True"
-                          spec="https://drafts.csswg.org/css-backgrounds/#box-shadow">
-    pub type SpecifiedValue = specified::Shadow;
-    pub mod computed_value {
-        use values::computed::Shadow;
-        pub type T = Shadow;
-    }
-
-    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
-                         -> Result<specified::Shadow, ParseError<'i>> {
-        specified::Shadow::parse(context, input, true)
-    }
-</%helpers:vector_longhand>
+${helpers.predefined_type(
+    "text-shadow",
+    "SimpleShadow",
+    None,
+    vector=True,
+    animation_value_type="AnimatedTextShadowList",
+    ignored_when_colors_disabled=True,
+    spec="https://drafts.csswg.org/css-text-decor-3/#text-shadow-property",
+)}
 
 <%helpers:longhand name="text-emphasis-style" products="gecko" need_clone="True" boxed="True"
                    animation_value_type="none"
                    spec="https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-style">
     use computed_values::writing_mode::T as writing_mode;
     use std::fmt;
     use style_traits::ToCss;
     use unicode_segmentation::UnicodeSegmentation;
--- a/servo/components/style/values/animated/effects.rs
+++ b/servo/components/style/values/animated/effects.rs
@@ -1,42 +1,237 @@
 /* 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 app_units::Au;
 use properties::animated_properties::{Animatable, IntermediateColor};
+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::computed::{Angle, Number};
+use values::computed::effects::BoxShadow as ComputedBoxShadow;
 #[cfg(feature = "gecko")]
 use values::computed::effects::Filter as ComputedFilter;
 use values::computed::effects::SimpleShadow as ComputedSimpleShadow;
 use values::computed::length::Length;
+use values::generics::effects::BoxShadow as GenericBoxShadow;
 use values::generics::effects::Filter as GenericFilter;
 use values::generics::effects::SimpleShadow as GenericSimpleShadow;
 
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Debug, PartialEq)]
+/// An animated value for the `box-shadow` property.
+pub struct BoxShadowList(pub Vec<BoxShadow>);
+
+/// An animated value for a single `box-shadow`.
+pub type BoxShadow = GenericBoxShadow<IntermediateColor, Length, Length>;
+
 /// An animated value for the `filter` property.
 #[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>;
 
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Debug, PartialEq)]
+/// An animated value for the `box-shadow` property.
+pub struct TextShadowList(pub Vec<SimpleShadow>);
+
 /// An animated value for the `drop-shadow()` filter.
 pub type SimpleShadow = GenericSimpleShadow<IntermediateColor, Length, Length>;
 
+impl From<BoxShadowList> for ComputedBoxShadowList {
+    fn from(list: BoxShadowList) -> Self {
+        ComputedBoxShadowList(list.0.into_iter().map(|s| s.into()).collect())
+    }
+}
+
+impl From<ComputedBoxShadowList> for BoxShadowList {
+    fn from(list: ComputedBoxShadowList) -> Self {
+        BoxShadowList(list.0.into_iter().map(|s| s.into()).collect())
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list
+impl Animatable for BoxShadowList {
+    #[inline]
+    fn add_weighted(
+        &self,
+        other: &Self,
+        self_portion: f64,
+        other_portion: f64,
+    ) -> Result<Self, ()> {
+        // The inset value must change
+        let mut zero = BoxShadow {
+            base: SimpleShadow {
+                color: IntermediateColor::transparent(),
+                horizontal: Au(0),
+                vertical: Au(0),
+                blur: Au(0),
+            },
+            spread: Au(0),
+            inset: false,
+        };
+
+        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.add_weighted(other, self_portion, other_portion)?
+                },
+                (Some(shadow), None) => {
+                    zero.inset = shadow.inset;
+                    shadow.add_weighted(&zero, self_portion, other_portion)?
+                },
+                (None, Some(shadow)) => {
+                    zero.inset = shadow.inset;
+                    zero.add_weighted(&shadow, self_portion, other_portion)?
+                },
+                (None, None) => unreachable!(),
+            });
+        }
+
+        Ok(BoxShadowList(shadows))
+    }
+
+    #[inline]
+    fn add(&self, other: &Self) -> Result<Self, ()> {
+        Ok(BoxShadowList(
+            self.0.iter().cloned().chain(other.0.iter().cloned()).collect(),
+        ))
+    }
+}
+
+impl From<TextShadowList> for ComputedTextShadowList {
+    fn from(list: TextShadowList) -> Self {
+        ComputedTextShadowList(list.0.into_iter().map(|s| s.into()).collect())
+    }
+}
+
+impl From<ComputedTextShadowList> for TextShadowList {
+    fn from(list: ComputedTextShadowList) -> Self {
+        TextShadowList(list.0.into_iter().map(|s| s.into()).collect())
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list
+impl Animatable for TextShadowList {
+    #[inline]
+    fn add_weighted(
+        &self,
+        other: &Self,
+        self_portion: f64,
+        other_portion: f64,
+    ) -> Result<Self, ()> {
+        let zero = SimpleShadow {
+            color: IntermediateColor::transparent(),
+            horizontal: Au(0),
+            vertical: Au(0),
+            blur: Au(0),
+        };
+
+        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.add_weighted(other, self_portion, other_portion)?
+                },
+                (Some(shadow), None) => {
+                    shadow.add_weighted(&zero, self_portion, other_portion)?
+                },
+                (None, Some(shadow)) => {
+                    zero.add_weighted(&shadow, self_portion, other_portion)?
+                },
+                (None, None) => unreachable!(),
+            });
+        }
+
+        Ok(TextShadowList(shadows))
+    }
+
+    #[inline]
+    fn add(&self, other: &Self) -> Result<Self, ()> {
+        Ok(TextShadowList(
+            self.0.iter().cloned().chain(other.0.iter().cloned()).collect(),
+        ))
+    }
+}
+
+impl From<ComputedBoxShadow> for BoxShadow {
+    #[inline]
+    fn from(shadow: ComputedBoxShadow) -> Self {
+        BoxShadow {
+            base: shadow.base.into(),
+            spread: shadow.spread,
+            inset: shadow.inset,
+        }
+    }
+}
+
+impl From<BoxShadow> for ComputedBoxShadow {
+    #[inline]
+    fn from(shadow: BoxShadow) -> Self {
+        ComputedBoxShadow {
+            base: shadow.base.into(),
+            spread: shadow.spread,
+            inset: shadow.inset,
+        }
+    }
+}
+
+impl Animatable for BoxShadow {
+    #[inline]
+    fn add_weighted(
+        &self,
+        other: &Self,
+        self_portion: f64,
+        other_portion: f64,
+    ) -> Result<Self, ()> {
+        if self.inset != other.inset {
+            return Err(());
+        }
+        Ok(BoxShadow {
+            base: self.base.add_weighted(&other.base, self_portion, other_portion)?,
+            spread: self.spread.add_weighted(&other.spread, self_portion, other_portion)?,
+            inset: self.inset,
+        })
+    }
+
+    #[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, ()> {
+        if self.inset != other.inset {
+            return Err(());
+        }
+        Ok(
+            self.base.compute_squared_distance(&other.base)? +
+            self.spread.compute_squared_distance(&other.spread)?,
+        )
+    }
+}
+
 impl From<ComputedFilterList> for FilterList {
     #[cfg(not(feature = "gecko"))]
     #[inline]
     fn from(filters: ComputedFilterList) -> Self {
         FilterList(filters.0)
     }
 
     #[cfg(feature = "gecko")]
@@ -72,17 +267,16 @@ impl From<ComputedFilter> for Filter {
             GenericFilter::HueRotate(factor) => GenericFilter::HueRotate(factor),
             GenericFilter::Invert(factor) => GenericFilter::Invert(factor),
             GenericFilter::Opacity(factor) => GenericFilter::Opacity(factor),
             GenericFilter::Saturate(factor) => GenericFilter::Saturate(factor),
             GenericFilter::Sepia(factor) => GenericFilter::Sepia(factor),
             GenericFilter::DropShadow(shadow) => {
                 GenericFilter::DropShadow(shadow.into())
             },
-            #[cfg(feature = "gecko")]
             GenericFilter::Url(url) => GenericFilter::Url(url),
         }
     }
 }
 
 #[cfg(feature = "gecko")]
 impl From<Filter> for ComputedFilter {
     #[inline]
@@ -95,17 +289,16 @@ impl From<Filter> for ComputedFilter {
             GenericFilter::HueRotate(factor) => GenericFilter::HueRotate(factor),
             GenericFilter::Invert(factor) => GenericFilter::Invert(factor),
             GenericFilter::Opacity(factor) => GenericFilter::Opacity(factor),
             GenericFilter::Saturate(factor) => GenericFilter::Saturate(factor),
             GenericFilter::Sepia(factor) => GenericFilter::Sepia(factor),
             GenericFilter::DropShadow(shadow) => {
                 GenericFilter::DropShadow(shadow.into())
             },
-            #[cfg(feature = "gecko")]
             GenericFilter::Url(url) => GenericFilter::Url(url.clone())
         }
     }
 }
 
 impl From<ComputedSimpleShadow> for SimpleShadow {
     #[inline]
     fn from(shadow: ComputedSimpleShadow) -> Self {
--- a/servo/components/style/values/computed/effects.rs
+++ b/servo/components/style/values/computed/effects.rs
@@ -4,19 +4,23 @@
 
 //! 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::BoxShadow as GenericBoxShadow;
 use values::generics::effects::Filter as GenericFilter;
 use values::generics::effects::SimpleShadow as GenericSimpleShadow;
 
+/// A computed value for a single shadow of the `box-shadow` property.
+pub type BoxShadow = GenericBoxShadow<Color, Length, Length>;
+
 /// 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/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -4,17 +4,16 @@
 
 //! Computed values.
 
 use Atom;
 use context::QuirksMode;
 use euclid::Size2D;
 use font_metrics::FontMetricsProvider;
 use media_queries::Device;
-use num_traits::Zero;
 #[cfg(feature = "gecko")]
 use properties;
 use properties::{ComputedValues, StyleBuilder};
 use std::f32;
 use std::f32::consts::PI;
 use std::fmt;
 use style_traits::ToCss;
 use super::{CSSFloat, CSSInteger, RGBA};
@@ -23,17 +22,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::Filter;
+pub use self::effects::{BoxShadow, Filter, SimpleShadow};
 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};
@@ -407,48 +406,16 @@ impl ToComputedValue for specified::Just
 #[cfg(feature = "gecko")]
 impl ComputedValueAsSpecified for specified::AlignItems {}
 #[cfg(feature = "gecko")]
 impl ComputedValueAsSpecified for specified::AlignJustifyContent {}
 #[cfg(feature = "gecko")]
 impl ComputedValueAsSpecified for specified::AlignJustifySelf {}
 impl ComputedValueAsSpecified for specified::BorderStyle {}
 
-#[derive(Debug, PartialEq, Clone, Copy)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub struct Shadow {
-    pub offset_x: Au,
-    pub offset_y: Au,
-    pub blur_radius: Au,
-    pub spread_radius: Au,
-    pub color: Color,
-    pub inset: bool,
-}
-
-impl ToCss for Shadow {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        if self.inset {
-            dest.write_str("inset ")?;
-        }
-        self.offset_x.to_css(dest)?;
-        dest.write_str(" ")?;
-        self.offset_y.to_css(dest)?;
-        dest.write_str(" ")?;
-        self.blur_radius.to_css(dest)?;
-        dest.write_str(" ")?;
-        if self.spread_radius != Au::zero() {
-            self.spread_radius.to_css(dest)?;
-            dest.write_str(" ")?;
-        }
-        self.color.to_css(dest)?;
-        Ok(())
-    }
-}
-
 /// A `<number>` value.
 pub type Number = CSSFloat;
 
 #[allow(missing_docs)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[derive(Clone, Copy, Debug, PartialEq, ToCss)]
 pub enum NumberOrPercentage {
     Percentage(Percentage),
--- a/servo/components/style/values/generics/effects.rs
+++ b/servo/components/style/values/generics/effects.rs
@@ -1,17 +1,31 @@
 /* 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::values::{SequenceWriter, ToCss};
 #[cfg(feature = "gecko")]
 use values::specified::url::SpecifiedUrl;
 
+/// A generic value for a single `box-shadow`.
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
+pub struct BoxShadow<Color, SizeLength, ShapeLength> {
+    /// The base shadow.
+    pub base: SimpleShadow<Color, SizeLength, ShapeLength>,
+    /// The spread radius.
+    pub spread: ShapeLength,
+    /// Whether this is an inset box shadow.
+    pub inset: bool,
+}
+
 /// 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>)`
@@ -57,8 +71,30 @@ pub struct SimpleShadow<Color, SizeLengt
     pub color: Color,
     /// Horizontal radius.
     pub horizontal: SizeLength,
     /// Vertical radius.
     pub vertical: SizeLength,
     /// Blur radius.
     pub blur: ShapeLength,
 }
+
+impl<Color, SizeLength, ShapeLength> ToCss for BoxShadow<Color, SizeLength, ShapeLength>
+where
+    Color: ToCss,
+    SizeLength: ToCss,
+    ShapeLength: ToCss,
+{
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
+    where
+        W: fmt::Write,
+    {
+        {
+            let mut writer = SequenceWriter::new(&mut *dest, " ");
+            writer.item(&self.base)?;
+            writer.item(&self.spread)?;
+        }
+        if self.inset {
+            dest.write_str(" inset")?;
+        }
+        Ok(())
+    }
+}
--- a/servo/components/style/values/specified/effects.rs
+++ b/servo/components/style/values/specified/effects.rs
@@ -1,29 +1,34 @@
 /* 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/. */
 
 //! Specified types for CSS values related to effects.
 
 use cssparser::{BasicParseError, Parser, Token};
 use parser::{Parse, ParserContext};
-use style_traits::ParseError;
+use style_traits::{ParseError, StyleParseError};
 #[cfg(not(feature = "gecko"))]
 use values::Impossible;
 use values::computed::{Context, Number as ComputedNumber, ToComputedValue};
+use values::computed::effects::BoxShadow as ComputedBoxShadow;
 use values::computed::effects::SimpleShadow as ComputedSimpleShadow;
+use values::generics::effects::BoxShadow as GenericBoxShadow;
 use values::generics::effects::Filter as GenericFilter;
 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 a single shadow of the `box-shadow` property.
+pub type BoxShadow = GenericBoxShadow<Option<Color>, Length, Option<Length>>;
+
 /// 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>;
 
@@ -37,16 +42,95 @@ 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 BoxShadow {
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Self, ParseError<'i>> {
+        let mut lengths = None;
+        let mut color = None;
+        let mut inset = false;
+
+        loop {
+            if !inset {
+                if input.try(|input| input.expect_ident_matching("inset")).is_ok() {
+                    inset = true;
+                    continue;
+                }
+            }
+            if lengths.is_none() {
+                let value = input.try::<_, _, ParseError>(|i| {
+                    let horizontal = Length::parse(context, i)?;
+                    let vertical = Length::parse(context, i)?;
+                    let (blur, spread) = match i.try::<_, _, ParseError>(|i| Length::parse_non_negative(context, i)) {
+                        Ok(blur) => {
+                            let spread = i.try(|i| Length::parse(context, i)).ok();
+                            (Some(blur), spread)
+                        },
+                        Err(_) => (None, None),
+                    };
+                    Ok((horizontal, vertical, blur, spread))
+                });
+                if let Ok(value) = value {
+                    lengths = Some(value);
+                    continue;
+                }
+            }
+            if color.is_none() {
+                if let Ok(value) = input.try(|i| Color::parse(context, i)) {
+                    color = Some(value);
+                    continue;
+                }
+            }
+            break;
+        }
+
+        let lengths = lengths.ok_or(StyleParseError::UnspecifiedError)?;
+        Ok(BoxShadow {
+            base: SimpleShadow {
+                color: color,
+                horizontal: lengths.0,
+                vertical: lengths.1,
+                blur: lengths.2,
+            },
+            spread: lengths.3,
+            inset: inset,
+        })
+    }
+}
+
+impl ToComputedValue for BoxShadow {
+    type ComputedValue = ComputedBoxShadow;
+
+    #[inline]
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        ComputedBoxShadow {
+            base: self.base.to_computed_value(context),
+            spread: self.spread.as_ref().unwrap_or(&Length::zero()).to_computed_value(context),
+            inset: self.inset,
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &ComputedBoxShadow) -> Self {
+        BoxShadow {
+            base: ToComputedValue::from_computed_value(&computed.base),
+            spread: Some(ToComputedValue::from_computed_value(&computed.spread)),
+            inset: computed.inset,
+        }
+    }
+}
+
 impl Parse for Filter {
     #[inline]
     fn parse<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>
     ) -> Result<Self, ParseError<'i>> {
         #[cfg(feature = "gecko")]
         {
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -14,31 +14,30 @@ use self::grid::TrackSizeOrRepeat;
 use self::url::SpecifiedUrl;
 use std::ascii::AsciiExt;
 use std::borrow::Cow;
 use std::f32;
 use std::fmt;
 use style_traits::{ToCss, ParseError, StyleParseError};
 use style_traits::values::specified::AllowedNumericType;
 use super::{Auto, CSSFloat, CSSInteger, Either, None_};
-use super::computed::{self, Context};
-use super::computed::{Shadow as ComputedShadow, ToComputedValue};
+use super::computed::{self, Context, ToComputedValue};
 use super::generics::grid::{TrackBreadth as GenericTrackBreadth, TrackSize as GenericTrackSize};
 use super::generics::grid::TrackList as GenericTrackList;
 use values::computed::ComputedValueAsSpecified;
 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::Filter;
+pub use self::effects::{BoxShadow, Filter, SimpleShadow};
 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};
@@ -685,140 +684,16 @@ pub type TrackSize = GenericTrackSize<Le
 
 /// The specified value of a grid `<track-list>`
 /// (could also be `<auto-track-list>` or `<explicit-track-list>`)
 pub type TrackList = GenericTrackList<TrackSizeOrRepeat>;
 
 /// `<track-list> | none`
 pub type TrackListOrNone = Either<TrackList, None_>;
 
-#[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub struct Shadow {
-    pub offset_x: Length,
-    pub offset_y: Length,
-    pub blur_radius: Length,
-    pub spread_radius: Length,
-    pub color: Option<Color>,
-    pub inset: bool,
-}
-
-impl ToComputedValue for Shadow {
-    type ComputedValue = ComputedShadow;
-
-    #[inline]
-    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
-        ComputedShadow {
-            offset_x: self.offset_x.to_computed_value(context),
-            offset_y: self.offset_y.to_computed_value(context),
-            blur_radius: self.blur_radius.to_computed_value(context),
-            spread_radius: self.spread_radius.to_computed_value(context),
-            color: self.color.as_ref().unwrap_or(&Color::CurrentColor)
-                             .to_computed_value(context),
-            inset: self.inset,
-        }
-    }
-
-    #[inline]
-    fn from_computed_value(computed: &ComputedShadow) -> Self {
-        Shadow {
-            offset_x: ToComputedValue::from_computed_value(&computed.offset_x),
-            offset_y: ToComputedValue::from_computed_value(&computed.offset_y),
-            blur_radius: ToComputedValue::from_computed_value(&computed.blur_radius),
-            spread_radius: ToComputedValue::from_computed_value(&computed.spread_radius),
-            color: Some(ToComputedValue::from_computed_value(&computed.color)),
-            inset: computed.inset,
-        }
-    }
-}
-
-impl ToCss for Shadow {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        if self.inset {
-            dest.write_str("inset ")?;
-        }
-        self.offset_x.to_css(dest)?;
-        dest.write_str(" ")?;
-        self.offset_y.to_css(dest)?;
-        dest.write_str(" ")?;
-        self.blur_radius.to_css(dest)?;
-        if self.spread_radius != Length::zero() {
-            dest.write_str(" ")?;
-            self.spread_radius.to_css(dest)?;
-        }
-        if let Some(ref color) = self.color {
-            dest.write_str(" ")?;
-            color.to_css(dest)?;
-        }
-        Ok(())
-    }
-}
-
-impl Shadow {
-    // disable_spread_and_inset is for filter: drop-shadow(...)
-    #[allow(missing_docs)]
-    pub fn parse<'i, 't>(context: &ParserContext,
-                         input: &mut Parser<'i, 't>,
-                         disable_spread_and_inset: bool)
-                         -> Result<Shadow, ParseError<'i>> {
-        let mut lengths = [Length::zero(), Length::zero(), Length::zero(), Length::zero()];
-        let mut lengths_parsed = false;
-        let mut color = None;
-        let mut inset = false;
-
-        loop {
-            if !inset && !disable_spread_and_inset {
-                if input.try(|input| input.expect_ident_matching("inset")).is_ok() {
-                    inset = true;
-                    continue
-                }
-            }
-            if !lengths_parsed {
-                if let Ok(value) = input.try(|i| Length::parse(context, i)) {
-                    lengths[0] = value;
-                    lengths[1] = Length::parse(context, input)?;
-                    if let Ok(value) = input.try(|i| Length::parse_non_negative(context, i)) {
-                        lengths[2] = value;
-                        if !disable_spread_and_inset {
-                            if let Ok(value) = input.try(|i| Length::parse(context, i)) {
-                                lengths[3] = value;
-                            }
-                        }
-                    }
-                    lengths_parsed = true;
-                    continue
-                }
-            }
-            if color.is_none() {
-                if let Ok(value) = input.try(|i| Color::parse(context, i)) {
-                    color = Some(value);
-                    continue
-                }
-            }
-            break
-        }
-
-        // Lengths must be specified.
-        if !lengths_parsed {
-            return Err(StyleParseError::UnspecifiedError.into())
-        }
-
-        debug_assert!(!disable_spread_and_inset || lengths[3] == Length::zero());
-        Ok(Shadow {
-            offset_x: lengths[0].take(),
-            offset_y: lengths[1].take(),
-            blur_radius: lengths[2].take(),
-            spread_radius: lengths[3].take(),
-            color: color,
-            inset: inset,
-        })
-    }
-}
-
 no_viewport_percentage!(SVGPaint);
 
 /// Specified SVG Paint value
 pub type SVGPaint = ::values::generics::SVGPaint<RGBAColor>;
 
 /// Specified SVG Paint Kind value
 pub type SVGPaintKind = ::values::generics::SVGPaintKind<RGBAColor>;
 
--- a/servo/tests/unit/style/properties/serialization.rs
+++ b/servo/tests/unit/style/properties/serialization.rs
@@ -1229,26 +1229,34 @@ mod shorthand_serialization {
 
             let serialization = block.to_css_string();
             assert_eq!(serialization, block_text);
         }
     }
 
     mod effects {
         pub use super::*;
-        pub use style::properties::longhands::box_shadow::SpecifiedValue as BoxShadow;
-        pub use style::values::specified::Shadow;
+        pub use style::properties::longhands::box_shadow::SpecifiedValue as BoxShadowList;
+        pub use style::values::specified::effects::{BoxShadow, SimpleShadow};
 
         #[test]
         fn box_shadow_should_serialize_correctly() {
             let mut properties = Vec::new();
-            let shadow_val = Shadow { offset_x: Length::from_px(1f32), offset_y: Length::from_px(2f32),
-            blur_radius: Length::from_px(3f32), spread_radius: Length::from_px(4f32), color: None, inset: false };
-            let shadow_decl = BoxShadow(vec![shadow_val]);
-            properties.push(PropertyDeclaration:: BoxShadow(shadow_decl));
+            let shadow_val = BoxShadow {
+                base: SimpleShadow {
+                    color: None,
+                    horizontal: Length::from_px(1f32),
+                    vertical: Length::from_px(2f32),
+                    blur: Some(Length::from_px(3f32)),
+                },
+                spread: Some(Length::from_px(4f32)),
+                inset: false,
+            };
+            let shadow_decl = BoxShadowList(vec![shadow_val]);
+            properties.push(PropertyDeclaration::BoxShadow(shadow_decl));
             let shadow_css = "box-shadow: 1px 2px 3px 4px;";
             let shadow = parse(|c, i| Ok(parse_property_declaration_list(c, i)), shadow_css).unwrap();
 
             assert_eq!(shadow.to_css_string(), shadow_css);
         }
     }
 
     mod counter_increment {