servo: Merge #16859 - Rewrite style images with a good dose of generics ๐Ÿ’‰ (from nox:gradients); r=emilio
authorAnthony Ramine <n.oxyde@gmail.com>
Mon, 15 May 2017 09:13:38 -0500
changeset 358474 27e007f58a3687c80fc3a4816fb7adcabfaab07f
parent 358473 1a4d93b432d98329335ae4d242d79c85fb23879b
child 358475 e5ce4b08fb6f1238db4903f063e12abba75e81c3
push id90339
push userkwierso@gmail.com
push dateMon, 15 May 2017 23:34:45 +0000
treeherdermozilla-inbound@30813e40f36c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #16859 - Rewrite style images with a good dose of generics ๐Ÿ’‰ (from nox:gradients); r=emilio Source-Repo: https://github.com/servo/servo Source-Revision: eb7314b4122ba981cdc98204660f9fa1ef43e698
servo/components/layout/display_list_builder.rs
servo/components/script/dom/element.rs
servo/components/style/gecko/conversions.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/longhand/background.mako.rs
servo/components/style/properties/longhand/border.mako.rs
servo/components/style/properties/longhand/svg.mako.rs
servo/components/style/values/computed/image.rs
servo/components/style/values/computed/length.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/generics/image.rs
servo/components/style/values/generics/mod.rs
servo/components/style/values/specified/image.rs
servo/components/style/values/specified/length.rs
servo/components/style/values/specified/mod.rs
servo/ports/geckolib/glue.rs
servo/tests/unit/style/parsing/image.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,22 +52,24 @@ use style::computed_values::{background_
 use style::computed_values::{image_rendering, overflow_x, pointer_events, position, visibility};
 use style::computed_values::filter::Filter;
 use style::computed_values::text_shadow::TextShadow;
 use style::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode};
 use style::properties::{self, ServoComputedValues};
 use style::properties::longhands::border_image_repeat::computed_value::RepeatKeyword;
 use style::properties::style_structs;
 use style::servo::restyle_damage::REPAINT;
-use style::values::{Either, RGBA, computed};
-use style::values::computed::{AngleOrCorner, Gradient, GradientItem, GradientKind, LengthOrPercentage};
-use style::values::computed::{LengthOrPercentageOrAuto, LengthOrKeyword, LengthOrPercentageOrKeyword};
-use style::values::computed::{NumberOrPercentage, Position};
-use style::values::computed::image::{EndingShape, SizeKeyword};
-use style::values::specified::{HorizontalDirection, VerticalDirection};
+use style::values::{Either, RGBA};
+use style::values::computed::{Gradient, GradientItem, LengthOrPercentage};
+use style::values::computed::{LengthOrPercentageOrAuto, NumberOrPercentage, Position};
+use style::values::computed::image::{EndingShape, LineDirection};
+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::specified::position::{X, Y};
 use style_traits::CSSPixel;
 use style_traits::cursor::Cursor;
 use table_cell::CollapsedBordersForCell;
 use webrender_traits::{ColorF, ClipId, GradientStop, RepeatMode, ScrollPolicy};
 
 trait ResolvePercentage {
     fn resolve(&self, length: u32) -> u32;
 }
