servo: Merge #16444 - Cleanup various modules and introduce generic types (from Wafflespeanut:generics); r=emilio
authorRavi Shankar <wafflespeanut@gmail.com>
Tue, 25 Apr 2017 07:29:33 -0500
changeset 354879 18a60e84a76718f3cb88bb7def73ce23641d6179
parent 354878 16a917f9afbcf8f75191f8189d5abccbe03b1c6b
child 354880 80316590ca0e986f526b6914a3a7297def254d14
push id89591
push usercbook@mozilla.com
push dateWed, 26 Apr 2017 07:05:50 +0000
treeherdermozilla-inbound@45c2aad0e684 [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 #16444 - Cleanup various modules and introduce generic types (from Wafflespeanut:generics); r=emilio Almost all the types in `values/specified` and `values/computed` share their `ToCss` implementations. The only reason they have duplicated code hanging around is a result of different specified and computed forms for types like `LengthOrPercentage`. This PR makes some of these types *generic* so that we could have a common definition and `ToCss` impl (`Parse` and `ToComputedValue` impls too, if possible). Source-Repo: https://github.com/servo/servo Source-Revision: 3c7c960e116200010ea4120741e520bea9c6cfd9
servo/components/layout/model.rs
servo/components/style/gecko/conversions.rs
servo/components/style/gecko/values.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/helpers.mako.rs
servo/components/style/properties/helpers/animated_properties.mako.rs
servo/components/style/properties/longhand/background.mako.rs
servo/components/style/properties/longhand/border.mako.rs
servo/components/style/properties/longhand/box.mako.rs
servo/components/style/properties/longhand/column.mako.rs
servo/components/style/properties/longhand/inherited_text.mako.rs
servo/components/style/properties/longhand/svg.mako.rs
servo/components/style/properties/shorthand/border.mako.rs
servo/components/style/properties/shorthand/outline.mako.rs
servo/components/style/values/computed/basic_shape.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/computed/position.rs
servo/components/style/values/generics/basic_shape.rs
servo/components/style/values/generics/mod.rs
servo/components/style/values/generics/position.rs
servo/components/style/values/mod.rs
servo/components/style/values/specified/basic_shape.rs
servo/components/style/values/specified/length.rs
servo/components/style/values/specified/mod.rs
servo/components/style/values/specified/position.rs
servo/tests/unit/style/properties/serialization.rs
--- a/servo/components/layout/model.rs
+++ b/servo/components/layout/model.rs
@@ -11,16 +11,17 @@ use euclid::{Matrix4D, SideOffsets2D, Si
 use fragment::Fragment;
 use std::cmp::{max, min};
 use std::fmt;
 use style::computed_values::transform::ComputedMatrix;
 use style::logical_geometry::{LogicalMargin, WritingMode};
 use style::properties::ServoComputedValues;
 use style::values::computed::{BorderRadiusSize, LengthOrPercentageOrAuto};
 use style::values::computed::{LengthOrPercentage, LengthOrPercentageOrNone};
+use style::values::generics;
 
 /// A collapsible margin. See CSS 2.1 ยง 8.3.1.
 #[derive(Copy, Clone, Debug)]
 pub struct AdjoiningMargins {
     /// The value of the greatest positive margin.
     pub most_positive: Au,
 
     /// The actual value (not the absolute value) of the negative margin with the largest absolute
@@ -473,17 +474,17 @@ pub fn specified(length: LengthOrPercent
         LengthOrPercentage::Length(length) => length,
         LengthOrPercentage::Percentage(p) => containing_length.scale_by(p),
         LengthOrPercentage::Calc(calc) =>
             containing_length.scale_by(calc.percentage()) + calc.length(),
     }
 }
 
 pub fn specified_border_radius(radius: BorderRadiusSize, containing_length: Au) -> Size2D<Au> {
-    let BorderRadiusSize(size) = radius;
+    let generics::BorderRadiusSize(size) = radius;
     let w = specified(size.width, containing_length);
     let h = specified(size.height, containing_length);
     Size2D::new(w, h)
 }
 
 #[inline]
 pub fn padding_from_style(style: &ServoComputedValues,
                           containing_block_inline_size: Au,
--- a/servo/components/style/gecko/conversions.rs
+++ b/servo/components/style/gecko/conversions.rs
@@ -268,18 +268,18 @@ impl nsStyleImage {
                             unsafe {
                                 (*gecko_gradient).mRadiusX.set(first_len);
                                 (*gecko_gradient).mRadiusY.set(second_len);
                             }
                         }
                     },
                 }
                 unsafe {
-                    (*gecko_gradient).mBgPosX.set(position.horizontal);
-                    (*gecko_gradient).mBgPosY.set(position.vertical);
+                    (*gecko_gradient).mBgPosX.set(position.horizontal.0);
+                    (*gecko_gradient).mBgPosY.set(position.vertical.0);
                 }
 
                 gecko_gradient
             },
         };
 
         for (index, stop) in gradient.stops.iter().enumerate() {
             // NB: stops are guaranteed to be none in the gecko side by
@@ -313,27 +313,29 @@ impl nsStyleImage {
             Gecko_SetGradientImageValue(self, gecko_gradient);
         }
     }
 }
 
 pub mod basic_shape {
     //! Conversions from and to CSS shape representations.
 
-    use euclid::size::Size2D;
     use gecko::values::GeckoStyleCoordConvertible;
     use gecko_bindings::structs;
     use gecko_bindings::structs::{StyleBasicShape, StyleBasicShapeType, StyleFillRule};
     use gecko_bindings::structs::{nsStyleCoord, nsStyleCorners};
     use gecko_bindings::structs::StyleGeometryBox;
     use gecko_bindings::sugar::ns_style_coord::{CoordDataMut, CoordDataValue};
     use std::borrow::Borrow;
     use values::computed::{BorderRadiusSize, LengthOrPercentage};
     use values::computed::basic_shape::*;
     use values::computed::position;
+    use values::generics::BorderRadiusSize as GenericBorderRadiusSize;
+    use values::generics::basic_shape::FillRule;
+    use values::generics::position::{HorizontalPosition, VerticalPosition};
 
     // using Borrow so that we can have a non-moving .into()
     impl<T: Borrow<StyleBasicShape>> From<T> for BasicShape {
         fn from(other: T) -> Self {
             let other = other.borrow();
             match other.mType {
                 StyleBasicShapeType::Inset => {
                     let t = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[0]);
@@ -386,21 +388,21 @@ pub mod basic_shape {
             }
         }
     }
 
     impl<T: Borrow<nsStyleCorners>> From<T> for BorderRadius {
         fn from(other: T) -> Self {
             let other = other.borrow();
             let get_corner = |index| {
-                BorderRadiusSize(Size2D::new(
+                GenericBorderRadiusSize::new(
                     LengthOrPercentage::from_gecko_style_coord(&other.data_at(index))
                         .expect("<border-radius> should be a length, percentage, or calc value"),
                     LengthOrPercentage::from_gecko_style_coord(&other.data_at(index + 1))
-                        .expect("<border-radius> should be a length, percentage, or calc value")))
+                        .expect("<border-radius> should be a length, percentage, or calc value"))
             };
 
             BorderRadius {
                 top_left: get_corner(0),
                 top_right: get_corner(2),
                 bottom_right: get_corner(4),
                 bottom_left: get_corner(6),
             }
@@ -434,36 +436,36 @@ pub mod basic_shape {
         }
     }
 
     // Can't be a From impl since we need to set an existing
     // Position, not create a new one
     impl From<position::Position> for structs::Position {
         fn from(other: position::Position) -> Self {
             structs::Position {
-                mXPosition: other.horizontal.into(),
-                mYPosition: other.vertical.into()
+                mXPosition: other.horizontal.0.into(),
+                mYPosition: other.vertical.0.into()
             }
         }
     }
 
     impl<T: Borrow<nsStyleCoord>> From<T> for ShapeRadius {
         fn from(other: T) -> Self {
             let other = other.borrow();
             ShapeRadius::from_gecko_style_coord(other)
                 .expect("<shape-radius> should be a length, percentage, calc, or keyword value")
         }
     }
 
     impl<T: Borrow<structs::Position>> From<T> for position::Position {
         fn from(other: T) -> Self {
             let other = other.borrow();
             position::Position {
-                horizontal: other.mXPosition.into(),
-                vertical: other.mYPosition.into(),
+                horizontal: HorizontalPosition(other.mXPosition.into()),
+                vertical: VerticalPosition(other.mYPosition.into()),
             }
         }
     }
 
     impl From<ShapeBox> for StyleGeometryBox {
         fn from(reference: ShapeBox) -> Self {
             use gecko_bindings::structs::StyleGeometryBox::*;
             match reference {
--- a/servo/components/style/gecko/values.rs
+++ b/servo/components/style/gecko/values.rs
@@ -10,17 +10,18 @@ use app_units::Au;
 use cssparser::RGBA;
 use gecko_bindings::structs::{nsStyleCoord, StyleGridTrackBreadth, StyleShapeRadius};
 use gecko_bindings::sugar::ns_style_coord::{CoordData, CoordDataMut, CoordDataValue};
 use std::cmp::max;
 use values::{Auto, Either, ExtremumLength, None_, Normal};
 use values::computed::{Angle, LengthOrPercentage, LengthOrPercentageOrAuto};
 use values::computed::{LengthOrPercentageOrNone, Number, NumberOrPercentage};
 use values::computed::{MaxLength, MinLength};
-use values::computed::basic_shape::ShapeRadius;
+use values::computed::basic_shape::ShapeRadius as ComputedShapeRadius;
+use values::generics::basic_shape::ShapeRadius;
 use values::specified::Percentage;
 use values::specified::grid::{TrackBreadth, TrackKeyword};
 
 /// A trait that defines an interface to convert from and to `nsStyleCoord`s.
 pub trait GeckoStyleCoordConvertible : Sized {
     /// Convert this to a `nsStyleCoord`.
     fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T);
     /// Given a `nsStyleCoord`, try to get a value of this type..
@@ -200,25 +201,23 @@ impl<L: GeckoStyleCoordConvertible> Geck
                 CoordDataValue::FlexFraction(fr) => Some(TrackBreadth::Flex(fr)),
                 CoordDataValue::Auto => Some(TrackBreadth::Keyword(TrackKeyword::Auto)),
                 _ => L::from_gecko_style_coord(coord).map(TrackBreadth::Breadth),
             }
         })
     }
 }
 
-impl GeckoStyleCoordConvertible for ShapeRadius {
+impl GeckoStyleCoordConvertible for ComputedShapeRadius {
     fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
         match *self {
-            ShapeRadius::ClosestSide => {
-                coord.set_value(CoordDataValue::Enumerated(StyleShapeRadius::ClosestSide as u32))
-            }
-            ShapeRadius::FarthestSide => {
-                coord.set_value(CoordDataValue::Enumerated(StyleShapeRadius::FarthestSide as u32))
-            }
+            ShapeRadius::ClosestSide =>
+                coord.set_value(CoordDataValue::Enumerated(StyleShapeRadius::ClosestSide as u32)),
+            ShapeRadius::FarthestSide =>
+                coord.set_value(CoordDataValue::Enumerated(StyleShapeRadius::FarthestSide as u32)),
             ShapeRadius::Length(lop) => lop.to_gecko_style_coord(coord),
         }
     }
 
     fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
         match coord.as_value() {
             CoordDataValue::Enumerated(v) => {
                 if v == StyleShapeRadius::ClosestSide as u32 {
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -371,27 +371,27 @@ fn color_to_nscolor_zero_currentcolor(co
             Au(self.gecko.${gecko_ffi_name})
         }
     % endif
 </%def>
 
 <%def name="impl_position(ident, gecko_ffi_name, need_clone=False)">
     #[allow(non_snake_case)]
     pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
-        ${set_gecko_property("%s.mXPosition" % gecko_ffi_name, "v.horizontal.into()")}
-        ${set_gecko_property("%s.mYPosition" % gecko_ffi_name, "v.vertical.into()")}
+        ${set_gecko_property("%s.mXPosition" % gecko_ffi_name, "v.horizontal.0.into()")}
+        ${set_gecko_property("%s.mYPosition" % gecko_ffi_name, "v.vertical.0.into()")}
     }
     <%call expr="impl_simple_copy(ident, gecko_ffi_name)"></%call>
     % if need_clone:
         #[allow(non_snake_case)]
         pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
-            use values::computed::Position;
+            use values::generics::position::{HorizontalPosition, Position, VerticalPosition};
             Position {
-                horizontal: self.gecko.${gecko_ffi_name}.mXPosition.into(),
-                vertical: self.gecko.${gecko_ffi_name}.mYPosition.into(),
+                horizontal: HorizontalPosition(self.gecko.${gecko_ffi_name}.mXPosition.into()),
+                vertical: VerticalPosition(self.gecko.${gecko_ffi_name}.mYPosition.into()),
             }
         }
     % endif
 </%def>
 
 <%def name="impl_color(ident, gecko_ffi_name, need_clone=False, complex_color=True)">
 <%call expr="impl_color_setter(ident, gecko_ffi_name, complex_color)"></%call>
 <%call expr="impl_color_copy(ident, gecko_ffi_name, complex_color)"></%call>
@@ -538,25 +538,24 @@ fn color_to_nscolor_zero_currentcolor(co
         self.gecko.${gecko_ffi_name}.data_at_mut(${x_index})
                   .copy_from(&other.gecko.${gecko_ffi_name}.data_at(${x_index}));
         self.gecko.${gecko_ffi_name}.data_at_mut(${y_index})
                   .copy_from(&other.gecko.${gecko_ffi_name}.data_at(${y_index}));
     }
     % if need_clone:
         #[allow(non_snake_case)]
         pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
-            use properties::longhands::${ident}::computed_value::T;
-            use euclid::Size2D;
+            use values::generics::BorderRadiusSize;
             let width = GeckoStyleCoordConvertible::from_gecko_style_coord(
                             &self.gecko.${gecko_ffi_name}.data_at(${x_index}))
                             .expect("Failed to clone ${ident}");
             let height = GeckoStyleCoordConvertible::from_gecko_style_coord(
                             &self.gecko.${gecko_ffi_name}.data_at(${y_index}))
                             .expect("Failed to clone ${ident}");
-            T(Size2D::new(width, height))
+            BorderRadiusSize::new(width, height)
         }
     % endif
 </%def>
 
 <%def name="impl_css_url(ident, gecko_ffi_name, need_clone=False)">
     #[allow(non_snake_case)]
     pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
         use gecko_bindings::sugar::refptr::RefPtr;
@@ -1943,18 +1942,18 @@ fn static_assert() {
 
     ${impl_coord_copy('scroll_snap_points_y', 'mScrollSnapPointsY')}
 
     pub fn set_scroll_snap_coordinate(&mut self, v: longhands::scroll_snap_coordinate::computed_value::T) {
         unsafe { self.gecko.mScrollSnapCoordinate.set_len_pod(v.0.len() as u32); }
         for (gecko, servo) in self.gecko.mScrollSnapCoordinate
                                .iter_mut()
                                .zip(v.0.iter()) {
-            gecko.mXPosition = servo.horizontal.into();
-            gecko.mYPosition = servo.vertical.into();
+            gecko.mXPosition = servo.horizontal.0.into();
+            gecko.mYPosition = servo.vertical.0.into();
         }
     }
 
     pub fn copy_scroll_snap_coordinate_from(&mut self, other: &Self) {
         unsafe {
             self.gecko.mScrollSnapCoordinate
                 .set_len_pod(other.gecko.mScrollSnapCoordinate.len() as u32);
         }
@@ -2596,17 +2595,17 @@ fn static_assert() {
                 = other.mPosition.m${orientation[0].upper()}Position;
         }
         self.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count
                = other.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count;
     }
 
     pub fn clone_${shorthand}_position_${orientation[0]}(&self)
         -> longhands::${shorthand}_position_${orientation[0]}::computed_value::T {
-        use values::computed::position::${orientation[1]}Position;
+        use values::generics::position::${orientation[1]}Position;
         longhands::${shorthand}_position_${orientation[0]}::computed_value::T(
             self.gecko.${image_layers_field}.mLayers.iter()
                 .take(self.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count as usize)
                 .map(|position| ${orientation[1]}Position(position.mPosition.m${orientation[0].upper()}Position.into()))
                 .collect()
         )
     }
 
@@ -3560,32 +3559,32 @@ fn static_assert() {
     pub fn has_line_through(&self) -> bool {
         (self.gecko.mTextDecorationLine & (structs::NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH as u8)) != 0
     }
 </%self:impl_trait>
 
 <%def name="impl_shape_source(ident, gecko_ffi_name)">
     pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
         use gecko_bindings::bindings::{Gecko_NewBasicShape, Gecko_DestroyShapeSource};
-        use gecko_bindings::structs::StyleGeometryBox;
         use gecko_bindings::structs::{StyleBasicShape, StyleBasicShapeType, StyleShapeSourceType};
-        use gecko_bindings::structs::{StyleFillRule, StyleShapeSource};
+        use gecko_bindings::structs::{StyleFillRule, StyleGeometryBox, StyleShapeSource};
         use gecko::conversions::basic_shape::set_corners_from_radius;
         use gecko::values::GeckoStyleCoordConvertible;
-        use values::computed::basic_shape::*;
+        use values::computed::basic_shape::BasicShape;
+        use values::generics::basic_shape::{ShapeSource, FillRule};
         let ref mut ${ident} = self.gecko.${gecko_ffi_name};
         // clean up existing struct
         unsafe { Gecko_DestroyShapeSource(${ident}) };
 
         ${ident}.mType = StyleShapeSourceType::None;
 
         match v {
             ShapeSource::Url(ref url) => {
                 unsafe {
-                    bindings::Gecko_StyleShapeSource_SetURLValue(${ident}, url.for_ffi());
+                    bindings::Gecko_StyleShapeSource_SetURLValue(${ident}, url.for_ffi())
                 }
             }
             ShapeSource::None => {} // don't change the type
             ShapeSource::Box(reference) => {
                 ${ident}.mReferenceBox = reference.into();
                 ${ident}.mType = StyleShapeSourceType::Box;
             }
             ShapeSource::Shape(servo_shape, maybe_box) => {
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -3,24 +3,28 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 <%!
     from data import Keyword, to_rust_ident, to_camel_case
     from data import LOGICAL_SIDES, PHYSICAL_SIDES, LOGICAL_SIZES, SYSTEM_FONT_LONGHANDS
 %>
 
 <%def name="predefined_type(name, type, initial_value, parse_method='parse',
-            needs_context=True, vector=False, initial_specified_value=None, **kwargs)">
+            needs_context=True, vector=False, computed_type=None, initial_specified_value=None, **kwargs)">
     <%def name="predefined_type_inner(name, type, initial_value, parse_method)">
         #[allow(unused_imports)]
         use app_units::Au;
         use cssparser::{Color as CSSParserColor, RGBA};
         pub use values::specified::${type} as SpecifiedValue;
         pub mod computed_value {
+            % if computed_type:
+            pub use ${computed_type} as T;
+            % else:
             pub use values::computed::${type} as T;
+            % endif
         }
         #[inline] pub fn get_initial_value() -> computed_value::T { ${initial_value} }
         % if initial_specified_value:
         #[inline] pub fn get_initial_specified_value() -> SpecifiedValue { ${initial_specified_value} }
         % endif
         #[allow(unused_variables)]
         #[inline]
         pub fn parse(context: &ParserContext,
@@ -31,20 +35,26 @@
             % else:
             specified::${type}::${parse_method}(input)
             % endif
         }
     </%def>
     % if vector:
         <%call expr="vector_longhand(name, predefined_type=type, **kwargs)">
             ${predefined_type_inner(name, type, initial_value, parse_method)}
+            % if caller:
+            ${caller.body()}
+            % endif
         </%call>
     % else:
         <%call expr="longhand(name, predefined_type=type, **kwargs)">
             ${predefined_type_inner(name, type, initial_value, parse_method)}
+            % if caller:
+            ${caller.body()}
+            % endif
         </%call>
     % endif
 </%def>
 
 // FIXME (Manishearth): Add computed_value_as_specified argument
 // and handle the empty case correctly
 <%doc>
     To be used in cases where we have a grammar like
@@ -233,17 +243,17 @@
         use cascade_info::CascadeInfo;
         use error_reporting::ParseErrorReporter;
         use properties::longhands;
         use properties::{DeclaredValue, LonghandId, LonghandIdSet};
         use properties::{CSSWideKeyword, ComputedValues, PropertyDeclaration};
         use properties::style_structs;
         use std::sync::Arc;
         use values::computed::{Context, ToComputedValue};
-        use values::{computed, specified};
+        use values::{computed, generics, specified};
         use Atom;
         ${caller.body()}
         #[allow(unused_variables)]
         pub fn cascade_property(declaration: &PropertyDeclaration,
                                 inherited_style: &ComputedValues,
                                 default_style: &ComputedValues,
                                 context: &mut computed::Context,
                                 cacheable: &mut bool,
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -26,24 +26,24 @@ use properties::longhands::vertical_alig
 use properties::longhands::visibility::computed_value::T as Visibility;
 #[cfg(feature = "gecko")] use properties::{PropertyDeclarationId, LonghandId};
 use std::cmp;
 #[cfg(feature = "gecko")] use std::collections::HashMap;
 use std::fmt;
 use style_traits::ToCss;
 use super::ComputedValues;
 use values::CSSFloat;
-use values::{Auto, Either, Normal};
+use values::{Auto, Either, Normal, generics};
 use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone};
 use values::computed::{BorderRadiusSize, ClipRect, LengthOrNone};
 use values::computed::{CalcLengthOrPercentage, Context, LengthOrPercentage};
 use values::computed::{MaxLength, MinLength};
-use values::computed::position::{HorizontalPosition, Position, VerticalPosition};
+use values::computed::position::{HorizontalPosition, VerticalPosition};
 use values::computed::ToComputedValue;
-
+use values::generics::position as generic_position;
 
 
 /// A given transition property, that is either `All`, or an animatable
 /// property.
 // NB: This needs to be here because it needs all the longhands generated
 // beforehand.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
@@ -706,17 +706,17 @@ impl<T: Interpolate + Copy> Interpolate 
 
         Ok(Point2D::new(x, y))
     }
 }
 
 impl Interpolate for BorderRadiusSize {
     #[inline]
     fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
-        self.0.interpolate(&other.0, progress).map(BorderRadiusSize)
+        self.0.interpolate(&other.0, progress).map(generics::BorderRadiusSize)
     }
 }
 
 /// https://drafts.csswg.org/css-transitions/#animtype-length
 impl Interpolate for VerticalAlign {
     #[inline]
     fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
         match (*self, *other) {
@@ -952,43 +952,44 @@ impl Interpolate for FontWeight {
             FontWeight::Weight800
         } else {
             FontWeight::Weight900
         })
     }
 }
 
 /// https://drafts.csswg.org/css-transitions/#animtype-simple-list
