servo: Merge #17095 - Refactor a few CSS properties (from servo:derive-all-the-things); r=emilio
authorAnthony Ramine <n.oxyde@gmail.com>
Wed, 31 May 2017 11:08:56 -0500
changeset 409705 46391a33a8f9efb4b29efd58087500ab63ca3fee
parent 409704 09fc715942f2b303b1a1ed5f65e977e55b35b890
child 409706 7f4d49af53f1bd362527154076d2bcb42e928d7e
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #17095 - Refactor a few CSS properties (from servo:derive-all-the-things); r=emilio Source-Repo: https://github.com/servo/servo Source-Revision: cd22fc6b902496d105f8003beaee5c8eb79e9669
servo/components/style/properties/longhand/box.mako.rs
servo/components/style/properties/longhand/effects.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/computed/transform.rs
servo/components/style/values/generics/mod.rs
servo/components/style/values/generics/transform.rs
servo/components/style/values/specified/mod.rs
servo/components/style/values/specified/transform.rs
servo/tests/unit/style/media_queries.rs
servo/tests/unit/style/parsing/effects.rs
--- a/servo/components/style/properties/longhand/box.mako.rs
+++ b/servo/components/style/properties/longhand/box.mako.rs
@@ -398,44 +398,26 @@
                          spec="https://drafts.csswg.org/css-overflow/#propdef-overflow-x")}
 
 // FIXME(pcwalton, #2742): Implement scrolling for `scroll` and `auto`.
 <%helpers:longhand name="overflow-y" need_clone="True" animation_value_type="discrete"
                    spec="https://drafts.csswg.org/css-overflow/#propdef-overflow-y">
     pub use super::overflow_x::{SpecifiedValue, parse, get_initial_value, computed_value};
 </%helpers:longhand>
 