@@ -385,17 +387,17 @@ pub trait FragmentDisplayListBuilding {
                                                absolute_bounds: &Rect<Au>,
                                                clip: &ClippingRegion,
                                                image_url: &ServoUrl,
                                                background_index: usize);
 
     fn convert_linear_gradient(&self,
                                bounds: &Rect<Au>,
                                stops: &[GradientItem],
-                               angle_or_corner: &AngleOrCorner,
+                               direction: &LineDirection,
                                repeating: bool,
                                style: &ServoComputedValues)
                                -> display_list::Gradient;
 
     fn convert_radial_gradient(&self,
                                bounds: &Rect<Au>,
                                stops: &[GradientItem],
                                shape: &EndingShape,
@@ -605,17 +607,17 @@ fn build_border_radius_for_inner_rect(ou
 fn convert_gradient_stops(gradient_items: &[GradientItem],
                           total_length: Au,
                           style: &ServoComputedValues) -> Vec<GradientStop> {
     // Determine the position of each stop per CSS-IMAGES ยง 3.4.
 
     // Only keep the color stops, discard the color interpolation hints.
     let mut stop_items = gradient_items.iter().filter_map(|item| {
         match *item {
-            GradientItem::ColorStop(ref stop) => Some(*stop),
+            GenericGradientItem::ColorStop(ref stop) => Some(*stop),
             _ => None,
         }
     }).collect::<Vec<_>>();
 
     assert!(stop_items.len() >= 2);
 
     // Run the algorithm from
     // https://drafts.csswg.org/css-images-3/#color-stop-syntax
@@ -753,46 +755,56 @@ fn get_ellipse_radius<F>(size: &Size2D<A
 {
     let dist = get_distance_to_sides(size, center, cmp);
     Size2D::new(dist.width.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
                 dist.height.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0))
 }
 
 /// Determines the radius of a circle if it was not explictly provided.
 /// https://drafts.csswg.org/css-images-3/#typedef-size
-fn convert_circle_size_keyword(keyword: SizeKeyword,
+fn convert_circle_size_keyword(keyword: ShapeExtent,
                                size: &Size2D<Au>,
                                center: &Point2D<Au>) -> Size2D<Au> {
-    use style::values::computed::image::SizeKeyword::*;
     let radius = match keyword {
-        ClosestSide | Contain => {
+        ShapeExtent::ClosestSide | ShapeExtent::Contain => {
             let dist = get_distance_to_sides(size, center, ::std::cmp::min);
             ::std::cmp::min(dist.width, dist.height)
         }
-        FarthestSide => {
+        ShapeExtent::FarthestSide => {
             let dist = get_distance_to_sides(size, center, ::std::cmp::max);
             ::std::cmp::max(dist.width, dist.height)
         }
-        ClosestCorner => get_distance_to_corner(size, center, ::std::cmp::min),
-        FarthestCorner | Cover => get_distance_to_corner(size, center, ::std::cmp::max),
+        ShapeExtent::ClosestCorner => {
+            get_distance_to_corner(size, center, ::std::cmp::min)
+        },
+        ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
+            get_distance_to_corner(size, center, ::std::cmp::max)
+        },
     };
     Size2D::new(radius, radius)
 }
 
 /// Determines the radius of an ellipse if it was not explictly provided.
 /// https://drafts.csswg.org/css-images-3/#typedef-size
-fn convert_ellipse_size_keyword(keyword: SizeKeyword,
+fn convert_ellipse_size_keyword(keyword: ShapeExtent,
                                 size: &Size2D<Au>,
                                 center: &Point2D<Au>) -> Size2D<Au> {
-    use style::values::computed::image::SizeKeyword::*;
     match keyword {
-        ClosestSide | Contain => get_distance_to_sides(size, center, ::std::cmp::min),
-        FarthestSide => get_distance_to_sides(size, center, ::std::cmp::max),
-        ClosestCorner => get_ellipse_radius(size, center, ::std::cmp::min),
-        FarthestCorner | Cover => get_ellipse_radius(size, center, ::std::cmp::max),
+        ShapeExtent::ClosestSide | ShapeExtent::Contain => {
+            get_distance_to_sides(size, center, ::std::cmp::min)
+        },
+        ShapeExtent::FarthestSide => {
+            get_distance_to_sides(size, center, ::std::cmp::max)
+        },
+        ShapeExtent::ClosestCorner => {
+            get_ellipse_radius(size, center, ::std::cmp::min)
+        },
+        ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
+            get_ellipse_radius(size, center, ::std::cmp::max)
+        },
     }
 }
 
 impl FragmentDisplayListBuilding for Fragment {
     fn build_display_list_for_background_if_applicable(&self,
                                                        state: &mut DisplayListBuildState,
                                                        style: &ServoComputedValues,
                                                        display_list_section: DisplayListSection,
@@ -848,42 +860,42 @@ impl FragmentDisplayListBuilding for Fra
                 color: background_color.to_gfx_color(),
             }));
 
         // The background image is painted on top of the background color.
         // Implements background image, per spec:
         // http://www.w3.org/TR/CSS21/colors.html#background
         let background = style.get_background();
         for (i, background_image) in background.background_image.0.iter().enumerate().rev() {
-            match background_image.0 {
-                None => {}
-                Some(computed::Image::Gradient(ref gradient)) => {
+            match *background_image {
+                Either::First(_) => {}
+                Either::Second(Image::Gradient(ref gradient)) => {
                     self.build_display_list_for_background_gradient(state,
                                                                     display_list_section,
                                                                     &absolute_bounds,
                                                                     &bounds,
                                                                     &clip,
                                                                     gradient,
                                                                     style);
                 }
-                Some(computed::Image::Url(ref image_url)) => {
+                Either::Second(Image::Url(ref image_url)) => {
                     if let Some(url) = image_url.url() {
                         self.build_display_list_for_background_image(state,
                                                                      style,
                                                                      display_list_section,
                                                                      &bounds,
                                                                      &clip,
                                                                      url,
                                                                      i);
                     }
                 }
-                Some(computed::Image::ImageRect(_)) => {
+                Either::Second(Image::Rect(_)) => {
                     // TODO: Implement `-moz-image-rect`
                 }
-                Some(computed::Image::Element(_)) => {
+                Either::Second(Image::Element(_)) => {
                     // TODO: Implement `-moz-element`
                 }
             }
         }
     }
 
     fn compute_background_image_size(&self,
                                      style: &ServoComputedValues,
@@ -1092,36 +1104,36 @@ impl FragmentDisplayListBuilding for Fra
             }));
 
         }
     }
 
     fn convert_linear_gradient(&self,
                                bounds: &Rect<Au>,
                                stops: &[GradientItem],
-                               angle_or_corner: &AngleOrCorner,
+                               direction: &LineDirection,
                                repeating: bool,
                                style: &ServoComputedValues)
                                -> display_list::Gradient {
-        let angle = match *angle_or_corner {
-            AngleOrCorner::Angle(angle) => angle.radians(),
-            AngleOrCorner::Corner(horizontal, vertical) => {
+        let angle = match *direction {
+            LineDirection::Angle(angle) => angle.radians(),
+            LineDirection::Corner(horizontal, vertical) => {
                 // This the angle for one of the diagonals of the box. Our angle
                 // will either be this one, this one + PI, or one of the other
                 // two perpendicular angles.
                 let atan = (bounds.size.height.to_f32_px() /
                             bounds.size.width.to_f32_px()).atan();
                 match (horizontal, vertical) {
-                    (HorizontalDirection::Right, VerticalDirection::Bottom)
+                    (X::Right, Y::Bottom)
                         => f32::consts::PI - atan,
-                    (HorizontalDirection::Left, VerticalDirection::Bottom)
+                    (X::Left, Y::Bottom)
                         => f32::consts::PI + atan,
-                    (HorizontalDirection::Right, VerticalDirection::Top)
+                    (X::Right, Y::Top)
                         => atan,
-                    (HorizontalDirection::Left, VerticalDirection::Top)
+                    (X::Left, Y::Top)
                         => -atan,
                 }
             }
         };
 
         // Get correct gradient line length, based on:
         // https://drafts.csswg.org/css-images-3/#linear-gradients
         let dir = Point2D::new(angle.sin(), -angle.cos());
@@ -1165,26 +1177,28 @@ impl FragmentDisplayListBuilding for Fra
                                shape: &EndingShape,
                                center: &Position,
                                repeating: bool,
                                style: &ServoComputedValues)
                                -> display_list::RadialGradient {
         let center = Point2D::new(specified(center.horizontal, bounds.size.width),
                                   specified(center.vertical, bounds.size.height));
         let radius = match *shape {
-            EndingShape::Circle(LengthOrKeyword::Length(length))
-                => Size2D::new(length, length),
-            EndingShape::Circle(LengthOrKeyword::Keyword(word))
-                => convert_circle_size_keyword(word, &bounds.size, &center),
-            EndingShape::Ellipse(LengthOrPercentageOrKeyword::LengthOrPercentage(horizontal,
-                                                                                    vertical))
-                    => Size2D::new(specified(horizontal, bounds.size.width),
-                                specified(vertical, bounds.size.height)),
-            EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(word))
-                => convert_ellipse_size_keyword(word, &bounds.size, &center),
+            GenericEndingShape::Circle(Circle::Radius(length)) => {
+                Size2D::new(length, length)
+            },
+            GenericEndingShape::Circle(Circle::Extent(extent)) => {
+                convert_circle_size_keyword(extent, &bounds.size, &center)
+            },
+            GenericEndingShape::Ellipse(Ellipse::Radii(x, y)) => {
+                Size2D::new(specified(x, bounds.size.width), specified(y, bounds.size.height))
+            },
+            GenericEndingShape::Ellipse(Ellipse::Extent(extent)) => {
+                convert_ellipse_size_keyword(extent, &bounds.size, &center)
+            },
         };
 
         let mut stops = convert_gradient_stops(stops, radius.width, style);
         // Repeating gradients have no last stops that can be ignored. So
         // fixup is not necessary but may actually break the gradient.
         if !repeating {
             fix_gradient_stops(&mut stops);
         }
@@ -1216,17 +1230,17 @@ impl FragmentDisplayListBuilding for Fra
         bounds.size.height = bounds.size.height - border_padding.vertical();
 
         let base = state.create_base_display_item(&bounds,
                                                   &clip,
                                                   self.node,
                                                   style.get_cursor(Cursor::Default),
                                                   display_list_section);
 
-        let display_item = match gradient.gradient_kind {
+        let display_item = match gradient.kind {
             GradientKind::Linear(ref angle_or_corner) => {
                 let gradient = self.convert_linear_gradient(&bounds,
                                                             &gradient.items[..],
                                                             angle_or_corner,
                                                             gradient.repeating,
                                                             style);
                 DisplayItem::Gradient(box GradientDisplayItem {
                     base: base,
@@ -1337,33 +1351,33 @@ impl FragmentDisplayListBuilding for Fra
 
         // Append the border to the display list.
         let base = state.create_base_display_item(&bounds,
                                                   &ClippingRegion::from_rect(&clip),
                                                   self.node,
                                                   style.get_cursor(Cursor::Default),
                                                   display_list_section);
 
-        match border_style_struct.border_image_source.0 {
-            None => {
+        match border_style_struct.border_image_source {
+            Either::First(_) => {
                 state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
                     base: base,
                     border_widths: border.to_physical(style.writing_mode),
                     details: BorderDetails::Normal(NormalBorder {
                         color: SideOffsets2D::new(colors.top.to_gfx_color(),
                                                   colors.right.to_gfx_color(),
                                                   colors.bottom.to_gfx_color(),
                                                   colors.left.to_gfx_color()),
                         style: border_style,
                         radius: build_border_radius(&bounds, border_style_struct),
                     }),
                 }));
             }
-            Some(computed::Image::Gradient(ref gradient)) => {
-                match gradient.gradient_kind {
+            Either::Second(Image::Gradient(ref gradient)) => {
+                match gradient.kind {
                     GradientKind::Linear(angle_or_corner) => {
                         let grad = self.convert_linear_gradient(&bounds,
                                                                 &gradient.items[..],
                                                                 &angle_or_corner,
                                                                 gradient.repeating,
                                                                 style);
 
                         state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
@@ -1393,23 +1407,23 @@ impl FragmentDisplayListBuilding for Fra
 
                                     // TODO(gw): Support border-image-outset
                                     outset: SideOffsets2D::zero(),
                                 }),
                         }));
                     }
                 }
             }
-            Some(computed::Image::ImageRect(..)) => {
+            Either::Second(Image::Rect(..)) => {
                 // TODO: Handle border-image with `-moz-image-rect`.
             }
-            Some(computed::Image::Element(..)) => {
+            Either::Second(Image::Element(..)) => {
                 // TODO: Handle border-image with `-moz-element`.
             }
-            Some(computed::Image::Url(ref image_url)) => {
+            Either::Second(Image::Url(ref image_url)) => {
                 if let Some(url) = image_url.url() {
                     let webrender_image = state.layout_context
                                                .get_webrender_image_for_url(self.node,
                                                                             url.clone(),
                                                                             UsePlaceholder::No);
                     if let Some(webrender_image) = webrender_image {
                         // The corners array is guaranteed to be len=4 by the css parser.
                         let corners = &border_style_struct.border_image_slice.corners;
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -104,17 +104,17 @@ use style::properties::longhands::{self,
 use style::restyle_hints::RESTYLE_SELF;
 use style::rule_tree::CascadeLevel;
 use style::selector_parser::{NonTSPseudoClass, RestyleDamage, SelectorImpl, SelectorParser};
 use style::shared_lock::{SharedRwLock, Locked};
 use style::sink::Push;
 use style::stylearc::Arc;
 use style::stylist::ApplicableDeclarationBlock;
 use style::thread_state;
-use style::values::CSSFloat;
+use style::values::{CSSFloat, Either};
 use style::values::specified::{self, CSSColor};
 use stylesheet_loader::StylesheetOwner;
 
 // TODO: Update focus state when the top-level browsing context gains or loses system focus,
 // and when the element enters or leaves a browsing context container.
 // https://html.spec.whatwg.org/multipage/#selector-focus
 
 #[dom_struct]
@@ -422,19 +422,17 @@ impl LayoutElementHelpers for LayoutJS<E
             None
         };
 
         if let Some(url) = background {
             hints.push(from_declaration(
                 shared_lock,
                 PropertyDeclaration::BackgroundImage(
                     background_image::SpecifiedValue(vec![
-                        background_image::single_value::SpecifiedValue(Some(
-                            specified::Image::for_cascade(url.into())
-                        ))
+                        Either::Second(specified::Image::for_cascade(url.into()))
                     ]))));
         }
 
         let color = if let Some(this) = self.downcast::<HTMLFontElement>() {
             this.get_color()
         } else if let Some(this) = self.downcast::<HTMLBodyElement>() {
             // https://html.spec.whatwg.org/multipage/#the-page:the-body-element-20
             this.get_color()
--- a/servo/components/style/gecko/conversions.rs
+++ b/servo/components/style/gecko/conversions.rs
@@ -11,18 +11,19 @@
 use app_units::Au;
 use gecko::values::{convert_rgba_to_nscolor, GeckoStyleCoordConvertible};
 use gecko_bindings::bindings::{Gecko_CreateGradient, Gecko_SetGradientImageValue, Gecko_SetUrlImageValue};
 use gecko_bindings::bindings::{Gecko_InitializeImageCropRect, Gecko_SetImageElement};
 use gecko_bindings::structs::{nsCSSUnit, nsStyleCoord_CalcValue, nsStyleImage};
 use gecko_bindings::structs::{nsresult, SheetType};
 use gecko_bindings::sugar::ns_style_coord::{CoordDataValue, CoordDataMut};
 use stylesheets::{Origin, RulesMutateError};
-use values::computed::{Angle, CalcLengthOrPercentage, Gradient, GradientItem, Image};
+use values::computed::{Angle, CalcLengthOrPercentage, Gradient, Image};
 use values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto};
+use values::generics::image::{CompatMode, Image as GenericImage, GradientItem};
 
 impl From<CalcLengthOrPercentage> for nsStyleCoord_CalcValue {
     fn from(other: CalcLengthOrPercentage) -> nsStyleCoord_CalcValue {
         let has_percentage = other.percentage.is_some();
         nsStyleCoord_CalcValue {
             mLength: other.length.0,
             mPercent: other.percentage.unwrap_or(0.0),
             mHasPercent: has_percentage,
@@ -133,33 +134,33 @@ impl Angle {
         }
     }
 }
 
 impl nsStyleImage {
     /// Set a given Servo `Image` value into this `nsStyleImage`.
     pub fn set(&mut self, image: Image, cacheable: &mut bool) {
         match image {
-            Image::Gradient(gradient) => {
+            GenericImage::Gradient(gradient) => {
                 self.set_gradient(gradient)
             },
-            Image::Url(ref url) => {
+            GenericImage::Url(ref url) => {
                 unsafe {
                     Gecko_SetUrlImageValue(self, url.for_ffi());
                     // We unfortunately must make any url() value uncacheable, since
                     // the applicable declarations cache is not per document, but
                     // global, and the imgRequestProxy objects we store in the style
                     // structs don't like to be tracked by more than one document.
                     //
                     // FIXME(emilio): With the scoped TLS thing this is no longer
                     // true, remove this line in a follow-up!
                     *cacheable = false;
                 }
             },
-            Image::ImageRect(ref image_rect) => {
+            GenericImage::Rect(ref image_rect) => {
                 unsafe {
                     Gecko_SetUrlImageValue(self, image_rect.url.for_ffi());
                     Gecko_InitializeImageCropRect(self);
 
                     // We unfortunately must make any url() value uncacheable, since
                     // the applicable declarations cache is not per document, but
                     // global, and the imgRequestProxy objects we store in the style
                     // structs don't like to be tracked by more than one document.
@@ -171,145 +172,142 @@ impl nsStyleImage {
                     // Set CropRect
                     let ref mut rect = *self.mCropRect.mPtr;
                     image_rect.top.to_gecko_style_coord(&mut rect.data_at_mut(0));
                     image_rect.right.to_gecko_style_coord(&mut rect.data_at_mut(1));
                     image_rect.bottom.to_gecko_style_coord(&mut rect.data_at_mut(2));
                     image_rect.left.to_gecko_style_coord(&mut rect.data_at_mut(3));
                 }
             }
-            Image::Element(ref element) => {
+            GenericImage::Element(ref element) => {
                 unsafe {
                     Gecko_SetImageElement(self, element.as_ptr());
                 }
             }
         }
     }
 
     fn set_gradient(&mut self, gradient: Gradient) {
         use cssparser::Color as CSSColor;
         use gecko_bindings::structs::{NS_STYLE_GRADIENT_SHAPE_CIRCULAR, NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL};
         use gecko_bindings::structs::{NS_STYLE_GRADIENT_SHAPE_LINEAR, NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER};
         use gecko_bindings::structs::{NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE, NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE};
         use gecko_bindings::structs::{NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER, NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE};
         use gecko_bindings::structs::nsStyleCoord;
-        use values::computed::{AngleOrCorner, GradientKind, GradientShape, LengthOrKeyword};
-        use values::computed::LengthOrPercentageOrKeyword;
-        use values::specified::{HorizontalDirection, SizeKeyword, VerticalDirection};
+        use values::computed::image::LineDirection;
+        use values::generics::image::{Circle, Ellipse, EndingShape, GradientKind, ShapeExtent};
+        use values::specified::position::{X, Y};
 
         let stop_count = gradient.items.len();
         if stop_count >= ::std::u32::MAX as usize {
             warn!("stylo: Prevented overflow due to too many gradient stops");
             return;
         }
 
-        let gecko_gradient = match gradient.gradient_kind {
-            GradientKind::Linear(angle_or_corner) => {
+        let gecko_gradient = match gradient.kind {
+            GradientKind::Linear(direction) => {
                 let gecko_gradient = unsafe {
                     Gecko_CreateGradient(NS_STYLE_GRADIENT_SHAPE_LINEAR as u8,
                                          NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER as u8,
                                          gradient.repeating,
-                                         /* legacy_syntax = */ false,
+                                         gradient.compat_mode == CompatMode::WebKit,
                                          stop_count as u32)
                 };
 
-                match angle_or_corner {
-                    AngleOrCorner::Angle(angle) => {
+                match direction {
+                    LineDirection::Angle(angle) => {
                         unsafe {
                             (*gecko_gradient).mAngle.set(angle);
                             (*gecko_gradient).mBgPosX.set_value(CoordDataValue::None);
                             (*gecko_gradient).mBgPosY.set_value(CoordDataValue::None);
                         }
                     },
-                    AngleOrCorner::Corner(horiz, vert) => {
+                    LineDirection::Corner(horiz, vert) => {
                         let percent_x = match horiz {
-                            HorizontalDirection::Left => 0.0,
-                            HorizontalDirection::Right => 1.0,
+                            X::Left => 0.0,
+                            X::Right => 1.0,
                         };
                         let percent_y = match vert {
-                            VerticalDirection::Top => 0.0,
-                            VerticalDirection::Bottom => 1.0,
+                            Y::Top => 0.0,
+                            Y::Bottom => 1.0,
                         };
 
                         unsafe {
                             (*gecko_gradient).mAngle.set_value(CoordDataValue::None);
                             (*gecko_gradient).mBgPosX
                                              .set_value(CoordDataValue::Percent(percent_x));
                             (*gecko_gradient).mBgPosY
                                              .set_value(CoordDataValue::Percent(percent_y));
                         }
                     }
                 }
                 gecko_gradient
             },
             GradientKind::Radial(shape, position) => {
                 let keyword_to_gecko_size = |keyword| {
                     match keyword {
-                        SizeKeyword::ClosestSide => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE,
-                        SizeKeyword::FarthestSide => NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE,
-                        SizeKeyword::ClosestCorner => NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER,
-                        SizeKeyword::FarthestCorner => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER,
-                        SizeKeyword::Contain => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE,
-                        SizeKeyword::Cover => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER,
+                        ShapeExtent::ClosestSide => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE,
+                        ShapeExtent::FarthestSide => NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE,
+                        ShapeExtent::ClosestCorner => NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER,
+                        ShapeExtent::FarthestCorner => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER,
+                        ShapeExtent::Contain => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE,
+                        ShapeExtent::Cover => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER,
                     }
                 };
                 let (gecko_shape, gecko_size) = match shape {
-                    GradientShape::Circle(ref length) => {
-                        let size = match *length {
-                            LengthOrKeyword::Keyword(keyword) => {
-                                keyword_to_gecko_size(keyword)
+                    EndingShape::Circle(ref circle) => {
+                        let size = match *circle {
+                            Circle::Extent(extent) => {
+                                keyword_to_gecko_size(extent)
                             },
                             _ => NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE,
                         };
                         (NS_STYLE_GRADIENT_SHAPE_CIRCULAR as u8, size as u8)
                     },
-                    GradientShape::Ellipse(ref length) => {
-                        let size = match *length {
-                            LengthOrPercentageOrKeyword::Keyword(keyword) => {
-                                keyword_to_gecko_size(keyword)
+                    EndingShape::Ellipse(ref ellipse) => {
+                        let size = match *ellipse {
+                            Ellipse::Extent(extent) => {
+                                keyword_to_gecko_size(extent)
                             },
                             _ => NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE,
                         };
                         (NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL as u8, size as u8)
                     }
                 };
 
                 let gecko_gradient = unsafe {
                     Gecko_CreateGradient(gecko_shape,
                                          gecko_size,
                                          gradient.repeating,
-                                         /* legacy_syntax = */ false,
+                                         gradient.compat_mode == CompatMode::WebKit,
                                          stop_count as u32)
                 };
 
                 // Clear mAngle and mBgPos fields
                 unsafe {
                     (*gecko_gradient).mAngle.set_value(CoordDataValue::None);
                     (*gecko_gradient).mBgPosX.set_value(CoordDataValue::None);
                     (*gecko_gradient).mBgPosY.set_value(CoordDataValue::None);
                 }
 
                 // Setting radius values depending shape
                 match shape {
-                    GradientShape::Circle(length) => {
-                        if let LengthOrKeyword::Length(len) = length {
-                            unsafe {
-                                (*gecko_gradient).mRadiusX.set_value(CoordDataValue::Coord(len.0));
-                                (*gecko_gradient).mRadiusY.set_value(CoordDataValue::Coord(len.0));
-                            }
+                    EndingShape::Circle(Circle::Radius(length)) => {
+                        unsafe {
+                            (*gecko_gradient).mRadiusX.set_value(CoordDataValue::Coord(length.0));
+                            (*gecko_gradient).mRadiusY.set_value(CoordDataValue::Coord(length.0));
                         }
                     },
-                    GradientShape::Ellipse(length) => {
-                        if let LengthOrPercentageOrKeyword::LengthOrPercentage(first_len, second_len) = length {
-                            unsafe {
-                                (*gecko_gradient).mRadiusX.set(first_len);
-                                (*gecko_gradient).mRadiusY.set(second_len);
-                            }
+                    EndingShape::Ellipse(Ellipse::Radii(x, y)) => {
+                        unsafe {
+                            (*gecko_gradient).mRadiusX.set(x);
+                            (*gecko_gradient).mRadiusY.set(y);
                         }
                     },
+                    _ => {},
                 }
                 unsafe {
                     (*gecko_gradient).mBgPosX.set(position.horizontal);
                     (*gecko_gradient).mBgPosY.set(position.vertical);
                 }
 
                 gecko_gradient
             },
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -922,17 +922,17 @@ fn static_assert() {
     % endfor
 
     pub fn set_border_image_source(&mut self, image: longhands::border_image_source::computed_value::T) {
         unsafe {
             // Prevent leaking of the last elements we did set
             Gecko_SetNullImageValue(&mut self.gecko.mBorderImageSource);
         }
 
-        if let Some(image) = image.0 {
+        if let Either::Second(image) = image {
             self.gecko.mBorderImageSource.set(image, &mut false)
         }
     }
 
     pub fn copy_border_image_source_from(&mut self, other: &Self) {
         unsafe {
             Gecko_CopyImageValueFrom(&mut self.gecko.mBorderImageSource,
                                      &other.gecko.mBorderImageSource);
@@ -2874,17 +2874,17 @@ fn static_assert() {
             Gecko_EnsureImageLayersLength(&mut self.gecko.${image_layers_field}, images.len(),
                                           LayerType::${shorthand.title()});
         }
 
         self.gecko.${image_layers_field}.mImageCount = images.len() as u32;
 
         for (image, geckoimage) in images.zip(self.gecko.${image_layers_field}
                                                   .mLayers.iter_mut()) {
-            if let Some(image) = image.0 {
+            if let Either::Second(image) = image {
                 geckoimage.mImage.set(image, cacheable)
             }
         }
     }
 
     <%
         fill_fields = "mRepeat mClip mOrigin mPositionX mPositionY mImage mSize"
         if shorthand == "background":
--- a/servo/components/style/properties/longhand/background.mako.rs
+++ b/servo/components/style/properties/longhand/background.mako.rs
@@ -7,19 +7,19 @@
 <% data.new_style_struct("Background", inherited=False) %>
 
 ${helpers.predefined_type("background-color", "CSSColor",
     "::cssparser::Color::RGBA(::cssparser::RGBA::transparent())",
     initial_specified_value="SpecifiedValue::transparent()",
     spec="https://drafts.csswg.org/css-backgrounds/#background-color",
     animation_value_type="IntermediateColor", complex_color=True)}
 
-${helpers.predefined_type("background-image", "LayerImage",
-    initial_value="computed_value::T(None)",
-    initial_specified_value="SpecifiedValue(None)",
+${helpers.predefined_type("background-image", "ImageLayer",
+    initial_value="Either::First(None_)",
+    initial_specified_value="Either::First(None_)",
     spec="https://drafts.csswg.org/css-backgrounds/#the-background-image",
     vector="True",
     animation_value_type="none",
     has_uncacheable_values="True" if product == "gecko" else "False")}
 
 % for (axis, direction, initial) in [("x", "Horizontal", "left"), ("y", "Vertical", "top")]:
     ${helpers.predefined_type("background-position-" + axis, "position::" + direction + "Position",
                               initial_value="computed::LengthOrPercentage::zero()",
--- a/servo/components/style/properties/longhand/border.mako.rs
+++ b/servo/components/style/properties/longhand/border.mako.rs
@@ -185,19 +185,19 @@
 ${helpers.single_keyword("-moz-float-edge", "content-box margin-box",
                          gecko_ffi_name="mFloatEdge",
                          gecko_enum_prefix="StyleFloatEdge",
                          gecko_inexhaustive=True,
                          products="gecko",
                          spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-float-edge)",
                          animation_value_type="none")}
 
-${helpers.predefined_type("border-image-source", "LayerImage",
-    initial_value="computed_value::T(None)",
-    initial_specified_value="SpecifiedValue(None)",
+${helpers.predefined_type("border-image-source", "ImageLayer",
+    initial_value="Either::First(None_)",
+    initial_specified_value="Either::First(None_)",
     spec="https://drafts.csswg.org/css-backgrounds/#the-background-image",
     vector=False,
     animation_value_type="none",
     has_uncacheable_values=False,
     boxed="True")}
 
 <%helpers:longhand name="border-image-outset" animation_value_type="none"
                    spec="https://drafts.csswg.org/css-backgrounds/#border-image-outset">
@@ -712,17 +712,17 @@
         }
     }
 
     pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
         let mut fill = input.try(|input| input.expect_ident_matching("fill")).is_ok();
 
         let mut values = vec![];
         for _ in 0..4 {
-            let value = input.try(|input| NumberOrPercentage::parse(context, input));
+            let value = input.try(|input| NumberOrPercentage::parse_non_negative(context, input));
             match value {
                 Ok(val) => values.push(val),
                 Err(_) => break,
             }
         }
 
         if !fill {
             fill = input.try(|input| input.expect_ident_matching("fill")).is_ok();
--- a/servo/components/style/properties/longhand/svg.mako.rs
+++ b/servo/components/style/properties/longhand/svg.mako.rs
@@ -136,18 +136,19 @@
 
 ${helpers.single_keyword("mask-composite",
                          "add subtract intersect exclude",
                          vector=True,
                          products="gecko",
                          extra_prefixes="webkit",
                          animation_value_type="none",
                          spec="https://drafts.fxtf.org/css-masking/#propdef-mask-composite")}
-${helpers.predefined_type("mask-image", "LayerImage",
-    initial_value="computed_value::T(None)",
-    initial_specified_value="SpecifiedValue(None)",
+
+${helpers.predefined_type("mask-image", "ImageLayer",
+    initial_value="Either::First(None_)",
+    initial_specified_value="Either::First(None_)",
     spec="https://drafts.fxtf.org/css-masking/#propdef-mask-image",
     vector=True,
     products="gecko",
     extra_prefixes="webkit",
     animation_value_type="none",
     flags="CREATES_STACKING_CONTEXT",
     has_uncacheable_values="True" if product == "gecko" else "False")}
--- a/servo/components/style/values/computed/image.rs
+++ b/servo/components/style/values/computed/image.rs
@@ -2,692 +2,131 @@
  * 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/. */
 
 //! CSS handling for the computed value of
 //! [`image`][image]s
 //!
 //! [image]: https://drafts.csswg.org/css-images/#image-values
 
-use Atom;
-use cssparser::{Color as CSSColor, serialize_identifier};
+use cssparser::Color as CSSColor;
 use std::f32::consts::PI;
 use std::fmt;
 use style_traits::ToCss;
+use values::{Either, None_};
 use values::computed::{Angle, Context, Length, LengthOrPercentage, NumberOrPercentage, ToComputedValue};
 use values::computed::position::Position;
-use values::specified::{self, HorizontalDirection, VerticalDirection};
-use values::specified::image::CompatMode;
-use values::specified::url::SpecifiedUrl;
-
-pub use values::specified::SizeKeyword;
-
-impl ToComputedValue for specified::Image {
-    type ComputedValue = Image;
+use values::generics::image::{CompatMode, ColorStop as GenericColorStop, EndingShape as GenericEndingShape};
+use values::generics::image::{Gradient as GenericGradient, GradientItem as GenericGradientItem};
+use values::generics::image::{Image as GenericImage, GradientKind as GenericGradientKind};
+use values::generics::image::{ImageRect as GenericImageRect, LineDirection as GenericLineDirection};
+use values::specified::image::LineDirection as SpecifiedLineDirection;
+use values::specified::position::{X, Y};
 
-    #[inline]
-    fn to_computed_value(&self, context: &Context) -> Image {
-        match *self {
-            specified::Image::Url(ref url_value) => {
-                Image::Url(url_value.clone())
-            },
-            specified::Image::Gradient(ref gradient) => {
-                Image::Gradient(gradient.to_computed_value(context))
-            },
-            specified::Image::ImageRect(ref image_rect) => {
-                Image::ImageRect(image_rect.to_computed_value(context))
-            },
-            specified::Image::Element(ref selector) => {
-                Image::Element(selector.clone())
-            }
-        }
-    }
-
-    #[inline]
-    fn from_computed_value(computed: &Image) -> Self {
-        match *computed {
-            Image::Url(ref url_value) => {
-                specified::Image::Url(url_value.clone())
-            },
-            Image::Gradient(ref linear_gradient) => {
-                specified::Image::Gradient(
-                    ToComputedValue::from_computed_value(linear_gradient)
-                )
-            },
-            Image::ImageRect(ref image_rect) => {
-                specified::Image::ImageRect(
-                    ToComputedValue::from_computed_value(image_rect)
-                )
-            },
-            Image::Element(ref selector) => {
-                specified::Image::Element(selector.clone())
-            },
-        }
-    }
-}
+/// A computed image layer.
+pub type ImageLayer = Either<None_, Image>;
 
 /// Computed values for an image according to CSS-IMAGES.
 /// https://drafts.csswg.org/css-images/#image-values
-#[derive(Clone, PartialEq)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub enum Image {
-    Url(SpecifiedUrl),
-    Gradient(Gradient),
-    ImageRect(ImageRect),
-    Element(Atom),
-}
-
-impl fmt::Debug for Image {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match *self {
-            Image::Url(ref url) => url.to_css(f),
-            Image::Gradient(ref grad) => {
-                if grad.repeating {
-                    let _ = write!(f, "repeating-");
-                }
-                match grad.gradient_kind {
-                    GradientKind::Linear(_) => write!(f, "linear-gradient({:?})", grad),
-                    GradientKind::Radial(_, _) => write!(f, "radial-gradient({:?})", grad),
-                }
-            },
-            Image::ImageRect(ref image_rect) => write!(f, "{:?}", image_rect),
-            Image::Element(ref selector) => {
-                f.write_str("-moz-element(#")?;
-                serialize_identifier(&*selector.to_string(), f)?;
-                f.write_str(")")
-            },
-        }
-    }
-}
-
-impl ToCss for Image {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            Image::Url(ref url) => url.to_css(dest),
-            Image::Gradient(ref gradient) => gradient.to_css(dest),
-            Image::ImageRect(ref image_rect) => image_rect.to_css(dest),
-            Image::Element(ref selector) => {
-                dest.write_str("-moz-element(#")?;
-                // FIXME: We should get rid of these intermediate strings.
-                serialize_identifier(&*selector.to_string(), dest)?;
-                dest.write_str(")")
-            },
-        }
-    }
-}
+pub type Image = GenericImage<Gradient, ImageRect>;
 
 /// Computed values for a CSS gradient.
 /// https://drafts.csswg.org/css-images/#gradients
-#[derive(Clone, PartialEq)]
+pub type Gradient = GenericGradient<
+    LineDirection,
+    Length,
+    LengthOrPercentage,
+    Position,
+    CSSColor,
+>;
+
+/// A computed gradient kind.
+pub type GradientKind = GenericGradientKind<
+    LineDirection,
+    Length,
+    LengthOrPercentage,
+    Position,
+>;
+
+/// A computed gradient line direction.
+#[derive(Clone, Copy, Debug, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub struct Gradient {
-    /// The color stops.
-    pub items: Vec<GradientItem>,
-    /// True if this is a repeating gradient.
-    pub repeating: bool,
-    /// Gradient kind can be linear or radial.
-    pub gradient_kind: GradientKind,
-    /// Compatibility mode.
-    pub compat_mode: CompatMode,
+pub enum LineDirection {
+    /// An angle.
+    Angle(Angle),
+    /// A corner.
+    Corner(X, Y),
 }
 
-impl ToCss for Gradient {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        if self.compat_mode == CompatMode::WebKit {
-            try!(dest.write_str("-webkit-"));
-        }
-        if self.repeating {
-            try!(dest.write_str("repeating-"));
-        }
-        match self.gradient_kind {
-            GradientKind::Linear(angle_or_corner) => {
-                try!(dest.write_str("linear-gradient("));
-                try!(angle_or_corner.to_css(dest, self.compat_mode));
-            },
-            GradientKind::Radial(ref shape, position) => {
-                try!(dest.write_str("radial-gradient("));
-                try!(shape.to_css(dest));
-                try!(dest.write_str(" at "));
-                try!(position.to_css(dest));
-            },
-        }
-        for item in &self.items {
-            try!(dest.write_str(", "));
-            try!(item.to_css(dest));
-        }
-        try!(dest.write_str(")"));
-        Ok(())
-    }
-}
+/// A computed radial gradient ending shape.
+pub type EndingShape = GenericEndingShape<Length, LengthOrPercentage>;
+
+/// A computed gradient item.
+pub type GradientItem = GenericGradientItem<CSSColor, LengthOrPercentage>;
+
+/// A computed color stop.
+pub type ColorStop = GenericColorStop<CSSColor, LengthOrPercentage>;
 
-impl fmt::Debug for Gradient {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self.gradient_kind {
-            GradientKind::Linear(angle_or_corner) => {
-                let _ = write!(f, "{:?}", angle_or_corner);
-            },
-            GradientKind::Radial(ref shape, position) => {
-                let _ = write!(f, "{:?} at {:?}", shape, position);
-            },
-        }
+/// Computed values for ImageRect.
+pub type ImageRect = GenericImageRect<NumberOrPercentage>;
 
-        for item in &self.items {
-            let _ = write!(f, ", {:?}", item);
-        }
-        Ok(())
-    }
-}
-
-impl ToComputedValue for specified::Gradient {
-    type ComputedValue = Gradient;
-
-    #[inline]
-    fn to_computed_value(&self, context: &Context) -> Gradient {
-        let specified::Gradient {
-            ref items,
-            repeating,
-            ref gradient_kind,
-            compat_mode,
-        } = *self;
-        Gradient {
-            items: items.iter().map(|s| s.to_computed_value(context)).collect(),
-            repeating: repeating,
-            gradient_kind: gradient_kind.to_computed_value(context),
-            compat_mode: compat_mode,
+impl GenericLineDirection for LineDirection {
+    fn points_downwards(&self) -> bool {
+        match *self {
+            LineDirection::Angle(angle) => angle.radians() == PI,
+            LineDirection::Corner(..) => false,
         }
     }
 
-    #[inline]
-    fn from_computed_value(computed: &Gradient) -> Self {
-        let Gradient {
-            ref items,
-            repeating,
-            ref gradient_kind,
-            compat_mode,
-        } = *computed;
-        specified::Gradient {
-            items: items.iter().map(ToComputedValue::from_computed_value).collect(),
-            repeating: repeating,
-            gradient_kind: ToComputedValue::from_computed_value(gradient_kind),
-            compat_mode: compat_mode,
-        }
-    }
-}
-
-/// Computed values for CSS linear or radial gradients.
-/// https://drafts.csswg.org/css-images/#gradients
-#[derive(Clone, PartialEq)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub enum GradientKind {
-    Linear(AngleOrCorner),
-    Radial(EndingShape, Position),
-}
-
-impl ToComputedValue for specified::GradientKind {
-    type ComputedValue = GradientKind;
-
-    #[inline]
-    fn to_computed_value(&self, context: &Context) -> GradientKind {
+    fn to_css<W>(&self, dest: &mut W, compat_mode: CompatMode) -> fmt::Result
+        where W: fmt::Write
+    {
         match *self {
-            specified::GradientKind::Linear(angle_or_corner) => {
-                GradientKind::Linear(angle_or_corner.to_computed_value(context))
-            },
-            specified::GradientKind::Radial(ref shape, ref position) => {
-                GradientKind::Radial(shape.to_computed_value(context),
-                                     position.to_computed_value(context))
-            },
-        }
-    }
-    #[inline]
-    fn from_computed_value(computed: &GradientKind) -> Self {
-        match *computed {
-            GradientKind::Linear(angle_or_corner) => {
-                specified::GradientKind::Linear(ToComputedValue::from_computed_value(&angle_or_corner))
-            },
-            GradientKind::Radial(ref shape, position) => {
-                specified::GradientKind::Radial(ToComputedValue::from_computed_value(shape),
-                                                ToComputedValue::from_computed_value(&position))
-            },
-        }
-    }
-}
-
-/// Specified values for color stops and interpolation hints.
-#[derive(Clone, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub enum GradientItem {
-    /// A color stop.
-    ColorStop(ColorStop),
-    /// An interpolation hint.
-    InterpolationHint(LengthOrPercentage),
-}
-
-impl ToCss for GradientItem {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            GradientItem::ColorStop(stop) => stop.to_css(dest),
-            GradientItem::InterpolationHint(hint) => hint.to_css(dest),
-        }
-    }
-}
-
-impl ToComputedValue for specified::GradientItem {
-    type ComputedValue = GradientItem;
-
-    #[inline]
-    fn to_computed_value(&self, context: &Context) -> GradientItem {
-        match *self {
-            specified::GradientItem::ColorStop(ref stop) => {
-                GradientItem::ColorStop(stop.to_computed_value(context))
-            },
-            specified::GradientItem::InterpolationHint(ref hint) => {
-                GradientItem::InterpolationHint(hint.to_computed_value(context))
-            },
-        }
-    }
-
-    #[inline]
-    fn from_computed_value(computed: &GradientItem) -> Self {
-        match *computed {
-            GradientItem::ColorStop(ref stop) => {
-                specified::GradientItem::ColorStop(ToComputedValue::from_computed_value(stop))
-            },
-            GradientItem::InterpolationHint(ref hint) => {
-                specified::GradientItem::InterpolationHint(ToComputedValue::from_computed_value(hint))
+            LineDirection::Angle(ref angle) => angle.to_css(dest),
+            LineDirection::Corner(x, y) => {
+                if compat_mode == CompatMode::Modern {
+                    dest.write_str("to ")?;
+                }
+                x.to_css(dest)?;
+                dest.write_str(" ")?;
+                y.to_css(dest)
             },
         }
     }
 }
 
-/// Computed values for one color stop in a linear gradient.
-/// https://drafts.csswg.org/css-images/#typedef-color-stop-list
-#[derive(Clone, PartialEq, Copy)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub struct ColorStop {
-    /// The color of this stop.
-    pub color: CSSColor,
-
-    /// The position of this stop. If not specified, this stop is placed halfway between the
-    /// point that precedes it and the point that follows it per CSS-IMAGES ยง 3.4.
-    pub position: Option<LengthOrPercentage>,
-}
-
-impl ToCss for ColorStop {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        try!(self.color.to_css(dest));
-        if let Some(position) = self.position {
-            try!(dest.write_str(" "));
-            try!(position.to_css(dest));
-        }
-        Ok(())
-    }
-}
+impl ToComputedValue for SpecifiedLineDirection {
+    type ComputedValue = LineDirection;
 
-impl fmt::Debug for ColorStop {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let _ = write!(f, "{:?}", self.color);
-        self.position.map(|pos| {
-            let _ = write!(f, " {:?}", pos);
-        });
-        Ok(())
-    }
-}
-
-impl ToComputedValue for specified::ColorStop {
-    type ComputedValue = ColorStop;
-
-    #[inline]
-    fn to_computed_value(&self, context: &Context) -> ColorStop {
-        ColorStop {
-            color: self.color.to_computed_value(context),
-            position: match self.position {
-                None => None,
-                Some(ref value) => Some(value.to_computed_value(context)),
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        match *self {
+            SpecifiedLineDirection::Angle(ref angle) => {
+                LineDirection::Angle(angle.to_computed_value(context))
+            },
+            SpecifiedLineDirection::Horizontal(X::Left) => {
+                LineDirection::Angle(Angle::Degree(270.))
             },
-        }
-    }
-    #[inline]
-    fn from_computed_value(computed: &ColorStop) -> Self {
-        specified::ColorStop {
-            color: ToComputedValue::from_computed_value(&computed.color),
-            position: match computed.position {
-                None => None,
-                Some(value) => Some(ToComputedValue::from_computed_value(&value)),
+            SpecifiedLineDirection::Horizontal(X::Right) => {
+                LineDirection::Angle(Angle::Degree(90.))
+            },
+            SpecifiedLineDirection::Vertical(Y::Top) => {
+                LineDirection::Angle(Angle::Degree(0.))
+            },
+            SpecifiedLineDirection::Vertical(Y::Bottom) => {
+                LineDirection::Angle(Angle::Degree(180.))
+            },
+            SpecifiedLineDirection::Corner(x, y) => {
+                LineDirection::Corner(x, y)
             },
         }
     }
-}
 
-/// Computed values for EndingShape
-/// https://drafts.csswg.org/css-images/#valdef-radial-gradient-ending-shape
-#[derive(Clone, PartialEq)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub enum EndingShape {
-    Circle(LengthOrKeyword),
-    Ellipse(LengthOrPercentageOrKeyword),
-}
-
-impl ToCss for EndingShape {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            EndingShape::Circle(ref length) => {
-                try!(dest.write_str("circle "));
-                try!(length.to_css(dest));
-            },
-            EndingShape::Ellipse(ref length) => {
-                try!(dest.write_str("ellipse "));
-                try!(length.to_css(dest));
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        match *computed {
+            LineDirection::Angle(ref angle) => {
+                SpecifiedLineDirection::Angle(ToComputedValue::from_computed_value(angle))
             },
-        }
-        Ok(())
-    }
-}
-
-impl fmt::Debug for EndingShape {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match *self {
-            EndingShape::Circle(ref length) => {
-                let _ = write!(f, "circle {:?}", length);
-            },
-            EndingShape::Ellipse(ref length) => {
-                let _ = write!(f, "ellipse {:?}", length);
-            }
-        }
-        Ok(())
-    }
-}
-
-impl ToComputedValue for specified::GradientEndingShape {
-    type ComputedValue = EndingShape;
-
-    #[inline]
-    fn to_computed_value(&self, context: &Context) -> EndingShape {
-        match *self {
-            specified::GradientEndingShape::Circle(ref length) => {
-                EndingShape::Circle(length.to_computed_value(context))
-            },
-            specified::GradientEndingShape::Ellipse(ref length) => {
-                EndingShape::Ellipse(length.to_computed_value(context))
-            },
-        }
-    }
-    #[inline]
-    fn from_computed_value(computed: &EndingShape) -> Self {
-        match *computed {
-            EndingShape::Circle(ref length) => {
-                specified::GradientEndingShape::Circle(ToComputedValue::from_computed_value(length))
-            },
-            EndingShape::Ellipse(ref length) => {
-                specified::GradientEndingShape::Ellipse(ToComputedValue::from_computed_value(length))
+            LineDirection::Corner(x, y) => {
+                SpecifiedLineDirection::Corner(x, y)
             },
         }
     }
 }
-
-/// Computed values for ImageRect
-#[derive(Clone, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub struct ImageRect {
-    pub url: SpecifiedUrl,
-    pub top: NumberOrPercentage,
-    pub bottom: NumberOrPercentage,
-    pub right: NumberOrPercentage,
-    pub left: NumberOrPercentage,
-}
-
-impl ToCss for ImageRect {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        dest.write_str("-moz-image-rect(")?;
-        self.url.to_css(dest)?;
-        dest.write_str(", ")?;
-        self.top.to_css(dest)?;
-        dest.write_str(", ")?;
-        self.right.to_css(dest)?;
-        dest.write_str(", ")?;
-        self.bottom.to_css(dest)?;
-        dest.write_str(", ")?;
-        self.left.to_css(dest)?;
-        dest.write_str(")")
-    }
-}
-
-impl ToComputedValue for specified::ImageRect {
-    type ComputedValue = ImageRect;
-
-    #[inline]
-    fn to_computed_value(&self, context: &Context) -> ImageRect {
-        ImageRect {
-            url: self.url.to_computed_value(context),
-            top: self.top.to_computed_value(context),
-            right: self.right.to_computed_value(context),
-            bottom: self.bottom.to_computed_value(context),
-            left: self.left.to_computed_value(context),
-        }
-    }
-    #[inline]
-    fn from_computed_value(computed: &ImageRect) -> Self {
-        specified::ImageRect {
-            url: ToComputedValue::from_computed_value(&computed.url),
-            top: ToComputedValue::from_computed_value(&computed.top),
-            right: ToComputedValue::from_computed_value(&computed.right),
-            bottom: ToComputedValue::from_computed_value(&computed.bottom),
-            left: ToComputedValue::from_computed_value(&computed.left),
-        }
-    }
-}
-
-/// https://drafts.csswg.org/css-images/#valdef-radial-gradient-size
-#[derive(Clone, PartialEq)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub enum LengthOrKeyword {
-    Length(Length),
-    Keyword(SizeKeyword),
-}
-
-impl ToCss for LengthOrKeyword {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            LengthOrKeyword::Length(ref length) => length.to_css(dest),
-            LengthOrKeyword::Keyword(keyword) => keyword.to_css(dest),
-        }
-    }
-}
-
-impl fmt::Debug for LengthOrKeyword {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match *self {
-            LengthOrKeyword::Length(ref length) => {
-                let _ = write!(f, "{:?}", length);
-            },
-            LengthOrKeyword::Keyword(keyword) => {
-                let _ = write!(f, "{:?}", keyword);
-            },
-        }
-        Ok(())
-    }
-}
-
-impl ToComputedValue for specified::LengthOrKeyword {
-    type ComputedValue = LengthOrKeyword;
-
-    #[inline]
-    fn to_computed_value(&self, context: &Context) -> LengthOrKeyword {
-        match *self {
-            specified::LengthOrKeyword::Length(ref length) => {
-                LengthOrKeyword::Length(length.to_computed_value(context))
-            },
-            specified::LengthOrKeyword::Keyword(keyword) => {
-                LengthOrKeyword::Keyword(keyword)
-            },
-        }
-    }
-    #[inline]
-    fn from_computed_value(computed: &LengthOrKeyword) -> Self {
-        match *computed {
-            LengthOrKeyword::Length(length) => {
-                specified::LengthOrKeyword::Length(ToComputedValue::from_computed_value(&length))
-            },
-            LengthOrKeyword::Keyword(keyword) => {
-                specified::LengthOrKeyword::Keyword(keyword)
-            },
-        }
-    }
-}
-
-/// https://drafts.csswg.org/css-images/#valdef-radial-gradient-size
-#[derive(Clone, PartialEq)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub enum LengthOrPercentageOrKeyword {
-    LengthOrPercentage(LengthOrPercentage, LengthOrPercentage),
-    Keyword(SizeKeyword),
-}
-
-impl ToCss for LengthOrPercentageOrKeyword {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            LengthOrPercentageOrKeyword::LengthOrPercentage(ref first_len, second_len) => {
-                try!(first_len.to_css(dest));
-                try!(dest.write_str(" "));
-                second_len.to_css(dest)
-            },
-            LengthOrPercentageOrKeyword::Keyword(keyword) => keyword.to_css(dest),
-        }
-    }
-}
-
-impl fmt::Debug for LengthOrPercentageOrKeyword {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match *self {
-            LengthOrPercentageOrKeyword::LengthOrPercentage(ref first_len, second_len) => {
-                let _ = write!(f, "{:?} {:?}", first_len, second_len);
-            },
-            LengthOrPercentageOrKeyword::Keyword(keyword) => {
-                let _ = write!(f, "{:?}", keyword);
-            },
-        }
-        Ok(())
-    }
-}
-
-impl ToComputedValue for specified::LengthOrPercentageOrKeyword {
-    type ComputedValue = LengthOrPercentageOrKeyword;
-
-    #[inline]
-    fn to_computed_value(&self, context: &Context) -> LengthOrPercentageOrKeyword {
-        match *self {
-            specified::LengthOrPercentageOrKeyword::LengthOrPercentage(ref first_len, ref second_len) => {
-                LengthOrPercentageOrKeyword::LengthOrPercentage(first_len.to_computed_value(context),
-                                                                second_len.to_computed_value(context))
-            },
-            specified::LengthOrPercentageOrKeyword::Keyword(keyword) => {
-                LengthOrPercentageOrKeyword::Keyword(keyword)
-            },
-        }
-    }
-    #[inline]
-    fn from_computed_value(computed: &LengthOrPercentageOrKeyword) -> Self {
-        match *computed {
-            LengthOrPercentageOrKeyword::LengthOrPercentage(first_len, second_len) => {
-                specified::LengthOrPercentageOrKeyword::LengthOrPercentage(
-                    ToComputedValue::from_computed_value(&first_len),
-                    ToComputedValue::from_computed_value(&second_len))
-            },
-            LengthOrPercentageOrKeyword::Keyword(keyword) => {
-                specified::LengthOrPercentageOrKeyword::Keyword(keyword)
-            },
-        }
-    }
-}
-
-#[derive(Clone, Copy, Debug, PartialEq)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub enum AngleOrCorner {
-    Angle(Angle),
-    Corner(HorizontalDirection, VerticalDirection)
-}
-
-impl ToComputedValue for specified::AngleOrCorner {
-    type ComputedValue = AngleOrCorner;
-
-    #[inline]
-    fn to_computed_value(&self, context: &Context) -> AngleOrCorner {
-        match *self {
-            specified::AngleOrCorner::None => {
-                AngleOrCorner::Angle(Angle::from_radians(PI))
-            },
-            specified::AngleOrCorner::Angle(angle) => {
-                AngleOrCorner::Angle(angle.to_computed_value(context))
-            },
-            specified::AngleOrCorner::Corner(horizontal, vertical) => {
-                match (horizontal, vertical) {
-                    (None, Some(VerticalDirection::Top)) => {
-                        AngleOrCorner::Angle(Angle::from_radians(0.0))
-                    },
-                    (Some(HorizontalDirection::Right), None) => {
-                        AngleOrCorner::Angle(Angle::from_radians(PI * 0.5))
-                    },
-                    (None, Some(VerticalDirection::Bottom)) => {
-                        AngleOrCorner::Angle(Angle::from_radians(PI))
-                    },
-                    (Some(HorizontalDirection::Left), None) => {
-                        AngleOrCorner::Angle(Angle::from_radians(PI * 1.5))
-                    },
-                    (Some(horizontal), Some(vertical)) => {
-                        AngleOrCorner::Corner(horizontal, vertical)
-                    },
-                    (None, None) => {
-                        unreachable!()
-                    }
-                }
-            }
-        }
-    }
-
-    #[inline]
-    fn from_computed_value(computed: &AngleOrCorner) -> Self {
-        match *computed {
-            AngleOrCorner::Angle(ref angle) => {
-                specified::AngleOrCorner::Angle(specified::Angle::from_computed_value(angle))
-            },
-            AngleOrCorner::Corner(horizontal, vertical) => {
-                specified::AngleOrCorner::Corner(Some(horizontal), Some(vertical))
-            }
-        }
-    }
-}
-
-impl AngleOrCorner {
-    fn to_css<W>(&self, dest: &mut W, mode: CompatMode) -> fmt::Result where W: fmt::Write {
-        match *self {
-            AngleOrCorner::Angle(angle) => angle.to_css(dest),
-            AngleOrCorner::Corner(horizontal, vertical) => {
-                if mode == CompatMode::Modern {
-                    try!(dest.write_str("to "));
-                }
-                try!(horizontal.to_css(dest));
-                try!(dest.write_str(" "));
-                try!(vertical.to_css(dest));
-                Ok(())
-            }
-        }
-    }
-}
-
-/// Computed values for none | <image> | <mask-source>.
-#[derive(Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub struct LayerImage(pub Option<Image>);
-
-impl ToCss for LayerImage {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match self.0 {
-            None => dest.write_str("none"),
-            Some(ref image) => image.to_css(dest),
-        }
-    }
-}
--- a/servo/components/style/values/computed/length.rs
+++ b/servo/components/style/values/computed/length.rs
@@ -7,18 +7,17 @@
 use app_units::{Au, AU_PER_PX};
 use ordered_float::NotNaN;
 use std::fmt;
 use style_traits::ToCss;
 use super::{Number, ToComputedValue, Context};
 use values::{Auto, CSSFloat, Either, ExtremumLength, None_, Normal, specified};
 use values::specified::length::{AbsoluteLength, FontBaseSize, FontRelativeLength, ViewportPercentageLength};
 
-pub use super::image::{EndingShape as GradientShape, Gradient, GradientKind, Image};
-pub use super::image::{LengthOrKeyword, LengthOrPercentageOrKeyword};
+pub use super::image::Image;
 pub use values::specified::{Angle, BorderStyle, Time, UrlOrNone};
 
 impl ToComputedValue for specified::NoCalcLength {
     type ComputedValue = Au;
 
     #[inline]
     fn to_computed_value(&self, context: &Context) -> Au {
         match *self {
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -17,18 +17,17 @@ use std::fmt;
 use style_traits::ToCss;
 use super::{CSSFloat, CSSInteger, RGBA};
 use super::generics::BorderRadiusSize as GenericBorderRadiusSize;
 use super::specified;
 use super::specified::grid::{TrackBreadth as GenericTrackBreadth, TrackSize as GenericTrackSize};
 
 pub use app_units::Au;
 pub use cssparser::Color as CSSColor;
-pub use self::image::{AngleOrCorner, EndingShape as GradientShape, Gradient, GradientItem, LayerImage};
-pub use self::image::{GradientKind, Image, ImageRect, LengthOrKeyword, LengthOrPercentageOrKeyword};
+pub use self::image::{Gradient, GradientItem, ImageLayer, LineDirection, Image, ImageRect};
 pub use super::{Auto, Either, None_};
 #[cfg(feature = "gecko")]
 pub use super::specified::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
 pub use super::specified::{BorderStyle, GridLine, Percentage, UrlOrNone};
 pub use super::specified::url::SpecifiedUrl;
 pub use self::length::{CalcLengthOrPercentage, Length, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
 pub use self::length::{LengthOrPercentageOrAutoOrContent, LengthOrPercentageOrNone, LengthOrNone};
 pub use self::length::{MaxLength, MinLength};
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/generics/image.rs
@@ -0,0 +1,636 @@
+/* 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 the handling of [images].
+//!
+//! [images]: https://drafts.csswg.org/css-images/#image-values
+
+use Atom;
+use cssparser::serialize_identifier;
+use std::fmt;
+use style_traits::ToCss;
+use values::HasViewportPercentage;
+use values::computed::{Context, ToComputedValue};
+use values::specified::url::SpecifiedUrl;
+
+/// An [image].
+///
+/// [image]: https://drafts.csswg.org/css-images/#image-values
+#[derive(Clone, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub enum Image<Gradient, ImageRect> {
+    /// A `<url()>` image.
+    Url(SpecifiedUrl),
+    /// A `<gradient>` image.
+    Gradient(Gradient),
+    /// A `-moz-image-rect` image
+    Rect(ImageRect),
+    /// A `-moz-element(# <element-id>)`
+    Element(Atom),
+}
+
+/// A CSS gradient.
+/// https://drafts.csswg.org/css-images/#gradients
+#[derive(Clone, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub struct Gradient<LineDirection, Length, LengthOrPercentage, Position, Color> {
+    /// Gradients can be linear or radial.
+    pub kind: GradientKind<LineDirection, Length, LengthOrPercentage, Position>,
+    /// The color stops and interpolation hints.
+    pub items: Vec<GradientItem<Color, LengthOrPercentage>>,
+    /// True if this is a repeating gradient.
+    pub repeating: bool,
+    /// Compatibility mode.
+    pub compat_mode: CompatMode,
+}
+
+#[derive(Clone, Copy, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+/// Whether we used the modern notation or the compatibility `-webkit` prefix.
+pub enum CompatMode {
+    /// Modern syntax.
+    Modern,
+    /// `-webkit` prefix.
+    WebKit,
+}
+
+/// A gradient kind.
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub enum GradientKind<LineDirection, Length, LengthOrPercentage, Position> {
+    /// A linear gradient.
+    Linear(LineDirection),
+    /// A radial gradient.
+    Radial(EndingShape<Length, LengthOrPercentage>, Position),
+}
+
+/// A radial gradient's ending shape.
+#[derive(Clone, Copy, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub enum EndingShape<Length, LengthOrPercentage> {
+    /// A circular gradient.
+    Circle(Circle<Length>),
+    /// An elliptic gradient.
+    Ellipse(Ellipse<LengthOrPercentage>),
+}
+
+/// A circle shape.
+#[derive(Clone, Copy, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub enum Circle<Length> {
+    /// A circle radius.
+    Radius(Length),
+    /// A circle extent.
+    Extent(ShapeExtent),
+}
+
+/// An ellipse shape.
+#[derive(Clone, Copy, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub enum Ellipse<LengthOrPercentage> {
+    /// An ellipse pair of radii.
+    Radii(LengthOrPercentage, LengthOrPercentage),
+    /// An ellipse extent.
+    Extent(ShapeExtent),
+}
+
+/// https://drafts.csswg.org/css-images/#typedef-extent-keyword
+define_css_keyword_enum!(ShapeExtent:
+    "closest-side" => ClosestSide,
+    "farthest-side" => FarthestSide,
+    "closest-corner" => ClosestCorner,
+    "farthest-corner" => FarthestCorner,
+    "contain" => Contain,
+    "cover" => Cover
+);
+
+/// A gradient item.
+/// https://drafts.csswg.org/css-images-4/#color-stop-syntax
+#[derive(Clone, Copy, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub enum GradientItem<Color, LengthOrPercentage> {
+    /// A color stop.
+    ColorStop(ColorStop<Color, LengthOrPercentage>),
+    /// An interpolation hint.
+    InterpolationHint(LengthOrPercentage),
+}
+
+/// A color stop.
+/// https://drafts.csswg.org/css-images/#typedef-color-stop-list
+#[derive(Clone, Copy, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub struct ColorStop<Color, LengthOrPercentage> {
+    /// The color of this stop.
+    pub color: Color,
+    /// The position of this stop.
+    pub position: Option<LengthOrPercentage>,
+}
+
+/// Values for `moz-image-rect`.
+///
+/// `-moz-image-rect(<uri>, top, right, bottom, left);`
+#[derive(Clone, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[allow(missing_docs)]
+pub struct ImageRect<NumberOrPercentage> {
+    pub url: SpecifiedUrl,
+    pub top: NumberOrPercentage,
+    pub bottom: NumberOrPercentage,
+    pub right: NumberOrPercentage,
+    pub left: NumberOrPercentage,
+}
+
+impl<G, R> fmt::Debug for Image<G, R>
+    where G: fmt::Debug, R: fmt::Debug,
+{
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match *self {
+            Image::Url(ref url) => url.to_css(f),
+            Image::Gradient(ref grad) => grad.fmt(f),
+            Image::Rect(ref rect) => rect.fmt(f),
+            Image::Element(ref selector) => {
+                f.write_str("-moz-element(#")?;
+                serialize_identifier(&selector.to_string(), f)?;
+                f.write_str(")")
+            },
+        }
+    }
+}
+
+impl<G, R> ToCss for Image<G, R>
+    where G: ToCss, R: ToCss,
+{
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            Image::Url(ref url) => url.to_css(dest),
+            Image::Gradient(ref gradient) => gradient.to_css(dest),
+            Image::Rect(ref rect) => rect.to_css(dest),
+            Image::Element(ref selector) => {
+                dest.write_str("-moz-element(#")?;
+                serialize_identifier(&selector.to_string(), dest)?;
+                dest.write_str(")")
+            },
+        }
+    }
+}
+
+impl<G, R> HasViewportPercentage for Image<G, R>
+    where G: HasViewportPercentage
+{
+    fn has_viewport_percentage(&self) -> bool {
+        match *self {
+            Image::Gradient(ref gradient) => gradient.has_viewport_percentage(),
+            _ => false,
+        }
+    }
+}
+
+impl<G, R> ToComputedValue for Image<G, R>
+    where G: ToComputedValue, R: ToComputedValue,
+{
+    type ComputedValue = Image<<G as ToComputedValue>::ComputedValue,
+                               <R as ToComputedValue>::ComputedValue>;
+
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        match *self {
+            Image::Url(ref url) => {
+                Image::Url(url.clone())
+            },
+            Image::Gradient(ref gradient) => {
+                Image::Gradient(gradient.to_computed_value(context))
+            },
+            Image::Rect(ref rect) => {
+                Image::Rect(rect.to_computed_value(context))
+            },
+            Image::Element(ref selector) => {
+                Image::Element(selector.clone())
+            }
+        }
+    }
+
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        match *computed {
+            Image::Url(ref url) => {
+                Image::Url(url.clone())
+            },
+            Image::Gradient(ref gradient) => {
+                Image::Gradient(ToComputedValue::from_computed_value(gradient))
+            },
+            Image::Rect(ref rect) => {
+                Image::Rect(ToComputedValue::from_computed_value(rect))
+            },
+            Image::Element(ref selector) => {
+                Image::Element(selector.clone())
+            },
+        }
+    }
+}
+
+impl<D, L, LoP, P, C> ToCss for Gradient<D, L, LoP, P, C>
+    where D: LineDirection, L: ToCss, LoP: ToCss, P: ToCss, C: ToCss,
+{
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        if self.compat_mode == CompatMode::WebKit {
+            dest.write_str("-webkit-")?;
+        }
+        if self.repeating {
+            dest.write_str("repeating-")?;
+        }
+        dest.write_str(self.kind.label())?;
+        dest.write_str("-gradient(")?;
+        let mut skip_comma = match self.kind {
+            GradientKind::Linear(ref direction) if direction.points_downwards() => true,
+            GradientKind::Linear(ref direction) => {
+                direction.to_css(dest, self.compat_mode)?;
+                false
+            },
+            GradientKind::Radial(ref shape, ref position) => {
+                let omit_shape = match *shape {
+                    EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::Cover)) |
+                    EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)) => {
+                        true
+                    },
+                    _ => false,
+                };
+                if self.compat_mode == CompatMode::Modern {
+                    if !omit_shape {
+                        shape.to_css(dest)?;
+                        dest.write_str(" ")?;
+                    }
+                    dest.write_str("at ")?;
+                    position.to_css(dest)?;
+                } else {
+                    position.to_css(dest)?;
+                    if !omit_shape {
+                        dest.write_str(", ")?;
+                        shape.to_css(dest)?;
+                    }
+                }
+                false
+            },
+        };
+        for item in &self.items {
+            if !skip_comma {
+                dest.write_str(", ")?;
+            }
+            skip_comma = false;
+            item.to_css(dest)?;
+        }
+        dest.write_str(")")
+    }
+}
+
+impl<D, L, LoP, P, C> HasViewportPercentage for Gradient<D, L, LoP, P, C>
+    where L: HasViewportPercentage,
+          LoP: HasViewportPercentage,
+          P: HasViewportPercentage,
+{
+    fn has_viewport_percentage(&self) -> bool {
+        self.kind.has_viewport_percentage() ||
+        self.items.iter().any(|i| i.has_viewport_percentage())
+    }
+}
+
+impl<D, L, LoP, P, C> ToComputedValue for Gradient<D, L, LoP, P, C>
+    where D: ToComputedValue,
+          L: ToComputedValue,
+          LoP: ToComputedValue,
+          P: ToComputedValue,
+          C: ToComputedValue,
+{
+    type ComputedValue = Gradient<<D as ToComputedValue>::ComputedValue,
+                                  <L as ToComputedValue>::ComputedValue,
+                                  <LoP as ToComputedValue>::ComputedValue,
+                                  <P as ToComputedValue>::ComputedValue,
+                                  <C as ToComputedValue>::ComputedValue>;
+
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        Gradient {
+            kind: self.kind.to_computed_value(context),
+            items: self.items.iter().map(|s| s.to_computed_value(context)).collect(),
+            repeating: self.repeating,
+            compat_mode: self.compat_mode,
+        }
+    }
+
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        Gradient {
+            kind: ToComputedValue::from_computed_value(&computed.kind),
+            items: computed.items.iter().map(ToComputedValue::from_computed_value).collect(),
+            repeating: computed.repeating,
+            compat_mode: computed.compat_mode,
+        }
+    }
+}
+
+impl<D, L, LoP, P> GradientKind<D, L, LoP, P> {
+    fn label(&self) -> &str {
+        match *self {
+            GradientKind::Linear(..) => "linear",
+            GradientKind::Radial(..) => "radial",
+        }
+    }
+}
+
+impl<D, L, LoP, P> HasViewportPercentage for GradientKind<D, L, LoP, P>
+    where L: HasViewportPercentage,
+          LoP: HasViewportPercentage,
+          P: HasViewportPercentage
+{
+    fn has_viewport_percentage(&self) -> bool {
+        match *self {
+            GradientKind::Linear(_) => false,
+            GradientKind::Radial(ref shape, ref position) => {
+                shape.has_viewport_percentage() || position.has_viewport_percentage()
+            },
+        }
+    }
+}
+
+impl<D, L, LoP, P> ToComputedValue for GradientKind<D, L, LoP, P>
+    where D: ToComputedValue,
+          L: ToComputedValue,
+          LoP: ToComputedValue,
+          P: ToComputedValue,
+{
+    type ComputedValue = GradientKind<<D as ToComputedValue>::ComputedValue,
+                                      <L as ToComputedValue>::ComputedValue,
+                                      <LoP as ToComputedValue>::ComputedValue,
+                                      <P as ToComputedValue>::ComputedValue>;
+
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        match *self {
+            GradientKind::Linear(ref direction) => {
+                GradientKind::Linear(direction.to_computed_value(context))
+            },
+            GradientKind::Radial(ref shape, ref position) => {
+                GradientKind::Radial(shape.to_computed_value(context), position.to_computed_value(context))
+            },
+        }
+    }
+
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        match *computed {
+            GradientKind::Linear(ref direction) => {
+                GradientKind::Linear(ToComputedValue::from_computed_value(direction))
+            },
+            GradientKind::Radial(ref shape, ref position) => {
+                GradientKind::Radial(
+                    ToComputedValue::from_computed_value(shape),
+                    ToComputedValue::from_computed_value(position),
+                )
+            }
+        }
+    }
+}
+
+/// The direction of a linear gradient.
+pub trait LineDirection {
+    /// Whether this direction points towards, and thus can be omitted.
+    fn points_downwards(&self) -> bool;
+
+    /// Serialises this direction according to the compatibility mode.
+    fn to_css<W>(&self, dest: &mut W, compat_mode: CompatMode) -> fmt::Result
+        where W: fmt::Write;
+}
+
+impl<L, LoP> ToCss for EndingShape<L, LoP>
+    where L: ToCss, LoP: ToCss,
+{
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            EndingShape::Circle(Circle::Extent(ShapeExtent::FarthestCorner)) |
+            EndingShape::Circle(Circle::Extent(ShapeExtent::Cover)) => {
+                dest.write_str("circle")
+            },
+            EndingShape::Circle(Circle::Extent(keyword)) => {
+                dest.write_str("circle ")?;
+                keyword.to_css(dest)
+            },
+            EndingShape::Circle(Circle::Radius(ref length)) => {
+                length.to_css(dest)
+            },
+            EndingShape::Ellipse(Ellipse::Extent(keyword)) => {
+                keyword.to_css(dest)
+            },
+            EndingShape::Ellipse(Ellipse::Radii(ref x, ref y)) => {
+                x.to_css(dest)?;
+                dest.write_str(" ")?;
+                y.to_css(dest)
+            },
+        }
+    }
+}
+
+impl<L, LoP> HasViewportPercentage for EndingShape<L, LoP>
+    where L: HasViewportPercentage, LoP: HasViewportPercentage,
+{
+    fn has_viewport_percentage(&self) -> bool {
+        match *self {
+            EndingShape::Circle(Circle::Radius(ref length)) => {
+                length.has_viewport_percentage()
+            },
+            EndingShape::Ellipse(Ellipse::Radii(ref x, ref y)) => {
+                x.has_viewport_percentage() || y.has_viewport_percentage()
+            },
+            _ => false,
+        }
+    }
+}
+
+impl<L, LoP> ToComputedValue for EndingShape<L, LoP>
+    where L: ToComputedValue, LoP: ToComputedValue,
+{
+    type ComputedValue = EndingShape<<L as ToComputedValue>::ComputedValue,
+                                     <LoP as ToComputedValue>::ComputedValue>;
+
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        match *self {
+            EndingShape::Circle(Circle::Radius(ref length)) => {
+                EndingShape::Circle(Circle::Radius(length.to_computed_value(context)))
+            },
+            EndingShape::Circle(Circle::Extent(extent)) => {
+                EndingShape::Circle(Circle::Extent(extent))
+            },
+            EndingShape::Ellipse(Ellipse::Radii(ref x, ref y)) => {
+                EndingShape::Ellipse(Ellipse::Radii(
+                    x.to_computed_value(context),
+                    y.to_computed_value(context),
+                ))
+            },
+            EndingShape::Ellipse(Ellipse::Extent(extent)) => {
+                EndingShape::Ellipse(Ellipse::Extent(extent))
+            },
+        }
+    }
+
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        match *computed {
+            EndingShape::Circle(Circle::Radius(ref length)) => {
+                EndingShape::Circle(Circle::Radius(ToComputedValue::from_computed_value(length)))
+            },
+            EndingShape::Circle(Circle::Extent(extent)) => {
+                EndingShape::Circle(Circle::Extent(extent))
+            },
+            EndingShape::Ellipse(Ellipse::Radii(ref x, ref y)) => {
+                EndingShape::Ellipse(Ellipse::Radii(
+                    ToComputedValue::from_computed_value(x),
+                    ToComputedValue::from_computed_value(y),
+                ))
+            },
+            EndingShape::Ellipse(Ellipse::Extent(extent)) => {
+                EndingShape::Ellipse(Ellipse::Extent(extent))
+            },
+        }
+    }
+}
+
+impl<C, L> ToCss for GradientItem<C, L>
+    where C: ToCss, L: ToCss,
+{
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            GradientItem::ColorStop(ref stop) => stop.to_css(dest),
+            GradientItem::InterpolationHint(ref hint) => hint.to_css(dest),
+        }
+    }
+}
+
+impl<C, L> HasViewportPercentage for GradientItem<C, L>
+    where L: HasViewportPercentage,
+{
+    fn has_viewport_percentage(&self) -> bool {
+        match *self {
+            GradientItem::ColorStop(ref stop) => stop.has_viewport_percentage(),
+            GradientItem::InterpolationHint(ref hint) => hint.has_viewport_percentage(),
+        }
+    }
+}
+
+impl<C, L> ToComputedValue for GradientItem<C, L>
+    where C: ToComputedValue, L: ToComputedValue,
+{
+    type ComputedValue = GradientItem<<C as ToComputedValue>::ComputedValue,
+                                      <L as ToComputedValue>::ComputedValue>;
+
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        match *self {
+            GradientItem::ColorStop(ref stop) => {
+                GradientItem::ColorStop(stop.to_computed_value(context))
+            },
+            GradientItem::InterpolationHint(ref hint) => {
+                GradientItem::InterpolationHint(hint.to_computed_value(context))
+            },
+        }
+    }
+
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        match *computed {
+            GradientItem::ColorStop(ref stop) => {
+                GradientItem::ColorStop(ToComputedValue::from_computed_value(stop))
+            },
+            GradientItem::InterpolationHint(ref hint) => {
+                GradientItem::InterpolationHint(ToComputedValue::from_computed_value(hint))
+            },
+        }
+    }
+}
+
+impl<C, L> fmt::Debug for ColorStop<C, L>
+    where C: fmt::Debug, L: fmt::Debug,
+{
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:?}", self.color)?;
+        if let Some(ref pos) = self.position {
+            write!(f, " {:?}", pos)?;
+        }
+        Ok(())
+    }
+}
+
+impl<C, L> ToCss for ColorStop<C, L>
+    where C: ToCss, L: ToCss,
+{
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        self.color.to_css(dest)?;
+        if let Some(ref position) = self.position {
+            dest.write_str(" ")?;
+            position.to_css(dest)?;
+        }
+        Ok(())
+    }
+}
+
+impl<C, L> HasViewportPercentage for ColorStop<C, L>
+    where L: HasViewportPercentage,
+{
+    fn has_viewport_percentage(&self) -> bool {
+        self.position.as_ref().map_or(false, HasViewportPercentage::has_viewport_percentage)
+    }
+}
+
+impl<C, L> ToComputedValue for ColorStop<C, L>
+    where C: ToComputedValue, L: ToComputedValue,
+{
+    type ComputedValue = ColorStop<<C as ToComputedValue>::ComputedValue,
+                                   <L as ToComputedValue>::ComputedValue>;
+
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        ColorStop {
+            color: self.color.to_computed_value(context),
+            position: self.position.as_ref().map(|p| p.to_computed_value(context)),
+        }
+    }
+
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        ColorStop {
+            color: ToComputedValue::from_computed_value(&computed.color),
+            position: computed.position.as_ref().map(ToComputedValue::from_computed_value),
+        }
+    }
+}
+
+impl<C> ToCss for ImageRect<C>
+    where C: ToCss,
+{
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        dest.write_str("-moz-image-rect(")?;
+        self.url.to_css(dest)?;
+        dest.write_str(", ")?;
+        self.top.to_css(dest)?;
+        dest.write_str(", ")?;
+        self.right.to_css(dest)?;
+        dest.write_str(", ")?;
+        self.bottom.to_css(dest)?;
+        dest.write_str(", ")?;
+        self.left.to_css(dest)?;
+        dest.write_str(")")
+    }
+}
+
+impl<C> ToComputedValue for ImageRect<C>
+    where C: ToComputedValue,
+{
+    type ComputedValue = ImageRect<<C as ToComputedValue>::ComputedValue>;
+
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        ImageRect {
+            url: self.url.to_computed_value(context),
+            top: self.top.to_computed_value(context),
+            right: self.right.to_computed_value(context),
+            bottom: self.bottom.to_computed_value(context),
+            left: self.left.to_computed_value(context),
+        }
+    }
+
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        ImageRect {
+            url: ToComputedValue::from_computed_value(&computed.url),
+            top: ToComputedValue::from_computed_value(&computed.top),
+            right: ToComputedValue::from_computed_value(&computed.right),
+            bottom: ToComputedValue::from_computed_value(&computed.bottom),
+            left: ToComputedValue::from_computed_value(&computed.left),
+        }
+    }
+}
--- a/servo/components/style/values/generics/mod.rs
+++ b/servo/components/style/values/generics/mod.rs
@@ -9,16 +9,17 @@ use euclid::size::Size2D;
 use std::fmt;
 use style_traits::ToCss;
 use super::HasViewportPercentage;
 use super::computed::{Context, ToComputedValue};
 
 pub use self::basic_shape::serialize_radius_values;
 
 pub mod basic_shape;
+pub mod image;
 pub mod position;
 
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 /// A type for representing CSS `widthh` and `height` values.
 pub struct BorderRadiusSize<L>(pub Size2D<L>);
 
 impl<L> HasViewportPercentage for BorderRadiusSize<L> {
--- a/servo/components/style/values/specified/image.rs
+++ b/servo/components/style/values/specified/image.rs
@@ -3,714 +3,678 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! CSS handling for the specified value of
 //! [`image`][image]s
 //!
 //! [image]: https://drafts.csswg.org/css-images/#image-values
 
 use Atom;
-use cssparser::{Parser, Token, serialize_identifier};
+use cssparser::{Parser, Token};
 use parser::{Parse, ParserContext};
 #[cfg(feature = "servo")]
 use servo_url::ServoUrl;
+use std::cmp::Ordering;
+use std::f32::consts::PI;
 use std::fmt;
 use style_traits::ToCss;
-use values::specified::{Angle, CSSColor, Length, LengthOrPercentage, NumberOrPercentage};
-use values::specified::position::Position;
+use values::{Either, None_};
+use values::generics::image::{Circle, CompatMode, Ellipse, ColorStop as GenericColorStop};
+use values::generics::image::{EndingShape as GenericEndingShape, Gradient as GenericGradient};
+use values::generics::image::{GradientItem as GenericGradientItem, GradientKind as GenericGradientKind};
+use values::generics::image::{Image as GenericImage, ImageRect as GenericImageRect};
+use values::generics::image::{LineDirection as GenericsLineDirection, ShapeExtent};
+use values::generics::position::Position as GenericPosition;
+use values::specified::{Angle, CSSColor, Color, Length, LengthOrPercentage};
+use values::specified::{Number, NumberOrPercentage, Percentage};
+use values::specified::position::{Position, PositionComponent, Side, X, Y};
 use values::specified::url::SpecifiedUrl;
 
+/// A specified image layer.
+pub type ImageLayer = Either<None_, Image>;
+
 /// Specified values for an image according to CSS-IMAGES.
 /// https://drafts.csswg.org/css-images/#image-values
-#[derive(Clone, PartialEq, Debug)]
+pub type Image = GenericImage<Gradient, ImageRect>;
+
+/// Specified values for a CSS gradient.
+/// https://drafts.csswg.org/css-images/#gradients
+pub type Gradient = GenericGradient<
+    LineDirection,
+    Length,
+    LengthOrPercentage,
+    Position,
+    CSSColor,
+>;
+
+/// A specified gradient kind.
+pub type GradientKind = GenericGradientKind<
+    LineDirection,
+    Length,
+    LengthOrPercentage,
+    Position,
+>;
+
+/// A specified gradient line direction.
+#[derive(Clone, Debug, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub enum Image {
-    /// A `<url()>` image.
-    Url(SpecifiedUrl),
-    /// A `<gradient>` image.
-    Gradient(Gradient),
-    /// A `-moz-image-rect` image
-    ImageRect(ImageRect),
-    /// A `-moz-element(# <element-id>)`
-    Element(Atom),
+pub enum LineDirection {
+    /// An angular direction.
+    Angle(Angle),
+    /// A horizontal direction.
+    Horizontal(X),
+    /// A vertical direction.
+    Vertical(Y),
+    /// A direction towards a corner of a box.
+    Corner(X, Y),
 }
 
-impl ToCss for Image {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            Image::Url(ref url_value) => url_value.to_css(dest),
-            Image::Gradient(ref gradient) => gradient.to_css(dest),
-            Image::ImageRect(ref image_rect) => image_rect.to_css(dest),
-            Image::Element(ref selector) => {
-                dest.write_str("-moz-element(#")?;
-                // FIXME: We should get rid of these intermediate strings.
-                serialize_identifier(&*selector.to_string(), dest)?;
-                dest.write_str(")")
-            },
+/// A specified ending shape.
+pub type EndingShape = GenericEndingShape<Length, LengthOrPercentage>;
+
+/// A specified gradient item.
+pub type GradientItem = GenericGradientItem<CSSColor, LengthOrPercentage>;
+
+/// A computed color stop.
+pub type ColorStop = GenericColorStop<CSSColor, LengthOrPercentage>;
+
+/// Specified values for `moz-image-rect`
+/// -moz-image-rect(<uri>, top, right, bottom, left);
+pub type ImageRect = GenericImageRect<NumberOrPercentage>;
+
+impl Parse for Image {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Image, ()> {
+        if let Ok(url) = input.try(|input| SpecifiedUrl::parse(context, input)) {
+            return Ok(GenericImage::Url(url));
         }
+        if let Ok(gradient) = input.try(|i| Gradient::parse(context, i)) {
+            return Ok(GenericImage::Gradient(gradient));
+        }
+        if let Ok(image_rect) = input.try(|input| ImageRect::parse(context, input)) {
+            return Ok(GenericImage::Rect(image_rect));
+        }
+
+        Ok(GenericImage::Element(Image::parse_element(input)?))
     }
 }
 
 impl Image {
-    #[allow(missing_docs)]
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<Image, ()> {
-        if let Ok(url) = input.try(|input| SpecifiedUrl::parse(context, input)) {
-            return Ok(Image::Url(url));
-        }
-        if let Ok(gradient) = input.try(|input| Gradient::parse_function(context, input)) {
-            return Ok(Image::Gradient(gradient));
-        }
-        if let Ok(image_rect) = input.try(|input| ImageRect::parse(context, input)) {
-            return Ok(Image::ImageRect(image_rect));
-        }
-
-        Ok(Image::Element(Image::parse_element(input)?))
-    }
-
     /// Creates an already specified image value from an already resolved URL
     /// for insertion in the cascade.
     #[cfg(feature = "servo")]
     pub fn for_cascade(url: ServoUrl) -> Self {
-        Image::Url(SpecifiedUrl::for_cascade(url))
+        GenericImage::Url(SpecifiedUrl::for_cascade(url))
     }
 
     /// Parses a `-moz-element(# <element-id>)`.
     fn parse_element(input: &mut Parser) -> Result<Atom, ()> {
-        if input.try(|i| i.expect_function_matching("-moz-element")).is_ok() {
-            input.parse_nested_block(|i| {
-                match i.next()? {
-                    Token::IDHash(id) => Ok(Atom::from(id)),
-                    _ => Err(()),
+        input.try(|i| i.expect_function_matching("-moz-element"))?;
+        input.parse_nested_block(|i| {
+            match i.next()? {
+                Token::IDHash(id) => Ok(Atom::from(id)),
+                _ => Err(()),
+            }
+        })
+    }
+}
+
+impl Parse for Gradient {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        enum Shape {
+            Linear,
+            Radial,
+        }
+
+        let (shape, repeating, compat_mode) = match_ignore_ascii_case! { &try!(input.expect_function()),
+            "linear-gradient" => {
+                (Shape::Linear, false, CompatMode::Modern)
+            },
+            "-webkit-linear-gradient" => {
+                (Shape::Linear, false, CompatMode::WebKit)
+            },
+            "repeating-linear-gradient" => {
+                (Shape::Linear, true, CompatMode::Modern)
+            },
+            "-webkit-repeating-linear-gradient" => {
+                (Shape::Linear, true, CompatMode::WebKit)
+            },
+            "radial-gradient" => {
+                (Shape::Radial, false, CompatMode::Modern)
+            },
+            "-webkit-radial-gradient" => {
+                (Shape::Radial, false, CompatMode::WebKit)
+            },
+            "repeating-radial-gradient" => {
+                (Shape::Radial, true, CompatMode::Modern)
+            },
+            "-webkit-repeating-radial-gradient" => {
+                (Shape::Radial, true, CompatMode::WebKit)
+            },
+            "-webkit-gradient" => {
+                return input.parse_nested_block(|i| Self::parse_webkit_gradient_argument(context, i));
+            },
+            _ => { return Err(()); }
+        };
+
+        let (kind, items) = input.parse_nested_block(|i| {
+            let shape = match shape {
+                Shape::Linear => GradientKind::parse_linear(context, i, compat_mode)?,
+                Shape::Radial => GradientKind::parse_radial(context, i, compat_mode)?,
+            };
+            let items = GradientItem::parse_comma_separated(context, i)?;
+            Ok((shape, items))
+        })?;
+
+        if items.len() < 2 {
+            return Err(());
+        }
+
+        Ok(Gradient {
+            items: items,
+            repeating: repeating,
+            kind: kind,
+            compat_mode: compat_mode,
+        })
+    }
+}
+
+impl Gradient {
+    fn parse_webkit_gradient_argument(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        type Point = GenericPosition<Component<X>, Component<Y>>;
+
+        #[derive(Clone, Copy)]
+        enum Component<S> {
+            Center,
+            Number(NumberOrPercentage),
+            Side(S),
+        }
+
+        impl LineDirection {
+            fn from_points(first: Point, second: Point) -> Self {
+                let h_ord = first.horizontal.partial_cmp(&second.horizontal);
+                let v_ord = first.vertical.partial_cmp(&second.vertical);
+                let (h, v) = match (h_ord, v_ord) {
+                    (Some(h), Some(v)) => (h, v),
+                    _ => return LineDirection::Vertical(Y::Bottom),
+                };
+                match (h, v) {
+                    (Ordering::Less, Ordering::Less) => {
+                        LineDirection::Corner(X::Right, Y::Bottom)
+                    },
+                    (Ordering::Less, Ordering::Equal) => {
+                        LineDirection::Horizontal(X::Right)
+                    },
+                    (Ordering::Less, Ordering::Greater) => {
+                        LineDirection::Corner(X::Right, Y::Top)
+                    },
+                    (Ordering::Equal, Ordering::Greater) => {
+                        LineDirection::Vertical(Y::Top)
+                    },
+                    (Ordering::Equal, Ordering::Equal) |
+                    (Ordering::Equal, Ordering::Less) => {
+                        LineDirection::Vertical(Y::Bottom)
+                    },
+                    (Ordering::Greater, Ordering::Less) => {
+                        LineDirection::Corner(X::Left, Y::Bottom)
+                    },
+                    (Ordering::Greater, Ordering::Equal) => {
+                        LineDirection::Horizontal(X::Left)
+                    },
+                    (Ordering::Greater, Ordering::Greater) => {
+                        LineDirection::Corner(X::Left, Y::Top)
+                    },
+                }
+            }
+        }
+
+        impl From<Point> for Position {
+            fn from(point: Point) -> Self {
+                Self::new(point.horizontal.into(), point.vertical.into())
+            }
+        }
+
+        impl Parse for Point {
+            fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+                input.try(|i| {
+                    let x = Component::parse(context, i)?;
+                    let y = Component::parse(context, i)?;
+
+                    Ok(Self::new(x, y))
+                })
+            }
+        }
+
+        impl<S: Side> From<Component<S>> for NumberOrPercentage {
+            fn from(component: Component<S>) -> Self {
+                match component {
+                    Component::Center => NumberOrPercentage::Percentage(Percentage(0.5)),
+                    Component::Number(number) => number,
+                    Component::Side(side) => {
+                        let p = Percentage(if side.is_start() { 0. } else { 1. });
+                        NumberOrPercentage::Percentage(p)
+                    },
+                }
+            }
+        }
+
+        impl<S: Side> From<Component<S>> for PositionComponent<S> {
+            fn from(component: Component<S>) -> Self {
+                match component {
+                    Component::Center => {
+                        PositionComponent::Center
+                    },
+                    Component::Number(NumberOrPercentage::Number(number)) => {
+                        PositionComponent::Length(Length::from_px(number.value).into())
+                    },
+                    Component::Number(NumberOrPercentage::Percentage(p)) => {
+                        PositionComponent::Length(p.into())
+                    },
+                    Component::Side(side) => {
+                        PositionComponent::Side(side, None)
+                    },
+                }
+            }
+        }
+
+        impl<S: Copy + Side> Component<S> {
+            fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+                match (NumberOrPercentage::from(*self), NumberOrPercentage::from(*other)) {
+                    (NumberOrPercentage::Percentage(a), NumberOrPercentage::Percentage(b)) => {
+                        a.0.partial_cmp(&b.0)
+                    },
+                    (NumberOrPercentage::Number(a), NumberOrPercentage::Number(b)) => {
+                        a.value.partial_cmp(&b.value)
+                    },
+                    (_, _) => {
+                        None
+                    }
+                }
+            }
+        }
+
+        impl<S: Parse> Parse for Component<S> {
+            fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+                if let Ok(side) = input.try(|i| S::parse(context, i)) {
+                    return Ok(Component::Side(side));
+                }
+                if let Ok(number) = input.try(|i| NumberOrPercentage::parse(context, i)) {
+                    return Ok(Component::Number(number));
+                }
+                input.try(|i| i.expect_ident_matching("center"))?;
+                Ok(Component::Center)
+            }
+        }
+
+        let ident = input.expect_ident()?;
+        input.expect_comma()?;
+
+        let (kind, reverse_stops) = match_ignore_ascii_case! { &ident,
+            "linear" => {
+                let first = Point::parse(context, input)?;
+                input.expect_comma()?;
+                let second = Point::parse(context, input)?;
+
+                let direction = LineDirection::from_points(first, second);
+                let kind = GenericGradientKind::Linear(direction);
+
+                (kind, false)
+            },
+            "radial" => {
+                let first_point = Point::parse(context, input)?;
+                input.expect_comma()?;
+                let first_radius = Number::parse(context, input)?;
+                input.expect_comma()?;
+                let second_point = Point::parse(context, input)?;
+                input.expect_comma()?;
+                let second_radius = Number::parse(context, input)?;
+
+                let (reverse_stops, point, radius) = if second_radius.value >= first_radius.value {
+                    (false, second_point, second_radius)
+                } else {
+                    (true, first_point, first_radius)
+                };
+
+                let shape = GenericEndingShape::Circle(Circle::Radius(Length::from_px(radius.value)));
+                let position = point.into();
+                let kind = GenericGradientKind::Radial(shape, position);
+
+                (kind, reverse_stops)
+            },
+            _ => return Err(()),
+        };
+
+        let mut items = input.try(|i| {
+            i.expect_comma()?;
+            i.parse_comma_separated(|i| {
+                let function = i.expect_function()?;
+                let (color, mut p) = i.parse_nested_block(|i| {
+                    let p = match_ignore_ascii_case! { &function,
+                        "color-stop" => {
+                            let p = match NumberOrPercentage::parse(context, i)? {
+                                NumberOrPercentage::Number(number) => number.value,
+                                NumberOrPercentage::Percentage(p) => p.0,
+                            };
+                            i.expect_comma()?;
+                            p
+                        },
+                        "from" => 0.,
+                        "to" => 1.,
+                        _ => return Err(()),
+                    };
+                    let color = CSSColor::parse(context, i)?;
+                    if color.parsed == Color::CurrentColor {
+                        return Err(());
+                    }
+                    Ok((color, p))
+                })?;
+                if reverse_stops {
+                    p = 1. - p;
+                }
+                Ok(GenericGradientItem::ColorStop(GenericColorStop {
+                    color: color,
+                    position: Some(LengthOrPercentage::Percentage(Percentage(p))),
+                }))
+            })
+        }).unwrap_or(vec![]);
+
+        if items.is_empty() {
+            items = vec![
+                GenericGradientItem::ColorStop(GenericColorStop {
+                    color: CSSColor::transparent(),
+                    position: Some(Percentage(0.).into()),
+                }),
+                GenericGradientItem::ColorStop(GenericColorStop {
+                    color: CSSColor::transparent(),
+                    position: Some(Percentage(1.).into()),
+                }),
+            ];
+        } else if items.len() == 1 {
+            let first = items[0].clone();
+            items.push(first);
+        } else {
+            items.sort_by(|a, b| {
+                match (a, b) {
+                    (&GenericGradientItem::ColorStop(ref a), &GenericGradientItem::ColorStop(ref b)) => {
+                        match (&a.position, &b.position) {
+                            (&Some(LengthOrPercentage::Percentage(a)), &Some(LengthOrPercentage::Percentage(b))) => {
+                                let ordering = a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal);
+                                if ordering != Ordering::Equal {
+                                    return ordering;
+                                }
+                            },
+                            _ => {},
+                        }
+                    },
+                    _ => {},
+                }
+                if reverse_stops {
+                    Ordering::Greater
+                } else {
+                    Ordering::Less
                 }
             })
+        }
+
+        Ok(GenericGradient {
+            kind: kind,
+            items: items,
+            repeating: false,
+            compat_mode: CompatMode::Modern,
+        })
+    }
+}
+
+impl GradientKind {
+    fn parse_linear(context: &ParserContext,
+                    input: &mut Parser,
+                    compat_mode: CompatMode)
+                    -> Result<Self, ()> {
+        let direction = if let Ok(d) = input.try(|i| LineDirection::parse(context, i, compat_mode)) {
+            input.expect_comma()?;
+            d
         } else {
-            Err(())
+            LineDirection::Vertical(Y::Bottom)
+        };
+        Ok(GenericGradientKind::Linear(direction))
+    }
+
+    fn parse_radial(context: &ParserContext,
+                    input: &mut Parser,
+                    compat_mode: CompatMode)
+                    -> Result<Self, ()> {
+        let (shape, position) = if compat_mode == CompatMode::Modern {
+            let shape = input.try(|i| EndingShape::parse(context, i, compat_mode));
+            let position = input.try(|i| {
+                i.expect_ident_matching("at")?;
+                Position::parse(context, i)
+            });
+            (shape, position)
+        } else {
+            let position = input.try(|i| Position::parse(context, i));
+            let shape = input.try(|i| {
+                if position.is_ok() {
+                    i.expect_comma()?;
+                }
+                EndingShape::parse(context, i, compat_mode)
+            });
+            (shape, position)
+        };
+
+        if shape.is_ok() || position.is_ok() {
+            input.expect_comma()?;
+        }
+
+        let shape = shape.unwrap_or({
+            GenericEndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner))
+        });
+        let position = position.unwrap_or(Position::center());
+        Ok(GenericGradientKind::Radial(shape, position))
+    }
+}
+
+impl GenericsLineDirection for LineDirection {
+    fn points_downwards(&self) -> bool {
+        match *self {
+            LineDirection::Angle(ref angle) => angle.radians() == PI,
+            LineDirection::Vertical(Y::Bottom) => true,
+            _ => false,
+        }
+    }
+
+    fn to_css<W>(&self, dest: &mut W, compat_mode: CompatMode) -> fmt::Result
+        where W: fmt::Write
+    {
+        match *self {
+            LineDirection::Angle(angle) => {
+                angle.to_css(dest)
+            },
+            LineDirection::Horizontal(x) => {
+                if compat_mode == CompatMode::Modern {
+                    dest.write_str("to ")?;
+                }
+                x.to_css(dest)
+            },
+            LineDirection::Vertical(y) => {
+                if compat_mode == CompatMode::Modern {
+                    dest.write_str("to ")?;
+                }
+                y.to_css(dest)
+            },
+            LineDirection::Corner(x, y) => {
+                if compat_mode == CompatMode::Modern {
+                    dest.write_str("to ")?;
+                }
+                x.to_css(dest)?;
+                dest.write_str(" ")?;
+                y.to_css(dest)
+            }
         }
     }
 }
 
-/// Specified values for a CSS gradient.
-/// https://drafts.csswg.org/css-images/#gradients
-#[derive(Clone, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub struct Gradient {
-    /// The color stops and interpolation hints.
-    pub items: Vec<GradientItem>,
-    /// True if this is a repeating gradient.
-    pub repeating: bool,
-    /// Gradients can be linear or radial.
-    pub gradient_kind: GradientKind,
-    /// Compatibility mode.
-    pub compat_mode: CompatMode,
-}
-
-impl ToCss for Gradient {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        if self.compat_mode == CompatMode::WebKit {
-            try!(dest.write_str("-webkit-"));
-        }
-        if self.repeating {
-            try!(dest.write_str("repeating-"));
+impl LineDirection {
+    fn parse(context: &ParserContext,
+             input: &mut Parser,
+             compat_mode: CompatMode)
+             -> Result<Self, ()> {
+        if let Ok(angle) = input.try(|i| Angle::parse_with_unitless(context, i)) {
+            return Ok(LineDirection::Angle(angle));
         }
-        let mut skipcomma = false;
-        match self.gradient_kind {
-            GradientKind::Linear(angle_or_corner) => {
-                try!(dest.write_str("linear-gradient("));
-                try!(angle_or_corner.to_css(dest, self.compat_mode));
-                if angle_or_corner == AngleOrCorner::None {
-                    skipcomma = true;
+        input.try(|i| {
+            if compat_mode == CompatMode::Modern {
+                i.expect_ident_matching("to")?;
+            }
+            if let Ok(x) = i.try(X::parse) {
+                if let Ok(y) = i.try(Y::parse) {
+                    return Ok(LineDirection::Corner(x, y));
                 }
-            },
-            GradientKind::Radial(ref shape, ref position) => {
-                try!(dest.write_str("radial-gradient("));
-                if self.compat_mode == CompatMode::Modern {
-                    try!(shape.to_css(dest));
-                    try!(dest.write_str(" at "));
-                    try!(position.to_css(dest));
-                } else {
-                    try!(position.to_css(dest));
-                    try!(dest.write_str(", "));
-                    try!(shape.to_css(dest));
-                }
-            },
-        }
-        for item in &self.items {
-            if !skipcomma {
-                try!(dest.write_str(", "));
-            } else {
-                skipcomma = false;
+                return Ok(LineDirection::Horizontal(x));
             }
-            try!(item.to_css(dest));
-        }
-        dest.write_str(")")
+            let y = Y::parse(i)?;
+            if let Ok(x) = i.try(X::parse) {
+                return Ok(LineDirection::Corner(x, y));
+            }
+            Ok(LineDirection::Vertical(y))
+        })
     }
 }
 
-impl Gradient {
-    /// Parses a gradient from the given arguments.
-    pub fn parse_function(context: &ParserContext, input: &mut Parser) -> Result<Gradient, ()> {
-        fn parse<F>(context: &ParserContext, input: &mut Parser, parse_kind: F)
-                    -> Result<(GradientKind, Vec<GradientItem>), ()>
-            where F: FnOnce(&ParserContext, &mut Parser) -> Result<GradientKind, ()>
-        {
-            input.parse_nested_block(|input| {
-                let kind = try!(parse_kind(context, input));
-                let items = try!(Gradient::parse_items(context, input));
-                Ok((kind, items))
-            })
-        };
-        let mut repeating = false;
-        let mut compat_mode = CompatMode::Modern;
-        let (gradient_kind, items) = match_ignore_ascii_case! { &try!(input.expect_function()),
-            "linear-gradient" => {
-                try!(parse(context, input, GradientKind::parse_modern_linear))
-            },
-            "-webkit-linear-gradient" => {
-                compat_mode = CompatMode::WebKit;
-                try!(parse(context, input, GradientKind::parse_webkit_linear))
-            },
-            "repeating-linear-gradient" => {
-                repeating = true;
-                try!(parse(context, input, GradientKind::parse_modern_linear))
-            },
-            "-webkit-repeating-linear-gradient" => {
-                repeating = true;
-                compat_mode = CompatMode::WebKit;
-                try!(parse(context, input, GradientKind::parse_webkit_linear))
-            },
-            "radial-gradient" => {
-                try!(parse(context, input, GradientKind::parse_modern_radial))
-            },
-            "-webkit-radial-gradient" => {
-                compat_mode = CompatMode::WebKit;
-                try!(parse(context, input, GradientKind::parse_webkit_radial))
-            },
-            "repeating-radial-gradient" => {
-                repeating = true;
-                try!(parse(context, input, GradientKind::parse_modern_radial))
-            },
-            "-webkit-repeating-radial-gradient" => {
-                repeating = true;
-                compat_mode = CompatMode::WebKit;
-                try!(parse(context, input, GradientKind::parse_webkit_radial))
-            },
-            _ => { return Err(()); }
-        };
-
-        Ok(Gradient {
-            items: items,
-            repeating: repeating,
-            gradient_kind: gradient_kind,
-            compat_mode: compat_mode,
+impl EndingShape {
+    fn parse(context: &ParserContext,
+             input: &mut Parser,
+             compat_mode: CompatMode)
+             -> Result<Self, ()> {
+        if let Ok(extent) = input.try(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode)) {
+            if input.try(|i| i.expect_ident_matching("circle")).is_ok() {
+                return Ok(GenericEndingShape::Circle(Circle::Extent(extent)));
+            }
+            let _ = input.try(|i| i.expect_ident_matching("ellipse"));
+            return Ok(GenericEndingShape::Ellipse(Ellipse::Extent(extent)));
+        }
+        if input.try(|i| i.expect_ident_matching("circle")).is_ok() {
+            if let Ok(extent) = input.try(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode)) {
+                return Ok(GenericEndingShape::Circle(Circle::Extent(extent)));
+            }
+            if compat_mode == CompatMode::Modern {
+                if let Ok(length) = input.try(|i| Length::parse(context, i)) {
+                    return Ok(GenericEndingShape::Circle(Circle::Radius(length)));
+                }
+            }
+            return Ok(GenericEndingShape::Circle(Circle::Extent(ShapeExtent::FarthestCorner)));
+        }
+        if input.try(|i| i.expect_ident_matching("ellipse")).is_ok() {
+            if let Ok(extent) = input.try(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode)) {
+                return Ok(GenericEndingShape::Ellipse(Ellipse::Extent(extent)));
+            }
+            if compat_mode == CompatMode::Modern {
+                let pair: Result<_, ()> = input.try(|i| {
+                    let x = LengthOrPercentage::parse(context, i)?;
+                    let y = LengthOrPercentage::parse(context, i)?;
+                    Ok((x, y))
+                });
+                if let Ok((x, y)) = pair {
+                    return Ok(GenericEndingShape::Ellipse(Ellipse::Radii(x, y)));
+                }
+            }
+            return Ok(GenericEndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)));
+        }
+        if let Ok(length) = input.try(|i| Length::parse(context, i)) {
+            if let Ok(y) = input.try(|i| LengthOrPercentage::parse(context, i)) {
+                if compat_mode == CompatMode::Modern {
+                    let _ = input.try(|i| i.expect_ident_matching("ellipse"));
+                }
+                return Ok(GenericEndingShape::Ellipse(Ellipse::Radii(length.into(), y)));
+            }
+            if compat_mode == CompatMode::Modern {
+                let y = input.try(|i| {
+                    i.expect_ident_matching("ellipse")?;
+                    LengthOrPercentage::parse(context, i)
+                });
+                if let Ok(y) = y {
+                    return Ok(GenericEndingShape::Ellipse(Ellipse::Radii(length.into(), y)));
+                }
+                let _ = input.try(|i| i.expect_ident_matching("circle"));
+            }
+            return Ok(GenericEndingShape::Circle(Circle::Radius(length)));
+        }
+        input.try(|i| {
+            let x = Percentage::parse(context, i)?;
+            let y = if let Ok(y) = i.try(|i| LengthOrPercentage::parse(context, i)) {
+                if compat_mode == CompatMode::Modern {
+                    let _ = i.try(|i| i.expect_ident_matching("ellipse"));
+                }
+                y
+            } else {
+                if compat_mode == CompatMode::Modern {
+                    i.expect_ident_matching("ellipse")?;
+                }
+                LengthOrPercentage::parse(context, i)?
+            };
+            Ok(GenericEndingShape::Ellipse(Ellipse::Radii(x.into(), y)))
         })
     }
+}
 
-    fn parse_items(context: &ParserContext, input: &mut Parser) -> Result<Vec<GradientItem>, ()> {
+impl ShapeExtent {
+    fn parse_with_compat_mode(input: &mut Parser,
+                              compat_mode: CompatMode)
+                              -> Result<Self, ()> {
+        match try!(Self::parse(input)) {
+            ShapeExtent::Contain | ShapeExtent::Cover if compat_mode == CompatMode::Modern => Err(()),
+            keyword => Ok(keyword),
+        }
+    }
+}
+
+impl GradientItem {
+    fn parse_comma_separated(context: &ParserContext, input: &mut Parser) -> Result<Vec<Self>, ()> {
         let mut seen_stop = false;
         let items = try!(input.parse_comma_separated(|input| {
             if seen_stop {
                 if let Ok(hint) = input.try(|i| LengthOrPercentage::parse(context, i)) {
                     seen_stop = false;
-                    return Ok(GradientItem::InterpolationHint(hint));
+                    return Ok(GenericGradientItem::InterpolationHint(hint));
                 }
             }
             seen_stop = true;
-            ColorStop::parse(context, input).map(GradientItem::ColorStop)
+            ColorStop::parse(context, input).map(GenericGradientItem::ColorStop)
         }));
         if !seen_stop || items.len() < 2 {
             return Err(());
         }
         Ok(items)
     }
 }
 
-/// Specified values for CSS linear or radial gradients.
-/// https://drafts.csswg.org/css-images/#gradients
-#[derive(Clone, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub enum GradientKind {
-    /// A `<linear-gradient()>`:
-    ///
-    /// https://drafts.csswg.org/css-images/#funcdef-linear-gradient
-    Linear(AngleOrCorner),
-
-    /// A `<radial-gradient()>`:
-    ///
-    /// https://drafts.csswg.org/css-images/#radial-gradients
-    Radial(EndingShape, Position),
-}
-
-#[derive(Clone, Copy, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-/// Whether we used the modern notation or the compatibility `-webkit` prefix.
-pub enum CompatMode {
-    /// Modern syntax.
-    Modern,
-    /// `-webkit` prefix.
-    WebKit,
-}
-
-impl GradientKind {
-    /// Parses a linear gradient kind from the given arguments.
-    fn parse_modern_linear(context: &ParserContext, input: &mut Parser) -> Result<GradientKind, ()> {
-        let direction = if let Ok(angle) = input.try(|i| Angle::parse_with_unitless(context, i)) {
-            try!(input.expect_comma());
-            AngleOrCorner::Angle(angle)
-        } else {
-            if input.try(|i| i.expect_ident_matching("to")).is_ok() {
-                let (horizontal, vertical) =
-                    if let Ok(value) = input.try(HorizontalDirection::parse) {
-                        (Some(value), input.try(VerticalDirection::parse).ok())
-                    } else {
-                        let value = try!(VerticalDirection::parse(input));
-                        (input.try(HorizontalDirection::parse).ok(), Some(value))
-                    };
-                try!(input.expect_comma());
-                AngleOrCorner::Corner(horizontal, vertical)
-            } else {
-                AngleOrCorner::None
-            }
-        };
-        Ok(GradientKind::Linear(direction))
-    }
-
-    fn parse_webkit_linear(context: &ParserContext, input: &mut Parser) -> Result<GradientKind, ()> {
-        let direction = if let Ok(angle) = input.try(|i| Angle::parse_with_unitless(context, i)) {
-            AngleOrCorner::Angle(angle)
-        } else {
-            if let Ok(value) = input.try(HorizontalDirection::parse) {
-                AngleOrCorner::Corner(Some(value), input.try(VerticalDirection::parse).ok())
-            } else {
-                if let Ok(value) = input.try(VerticalDirection::parse) {
-                    AngleOrCorner::Corner(input.try(HorizontalDirection::parse).ok(), Some(value))
-                } else {
-                    AngleOrCorner::None
-                }
-            }
-        };
-        if direction != AngleOrCorner::None {
-            try!(input.expect_comma());
-        }
-        Ok(GradientKind::Linear(direction))
-    }
-
-    /// Parses a modern radial gradient from the given arguments.
-    pub fn parse_modern_radial(context: &ParserContext, input: &mut Parser) -> Result<GradientKind, ()> {
-        let mut needs_comma = true;
-
-        // Ending shape and position can be in various order. Checks all probabilities.
-        let (shape, position) = if let Ok(position) = input.try(|i| parse_position(context, i)) {
-            // Handle just <position>
-            (EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::FarthestCorner)), position)
-        } else if let Ok((first, second)) = input.try(|i| parse_two_length(context, i)) {
-            // Handle <LengthOrPercentage> <LengthOrPercentage> <shape>? <position>?
-            let _ = input.try(|input| input.expect_ident_matching("ellipse"));
-            (EndingShape::Ellipse(LengthOrPercentageOrKeyword::LengthOrPercentage(first, second)),
-             input.try(|i| parse_position(context, i)).unwrap_or(Position::center()))
-        } else if let Ok(length) = input.try(|i| Length::parse(context, i)) {
-            // Handle <Length> <circle>? <position>?
-            let _ = input.try(|input| input.expect_ident_matching("circle"));
-            (EndingShape::Circle(LengthOrKeyword::Length(length)),
-             input.try(|i| parse_position(context, i)).unwrap_or(Position::center()))
-        } else if let Ok(keyword) = input.try(SizeKeyword::parse_modern) {
-            // Handle <keyword> <shape-keyword>? <position>?
-            let shape = if input.try(|input| input.expect_ident_matching("circle")).is_ok() {
-                EndingShape::Circle(LengthOrKeyword::Keyword(keyword))
-            } else {
-                let _ = input.try(|input| input.expect_ident_matching("ellipse"));
-                EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(keyword))
-            };
-            (shape, input.try(|i| parse_position(context, i)).unwrap_or(Position::center()))
-        } else {
-            // Handle <shape-keyword> <length>? <position>?
-            if input.try(|input| input.expect_ident_matching("ellipse")).is_ok() {
-                // Handle <ellipse> <LengthOrPercentageOrKeyword>? <position>?
-                let length = input.try(|i| LengthOrPercentageOrKeyword::parse(context, i, SizeKeyword::parse_modern))
-                                  .unwrap_or(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::FarthestCorner));
-                (EndingShape::Ellipse(length),
-                 input.try(|i| parse_position(context, i)).unwrap_or(Position::center()))
-            } else if input.try(|input| input.expect_ident_matching("circle")).is_ok() {
-                // Handle <ellipse> <LengthOrKeyword>? <position>?
-                let length = input.try(|i| LengthOrKeyword::parse(context, i, SizeKeyword::parse_modern))
-                                  .unwrap_or(LengthOrKeyword::Keyword(SizeKeyword::FarthestCorner));
-                (EndingShape::Circle(length), input.try(|i| parse_position(context, i))
-                                                   .unwrap_or(Position::center()))
-            } else {
-                // If there is no shape keyword, it should set to default.
-                needs_comma = false;
-                (EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::FarthestCorner)),
-                 input.try(|i| parse_position(context, i)).unwrap_or(Position::center()))
-            }
-        };
-
-        if needs_comma {
-            try!(input.expect_comma());
-        }
-
-        Ok(GradientKind::Radial(shape, position))
-    }
-
-    /// Parses a webkit radial gradient from the given arguments.
-    /// https://compat.spec.whatwg.org/#css-gradients-webkit-radial-gradient
-    pub fn parse_webkit_radial(context: &ParserContext, input: &mut Parser) -> Result<GradientKind, ()> {
-        let position = if let Ok(position) = input.try(|i| Position::parse(context, i)) {
-            try!(input.expect_comma());
-            position
-        } else {
-            Position::center()
-        };
-
-        let mut needs_comma = true;
-
-        // Ending shape and position can be in various order. Checks all probabilities.
-        let shape = if let Ok((first, second)) = input.try(|i| parse_two_length(context, i)) {
-            EndingShape::Ellipse(LengthOrPercentageOrKeyword::LengthOrPercentage(first, second))
-        } else if let Ok(keyword) = input.try(SizeKeyword::parse) {
-            // Handle <keyword> <shape-keyword>?
-            if input.try(|input| input.expect_ident_matching("circle")).is_ok() {
-                EndingShape::Circle(LengthOrKeyword::Keyword(keyword))
-            } else {
-                let _ = input.try(|input| input.expect_ident_matching("ellipse"));
-                EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(keyword))
-            }
-        } else {
-            // Handle <shape-keyword> <keyword>?
-            if input.try(|input| input.expect_ident_matching("ellipse")).is_ok() {
-                // Handle <ellipse> <keyword>?
-                let keyword = input.try(SizeKeyword::parse).unwrap_or((SizeKeyword::Cover));
-                EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(keyword))
-            } else if input.try(|input| input.expect_ident_matching("circle")).is_ok() {
-                // Handle <circle> <keyword>?
-                let keyword = input.try(SizeKeyword::parse).unwrap_or((SizeKeyword::Cover));
-                EndingShape::Circle(LengthOrKeyword::Keyword(keyword))
-            } else {
-                // If there is no shape keyword, it should set to default.
-                needs_comma = false;
-                EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::Cover))
-            }
-        };
-
-        if needs_comma {
-            try!(input.expect_comma());
-        }
-
-        Ok(GradientKind::Radial(shape, position))
-    }
-}
-
-/// Specified values for `moz-image-rect`
-/// -moz-image-rect(<uri>, top, right, bottom, left);
-#[derive(Clone, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub struct ImageRect {
-    pub url: SpecifiedUrl,
-    pub top: NumberOrPercentage,
-    pub bottom: NumberOrPercentage,
-    pub right: NumberOrPercentage,
-    pub left: NumberOrPercentage,
-}
-
-impl ToCss for ImageRect {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        dest.write_str("-moz-image-rect(")?;
-        self.url.to_css(dest)?;
-        dest.write_str(", ")?;
-        self.top.to_css(dest)?;
-        dest.write_str(", ")?;
-        self.right.to_css(dest)?;
-        dest.write_str(", ")?;
-        self.bottom.to_css(dest)?;
-        dest.write_str(", ")?;
-        self.left.to_css(dest)?;
-        dest.write_str(")")
-    }
-}
-
-impl Parse for ImageRect {
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
-        match_ignore_ascii_case! { &try!(input.expect_function()),
-            "-moz-image-rect" => {
-                input.parse_nested_block(|input| {
-                    let url = SpecifiedUrl::parse(context, input)?;
-                    input.expect_comma()?;
-                    let top = NumberOrPercentage::parse(context, input)?;
-                    input.expect_comma()?;
-                    let right = NumberOrPercentage::parse(context, input)?;
-                    input.expect_comma()?;
-                    let bottom = NumberOrPercentage::parse(context, input)?;
-                    input.expect_comma()?;
-                    let left = NumberOrPercentage::parse(context, input)?;
-
-                    Ok(ImageRect {
-                        url: url,
-                        top: top,
-                        right: right,
-                        bottom: bottom,
-                        left: left,
-                    })
-                })
-            }
-            _ => Err(())
-        }
-    }
-}
-
-fn parse_two_length(context: &ParserContext, input: &mut Parser)
-                    -> Result<(LengthOrPercentage, LengthOrPercentage), ()> {
-    let first = try!(LengthOrPercentage::parse(context, input));
-    let second = try!(LengthOrPercentage::parse(context, input));
-    Ok((first, second))
-}
-
-fn parse_position(context: &ParserContext, input: &mut Parser) -> Result<Position, ()> {
-    try!(input.expect_ident_matching("at"));
-    input.try(|i| Position::parse(context, i))
-}
-
-/// Specified values for an angle or a corner in a linear gradient.
-#[derive(Clone, PartialEq, Copy, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub enum AngleOrCorner {
-    Angle(Angle),
-    Corner(Option<HorizontalDirection>, Option<VerticalDirection>),
-    None,
-}
-
-impl AngleOrCorner {
-    fn to_css<W>(&self, dest: &mut W, compat_mode: CompatMode) -> fmt::Result where W: fmt::Write {
-        match *self {
-            AngleOrCorner::None => Ok(()),
-            AngleOrCorner::Angle(angle) => angle.to_css(dest),
-            AngleOrCorner::Corner(horizontal, vertical) => {
-                if compat_mode == CompatMode::Modern {
-                    try!(dest.write_str("to "));
-                }
-                let mut horizontal_present = false;
-                if let Some(horizontal) = horizontal {
-                    try!(horizontal.to_css(dest));
-                    horizontal_present = true;
-                }
-                if let Some(vertical) = vertical {
-                    if horizontal_present {
-                        try!(dest.write_str(" "));
-                    }
-                    try!(vertical.to_css(dest));
-                }
-                Ok(())
-            }
-        }
-    }
-}
-
-/// Specified values for color stops and interpolation hints.
-/// https://drafts.csswg.org/css-images-4/#color-stop-syntax
-#[derive(Clone, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub enum GradientItem {
-    /// A color stop.
-    ColorStop(ColorStop),
-    /// An interpolation hint.
-    InterpolationHint(LengthOrPercentage),
-}
-
-impl ToCss for GradientItem {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            GradientItem::ColorStop(ref stop) => stop.to_css(dest),
-            GradientItem::InterpolationHint(ref hint) => hint.to_css(dest),
-        }
-    }
-}
-
-/// Specified values for one color stop in a gradient.
-/// https://drafts.csswg.org/css-images/#typedef-color-stop-list
-#[derive(Clone, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub struct ColorStop {
-    /// The color of this stop.
-    pub color: CSSColor,
-
-    /// The position of this stop. If not specified, this stop is placed halfway between the
-    /// point that precedes it and the point that follows it.
-    pub position: Option<LengthOrPercentage>,
-}
-
-impl ToCss for ColorStop {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        try!(self.color.to_css(dest));
-        if let Some(ref position) = self.position {
-            try!(dest.write_str(" "));
-            try!(position.to_css(dest));
-        }
-        Ok(())
-    }
-}
-
-define_css_keyword_enum!(HorizontalDirection: "left" => Left, "right" => Right);
-define_css_keyword_enum!(VerticalDirection: "top" => Top, "bottom" => Bottom);
-
 impl Parse for ColorStop {
     fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         Ok(ColorStop {
             color: try!(CSSColor::parse(context, input)),
             position: input.try(|i| LengthOrPercentage::parse(context, i)).ok(),
         })
     }
 }
 
-/// Determines whether the gradient's ending shape is a circle or an ellipse.
-/// If <shape> is omitted, the ending shape defaults to a circle
-/// if the <size> is a single <length>, and to an ellipse otherwise.
-/// https://drafts.csswg.org/css-images/#valdef-radial-gradient-ending-shape
-#[derive(Clone, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub enum EndingShape {
-    Circle(LengthOrKeyword),
-    Ellipse(LengthOrPercentageOrKeyword),
-}
-
-impl ToCss for EndingShape {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            EndingShape::Circle(ref length) => {
-                try!(dest.write_str("circle "));
-                try!(length.to_css(dest));
-            },
-            EndingShape::Ellipse(ref length) => {
-                try!(dest.write_str("ellipse "));
-                try!(length.to_css(dest));
-            },
-        }
-        Ok(())
-    }
-}
+impl Parse for ImageRect {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        input.try(|i| i.expect_function_matching("-moz-image-rect"))?;
+        input.parse_nested_block(|i| {
+            let string = i.expect_url_or_string()?;
+            let url = SpecifiedUrl::parse_from_string(string, context)?;
+            i.expect_comma()?;
+            let top = NumberOrPercentage::parse_non_negative(context, i)?;
+            i.expect_comma()?;
+            let right = NumberOrPercentage::parse_non_negative(context, i)?;
+            i.expect_comma()?;
+            let bottom = NumberOrPercentage::parse_non_negative(context, i)?;
+            i.expect_comma()?;
+            let left = NumberOrPercentage::parse_non_negative(context, i)?;
 
-/// https://drafts.csswg.org/css-images/#valdef-radial-gradient-size
-#[derive(Clone, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub enum LengthOrKeyword {
-    Length(Length),
-    Keyword(SizeKeyword),
-}
-
-impl LengthOrKeyword {
-    fn parse<F>(context: &ParserContext, input: &mut Parser, parse_size_keyword: F) -> Result<Self, ()>
-        where F: Fn(&mut Parser) -> Result<SizeKeyword, ()>
-    {
-        if let Ok(keyword) = input.try(parse_size_keyword) {
-            Ok(LengthOrKeyword::Keyword(keyword))
-        } else {
-            Ok(LengthOrKeyword::Length(try!(Length::parse(context, input))))
-        }
-    }
-}
-
-impl ToCss for LengthOrKeyword {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            LengthOrKeyword::Length(ref length) => length.to_css(dest),
-            LengthOrKeyword::Keyword(keyword) => keyword.to_css(dest),
-        }
+            Ok(ImageRect {
+                url: url,
+                top: top,
+                right: right,
+                bottom: bottom,
+                left: left,
+            })
+        })
     }
 }
-
-/// https://drafts.csswg.org/css-images/#valdef-radial-gradient-size
-#[derive(Clone, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub enum LengthOrPercentageOrKeyword {
-    LengthOrPercentage(LengthOrPercentage, LengthOrPercentage),
-    Keyword(SizeKeyword),
-}
-
-
-impl LengthOrPercentageOrKeyword {
-    fn parse<F>(context: &ParserContext, input: &mut Parser, parse_size_keyword: F) -> Result<Self, ()>
-        where F: Fn(&mut Parser) -> Result<SizeKeyword, ()>
-    {
-        if let Ok(keyword) = input.try(parse_size_keyword) {
-            Ok(LengthOrPercentageOrKeyword::Keyword(keyword))
-        } else {
-            Ok(LengthOrPercentageOrKeyword::LengthOrPercentage(
-                try!(LengthOrPercentage::parse(context, input)),
-                try!(LengthOrPercentage::parse(context, input))))
-        }
-    }
-}
-
-impl ToCss for LengthOrPercentageOrKeyword {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            LengthOrPercentageOrKeyword::LengthOrPercentage(ref first_len, ref second_len) => {
-                try!(first_len.to_css(dest));
-                try!(dest.write_str(" "));
-                second_len.to_css(dest)
-            },
-            LengthOrPercentageOrKeyword::Keyword(keyword) => keyword.to_css(dest),
-        }
-    }
-}
-
-/// https://drafts.csswg.org/css-images/#typedef-extent-keyword
-define_css_keyword_enum!(SizeKeyword: "closest-side" => ClosestSide, "farthest-side" => FarthestSide,
-                         "closest-corner" => ClosestCorner, "farthest-corner" => FarthestCorner,
-                         "contain" => Contain, "cover" => Cover);
-
-impl SizeKeyword {
-    fn parse_modern(input: &mut Parser) -> Result<Self, ()> {
-        match try!(SizeKeyword::parse(input)) {
-            SizeKeyword::Contain | SizeKeyword::Cover => Err(()),
-            keyword => Ok(keyword),
-        }
-    }
-}
-
-/// Specified values for none | <image> | <mask-source>.
-#[derive(Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub struct LayerImage(pub Option<Image>);
-no_viewport_percentage!(LayerImage);
-
-impl ToCss for LayerImage {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            LayerImage(Some(ref image)) => image.to_css(dest),
-            LayerImage(None) => dest.write_str("none"),
-        }
-    }
-}
-
-use super::computed::{ToComputedValue, Context};
-impl ToComputedValue for LayerImage {
-    type ComputedValue = super::computed::LayerImage;
-
-    #[inline]
-    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
-        match *self {
-            LayerImage(None) => super::computed::LayerImage(None),
-            LayerImage(Some(ref image)) =>
-                super::computed::LayerImage(Some(image.to_computed_value(context))),
-        }
-    }
-
-    #[inline]
-    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
-        match *computed {
-            super::computed::LayerImage(None) => LayerImage(None),
-            super::computed::LayerImage(Some(ref image)) =>
-                LayerImage(Some(ToComputedValue::from_computed_value(image))),
-        }
-    }
-}
-
-impl Parse for LayerImage {
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
-        if input.try(|input| input.expect_ident_matching("none")).is_ok() {
-            Ok(LayerImage(None))
-        } else {
-            Ok(LayerImage(Some(try!(Image::parse(context, input)))))
-        }
-    }
-}
--- a/servo/components/style/values/specified/length.rs
+++ b/servo/components/style/values/specified/length.rs
@@ -10,28 +10,27 @@ use app_units::Au;
 use cssparser::{Parser, Token};
 use euclid::size::Size2D;
 use font_metrics::FontMetricsQueryResult;
 use parser::{Parse, ParserContext};
 use std::{cmp, fmt, mem};
 use std::ascii::AsciiExt;
 use std::ops::Mul;
 use style_traits::ToCss;
-use style_traits::values::specified::AllowedLengthType;
+use style_traits::values::specified::{AllowedLengthType, AllowedNumericType};
 use stylesheets::CssRuleType;
 use super::{AllowQuirks, Number, ToComputedValue};
 use values::{Auto, CSSFloat, Either, FONT_MEDIUM_PX, HasViewportPercentage, None_, Normal};
 use values::ExtremumLength;
 use values::computed::{ComputedValueAsSpecified, Context};
 use values::specified::calc::CalcNode;
 
 pub use values::specified::calc::CalcLengthOrPercentage;
-pub use super::image::{AngleOrCorner, ColorStop, EndingShape as GradientEndingShape, Gradient};
-pub use super::image::{GradientKind, HorizontalDirection, Image, LengthOrKeyword, LengthOrPercentageOrKeyword};
-pub use super::image::{SizeKeyword, VerticalDirection};
+pub use super::image::{ColorStop, EndingShape as GradientEndingShape, Gradient};
+pub use super::image::{GradientKind, Image};
 
 /// Number of app units per pixel
 pub const AU_PER_PX: CSSFloat = 60.;
 /// Number of app units per inch
 pub const AU_PER_IN: CSSFloat = AU_PER_PX * 96.;
 /// Number of app units per centimeter
 pub const AU_PER_CM: CSSFloat = AU_PER_IN / 2.54;
 /// Number of app units per millimeter
@@ -725,35 +724,38 @@ impl ToCss for Percentage {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
         where W: fmt::Write,
     {
         write!(dest, "{}%", self.0 * 100.)
     }
 }
 
 impl Percentage {
-    fn parse_internal(input: &mut Parser, context: AllowedLengthType) -> Result<Self, ()> {
+    /// Parse a specific kind of percentage.
+    pub fn parse_with_clamping_mode(input: &mut Parser,
+                                    context: AllowedNumericType)
+                                    -> Result<Self, ()> {
         match try!(input.next()) {
             Token::Percentage(ref value) if context.is_ok(value.unit_value) => {
                 Ok(Percentage(value.unit_value))
             }
             _ => Err(())
         }
     }
 
     /// Parses a percentage token, but rejects it if it's negative.
     pub fn parse_non_negative(input: &mut Parser) -> Result<Self, ()> {
-        Self::parse_internal(input, AllowedLengthType::NonNegative)
+        Self::parse_with_clamping_mode(input, AllowedNumericType::NonNegative)
     }
 }
 
 impl Parse for Percentage {
     #[inline]
     fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
-        Self::parse_internal(input, AllowedLengthType::All)
+        Self::parse_with_clamping_mode(input, AllowedNumericType::All)
     }
 }
 
 impl ComputedValueAsSpecified for Percentage {}
 
 /// A length or a percentage value.
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -23,19 +23,18 @@ use super::computed::{self, Context};
 use super::computed::{Shadow as ComputedShadow, ToComputedValue};
 use super::generics::BorderRadiusSize as GenericBorderRadiusSize;
 use values::specified::calc::CalcNode;
 
 #[cfg(feature = "gecko")]
 pub use self::align::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
 pub use self::color::Color;
 pub use self::grid::{GridLine, TrackKeyword};
-pub use self::image::{AngleOrCorner, ColorStop, EndingShape as GradientEndingShape, Gradient};
-pub use self::image::{GradientItem, GradientKind, HorizontalDirection, Image, ImageRect, LayerImage};
-pub use self::image::{LengthOrKeyword, LengthOrPercentageOrKeyword, SizeKeyword, VerticalDirection};
+pub use self::image::{ColorStop, EndingShape as GradientEndingShape, Gradient};
+pub use self::image::{GradientItem, GradientKind, Image, ImageRect, ImageLayer};
 pub use self::length::AbsoluteLength;
 pub use self::length::{FontRelativeLength, ViewportPercentageLength, CharacterWidth, Length, CalcLengthOrPercentage};
 pub use self::length::{Percentage, LengthOrNone, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
 pub use self::length::{LengthOrPercentageOrNone, LengthOrPercentageOrAutoOrContent, NoCalcLength};
 pub use self::length::{MaxLength, MinLength};
 pub use self::position::{Position, PositionComponent};
 
 #[cfg(feature = "gecko")]
@@ -676,33 +675,47 @@ impl ToCss for Number {
             dest.write_str(")")?;
         }
         Ok(())
     }
 }
 
 /// <number-percentage>
 /// Accepts only non-negative numbers.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Clone, Copy, Debug, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 pub enum NumberOrPercentage {
     Percentage(Percentage),
     Number(Number),
 }
 
 no_viewport_percentage!(NumberOrPercentage);
 
-impl Parse for NumberOrPercentage {
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
-        if let Ok(per) = input.try(Percentage::parse_non_negative) {
+impl NumberOrPercentage {
+    fn parse_with_clamping_mode(context: &ParserContext,
+                                input: &mut Parser,
+                                type_: AllowedNumericType)
+                                -> Result<Self, ()> {
+        if let Ok(per) = input.try(|i| Percentage::parse_with_clamping_mode(i, type_)) {
             return Ok(NumberOrPercentage::Percentage(per));
         }
 
-        Number::parse_non_negative(context, input).map(NumberOrPercentage::Number)
+        parse_number_with_clamping_mode(context, input, type_).map(NumberOrPercentage::Number)
+    }
+
+    /// Parse a non-negative number or percentage.
+    pub fn parse_non_negative(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
+    }
+}
+
+impl Parse for NumberOrPercentage {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        Self::parse_with_clamping_mode(context, input, AllowedNumericType::All)
     }
 }
 
 impl ToCss for NumberOrPercentage {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         match *self {
             NumberOrPercentage::Percentage(percentage) => percentage.to_css(dest),
             NumberOrPercentage::Number(number) => number.to_css(dest),
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -1830,31 +1830,29 @@ pub extern "C" fn Servo_DeclarationBlock
 
 #[no_mangle]
 pub extern "C" fn Servo_DeclarationBlock_SetBackgroundImage(declarations:
                                                             RawServoDeclarationBlockBorrowed,
                                                             value: *const nsAString,
                                                             raw_extra_data: *mut URLExtraData) {
     use style::properties::PropertyDeclaration;
     use style::properties::longhands::background_image::SpecifiedValue as BackgroundImage;
-    use style::properties::longhands::background_image::single_value::SpecifiedValue as SingleBackgroundImage;
-    use style::values::specified::image::Image;
+    use style::values::Either;
+    use style::values::generics::image::Image;
     use style::values::specified::url::SpecifiedUrl;
 
     let url_data = unsafe { RefPtr::from_ptr_ref(&raw_extra_data) };
     let string = unsafe { (*value).to_string() };
     let error_reporter = RustLogReporter;
     let context = ParserContext::new(Origin::Author, url_data, &error_reporter,
                                      Some(CssRuleType::Style), PARSING_MODE_DEFAULT,
                                      QuirksMode::NoQuirks);
     if let Ok(url) = SpecifiedUrl::parse_from_string(string.into(), &context) {
         let decl = PropertyDeclaration::BackgroundImage(BackgroundImage(
-            vec![SingleBackgroundImage(
-                Some(Image::Url(url))
-            )]
+            vec![Either::Second(Image::Url(url))]
         ));
         write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
             decls.push(decl, Importance::Normal);
         })
     }
 }
 
 #[no_mangle]
--- a/servo/tests/unit/style/parsing/image.rs
+++ b/servo/tests/unit/style/parsing/image.rs
@@ -1,22 +1,14 @@
 /* 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/. */
 
-use euclid::size::TypedSize2D;
 use parsing::parse;
-use std::f32::consts::PI;
-use style::context::QuirksMode;
-use style::font_metrics::ServoMetricsProvider;
-use style::media_queries::{Device, MediaType};
-use style::properties::{ComputedValues, StyleBuilder};
-use style::values::computed;
-use style::values::computed::{Angle, Context, ToComputedValue};
-use style::values::specified;
+use style::parser::Parse;
 use style::values::specified::image::*;
 use style_traits::ToCss;
 
 #[test]
 fn test_linear_gradient() {
     // Parsing from the right
     assert_roundtrip_with_context!(Image::parse, "linear-gradient(to left, red, green)");
 
@@ -32,106 +24,82 @@ fn test_linear_gradient() {
     // Parsing with more than two entries in <color-stop-list>
     assert_roundtrip_with_context!(Image::parse, "linear-gradient(red, yellow, green)");
 
     // Parsing with percentage in the <color-stop-list>
     assert_roundtrip_with_context!(Image::parse, "linear-gradient(red, green, yellow 50%)");
 
     // Parsing without <angle> and <side-or-corner>
     assert_roundtrip_with_context!(Image::parse, "linear-gradient(red, green)");
-
-    // AngleOrCorner::None should become AngleOrCorner::Angle(Angle(PI)) when parsed
-    // Note that Angle(PI) is correct for top-to-bottom rendering, whereas Angle(0) would render bottom-to-top.
-    // ref: https://developer.mozilla.org/en-US/docs/Web/CSS/angle
-    let viewport_size = TypedSize2D::new(0., 0.);
-    let initial_style = ComputedValues::initial_values();
-    let device = Device::new(MediaType::Screen, viewport_size);
-    let specified_context = Context {
-        is_root_element: true,
-        device: &device,
-        inherited_style: initial_style,
-        layout_parent_style: initial_style,
-        style: StyleBuilder::for_derived_style(&initial_style),
-        cached_system_font: None,
-        font_metrics_provider: &ServoMetricsProvider,
-        in_media_query: false,
-        quirks_mode: QuirksMode::NoQuirks,
-    };
-    assert_eq!(specified::AngleOrCorner::None.to_computed_value(&specified_context),
-               computed::AngleOrCorner::Angle(Angle::from_radians(PI)));
 }
 
 #[test]
 fn test_radial_gradient() {
     // Parsing with all values
     assert_roundtrip_with_context!(Image::parse, "radial-gradient(circle closest-side at 20px 30px, red, green)");
-    assert_roundtrip_with_context!(Image::parse, "radial-gradient(ellipse closest-side at 20px 30px, red, green)");
+    assert_roundtrip_with_context!(Image::parse, "radial-gradient(ellipse closest-side at 20px 30px, red, green)",
+                                                 "radial-gradient(closest-side at 20px 30px, red, green)");
     assert_roundtrip_with_context!(Image::parse, "radial-gradient(closest-side circle at 20px 30px, red, green)",
                                                  "radial-gradient(circle closest-side at 20px 30px, red, green)");
     assert_roundtrip_with_context!(Image::parse, "radial-gradient(closest-side ellipse at 20px 30px, red, green)",
-                                                 "radial-gradient(ellipse closest-side at 20px 30px, red, green)");
+                                                 "radial-gradient(closest-side at 20px 30px, red, green)");
 
     // Parsing with <shape-keyword> and <size> reversed
     assert_roundtrip_with_context!(Image::parse, "radial-gradient(closest-side circle at 20px 30px, red, green)",
                                                  "radial-gradient(circle closest-side at 20px 30px, red, green)");
     assert_roundtrip_with_context!(Image::parse, "radial-gradient(closest-corner ellipse at 20px 30px, red, green)",
-                                                 "radial-gradient(ellipse closest-corner at 20px 30px, red, green)");
+                                                 "radial-gradient(closest-corner at 20px 30px, red, green)");
     assert_roundtrip_with_context!(Image::parse, "radial-gradient(30px circle, red, green)",
-                                                 "radial-gradient(circle 30px at center center, red, green)");
+                                                 "radial-gradient(30px at center center, red, green)");
     assert_roundtrip_with_context!(Image::parse, "radial-gradient(30px 40px ellipse, red, green)",
-                                                 "radial-gradient(ellipse 30px 40px at center center, red, green)");
+                                                 "radial-gradient(30px 40px at center center, red, green)");
 
     // Parsing without <size>
     assert_roundtrip_with_context!(Image::parse,
                                    "radial-gradient(circle, red, green)",
-                                   "radial-gradient(circle farthest-corner at center center, red, green)");
+                                   "radial-gradient(circle at center center, red, green)");
     assert_roundtrip_with_context!(Image::parse,
                                    "radial-gradient(ellipse, red, green)",
-                                   "radial-gradient(ellipse farthest-corner at center center, red, green)");
+                                   "radial-gradient(at center center, red, green)");
     assert_roundtrip_with_context!(Image::parse,
-                                   "radial-gradient(circle at 20px 30px, red, green)",
-                                   "radial-gradient(circle farthest-corner at 20px 30px, red, green)");
+                                   "radial-gradient(circle at 20px 30px, red, green)");
     assert_roundtrip_with_context!(Image::parse,
                                    "radial-gradient(ellipse at 20px 30px, red, green)",
-                                   "radial-gradient(ellipse farthest-corner at 20px 30px, red, green)");
+                                   "radial-gradient(at 20px 30px, red, green)");
 
 
     // Parsing without <shape-keyword>
     assert_roundtrip_with_context!(Image::parse,
-                                   "radial-gradient(20px at 20px 30px, red, green)",
-                                   "radial-gradient(circle 20px at 20px 30px, red, green)");
+                                   "radial-gradient(20px at 20px 30px, red, green)");
     assert_roundtrip_with_context!(Image::parse,
-                                   "radial-gradient(20px 30px at left center, red, green)",
-                                   "radial-gradient(ellipse 20px 30px at left center, red, green)");
+                                   "radial-gradient(20px 30px at left center, red, green)");
     assert_roundtrip_with_context!(Image::parse,
                                    "radial-gradient(closest-side at center, red, green)",
-                                   "radial-gradient(ellipse closest-side at center center, red, green)");
+                                   "radial-gradient(closest-side at center center, red, green)");
     assert_roundtrip_with_context!(Image::parse,
                                    "radial-gradient(20px, red, green)",
-                                   "radial-gradient(circle 20px at center center, red, green)");
+                                   "radial-gradient(20px at center center, red, green)");
     assert_roundtrip_with_context!(Image::parse,
                                    "radial-gradient(20px 30px, red, green)",
-                                   "radial-gradient(ellipse 20px 30px at center center, red, green)");
+                                   "radial-gradient(20px 30px at center center, red, green)");
     assert_roundtrip_with_context!(Image::parse,
                                    "radial-gradient(closest-side, red, green)",
-                                   "radial-gradient(ellipse closest-side at center center, red, green)");
+                                   "radial-gradient(closest-side at center center, red, green)");
 
     // Parsing without <shape-keyword> and <size>
     assert_roundtrip_with_context!(Image::parse,
                                    "radial-gradient(at center, red, green)",
-                                   "radial-gradient(ellipse farthest-corner at center center, red, green)");
+                                   "radial-gradient(at center center, red, green)");
     assert_roundtrip_with_context!(Image::parse,
-                                   "radial-gradient(at center bottom, red, green)",
-                                   "radial-gradient(ellipse farthest-corner at center bottom, red, green)");
+                                   "radial-gradient(at center bottom, red, green)");
     assert_roundtrip_with_context!(Image::parse,
-                                   "radial-gradient(at 40px 50px, red, green)",
-                                   "radial-gradient(ellipse farthest-corner at 40px 50px, red, green)");
+                                   "radial-gradient(at 40px 50px, red, green)");
 
     // Parsing with just color stops
     assert_roundtrip_with_context!(Image::parse,
                                    "radial-gradient(red, green)",
-                                   "radial-gradient(ellipse farthest-corner at center center, red, green)");
+                                   "radial-gradient(at center center, red, green)");
 
     // Parsing repeating radial gradient
     assert_roundtrip_with_context!(Image::parse,
                                    "repeating-radial-gradient(red, green)",
-                                   "repeating-radial-gradient(ellipse farthest-corner at center center, red, green)");
+                                   "repeating-radial-gradient(at center center, red, green)");
 }
--- a/servo/tests/unit/style/properties/serialization.rs
+++ b/servo/tests/unit/style/properties/serialization.rs
@@ -794,17 +794,18 @@ mod shorthand_serialization {
         use style::properties::longhands::mask_composite as composite;
         use style::properties::longhands::mask_image as image;
         use style::properties::longhands::mask_mode as mode;
         use style::properties::longhands::mask_origin as origin;
         use style::properties::longhands::mask_position_x as position_x;
         use style::properties::longhands::mask_position_y as position_y;
         use style::properties::longhands::mask_repeat as repeat;
         use style::properties::longhands::mask_size as size;
-        use style::values::specified::Image;
+        use style::values::Either;
+        use style::values::generics::image::Image;
         use super::*;
 
         macro_rules! single_vec_value_typedef {
             ($name:ident, $path:expr) => {
                 $name::SpecifiedValue(
                     vec![$path]
                 )
             };
@@ -823,19 +824,20 @@ mod shorthand_serialization {
                 )
             };
         }
 
         #[test]
         fn mask_should_serialize_all_available_properties_when_specified() {
             let mut properties = Vec::new();
 
-            let image = single_vec_value_typedef!(image,
-                image::single_value::SpecifiedValue(
-                    Some(Image::Url(SpecifiedUrl::new_for_testing("http://servo/test.png")))));
+            let image = single_vec_value_typedef!(
+                image,
+                Either::Second(Image::Url(SpecifiedUrl::new_for_testing("http://servo/test.png")))
+            );
 
             let mode = single_vec_keyword_value!(mode, luminance);
 
             let position_x = single_vec_value_typedef!(position_x,
                 PositionComponent::Length(LengthOrPercentage::Length(NoCalcLength::from_px(7f32)))
             );
             let position_y = single_vec_value_typedef!(position_y,
                 PositionComponent::Side(
@@ -875,19 +877,20 @@ mod shorthand_serialization {
                 repeat-x padding-box border-box subtract;"
             );
         }
 
         #[test]
         fn mask_should_combine_origin_and_clip_properties_when_equal() {
             let mut properties = Vec::new();
 
-            let image = single_vec_value_typedef!(image,
-                image::single_value::SpecifiedValue(
-                    Some(Image::Url(SpecifiedUrl::new_for_testing("http://servo/test.png")))));
+            let image = single_vec_value_typedef!(
+                image,
+                Either::Second(Image::Url(SpecifiedUrl::new_for_testing("http://servo/test.png")))
+            );
 
             let mode = single_vec_keyword_value!(mode, luminance);
 
             let position_x = single_vec_value_typedef!(position_x,
                 PositionComponent::Length(LengthOrPercentage::Length(NoCalcLength::from_px(7f32)))
             );
 
             let position_y = single_vec_value_typedef!(position_y,