-impl Interpolate for Position {
+impl<H: Interpolate, V: Interpolate> Interpolate for generic_position::Position<H, V> {
     #[inline]
     fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
-        Ok(Position {
+        Ok(generic_position::Position {
             horizontal: try!(self.horizontal.interpolate(&other.horizontal, progress)),
             vertical: try!(self.vertical.interpolate(&other.vertical, progress)),
         })
     }
 }
 
-impl RepeatableListInterpolate for Position {}
+impl<H, V> RepeatableListInterpolate for generic_position::Position<H, V>
+    where H: RepeatableListInterpolate, V: RepeatableListInterpolate {}
 
 /// https://drafts.csswg.org/css-transitions/#animtype-simple-list
 impl Interpolate for HorizontalPosition {
     #[inline]
     fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
-        Ok(HorizontalPosition(try!(self.0.interpolate(&other.0, progress))))
+        self.0.interpolate(&other.0, progress).map(generic_position::HorizontalPosition)
     }
 }
 
 impl RepeatableListInterpolate for HorizontalPosition {}
 
 /// https://drafts.csswg.org/css-transitions/#animtype-simple-list
 impl Interpolate for VerticalPosition {
     #[inline]
     fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
-        Ok(VerticalPosition(try!(self.0.interpolate(&other.0, progress))))
+        self.0.interpolate(&other.0, progress).map(generic_position::VerticalPosition)
     }
 }
 
 impl RepeatableListInterpolate for VerticalPosition {}
 
 /// https://drafts.csswg.org/css-transitions/#animtype-rect
 impl Interpolate for ClipRect {
     #[inline]
@@ -2602,17 +2603,19 @@ impl ComputeDistance for FontWeight {
     #[inline]
     fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
         let a = (*self as u32) as f64;
         let b = (*other as u32) as f64;
         a.compute_distance(&b)
     }
 }
 
-impl ComputeDistance for Position {
+impl<H, V> ComputeDistance for generic_position::Position<H, V>
+    where H: ComputeDistance, V: ComputeDistance
+{
     #[inline]
     fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
         self.compute_squared_distance(other).map(|sd| sd.sqrt())
     }
 
     #[inline]
     fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
         Ok(try!(self.horizontal.compute_squared_distance(&other.horizontal)) +
--- a/servo/components/style/properties/longhand/background.mako.rs
+++ b/servo/components/style/properties/longhand/background.mako.rs
@@ -84,118 +84,48 @@
                 computed_value::T(None) => SpecifiedValue(None),
                 computed_value::T(Some(ref image)) =>
                     SpecifiedValue(Some(ToComputedValue::from_computed_value(image))),
             }
         }
     }
 </%helpers:vector_longhand>
 
-<%helpers:vector_longhand name="background-position-x" animation_value_type="ComputedValue"
+<%helpers:predefined_type name="background-position-x" type="position::HorizontalPosition"
+                          initial_value="computed::position::HorizontalPosition::zero()"
+                          initial_specified_value="specified::position::HorizontalPosition::left()"
                           spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-x"
-                          delegate_animate="True">
-    use std::fmt;
-    use style_traits::ToCss;
-    use values::HasViewportPercentage;
-    use values::specified::position::HorizontalPosition;
-
-    #[allow(missing_docs)]
-    pub mod computed_value {
-        use values::computed::position::HorizontalPosition;
-        use properties::animated_properties::{Interpolate, RepeatableListInterpolate};
-
-        pub type T = HorizontalPosition;
-    }
-
-    #[allow(missing_docs)]
-    pub type SpecifiedValue = HorizontalPosition;
-
+                          animation_value_type="ComputedValue" vector="True" delegate_animate="True">
     #[inline]
-    #[allow(missing_docs)]
-    pub fn get_initial_value() -> computed_value::T {
-        use values::computed::position::HorizontalPosition;
-        HorizontalPosition(computed::LengthOrPercentage::Percentage(0.0))
-    }
-    #[inline]
-    #[allow(missing_docs)]
-    pub fn get_initial_specified_value() -> SpecifiedValue {
-        use values::specified::position::Keyword;
-        HorizontalPosition {
-            keyword: Some(Keyword::Left),
-            position: None,
-        }
-    }
-    #[inline]
-    #[allow(missing_docs)]
+    /// Get the initial value for horizontal position.
     pub fn get_initial_position_value() -> SpecifiedValue {
+        use values::generics::position::{HorizontalPosition, PositionValue};
         use values::specified::{LengthOrPercentage, Percentage};
-        HorizontalPosition {
+        HorizontalPosition(PositionValue {
             keyword: None,
             position: Some(LengthOrPercentage::Percentage(Percentage(0.0))),
-        }
-    }
-
-    #[allow(missing_docs)]
-    pub fn parse(context: &ParserContext, input: &mut Parser)
-                 -> Result<SpecifiedValue, ()> {
-        HorizontalPosition::parse(context, input)
-    }
-</%helpers:vector_longhand>
-
-<%helpers:vector_longhand name="background-position-y" animation_value_type="ComputedValue"
-                          spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-y"
-                          delegate_animate="True">
-    use std::fmt;
-    use style_traits::ToCss;
-    use values::HasViewportPercentage;
-    use values::specified::position::VerticalPosition;
-
-    #[allow(missing_docs)]
-    pub mod computed_value {
-        use values::computed::position::VerticalPosition;
-        use properties::animated_properties::{Interpolate, RepeatableListInterpolate};
-
-        pub type T = VerticalPosition;
+        })
     }
-
-    #[allow(missing_docs)]
-    pub type SpecifiedValue = VerticalPosition;
+</%helpers:predefined_type>
 
-    #[inline]
-    #[allow(missing_docs)]
-    pub fn get_initial_value() -> computed_value::T {
-        use values::computed::position::VerticalPosition;
-        VerticalPosition(computed::LengthOrPercentage::Percentage(0.0))
-    }
-    #[inline]
-    #[allow(missing_docs)]
-    pub fn get_initial_specified_value() -> SpecifiedValue {
-        use values::specified::position::Keyword;
-        VerticalPosition {
-            keyword: Some(Keyword::Top),
-            position: None,
-        }
-    }
-    #[inline]
-    #[allow(missing_docs)]
+<%helpers:predefined_type name="background-position-y" type="position::VerticalPosition"
+                          initial_value="computed::position::VerticalPosition::zero()"
+                          initial_specified_value="specified::position::VerticalPosition::top()"
+                          spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-y"
+                          animation_value_type="ComputedValue" vector="True" delegate_animate="True">
+    /// Get the initial value for vertical position.
     pub fn get_initial_position_value() -> SpecifiedValue {
+        use values::generics::position::{VerticalPosition, PositionValue};
         use values::specified::{LengthOrPercentage, Percentage};
-        VerticalPosition {
+        VerticalPosition(PositionValue {
             keyword: None,
             position: Some(LengthOrPercentage::Percentage(Percentage(0.0))),
-        }
+        })
     }
-
-    #[inline]
-    #[allow(missing_docs)]
-    pub fn parse(context: &ParserContext, input: &mut Parser)
-                 -> Result<SpecifiedValue, ()> {
-        VerticalPosition::parse(context, input)
-    }
-</%helpers:vector_longhand>
+</%helpers:predefined_type>
 
 <%helpers:vector_longhand name="background-repeat" animation_value_type="none"
                           spec="https://drafts.csswg.org/css-backgrounds/#the-background-repeat">
     use std::fmt;
     use style_traits::ToCss;
     use values::HasViewportPercentage;
 
     define_css_keyword_enum!(RepeatKeyword:
--- a/servo/components/style/properties/longhand/border.mako.rs
+++ b/servo/components/style/properties/longhand/border.mako.rs
@@ -16,57 +16,34 @@
             return "https://drafts.csswg.org/css-backgrounds/#border-%s-%s" % (side[0], kind)
 %>
 % for side in ALL_SIDES:
     ${helpers.predefined_type("border-%s-color" % side[0], "CSSColor",
                               "::cssparser::Color::CurrentColor",
                               alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-color"),
                               spec=maybe_logical_spec(side, "color"),
                               animation_value_type="IntermediateColor", logical = side[1])}
-% endfor
 
-% for side in ALL_SIDES:
     ${helpers.predefined_type("border-%s-style" % side[0], "BorderStyle",
                               "specified::BorderStyle::none",
                               need_clone=True,
                               alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-style"),
                               spec=maybe_logical_spec(side, "style"),
-                              animation_value_type="none", logical = side[1])}
+                              animation_value_type="none", logical=side[1])}
+
+    ${helpers.predefined_type("border-%s-width" % side[0], "BorderWidth", "Au::from_px(3)",
+                              computed_type="::app_units::Au",
+                              alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-width"),
+                              spec=maybe_logical_spec(side, "width"),
+                              animation_value_type="ComputedValue", logical=side[1])}
 % endfor
 
 ${helpers.gecko_keyword_conversion(Keyword('border-style',
                                    "none solid double dotted dashed hidden groove ridge inset outset"),
                                    type="::values::specified::BorderStyle")}
-% for side in ALL_SIDES:
-    <%helpers:longhand name="border-${side[0]}-width" animation_value_type="ComputedValue" logical="${side[1]}"
-                       alias="${maybe_moz_logical_alias(product, side, '-moz-border-%s-width')}"
-                       spec="${maybe_logical_spec(side, 'width')}">
-        use app_units::Au;
-        use std::fmt;
-        use style_traits::ToCss;
-        use values::HasViewportPercentage;
-        use values::specified::BorderWidth;
-
-        pub type SpecifiedValue = BorderWidth;
-
-        #[inline]
-        pub fn parse(context: &ParserContext, input: &mut Parser)
-                     -> Result<SpecifiedValue, ()> {
-            BorderWidth::parse(context, input)
-        }
-
-        pub mod computed_value {
-            use app_units::Au;
-            pub type T = Au;
-        }
-        #[inline] pub fn get_initial_value() -> computed_value::T {
-            Au::from_px(3)  // medium
-        }
-    </%helpers:longhand>
-% endfor
 
 // FIXME(#4126): when gfx supports painting it, make this Size2D<LengthOrPercentage>
 % for corner in ["top-left", "top-right", "bottom-right", "bottom-left"]:
     ${helpers.predefined_type("border-" + corner + "-radius", "BorderRadiusSize",
                               "computed::BorderRadiusSize::zero()",
                               "parse", extra_prefixes="webkit",
                               spec="https://drafts.csswg.org/css-backgrounds/#border-%s-radius" % corner,
                               boxed=True,
--- a/servo/components/style/properties/longhand/box.mako.rs
+++ b/servo/components/style/properties/longhand/box.mako.rs
@@ -1108,17 +1108,16 @@
                           vector=True,
                           products="gecko",
                           spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-destination)",
                           animation_value_type="ComputedValue",
                           allow_empty=True,
                           delegate_animate=True)}
 
 
-
 <%helpers:longhand name="transform" extra_prefixes="webkit"
                    animation_value_type="ComputedValue"
                    flags="CREATES_STACKING_CONTEXT FIXPOS_CB"
                    spec="https://drafts.csswg.org/css-transforms/#propdef-transform">
     use app_units::Au;
     use values::computed::{LengthOrPercentageOrNumber as ComputedLoPoNumber, LengthOrNumber as ComputedLoN};
     use values::computed::{LengthOrPercentage as ComputedLoP, Length as ComputedLength};
     use values::specified::{Angle, Length, LengthOrPercentage};
@@ -2091,93 +2090,23 @@
                           "Either::Second(None_)",
                           "parse_non_negative_length",
                           gecko_ffi_name="mChildPerspective",
                           spec="https://drafts.csswg.org/css-transforms/#perspective",
                           extra_prefixes="moz webkit",
                           flags="CREATES_STACKING_CONTEXT FIXPOS_CB",
                           animation_value_type="ComputedValue")}
 