-<%helpers:vector_longhand name="transition-duration"
-                          need_index="True"
-                          animation_value_type="none"
-                          extra_prefixes="moz webkit"
-                          spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration">
-    use values::specified::Time;
-
-    pub use values::specified::Time as SpecifiedValue;
-    no_viewport_percentage!(SpecifiedValue);
-
-    pub mod computed_value {
-        pub use values::computed::Time as T;
-    }
-
-    #[inline]
-    pub fn get_initial_value() -> computed_value::T {
-        computed_value::T::zero()
-    }
-
-    #[inline]
-    pub fn get_initial_specified_value() -> SpecifiedValue {
-        Time::zero()
-    }
-
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
-        Time::parse_non_negative(context, input)
-    }
-</%helpers:vector_longhand>
+${helpers.predefined_type("transition-duration",
+                          "Time",
+                          "computed::Time::zero()",
+                          initial_specified_value="specified::Time::zero()",
+                          parse_method="parse_non_negative",
+                          vector=True,
+                          need_index=True,
+                          animation_value_type="none",
+                          extra_prefixes="moz webkit",
+                          spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration")}
 
 // TODO(pcwalton): Lots more timing functions.
 <%helpers:vector_longhand name="transition-timing-function"
                           need_index="True"
                           animation_value_type="none"
                           extra_prefixes="moz webkit"
                           spec="https://drafts.csswg.org/css-transitions/#propdef-transition-timing-function">
     use self::computed_value::StartEnd;
@@ -789,30 +771,25 @@
         TransitionProperty::All
     }
 
     no_viewport_percentage!(SpecifiedValue);
 
     impl ComputedValueAsSpecified for SpecifiedValue { }
 </%helpers:vector_longhand>
 
-<%helpers:vector_longhand name="transition-delay"
-                          need_index="True"
-                          animation_value_type="none"
-                          extra_prefixes="moz webkit"
-                          spec="https://drafts.csswg.org/css-transitions/#propdef-transition-delay">
-    pub use properties::longhands::transition_duration::single_value::SpecifiedValue;
-    pub use properties::longhands::transition_duration::single_value::computed_value;
-    pub use properties::longhands::transition_duration::single_value::{get_initial_value, get_initial_specified_value};
-
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
-        use values::specified::Time;
-        Time::parse(context, input)
-    }
-</%helpers:vector_longhand>
+${helpers.predefined_type("transition-delay",
+                          "Time",
+                          "computed::Time::zero()",
+                          initial_specified_value="specified::Time::zero()",
+                          vector=True,
+                          need_index=True,
+                          animation_value_type="none",
+                          extra_prefixes="moz webkit",
+                          spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration")}
 
 <%helpers:vector_longhand name="animation-name"
                           need_index="True"
                           animation_value_type="none",
                           extra_prefixes="moz webkit"
                           allowed_in_keyframe_block="False"
                           spec="https://drafts.csswg.org/css-animations/#propdef-animation-name">
     use Atom;
@@ -875,27 +852,26 @@
 
     pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
         SpecifiedValue::parse(context, input)
     }
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
 </%helpers:vector_longhand>
 
-<%helpers:vector_longhand name="animation-duration"
-                          need_index="True"
+${helpers.predefined_type("animation-duration",
+                          "Time",
+                          "computed::Time::zero()",
+                          initial_specified_value="specified::Time::zero()",
+                          parse_method="parse_non_negative",
+                          vector=True,
+                          need_index=True,
                           animation_value_type="none",
-                          extra_prefixes="moz webkit"
-                          spec="https://drafts.csswg.org/css-animations/#propdef-animation-duration",
-                          allowed_in_keyframe_block="False">
-    pub use properties::longhands::transition_duration::single_value::computed_value;
-    pub use properties::longhands::transition_duration::single_value::get_initial_specified_value;
-    pub use properties::longhands::transition_duration::single_value::{get_initial_value, parse};
-    pub use properties::longhands::transition_duration::single_value::SpecifiedValue;
-</%helpers:vector_longhand>
+                          extra_prefixes="moz webkit",
+                          spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration")}
 
 <%helpers:vector_longhand name="animation-timing-function"
                           need_index="True"
                           animation_value_type="none",
                           extra_prefixes="moz webkit"
                           spec="https://drafts.csswg.org/css-animations/#propdef-animation-timing-function",
                           allowed_in_keyframe_block="True">
     pub use properties::longhands::transition_timing_function::single_value::computed_value;
@@ -2095,130 +2071,23 @@
 ${helpers.single_keyword("transform-style",
                          "auto flat preserve-3d" if product == "servo" else
                          "flat preserve-3d",
                          spec="https://drafts.csswg.org/css-transforms/#transform-style-property",
                          extra_prefixes="moz webkit",
                          flags="CREATES_STACKING_CONTEXT FIXPOS_CB",
                          animation_value_type="discrete")}
 
-<%helpers:longhand name="transform-origin" animation_value_type="ComputedValue" extra_prefixes="moz webkit" boxed="True"
-                   spec="https://drafts.csswg.org/css-transforms/#transform-origin-property">
-    use app_units::Au;
-    use std::fmt;
-    use style_traits::ToCss;
-    use values::specified::{NoCalcLength, LengthOrPercentage, Percentage};
-
-    pub mod computed_value {
-        use properties::animated_properties::Animatable;
-        use values::computed::{Length, LengthOrPercentage};
-
-        #[derive(Clone, Copy, Debug, PartialEq)]
-        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-        pub struct T {
-            pub horizontal: LengthOrPercentage,
-            pub vertical: LengthOrPercentage,
-            pub depth: Length,
-        }
-
-        impl Animatable for T {
-            #[inline]
-            fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64)
-                -> Result<Self, ()> {
-                Ok(T {
-                    horizontal: try!(self.horizontal.add_weighted(&other.horizontal,
-                                                                  self_portion, other_portion)),
-                    vertical: try!(self.vertical.add_weighted(&other.vertical,
-                                                              self_portion, other_portion)),
-                    depth: try!(self.depth.add_weighted(&other.depth, self_portion, other_portion)),
-                })
-            }
-
-            #[inline]
-            fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
-                self.compute_squared_distance(other).map(|sd| sd.sqrt())
-            }
-
-            #[inline]
-            fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
-                Ok(try!(self.horizontal.compute_squared_distance(&other.horizontal)) +
-                   try!(self.vertical.compute_squared_distance(&other.vertical)) +
-                   try!(self.depth.compute_squared_distance(&other.depth)))
-            }
-        }
-    }
-
-    #[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
-    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-    pub struct SpecifiedValue {
-        horizontal: LengthOrPercentage,
-        vertical: LengthOrPercentage,
-        depth: NoCalcLength,
-    }
-
-    impl ToCss for computed_value::T {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            try!(self.horizontal.to_css(dest));
-            try!(dest.write_str(" "));
-            try!(self.vertical.to_css(dest));
-            try!(dest.write_str(" "));
-            self.depth.to_css(dest)
-        }
-    }
-
-    impl ToCss for SpecifiedValue {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            try!(self.horizontal.to_css(dest));
-            try!(dest.write_str(" "));
-            try!(self.vertical.to_css(dest));
-            try!(dest.write_str(" "));
-            self.depth.to_css(dest)
-        }
-    }
-
-    #[inline]
-    pub fn get_initial_value() -> computed_value::T {
-        computed_value::T {
-            horizontal: computed::LengthOrPercentage::Percentage(0.5),
-            vertical: computed::LengthOrPercentage::Percentage(0.5),
-            depth: Au(0),
-        }
-    }
-
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
-        let result = try!(super::parse_origin(context, input));
-        Ok(SpecifiedValue {
-            horizontal: result.horizontal.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))),
-            vertical: result.vertical.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))),
-            depth: result.depth.unwrap_or(NoCalcLength::zero()),
-        })
-    }
-
-    impl ToComputedValue for SpecifiedValue {
-        type ComputedValue = computed_value::T;
-
-        #[inline]
-        fn to_computed_value(&self, context: &Context) -> computed_value::T {
-            computed_value::T {
-                horizontal: self.horizontal.to_computed_value(context),
-                vertical: self.vertical.to_computed_value(context),
-                depth: self.depth.to_computed_value(context),
-            }
-        }
-
-        #[inline]
-        fn from_computed_value(computed: &computed_value::T) -> Self {
-            SpecifiedValue {
-                horizontal: ToComputedValue::from_computed_value(&computed.horizontal),
-                vertical: ToComputedValue::from_computed_value(&computed.vertical),
-                depth: ToComputedValue::from_computed_value(&computed.depth),
-            }
-        }
-    }
-</%helpers:longhand>
+${helpers.predefined_type("transform-origin",
+                          "TransformOrigin",
+                          "computed::TransformOrigin::initial_value()",
+                          animation_value_type="ComputedValue",
+                          extra_prefixes="moz webkit",
+                          boxed=True,
+                          spec="https://drafts.csswg.org/css-transforms/#transform-origin-property")}
 
 // FIXME: `size` and `content` values are not implemented and `strict` is implemented
 // like `content`(layout style paint) in gecko. We should implement `size` and `content`,
 // also update the glue once they are implemented in gecko.
 <%helpers:longhand name="contain" animation_value_type="none" products="gecko" need_clone="True"
                    flags="FIXPOS_CB"
                    spec="https://drafts.csswg.org/css-contain/#contain-property">
     use std::fmt;
