servo: Merge #16770 - Refactor Position (from nox:POSITION-DO-YOU-EVEN); r=Wafflespeanut,nox
authorAnthony Ramine <n.oxyde@gmail.com>
Wed, 10 May 2017 09:56:17 -0500
changeset 357689 98b33f5d5569cba631b8632b3dc4692b6d558aa7
parent 357688 e595d2a6c7f8a55b36fb6fcfc45540b3b7a775d6
child 357690 29d31be523f10817d9d2fa73544fcadeaa90592b
push id90186
push usercbook@mozilla.com
push dateThu, 11 May 2017 10:53:57 +0000
treeherdermozilla-inbound@24aa1cddef2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWafflespeanut, nox
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 #16770 - Refactor Position (from nox:POSITION-DO-YOU-EVEN); r=Wafflespeanut,nox Source-Repo: https://github.com/servo/servo Source-Revision: d5efed6c6a3e05f09300a3ed36d0e1254fcb407c
servo/components/layout/display_list_builder.rs
servo/components/style/gecko/conversions.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/helpers/animated_properties.mako.rs
servo/components/style/properties/longhand/background.mako.rs
servo/components/style/properties/longhand/box.mako.rs
servo/components/style/properties/longhand/svg.mako.rs
servo/components/style/properties/shorthand/background.mako.rs
servo/components/style/properties/shorthand/mask.mako.rs
servo/components/style/values/computed/position.rs
servo/components/style/values/generics/position.rs
servo/components/style/values/specified/basic_shape.rs
servo/components/style/values/specified/mod.rs
servo/components/style/values/specified/position.rs
servo/tests/unit/style/parsing/basic_shape.rs
servo/tests/unit/style/parsing/mod.rs
servo/tests/unit/style/parsing/position.rs
servo/tests/unit/style/parsing/value.rs
servo/tests/unit/style/properties/serialization.rs
servo/tests/unit/style/stylesheets.rs
--- a/servo/components/layout/display_list_builder.rs
+++ b/servo/components/layout/display_list_builder.rs
@@ -1003,19 +1003,19 @@ impl FragmentDisplayListBuilding for Fra
                     origin_y = Au(0);
                     (Au(0), Au(0))
                 }
             };
 
             let horiz_position = *get_cyclic(&background.background_position_x.0, index);
             let vert_position = *get_cyclic(&background.background_position_y.0, index);
             // Use `background-position` to get the offset.
-            let horizontal_position = model::specified(horiz_position.0,
+            let horizontal_position = model::specified(horiz_position,
                                                        bounds.size.width - image_size.width);
-            let vertical_position = model::specified(vert_position.0,
+            let vertical_position = model::specified(vert_position,
                                                      bounds.size.height - image_size.height);
 
             // The anchor position for this background, based on both the background-attachment
             // and background-position properties.
             let anchor_origin_x = border.left + virtual_origin_x + origin_x + horizontal_position;
             let anchor_origin_y = border.top + virtual_origin_y + origin_y + vertical_position;
 
             let mut tile_spacing = Size2D::zero();
@@ -1162,18 +1162,18 @@ impl FragmentDisplayListBuilding for Fra
     fn convert_radial_gradient(&self,
                                bounds: &Rect<Au>,
                                stops: &[GradientItem],
                                shape: &EndingShape,
                                center: &Position,
                                repeating: bool,
                                style: &ServoComputedValues)
                                -> display_list::RadialGradient {
-        let center = Point2D::new(specified(center.horizontal.0, bounds.size.width),
-                                  specified(center.vertical.0, bounds.size.height));
+        let center = Point2D::new(specified(center.horizontal, bounds.size.width),
+                                  specified(center.vertical, bounds.size.height));
         let radius = match *shape {
             EndingShape::Circle(LengthOrKeyword::Length(length))
                 => Size2D::new(length, length),
             EndingShape::Circle(LengthOrKeyword::Keyword(word))
                 => convert_circle_size_keyword(word, &bounds.size, &center),
             EndingShape::Ellipse(LengthOrPercentageOrKeyword::LengthOrPercentage(horizontal,
                                                                                     vertical))
                     => Size2D::new(specified(horizontal, bounds.size.width),
--- a/servo/components/style/gecko/conversions.rs
+++ b/servo/components/style/gecko/conversions.rs
@@ -302,18 +302,18 @@ impl nsStyleImage {
                             unsafe {
                                 (*gecko_gradient).mRadiusX.set(first_len);
                                 (*gecko_gradient).mRadiusY.set(second_len);
                             }
                         }
                     },
                 }
                 unsafe {
-                    (*gecko_gradient).mBgPosX.set(position.horizontal.0);
-                    (*gecko_gradient).mBgPosY.set(position.vertical.0);
+                    (*gecko_gradient).mBgPosX.set(position.horizontal);
+                    (*gecko_gradient).mBgPosY.set(position.vertical);
                 }
 
                 gecko_gradient
             },
         };
 
         for (index, item) in gradient.items.iter().enumerate() {
             // NB: stops are guaranteed to be none in the gecko side by
@@ -367,17 +367,16 @@ pub mod basic_shape {
     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]);
@@ -478,36 +477,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.0.into(),
-                mYPosition: other.vertical.0.into()
+                mXPosition: other.horizontal.into(),
+                mYPosition: other.vertical.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: HorizontalPosition(other.mXPosition.into()),
-                vertical: VerticalPosition(other.mYPosition.into()),
+                horizontal: other.mXPosition.into(),
+                vertical: other.mYPosition.into(),
             }
         }
     }
 
     impl From<ShapeBox> for StyleGeometryBox {
         fn from(reference: ShapeBox) -> Self {
             use gecko_bindings::structs::StyleGeometryBox::*;
             match reference {
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -362,27 +362,26 @@ 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.0.into()")}
-        ${set_gecko_property("%s.mYPosition" % gecko_ffi_name, "v.vertical.0.into()")}
+        ${set_gecko_property("%s.mXPosition" % gecko_ffi_name, "v.horizontal.into()")}
+        ${set_gecko_property("%s.mYPosition" % gecko_ffi_name, "v.vertical.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::generics::position::{HorizontalPosition, Position, VerticalPosition};
-            Position {
-                horizontal: HorizontalPosition(self.gecko.${gecko_ffi_name}.mXPosition.into()),
-                vertical: VerticalPosition(self.gecko.${gecko_ffi_name}.mYPosition.into()),
+            longhands::${ident}::computed_value::T {
+                horizontal: self.gecko.${gecko_ffi_name}.mXPosition.into(),
+                vertical: 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>
@@ -2035,18 +2034,18 @@ fn static_assert() {
               I::IntoIter: ExactSizeIterator
     {
         let v = v.into_iter();
 
         unsafe { self.gecko.mScrollSnapCoordinate.set_len_pod(v.len() as u32); }
         for (gecko, servo) in self.gecko.mScrollSnapCoordinate
                                .iter_mut()
                                .zip(v) {
-            gecko.mXPosition = servo.horizontal.0.into();
-            gecko.mYPosition = servo.vertical.0.into();
+            gecko.mXPosition = servo.horizontal.into();
+            gecko.mYPosition = servo.vertical.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);
         }
@@ -2721,46 +2720,45 @@ fn static_assert() {
             % if shorthand == "mask":
             T::fill_box => StyleGeometryBox::FillBox,
             T::stroke_box => StyleGeometryBox::StrokeBox,
             T::view_box => StyleGeometryBox::ViewBox,
             % endif
         }
     </%self:simple_image_array_property>
 
-    % for orientation in [("x", "Horizontal"), ("y", "Vertical")]:
-    pub fn copy_${shorthand}_position_${orientation[0]}_from(&mut self, other: &Self) {
+    % for orientation in ["x", "y"]:
+    pub fn copy_${shorthand}_position_${orientation}_from(&mut self, other: &Self) {
         use gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType;
 
-        self.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count
-            = cmp::min(1, other.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count);
+        self.gecko.${image_layers_field}.mPosition${orientation.upper()}Count
+            = cmp::min(1, other.gecko.${image_layers_field}.mPosition${orientation.upper()}Count);
         self.gecko.${image_layers_field}.mLayers.mFirstElement.mPosition =
             other.gecko.${image_layers_field}.mLayers.mFirstElement.mPosition;
         unsafe {
             Gecko_EnsureImageLayersLength(&mut self.gecko.${image_layers_field},
                                           other.gecko.${image_layers_field}.mLayers.len(),
                                           LayerType::${shorthand.capitalize()});
         }
 
         for (layer, other) in self.gecko.${image_layers_field}.mLayers.iter_mut()
                                   .zip(other.gecko.${image_layers_field}.mLayers.iter()) {
-            layer.mPosition.m${orientation[0].upper()}Position
-                = other.mPosition.m${orientation[0].upper()}Position;
+            layer.mPosition.m${orientation.upper()}Position
+                = other.mPosition.m${orientation.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::generics::position::${orientation[1]}Position;
-        longhands::${shorthand}_position_${orientation[0]}::computed_value::T(
+        self.gecko.${image_layers_field}.mPosition${orientation.upper()}Count
+               = other.gecko.${image_layers_field}.mPosition${orientation.upper()}Count;
+    }
+
+    pub fn clone_${shorthand}_position_${orientation}(&self)
+        -> longhands::${shorthand}_position_${orientation}::computed_value::T {
+        longhands::${shorthand}_position_${orientation}::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()))
+                .take(self.gecko.${image_layers_field}.mPosition${orientation.upper()}Count as usize)
+                .map(|position| position.mPosition.m${orientation.upper()}Position.into())
                 .collect()
         )
     }
 
     pub fn set_${shorthand}_position_${orientation[0]}<I>(&mut self,
                                      v: I)
         where I: IntoIterator<Item = longhands::${shorthand}_position_${orientation[0]}
                                               ::computed_value::single_value::T>,
@@ -2773,17 +2771,17 @@ fn static_assert() {
         unsafe {
             Gecko_EnsureImageLayersLength(&mut self.gecko.${image_layers_field}, v.len(),
                                         LayerType::${shorthand.capitalize()});
         }
 
         self.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count = v.len() as u32;
         for (servo, geckolayer) in v.zip(self.gecko.${image_layers_field}
                                                            .mLayers.iter_mut()) {
-            geckolayer.mPosition.m${orientation[0].upper()}Position = servo.0.into();
+            geckolayer.mPosition.m${orientation[0].upper()}Position = servo.into();
         }
     }
     % endfor
 
     <%self:simple_image_array_property name="size" shorthand="${shorthand}" field_name="mSize">
         use gecko_bindings::structs::nsStyleImageLayers_Size_Dimension;
         use gecko_bindings::structs::nsStyleImageLayers_Size_DimensionType;
         use gecko_bindings::structs::{nsStyleCoord_CalcValue, nsStyleImageLayers_Size};
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -36,17 +36,16 @@ use std::fmt;
 use style_traits::ToCss;
 use super::ComputedValues;
 use values::CSSFloat;
 use values::{Auto, Either, generics};
 use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone};
 use values::computed::{BorderRadiusSize, ClipRect};
 use values::computed::{CalcLengthOrPercentage, Context, LengthOrPercentage};
 use values::computed::{MaxLength, MinLength};
-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.
@@ -650,16 +649,17 @@ pub trait Animatable: Sized {
     fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
         self.compute_distance(other).map(|d| d * d)
     }
 }
 
 /// https://drafts.csswg.org/css-transitions/#animtype-repeatable-list
 pub trait RepeatableListAnimatable: Animatable {}
 
+impl RepeatableListAnimatable for LengthOrPercentage {}
 impl RepeatableListAnimatable for Either<f32, LengthOrPercentage> {}
 
 impl<T: RepeatableListAnimatable> Animatable for SmallVec<[T; 1]> {
     fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
         use num_integer::lcm;
         let len = lcm(self.len(), other.len());
         self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(me, you)| {
             me.interpolate(you, progress)
@@ -1331,56 +1331,16 @@ impl<H: Animatable, V: Animatable> Anima
         Ok(try!(self.horizontal.compute_squared_distance(&other.horizontal)) +
            try!(self.vertical.compute_squared_distance(&other.vertical)))
     }
 }
 
 impl<H, V> RepeatableListAnimatable for generic_position::Position<H, V>
     where H: RepeatableListAnimatable, V: RepeatableListAnimatable {}
 
-/// https://drafts.csswg.org/css-transitions/#animtype-simple-list
-impl Animatable for HorizontalPosition {
-    #[inline]
-    fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
-        self.0.interpolate(&other.0, progress).map(generic_position::HorizontalPosition)
-    }
-
-    #[inline]
-    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
-        self.0.compute_distance(&other.0)
-    }
-
-    #[inline]
-    fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
-        self.0.compute_squared_distance(&other.0)
-    }
-}
-
-impl RepeatableListAnimatable for HorizontalPosition {}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-simple-list
-impl Animatable for VerticalPosition {
-    #[inline]
-    fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
-        self.0.interpolate(&other.0, progress).map(generic_position::VerticalPosition)
-    }
-
-    #[inline]
-    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
-        self.0.compute_distance(&other.0)
-    }
-
-    #[inline]
-    fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
-        self.0.compute_squared_distance(&other.0)
-    }
-}
-
-impl RepeatableListAnimatable for VerticalPosition {}
-
 /// https://drafts.csswg.org/css-transitions/#animtype-rect
 impl Animatable for ClipRect {
     #[inline]
     fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> {
         Ok(ClipRect {
             top: try!(self.top.interpolate(&other.top, time)),
             right: try!(self.right.interpolate(&other.right, time)),
             bottom: try!(self.bottom.interpolate(&other.bottom, time)),
--- a/servo/components/style/properties/longhand/background.mako.rs
+++ b/servo/components/style/properties/longhand/background.mako.rs
@@ -15,48 +15,23 @@
 ${helpers.predefined_type("background-image", "LayerImage",
     initial_value="computed_value::T(None)",
     initial_specified_value="SpecifiedValue(None)",
     spec="https://drafts.csswg.org/css-backgrounds/#the-background-image",
     vector="True",
     animation_value_type="none",
     has_uncacheable_values="True" if product == "gecko" else "False")}
 
-<%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"
-                          animation_value_type="ComputedValue" vector="True" delegate_animate="True">
-    #[inline]
-    /// 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(PositionValue {
-            keyword: None,
-            position: Some(LengthOrPercentage::Percentage(Percentage(0.0))),
-        })
-    }
-</%helpers:predefined_type>
-
-<%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(PositionValue {
-            keyword: None,
-            position: Some(LengthOrPercentage::Percentage(Percentage(0.0))),
-        })
-    }
-</%helpers:predefined_type>
+% for (axis, direction, initial) in [("x", "Horizontal", "left"), ("y", "Vertical", "top")]:
+    ${helpers.predefined_type("background-position-" + axis, "position::" + direction + "Position",
+                              initial_value="computed::LengthOrPercentage::zero()",
+                              initial_specified_value="SpecifiedValue::initial_specified_value()",
+                              spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-" + axis,
+                              animation_value_type="ComputedValue", vector=True, delegate_animate=True)}
+% endfor
 
 <%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/box.mako.rs
+++ b/servo/components/style/properties/longhand/box.mako.rs
@@ -2138,21 +2138,21 @@
                           "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.predefined_type("perspective-origin",
-                          "position::OriginPosition",
-                          "computed::position::OriginPosition::center()",
+                          "position::Position",
+                          "computed::position::Position::center()",
                           boxed="True",
                           extra_prefixes="moz webkit",
-                          spec="https://drafts.csswg.org/css-transforms/#perspective-origin-property",
+                          spec="https://drafts.csswg.org/css-transforms-2/#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")}
 