-<%helpers:longhand name="perspective-origin" boxed="True"
-                   animation_value_type="ComputedValue"
-                   extra_prefixes="moz webkit"
-                   spec="https://drafts.csswg.org/css-transforms/#perspective-origin-property">
-    use std::fmt;
-    use style_traits::ToCss;
-    use values::HasViewportPercentage;
-    use values::specified::{LengthOrPercentage, Percentage};
-
-    pub mod computed_value {
-        use properties::animated_properties::Interpolate;
-        use values::computed::LengthOrPercentage;
-        use values::computed::Position;
-
-        pub type T = Position;
-    }
-
-    impl HasViewportPercentage for SpecifiedValue {
-        fn has_viewport_percentage(&self) -> bool {
-            self.horizontal.has_viewport_percentage() || self.vertical.has_viewport_percentage()
-        }
-    }
-
-    #[derive(Clone, Debug, PartialEq)]
-    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-    pub struct SpecifiedValue {
-        horizontal: LengthOrPercentage,
-        vertical: LengthOrPercentage,
-    }
-
-    impl ToCss for SpecifiedValue {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            try!(self.horizontal.to_css(dest));
-            try!(dest.write_str(" "));
-            self.vertical.to_css(dest)
-        }
-    }
-
-    #[inline]
-    pub fn get_initial_value() -> computed_value::T {
-        computed_value::T {
-            horizontal: computed::LengthOrPercentage::Percentage(0.5),
-            vertical: computed::LengthOrPercentage::Percentage(0.5),
-        }
-    }
-
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
-        let result = try!(super::parse_origin(context, input));
-        match result.depth {
-            Some(_) => Err(()),
-            None => Ok(SpecifiedValue {
-                horizontal: result.horizontal.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))),
-                vertical: result.vertical.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))),
-            })
-        }
-    }
-
-    impl ToComputedValue for SpecifiedValue {
-        type ComputedValue = computed_value::T;
-
-        #[inline]
-        fn to_computed_value(&self, context: &Context) -> computed_value::T {
-            computed_value::T {
-                horizontal: self.horizontal.to_computed_value(context),
-                vertical: self.vertical.to_computed_value(context),
-            }
-        }
-
-        #[inline]
-        fn from_computed_value(computed: &computed_value::T) -> Self {
-            SpecifiedValue {
-                horizontal: ToComputedValue::from_computed_value(&computed.horizontal),
-                vertical: ToComputedValue::from_computed_value(&computed.vertical),
-            }
-        }
-    }
-</%helpers:longhand>
+${helpers.predefined_type("perspective-origin",
+                          "position::OriginPosition",
+                          "computed::position::OriginPosition::center()",
+                          boxed="True",
+                          extra_prefixes="moz webkit",
+                          spec="https://drafts.csswg.org/css-transforms/#perspective-origin-property",
+                          animation_value_type="ComputedValue")}
 
 ${helpers.single_keyword("backface-visibility",
                          "visible hidden",
                          spec="https://drafts.csswg.org/css-transforms/#backface-visibility-property",
                          extra_prefixes="moz webkit",
                          animation_value_type="none")}
 
 ${helpers.single_keyword("transform-box",
@@ -2528,34 +2457,13 @@
                     _ => {},
                 }
                 Ok((Atom::from(ident)))
             }).map(SpecifiedValue::AnimateableFeatures)
         }
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="shape-outside" products="gecko" animation_value_type="none" boxed="True"
-                   spec="https://drafts.csswg.org/css-shapes/#shape-outside-property">
-    use std::fmt;
-    use style_traits::ToCss;
-    use values::specified::basic_shape::{ShapeBox, ShapeSource};
-    use values::HasViewportPercentage;
-
-    no_viewport_percentage!(SpecifiedValue);
-
-    pub mod computed_value {
-        use values::computed::basic_shape::{ShapeBox, ShapeSource};
-
-        pub type T = ShapeSource<ShapeBox>;
-    }
-
-    pub type SpecifiedValue = ShapeSource<ShapeBox>;
-
-    #[inline]
-    pub fn get_initial_value() -> computed_value::T {
-        Default::default()
-    }
-
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
-        ShapeSource::parse(context, input)
-    }
-</%helpers:longhand>
+${helpers.predefined_type("shape-outside", "basic_shape::ShapeWithShapeBox",
+                          "generics::basic_shape::ShapeSource::None",
+                          products="gecko", boxed="True",
+                          animation_value_type="none",
+                          spec="https://drafts.csswg.org/css-shapes/#shape-outside-property")}
--- a/servo/components/style/properties/longhand/column.mako.rs
+++ b/servo/components/style/properties/longhand/column.mako.rs
@@ -35,46 +35,21 @@
                           experimental=True,
                           animation_value_type="ComputedValue",
                           spec="https://drafts.csswg.org/css-multicol/#propdef-column-gap")}
 
 ${helpers.single_keyword("column-fill", "balance auto", extra_prefixes="moz",
                          products="gecko", animation_value_type="none",
                          spec="https://drafts.csswg.org/css-multicol/#propdef-column-fill")}
 
-// https://drafts.csswg.org/css-multicol-1/#propdef-column-rule-width
-<%helpers:longhand name="column-rule-width" products="gecko" animation_value_type="ComputedValue" extra_prefixes="moz"
-                   spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-width">
-    use app_units::Au;
-    use std::fmt;
-    use style_traits::ToCss;
-    use values::HasViewportPercentage;
-    use values::specified::BorderWidth;
-
-    pub mod computed_value {
-        use app_units::Au;
-        pub type T = Au;
-    }
-
-    pub type SpecifiedValue = BorderWidth;
-
-    #[inline]
-    pub fn get_initial_value() -> computed_value::T {
-        Au::from_px(3) // medium
-    }
-
-    #[inline]
-    pub fn get_initial_specified_value() -> SpecifiedValue {
-        BorderWidth::Medium
-    }
-
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
-        BorderWidth::parse(context, input)
-    }
-</%helpers:longhand>
+${helpers.predefined_type("column-rule-width", "BorderWidth", "Au::from_px(3)",
+                          initial_specified_value="specified::BorderWidth::Medium",
+                          products="gecko", computed_type="::app_units::Au",
+                          spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-width",
+                          animation_value_type="ComputedValue", extra_prefixes="moz")}
 
 // https://drafts.csswg.org/css-multicol-1/#crc
 ${helpers.predefined_type("column-rule-color", "CSSColor",
                           "::cssparser::Color::CurrentColor",
                           initial_specified_value="specified::CSSColor::currentcolor()",
                           products="gecko", animation_value_type="IntermediateColor", extra_prefixes="moz",
                           complex_color=True, need_clone=True,
                           spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-color")}
--- a/servo/components/style/properties/longhand/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_text.mako.rs
@@ -1231,43 +1231,21 @@
 ${helpers.predefined_type(
     "-webkit-text-stroke-color", "CSSColor",
     "CSSParserColor::CurrentColor",
     initial_specified_value="specified::CSSColor::currentcolor()",
     products="gecko", animation_value_type="IntermediateColor",
     complex_color=True, need_clone=True,
     spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-color")}
 
-<%helpers:longhand products="gecko" name="-webkit-text-stroke-width" animation_value_type="none"
-                   spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-width">
-    use app_units::Au;
-    use std::fmt;
-    use style_traits::ToCss;
-    use values::HasViewportPercentage;
-    use values::specified::{BorderWidth, Length};
-
-    pub type SpecifiedValue = BorderWidth;
-
-    #[inline]
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
-        BorderWidth::parse(context, input)
-    }
-
-    pub mod computed_value {
-        use app_units::Au;
-        pub type T = Au;
-    }
-    #[inline] pub fn get_initial_value() -> computed_value::T {
-        Au::from_px(0)
-    }
-    #[inline]
-    pub fn get_initial_specified_value() -> SpecifiedValue {
-        BorderWidth::from_length(Length::zero())
-    }
-</%helpers:longhand>
+${helpers.predefined_type("-webkit-text-stroke-width", "BorderWidth", "Au::from_px(0)",
+                          initial_specified_value="specified::BorderWidth::from_length(specified::Length::zero())",
+                          computed_type="::app_units::Au", products="gecko",
+                          spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-width",
+                          animation_value_type="none")}
 
 
 // CSS Ruby Layout Module Level 1
 // https://drafts.csswg.org/css-ruby/
 ${helpers.single_keyword("ruby-align", "space-around start center space-between",
                          products="gecko", animation_value_type="none",
                          spec="https://drafts.csswg.org/css-ruby/#ruby-align-property")}
 
--- a/servo/components/style/properties/longhand/svg.mako.rs
+++ b/servo/components/style/properties/longhand/svg.mako.rs
@@ -53,44 +53,21 @@
     spec="https://www.w3.org/TR/SVG/filters.html#LightingColorProperty")}
 
 // CSS Masking Module Level 1
 // https://drafts.fxtf.org/css-masking
 ${helpers.single_keyword("mask-type", "luminance alpha",
                          products="gecko", animation_value_type="none",
                          spec="https://drafts.fxtf.org/css-masking/#propdef-mask-type")}
 
-<%helpers:longhand name="clip-path" animation_value_type="none" products="gecko" boxed="True"
-                   flags="CREATES_STACKING_CONTEXT"
-                   spec="https://drafts.fxtf.org/css-masking/#propdef-clip-path">
-    use std::fmt;
-    use style_traits::ToCss;
-    use values::HasViewportPercentage;
-    use values::specified::basic_shape::{ShapeSource, GeometryBox};
-
-    pub mod computed_value {
-        use app_units::Au;
-        use values::computed::basic_shape::{ShapeSource, GeometryBox};
-
-        pub type T = ShapeSource<GeometryBox>;
-    }
-
-    pub type SpecifiedValue = ShapeSource<GeometryBox>;
-
-    #[inline]
-    pub fn get_initial_value() -> computed_value::T {
-        Default::default()
-    }
-
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
-        ShapeSource::parse(context, input)
-    }
-
-    no_viewport_percentage!(SpecifiedValue);
-</%helpers:longhand>
+${helpers.predefined_type("clip-path", "basic_shape::ShapeWithGeometryBox",
+                          "generics::basic_shape::ShapeSource::None",
+                          products="gecko", boxed="True",
+                          animation_value_type="none", flags="CREATES_STACKING_CONTEXT",
+                          spec="https://drafts.fxtf.org/css-masking/#propdef-clip-path")}
 
 ${helpers.single_keyword("mask-mode",
                          "match-source alpha luminance",
                          vector=True,
                          products="gecko",
                          animation_value_type="none",
                          spec="https://drafts.fxtf.org/css-masking/#propdef-mask-mode")}
 
--- a/servo/components/style/properties/shorthand/border.mako.rs
+++ b/servo/components/style/properties/shorthand/border.mako.rs
@@ -198,17 +198,18 @@ pub fn parse_border(context: &ParserCont
     }
 
 </%helpers:shorthand>
 
 <%helpers:shorthand name="border-radius" sub_properties="${' '.join(
     'border-%s-radius' % (corner)
      for corner in ['top-left', 'top-right', 'bottom-right', 'bottom-left']
 )}" extra_prefixes="webkit" spec="https://drafts.csswg.org/css-backgrounds/#border-radius">
-    use values::specified::basic_shape::{BorderRadius, serialize_radius_values};
+    use values::generics::serialize_radius_values;
+    use values::specified::basic_shape::BorderRadius;
     use parser::Parse;
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         let radii = try!(BorderRadius::parse(context, input));
         Ok(Longhands {
             border_top_left_radius: radii.top_left,
             border_top_right_radius: radii.top_right,
             border_bottom_right_radius: radii.bottom_right,
--- a/servo/components/style/properties/shorthand/outline.mako.rs
+++ b/servo/components/style/properties/shorthand/outline.mako.rs
@@ -63,17 +63,17 @@
 </%helpers:shorthand>
 
 // The -moz-outline-radius shorthand is non-standard and not on a standards track.
 <%helpers:shorthand name="-moz-outline-radius" sub_properties="${' '.join(
     '-moz-outline-radius-%s' % corner
     for corner in ['topleft', 'topright', 'bottomright', 'bottomleft']
 )}" products="gecko" spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-outline-radius)">
     use properties::shorthands;
-    use values::specified::basic_shape::serialize_radius_values;
+    use values::generics::serialize_radius_values;
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         // Re-use border-radius parsing.
         shorthands::border_radius::parse_value(context, input).map(|longhands| {
             Longhands {
                 % for corner in ["top_left", "top_right", "bottom_right", "bottom_left"]:
                 _moz_outline_radius_${corner.replace("_", "")}: longhands.border_${corner}_radius,
                 % endfor
--- a/servo/components/style/values/computed/basic_shape.rs
+++ b/servo/components/style/values/computed/basic_shape.rs
@@ -4,55 +4,29 @@
 
 //! CSS handling for the computed value of
 //! [`basic-shape`][basic-shape]s
 //!
 //! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape
 
 use std::fmt;
 use style_traits::ToCss;
-use values::computed::{BorderRadiusSize, LengthOrPercentage};
+use values::computed::LengthOrPercentage;
 use values::computed::position::Position;
-use values::specified::url::SpecifiedUrl;
-
-pub use values::specified::basic_shape::{self, FillRule, GeometryBox, ShapeBox};
-
-#[derive(Clone, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub enum ShapeSource<T> {
-    Url(SpecifiedUrl),
-    Shape(BasicShape, Option<T>),
-    Box(T),
-    None,
-}
+use values::generics::basic_shape::{BorderRadius as GenericBorderRadius, ShapeRadius as GenericShapeRadius};
+use values::generics::basic_shape::{InsetRect as GenericInsetRect, Polygon as GenericPolygon, ShapeSource};
 
-impl<T> Default for ShapeSource<T> {
-    fn default() -> Self {
-        ShapeSource::None
-    }
-}
+pub use values::generics::basic_shape::FillRule;
+pub use values::specified::basic_shape::{self, GeometryBox, ShapeBox};
 
-impl<T: ToCss> ToCss for ShapeSource<T> {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            ShapeSource::Url(ref url) => url.to_css(dest),
-            ShapeSource::Shape(ref shape, Some(ref reference)) => {
-                try!(shape.to_css(dest));
-                try!(dest.write_str(" "));
-                reference.to_css(dest)
-            }
-            ShapeSource::Shape(ref shape, None) => shape.to_css(dest),
-            ShapeSource::Box(ref reference) => reference.to_css(dest),
-            ShapeSource::None => dest.write_str("none"),
+/// The computed value used by `clip-path`
+pub type ShapeWithGeometryBox = ShapeSource<BasicShape, GeometryBox>;
 
-        }
-    }
-}
-
+/// The computed value used by `shape-outside`
+pub type ShapeWithShapeBox = ShapeSource<BasicShape, ShapeBox>;
 
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 pub enum BasicShape {
     Inset(InsetRect),
     Circle(Circle),
     Ellipse(Ellipse),
@@ -65,45 +39,18 @@ impl ToCss for BasicShape {
             BasicShape::Inset(ref rect) => rect.to_css(dest),
             BasicShape::Circle(ref circle) => circle.to_css(dest),
             BasicShape::Ellipse(ref e) => e.to_css(dest),
             BasicShape::Polygon(ref poly) => poly.to_css(dest),
         }
     }
 }
 
-#[derive(Clone, PartialEq, Copy, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub struct InsetRect {
-    pub top: LengthOrPercentage,
-    pub right: LengthOrPercentage,
-    pub bottom: LengthOrPercentage,
-    pub left: LengthOrPercentage,
-    pub round: Option<BorderRadius>,
-}
-
-impl ToCss for InsetRect {
-    // XXXManishearth again, we should try to reduce the number of values printed here
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        try!(dest.write_str("inset("));
-        try!(self.top.to_css(dest));
-        try!(dest.write_str(" "));
-        try!(self.right.to_css(dest));
-        try!(dest.write_str(" "));
-        try!(self.bottom.to_css(dest));
-        try!(dest.write_str(" "));
-        try!(self.left.to_css(dest));
-        if let Some(ref radius) = self.round {
-            try!(dest.write_str(" round "));
-            try!(radius.to_css(dest));
-        }
-        dest.write_str(")")
-    }
-}
+/// The computed value of `inset()`
+pub type InsetRect = GenericInsetRect<LengthOrPercentage>;
 
 #[derive(Clone, PartialEq, Copy, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 pub struct Circle {
     pub radius: ShapeRadius,
     pub position: Position,
 }
@@ -135,82 +82,18 @@ impl ToCss for Ellipse {
             try!(dest.write_str(" "));
         }
         try!(dest.write_str("at "));
         try!(self.position.to_css(dest));
         dest.write_str(")")
     }
 }
 
-#[derive(Clone, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-/// https://drafts.csswg.org/css-shapes/#funcdef-polygon
-pub struct Polygon {
-    pub fill: FillRule,
-    pub coordinates: Vec<(LengthOrPercentage, LengthOrPercentage)>,
-}
-
-impl ToCss for Polygon {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        try!(dest.write_str("polygon("));
-        let mut need_space = false;
-        if self.fill != Default::default() {
-            try!(self.fill.to_css(dest));
-            try!(dest.write_str(", "));
-        }
-        for coord in &self.coordinates {
-            if need_space {
-                try!(dest.write_str(", "));
-            }
-            try!(coord.0.to_css(dest));
-            try!(dest.write_str(" "));
-            try!(coord.1.to_css(dest));
-            need_space = true;
-        }
-        dest.write_str(")")
-    }
-}
+/// The computed value of `Polygon`
+pub type Polygon = GenericPolygon<LengthOrPercentage>;
 
-/// https://drafts.csswg.org/css-shapes/#typedef-shape-radius
-#[derive(Clone, PartialEq, Copy, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub enum ShapeRadius {
-    Length(LengthOrPercentage),
-    ClosestSide,
-    FarthestSide,
-}
-
-impl Default for ShapeRadius {
-    fn default() -> Self {
-        ShapeRadius::ClosestSide
-    }
-}
+/// The computed value of `BorderRadius`
+pub type BorderRadius = GenericBorderRadius<LengthOrPercentage>;
 
-impl ToCss for ShapeRadius {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            ShapeRadius::Length(lop) => lop.to_css(dest),
-            ShapeRadius::ClosestSide => dest.write_str("closest-side"),
-            ShapeRadius::FarthestSide => dest.write_str("farthest-side"),
-        }
-    }
-}
+/// The computed value of `ShapeRadius`
+pub type ShapeRadius = GenericShapeRadius<LengthOrPercentage>;
 