--- a/servo/components/style/properties/longhand/effects.mako.rs
+++ b/servo/components/style/properties/longhand/effects.mako.rs
@@ -424,105 +424,15 @@
                     }
                     % endif
                 }
             }).collect())
         }
     }
 </%helpers:longhand>
 
-pub struct OriginParseResult {
-    pub horizontal: Option<specified::LengthOrPercentage>,
-    pub vertical: Option<specified::LengthOrPercentage>,
-    pub depth: Option<specified::NoCalcLength>
-}
-
-pub fn parse_origin(context: &ParserContext, input: &mut Parser) -> Result<OriginParseResult,()> {
-    use values::specified::{LengthOrPercentage, Percentage};
-    let (mut horizontal, mut vertical, mut depth, mut horizontal_is_center) = (None, None, None, false);
-    loop {
-        if let Err(_) = input.try(|input| {
-            let token = try!(input.expect_ident());
-            match_ignore_ascii_case! {
-                &token,
-                "left" => {
-                    if horizontal.is_none() {
-                        horizontal = Some(LengthOrPercentage::Percentage(Percentage(0.0)))
-                    } else if horizontal_is_center && vertical.is_none() {
-                        vertical = Some(LengthOrPercentage::Percentage(Percentage(0.5)));
-                        horizontal = Some(LengthOrPercentage::Percentage(Percentage(0.0)));
-                    } else {
-                        return Err(())
-                    }
-                },
-                "center" => {
-                    if horizontal.is_none() {
-                        horizontal_is_center = true;
-                        horizontal = Some(LengthOrPercentage::Percentage(Percentage(0.5)))
-                    } else if vertical.is_none() {
-                        vertical = Some(LengthOrPercentage::Percentage(Percentage(0.5)))
-                    } else {
-                        return Err(())
-                    }
-                },
-                "right" => {
-                    if horizontal.is_none() {
-                        horizontal = Some(LengthOrPercentage::Percentage(Percentage(1.0)))
-                    } else if horizontal_is_center && vertical.is_none() {
-                        vertical = Some(LengthOrPercentage::Percentage(Percentage(0.5)));
-                        horizontal = Some(LengthOrPercentage::Percentage(Percentage(1.0)));
-                    } else {
-                        return Err(())
-                    }
-                },
-                "top" => {
-                    if vertical.is_none() {
-                        vertical = Some(LengthOrPercentage::Percentage(Percentage(0.0)))
-                    } else {
-                        return Err(())
-                    }
-                },
-                "bottom" => {
-                    if vertical.is_none() {
-                        vertical = Some(LengthOrPercentage::Percentage(Percentage(1.0)))
-                    } else {
-                        return Err(())
-                    }
-                },
-                _ => return Err(())
-            }
-            Ok(())
-        }) {
-            match input.try(|input| LengthOrPercentage::parse(context, input)) {
-                Ok(value) => {
-                    if horizontal.is_none() {
-                        horizontal = Some(value);
-                    } else if vertical.is_none() {
-                        vertical = Some(value);
-                    } else if let LengthOrPercentage::Length(length) = value {
-                        depth = Some(length);
-                    } else {
-                        break;
-                    }
-                }
-                _ => break,
-            }
-        }
-    }
-
-    if horizontal.is_some() || vertical.is_some() {
-        Ok(OriginParseResult {
-            horizontal: horizontal,
-            vertical: vertical,
-            depth: depth,
-        })
-    } else {
-        Err(())
-    }
-}
-
 ${helpers.single_keyword("mix-blend-mode",
                          """normal multiply screen overlay darken lighten color-dodge
                             color-burn hard-light soft-light difference exclusion hue
                             saturation color luminosity""", gecko_constant_prefix="NS_STYLE_BLEND",
                          animation_value_type="discrete",
                          flags="CREATES_STACKING_CONTEXT",
                          spec="https://drafts.fxtf.org/compositing/#propdef-mix-blend-mode")}
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -125,20 +125,16 @@ macro_rules! expanded {
             )+
         }
     }
 }
 
 /// A module with all the code for longhand properties.
 #[allow(missing_docs)]
 pub mod longhands {
-    use cssparser::Parser;
-    use parser::{Parse, ParserContext};
-    use values::specified;
-
     <%include file="/longhand/background.mako.rs" />
     <%include file="/longhand/border.mako.rs" />
     <%include file="/longhand/box.mako.rs" />
     <%include file="/longhand/color.mako.rs" />
     <%include file="/longhand/column.mako.rs" />
     <%include file="/longhand/counters.mako.rs" />
     <%include file="/longhand/effects.mako.rs" />
     <%include file="/longhand/font.mako.rs" />
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -33,24 +33,26 @@ pub use super::{Auto, Either, None_};
 pub use super::specified::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
 pub use super::specified::{BorderStyle, Percentage, UrlOrNone};
 pub use super::generics::grid::GridLine;
 pub use super::specified::url::SpecifiedUrl;
 pub use self::length::{CalcLengthOrPercentage, Length, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
 pub use self::length::{LengthOrPercentageOrAutoOrContent, LengthOrPercentageOrNone, LengthOrNone};
 pub use self::length::{MaxLength, MozLength};
 pub use self::position::Position;
+pub use self::transform::TransformOrigin;
 
 pub mod background;
 pub mod basic_shape;
 pub mod border;
 pub mod image;
 pub mod length;
 pub mod position;
 pub mod rect;
+pub mod transform;
 
 /// A `Context` is all the data a specified value could ever need to compute
 /// itself and be transformed to a computed value.
 pub struct Context<'a> {
     /// Whether the current element is the root element.
     pub is_root_element: bool,
 
     /// The Device holds the viewport and other external state.
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/computed/transform.rs
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Computed types for CSS values that are related to transformations.
+
+use properties::animated_properties::Animatable;
+use values::computed::{Length, LengthOrPercentage};
+use values::generics::transform::TransformOrigin as GenericTransformOrigin;
+
+/// The computed value of a CSS `<transform-origin>`
+pub type TransformOrigin = GenericTransformOrigin<LengthOrPercentage, LengthOrPercentage, Length>;
+
+impl TransformOrigin {
+    /// Returns the initial computed value for `transform-origin`.
+    #[inline]
+    pub fn initial_value() -> Self {
+        Self::new(
+            LengthOrPercentage::Percentage(0.5),
+            LengthOrPercentage::Percentage(0.5),
+            Length::from_px(0)
+        )
+    }
+}
+
+impl Animatable for TransformOrigin {
+    #[inline]
+    fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
+        Ok(Self::new(
+            self.horizontal.add_weighted(&other.horizontal, self_portion, other_portion)?,
+            self.vertical.add_weighted(&other.vertical, self_portion, other_portion)?,
+            self.depth.add_weighted(&other.depth, self_portion, other_portion)?,
+        ))
+    }
+
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        self.compute_squared_distance(other).map(f64::sqrt)
+    }
+
+    #[inline]
+    fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
+        Ok(
+            self.horizontal.compute_squared_distance(&other.horizontal)? +
+            self.vertical.compute_squared_distance(&other.vertical)? +
+            self.depth.compute_squared_distance(&other.depth)?
+        )
+    }
+}
--- a/servo/components/style/values/generics/mod.rs
+++ b/servo/components/style/values/generics/mod.rs
@@ -14,16 +14,17 @@ use super::CustomIdent;
 
 pub mod background;
 pub mod basic_shape;
 pub mod border;
 pub mod grid;
 pub mod image;
 pub mod position;
 pub mod rect;