--- a/servo/components/style/properties/longhand/svg.mako.rs
+++ b/servo/components/style/properties/longhand/svg.mako.rs
@@ -85,59 +85,24 @@
     }
 
     #[inline]
     pub fn get_initial_specified_value() -> SpecifiedValue {
         SpecifiedValue::Other(RepeatKeyword::NoRepeat, None)
     }
 </%helpers:vector_longhand>
 
-<%helpers:vector_longhand name="mask-position-x" products="gecko"
-                          animation_value_type="ComputedValue" extra_prefixes="webkit"
-                          spec="https://drafts.fxtf.org/css-masking/#propdef-mask-position">
-    pub use properties::longhands::background_position_x::single_value::get_initial_value;
-    pub use properties::longhands::background_position_x::single_value::get_initial_position_value;
-    pub use properties::longhands::background_position_x::single_value::get_initial_specified_value;
-    pub use properties::longhands::background_position_x::single_value::parse;
-    pub use properties::longhands::background_position_x::single_value::SpecifiedValue;
-    pub use properties::longhands::background_position_x::single_value::computed_value;
-    use properties::animated_properties::{Animatable, RepeatableListAnimatable};
-    use properties::longhands::mask_position_x::computed_value::T as MaskPositionX;
-
-    impl Animatable for MaskPositionX {
-        #[inline]
-        fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
-            Ok(MaskPositionX(try!(self.0.interpolate(&other.0, progress))))
-        }
-    }
-
-    impl RepeatableListAnimatable for MaskPositionX {}
-</%helpers:vector_longhand>
-
-<%helpers:vector_longhand name="mask-position-y" products="gecko"
-                          animation_value_type="ComputedValue" extra_prefixes="webkit"
-                          spec="https://drafts.fxtf.org/css-masking/#propdef-mask-position">
-    pub use properties::longhands::background_position_y::single_value::get_initial_value;
-    pub use properties::longhands::background_position_y::single_value::get_initial_position_value;
-    pub use properties::longhands::background_position_y::single_value::get_initial_specified_value;
-    pub use properties::longhands::background_position_y::single_value::parse;
-    pub use properties::longhands::background_position_y::single_value::SpecifiedValue;
-    pub use properties::longhands::background_position_y::single_value::computed_value;
-    use properties::animated_properties::{Animatable, RepeatableListAnimatable};
-    use properties::longhands::mask_position_y::computed_value::T as MaskPositionY;
-
-    impl Animatable for MaskPositionY {
-        #[inline]
-        fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
-            Ok(MaskPositionY(try!(self.0.interpolate(&other.0, progress))))
-        }
-    }
-
-    impl RepeatableListAnimatable for MaskPositionY {}
-</%helpers:vector_longhand>
+% for (axis, direction) in [("x", "Horizontal"), ("y", "Vertical")]:
+    ${helpers.predefined_type("mask-position-" + axis, "position::" + direction + "Position",
+                              products="gecko", extra_prefixes="webkit",
+                              initial_value="computed::LengthOrPercentage::zero()",
+                              initial_specified_value="specified::PositionComponent::Center",
+                              spec="https://drafts.fxtf.org/css-masking/#propdef-mask-position",
+                              animation_value_type="ComputedValue", vector=True, delegate_animate=True)}
+% endfor
 
 ${helpers.single_keyword("mask-clip",
                          "border-box content-box padding-box",
                          extra_gecko_values="fill-box stroke-box view-box no-clip",
                          vector=True,
                          products="gecko",
                          extra_prefixes="webkit",
                          animation_value_type="none",
--- a/servo/components/style/properties/shorthand/background.mako.rs
+++ b/servo/components/style/properties/shorthand/background.mako.rs
@@ -10,17 +10,17 @@
                                     background-attachment background-image background-size background-origin
                                     background-clip"
                     spec="https://drafts.csswg.org/css-backgrounds/#the-background">
     use properties::longhands::{background_color, background_position_x, background_position_y, background_repeat};
     use properties::longhands::{background_attachment, background_image, background_size, background_origin};
     use properties::longhands::background_clip;
     use properties::longhands::background_clip::single_value::computed_value::T as Clip;
     use properties::longhands::background_origin::single_value::computed_value::T as Origin;
-    use values::specified::position::Position;
+    use values::specified::{Position, PositionComponent};
     use parser::Parse;
 
     impl From<background_origin::single_value::SpecifiedValue> for background_clip::single_value::SpecifiedValue {
         fn from(origin: background_origin::single_value::SpecifiedValue) ->
             background_clip::single_value::SpecifiedValue {
             match origin {
                 background_origin::single_value::SpecifiedValue::content_box =>
                     background_clip::single_value::SpecifiedValue::content_box,
@@ -34,33 +34,32 @@
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         let mut background_color = None;
 
         % for name in "image position_x position_y repeat size attachment origin clip".split():
             let mut background_${name} = background_${name}::SpecifiedValue(Vec::new());
         % endfor
         try!(input.parse_comma_separated(|input| {
-            % for name in "image position_x position_y repeat size attachment origin clip".split():
+            % for name in "image position repeat size attachment origin clip".split():
                 let mut ${name} = None;
             % endfor
             loop {
                 if let Ok(value) = input.try(|input| background_color::parse(context, input)) {
                     if background_color.is_none() {
                         background_color = Some(value);
                         continue
                     } else {
                         // color can only be the last element
                         return Err(())
                     }
                 }
-                if position_x.is_none() && position_y.is_none() {
+                if position.is_none() {
                     if let Ok(value) = input.try(|input| Position::parse(context, input)) {
-                        position_x = Some(value.horizontal);
-                        position_y = Some(value.vertical);
+                        position = Some(value);
 
                         // Parse background size, if applicable.
                         size = input.try(|input| {
                             try!(input.expect_delim('/'));
                             background_size::single_value::parse(context, input)
                         }).ok();
 
                         continue
@@ -78,32 +77,27 @@
                 break
             }
             if clip.is_none() {
                 if let Some(origin) = origin {
                     clip = Some(background_clip::single_value::SpecifiedValue::from(origin));
                 }
             }
             let mut any = false;
-            % for name in "image position_x position_y repeat size attachment origin clip".split():
+            % for name in "image position repeat size attachment origin clip".split():
                 any = any || ${name}.is_some();
             % endfor
             any = any || background_color.is_some();
             if any {
-                if position_x.is_some() || position_y.is_some() {
-                    % for name in "position_x position_y".split():
-                        if let Some(bg_${name}) = ${name} {
-                            background_${name}.0.push(bg_${name});
-                        }
-                    % endfor
+                if let Some(position) = position {
+                    background_position_x.0.push(position.horizontal);
+                    background_position_y.0.push(position.vertical);
                 } else {
-                    % for name in "position_x position_y".split():
-                        background_${name}.0.push(background_${name}::single_value
-                                                                    ::get_initial_position_value());
-                    % endfor
+                    background_position_x.0.push(PositionComponent::zero());
+                    background_position_y.0.push(PositionComponent::zero());
                 }
                 % for name in "image repeat size attachment origin clip".split():
                     if let Some(bg_${name}) = ${name} {
                         background_${name}.0.push(bg_${name});
                     } else {
                         background_${name}.0.push(background_${name}::single_value
                                                                     ::get_initial_specified_value());
                     }
@@ -188,37 +182,32 @@
             Ok(())
         }
     }
 </%helpers:shorthand>
 
 <%helpers:shorthand name="background-position"
                     sub_properties="background-position-x background-position-y"
                     spec="https://drafts.csswg.org/css-backgrounds-4/#the-background-position">
-    use properties::longhands::{background_position_x,background_position_y};
+    use properties::longhands::{background_position_x, background_position_y};
     use values::specified::AllowQuirks;
     use values::specified::position::Position;
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         let mut position_x = background_position_x::SpecifiedValue(Vec::new());
         let mut position_y = background_position_y::SpecifiedValue(Vec::new());
         let mut any = false;
 
-        try!(input.parse_comma_separated(|input| {
-            loop {
-                if let Ok(value) = input.try(|input| Position::parse_quirky(context, input, AllowQuirks::Yes)) {
-                    position_x.0.push(value.horizontal);
-                    position_y.0.push(value.vertical);
-                    any = true;
-                    continue
-                }
-                break
-            }
+        input.parse_comma_separated(|input| {
+            let value = Position::parse_quirky(context, input, AllowQuirks::Yes)?;
+            position_x.0.push(value.horizontal);
+            position_y.0.push(value.vertical);
+            any = true;
             Ok(())
-        }));
+        })?;
         if !any {
             return Err(());
         }
 
         Ok(Longhands {
             background_position_x: position_x,
             background_position_y: position_y,
         })
--- a/servo/components/style/properties/shorthand/mask.mako.rs
+++ b/servo/components/style/properties/shorthand/mask.mako.rs
@@ -6,17 +6,17 @@
 
 <%helpers:shorthand name="mask" products="gecko" extra_prefixes="webkit"
                     sub_properties="mask-mode mask-repeat mask-clip mask-origin mask-composite mask-position-x
                                     mask-position-y mask-size mask-image"
                     spec="https://drafts.fxtf.org/css-masking/#propdef-mask">
     use properties::longhands::{mask_mode, mask_repeat, mask_clip, mask_origin, mask_composite, mask_position_x,
                                 mask_position_y};
     use properties::longhands::{mask_size, mask_image};
-    use values::specified::position::Position;
+    use values::specified::{Position, PositionComponent};
     use parser::Parse;
 
     impl From<mask_origin::single_value::SpecifiedValue> for mask_clip::single_value::SpecifiedValue {
         fn from(origin: mask_origin::single_value::SpecifiedValue) -> mask_clip::single_value::SpecifiedValue {
             match origin {
                 mask_origin::single_value::SpecifiedValue::content_box =>
                     mask_clip::single_value::SpecifiedValue::content_box,
                 mask_origin::single_value::SpecifiedValue::padding_box =>
@@ -36,31 +36,30 @@
     }
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         % for name in "image mode position_x position_y size repeat origin clip composite".split():
             let mut mask_${name} = mask_${name}::SpecifiedValue(Vec::new());
         % endfor
 
         try!(input.parse_comma_separated(|input| {
-            % for name in "image mode position_x position_y size repeat origin clip composite".split():
+            % for name in "image mode position size repeat origin clip composite".split():
                 let mut ${name} = None;
             % endfor
             loop {
                 if image.is_none() {
                     if let Ok(value) = input.try(|input| mask_image::single_value
                                                                    ::parse(context, input)) {
                         image = Some(value);
                         continue
                     }
                 }
-                if position_x.is_none() && position_y.is_none() {
+                if position.is_none() {
                     if let Ok(value) = input.try(|input| Position::parse(context, input)) {
-                        position_x = Some(value.horizontal);
-                        position_y = Some(value.vertical);
+                        position = Some(value);
 
                         // Parse mask size, if applicable.
                         size = input.try(|input| {
                             try!(input.expect_delim('/'));
                             mask_size::single_value::parse(context, input)
                         }).ok();
 
                         continue
@@ -78,31 +77,26 @@
                 break
             }
             if clip.is_none() {
                 if let Some(origin) = origin {
                     clip = Some(mask_clip::single_value::SpecifiedValue::from(origin));
                 }
             }
             let mut any = false;
-            % for name in "image mode position_x position_y size repeat origin clip composite".split():
+            % for name in "image mode position size repeat origin clip composite".split():
                 any = any || ${name}.is_some();
             % endfor
             if any {
-                if position_x.is_some() || position_y.is_some() {
-                    % for name in "position_x position_y".split():
-                        if let Some(bg_${name}) = ${name} {
-                            mask_${name}.0.push(bg_${name});
-                        }
-                    % endfor
+                if let Some(position) = position {
+                    mask_position_x.0.push(position.horizontal);
+                    mask_position_y.0.push(position.vertical);
                 } else {
-                    % for name in "position_x position_y".split():
-                        mask_${name}.0.push(mask_${name}::single_value
-                                            ::get_initial_position_value());
-                    % endfor
+                    mask_position_x.0.push(PositionComponent::zero());
+                    mask_position_y.0.push(PositionComponent::zero());
                 }
                 % for name in "image mode size repeat origin clip composite".split():
                     if let Some(m_${name}) = ${name} {
                         mask_${name}.0.push(m_${name});
                     } else {
                         mask_${name}.0.push(mask_${name}::single_value
                                                         ::get_initial_specified_value());
                     }
@@ -186,28 +180,23 @@
     use values::specified::position::Position;
     use parser::Parse;
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         let mut position_x = mask_position_x::SpecifiedValue(Vec::new());
         let mut position_y = mask_position_y::SpecifiedValue(Vec::new());
         let mut any = false;
 
-        try!(input.parse_comma_separated(|input| {
-            loop {
-                if let Ok(value) = input.try(|input| Position::parse(context, input)) {
-                    position_x.0.push(value.horizontal);
-                    position_y.0.push(value.vertical);
-                    any = true;
-                    continue
-                }
-                break
-            }
+        input.parse_comma_separated(|input| {
+            let value = Position::parse(context, input)?;
+            position_x.0.push(value.horizontal);
+            position_y.0.push(value.vertical);
+            any = true;
             Ok(())
-        }));
+        })?;
         if any == false {
             return Err(());
         }
 
         Ok(Longhands {
             mask_position_x: position_x,
             mask_position_y: position_y,
         })
--- a/servo/components/style/values/computed/position.rs
+++ b/servo/components/style/values/computed/position.rs
@@ -5,77 +5,40 @@
 //! CSS handling for the computed value of
 //! [`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;
+use values::generics::position::Position as GenericPosition;
 
 /// 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 {}
+pub type Position = GenericPosition<HorizontalPosition, VerticalPosition>;
 
-impl OriginPosition {
-    #[inline]
-    /// The initial value for `perspective-origin`
-    pub fn center() -> OriginPosition {
-        GenericPosition {
-            horizontal: LengthOrPercentage::Percentage(0.5),
-            vertical: LengthOrPercentage::Percentage(0.5),
-        }
-    }
-}
+/// The computed value of a CSS horizontal position.
+pub type HorizontalPosition = LengthOrPercentage;
+
+/// The computed value of a CSS vertical position.
+pub type VerticalPosition = LengthOrPercentage;
 
 impl Position {
+    /// `50% 50%`
     #[inline]
-    /// Construct a position at (0, 0)
+    pub fn center() -> Self {
+        Self::new(LengthOrPercentage::Percentage(0.5), LengthOrPercentage::Percentage(0.5))
+    }
+
+    /// `0% 0%`
+    #[inline]
     pub fn zero() -> Self {
-        Position {
-            horizontal: GenericHorizontalPosition(LengthOrPercentage::zero()),
-            vertical: GenericVerticalPosition(LengthOrPercentage::zero()),
-        }
+        Self::new(LengthOrPercentage::zero(), LengthOrPercentage::zero())
     }
 }
 
 impl ToCss for Position {
     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)
     }
 }
-
-/// The computed value of a horizontal `<position>`
-pub type HorizontalPosition = GenericHorizontalPosition<LengthOrPercentage>;
-
-impl Copy for HorizontalPosition {}
-
-impl HorizontalPosition {
-    #[inline]
-    /// Create a zero position value.
-    pub fn zero() -> HorizontalPosition {
-        GenericHorizontalPosition(LengthOrPercentage::Percentage(0.0))
-    }
-}
-
-/// The computed value of a vertical `<position>`
-pub type VerticalPosition = GenericVerticalPosition<LengthOrPercentage>;
-
-impl Copy for VerticalPosition {}
-
-impl VerticalPosition {
-    #[inline]
-    /// Create a zero position value.
-    pub fn zero() -> VerticalPosition {
-        GenericVerticalPosition(LengthOrPercentage::Percentage(0.0))
-    }
-}
--- a/servo/components/style/values/generics/position.rs
+++ b/servo/components/style/values/generics/position.rs
@@ -1,302 +1,57 @@
 /* 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())
-    }
-}
+use values::computed::{Context, ToComputedValue};
 
-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)]
+#[derive(Clone, Copy, Debug, 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, V> Position<H, V> {
+    /// Returns a new position.
+    pub fn new(horizontal: H, vertical: V) -> Self {
+        Self {
+            horizontal: horizontal,
+            vertical: vertical,
+        }
+    }
+}
 
 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>;
+    type ComputedValue = Position<<H as ToComputedValue>::ComputedValue,
+                                  <V as ToComputedValue>::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 {
+        Self {
             horizontal: ToComputedValue::from_computed_value(&computed.horizontal),
             vertical: ToComputedValue::from_computed_value(&computed.vertical),
         }
     }
 }
--- a/servo/components/style/values/specified/basic_shape.rs
+++ b/servo/components/style/values/specified/basic_shape.rs
@@ -6,26 +6,27 @@
 //! [`basic-shape`][basic-shape]s
 //!
 //! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape
 
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
 use properties::shorthands::parse_four_sides;
 use std::ascii::AsciiExt;
+use std::borrow::Cow;
 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::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::{LengthOrPercentage, Percentage, Position, PositionComponent};
+use values::specified::position::Side;
 
 /// The specified value used by `clip-path`
 pub type ShapeWithGeometryBox = ShapeSource<BasicShape, GeometryBox>;
 
 /// The specified value used by `shape-outside`
 pub type ShapeWithShapeBox = ShapeSource<BasicShape, ShapeBox>;
 
 #[derive(Clone, PartialEq, Debug)]
@@ -133,85 +134,72 @@ impl Parse for InsetRect {
 ///
 /// 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
 {
-    // 0 length should be replaced with 0%
-    fn replace_with_percent(input: LengthOrPercentage) -> LengthOrPercentage {
-        match input {
-            LengthOrPercentage::Length(ref l) if l.is_zero() =>
-                LengthOrPercentage::Percentage(Percentage(0.0)),
-            _ => input
+    fn to_keyword_and_lop<S>(component: &PositionComponent<S>) -> (S, Cow<LengthOrPercentage>)
+        where S: Copy + Side
+    {
+        match *component {
+            PositionComponent::Center => {
+                (S::start(), Cow::Owned(LengthOrPercentage::Percentage(Percentage(0.5))))
+            },
+            PositionComponent::Side(keyword, None) => {
+                // left | top => 0%
+                // right | bottom => 100%
+                let p = if keyword.is_start() { 0. } else { 1. };
+                (S::start(), Cow::Owned(LengthOrPercentage::Percentage(Percentage(p))))
+            },
+            PositionComponent::Side(keyword, Some(ref lop)) if !keyword.is_start() => {
+                if let LengthOrPercentage::Percentage(p) = *to_non_zero_length(lop) {
+                    (S::start(), Cow::Owned(LengthOrPercentage::Percentage(Percentage(1. - p.0))))
+                } else {
+                    (keyword, Cow::Borrowed(lop))
+                }
+            },
+            PositionComponent::Length(ref lop) |
+            PositionComponent::Side(_, Some(ref lop)) => {
+                (S::start(), to_non_zero_length(lop))
+            },
         }
     }
 
-    // keyword-percentage pairs can be folded into a single percentage
-    fn fold_keyword(keyword: Option<Keyword>,
-                    length: Option<LengthOrPercentage>) -> Option<LengthOrPercentage> {
-        let is_length_none = length.is_none();
-        let pc = match length.map(replace_with_percent) {
-            Some(LengthOrPercentage::Percentage(pc)) => pc,
-            None => Percentage(0.0),        // unspecified length = 0%
-            _ => return None
-        };
-
-        let percent = match keyword {
-            Some(Keyword::Center) => {
-                assert!(is_length_none);        // center cannot pair with lengths
-                Percentage(0.5)
+    fn to_non_zero_length(lop: &LengthOrPercentage) -> Cow<LengthOrPercentage> {
+        match *lop {
+            LengthOrPercentage::Length(ref l) if l.is_zero() => {
+                Cow::Owned(LengthOrPercentage::Percentage(Percentage(0.)))
             },
-            Some(Keyword::Left) | Some(Keyword::Top) | None => pc,
-            Some(Keyword::Right) | Some(Keyword::Bottom) => Percentage(1.0 - pc.0),
-            _ => return None,
-        };
-
-        Some(LengthOrPercentage::Percentage(percent))
-    }
-
-    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.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.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.
-            if let (Some(x), Some(y)) = (fold_keyword(hk, hp.clone()),
-                                         fold_keyword(vk, vp.clone())) {
-                serialize_position_pair(x, y, dest)
-            } else {
-                // We failed to reduce it to a two-value form,
-                // so we expand it to 4-value
-                let zero = LengthOrPercentage::Percentage(Percentage(0.0));
-                hk.unwrap_or(Keyword::Left).to_css(dest)?;
-                dest.write_str(" ")?;
-                replace_with_percent(hp.unwrap_or(zero.clone())).to_css(dest)?;
-                dest.write_str(" ")?;
-                vk.unwrap_or(Keyword::Top).to_css(dest)?;
-                dest.write_str(" ")?;
-                replace_with_percent(vp.unwrap_or(zero)).to_css(dest)
+            _ => {
+                Cow::Borrowed(lop)
             }
         }
     }
+
+    fn write_pair<A, B, W>(a: &A, b: &B, dest: &mut W) -> fmt::Result
+        where A: ToCss, B: ToCss, W: fmt::Write
+    {
+        a.to_css(dest)?;
+        dest.write_str(" ")?;
+        b.to_css(dest)
+    }
+
+    let (x_pos, x_lop) = to_keyword_and_lop(&position.horizontal);
+    let (y_pos, y_lop) = to_keyword_and_lop(&position.vertical);
+
+    if x_pos.is_start() && y_pos.is_start() {
+        return write_pair(&*x_lop, &*y_lop, dest);
+    }
+
+    write_pair(&x_pos, &*x_lop, dest)?;
+    dest.write_str(" ")?;
+    write_pair(&y_pos, &*y_lop, dest)
 }
 
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 /// https://drafts.csswg.org/css-shapes/#funcdef-circle
 #[allow(missing_docs)]
 pub struct Circle {
     pub radius: ShapeRadius,
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -31,17 +31,17 @@ pub use self::grid::{GridLine, TrackKeyw
 pub use self::image::{AngleOrCorner, ColorStop, EndingShape as GradientEndingShape, Gradient};
 pub use self::image::{GradientItem, GradientKind, HorizontalDirection, Image, ImageRect, LayerImage};
 pub use self::image::{LengthOrKeyword, LengthOrPercentageOrKeyword, SizeKeyword, VerticalDirection};
 pub use self::length::AbsoluteLength;
 pub use self::length::{FontRelativeLength, ViewportPercentageLength, CharacterWidth, Length, CalcLengthOrPercentage};
 pub use self::length::{Percentage, LengthOrNone, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
 pub use self::length::{LengthOrPercentageOrNone, LengthOrPercentageOrAutoOrContent, NoCalcLength};
 pub use self::length::{MaxLength, MinLength};
-pub use self::position::{HorizontalPosition, Position, VerticalPosition};
+pub use self::position::{Position, PositionComponent};
 
 #[cfg(feature = "gecko")]
 pub mod align;
 pub mod basic_shape;
 pub mod calc;
 pub mod color;
 pub mod grid;
 pub mod image;
--- a/servo/components/style/values/specified/position.rs
+++ b/servo/components/style/values/specified/position.rs
@@ -2,283 +2,302 @@
  * 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 specified value of
 //! [`position`][position]s
 //!
 //! [position]: https://drafts.csswg.org/css-backgrounds-3/#position
 
-use app_units::Au;
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
-use properties::longhands::parse_origin;
-use std::mem;
-use values::Either;
-use values::computed::{CalcLengthOrPercentage, Context};
+use std::fmt;
+use style_traits::ToCss;
+use values::HasViewportPercentage;
+use values::computed::{CalcLengthOrPercentage, ComputedValueAsSpecified, 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::generics::position::Position as GenericPosition;
 use values::specified::{AllowQuirks, LengthOrPercentage, Percentage};
 
-pub use values::generics::position::Keyword;
-
 /// The specified value of a CSS `<position>`
-pub type Position = PositionWithKeyword<PositionValue<LengthOrPercentage>>;
+pub type Position = GenericPosition<HorizontalPosition, VerticalPosition>;
 
-/// The specified value for `<position>` values without a keyword.
-pub type OriginPosition = GenericPosition<LengthOrPercentage, LengthOrPercentage>;
+/// The specified value of a horizontal position.
+pub type HorizontalPosition = PositionComponent<X>;
+
+/// The specified value of a vertical position.
+pub type VerticalPosition = PositionComponent<Y>;
 
-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))),
-            })
-        }
-    }
+/// The specified value of a component of a CSS `<position>`.
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Debug, PartialEq)]
+pub enum PositionComponent<S> {
+    /// `center`
+    Center,
+    /// `<lop>`
+    Length(LengthOrPercentage),
+    /// `<side> <lop>?`
+    Side(S, Option<LengthOrPercentage>),
 }
 
-type PositionComponent = Either<LengthOrPercentage, Keyword>;
-
-impl 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(Either::Second(Keyword::Left));
-        let second_key = second_keyword.clone().unwrap_or(Either::Second(Keyword::Top));
-
-        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.
-
-            // FIXME(canaltinova): Allow logical keywords for Position. They are not in current spec yet.
-            (&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.
-            (&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),
-        };
+define_css_keyword_enum! { X:
+    "left" => Left,
+    "right" => Right,
+}
+add_impls_for_keyword_enum!(X);
 
-        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);
-        }
-
-        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: 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: GenericHorizontalPosition(PositionValue {
-                keyword: Some(Keyword::Center),
-                position: None,
-            }),
-            vertical: GenericVerticalPosition(PositionValue {
-                keyword: Some(Keyword::Center),
-                position: None,
-            }),
-        }
-    }
+define_css_keyword_enum! { Y:
+    "top" => Top,
+    "bottom" => Bottom,
 }
+add_impls_for_keyword_enum!(Y);
 
 impl Parse for Position {
     fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
-        Position::parse_quirky(context, input, AllowQuirks::No)
+        Self::parse_quirky(context, input, AllowQuirks::No)
     }
 }
 
 impl Position {
-    /// Parses, with quirks.
+    /// Parses a `<position>`, with quirks.
     pub fn parse_quirky(context: &ParserContext,
                         input: &mut Parser,
                         allow_quirks: AllowQuirks)
                         -> Result<Self, ()> {
-        let first = input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks))?;
-        let second = input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
-                          .unwrap_or(Either::Second(Keyword::Center));
-
-        if let Ok(third) = input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) {
-            // There's a 3rd value.
-            if let Ok(fourth) = input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) {
-                // There's a 4th value.
-                Position::from_components(Some(second), Some(fourth), Some(first), Some(third))
-            } else {
-                // For 3 value background position, there are several options.
-                if let Either::First(_) = first {
-                    return Err(())      // <length-percentage> must be preceded by <keyword>
+        match input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) {
+            Ok(x_pos @ PositionComponent::Center) => {
+                if let Ok(y_pos) = input.try(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) {
+                    return Ok(Self::new(x_pos, y_pos));
+                }
+                let x_pos = input
+                    .try(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
+                    .unwrap_or(x_pos);
+                let y_pos = PositionComponent::Center;
+                return Ok(Self::new(x_pos, y_pos));
+            },
+            Ok(PositionComponent::Side(x_keyword, lop)) => {
+                if input.try(|i| i.expect_ident_matching("center")).is_ok() {
+                    let x_pos = PositionComponent::Side(x_keyword, lop);
+                    let y_pos = PositionComponent::Center;
+                    return Ok(Self::new(x_pos, y_pos));
+                }
+                if let Ok(y_keyword) = input.try(Y::parse) {
+                    let y_lop = input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok();
+                    let x_pos = PositionComponent::Side(x_keyword, lop);
+                    let y_pos = PositionComponent::Side(y_keyword, y_lop);
+                    return Ok(Self::new(x_pos, y_pos));
                 }
+                let x_pos = PositionComponent::Side(x_keyword, None);
+                let y_pos = lop.map_or(PositionComponent::Center, PositionComponent::Length);
+                return Ok(Self::new(x_pos, y_pos));
+            },
+            Ok(x_pos @ PositionComponent::Length(_)) => {
+                if let Ok(y_keyword) = input.try(Y::parse) {
+                    let y_pos = PositionComponent::Side(y_keyword, None);
+                    return Ok(Self::new(x_pos, y_pos));
+                }
+                if let Ok(y_lop) = input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)) {
+                    let y_pos = PositionComponent::Length(y_lop);
+                    return Ok(Self::new(x_pos, y_pos));
+                }
+                let y_pos = PositionComponent::Center;
+                let _ = input.try(|i| i.expect_ident_matching("center"));
+                return Ok(Self::new(x_pos, y_pos));
+            },
+            Err(_) => {},
+        }
+        let y_keyword = Y::parse(input)?;
+        let lop_and_x_pos: Result<_, ()> = input.try(|i| {
+            let y_lop = i.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok();
+            if let Ok(x_keyword) = i.try(X::parse) {
+                let x_lop = i.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok();
+                let x_pos = PositionComponent::Side(x_keyword, x_lop);
+                return Ok((y_lop, x_pos));
+            }
+            i.expect_ident_matching("center")?;
+            let x_pos = PositionComponent::Center;
+            Ok((y_lop, x_pos))
+        });
+        if let Ok((y_lop, x_pos)) = lop_and_x_pos {
+            let y_pos = PositionComponent::Side(y_keyword, y_lop);
+            return Ok(Self::new(x_pos, y_pos));
+        }
+        let x_pos = PositionComponent::Center;
+        let y_pos = PositionComponent::Side(y_keyword, None);
+        Ok(Self::new(x_pos, y_pos))
+    }
 
-                // 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 {
-            // 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)),
-            }
+    /// `center center`
+    #[inline]
+    pub fn center() -> Self {
+        Self::new(PositionComponent::Center, PositionComponent::Center)
+    }
+}
+
+impl ToCss for Position {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match (&self.horizontal, &self.vertical) {
+            (x_pos @ &PositionComponent::Side(_, Some(_)), &PositionComponent::Length(ref y_lop)) => {
+                x_pos.to_css(dest)?;
+                dest.write_str(" top ")?;
+                y_lop.to_css(dest)
+            },
+            (&PositionComponent::Length(ref x_lop), y_pos @ &PositionComponent::Side(_, Some(_))) => {
+                dest.write_str("left ")?;
+                x_lop.to_css(dest)?;
+                dest.write_str(" ")?;
+                y_pos.to_css(dest)
+            },
+            (x_pos, y_pos) => {
+                x_pos.to_css(dest)?;
+                dest.write_str(" ")?;
+                y_pos.to_css(dest)
+            },
+        }
+    }
+}
+
+impl<S> HasViewportPercentage for PositionComponent<S> {
+    fn has_viewport_percentage(&self) -> bool {
+        match *self {
+            PositionComponent::Length(ref lop) |
+            PositionComponent::Side(_, Some(ref lop)) => {
+                lop.has_viewport_percentage()
+            },
+            _ => false,
         }
     }
 }
 
-impl PositionComponent {
-    /// Parses, with quirks.
-    fn parse_quirky(context: &ParserContext,
-                    input: &mut Parser,
-                    allow_quirks: AllowQuirks) -> Result<Self, ()> {
-        input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks))
-            .map(Either::First)
-            .or_else(|()| input.try(Keyword::parse).map(Either::Second))
+impl<S: Parse> Parse for PositionComponent<S> {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        Self::parse_quirky(context, input, AllowQuirks::No)
+    }
+}
+
+impl<S: Parse> PositionComponent<S> {
+    /// Parses a component of a CSS position, with quirks.
+    pub fn parse_quirky(context: &ParserContext,
+                        input: &mut Parser,
+                        allow_quirks: AllowQuirks)
+                        -> Result<Self, ()> {
+        if input.try(|i| i.expect_ident_matching("center")).is_ok() {
+            return Ok(PositionComponent::Center);
+        }
+        if let Ok(lop) = input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)) {
+            return Ok(PositionComponent::Length(lop));
+        }
+        let keyword = S::parse(context, input)?;
+        let lop = input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok();
+        Ok(PositionComponent::Side(keyword, lop))
     }
 }
 
-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),
-                    };
+impl<S> PositionComponent<S> {
+    /// `0%`
+    pub fn zero() -> Self {
+        PositionComponent::Length(LengthOrPercentage::Percentage(Percentage(0.)))
+    }
+}
 
-                    ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage {
-                        length: length,
-                        percentage: percentage
-                    })
-                },
-                None => ComputedLengthOrPercentage::Percentage(1.0),
+impl<S: ToCss> ToCss for PositionComponent<S> {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            PositionComponent::Center => {
+                dest.write_str("center")
+            },
+            PositionComponent::Length(ref lop) => {
+                lop.to_css(dest)
             },
-            _ => self.position.as_ref().map(|l| l.to_computed_value(context))
-                              .unwrap_or(ComputedLengthOrPercentage::Percentage(0.0)),
+            PositionComponent::Side(ref keyword, ref lop) => {
+                keyword.to_css(dest)?;
+                if let Some(ref lop) = *lop {
+                    dest.write_str(" ")?;
+                    lop.to_css(dest)?;
+                }
+                Ok(())
+            },
         }
     }
 }
 
-/// The specified value of horizontal `<position>`
-pub type HorizontalPosition = GenericHorizontalPosition<PositionValue<LengthOrPercentage>>;
+impl<S: Side> ToComputedValue for PositionComponent<S> {
+    type ComputedValue = ComputedLengthOrPercentage;
 
-impl ToComputedValue for HorizontalPosition {
-    type ComputedValue = computed_position::HorizontalPosition;
-
-    #[inline]
-    fn to_computed_value(&self, context: &Context) -> computed_position::HorizontalPosition {
-        GenericHorizontalPosition(self.0.computed_value(context))
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        match *self {
+            PositionComponent::Center => {
+                ComputedLengthOrPercentage::Percentage(0.5)
+            },
+            PositionComponent::Side(ref keyword, None) => {
+                let p = if keyword.is_start() { 0. } else { 1. };
+                ComputedLengthOrPercentage::Percentage(p)
+            },
+            PositionComponent::Side(ref keyword, Some(ref length)) if !keyword.is_start() => {
+                match length.to_computed_value(context) {
+                    ComputedLengthOrPercentage::Length(length) => {
+                        ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage {
+                            length: -length,
+                            percentage: Some(1.0),
+                        })
+                    },
+                    ComputedLengthOrPercentage::Percentage(p) => {
+                        ComputedLengthOrPercentage::Percentage(1.0 - p)
+                    },
+                    ComputedLengthOrPercentage::Calc(calc) => {
+                        ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage {
+                            length: -calc.length,
+                            percentage: Some(1.0 - calc.percentage.unwrap_or(0.)),
+                        })
+                    },
+                }
+            },
+            PositionComponent::Side(_, Some(ref length)) |
+            PositionComponent::Length(ref length) => {
+                length.to_computed_value(context)
+            },
+        }
     }
 
-    #[inline]
-    fn from_computed_value(computed: &computed_position::HorizontalPosition) -> HorizontalPosition {
-        GenericHorizontalPosition(PositionValue {
-            keyword: None,
-            position: Some(ToComputedValue::from_computed_value(&computed.0)),
-        })
-    }
-}
-
-impl HorizontalPosition {
-    #[inline]
-    /// Initial specified value for vertical position (`top` keyword).
-    pub fn left() -> HorizontalPosition {
-        GenericHorizontalPosition(PositionValue {
-            keyword: Some(Keyword::Left),
-            position: None,
-        })
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        PositionComponent::Length(ToComputedValue::from_computed_value(computed))
     }
 }
 
-
-/// The specified value of vertical `<position>`
-pub type VerticalPosition = GenericVerticalPosition<PositionValue<LengthOrPercentage>>;
+impl<S: Side> PositionComponent<S> {
+    /// The initial specified value of a position component, i.e. the start side.
+    pub fn initial_specified_value() -> Self {
+        PositionComponent::Side(S::start(), None)
+    }
+}
 
-impl ToComputedValue for VerticalPosition {
-    type ComputedValue = computed_position::VerticalPosition;
+/// Represents a side, either horizontal or vertical, of a CSS position.
+pub trait Side {
+    /// Returns the start side.
+    fn start() -> Self;
 
+    /// Returns whether this side is the start side.
+    fn is_start(&self) -> bool;
+}
+
+impl Side for X {
     #[inline]
-    fn to_computed_value(&self, context: &Context) -> computed_position::VerticalPosition {
-        GenericVerticalPosition(self.0.computed_value(context))
+    fn start() -> Self {
+        X::Left
     }
 
     #[inline]
-    fn from_computed_value(computed: &computed_position::VerticalPosition) -> VerticalPosition {
-        GenericVerticalPosition(PositionValue {
-            keyword: None,
-            position: Some(ToComputedValue::from_computed_value(&computed.0)),
-        })
+    fn is_start(&self) -> bool {
+        *self == X::Left
     }
 }
 
-impl VerticalPosition {
+impl Side for Y {
     #[inline]
-    /// Initial specified value for vertical position (`top` keyword).
-    pub fn top() -> VerticalPosition {
-        GenericVerticalPosition(PositionValue {
-            keyword: Some(Keyword::Top),
-            position: None,
-        })
+    fn start() -> Self {
+        Y::Top
+    }
+
+    #[inline]
+    fn is_start(&self) -> bool {
+        *self == Y::Top
     }
 }
--- a/servo/tests/unit/style/parsing/basic_shape.rs
+++ b/servo/tests/unit/style/parsing/basic_shape.rs
@@ -119,17 +119,17 @@ fn test_circle() {
                                                 "circle(at right 5px bottom 10px)");
     assert_roundtrip_basicshape!(Circle::parse, "circle(at bottom 5px right 10px)",
                                                 "circle(at right 10px bottom 5px)");
     assert_roundtrip_basicshape!(Circle::parse, "circle(at right 5% top 0px)",
                                                 "circle(at 95% 0%)");
     assert_roundtrip_basicshape!(Circle::parse, "circle(at right 5% bottom 0px)",
                                                 "circle(at 95% 100%)");
     assert_roundtrip_basicshape!(Circle::parse, "circle(at right 5% bottom 1px)",
-                                                "circle(at right 5% bottom 1px)");
+                                                "circle(at left 95% bottom 1px)");
 
     assert!(parse(Circle::parse, "circle(at 5% bottom 1px)").is_err());
     assert!(parse(Circle::parse, "circle(at top 40%)").is_err());
     assert!(parse(Circle::parse, "circle(-10px)").is_err());
 }
 
 #[test]
 fn test_ellipse() {
--- a/servo/tests/unit/style/parsing/mod.rs
+++ b/servo/tests/unit/style/parsing/mod.rs
@@ -15,16 +15,20 @@ fn parse<T, F: Fn(&ParserContext, &mut P
     let reporter = CSSErrorReporterTest;
     let context = ParserContext::new(Origin::Author, &url, &reporter, Some(CssRuleType::Style),
                                      LengthParsingMode::Default,
                                      QuirksMode::NoQuirks);
     let mut parser = Parser::new(s);
     f(&context, &mut parser)
 }
 
+fn parse_entirely<T, F: Fn(&ParserContext, &mut Parser) -> Result<T, ()>>(f: F, s: &str) -> Result<T, ()> {
+    parse(|context, parser| parser.parse_entirely(|p| f(context, p)), s)
+}
+
 // This is a macro so that the file/line information
 // is preserved in the panic
 macro_rules! assert_roundtrip_with_context {
     ($fun:expr, $string:expr) => {
         assert_roundtrip_with_context!($fun, $string, $string);
     };
     ($fun:expr, $input:expr, $output:expr) => {{
         let serialized = parse(|context, i| {
--- a/servo/tests/unit/style/parsing/position.rs
+++ b/servo/tests/unit/style/parsing/position.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use parsing::parse;
+use parsing::{parse, parse_entirely};
 use style::parser::Parse;
 use style::values::specified::position::*;
 use style_traits::ToCss;
 
 #[test]
 fn test_position() {
     // Serialization is not actually specced
     // though these are the values expected by basic-shape
@@ -23,126 +23,130 @@ fn test_position() {
     assert_roundtrip_with_context!(Position::parse, "right center", "right center");
     assert_roundtrip_with_context!(Position::parse, "center top", "center top");
     assert_roundtrip_with_context!(Position::parse, "center bottom", "center bottom");
     assert_roundtrip_with_context!(Position::parse, "center 10px", "center 10px");
     assert_roundtrip_with_context!(Position::parse, "center 10%", "center 10%");
     assert_roundtrip_with_context!(Position::parse, "right 10%", "right 10%");
 
     // Only keywords can be reordered
-    assert!(parse(Position::parse, "top 40%").is_err());
-    assert!(parse(Position::parse, "40% left").is_err());
+    assert!(parse_entirely(Position::parse, "top 40%").is_err());
+    assert!(parse_entirely(Position::parse, "40% left").is_err());
 
     // 3 and 4 value serialization
     assert_roundtrip_with_context!(Position::parse, "left 10px top 15px", "left 10px top 15px");
     assert_roundtrip_with_context!(Position::parse, "top 15px left 10px", "left 10px top 15px");
     assert_roundtrip_with_context!(Position::parse, "left 10% top 15px", "left 10% top 15px");
     assert_roundtrip_with_context!(Position::parse, "top 15px left 10%", "left 10% top 15px");
     assert_roundtrip_with_context!(Position::parse, "left top 15px", "left top 15px");
     assert_roundtrip_with_context!(Position::parse, "top 15px left", "left top 15px");
     assert_roundtrip_with_context!(Position::parse, "left 10px top", "left 10px top");
     assert_roundtrip_with_context!(Position::parse, "top left 10px", "left 10px top");
     assert_roundtrip_with_context!(Position::parse, "right 10px bottom", "right 10px bottom");
     assert_roundtrip_with_context!(Position::parse, "bottom right 10px", "right 10px bottom");
     assert_roundtrip_with_context!(Position::parse, "center right 10px", "right 10px center");
     assert_roundtrip_with_context!(Position::parse, "center bottom 10px", "center bottom 10px");
 
     // Invalid 3 value positions
-    assert!(parse(Position::parse, "20px 30px 20px").is_err());
-    assert!(parse(Position::parse, "top 30px 20px").is_err());
-    assert!(parse(Position::parse, "50% bottom 20%").is_err());
+    assert!(parse_entirely(Position::parse, "20px 30px 20px").is_err());
+    assert!(parse_entirely(Position::parse, "top 30px 20px").is_err());
+    assert!(parse_entirely(Position::parse, "50% bottom 20%").is_err());
 
     // Only horizontal and vertical keywords can have positions
-    assert!(parse(Position::parse, "center 10px left 15px").is_err());
-    assert!(parse(Position::parse, "center 10px 15px").is_err());
-    assert!(parse(Position::parse, "center 10px bottom").is_err());
+    assert!(parse_entirely(Position::parse, "center 10px left 15px").is_err());
+    assert!(parse_entirely(Position::parse, "center 10px 15px").is_err());
+    assert!(parse_entirely(Position::parse, "center 10px bottom").is_err());
 
     // "Horizontal Horizontal" or "Vertical Vertical" positions cause error
-    assert!(parse(Position::parse, "left right").is_err());
-    assert!(parse(Position::parse, "left 10px right").is_err());
-    assert!(parse(Position::parse, "left 10px right 15%").is_err());
-    assert!(parse(Position::parse, "top bottom").is_err());
-    assert!(parse(Position::parse, "top 10px bottom").is_err());
-    assert!(parse(Position::parse, "top 10px bottom 15%").is_err());
+    assert!(parse_entirely(Position::parse, "left right").is_err());
+    assert!(parse_entirely(Position::parse, "left 10px right").is_err());
+    assert!(parse_entirely(Position::parse, "left 10px right 15%").is_err());
+    assert!(parse_entirely(Position::parse, "top bottom").is_err());
+    assert!(parse_entirely(Position::parse, "top 10px bottom").is_err());
+    assert!(parse_entirely(Position::parse, "top 10px bottom 15%").is_err());
 
-    // Logical keywords are not supported in Position yet
+    // Logical keywords are not supported in Position yet.
     assert!(parse(Position::parse, "x-start").is_err());
     assert!(parse(Position::parse, "y-end").is_err());
     assert!(parse(Position::parse, "x-start y-end").is_err());
     assert!(parse(Position::parse, "x-end 10px").is_err());
     assert!(parse(Position::parse, "y-start 20px").is_err());
     assert!(parse(Position::parse, "x-start bottom 10%").is_err());
-    assert!(parse(Position::parse, "left y-start 10%").is_err());
+    assert!(parse_entirely(Position::parse, "left y-start 10%").is_err());
     assert!(parse(Position::parse, "x-start 20px y-end 10%").is_err());
 }
 
 #[test]
 fn test_horizontal_position() {
     // One value serializations.
     assert_roundtrip_with_context!(HorizontalPosition::parse, "20px", "20px");
     assert_roundtrip_with_context!(HorizontalPosition::parse, "25%", "25%");
     assert_roundtrip_with_context!(HorizontalPosition::parse, "center", "center");
     assert_roundtrip_with_context!(HorizontalPosition::parse, "left", "left");
     assert_roundtrip_with_context!(HorizontalPosition::parse, "right", "right");
-    assert_roundtrip_with_context!(HorizontalPosition::parse, "x-start", "x-start");
-    assert_roundtrip_with_context!(HorizontalPosition::parse, "x-end", "x-end");
 
     // Two value serializations.
     assert_roundtrip_with_context!(HorizontalPosition::parse, "right 10px", "right 10px");
-    assert_roundtrip_with_context!(HorizontalPosition::parse, "10px left", "left 10px");
-    assert_roundtrip_with_context!(HorizontalPosition::parse, "x-end 20%", "x-end 20%");
-    assert_roundtrip_with_context!(HorizontalPosition::parse, "20px x-start", "x-start 20px");
 
     // Invalid horizontal positions.
     assert!(parse(HorizontalPosition::parse, "top").is_err());
     assert!(parse(HorizontalPosition::parse, "bottom").is_err());
     assert!(parse(HorizontalPosition::parse, "y-start").is_err());
     assert!(parse(HorizontalPosition::parse, "y-end").is_err());
-    assert!(parse(HorizontalPosition::parse, "20px y-end").is_err());
     assert!(parse(HorizontalPosition::parse, "y-end 20px ").is_err());
     assert!(parse(HorizontalPosition::parse, "bottom 20px").is_err());
-    assert!(parse(HorizontalPosition::parse, "20px top").is_err());
-    assert!(parse(HorizontalPosition::parse, "left center").is_err());
     assert!(parse(HorizontalPosition::parse, "bottom top").is_err());
-    assert!(parse(HorizontalPosition::parse, "left top").is_err());
-    assert!(parse(HorizontalPosition::parse, "left right").is_err());
-    assert!(parse(HorizontalPosition::parse, "20px 30px").is_err());
+    assert!(parse_entirely(HorizontalPosition::parse, "20px y-end").is_err());
+    assert!(parse_entirely(HorizontalPosition::parse, "20px top").is_err());
+    assert!(parse_entirely(HorizontalPosition::parse, "left center").is_err());
+    assert!(parse_entirely(HorizontalPosition::parse, "left top").is_err());
+    assert!(parse_entirely(HorizontalPosition::parse, "left right").is_err());
+    assert!(parse_entirely(HorizontalPosition::parse, "20px 30px").is_err());
+    assert!(parse_entirely(HorizontalPosition::parse, "10px left").is_err());
+    assert!(parse_entirely(HorizontalPosition::parse, "x-end 20%").is_err());
+    assert!(parse_entirely(HorizontalPosition::parse, "20px x-start").is_err());
+
+    // Logical keywords are not supported in Position yet.
+    assert!(parse(HorizontalPosition::parse, "x-start").is_err());
+    assert!(parse(HorizontalPosition::parse, "x-end").is_err());
 }
 
 #[test]
 fn test_vertical_position() {
     // One value serializations.
     assert_roundtrip_with_context!(VerticalPosition::parse, "20px", "20px");
     assert_roundtrip_with_context!(VerticalPosition::parse, "25%", "25%");
     assert_roundtrip_with_context!(VerticalPosition::parse, "center", "center");
     assert_roundtrip_with_context!(VerticalPosition::parse, "top", "top");
     assert_roundtrip_with_context!(VerticalPosition::parse, "bottom", "bottom");
-    assert_roundtrip_with_context!(VerticalPosition::parse, "y-start", "y-start");
-    assert_roundtrip_with_context!(VerticalPosition::parse, "y-end", "y-end");
 
     // Two value serializations.
     assert_roundtrip_with_context!(VerticalPosition::parse, "bottom 10px", "bottom 10px");
-    assert_roundtrip_with_context!(VerticalPosition::parse, "10px top", "top 10px");
-    assert_roundtrip_with_context!(VerticalPosition::parse, "y-end 20%", "y-end 20%");
-    assert_roundtrip_with_context!(VerticalPosition::parse, "20px y-start", "y-start 20px");
 
     // Invalid vertical positions.
     assert!(parse(VerticalPosition::parse, "left").is_err());
     assert!(parse(VerticalPosition::parse, "right").is_err());
     assert!(parse(VerticalPosition::parse, "x-start").is_err());
     assert!(parse(VerticalPosition::parse, "x-end").is_err());
-    assert!(parse(VerticalPosition::parse, "20px x-end").is_err());
-    assert!(parse(VerticalPosition::parse, "x-end 20px ").is_err());
+    assert!(parse(VerticalPosition::parse, "x-end 20px").is_err());
     assert!(parse(VerticalPosition::parse, "left 20px").is_err());
-    assert!(parse(VerticalPosition::parse, "20px right").is_err());
     assert!(parse(VerticalPosition::parse, "left center").is_err());
-    assert!(parse(VerticalPosition::parse, "bottom top").is_err());
     assert!(parse(VerticalPosition::parse, "left top").is_err());
     assert!(parse(VerticalPosition::parse, "left right").is_err());
-    assert!(parse(VerticalPosition::parse, "20px 30px").is_err());
+    assert!(parse_entirely(VerticalPosition::parse, "20px x-end").is_err());
+    assert!(parse_entirely(VerticalPosition::parse, "20px right").is_err());
+    assert!(parse_entirely(VerticalPosition::parse, "bottom top").is_err());
+    assert!(parse_entirely(VerticalPosition::parse, "20px 30px").is_err());
+    assert!(parse_entirely(VerticalPosition::parse, "10px top").is_err());
+    assert!(parse_entirely(VerticalPosition::parse, "y-end 20%").is_err());
+    assert!(parse_entirely(VerticalPosition::parse, "20px y-start").is_err());
+
+    // Logical keywords are not supported in Position yet.
+    assert!(parse(VerticalPosition::parse, "y-start").is_err());
+    assert!(parse(VerticalPosition::parse, "y-end").is_err());
 }
 
 #[test]
 fn test_grid_auto_flow() {
     use style::properties::longhands::grid_auto_flow;
 
     assert_roundtrip_with_context!(grid_auto_flow::parse, "row dense", "row dense");
     assert_roundtrip_with_context!(grid_auto_flow::parse, "dense row", "row dense");
--- a/servo/tests/unit/style/parsing/value.rs
+++ b/servo/tests/unit/style/parsing/value.rs
@@ -1,14 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
-use parsing::parse;
 use style::values::HasViewportPercentage;
 use style::values::specified::{AbsoluteLength, NoCalcLength, ViewportPercentageLength};
 
 #[test]
 fn length_has_viewport_percentage() {
     let l = NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(100.));
     assert!(l.has_viewport_percentage());
     let l = NoCalcLength::Absolute(AbsoluteLength::Px(Au(100).to_f32_px()));
--- a/servo/tests/unit/style/properties/serialization.rs
+++ b/servo/tests/unit/style/properties/serialization.rs
@@ -4,18 +4,20 @@
 
 use properties::parse;
 use style::computed_values::display::T::inline_block;
 use style::properties::{PropertyDeclaration, Importance, PropertyId};
 use style::properties::longhands::outline_color::computed_value::T as ComputedColor;
 use style::properties::parse_property_declaration_list;
 use style::values::{RGBA, Auto};
 use style::values::CustomIdent;
-use style::values::specified::{BorderStyle, BorderWidth, CSSColor, Length, NoCalcLength};
-use style::values::specified::{LengthOrPercentage, LengthOrPercentageOrAuto, LengthOrPercentageOrAutoOrContent};
+use style::values::specified::{BorderStyle, BorderWidth, CSSColor, Length, LengthOrPercentage};
+use style::values::specified::{LengthOrPercentageOrAuto, LengthOrPercentageOrAutoOrContent};
+use style::values::specified::{NoCalcLength, PositionComponent};
+use style::values::specified::position::Y;
 use style::values::specified::url::SpecifiedUrl;
 use style_traits::ToCss;
 use stylesheets::block_from;
 
 #[test]
 fn property_declaration_block_should_serialize_correctly() {
     use style::properties::longhands::overflow_x::SpecifiedValue as OverflowValue;
 
@@ -791,17 +793,16 @@ 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 super::*;
 
         macro_rules! single_vec_value_typedef {
             ($name:ident, $path:expr) => {
                 $name::SpecifiedValue(
                     vec![$path]
                 )
@@ -828,26 +829,23 @@ mod shorthand_serialization {
 
             let image = single_vec_value_typedef!(image,
                 image::single_value::SpecifiedValue(
                     Some(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(PositionValue {
-                    keyword: None,
-                    position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(7f32))),
-                })
+                PositionComponent::Length(LengthOrPercentage::Length(NoCalcLength::from_px(7f32)))
             );
             let position_y = single_vec_value_typedef!(position_y,
-                VerticalPosition(PositionValue {
-                    keyword: Some(Keyword::Bottom),
-                    position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(4f32))),
-                })
+                PositionComponent::Side(
+                    Y::Bottom,
+                    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))
                     }
@@ -883,27 +881,21 @@ mod shorthand_serialization {
 
             let image = single_vec_value_typedef!(image,
                 image::single_value::SpecifiedValue(
                     Some(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(PositionValue {
-                    keyword: None,
-                    position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(7f32))),
-                })
+                PositionComponent::Length(LengthOrPercentage::Length(NoCalcLength::from_px(7f32)))
             );
 
             let position_y = single_vec_value_typedef!(position_y,
-                VerticalPosition(PositionValue  {
-                    keyword: None,
-                    position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(4f32))),
-                })
+                PositionComponent::Length(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))
                     }
--- a/servo/tests/unit/style/stylesheets.rs
+++ b/servo/tests/unit/style/stylesheets.rs
@@ -20,17 +20,17 @@ use style::properties::Importance;
 use style::properties::{CSSWideKeyword, DeclaredValueOwned, PropertyDeclaration, PropertyDeclarationBlock};
 use style::properties::longhands;
 use style::properties::longhands::animation_play_state;
 use style::shared_lock::SharedRwLock;
 use style::stylearc::Arc;
 use style::stylesheets::{Origin, Namespaces};
 use style::stylesheets::{Stylesheet, NamespaceRule, CssRule, CssRules, StyleRule, KeyframesRule};
 use style::values::{KeyframesName, CustomIdent};
-use style::values::specified::{LengthOrPercentageOrAuto, Percentage};
+use style::values::specified::{LengthOrPercentageOrAuto, Percentage, PositionComponent};
 
 pub fn block_from<I>(iterable: I) -> PropertyDeclarationBlock
 where I: IntoIterator<Item=(PropertyDeclaration, Importance)> {
     let mut block = PropertyDeclarationBlock::new();
     for (d, i) in iterable {
         block.push(d, i)
     }
     block
@@ -178,23 +178,21 @@ fn test_parse_stylesheet() {
                         longhands::background_color::SpecifiedValue {
                             authored: Some("blue".to_owned().into_boxed_str()),
                             parsed: cssparser::Color::RGBA(cssparser::RGBA::new(0, 0, 255, 255)),
                         }
                      ),
                      Importance::Normal),
                     (PropertyDeclaration::BackgroundPositionX(
                         longhands::background_position_x::SpecifiedValue(
-                        vec![longhands::background_position_x::single_value
-                                                   ::get_initial_position_value()])),
-                    Importance::Normal),
+                        vec![PositionComponent::zero()])),
+                     Importance::Normal),
                     (PropertyDeclaration::BackgroundPositionY(
                         longhands::background_position_y::SpecifiedValue(
-                        vec![longhands::background_position_y::single_value
-                                                   ::get_initial_position_value()])),
+                        vec![PositionComponent::zero()])),
                      Importance::Normal),
                     (PropertyDeclaration::BackgroundRepeat(
                         longhands::background_repeat::SpecifiedValue(
                         vec![longhands::background_repeat::single_value
                                                    ::get_initial_specified_value()])),
                      Importance::Normal),
                     (PropertyDeclaration::BackgroundAttachment(
                         longhands::background_attachment::SpecifiedValue(