-/// https://drafts.csswg.org/css-backgrounds-3/#border-radius
-#[derive(Clone, PartialEq, Copy, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub struct BorderRadius {
-    pub top_left: BorderRadiusSize,
-    pub top_right: BorderRadiusSize,
-    pub bottom_right: BorderRadiusSize,
-    pub bottom_left: BorderRadiusSize,
-}
-
-impl ToCss for BorderRadius {
-    #[inline]
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        basic_shape::serialize_radius_values(dest, &self.top_left.0, &self.top_right.0,
-                                             &self.bottom_right.0, &self.bottom_left.0)
-    }
-}
+impl Copy for ShapeRadius {}
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -1,24 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Computed values.
 
-use app_units::Au;
 use euclid::size::Size2D;
 use font_metrics::FontMetricsProvider;
 use media_queries::Device;
 use properties::ComputedValues;
 use std::fmt;
 use style_traits::ToCss;
-use super::{CSSFloat, CSSInteger, RGBA, specified};
+use super::{CSSFloat, CSSInteger, RGBA};
+use super::generics::BorderRadiusSize as GenericBorderRadiusSize;
+use super::specified;
 use super::specified::grid::{TrackBreadth as GenericTrackBreadth, TrackSize as GenericTrackSize};
 
+pub use app_units::Au;
 pub use cssparser::Color as CSSColor;
 pub use self::image::{AngleOrCorner, EndingShape as GradientShape, Gradient, GradientKind, Image, ImageRect};
 pub use self::image::{LengthOrKeyword, LengthOrPercentageOrKeyword};
 pub use 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;
@@ -292,53 +294,29 @@ impl ToComputedValue for specified::Just
 #[cfg(feature = "gecko")]
 impl ComputedValueAsSpecified for specified::AlignItems {}
 #[cfg(feature = "gecko")]
 impl ComputedValueAsSpecified for specified::AlignJustifyContent {}
 #[cfg(feature = "gecko")]
 impl ComputedValueAsSpecified for specified::AlignJustifySelf {}
 impl ComputedValueAsSpecified for specified::BorderStyle {}
 
-#[derive(Debug, PartialEq, Clone, Copy)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub struct BorderRadiusSize(pub Size2D<LengthOrPercentage>);
+/// The computed value of `BorderRadiusSize`
+pub type BorderRadiusSize = GenericBorderRadiusSize<LengthOrPercentage>;
 
 impl BorderRadiusSize {
-    #[allow(missing_docs)]
+    /// Create a null value.
+    #[inline]
     pub fn zero() -> BorderRadiusSize {
-        BorderRadiusSize(Size2D::new(LengthOrPercentage::Length(Au(0)), LengthOrPercentage::Length(Au(0))))
+        let zero = LengthOrPercentage::zero();
+        GenericBorderRadiusSize(Size2D::new(zero.clone(), zero))
     }
 }
 
-impl ToComputedValue for specified::BorderRadiusSize {
-    type ComputedValue = BorderRadiusSize;
-
-    #[inline]
-    fn to_computed_value(&self, context: &Context) -> BorderRadiusSize {
-        let w = self.0.width.to_computed_value(context);
-        let h = self.0.height.to_computed_value(context);
-        BorderRadiusSize(Size2D::new(w, h))
-    }
-
-    #[inline]
-    fn from_computed_value(computed: &BorderRadiusSize) -> Self {
-        let w = ToComputedValue::from_computed_value(&computed.0.width);
-        let h = ToComputedValue::from_computed_value(&computed.0.height);
-        specified::BorderRadiusSize(Size2D::new(w, h))
-    }
-}
-
-impl ToCss for BorderRadiusSize {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        try!(self.0.width.to_css(dest));
-        try!(dest.write_str("/"));
-        self.0.height.to_css(dest)
-    }
-}
+impl Copy for BorderRadiusSize {}
 
 #[derive(Debug, PartialEq, Clone, Copy)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 pub struct Shadow {
     pub offset_x: Au,
     pub offset_y: Au,
     pub blur_radius: Au,
--- a/servo/components/style/values/computed/position.rs
+++ b/servo/components/style/values/computed/position.rs
@@ -1,61 +1,81 @@
 /* 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/. */
 
 //! CSS handling for the computed value of
-//! [`position`][position]s
+//! [`position`][position] values.
 //!
 //! [position]: https://drafts.csswg.org/css-backgrounds-3/#position
 
 use std::fmt;
 use style_traits::ToCss;
 use values::computed::LengthOrPercentage;
+use values::generics::position::{Position as GenericPosition, PositionWithKeyword};
+use values::generics::position::HorizontalPosition as GenericHorizontalPosition;
+use values::generics::position::VerticalPosition as GenericVerticalPosition;
 
-#[derive(Debug, Clone, PartialEq, Copy)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub struct Position {
-    pub horizontal: LengthOrPercentage,
-    pub vertical: LengthOrPercentage,
+/// The computed value of a CSS `<position>`
+pub type Position = PositionWithKeyword<LengthOrPercentage>;
+
+impl Copy for Position {}
+
+/// The computed value for `<position>` values without a keyword.
+pub type OriginPosition = GenericPosition<LengthOrPercentage, LengthOrPercentage>;
+
+impl Copy for OriginPosition {}
+
+impl OriginPosition {
+    #[inline]
+    /// The initial value for `perspective-origin`
+    pub fn center() -> OriginPosition {
+        GenericPosition {
+            horizontal: LengthOrPercentage::Percentage(0.5),
+            vertical: LengthOrPercentage::Percentage(0.5),
+        }
+    }
 }
 
 impl Position {
+    #[inline]
     /// Construct a position at (0, 0)
     pub fn zero() -> Self {
         Position {
-            horizontal: LengthOrPercentage::zero(),
-            vertical: LengthOrPercentage::zero(),
+            horizontal: GenericHorizontalPosition(LengthOrPercentage::zero()),
+            vertical: GenericVerticalPosition(LengthOrPercentage::zero()),
         }
     }
 }
 
 impl ToCss for Position {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        try!(self.horizontal.to_css(dest));
-        try!(dest.write_str(" "));
-        try!(self.vertical.to_css(dest));
-        Ok(())
+        self.horizontal.to_css(dest)?;
+        dest.write_str(" ")?;
+        self.vertical.to_css(dest)
     }
 }
 
-#[derive(Debug, Clone, PartialEq, Copy)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub struct HorizontalPosition(pub LengthOrPercentage);
+/// The computed value of a horizontal `<position>`
+pub type HorizontalPosition = GenericHorizontalPosition<LengthOrPercentage>;
+
+impl Copy for HorizontalPosition {}
 