+pub mod transform;
 
 // https://drafts.csswg.org/css-counter-styles/#typedef-symbols-type
 define_css_keyword_enum! { SymbolsType:
     "cyclic" => Cyclic,
     "numeric" => Numeric,
     "alphabetic" => Alphabetic,
     "symbolic" => Symbolic,
     "fixed" => Fixed,
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/generics/transform.rs
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Generic types for CSS values that are related to transformations.
+
+use std::fmt;
+use style_traits::ToCss;
+
+/// A generic transform origin.
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)]
+pub struct TransformOrigin<H, V, Depth> {
+    /// The horizontal origin.
+    pub horizontal: H,
+    /// The vertical origin.
+    pub vertical: V,
+    /// The depth.
+    pub depth: Depth,
+}
+
+impl<H, V, D> TransformOrigin<H, V, D> {
+    /// Returns a new transform origin.
+    pub fn new(horizontal: H, vertical: V, depth: D) -> Self {
+        Self {
+            horizontal: horizontal,
+            vertical: vertical,
+            depth: depth,
+        }
+    }
+}
+
+impl<H, V, D> ToCss for TransformOrigin<H, V, D>
+    where H: ToCss, V: ToCss, D: ToCss,
+{
+    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)?;
+        dest.write_str(" ")?;
+        self.depth.to_css(dest)
+    }
+}
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -37,29 +37,31 @@ pub use super::generics::grid::GridLine;
 pub use self::image::{ColorStop, EndingShape as GradientEndingShape, Gradient};
 pub use self::image::{GradientItem, GradientKind, Image, ImageRect, ImageLayer};
 pub use self::length::AbsoluteLength;
 pub use self::length::{FontRelativeLength, ViewportPercentageLength, CharacterWidth, Length, CalcLengthOrPercentage};
 pub use self::length::{Percentage, LengthOrNone, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
 pub use self::length::{LengthOrPercentageOrNone, LengthOrPercentageOrAutoOrContent, NoCalcLength};
 pub use self::length::{MaxLength, MozLength};
 pub use self::position::{Position, PositionComponent};
+pub use self::transform::TransformOrigin;
 
 #[cfg(feature = "gecko")]
 pub mod align;
 pub mod background;
 pub mod basic_shape;
 pub mod border;
 pub mod calc;
 pub mod color;
 pub mod grid;
 pub mod image;
 pub mod length;
 pub mod position;
 pub mod rect;
+pub mod transform;
 
 /// Common handling for the specified value CSS url() values.
 pub mod url {
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
 use values::computed::ComputedValueAsSpecified;
 
 #[cfg(feature = "servo")]
@@ -534,17 +536,17 @@ no_viewport_percentage!(BorderStyle);
 impl BorderStyle {
     /// Whether this border style is either none or hidden.
     pub fn none_or_hidden(&self) -> bool {
         matches!(*self, BorderStyle::none | BorderStyle::hidden)
     }
 }
 
 /// A time in seconds according to CSS-VALUES ยง 6.2.
-#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
+#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, PartialOrd)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct Time {
     seconds: CSSFloat,
     was_calc: bool,
 }
 
 impl Time {
     /// Return a `<time>` value that represents `seconds` seconds.
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/specified/transform.rs
@@ -0,0 +1,132 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Specified types for CSS values that are related to transformations.
+
+use cssparser::Parser;
+use parser::{Parse, ParserContext};
+use std::fmt;
+use style_traits::ToCss;
+use values::computed::{LengthOrPercentage as ComputedLengthOrPercentage, Context, ToComputedValue};
+use values::generics::transform::TransformOrigin as GenericTransformOrigin;
+use values::specified::length::{Length, LengthOrPercentage};
+use values::specified::position::{Side, X, Y};
+
+/// The specified value of a CSS `<transform-origin>`
+pub type TransformOrigin = GenericTransformOrigin<OriginComponent<X>, OriginComponent<Y>, Length>;
+
+/// The specified value of a component of a CSS `<transform-origin>`.
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
+pub enum OriginComponent<S> {
+    /// `center`
+    Center,
+    /// `<lop>`
+    Length(LengthOrPercentage),
+    /// `<side>`
+    Side(S),
+}
+
+impl Parse for TransformOrigin {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        let parse_depth = |input: &mut Parser| {
+            input.try(|i| Length::parse(context, i)).unwrap_or(Length::from_px(0.))
+        };
+        match input.try(|i| OriginComponent::parse(context, i)) {
+            Ok(x_origin @ OriginComponent::Center) => {
+                if let Ok(y_origin) = input.try(|i| OriginComponent::parse(context, i)) {
+                    let depth = parse_depth(input);
+                    return Ok(Self::new(x_origin, y_origin, depth));
+                }
+                let y_origin = OriginComponent::Center;
+                if let Ok(x_keyword) = input.try(X::parse) {
+                    let x_origin = OriginComponent::Side(x_keyword);
+                    let depth = parse_depth(input);
+                    return Ok(Self::new(x_origin, y_origin, depth));
+                }
+                let depth = Length::from_px(0.);
+                return Ok(Self::new(x_origin, y_origin, depth));
+            },
+            Ok(x_origin) => {
+                if let Ok(y_origin) = input.try(|i| OriginComponent::parse(context, i)) {
+                    let depth = parse_depth(input);
+                    return Ok(Self::new(x_origin, y_origin, depth));
+                }
+                let y_origin = OriginComponent::Center;
+                let depth = Length::from_px(0.);
+                return Ok(Self::new(x_origin, y_origin, depth));
+            },
+            Err(_) => {},
+        }
+        let y_keyword = Y::parse(input)?;
+        let y_origin = OriginComponent::Side(y_keyword);
+        if let Ok(x_keyword) = input.try(X::parse) {
+            let x_origin = OriginComponent::Side(x_keyword);
+            let depth = parse_depth(input);
+            return Ok(Self::new(x_origin, y_origin, depth));
+        }
+        if input.try(|i| i.expect_ident_matching("center")).is_ok() {
+            let x_origin = OriginComponent::Center;
+            let depth = parse_depth(input);
+            return Ok(Self::new(x_origin, y_origin, depth));
+        }
+        let x_origin = OriginComponent::Center;
+        let depth = Length::from_px(0.);
+        Ok(Self::new(x_origin, y_origin, depth))
+    }
+}
+
+impl<S> Parse for OriginComponent<S>
+    where S: Parse,
+{
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        if input.try(|i| i.expect_ident_matching("center")).is_ok() {
+            return Ok(OriginComponent::Center);
+        }
+        if let Ok(lop) = input.try(|i| LengthOrPercentage::parse(context, i)) {
+            return Ok(OriginComponent::Length(lop));
+        }
+        let keyword = S::parse(context, input)?;
+        Ok(OriginComponent::Side(keyword))
+    }
+}
+
+impl<S: ToCss> ToCss for OriginComponent<S>
+    where S: ToCss,
+{
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
+        where W: fmt::Write,
+    {
+        match *self {
+            OriginComponent::Center => dest.write_str("center"),
+            OriginComponent::Length(ref lop) => lop.to_css(dest),
+            OriginComponent::Side(ref keyword) => keyword.to_css(dest),
+        }
+    }
+}
+
+impl<S> ToComputedValue for OriginComponent<S>
+    where S: Side,
+{
+    type ComputedValue = ComputedLengthOrPercentage;
+
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        match *self {
+            OriginComponent::Center => {
+                ComputedLengthOrPercentage::Percentage(0.5)
+            },
+            OriginComponent::Length(ref length) => {
+                length.to_computed_value(context)
+            },
+            OriginComponent::Side(ref keyword) => {
+                let p = if keyword.is_start() { 0. } else { 1. };
+                ComputedLengthOrPercentage::Percentage(p)
+            },
+        }
+    }
+
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        OriginComponent::Length(ToComputedValue::from_computed_value(computed))
+    }
+}
--- a/servo/tests/unit/style/media_queries.rs
+++ b/servo/tests/unit/style/media_queries.rs
@@ -6,17 +6,17 @@ use cssparser::{Parser, SourcePosition};
 use euclid::size::TypedSize2D;
 use servo_url::ServoUrl;
 use std::borrow::ToOwned;
 use style::Atom;
 use style::context::QuirksMode;
 use style::error_reporting::ParseErrorReporter;
 use style::media_queries::*;
 use style::servo::media_queries::*;