-impl ToCss for HorizontalPosition {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        self.0.to_css(dest)
+impl HorizontalPosition {
+    #[inline]
+    /// Create a zero position value.
+    pub fn zero() -> HorizontalPosition {
+        GenericHorizontalPosition(LengthOrPercentage::Percentage(0.0))
     }
 }
 
-#[derive(Debug, Clone, PartialEq, Copy)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub struct VerticalPosition(pub LengthOrPercentage);
+/// The computed value of a vertical `<position>`
+pub type VerticalPosition = GenericVerticalPosition<LengthOrPercentage>;
+
+impl Copy for VerticalPosition {}
 
-impl ToCss for VerticalPosition {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        self.0.to_css(dest)
+impl VerticalPosition {
+    #[inline]
+    /// Create a zero position value.
+    pub fn zero() -> VerticalPosition {
+        GenericVerticalPosition(LengthOrPercentage::Percentage(0.0))
     }
 }
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/generics/basic_shape.rs
@@ -0,0 +1,397 @@
+/* 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/. */
+
+//! CSS handling for the [`basic-shape`](https://drafts.csswg.org/css-shapes/#typedef-basic-shape)
+//! types that are generic over their `ToCss` implementations.
+
+use cssparser::Parser;
+use euclid::size::Size2D;
+use parser::{Parse, ParserContext};
+use properties::shorthands::serialize_four_sides;
+use std::ascii::AsciiExt;
+use std::fmt;
+use style_traits::ToCss;
+use values::HasViewportPercentage;
+use values::computed::{ComputedValueAsSpecified, Context, ToComputedValue};
+use values::generics::BorderRadiusSize;
+use values::specified::url::SpecifiedUrl;
+
+/// A generic type used for `border-radius`, `outline-radius` and `inset()` values.
+///
+/// https://drafts.csswg.org/css-backgrounds-3/#border-radius
+#[derive(Clone, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub struct BorderRadius<L> {
+    /// The top left radius.
+    pub top_left: BorderRadiusSize<L>,
+    /// The top right radius.
+    pub top_right: BorderRadiusSize<L>,
+    /// The bottom right radius.
+    pub bottom_right: BorderRadiusSize<L>,
+    /// The bottom left radius.
+    pub bottom_left: BorderRadiusSize<L>,
+}
+
+/// Serialization helper for types of longhands like `border-radius` and `outline-radius`
+pub fn serialize_radius_values<L, W>(dest: &mut W, top_left: &Size2D<L>,
+                                     top_right: &Size2D<L>, bottom_right: &Size2D<L>,
+                                     bottom_left: &Size2D<L>) -> fmt::Result
+    where L: ToCss + PartialEq, W: fmt::Write
+{
+    if top_left.width == top_left.height && top_right.width == top_right.height &&
+       bottom_right.width == bottom_right.height && bottom_left.width == bottom_left.height {
+        serialize_four_sides(dest, &top_left.width, &top_right.width,
+                             &bottom_right.width, &bottom_left.width)
+    } else {
+        serialize_four_sides(dest, &top_left.width, &top_right.width,
+                             &bottom_right.width, &bottom_left.width)?;
+        dest.write_str(" / ")?;
+        serialize_four_sides(dest, &top_left.height, &top_right.height,
+                             &bottom_right.height, &bottom_left.height)
+    }
+}
+
+impl<L: ToCss + PartialEq> ToCss for BorderRadius<L> {
+    #[inline]
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        serialize_radius_values(dest, &self.top_left.0, &self.top_right.0,
+                                &self.bottom_right.0, &self.bottom_left.0)
+    }
+}
+
+impl<L: ToComputedValue> ToComputedValue for BorderRadius<L> {
+    type ComputedValue = BorderRadius<L::ComputedValue>;
+
+    #[inline]
+    fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue {
+        BorderRadius {
+            top_left: self.top_left.to_computed_value(cx),
+            top_right: self.top_right.to_computed_value(cx),
+            bottom_right: self.bottom_right.to_computed_value(cx),
+            bottom_left: self.bottom_left.to_computed_value(cx),
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        BorderRadius {
+            top_left: ToComputedValue::from_computed_value(&computed.top_left),
+            top_right: ToComputedValue::from_computed_value(&computed.top_right),
+            bottom_right: ToComputedValue::from_computed_value(&computed.bottom_right),
+            bottom_left: ToComputedValue::from_computed_value(&computed.bottom_left),
+        }
+    }
+}
+
+/// https://drafts.csswg.org/css-shapes/#typedef-shape-radius
+#[derive(Clone, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[allow(missing_docs)]
+pub enum ShapeRadius<L> {
+    Length(L),
+    ClosestSide,
+    FarthestSide,
+}
+
+impl<L> Default for ShapeRadius<L> {
+    #[inline]
+    fn default() -> Self { ShapeRadius::ClosestSide }
+}
+
+impl<L: ToCss> ToCss for ShapeRadius<L> {
+    #[inline]
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            ShapeRadius::Length(ref lop) => lop.to_css(dest),
+            ShapeRadius::ClosestSide => dest.write_str("closest-side"),
+            ShapeRadius::FarthestSide => dest.write_str("farthest-side"),
+        }
+    }
+}
+
+impl<L: ToComputedValue> ToComputedValue for ShapeRadius<L> {
+    type ComputedValue = ShapeRadius<L::ComputedValue>;
+
+    #[inline]
+    fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue {
+        match *self {
+            ShapeRadius::Length(ref lop) => ShapeRadius::Length(lop.to_computed_value(cx)),
+            ShapeRadius::ClosestSide => ShapeRadius::ClosestSide,
+            ShapeRadius::FarthestSide => ShapeRadius::FarthestSide,
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        match *computed {
+            ShapeRadius::Length(ref lop) => ShapeRadius::Length(ToComputedValue::from_computed_value(lop)),
+            ShapeRadius::ClosestSide => ShapeRadius::ClosestSide,
+            ShapeRadius::FarthestSide => ShapeRadius::FarthestSide,
+        }
+    }
+}
+
+// https://drafts.csswg.org/css-shapes/#typedef-fill-rule
+// NOTE: Basic shapes spec says that these are the only two values, however
+// https://www.w3.org/TR/SVG/painting.html#FillRuleProperty
+// says that it can also be `inherit`
+define_css_keyword_enum!(FillRule:
+    "nonzero" => NonZero,
+    "evenodd" => EvenOdd
+);
+
+impl ComputedValueAsSpecified for FillRule {}
+
+impl Default for FillRule {
+    #[inline]
+    fn default() -> Self { FillRule::NonZero }
+}
+
+#[derive(Clone, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+/// A generic type for representing the `polygon()` function
+///
+/// https://drafts.csswg.org/css-shapes/#funcdef-polygon
+pub struct Polygon<L> {
+    /// The filling rule for a polygon.
+    pub fill: FillRule,
+    /// A collection of (x, y) coordinates to draw the polygon.
+    pub coordinates: Vec<(L, L)>,
+}
+
+impl<L: Parse> Polygon<L> {
+    /// Parse the inner arguments of a `polygon` function.
+    pub fn parse_function_arguments(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        let fill = input.try(|i| -> Result<_, ()> {
+            let fill = FillRule::parse(i)?;
+            i.expect_comma()?;      // only eat the comma if there is something before it
+            Ok(fill)
+        }).ok().unwrap_or_default();
+
+        let buf = input.parse_comma_separated(|i| {
+            Ok((L::parse(context, i)?, L::parse(context, i)?))
+        })?;
+
+        Ok(Polygon {
+            fill: fill,
+            coordinates: buf,
+        })
+    }
+}
+
+impl<L: Parse> Parse for Polygon<L> {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        match input.expect_function() {
+            Ok(ref s) if s.eq_ignore_ascii_case("polygon") =>
+                input.parse_nested_block(|i| Polygon::parse_function_arguments(context, i)),
+            _ => Err(())
+        }
+    }
+}
+
+impl<L: ToCss> ToCss for Polygon<L> {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        dest.write_str("polygon(")?;
+        if self.fill != FillRule::default() {
+            self.fill.to_css(dest)?;
+            dest.write_str(", ")?;
+        }
+
+        for (i, coord) in self.coordinates.iter().enumerate() {
+            if i > 0 {
+                dest.write_str(", ")?;
+            }
+
+            coord.0.to_css(dest)?;
+            dest.write_str(" ")?;
+            coord.1.to_css(dest)?;
+        }
+
+        dest.write_str(")")
+    }
+}
+
+impl<L: ToComputedValue> ToComputedValue for Polygon<L> {
+    type ComputedValue = Polygon<L::ComputedValue>;
+
+    #[inline]
+    fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue {
+        Polygon {
+            fill: self.fill.to_computed_value(cx),
+            coordinates: self.coordinates.iter().map(|c| {
+                (c.0.to_computed_value(cx), c.1.to_computed_value(cx))
+            }).collect(),
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        Polygon {
+            fill: ToComputedValue::from_computed_value(&computed.fill),
+            coordinates: computed.coordinates.iter().map(|c| {
+                (ToComputedValue::from_computed_value(&c.0),
+                 ToComputedValue::from_computed_value(&c.1))
+            }).collect(),
+        }
+    }
+}
+
+#[derive(Clone, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+/// https://drafts.csswg.org/css-shapes/#funcdef-inset
+#[allow(missing_docs)]
+pub struct InsetRect<L> {
+    pub top: L,
+    pub right: L,
+    pub bottom: L,
+    pub left: L,
+    pub round: Option<BorderRadius<L>>,
+}
+
+impl<L: ToCss + PartialEq> ToCss for InsetRect<L> {
+    // XXXManishearth We should try to reduce the number of values printed here
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        dest.write_str("inset(")?;
+        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)?;
+        if let Some(ref radius) = self.round {
+            dest.write_str(" round ")?;
+            radius.to_css(dest)?;
+        }
+
+        dest.write_str(")")
+    }
+}
+
+impl<L: ToComputedValue> ToComputedValue for InsetRect<L> {
+    type ComputedValue = InsetRect<L::ComputedValue>;
+
+    #[inline]
+    fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue {
+        InsetRect {
+            top: self.top.to_computed_value(cx),
+            right: self.right.to_computed_value(cx),
+            bottom: self.bottom.to_computed_value(cx),
+            left: self.left.to_computed_value(cx),
+            round: self.round.as_ref().map(|r| r.to_computed_value(cx)),
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        InsetRect {
+            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),
+            round: computed.round.as_ref().map(|r| ToComputedValue::from_computed_value(r)),
+        }
+    }
+}
+
+/// A shape source, for some reference box
+///
+/// `clip-path` uses ShapeSource<BasicShape, GeometryBox>,
+/// `shape-outside` uses ShapeSource<BasicShape, ShapeBox>
+#[derive(Clone, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[allow(missing_docs)]
+pub enum ShapeSource<B, T> {
+    Url(SpecifiedUrl),
+    Shape(B, Option<T>),
+    Box(T),
+    None,
+}
+
+impl<B, T> HasViewportPercentage for ShapeSource<B, T> {
+    #[inline]
+    fn has_viewport_percentage(&self) -> bool { false }
+}
+
+impl<B: ToCss, T: ToCss> ToCss for ShapeSource<B, T> {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            ShapeSource::Url(ref url) => url.to_css(dest),
+            ShapeSource::Shape(ref shape, Some(ref ref_box)) => {
+                shape.to_css(dest)?;
+                dest.write_str(" ")?;
+                ref_box.to_css(dest)
+            },
+            ShapeSource::Shape(ref shape, None) => shape.to_css(dest),
+            ShapeSource::Box(ref val) => val.to_css(dest),
+            ShapeSource::None => dest.write_str("none"),
+        }
+    }
+}
+
+impl<B: Parse, T: Parse> Parse for ShapeSource<B, T> {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        if input.try(|i| i.expect_ident_matching("none")).is_ok() {
+            return Ok(ShapeSource::None)
+        }
+
+        if let Ok(url) = input.try(|i| SpecifiedUrl::parse(context, i)) {
+            return Ok(ShapeSource::Url(url))
+        }
+
+        fn parse_component<U: Parse>(context: &ParserContext, input: &mut Parser,
+                                     component: &mut Option<U>) -> bool {
+            if component.is_some() {
+                return false            // already parsed this component
+            }
+
+            *component = input.try(|i| U::parse(context, i)).ok();
+            component.is_some()
+        }
+
+        let mut shape = None;
+        let mut ref_box = None;
+
+        while parse_component(context, input, &mut shape) ||
+              parse_component(context, input, &mut ref_box) {
+            //
+        }
+
+        if let Some(shp) = shape {
+            return Ok(ShapeSource::Shape(shp, ref_box))
+        }
+
+        ref_box.map(|v| ShapeSource::Box(v)).ok_or(())
+    }
+}
+
+impl<B: ToComputedValue, T: ToComputedValue> ToComputedValue for ShapeSource<B, T> {
+    type ComputedValue = ShapeSource<B::ComputedValue, T::ComputedValue>;
+
+    #[inline]
+    fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue {
+        match *self {
+            ShapeSource::Url(ref url) => ShapeSource::Url(url.to_computed_value(cx)),
+            ShapeSource::Shape(ref shape, ref ref_box) => {
+                ShapeSource::Shape(shape.to_computed_value(cx),
+                                   ref_box.as_ref().map(|ref val| val.to_computed_value(cx)))
+            },
+            ShapeSource::Box(ref ref_box) => ShapeSource::Box(ref_box.to_computed_value(cx)),
+            ShapeSource::None => ShapeSource::None,
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        match *computed {
+            ShapeSource::Url(ref url) => ShapeSource::Url(SpecifiedUrl::from_computed_value(url)),
+            ShapeSource::Shape(ref shape, ref ref_box) => {
+                ShapeSource::Shape(ToComputedValue::from_computed_value(shape),
+                                    ref_box.as_ref().map(|val| ToComputedValue::from_computed_value(val)))
+            },
+            ShapeSource::Box(ref ref_box) => ShapeSource::Box(ToComputedValue::from_computed_value(ref_box)),
+            ShapeSource::None => ShapeSource::None,
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/generics/mod.rs
@@ -0,0 +1,70 @@
+/* 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 that share their serialization implementations
+//! for both specified and computed values.
+
+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 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> {
+    #[inline]
+    fn has_viewport_percentage(&self) -> bool { false }
+}
+
+impl<L> BorderRadiusSize<L> {
+    #[inline]
+    /// Create a new `BorderRadiusSize` for an area of given width and height.
+    pub fn new(width: L, height: L) -> BorderRadiusSize<L> {
+        BorderRadiusSize(Size2D::new(width, height))
+    }
+}
+
+impl<L: Clone> BorderRadiusSize<L> {
+    #[inline]
+    /// Create a new `BorderRadiusSize` for a circle of given radius.
+    pub fn circle(radius: L) -> BorderRadiusSize<L> {
+        BorderRadiusSize(Size2D::new(radius.clone(), radius))
+    }
+}
+
+impl<L: ToCss> ToCss for BorderRadiusSize<L> {
+    #[inline]
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        self.0.width.to_css(dest)?;
+        dest.write_str(" ")?;
+        self.0.height.to_css(dest)
+    }
+}
+
+impl<L: ToComputedValue> ToComputedValue for BorderRadiusSize<L> {
+    type ComputedValue = BorderRadiusSize<L::ComputedValue>;
+
+    #[inline]
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        let w = self.0.width.to_computed_value(context);
+        let h = self.0.height.to_computed_value(context);
+        BorderRadiusSize(Size2D::new(w, h))
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        let w = ToComputedValue::from_computed_value(&computed.0.width);
+        let h = ToComputedValue::from_computed_value(&computed.0.height);
+        BorderRadiusSize(Size2D::new(w, h))
+    }
+}
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/generics/position.rs
@@ -0,0 +1,302 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Generic types for CSS handling of specified and computed values of
+//! [`position`](https://drafts.csswg.org/css-backgrounds-3/#position)
+
+use cssparser::Parser;
+use parser::{Parse, ParserContext};
+use std::fmt;
+use style_traits::ToCss;
+use values::HasViewportPercentage;
+use values::computed::{ComputedValueAsSpecified, Context, ToComputedValue};
+use values::specified::{LengthOrPercentage, Percentage};
+
+define_css_keyword_enum!{ Keyword:
+    "center" => Center,
+    "left" => Left,
+    "right" => Right,
+    "top" => Top,
+    "bottom" => Bottom,
+    "x-start" => XStart,
+    "x-end" => XEnd,
+    "y-start" => YStart,
+    "y-end" => YEnd
+}
+
+add_impls_for_keyword_enum!(Keyword);
+
+impl Keyword {
+    #[inline]
+    /// The defaults for position keywords are `left` and `top` (`x-start` and `y-start` for logical).
+    /// This method checks whether this keyword indicates their opposite sides. See the
+    /// `ToComputedValue` impl on `HorizontalPosition` and `VerticalPosition` for its use case.
+    pub fn is_other_side(&self) -> bool {
+        if self.is_horizontal() || self.is_logical_x() {
+            matches!(*self, Keyword::Right | Keyword::XEnd)
+        } else {
+            matches!(*self, Keyword::Bottom | Keyword::YEnd)
+        }
+    }
+
+    #[inline]
+    /// Check whether this is a keyword for horizontal position.
+    pub fn is_horizontal(&self) -> bool {
+        matches!(*self, Keyword::Left | Keyword::Right)
+    }
+
+    #[inline]
+    /// Check whether this is a keyword for vertical position.
+    pub fn is_vertical(&self) -> bool {
+        matches!(*self, Keyword::Top | Keyword::Bottom)
+    }
+
+    #[inline]
+    /// Check whether this is a horizontal logical keyword.
+    pub fn is_logical_x(&self) -> bool {
+        matches!(*self, Keyword::XStart | Keyword::XEnd)
+    }
+
+    #[inline]
+    /// Check whether this is a vertical logical keyword.
+    pub fn is_logical_y(&self) -> bool {
+        matches!(*self, Keyword::YStart | Keyword::YEnd)
+    }
+
+    #[inline]
+    /// Check whether this is a logical keyword.
+    pub fn is_logical(&self) -> bool {
+        self.is_logical_x() || self.is_logical_y()
+    }
+}
+
+impl From<Keyword> for LengthOrPercentage {
+    fn from(val: Keyword) -> LengthOrPercentage {
+        match val {
+            Keyword::Center => LengthOrPercentage::Percentage(Percentage(0.5)),
+            Keyword::Left | Keyword::Top => LengthOrPercentage::Percentage(Percentage(0.0)),
+            Keyword::Right | Keyword::Bottom => LengthOrPercentage::Percentage(Percentage(1.0)),
+            // FIXME(canaltinova): Support logical keywords
+            Keyword::XStart | Keyword::YStart => LengthOrPercentage::Percentage(Percentage(0.0)),
+            Keyword::XEnd | Keyword::YEnd => LengthOrPercentage::Percentage(Percentage(1.0)),
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+/// A generic type for representing horizontal or vertical `<position>` value.
+pub struct PositionValue<L> {
+    /// Even though this is generic, it's always a `<length-percentage>` value.
+    pub position: Option<L>,
+    /// A position keyword.
+    pub keyword: Option<Keyword>,
+}
+
+impl<L: HasViewportPercentage> HasViewportPercentage for PositionValue<L> {
+    #[inline]
+    fn has_viewport_percentage(&self) -> bool {
+        self.position.as_ref().map_or(false, |pos| pos.has_viewport_percentage())
+    }
+}
+
+impl<L: Parse> PositionValue<L> {
+    /// Internal parsing function which (after parsing) checks the keyword with the
+    /// given function.
+    pub fn parse_internal<F>(context: &ParserContext, input: &mut Parser,
+                             mut is_allowed_keyword: F) -> Result<PositionValue<L>, ()>
+        where F: FnMut(Keyword) -> bool
+    {
+        let (mut pos, mut keyword) = (None, None);
+        for _ in 0..2 {
+            if let Ok(l) = input.try(|i| L::parse(context, i)) {
+                if pos.is_some() {
+                    return Err(())
+                }
+
+                pos = Some(l);
+            }
+
+            if let Ok(k) = input.try(Keyword::parse) {
+                if keyword.is_some() || !is_allowed_keyword(k) {
+                    return Err(())
+                }
+
+                keyword = Some(k);
+            }
+        }
+
+        if pos.is_some() {
+            if let Some(Keyword::Center) = keyword {
+                return Err(())      // "center" and <length> is not allowed
+            }
+        } else if keyword.is_none() {
+            return Err(())      // at least one value is necessary
+        }
+
+        Ok(PositionValue {
+            position: pos,
+            keyword: keyword,
+        })
+    }
+}
+
+impl<L: ToCss> ToCss for PositionValue<L> {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        if let Some(keyword) = self.keyword {
+            keyword.to_css(dest)?;
+        }
+
+        if let Some(ref position) = self.position {
+            if self.keyword.is_some() {
+                dest.write_str(" ")?;
+            }
+
+            position.to_css(dest)?;
+        }
+
+        Ok(())
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+/// A generic type for representing horizontal `<position>`
+pub struct HorizontalPosition<L>(pub L);
+
+impl<L: ToCss> ToCss for HorizontalPosition<L> {
+    #[inline]
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        self.0.to_css(dest)
+    }
+}
+
+impl<L: HasViewportPercentage> HasViewportPercentage for HorizontalPosition<L> {
+    #[inline]
+    fn has_viewport_percentage(&self) -> bool {
+        self.0.has_viewport_percentage()
+    }
+}
+
+impl<L: Parse> Parse for HorizontalPosition<PositionValue<L>> {
+    #[inline]
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        PositionValue::parse_internal(context, input, |keyword| {
+            matches!{ keyword,
+                Keyword::Left | Keyword::Right | Keyword::Center |
+                Keyword::XStart | Keyword::XEnd
+            }
+        }).map(HorizontalPosition)
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+/// A generic type for representing vertical `<position>`
+pub struct VerticalPosition<L>(pub L);
+
+impl<L: ToCss> ToCss for VerticalPosition<L> {
+    #[inline]
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        self.0.to_css(dest)
+    }
+}
+
+impl<L: HasViewportPercentage> HasViewportPercentage for VerticalPosition<L> {
+    #[inline]
+    fn has_viewport_percentage(&self) -> bool {
+        self.0.has_viewport_percentage()
+    }
+}
+
+impl<L: Parse> Parse for VerticalPosition<PositionValue<L>> {
+    #[inline]
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        PositionValue::parse_internal(context, input, |keyword| {
+            matches!{ keyword,
+                Keyword::Top | Keyword::Bottom | Keyword::Center |
+                Keyword::YStart | Keyword::YEnd
+            }
+        }).map(VerticalPosition)
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+/// A generic type for representing a CSS [position](https://drafts.csswg.org/css-values/#position).
+///
+/// Note that the horizontal and vertical positions aren't really different types.
+/// They're just unit struct wrappers over `LengthOrPercentage`. They should be different
+/// because they allow different keywords (for e.g., vertical position doesn't allow
+/// `right` or `left` keywords and vice versa).
+pub struct Position<H, V> {
+    /// The horizontal component of position.
+    pub horizontal: H,
+    /// The vertical component of position.
+    pub vertical: V,
+}
+
+/// A generic type for representing positions with keywords.
+pub type PositionWithKeyword<L> = Position<HorizontalPosition<L>, VerticalPosition<L>>;
+
+impl<H: HasViewportPercentage, V: HasViewportPercentage> HasViewportPercentage for Position<H, V> {
+    #[inline]
+    fn has_viewport_percentage(&self) -> bool {
+        self.horizontal.has_viewport_percentage() || self.vertical.has_viewport_percentage()
+    }
+}
+
+impl<L: ToCss> ToCss for Position<L, L> {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        self.horizontal.to_css(dest)?;
+        dest.write_str(" ")?;
+        self.vertical.to_css(dest)
+    }
+}
+
+impl<L: ToCss> ToCss for PositionWithKeyword<PositionValue<L>> {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        macro_rules! to_css_with_keyword {
+            ($pos:expr, $default:expr) => {
+                $pos.keyword.unwrap_or($default).to_css(dest)?;
+                if let Some(ref position) = $pos.position {
+                    dest.write_str(" ")?;
+                    position.to_css(dest)?;
+                }
+            }
+        }
+
+        if (self.horizontal.0.keyword.is_some() && self.horizontal.0.position.is_some()) ||
+           (self.vertical.0.keyword.is_some() && self.vertical.0.position.is_some()) {
+            to_css_with_keyword!(self.horizontal.0, Keyword::Left);
+            dest.write_str(" ")?;
+            to_css_with_keyword!(self.vertical.0, Keyword::Top);
+            return Ok(())
+        }
+
+        self.horizontal.to_css(dest)?;
+        dest.write_str(" ")?;
+        self.vertical.to_css(dest)
+    }
+}
+
+impl<H: ToComputedValue, V: ToComputedValue> ToComputedValue for Position<H, V> {
+    type ComputedValue = Position<H::ComputedValue, V::ComputedValue>;
+
+    #[inline]
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        Position {
+            horizontal: self.horizontal.to_computed_value(context),
+            vertical: self.vertical.to_computed_value(context),
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        Position {
+            horizontal: ToComputedValue::from_computed_value(&computed.horizontal),
+            vertical: ToComputedValue::from_computed_value(&computed.vertical),
+        }
+    }
+}
--- a/servo/components/style/values/mod.rs
+++ b/servo/components/style/values/mod.rs
@@ -77,16 +77,17 @@ macro_rules! add_impls_for_keyword_enum 
         }
 
         impl ComputedValueAsSpecified for $name {}
         no_viewport_percentage!($name);
     };
 }
 
 pub mod computed;
+pub mod generics;
 pub mod specified;
 
 /// A CSS float value.
 pub type CSSFloat = f32;
 
 /// A CSS integer value.
 pub type CSSInteger = i32;
 
--- a/servo/components/style/values/specified/basic_shape.rs
+++ b/servo/components/style/values/specified/basic_shape.rs
@@ -3,139 +3,35 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! CSS handling for the specified value of
 //! [`basic-shape`][basic-shape]s
 //!
 //! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape
 
 use cssparser::Parser;
-use euclid::size::Size2D;
 use parser::{Parse, ParserContext};
-use properties::shorthands::{parse_four_sides, serialize_four_sides};
+use properties::shorthands::parse_four_sides;
 use std::ascii::AsciiExt;
 use std::fmt;
 use style_traits::ToCss;
 use values::HasViewportPercentage;
 use values::computed::{ComputedValueAsSpecified, Context, ToComputedValue};
 use values::computed::basic_shape as computed_basic_shape;
-use values::specified::{BorderRadiusSize, LengthOrPercentage, Percentage};
+use values::generics::BorderRadiusSize;
+use values::generics::basic_shape::{BorderRadius as GenericBorderRadius, ShapeRadius as GenericShapeRadius};
+use values::generics::basic_shape::{InsetRect as GenericInsetRect, Polygon as GenericPolygon, ShapeSource};
+use values::specified::{LengthOrPercentage, Percentage};
 use values::specified::position::{Keyword, Position};
-use values::specified::url::SpecifiedUrl;
-
-/// A shape source, for some reference box
-///
-/// clip-path uses ShapeSource<GeometryBox>,
-/// shape-outside uses ShapeSource<ShapeBox>
-#[derive(Clone, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub enum ShapeSource<T> {
-    Url(SpecifiedUrl),
-    Shape(BasicShape, Option<T>),
-    Box(T),
-    None,
-}
-
-impl<T> Default for ShapeSource<T> {
-    fn default() -> Self {
-        ShapeSource::None
-    }
-}
-
-impl<T: ToCss> ToCss for ShapeSource<T> {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            ShapeSource::Url(ref url) => url.to_css(dest),
-            ShapeSource::Shape(ref shape, Some(ref reference)) => {
-                try!(shape.to_css(dest));
-                try!(dest.write_str(" "));
-                reference.to_css(dest)
-            }
-            ShapeSource::Shape(ref shape, None) => shape.to_css(dest),
-            ShapeSource::Box(ref reference) => reference.to_css(dest),
-            ShapeSource::None => dest.write_str("none"),
-
-        }
-    }
-}
-
-impl<T: Parse> Parse for ShapeSource<T> {
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
-        if input.try(|input| input.expect_ident_matching("none")).is_ok() {
-            return Ok(ShapeSource::None)
-        }
-
-        if let Ok(url) = input.try(|input| SpecifiedUrl::parse(context, input)) {
-            return Ok(ShapeSource::Url(url))
-        }
-
-        fn parse_component<U: Parse>(context: &ParserContext, input: &mut Parser,
-                                     component: &mut Option<U>) -> bool {
-            if component.is_some() {
-                return false            // already parsed this component
-            }
 
-            *component = input.try(|i| U::parse(context, i)).ok();
-            component.is_some()
-        }
-
-        let mut shape = None;
-        let mut reference = None;
-
-        while parse_component(context, input, &mut shape) ||
-              parse_component(context, input, &mut reference) {
-            //
-        }
-
-        if let Some(shp) = shape {
-            return Ok(ShapeSource::Shape(shp, reference))
-        }
-
-        match reference {
-            Some(r) => Ok(ShapeSource::Box(r)),
-            None => Err(())
-        }
-    }
-}
-
-impl<T: ToComputedValue> ToComputedValue for ShapeSource<T> {
-    type ComputedValue = computed_basic_shape::ShapeSource<T::ComputedValue>;
+/// The specified value used by `clip-path`
+pub type ShapeWithGeometryBox = ShapeSource<BasicShape, GeometryBox>;
 
-    #[inline]
-    fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue {
-        match *self {
-            ShapeSource::Url(ref url) => computed_basic_shape::ShapeSource::Url(url.to_computed_value(cx)),
-            ShapeSource::Shape(ref shape, ref reference) => {
-                computed_basic_shape::ShapeSource::Shape(
-                    shape.to_computed_value(cx),
-                    reference.as_ref().map(|ref r| r.to_computed_value(cx)))
-            },
-            ShapeSource::Box(ref reference) =>
-                computed_basic_shape::ShapeSource::Box(reference.to_computed_value(cx)),
-            ShapeSource::None => computed_basic_shape::ShapeSource::None,
-        }
-    }
-
-    #[inline]
-    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
-        match *computed {
-            computed_basic_shape::ShapeSource::Url(ref url) =>
-                ShapeSource::Url(SpecifiedUrl::from_computed_value(url)),
-            computed_basic_shape::ShapeSource::Shape(ref shape, ref reference) => {
-                ShapeSource::Shape(
-                    ToComputedValue::from_computed_value(shape),
-                    reference.as_ref().map(|r| ToComputedValue::from_computed_value(r)))
-            }
-            computed_basic_shape::ShapeSource::Box(ref reference) =>
-                ShapeSource::Box(ToComputedValue::from_computed_value(reference)),
-            computed_basic_shape::ShapeSource::None => ShapeSource::None,
-        }
-    }
-}
+/// The specified value used by `shape-outside`
+pub type ShapeWithShapeBox = ShapeSource<BasicShape, ShapeBox>;
 
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 pub enum BasicShape {
     Inset(InsetRect),
     Circle(Circle),
     Ellipse(Ellipse),
@@ -195,33 +91,24 @@ impl ToComputedValue for BasicShape {
             computed_basic_shape::BasicShape::Ellipse(ref e) =>
                 BasicShape::Ellipse(ToComputedValue::from_computed_value(e)),
             computed_basic_shape::BasicShape::Polygon(ref poly) =>
                 BasicShape::Polygon(ToComputedValue::from_computed_value(poly)),
         }
     }
 }
 
-#[derive(Clone, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-/// https://drafts.csswg.org/css-shapes/#funcdef-inset
-#[allow(missing_docs)]
-pub struct InsetRect {
-    pub top: LengthOrPercentage,
-    pub right: LengthOrPercentage,
-    pub bottom: LengthOrPercentage,
-    pub left: LengthOrPercentage,
-    pub round: Option<BorderRadius>,
-}
+/// The specified value of `inset()`
+pub type InsetRect = GenericInsetRect<LengthOrPercentage>;
 
 impl InsetRect {
-    #[allow(missing_docs)]
+    /// Parse the inner function arguments of `inset()`
     pub fn parse_function_arguments(context: &ParserContext, input: &mut Parser) -> Result<InsetRect, ()> {
-        let (t, r, b, l) = try!(parse_four_sides(input, |i| LengthOrPercentage::parse(context, i)));
-        let mut rect = InsetRect {
+        let (t, r, b, l) = parse_four_sides(input, |i| LengthOrPercentage::parse(context, i))?;
+        let mut rect = GenericInsetRect {
             top: t,
             right: r,
             bottom: b,
             left: l,
             round: None,
         };
 
         if input.try(|i| i.expect_ident_matching("round")).is_ok() {
@@ -231,68 +118,22 @@ impl InsetRect {
         Ok(rect)
     }
 }
 
 impl Parse for InsetRect {
     fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         match input.try(|i| i.expect_function()) {
             Ok(ref s) if s.eq_ignore_ascii_case("inset") =>
-                input.parse_nested_block(|i| InsetRect::parse_function_arguments(context, i)),
+                input.parse_nested_block(|i| GenericInsetRect::parse_function_arguments(context, i)),
             _ => Err(())
         }
     }
 }
 
-impl ToCss for InsetRect {
-    // XXXManishearth again, we should try to reduce the number of values printed here
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        try!(dest.write_str("inset("));
-        try!(self.top.to_css(dest));
-        try!(dest.write_str(" "));
-        try!(self.right.to_css(dest));
-        try!(dest.write_str(" "));
-        try!(self.bottom.to_css(dest));
-        try!(dest.write_str(" "));
-        try!(self.left.to_css(dest));
-        if let Some(ref radius) = self.round {
-            try!(dest.write_str(" round "));
-            try!(radius.to_css(dest));
-        }
-
-        dest.write_str(")")
-    }
-}
-
-impl ToComputedValue for InsetRect {
-    type ComputedValue = computed_basic_shape::InsetRect;
-
-    #[inline]
-    fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue {
-        computed_basic_shape::InsetRect {
-            top: self.top.to_computed_value(cx),
-            right: self.right.to_computed_value(cx),
-            bottom: self.bottom.to_computed_value(cx),
-            left: self.left.to_computed_value(cx),
-            round: self.round.as_ref().map(|r| r.to_computed_value(cx)),
-        }
-    }
-
-    #[inline]
-    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
-        InsetRect {
-            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),
-            round: computed.round.map(|ref r| ToComputedValue::from_computed_value(r)),
-        }
-    }
-}
-
 /// https://drafts.csswg.org/css-shapes/#basic-shape-serialization
 ///
 /// Positions get serialized differently with basic shapes. Keywords
 /// are converted to percentages where possible. Only the two or four
 /// value forms are used. In case of two keyword-percentage pairs,
 /// the keywords are folded into the percentages
 fn serialize_basicshape_position<W>(position: &Position, dest: &mut W) -> fmt::Result
     where W: fmt::Write
@@ -331,23 +172,21 @@ fn serialize_basicshape_position<W>(posi
 
     fn serialize_position_pair<W>(x: LengthOrPercentage, y: LengthOrPercentage,
                                   dest: &mut W) -> fmt::Result where W: fmt::Write {
         replace_with_percent(x).to_css(dest)?;
         dest.write_str(" ")?;
         replace_with_percent(y).to_css(dest)
     }
 
-    match (position.horizontal.keyword, position.horizontal.position.clone(),
-           position.vertical.keyword, position.vertical.position.clone()) {
+    match (position.horizontal.0.keyword, position.horizontal.0.position.clone(),
+           position.vertical.0.keyword, position.vertical.0.position.clone()) {
         (Some(hk), None, Some(vk), None) => {
             // two keywords: serialize as two lengths
-            serialize_position_pair(hk.to_length_or_percentage(),
-                                    vk.to_length_or_percentage(),
-                                    dest)
+            serialize_position_pair(hk.into(), vk.into(), dest)
         }
         (None, Some(hp), None, Some(vp)) => {
             // two lengths: just serialize regularly
             serialize_position_pair(hp, vp, dest)
         }
         (hk, hp, vk, vp) => {
             // only fold if both fold; the three-value form isn't
             // allowed here.
@@ -405,17 +244,17 @@ impl Parse for Circle {
            _ => Err(())
         }
     }
 }
 
 impl ToCss for Circle {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         try!(dest.write_str("circle("));
-        if ShapeRadius::ClosestSide != self.radius {
+        if GenericShapeRadius::ClosestSide != self.radius {
             try!(self.radius.to_css(dest));
             try!(dest.write_str(" "));
         }
 
         try!(dest.write_str("at "));
         try!(serialize_basicshape_position(&self.position, dest));
         dest.write_str(")")
     }
@@ -479,17 +318,17 @@ impl Parse for Ellipse {
             _ => Err(())
         }
     }
 }
 
 impl ToCss for Ellipse {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         try!(dest.write_str("ellipse("));
-        if !self.semiaxis_x.is_default() || !self.semiaxis_y.is_default() {
+        if self.semiaxis_x != ShapeRadius::default() || self.semiaxis_y != ShapeRadius::default() {
             try!(self.semiaxis_x.to_css(dest));
             try!(dest.write_str(" "));
             try!(self.semiaxis_y.to_css(dest));
             try!(dest.write_str(" "));
         }
 
         try!(dest.write_str("at "));
         try!(serialize_basicshape_position(&self.position, dest));
@@ -514,239 +353,51 @@ impl ToComputedValue for Ellipse {
         Ellipse {
             semiaxis_x: ToComputedValue::from_computed_value(&computed.semiaxis_x),
             semiaxis_y: ToComputedValue::from_computed_value(&computed.semiaxis_y),
             position: ToComputedValue::from_computed_value(&computed.position),
         }
     }
 }
 
+/// The specified value of `Polygon`
+pub type Polygon = GenericPolygon<LengthOrPercentage>;
 
-#[derive(Clone, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-/// https://drafts.csswg.org/css-shapes/#funcdef-polygon
-#[allow(missing_docs)]
-pub struct Polygon {
-    pub fill: FillRule,
-    pub coordinates: Vec<(LengthOrPercentage, LengthOrPercentage)>,
-}
+/// The specified value of `ShapeRadius`
+pub type ShapeRadius = GenericShapeRadius<LengthOrPercentage>;
 
-impl Polygon {
-    #[allow(missing_docs)]
-    pub fn parse_function_arguments(context: &ParserContext, input: &mut Parser) -> Result<Polygon, ()> {
-        let fill = input.try(|input| {
-            let fill = FillRule::parse(input);
-            // only eat the comma if there is something before it
-            try!(input.expect_comma());
-            fill
-        }).ok().unwrap_or_else(Default::default);
+impl Parse for ShapeRadius {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        if let Ok(lop) = input.try(|i| LengthOrPercentage::parse_non_negative(context, i)) {
+            return Ok(GenericShapeRadius::Length(lop))
+        }
 
-        let buf = try!(input.parse_comma_separated(|input| {
-            Ok((try!(LengthOrPercentage::parse(context, input)),
-                try!(LengthOrPercentage::parse(context, input))))
-        }));
-
-        Ok(Polygon {
-            fill: fill,
-            coordinates: buf,
-        })
-    }
-}
-
-impl Parse for Polygon {
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
-        match input.try(|i| i.expect_function()) {
-            Ok(ref s) if s.eq_ignore_ascii_case("polygon") =>
-                input.parse_nested_block(|i| Polygon::parse_function_arguments(context, i)),
+        match_ignore_ascii_case! { &input.expect_ident()?,
+            "closest-side" => Ok(GenericShapeRadius::ClosestSide),
+            "farthest-side" => Ok(GenericShapeRadius::FarthestSide),
             _ => Err(())
         }
     }
 }
 
-impl ToCss for Polygon {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        try!(dest.write_str("polygon("));
-        let mut need_space = false;
-        if self.fill != Default::default() {
-            try!(self.fill.to_css(dest));
-            try!(dest.write_str(", "));
-        }
-
-        for coord in &self.coordinates {
-            if need_space {
-                try!(dest.write_str(", "));
-            }
-
-            try!(coord.0.to_css(dest));
-            try!(dest.write_str(" "));
-            try!(coord.1.to_css(dest));
-            need_space = true;
-        }
-
-        dest.write_str(")")
-    }
-}
-
-impl ToComputedValue for Polygon {
-    type ComputedValue = computed_basic_shape::Polygon;
-
-    #[inline]
-    fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue {
-        computed_basic_shape::Polygon {
-            fill: self.fill.to_computed_value(cx),
-            coordinates: self.coordinates.iter()
-                                         .map(|c| {
-                                            (c.0.to_computed_value(cx),
-                                             c.1.to_computed_value(cx))
-                                         }).collect(),
-        }
-    }
-
-    #[inline]
-    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
-        Polygon {
-            fill: ToComputedValue::from_computed_value(&computed.fill),
-            coordinates: computed.coordinates.iter()
-                                             .map(|c| {
-                                                (ToComputedValue::from_computed_value(&c.0),
-                                                 ToComputedValue::from_computed_value(&c.1))
-                                             }).collect(),
-        }
-    }
-}
-
-/// https://drafts.csswg.org/css-shapes/#typedef-shape-radius
-#[derive(Clone, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub enum ShapeRadius {
-    Length(LengthOrPercentage),
-    ClosestSide,
-    FarthestSide,
-}
-
-impl ShapeRadius {
-    fn is_default(&self) -> bool {
-        *self == ShapeRadius::ClosestSide
-    }
-}
-
-impl Default for ShapeRadius {
-    fn default() -> Self {
-        ShapeRadius::ClosestSide
-    }
-}
-
-impl Parse for ShapeRadius {
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
-        input.try(|i| LengthOrPercentage::parse_non_negative(context, i)).map(ShapeRadius::Length).or_else(|_| {
-            match_ignore_ascii_case! { &try!(input.expect_ident()),
-                "closest-side" => Ok(ShapeRadius::ClosestSide),
-                "farthest-side" => Ok(ShapeRadius::FarthestSide),
-                _ => Err(())
-            }
-        })
-    }
-}
-
-impl ToCss for ShapeRadius {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            ShapeRadius::Length(ref lop) => lop.to_css(dest),
-            ShapeRadius::ClosestSide => dest.write_str("closest-side"),
-            ShapeRadius::FarthestSide => dest.write_str("farthest-side"),
-        }
-    }
-}
-
-
-impl ToComputedValue for ShapeRadius {
-    type ComputedValue = computed_basic_shape::ShapeRadius;
-
-    #[inline]
-    fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue {
-        match *self {
-            ShapeRadius::Length(ref lop) =>
-                computed_basic_shape::ShapeRadius::Length(lop.to_computed_value(cx)),
-            ShapeRadius::ClosestSide => computed_basic_shape::ShapeRadius::ClosestSide,
-            ShapeRadius::FarthestSide => computed_basic_shape::ShapeRadius::FarthestSide,
-        }
-    }
-
-    #[inline]
-    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
-        match *computed {
-            computed_basic_shape::ShapeRadius::Length(ref lop) =>
-                ShapeRadius::Length(ToComputedValue::from_computed_value(lop)),
-            computed_basic_shape::ShapeRadius::ClosestSide => ShapeRadius::ClosestSide,
-            computed_basic_shape::ShapeRadius::FarthestSide => ShapeRadius::FarthestSide,
-        }
-    }
-}
-
-/// https://drafts.csswg.org/css-backgrounds-3/#border-radius
-#[derive(Clone, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub struct BorderRadius {
-    pub top_left: BorderRadiusSize,
-    pub top_right: BorderRadiusSize,
-    pub bottom_right: BorderRadiusSize,
-    pub bottom_left: BorderRadiusSize,
-}
-
-/// Serialization helper for types of longhands like `border-radius` and `outline-radius`
-pub fn serialize_radius_values<L, W>(dest: &mut W, top_left: &Size2D<L>,
-                                     top_right: &Size2D<L>, bottom_right: &Size2D<L>,
-                                     bottom_left: &Size2D<L>) -> fmt::Result
-    where L: ToCss + PartialEq, W: fmt::Write
-{
-    if top_left.width == top_left.height &&
-       top_right.width == top_right.height &&
-       bottom_right.width == bottom_right.height &&
-       bottom_left.width == bottom_left.height {
-        serialize_four_sides(dest,
-                             &top_left.width,
-                             &top_right.width,
-                             &bottom_right.width,
-                             &bottom_left.width)
-    } else {
-        serialize_four_sides(dest,
-                             &top_left.width,
-                             &top_right.width,
-                             &bottom_right.width,
-                             &bottom_left.width)?;
-        dest.write_str(" / ")?;
-        serialize_four_sides(dest,
-                             &top_left.height,
-                             &top_right.height,
-                             &bottom_right.height,
-                             &bottom_left.height)
-    }
-}
-
-impl ToCss for BorderRadius {
-    #[inline]
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        serialize_radius_values(dest, &self.top_left.0, &self.top_right.0,
-                                &self.bottom_right.0, &self.bottom_left.0)
-    }
-}
+/// The specified value of `BorderRadius`
+pub type BorderRadius = GenericBorderRadius<LengthOrPercentage>;
 
 impl Parse for BorderRadius {
     fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
-        let mut widths = try!(parse_one_set_of_border_values(context, input));
+        let mut widths = parse_one_set_of_border_values(context, input)?;
         let mut heights = if input.try(|input| input.expect_delim('/')).is_ok() {
-            try!(parse_one_set_of_border_values(context, input))
+            parse_one_set_of_border_values(context, input)?
         } else {
             [widths[0].clone(),
              widths[1].clone(),
              widths[2].clone(),
              widths[3].clone()]
         };
+
         Ok(BorderRadius {
             top_left: BorderRadiusSize::new(widths[0].take(), heights[0].take()),
             top_right: BorderRadiusSize::new(widths[1].take(), heights[1].take()),
             bottom_right: BorderRadiusSize::new(widths[2].take(), heights[2].take()),
             bottom_left: BorderRadiusSize::new(widths[3].take(), heights[3].take()),
         })
     }
 }
@@ -768,58 +419,16 @@ fn parse_one_set_of_border_values(contex
 
     if let Ok(d) = input.try(|i| LengthOrPercentage::parse_non_negative(context, i)) {
         Ok([a, b, c, d])
     } else {
         Ok([a, b.clone(), c, b])
     }
 }
 
-
-impl ToComputedValue for BorderRadius {
-    type ComputedValue = computed_basic_shape::BorderRadius;
-
-    #[inline]
-    fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue {
-        computed_basic_shape::BorderRadius {
-            top_left: self.top_left.to_computed_value(cx),
-            top_right: self.top_right.to_computed_value(cx),
-            bottom_right: self.bottom_right.to_computed_value(cx),
-            bottom_left: self.bottom_left.to_computed_value(cx),
-        }
-    }
-
-    #[inline]
-    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
-        BorderRadius {
-            top_left: ToComputedValue::from_computed_value(&computed.top_left),
-            top_right: ToComputedValue::from_computed_value(&computed.top_right),
-            bottom_right: ToComputedValue::from_computed_value(&computed.bottom_right),
-            bottom_left: ToComputedValue::from_computed_value(&computed.bottom_left),
-        }
-    }
-}
-
-// https://drafts.csswg.org/css-shapes/#typedef-fill-rule
-// NOTE: Basic shapes spec says that these are the only two values, however
-// https://www.w3.org/TR/SVG/painting.html#FillRuleProperty
-// says that it can also be `inherit`
-define_css_keyword_enum!(FillRule:
-    "nonzero" => NonZero,
-    "evenodd" => EvenOdd
-);
-
-impl ComputedValueAsSpecified for FillRule {}
-
-impl Default for FillRule {
-    fn default() -> Self {
-        FillRule::NonZero
-    }
-}
-
 /// https://drafts.fxtf.org/css-masking-1/#typedef-geometry-box
 #[derive(Clone, PartialEq, Copy, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 pub enum GeometryBox {
     FillBox,
     StrokeBox,
     ViewBox,
--- a/servo/components/style/values/specified/length.rs
+++ b/servo/components/style/values/specified/length.rs
@@ -1141,17 +1141,19 @@ impl ToCss for LengthOrPercentage {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         match *self {
             LengthOrPercentage::Length(ref length) => length.to_css(dest),
             LengthOrPercentage::Percentage(percentage) => percentage.to_css(dest),
             LengthOrPercentage::Calc(ref calc) => calc.to_css(dest),
         }
     }
 }
+
 impl LengthOrPercentage {
+    #[inline]
     /// Returns a `zero` length.
     pub fn zero() -> LengthOrPercentage {
         LengthOrPercentage::Length(NoCalcLength::zero())
     }
 
     fn parse_internal(context: &ParserContext, input: &mut Parser, num_context: AllowedLengthType)
                       -> Result<LengthOrPercentage, ()>
     {
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -17,16 +17,17 @@ use std::f32;
 use std::f32::consts::PI;
 use std::fmt;
 use std::ops::Mul;
 use style_traits::ToCss;
 use style_traits::values::specified::AllowedNumericType;
 use super::{Auto, CSSFloat, CSSInteger, HasViewportPercentage, Either, None_};
 use super::computed::{self, Context};
 use super::computed::{Shadow as ComputedShadow, ToComputedValue};
+use super::generics::BorderRadiusSize as GenericBorderRadiusSize;
 
 #[cfg(feature = "gecko")]
 pub use self::align::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
 pub use self::color::Color;
 pub use self::grid::{GridLine, TrackKeyword};
 pub use self::image::{AngleOrCorner, ColorStop, EndingShape as GradientEndingShape, Gradient};
 pub use self::image::{GradientKind, HorizontalDirection, Image, ImageRect, LengthOrKeyword};
 pub use self::image::{LengthOrPercentageOrKeyword, SizeKeyword, VerticalDirection};
@@ -277,56 +278,26 @@ pub fn parse_number_with_clamping_mode(c
                 },
                 _ => Err(())
             }
         }
         _ => Err(())
     }
 }
 
-#[derive(Clone, PartialEq, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub struct BorderRadiusSize(pub Size2D<LengthOrPercentage>);
-
-no_viewport_percentage!(BorderRadiusSize);
-
-impl BorderRadiusSize {
-    #[allow(missing_docs)]
-    pub fn zero() -> BorderRadiusSize {
-        let zero = LengthOrPercentage::Length(NoCalcLength::zero());
-        BorderRadiusSize(Size2D::new(zero.clone(), zero))
-    }
-
-    #[allow(missing_docs)]
-    pub fn new(width: LengthOrPercentage, height: LengthOrPercentage) -> BorderRadiusSize {
-        BorderRadiusSize(Size2D::new(width, height))
-    }
-
-    #[allow(missing_docs)]
-    pub fn circle(radius: LengthOrPercentage) -> BorderRadiusSize {
-        BorderRadiusSize(Size2D::new(radius.clone(), radius))
-    }
-}
+/// The specified value of `BorderRadiusSize`
+pub type BorderRadiusSize = GenericBorderRadiusSize<LengthOrPercentage>;
 
 impl Parse for BorderRadiusSize {
     #[inline]
     fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         let first = try!(LengthOrPercentage::parse_non_negative(context, input));
         let second = input.try(|i| LengthOrPercentage::parse_non_negative(context, i))
             .unwrap_or_else(|()| first.clone());
-        Ok(BorderRadiusSize(Size2D::new(first, second)))
-    }
-}
-
-impl ToCss for BorderRadiusSize {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        try!(self.0.width.to_css(dest));
-        try!(dest.write_str(" "));
-        self.0.height.to_css(dest)
+        Ok(GenericBorderRadiusSize(Size2D::new(first, second)))
     }
 }
 
 #[derive(Clone, PartialEq, Copy, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
 /// An angle consisting of a value and a unit.
 pub struct Angle {
     value: CSSFloat,
--- a/servo/components/style/values/specified/position.rs
+++ b/servo/components/style/values/specified/position.rs
@@ -3,621 +3,261 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! CSS handling for the specified value of
 //! [`position`][position]s
 //!
 //! [position]: https://drafts.csswg.org/css-backgrounds-3/#position
 
 use app_units::Au;
-use cssparser::{Parser, Token};
+use cssparser::Parser;
 use parser::{Parse, ParserContext};
-use std::fmt;
-use style_traits::ToCss;
-use values::HasViewportPercentage;
-use values::computed::{self, CalcLengthOrPercentage, Context};
+use properties::longhands::parse_origin;
+use std::mem;
+use values::Either;
+use values::computed::{CalcLengthOrPercentage, Context};
 use values::computed::{LengthOrPercentage as ComputedLengthOrPercentage, ToComputedValue};
 use values::computed::position as computed_position;
+use values::generics::position::{Position as GenericPosition, PositionValue, PositionWithKeyword};
+use values::generics::position::HorizontalPosition as GenericHorizontalPosition;
+use values::generics::position::VerticalPosition as GenericVerticalPosition;
 use values::specified::{LengthOrPercentage, Percentage};
 
-#[derive(Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-/// A [position][pos].
-///
-/// [pos]: https://drafts.csswg.org/css-values/#position
-pub struct Position {
-    /// The horizontal component.
-    pub horizontal: HorizontalPosition,
-    /// The vertical component.
-    pub vertical: VerticalPosition,
-}
+pub use values::generics::position::Keyword;
 
-impl ToCss for Position {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        macro_rules! to_css_with_keyword {
-            ($pos:expr, $default_keyword:expr) => {
-                $pos.keyword.unwrap_or($default_keyword).to_css(dest)?;
+/// The specified value of a CSS `<position>`
+pub type Position = PositionWithKeyword<PositionValue<LengthOrPercentage>>;
+
+/// The specified value for `<position>` values without a keyword.
+pub type OriginPosition = GenericPosition<LengthOrPercentage, LengthOrPercentage>;
 
-                if let Some(ref position) = $pos.position {
-                    dest.write_str(" ")?;
-                    position.to_css(dest)?;
-                };
-            };
+impl Parse for OriginPosition {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        let result = parse_origin(context, input)?;
+        match result.depth {
+            Some(_) => Err(()),
+            None => Ok(GenericPosition {
+                horizontal: result.horizontal.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))),
+                vertical: result.vertical.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))),
+            })
         }
-
-        let mut is_keyword_needed = false;
-        let position_x = &self.horizontal;
-        let position_y = &self.vertical;
-
-        if (position_x.keyword.is_some() && position_x.position.is_some()) ||
-           (position_y.keyword.is_some() && position_y.position.is_some()) {
-            is_keyword_needed = true;
-        }
-
-        if is_keyword_needed {
-            to_css_with_keyword!(position_x, Keyword::Left);
-            dest.write_str(" ")?;
-            to_css_with_keyword!(position_y, Keyword::Top);
-        } else {
-            position_x.to_css(dest)?;
-            dest.write_str(" ")?;
-            position_y.to_css(dest)?;
-        }
-
-        Ok(())
     }
 }
 
-impl HasViewportPercentage for Position {
-    fn has_viewport_percentage(&self) -> bool {
-        self.horizontal.has_viewport_percentage() || self.vertical.has_viewport_percentage()
-    }
-}
+type PositionComponent = Either<LengthOrPercentage, Keyword>;
 
 impl Position {
-    /// Create a new position value.
-    pub fn new(mut first_position: Option<PositionComponent>,
-               mut second_position: Option<PositionComponent>,
-               first_keyword: Option<PositionComponent>,
-               second_keyword: Option<PositionComponent>)
-               -> Result<Position, ()> {
+    /// Create a new position value from either a length or a keyword.
+    pub fn from_components(mut first_position: Option<PositionComponent>,
+                           mut second_position: Option<PositionComponent>,
+                           first_keyword: Option<PositionComponent>,
+                           second_keyword: Option<PositionComponent>) -> Result<Position, ()> {
         // Unwrap for checking if values are at right place.
-        let first_key = first_keyword.clone().unwrap_or(PositionComponent::Keyword(Keyword::Left));
-        let second_key = second_keyword.clone().unwrap_or(PositionComponent::Keyword(Keyword::Top));
+        let first_key = first_keyword.clone().unwrap_or(Either::Second(Keyword::Left));
+        let second_key = second_keyword.clone().unwrap_or(Either::Second(Keyword::Top));
 
-        // Check if position specified after center keyword.
-        if let PositionCategory::OtherKeyword = category(&first_key) {
-            if let Some(_) = first_position {
-                return Err(());
-            };
-        };
-        if let PositionCategory::OtherKeyword = category(&second_key) {
-            if let Some(_) = second_position {
-                return Err(());
-            };
-        };
+        let (horiz_keyword, vert_keyword) = match (&first_key, &second_key) {
+            // Check if a position is specified after center keyword.
+            (&Either::Second(Keyword::Center), _) if first_position.is_some() => return Err(()),
+            (_, &Either::Second(Keyword::Center)) if second_position.is_some() => return Err(()),
 
-        // Check first and second keywords for both 2 and 4 value positions.
-        let (horiz_keyword, vert_keyword) = match (category(&first_key), category(&second_key)) {
-            // Don't allow two vertical keywords or two horizontal keywords.
-            // also don't allow length/percentage values in the wrong position
-            (PositionCategory::HorizontalKeyword, PositionCategory::HorizontalKeyword) |
-            (PositionCategory::VerticalKeyword, PositionCategory::VerticalKeyword) |
-            (PositionCategory::LengthOrPercentage, PositionCategory::HorizontalKeyword) |
-            (PositionCategory::VerticalKeyword, PositionCategory::LengthOrPercentage) => return Err(()),
+            // Check first and second keywords for both 2 and 4 value positions.
 
             // FIXME(canaltinova): Allow logical keywords for Position. They are not in current spec yet.
-            (PositionCategory::HorizontalLogicalKeyword, _) |
-            (PositionCategory::VerticalLogicalKeyword, _) |
-            (_, PositionCategory::HorizontalLogicalKeyword) |
-            (_, PositionCategory::VerticalLogicalKeyword) => return Err(()),
+            (&Either::Second(k), _) if k.is_logical() => return Err(()),
+            (_, &Either::Second(k)) if k.is_logical() => return Err(()),
+
+             // Don't allow two vertical keywords or two horizontal keywords.
+            (&Either::Second(k1), &Either::Second(k2))
+                if (k1.is_horizontal() && k2.is_horizontal()) || (k1.is_vertical() && k2.is_vertical()) =>
+                    return Err(()),
+
+            // Also don't allow <length-percentage> values in the wrong position
+            (&Either::First(_), &Either::Second(k)) if k.is_horizontal() => return Err(()),
+            (&Either::Second(k), &Either::First(_)) if k.is_vertical() => return Err(()),
 
             // Swap if both are keywords and vertical precedes horizontal.
-            (PositionCategory::VerticalKeyword, PositionCategory::HorizontalKeyword) |
-            (PositionCategory::VerticalKeyword, PositionCategory::OtherKeyword) |
-            (PositionCategory::OtherKeyword, PositionCategory::HorizontalKeyword) => {
-                let tmp = first_position;
-                first_position = second_position;
-                second_position = tmp;
-
+            (&Either::Second(k1), &Either::Second(k2))
+                if (k1.is_vertical() && k2.is_horizontal()) || (k1.is_vertical() && k2 == Keyword::Center) ||
+                   (k1 == Keyword::Center && k2.is_horizontal()) => {
+                mem::swap(&mut first_position, &mut second_position);
                 (second_keyword, first_keyword)
             },
+
             // By default, horizontal is first.
             _ => (first_keyword, second_keyword),
         };
 
-        // Unwrap positions from PositionComponent and wrap with Option
-        let (first_position, second_position) = if let Some(PositionComponent::Length(horiz_pos)) = first_position {
-            if let Some(PositionComponent::Length(vert_pos)) = second_position {
-                (Some(horiz_pos), Some(vert_pos))
-            } else {
-                (Some(horiz_pos), None)
-            }
-        } else {
-            if let Some(PositionComponent::Length(vert_pos)) = second_position {
-                (None, Some(vert_pos))
-            } else {
-                (None, None)
-            }
-        };
+        let (mut h_pos, mut h_key, mut v_pos, mut v_key) = (None, None, None, None);
+        if let Some(Either::First(l)) = first_position {
+            h_pos = Some(l);
+        }
 
-        // Unwrap keywords from PositionComponent and wrap with Option.
-        let (horizontal_keyword, vertical_keyword) = if let Some(PositionComponent::Keyword(horiz_key)) =
-                                                     horiz_keyword {
-            if let Some(PositionComponent::Keyword(vert_key)) = vert_keyword {
-                (Some(horiz_key), Some(vert_key))
-            } else {
-                (Some(horiz_key), None)
-            }
-        } else {
-            if let Some(PositionComponent::Keyword(vert_key)) = vert_keyword {
-                (None, Some(vert_key))
-            } else {
-                (None, None)
-            }
-        };
+        if let Some(Either::First(l)) = second_position {
+            v_pos = Some(l);
+        }
+
+        if let Some(Either::Second(k)) = horiz_keyword {
+            h_key = Some(k);
+        }
+
+        if let Some(Either::Second(k)) = vert_keyword {
+            v_key = Some(k);
+        }
 
         Ok(Position {
-            horizontal: HorizontalPosition {
-                keyword: horizontal_keyword,
-                position: first_position,
-            },
-            vertical: VerticalPosition {
-                keyword: vertical_keyword,
-                position: second_position,
-            },
+            horizontal: GenericHorizontalPosition(PositionValue {
+                keyword: h_key,
+                position: h_pos,
+            }),
+            vertical: GenericVerticalPosition(PositionValue {
+                keyword: v_key,
+                position: v_pos,
+            }),
         })
     }
 
     /// Returns a "centered" position, as in "center center".
     pub fn center() -> Position {
         Position {
-            horizontal: HorizontalPosition {
+            horizontal: GenericHorizontalPosition(PositionValue {
                 keyword: Some(Keyword::Center),
                 position: None,
-            },
-            vertical: VerticalPosition {
+            }),
+            vertical: GenericVerticalPosition(PositionValue {
                 keyword: Some(Keyword::Center),
                 position: None,
-            },
+            }),
         }
     }
 }
 
 impl Parse for Position {
     fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
-        let first = try!(PositionComponent::parse(context, input));
+        let first = input.try(|i| PositionComponent::parse(context, i))?;
         let second = input.try(|i| PositionComponent::parse(context, i))
-            .unwrap_or(PositionComponent::Keyword(Keyword::Center));
+                          .unwrap_or(Either::Second(Keyword::Center));
 
-        // Try to parse third and fourth values
         if let Ok(third) = input.try(|i| PositionComponent::parse(context, i)) {
+            // There's a 3rd value.
             if let Ok(fourth) = input.try(|i| PositionComponent::parse(context, i)) {
-                // Handle 4 value background position
-                Position::new(Some(second), Some(fourth), Some(first), Some(third))
+                // There's a 4th value.
+                Position::from_components(Some(second), Some(fourth), Some(first), Some(third))
             } else {
-                // Handle 3 value background position there are several options:
-                if let PositionCategory::LengthOrPercentage = category(&first) {
-                    Err(())
-                } else {
-                    if let PositionCategory::LengthOrPercentage = category(&second) {
-                        if let PositionCategory::LengthOrPercentage = category(&third) {
-                            Err(())
-                        } else {
-                            // "keyword length keyword"
-                            Position::new(Some(second), None, Some(first), Some(third))
-                        }
-                    } else {
-                        // "keyword keyword length"
-                        Position::new(None, Some(third), Some(first), Some(second))
-                    }
+                // For 3 value background position, there are several options.
+                if let Either::First(_) = first {
+                    return Err(())      // <length-percentage> must be preceded by <keyword>
+                }
+
+                // only 3 values.
+                match (&second, &third) {
+                    (&Either::First(_), &Either::First(_)) => Err(()),
+                    // "keyword length keyword"
+                    (&Either::First(_), _) => Position::from_components(Some(second), None,
+                                                                        Some(first), Some(third)),
+                    // "keyword keyword length"
+                    _ => Position::from_components(None, Some(third), Some(first), Some(second)),
                 }
             }
         } else {
-            // Handle 2 value background position.
-            if let PositionCategory::LengthOrPercentage = category(&first) {
-                if let PositionCategory::LengthOrPercentage = category(&second) {
-                    Position::new(Some(first), Some(second), None, None)
-                } else {
-                    Position::new(Some(first), None, None, Some(second))
-                }
-            } else {
-                if let PositionCategory::LengthOrPercentage = category(&second) {
-                    Position::new(None, Some(second), Some(first), None)
-                } else {
-                    Position::new(None, None, Some(first), Some(second))
-                }
+            // only 2 values.
+            match (&first, &second) {
+                (&Either::First(_), &Either::First(_)) =>
+                    Position::from_components(Some(first), Some(second), None, None),
+                (&Either::First(_), &Either::Second(_)) =>
+                    Position::from_components(Some(first), None, None, Some(second)),
+                (&Either::Second(_), &Either::First(_)) =>
+                    Position::from_components(None, Some(second), Some(first), None),
+                (&Either::Second(_), &Either::Second(_)) =>
+                    Position::from_components(None, None, Some(first), Some(second)),
             }
         }
     }
 }
 
-impl ToComputedValue for Position {
-    type ComputedValue = computed_position::Position;
-
-    #[inline]
-    fn to_computed_value(&self, context: &Context) -> computed_position::Position {
-        computed_position::Position {
-            horizontal: self.horizontal.to_computed_value(context).0,
-            vertical: self.vertical.to_computed_value(context).0,
-        }
-    }
+impl PositionValue<LengthOrPercentage> {
+    /// Generic function for the computed value of a position.
+    fn computed_value(&self, context: &Context) -> ComputedLengthOrPercentage {
+        match self.keyword {
+            Some(Keyword::Center) => ComputedLengthOrPercentage::Percentage(0.5),
+            Some(k) if k.is_other_side() => match self.position {
+                Some(ref x) => {
+                    let (length, percentage) = match *x {
+                        LengthOrPercentage::Percentage(Percentage(y)) => (Au(0), Some(1.0 - y)),
+                        LengthOrPercentage::Length(ref y) => (-y.to_computed_value(context), Some(1.0)),
+                        _ => (Au(0), None),
+                    };
 
-    #[inline]
-    fn from_computed_value(computed: &computed_position::Position) -> Position {
-        Position {
-            horizontal: HorizontalPosition {
-                keyword: None,
-                position: Some(ToComputedValue::from_computed_value(&computed.horizontal)),
+                    ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage {
+                        length: length,
+                        percentage: percentage
+                    })
+                },
+                None => ComputedLengthOrPercentage::Percentage(1.0),
             },
-            vertical: VerticalPosition {
-                keyword: None,
-                position: Some(ToComputedValue::from_computed_value(&computed.vertical)),
-            },
+            _ => self.position.as_ref().map(|l| l.to_computed_value(context))
+                              .unwrap_or(ComputedLengthOrPercentage::Percentage(0.0)),
         }
     }
 }
 
-#[derive(Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub struct HorizontalPosition {
-    pub keyword: Option<Keyword>,
-    pub position: Option<LengthOrPercentage>,
-}
-
-impl HasViewportPercentage for HorizontalPosition {
-    fn has_viewport_percentage(&self) -> bool {
-        self.position.as_ref().map_or(false, |pos| pos.has_viewport_percentage())
-    }
-}
-
-impl ToCss for HorizontalPosition {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        let mut keyword_present = false;
-        if let Some(keyword) = self.keyword {
-            try!(keyword.to_css(dest));
-            keyword_present = true;
-        };
-
-        if let Some(ref position) = self.position {
-            if keyword_present {
-                try!(dest.write_str(" "));
-            }
-            try!(position.to_css(dest));
-        };
-        Ok(())
-    }
-}
-
-impl Parse for HorizontalPosition {
-    #[inline]
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
-        let first = try!(PositionComponent::parse(context, input));
-        let second = input.try(|i| PositionComponent::parse(context, i)).ok();
-
-        let (keyword, position) = if let PositionCategory::LengthOrPercentage = category(&first) {
-            // "length keyword?"
-            (second, Some(first))
-        } else {
-            // "keyword length?"
-            (Some(first), second)
-        };
-
-        // Unwrapping and checking keyword.
-        let keyword = match keyword {
-            Some(PositionComponent::Keyword(key)) => {
-                match category(keyword.as_ref().unwrap()) {
-                    PositionCategory::VerticalKeyword |
-                    PositionCategory::VerticalLogicalKeyword => return Err(()),
-                    _ => Some(key),
-                }
-            },
-            Some(_) => return Err(()),
-            None => None,
-        };
-
-        // Unwrapping and checking position.
-        let position = match position {
-            Some(PositionComponent::Length(pos)) => {
-                // "center <length>" is not allowed
-                if let Some(Keyword::Center) = keyword {
-                    return Err(());
-                }
-                Some(pos)
-            },
-            Some(_) => return Err(()),
-            None => None,
-        };
-
-        Ok(HorizontalPosition {
-            keyword: keyword,
-            position: position,
-        })
-    }
-}
+/// The specified value of horizontal `<position>`
+pub type HorizontalPosition = GenericHorizontalPosition<PositionValue<LengthOrPercentage>>;
 
 impl ToComputedValue for HorizontalPosition {
     type ComputedValue = computed_position::HorizontalPosition;
 
     #[inline]
     fn to_computed_value(&self, context: &Context) -> computed_position::HorizontalPosition {
-        let keyword = self.keyword.unwrap_or(Keyword::Left);
-
-        // Construct horizontal computed LengthOrPercentage
-        let horizontal = match keyword {
-            // FIXME(canaltinova): Support logical keywords.
-            Keyword::Right | Keyword::XEnd => {
-                if let Some(ref x) = self.position {
-                    let (length, percentage) = match *x {
-                        LengthOrPercentage::Percentage(Percentage(y)) => (Au(0), Some(1.0 - y)),
-                        LengthOrPercentage::Length(ref y) => (-y.to_computed_value(context), Some(1.0)),
-                        _ => (Au(0), None),
-                    };
-                    ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage {
-                        length: length,
-                        percentage: percentage
-                    })
-                } else {
-                    ComputedLengthOrPercentage::Percentage(1.0)
-                }
-            },
-            Keyword::Center => {
-                keyword.to_length_or_percentage().to_computed_value(context)
-            },
-             _ => {
-                let zero = LengthOrPercentage::Percentage(Percentage(0.0));
-                let horiz = self.position.as_ref().unwrap_or(&zero);
-                horiz.to_computed_value(context)
-            },
-        };
-
-        computed_position::HorizontalPosition(horizontal)
+        GenericHorizontalPosition(self.0.computed_value(context))
     }
 
     #[inline]
     fn from_computed_value(computed: &computed_position::HorizontalPosition) -> HorizontalPosition {
-        HorizontalPosition {
+        GenericHorizontalPosition(PositionValue {
             keyword: None,
             position: Some(ToComputedValue::from_computed_value(&computed.0)),
-        }
-    }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub struct VerticalPosition {
-    pub keyword: Option<Keyword>,
-    pub position: Option<LengthOrPercentage>,
-}
-
-impl HasViewportPercentage for VerticalPosition {
-    fn has_viewport_percentage(&self) -> bool {
-        self.position.as_ref().map_or(false, |pos| pos.has_viewport_percentage())
+        })
     }
 }
 
-impl ToCss for VerticalPosition {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        let mut keyword_present = false;
-        if let Some(keyword) = self.keyword {
-            try!(keyword.to_css(dest));
-            keyword_present = true;
-        };
-
-        if let Some(ref position) = self.position {
-            if keyword_present {
-                try!(dest.write_str(" "));
-            }
-            try!(position.to_css(dest));
-        };
-        Ok(())
-    }
-
-}
-
-impl Parse for VerticalPosition {
+impl HorizontalPosition {
     #[inline]
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
-        let first = try!(PositionComponent::parse(context, input));
-        let second = input.try(|i| PositionComponent::parse(context, i)).ok();
-
-        let (keyword, position) = if let PositionCategory::LengthOrPercentage = category(&first) {
-            // "length keyword?"
-            (second, Some(first))
-        } else {
-            // "keyword length?"
-            (Some(first), second)
-        };
-
-        // Unwrapping and checking keyword.
-        let keyword = match keyword {
-            Some(PositionComponent::Keyword(key)) => {
-                match category(keyword.as_ref().unwrap()) {
-                    PositionCategory::HorizontalKeyword |
-                    PositionCategory::HorizontalLogicalKeyword => return Err(()),
-                    _ => Some(key),
-                }
-            },
-            Some(_) => return Err(()),
-            None => None,
-        };
-
-        // Unwrapping and checking position.
-        let position = match position {
-            Some(PositionComponent::Length(pos)) => {
-                // "center <length>" is not allowed
-                if let Some(Keyword::Center) = keyword {
-                    return Err(());
-                }
-                Some(pos)
-            },
-            Some(_) => return Err(()),
-            None => None,
-        };
-
-        Ok(VerticalPosition {
-            keyword: keyword,
-            position: position,
+    /// Initial specified value for vertical position (`top` keyword).
+    pub fn left() -> HorizontalPosition {
+        GenericHorizontalPosition(PositionValue {
+            keyword: Some(Keyword::Left),
+            position: None,
         })
     }
 }
 
+
+/// The specified value of vertical `<position>`
+pub type VerticalPosition = GenericVerticalPosition<PositionValue<LengthOrPercentage>>;
+
 impl ToComputedValue for VerticalPosition {
     type ComputedValue = computed_position::VerticalPosition;
 
     #[inline]
     fn to_computed_value(&self, context: &Context) -> computed_position::VerticalPosition {
-        let keyword = self.keyword.unwrap_or(Keyword::Left);
-
-        // Construct vertical computed LengthOrPercentage
-        let vertical = match keyword {
-            // FIXME(canaltinova): Support logical keywords.
-            Keyword::Bottom | Keyword::YEnd => {
-                if let Some(ref x) = self.position {
-                    let (length, percentage) = match *x {
-                        LengthOrPercentage::Percentage(Percentage(y)) => (Au(0), Some(1.0 - y)),
-                        LengthOrPercentage::Length(ref y) => (-y.to_computed_value(context), Some(1.0)),
-                        _ => (Au(0), None),
-                    };
-                    ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage {
-                        length: length,
-                        percentage: percentage
-                    })
-                } else {
-                    ComputedLengthOrPercentage::Percentage(1.0)
-                }
-            },
-            Keyword::Center => {
-                keyword.to_length_or_percentage().to_computed_value(context)
-            },
-             _ => {
-                let zero = LengthOrPercentage::Percentage(Percentage(0.0));
-                let vert = self.position.as_ref().unwrap_or(&zero);
-                vert.to_computed_value(context)
-            },
-        };
-
-        computed_position::VerticalPosition(vertical)
+        GenericVerticalPosition(self.0.computed_value(context))
     }
 
     #[inline]
     fn from_computed_value(computed: &computed_position::VerticalPosition) -> VerticalPosition {
-        VerticalPosition {
+        GenericVerticalPosition(PositionValue {
             keyword: None,
             position: Some(ToComputedValue::from_computed_value(&computed.0)),
-        }
-    }
-}
-
-define_css_keyword_enum!(Keyword:
-                         "center" => Center,
-                         "left" => Left,
-                         "right" => Right,
-                         "top" => Top,
-                         "bottom" => Bottom,
-                         "x-start" => XStart,
-                         "x-end" => XEnd,
-                         "y-start" => YStart,
-                         "y-end" => YEnd);
-
-impl Keyword {
-    /// Convert the given keyword to a length or a percentage.
-    pub fn to_length_or_percentage(self) -> LengthOrPercentage {
-        match self {
-            Keyword::Center => LengthOrPercentage::Percentage(Percentage(0.5)),
-            Keyword::Left | Keyword::Top => LengthOrPercentage::Percentage(Percentage(0.0)),
-            Keyword::Right | Keyword::Bottom => LengthOrPercentage::Percentage(Percentage(1.0)),
-            // FIXME(canaltinova): Support logical keywords
-            Keyword::XStart | Keyword::YStart => LengthOrPercentage::Percentage(Percentage(0.0)),
-            Keyword::XEnd | Keyword::YEnd => LengthOrPercentage::Percentage(Percentage(1.0)),
-        }
+        })
     }
 }
 
-// Collapse `Position` into a few categories to simplify the above `match` expression.
-enum PositionCategory {
-    HorizontalKeyword,
-    VerticalKeyword,
-    HorizontalLogicalKeyword,
-    VerticalLogicalKeyword,
-    OtherKeyword,
-    LengthOrPercentage,
-}
-
-/// A position component.
-///
-/// http://dev.w3.org/csswg/css2/colors.html#propdef-background-position
-#[derive(Clone, PartialEq)]
-pub enum PositionComponent {
-    /// A `<length>`
-    Length(LengthOrPercentage),
-    /// A position keyword.
-    Keyword(Keyword),
-}
-
-fn category(p: &PositionComponent) -> PositionCategory {
-    if let PositionComponent::Keyword(keyword) = *p {
-        match keyword {
-            Keyword::Left | Keyword::Right =>
-                PositionCategory::HorizontalKeyword,
-            Keyword::Top | Keyword::Bottom =>
-                PositionCategory::VerticalKeyword,
-            Keyword::XStart | Keyword::XEnd =>
-                PositionCategory::HorizontalLogicalKeyword,
-            Keyword::YStart | Keyword::YEnd =>
-                PositionCategory::VerticalLogicalKeyword,
-            Keyword::Center =>
-                PositionCategory::OtherKeyword,
-        }
-    } else {
-        PositionCategory::LengthOrPercentage
+impl VerticalPosition {
+    #[inline]
+    /// Initial specified value for vertical position (`top` keyword).
+    pub fn top() -> VerticalPosition {
+        GenericVerticalPosition(PositionValue {
+            keyword: Some(Keyword::Top),
+            position: None,
+        })
     }
 }
-
-impl HasViewportPercentage for PositionComponent {
-    fn has_viewport_percentage(&self) -> bool {
-        match *self {
-            PositionComponent::Length(ref length) => length.has_viewport_percentage(),
-            _ => false
-        }
-    }
-}
-
-impl PositionComponent {
-    /// Convert the given position component to a length or a percentage.
-    #[inline]
-    pub fn to_length_or_percentage_computed(&self, cx: &Context) -> computed::LengthOrPercentage {
-        match *self {
-            PositionComponent::Length(ref value) => value.to_computed_value(cx),
-            PositionComponent::Keyword(keyword) => {
-                keyword.to_length_or_percentage().to_computed_value(cx)
-            }
-        }
-    }
-}
-
-impl Parse for PositionComponent {
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
-        input.try(|i| LengthOrPercentage::parse(context, i))
-            .map(PositionComponent::Length)
-            .or_else(|()| {
-                match try!(input.next()) {
-                    Token::Ident(value) => {
-                        match_ignore_ascii_case! { &value,
-                            "center" => Ok(PositionComponent::Keyword(Keyword::Center)),
-                            "left" => Ok(PositionComponent::Keyword(Keyword::Left)),
-                            "right" => Ok(PositionComponent::Keyword(Keyword::Right)),
-                            "top" => Ok(PositionComponent::Keyword(Keyword::Top)),
-                            "bottom" => Ok(PositionComponent::Keyword(Keyword::Bottom)),
-                            "x-start" => Ok(PositionComponent::Keyword(Keyword::XStart)),
-                            "x-end" => Ok(PositionComponent::Keyword(Keyword::XEnd)),
-                            "y-start" => Ok(PositionComponent::Keyword(Keyword::YStart)),
-                            "y-end" => Ok(PositionComponent::Keyword(Keyword::YEnd)),
-                            _ => Err(())
-                        }
-                    },
-                    _ => Err(())
-                }
-            })
-    }
-}
--- a/servo/tests/unit/style/properties/serialization.rs
+++ b/servo/tests/unit/style/properties/serialization.rs
@@ -794,18 +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::generics::position::{HorizontalPosition, Keyword, PositionValue, VerticalPosition};
         use style::values::specified::Image;
-        use style::values::specified::position::{HorizontalPosition, VerticalPosition, Keyword};
         use super::*;
 
         macro_rules! single_vec_value_typedef {
             ($name:ident, $path:expr) => {
                 $name::SpecifiedValue(
                     vec![$path]
                 )
             };
@@ -831,26 +831,26 @@ mod shorthand_serialization {
 
             let image = single_vec_value_typedef!(image,
                 image::single_value::SpecifiedValue::Image(
                     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,
-                HorizontalPosition {
+                HorizontalPosition(PositionValue {
                     keyword: None,
                     position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(7f32))),
-                }
+                })
             );
             let position_y = single_vec_value_typedef!(position_y,
-                VerticalPosition {
+                VerticalPosition(PositionValue {
                     keyword: Some(Keyword::Bottom),
                     position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(4f32))),
-                }
+                })
             );
 
             let size = single_vec_variant_value!(size,
                 size::single_value::SpecifiedValue::Explicit(
                     size::single_value::ExplicitSize {
                         width: LengthOrPercentageOrAuto::Length(NoCalcLength::from_px(70f32)),
                         height: LengthOrPercentageOrAuto::Length(NoCalcLength::from_px(50f32))
                     }
@@ -886,27 +886,27 @@ mod shorthand_serialization {
 
             let image = single_vec_value_typedef!(image,
                 image::single_value::SpecifiedValue::Image(
                     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,
-                HorizontalPosition {
+                HorizontalPosition(PositionValue {
                     keyword: None,
                     position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(7f32))),
-                }
+                })
             );
 
             let position_y = single_vec_value_typedef!(position_y,
-                VerticalPosition {
+                VerticalPosition(PositionValue  {
                     keyword: None,
                     position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(4f32))),
-                }
+                })
             );
 
             let size = single_vec_variant_value!(size,
                 size::single_value::SpecifiedValue::Explicit(
                     size::single_value::ExplicitSize {
                         width: LengthOrPercentageOrAuto::Length(NoCalcLength::from_px(70f32)),
                         height: LengthOrPercentageOrAuto::Length(NoCalcLength::from_px(50f32))
                     }