-use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard};
+use style::shared_lock::SharedRwLock;
 use style::stylearc::Arc;
 use style::stylesheets::{AllRules, Stylesheet, Origin, CssRule};
 use style::values::specified;
 use style_traits::ToCss;
 
 pub struct CSSErrorReporterTest;
 
 impl ParseErrorReporter for CSSErrorReporterTest {
--- a/servo/tests/unit/style/parsing/effects.rs
+++ b/servo/tests/unit/style/parsing/effects.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use parsing::parse;
-use style::properties::longhands::{self, perspective_origin, transform_origin};
+use style::properties::longhands::{perspective_origin, transform_origin};
 use style_traits::ToCss;
 
 #[test]
 fn test_clip() {
     use style::properties::longhands::clip;
 
     assert_roundtrip_with_context!(clip::parse, "auto");
     assert_roundtrip_with_context!(clip::parse, "rect(1px, 2px, 3px, 4px)");
@@ -26,26 +26,16 @@ fn test_clip() {
                                    "rect(1px auto auto 4px)",
                                    "rect(1px, auto, auto, 4px)");
     assert_roundtrip_with_context!(clip::parse,
                                    "rect(auto auto auto auto)",
                                    "rect(auto, auto, auto, auto)");
 }
 
 #[test]
-fn test_longhands_parse_origin() {
-    assert_parser_exhausted!(longhands::parse_origin, "1px some-rubbish", false);
-    assert_parser_exhausted!(longhands::parse_origin, "1px 2px", true);
-    assert_parser_exhausted!(longhands::parse_origin, "center left", true);
-    assert_parser_exhausted!(longhands::parse_origin, "center right", true);
-    assert_parser_exhausted!(longhands::parse_origin, "center right 1px", true);
-    assert_parser_exhausted!(longhands::parse_origin, "1% right", false);
-}
-
-#[test]
 fn test_effects_parser_exhaustion() {
     assert_parser_exhausted!(perspective_origin::parse, "1px 1px", true);
     assert_parser_exhausted!(transform_origin::parse, "1px 1px", true);
 
     assert_parser_exhausted!(perspective_origin::parse, "1px some-rubbish", false);
     assert_parser_exhausted!(transform_origin::parse, "1px some-rubbish", false);
 }