servo: Merge #17167 - Use generics for transition-timing-function 📈 (from servo:derive-all-the-things); r=emilio
authorAnthony Ramine <n.oxyde@gmail.com>
Mon, 05 Jun 2017 13:38:01 -0700
changeset 362421 0cf5121dfb27b707912e80851c93091dd00418ac
parent 362420 975ca096c5d96728a6b35886c35c3dd87e3c7da1
child 362422 a7e36fe5ac723903f95532b93a1af63c4a1efc3b
push id31977
push userarchaeopteryx@coole-files.de
push dateTue, 06 Jun 2017 09:17:27 +0000
treeherdermozilla-central@d3b8e8571020 [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 #17167 - Use generics for transition-timing-function 📈 (from servo:derive-all-the-things); r=emilio Source-Repo: https://github.com/servo/servo Source-Revision: 621c1a4f969ba5119fdd974932b5b82042891893
servo/components/style/animation.rs
servo/components/style/gecko_bindings/sugar/ns_timing_function.rs
servo/components/style/properties/longhand/box.mako.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/computed/transform.rs
servo/components/style/values/generics/transform.rs
servo/components/style/values/specified/mod.rs
servo/components/style/values/specified/transform.rs
--- a/servo/components/style/animation.rs
+++ b/servo/components/style/animation.rs
@@ -11,24 +11,24 @@ use context::SharedStyleContext;
 use dom::OpaqueNode;
 use euclid::point::Point2D;
 use font_metrics::FontMetricsProvider;
 use properties::{self, CascadeFlags, ComputedValues, Importance};
 use properties::animated_properties::{AnimatedProperty, TransitionProperty};
 use properties::longhands::animation_direction::computed_value::single_value::T as AnimationDirection;
 use properties::longhands::animation_iteration_count::single_value::computed_value::T as AnimationIterationCount;
 use properties::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState;
-use properties::longhands::transition_timing_function::single_value::computed_value::StartEnd;
-use properties::longhands::transition_timing_function::single_value::computed_value::T as TransitionTimingFunction;
 use rule_tree::CascadeLevel;
 use std::sync::mpsc::Sender;
 use stylearc::Arc;
 use stylesheets::keyframes_rule::{KeyframesStep, KeyframesStepValue};
 use timer::Timer;
 use values::computed::Time;
+use values::computed::transform::TimingFunction;
+use values::generics::transform::{StepPosition, TimingFunction as GenericTimingFunction};
 
 /// This structure represents a keyframes animation current iteration state.
 ///
 /// If the iteration count is infinite, there's no other state, otherwise we
 /// have to keep track the current iteration and the max iteration count.
 #[derive(Debug, Clone)]
 pub enum KeyframesIterationState {
     /// Infinite iterations, so no need to track a state.
@@ -253,17 +253,17 @@ pub struct AnimationFrame {
     /// case (a number between 0 and 1), or absolute in the transition case.
     pub duration: f64,
 }
 
 /// Represents an animation for a given property.
 #[derive(Debug, Clone)]
 pub struct PropertyAnimation {
     property: AnimatedProperty,
-    timing_function: TransitionTimingFunction,
+    timing_function: TimingFunction,
     duration: Time, // TODO: isn't this just repeated?
 }
 
 impl PropertyAnimation {
     /// Returns the given property name.
     pub fn property_name(&self) -> &'static str {
         self.property.name()
     }
@@ -318,17 +318,17 @@ impl PropertyAnimation {
                 result.push(property_animation)
             }
         });
 
         result
     }
 
     fn from_transition_property(transition_property: &TransitionProperty,
-                                timing_function: TransitionTimingFunction,
+                                timing_function: TimingFunction,
                                 duration: Time,
                                 old_style: &ComputedValues,
                                 new_style: &ComputedValues)
                                 -> Option<PropertyAnimation> {
         debug_assert!(!transition_property.is_shorthand() &&
                       transition_property != &TransitionProperty::All);
         let animated_property = AnimatedProperty::from_transition_property(transition_property,
                                                                            old_style,
@@ -344,52 +344,53 @@ impl PropertyAnimation {
             Some(property_animation)
         } else {
             None
         }
     }
 
     /// Update the given animation at a given point of progress.
     pub fn update(&self, style: &mut ComputedValues, time: f64) {
-        let timing_function = match self.timing_function {
-            TransitionTimingFunction::Keyword(keyword) =>
-                keyword.to_non_keyword_value(),
-            other => other,
+        let solve_bezier = |(p1, p2): (Point2D<_>, Point2D<_>)| {
+            let epsilon = 1. / (200. * (self.duration.seconds() as f64));
+            let bezier = Bezier::new(
+                Point2D::new(p1.x as f64, p1.y as f64),
+                Point2D::new(p2.x as f64, p2.y as f64),
+            );
+            bezier.solve(time, epsilon)
         };
-        let progress = match timing_function {
-            TransitionTimingFunction::CubicBezier(p1, p2) => {
-                // See `WebCore::AnimationBase::solveEpsilon(double)` in WebKit.
-                let epsilon = 1.0 / (200.0 * (self.duration.seconds() as f64));
-                Bezier::new(Point2D::new(p1.x as f64, p1.y as f64),
-                            Point2D::new(p2.x as f64, p2.y as f64)).solve(time, epsilon)
+
+        let progress = match self.timing_function {
+            GenericTimingFunction::CubicBezier(p1, p2) => {
+                solve_bezier((p1, p2))
             },
-            TransitionTimingFunction::Steps(steps, StartEnd::Start) => {
+            GenericTimingFunction::Steps(steps, StepPosition::Start) => {
                 (time * (steps as f64)).ceil() / (steps as f64)
             },
-            TransitionTimingFunction::Steps(steps, StartEnd::End) => {
+            GenericTimingFunction::Steps(steps, StepPosition::End) => {
                 (time * (steps as f64)).floor() / (steps as f64)
             },
-            TransitionTimingFunction::Frames(frames) => {
+            GenericTimingFunction::Frames(frames) => {
                 // https://drafts.csswg.org/css-timing/#frames-timing-functions
                 let mut out = (time * (frames as f64)).floor() / ((frames - 1) as f64);
                 if out > 1.0 {
                     // FIXME: Basically, during the animation sampling process, the input progress
                     // should be in the range of [0, 1]. However, |time| is not accurate enough
                     // here, which means |time| could be larger than 1.0 in the last animation
                     // frame. (It should be equal to 1.0 exactly.) This makes the output of frames
                     // timing function jumps to the next frame/level.
                     // However, this solution is still not correct because |time| is possible
                     // outside the range of [0, 1] after introducing Web Animations. We should fix
                     // this problem when implementing web animations.
                     out = 1.0;
                 }
                 out
             },
-            TransitionTimingFunction::Keyword(_) => {
-                panic!("Keyword function should not appear")
+            GenericTimingFunction::Keyword(keyword) => {
+                solve_bezier(keyword.to_bezier_points())
             },
         };
 
         self.property.update(style, progress);
     }
 
     #[inline]
     fn does_animate(&self) -> bool {
--- a/servo/components/style/gecko_bindings/sugar/ns_timing_function.rs
+++ b/servo/components/style/gecko_bindings/sugar/ns_timing_function.rs
@@ -1,20 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use euclid::point::{Point2D, TypedPoint2D};
 use gecko_bindings::structs::{nsTimingFunction, nsTimingFunction_Type};
-use properties::longhands::transition_timing_function::single_value::FunctionKeyword;
-use properties::longhands::transition_timing_function::single_value::SpecifiedValue as SpecifiedTimingFunction;
-use properties::longhands::transition_timing_function::single_value::computed_value::StartEnd;
-use properties::longhands::transition_timing_function::single_value::computed_value::T as ComputedTimingFunction;
 use std::mem;
 use values::computed::ToComputedValue;
+use values::computed::transform::TimingFunction as ComputedTimingFunction;
+use values::generics::transform::{StepPosition, TimingFunction as GenericTimingFunction, TimingKeyword};
+use values::specified::transform::TimingFunction;
 
 impl nsTimingFunction {
     fn set_as_step(&mut self, function_type: nsTimingFunction_Type, steps: u32) {
         debug_assert!(function_type == nsTimingFunction_Type::StepStart ||
                       function_type == nsTimingFunction_Type::StepEnd,
                       "function_type should be step-start or step-end");
         self.mType = function_type;
         unsafe {
@@ -42,119 +41,97 @@ impl nsTimingFunction {
             gecko_cubic_bezier.mX2 = p2.x;
             gecko_cubic_bezier.mY2 = p2.y;
         }
     }
 }
 
 impl From<ComputedTimingFunction> for nsTimingFunction {
     fn from(function: ComputedTimingFunction) -> nsTimingFunction {
-        SpecifiedTimingFunction::from_computed_value(&function).into()
+        TimingFunction::from_computed_value(&function).into()
     }
 }
 
-impl From<SpecifiedTimingFunction> for nsTimingFunction {
-    fn from(function: SpecifiedTimingFunction) -> nsTimingFunction {
+impl From<TimingFunction> for nsTimingFunction {
+    fn from(function: TimingFunction) -> nsTimingFunction {
         let mut tf: nsTimingFunction = unsafe { mem::zeroed() };
 
         match function {
-            SpecifiedTimingFunction::Steps(steps, StartEnd::Start) => {
+            GenericTimingFunction::Steps(steps, StepPosition::Start) => {
                 debug_assert!(steps.value() >= 0);
                 tf.set_as_step(nsTimingFunction_Type::StepStart, steps.value() as u32);
             },
-            SpecifiedTimingFunction::Steps(steps, StartEnd::End) => {
+            GenericTimingFunction::Steps(steps, StepPosition::End) => {
                 debug_assert!(steps.value() >= 0);
                 tf.set_as_step(nsTimingFunction_Type::StepEnd, steps.value() as u32);
             },
-            SpecifiedTimingFunction::Frames(frames) => {
+            GenericTimingFunction::Frames(frames) => {
                 debug_assert!(frames.value() >= 2);
                 tf.set_as_frames(frames.value() as u32);
             },
-            SpecifiedTimingFunction::CubicBezier(p1, p2) => {
+            GenericTimingFunction::CubicBezier(p1, p2) => {
                 tf.set_as_bezier(nsTimingFunction_Type::CubicBezier,
                                  Point2D::new(p1.x.get(), p1.y.get()),
                                  Point2D::new(p2.x.get(), p2.y.get()));
             },
-            SpecifiedTimingFunction::Keyword(keyword) => {
-                match keyword.to_non_keyword_value() {
-                    ComputedTimingFunction::CubicBezier(p1, p2) => {
-                        match keyword {
-                            FunctionKeyword::Ease => {
-                                tf.set_as_bezier(nsTimingFunction_Type::Ease, p1, p2);
-                            },
-                            FunctionKeyword::Linear => {
-                                tf.set_as_bezier(nsTimingFunction_Type::Linear, p1, p2);
-                            },
-                            FunctionKeyword::EaseIn => {
-                                tf.set_as_bezier(nsTimingFunction_Type::EaseIn, p1, p2);
-                            },
-                            FunctionKeyword::EaseOut => {
-                                tf.set_as_bezier(nsTimingFunction_Type::EaseOut, p1, p2);
-                            },
-                            FunctionKeyword::EaseInOut => {
-                                tf.set_as_bezier(nsTimingFunction_Type::EaseInOut, p1, p2);
-                            },
-                            _ => unreachable!("Unexpected bezier function type"),
-                        }
-                    },
-                    ComputedTimingFunction::Steps(steps, StartEnd::Start) => {
-                        debug_assert!(keyword == FunctionKeyword::StepStart && steps == 1);
-                        tf.set_as_step(nsTimingFunction_Type::StepStart, steps);
-                    },
-                    ComputedTimingFunction::Steps(steps, StartEnd::End) => {
-                        debug_assert!(keyword == FunctionKeyword::StepEnd && steps == 1);
-                        tf.set_as_step(nsTimingFunction_Type::StepEnd, steps);
-                    },
-                    ComputedTimingFunction::Frames(frames) => {
-                        tf.set_as_frames(frames)
-                    },
-                    ComputedTimingFunction::Keyword(_) => {
-                        panic!("Keyword function should not appear")
-                    },
-                }
+            GenericTimingFunction::Keyword(keyword) => {
+                let (p1, p2) = keyword.to_bezier_points();
+                tf.set_as_bezier(keyword.into(), p1, p2)
             },
         }
         tf
     }
 }
 
 impl From<nsTimingFunction> for ComputedTimingFunction {
     fn from(function: nsTimingFunction) -> ComputedTimingFunction {
         match function.mType {
             nsTimingFunction_Type::StepStart => {
-                ComputedTimingFunction::Steps(
+                GenericTimingFunction::Steps(
                     unsafe { function.__bindgen_anon_1.__bindgen_anon_1.as_ref().mStepsOrFrames },
-                    StartEnd::Start)
+                    StepPosition::Start)
             },
             nsTimingFunction_Type::StepEnd => {
-                ComputedTimingFunction::Steps(
+                GenericTimingFunction::Steps(
                     unsafe { function.__bindgen_anon_1.__bindgen_anon_1.as_ref().mStepsOrFrames },
-                    StartEnd::End)
+                    StepPosition::End)
             },
             nsTimingFunction_Type::Frames => {
-                ComputedTimingFunction::Frames(
+                GenericTimingFunction::Frames(
                     unsafe { function.__bindgen_anon_1.__bindgen_anon_1.as_ref().mStepsOrFrames })
             }
             nsTimingFunction_Type::Ease => {
-                ComputedTimingFunction::Keyword(FunctionKeyword::Ease)
+                GenericTimingFunction::Keyword(TimingKeyword::Ease)
             },
             nsTimingFunction_Type::Linear => {
-                ComputedTimingFunction::Keyword(FunctionKeyword::Linear)
+                GenericTimingFunction::Keyword(TimingKeyword::Linear)
             },
             nsTimingFunction_Type::EaseIn => {
-                ComputedTimingFunction::Keyword(FunctionKeyword::EaseIn)
+                GenericTimingFunction::Keyword(TimingKeyword::EaseIn)
             },
             nsTimingFunction_Type::EaseOut => {
-                ComputedTimingFunction::Keyword(FunctionKeyword::EaseOut)
+                GenericTimingFunction::Keyword(TimingKeyword::EaseOut)
             },
             nsTimingFunction_Type::EaseInOut => {
-                ComputedTimingFunction::Keyword(FunctionKeyword::EaseInOut)
+                GenericTimingFunction::Keyword(TimingKeyword::EaseInOut)
             },
             nsTimingFunction_Type::CubicBezier => {
-                ComputedTimingFunction::CubicBezier(
+                GenericTimingFunction::CubicBezier(
                     TypedPoint2D::new(unsafe { function.__bindgen_anon_1.mFunc.as_ref().mX1 },
                                       unsafe { function.__bindgen_anon_1.mFunc.as_ref().mY1 }),
                     TypedPoint2D::new(unsafe { function.__bindgen_anon_1.mFunc.as_ref().mX2 },
                                       unsafe { function.__bindgen_anon_1.mFunc.as_ref().mY2 }))
             },
         }
     }
 }
+
+impl From<TimingKeyword> for nsTimingFunction_Type {
+    fn from(keyword: TimingKeyword) -> Self {
+        match keyword {
+            TimingKeyword::Linear => nsTimingFunction_Type::Linear,
+            TimingKeyword::Ease => nsTimingFunction_Type::Ease,
+            TimingKeyword::EaseIn => nsTimingFunction_Type::EaseIn,
+            TimingKeyword::EaseOut => nsTimingFunction_Type::EaseOut,
+            TimingKeyword::EaseInOut => nsTimingFunction_Type::EaseInOut,
+        }
+    }
+}
--- a/servo/components/style/properties/longhand/box.mako.rs
+++ b/servo/components/style/properties/longhand/box.mako.rs
@@ -409,346 +409,25 @@
                           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;
-    use values::specified::Number;
-    use euclid::point::{Point2D, TypedPoint2D};
-    use std::fmt;
-    use style_traits::ToCss;
-
-    // FIXME: This could use static variables and const functions when they are available.
-    #[inline(always)]
-    fn ease() -> computed_value::T {
-        computed_value::T::CubicBezier(TypedPoint2D::new(0.25, 0.1),
-                                       TypedPoint2D::new(0.25, 1.0))
-    }
-
-    #[inline(always)]
-    fn linear() -> computed_value::T {
-        computed_value::T::CubicBezier(TypedPoint2D::new(0.0, 0.0),
-                                       TypedPoint2D::new(1.0, 1.0))
-    }
-
-    #[inline(always)]
-    fn ease_in() -> computed_value::T {
-        computed_value::T::CubicBezier(TypedPoint2D::new(0.42, 0.0),
-                                       TypedPoint2D::new(1.0, 1.0))
-    }
-
-    #[inline(always)]
-    fn ease_out() -> computed_value::T {
-        computed_value::T::CubicBezier(TypedPoint2D::new(0.0, 0.0),
-                                       TypedPoint2D::new(0.58, 1.0))
-    }
-
-    #[inline(always)]
-    fn ease_in_out() -> computed_value::T {
-        computed_value::T::CubicBezier(TypedPoint2D::new(0.42, 0.0),
-                                       TypedPoint2D::new(0.58, 1.0))
-    }
-
-    static STEP_START: computed_value::T =
-        computed_value::T::Steps(1, StartEnd::Start);
-    static STEP_END: computed_value::T =
-        computed_value::T::Steps(1, StartEnd::End);
-
-    pub mod computed_value {
-        use euclid::point::Point2D;
-        use std::fmt;
-        use style_traits::ToCss;
-        use super::FunctionKeyword;
-        use values::specified;
-
-        pub use super::parse;
-
-        #[derive(Copy, Clone, Debug, PartialEq)]
-        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-        pub enum T {
-            CubicBezier(Point2D<f32>, Point2D<f32>),
-            Steps(u32, StartEnd),
-            Frames(u32),
-            Keyword(FunctionKeyword),
-        }
-
-        impl ToCss for T {
-            fn to_css<W>(&self, dest: &mut W) -> fmt::Result
-                where W: fmt::Write,
-            {
-                match *self {
-                    T::CubicBezier(p1, p2) => {
-                        try!(dest.write_str("cubic-bezier("));
-                        try!(p1.x.to_css(dest));
-                        try!(dest.write_str(", "));
-                        try!(p1.y.to_css(dest));
-                        try!(dest.write_str(", "));
-                        try!(p2.x.to_css(dest));
-                        try!(dest.write_str(", "));
-                        try!(p2.y.to_css(dest));
-                        dest.write_str(")")
-                    },
-                    T::Steps(steps, start_end) => {
-                        super::serialize_steps(dest, specified::Integer::new(steps as i32), start_end)
-                    },
-                    T::Frames(frames) => {
-                        try!(dest.write_str("frames("));
-                        try!(frames.to_css(dest));
-                        dest.write_str(")")
-                    },
-                    T::Keyword(keyword) => {
-                        super::serialize_keyword(dest, keyword)
-                    }
-                }
-            }
-        }
-
-        #[derive(Copy, Clone, Debug, PartialEq)]
-        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-        pub enum StartEnd {
-            Start,
-            End,
-        }
-
-        impl ToCss for StartEnd {
-            fn to_css<W>(&self, dest: &mut W) -> fmt::Result
-                where W: fmt::Write,
-            {
-                match *self {
-                    StartEnd::Start => dest.write_str("start"),
-                    StartEnd::End => dest.write_str("end"),
-                }
-            }
-        }
-    }
-
-    define_css_keyword_enum!(FunctionKeyword:
-                             "ease" => Ease,
-                             "linear" => Linear,
-                             "ease-in" => EaseIn,
-                             "ease-out" => EaseOut,
-                             "ease-in-out" => EaseInOut,
-                             "step-start" => StepStart,
-                             "step-end" => StepEnd);
-
-    #[derive(Copy, Clone, Debug, PartialEq)]
-    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-    pub enum SpecifiedValue {
-        CubicBezier(Point2D<Number>, Point2D<Number>),
-        Steps(specified::Integer, StartEnd),
-        Frames(specified::Integer),
-        Keyword(FunctionKeyword),
-    }
-
-    impl Parse for SpecifiedValue {
-        fn parse(context: &ParserContext, input: &mut ::cssparser::Parser) -> Result<Self, ()> {
-            if let Ok(function_name) = input.try(|input| input.expect_function()) {
-                return match_ignore_ascii_case! { &function_name,
-                    "cubic-bezier" => {
-                        let (mut p1x, mut p1y, mut p2x, mut p2y) =
-                            (Number::new(0.0), Number::new(0.0), Number::new(0.0), Number::new(0.0));
-                        try!(input.parse_nested_block(|input| {
-                            p1x = try!(specified::parse_number(context, input));
-                            try!(input.expect_comma());
-                            p1y = try!(specified::parse_number(context, input));
-                            try!(input.expect_comma());
-                            p2x = try!(specified::parse_number(context, input));
-                            try!(input.expect_comma());
-                            p2y = try!(specified::parse_number(context, input));
-                            Ok(())
-                        }));
-                        if p1x.get() < 0.0 || p1x.get() > 1.0 ||
-                           p2x.get() < 0.0 || p2x.get() > 1.0 {
-                            return Err(())
-                        }
-
-                        let (p1, p2) = (Point2D::new(p1x, p1y), Point2D::new(p2x, p2y));
-                        Ok(SpecifiedValue::CubicBezier(p1, p2))
-                    },
-                    "steps" => {
-                        let (mut step_count, mut start_end) = (specified::Integer::new(0), StartEnd::End);
-                        try!(input.parse_nested_block(|input| {
-                            step_count = try!(specified::parse_integer(context, input));
-                            if step_count.value() < 1 {
-                                return Err(())
-                            }
-
-                            if input.try(|input| input.expect_comma()).is_ok() {
-                                start_end = try!(match_ignore_ascii_case! {
-                                    &try!(input.expect_ident()),
-                                    "start" => Ok(StartEnd::Start),
-                                    "end" => Ok(StartEnd::End),
-                                    _ => Err(())
-                                });
-                            }
-                            Ok(())
-                        }));
-                        Ok(SpecifiedValue::Steps(step_count, start_end))
-                    },
-                    "frames" => {
-                        // https://drafts.csswg.org/css-timing/#frames-timing-functions
-                        let frames = try!(input.parse_nested_block(|input| {
-                            specified::Integer::parse_with_minimum(context, input, 2)
-                        }));
-                        Ok(SpecifiedValue::Frames(frames))
-                    },
-                    _ => Err(())
-                }
-            }
-            Ok(SpecifiedValue::Keyword(try!(FunctionKeyword::parse(input))))
-        }
-    }
-
-    fn serialize_steps<W>(dest: &mut W,
-                          steps: specified::Integer,
-                          start_end: StartEnd) -> fmt::Result
-        where W: fmt::Write,
-    {
-        try!(dest.write_str("steps("));
-        try!(steps.to_css(dest));
-        if let StartEnd::Start = start_end {
-            try!(dest.write_str(", start"));
-        }
-        dest.write_str(")")
-    }
-
-    fn serialize_keyword<W>(dest: &mut W, keyword: FunctionKeyword) -> fmt::Result
-        where W: fmt::Write,
-    {
-        match keyword {
-            FunctionKeyword::StepStart => {
-                serialize_steps(dest, specified::Integer::new(1), StartEnd::Start)
-            },
-            FunctionKeyword::StepEnd => {
-                serialize_steps(dest, specified::Integer::new(1), StartEnd::End)
-            },
-            _ => {
-                keyword.to_css(dest)
-            },
-        }
-    }
-
-    // https://drafts.csswg.org/css-transitions/#serializing-a-timing-function
-    impl ToCss for SpecifiedValue {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match *self {
-                SpecifiedValue::CubicBezier(p1, p2) => {
-                    try!(dest.write_str("cubic-bezier("));
-                    try!(p1.x.to_css(dest));
-                    try!(dest.write_str(", "));
-                    try!(p1.y.to_css(dest));
-                    try!(dest.write_str(", "));
-                    try!(p2.x.to_css(dest));
-                    try!(dest.write_str(", "));
-                    try!(p2.y.to_css(dest));
-                    dest.write_str(")")
-                },
-                SpecifiedValue::Steps(steps, start_end) => {
-                    serialize_steps(dest, steps, start_end)
-                },
-                SpecifiedValue::Frames(frames) => {
-                    try!(dest.write_str("frames("));
-                    try!(frames.to_css(dest));
-                    dest.write_str(")")
-                },
-                SpecifiedValue::Keyword(keyword) => {
-                    serialize_keyword(dest, keyword)
-                },
-            }
-        }
-    }
-
-    impl ToComputedValue for SpecifiedValue {
-        type ComputedValue = computed_value::T;
-
-        #[inline]
-        fn to_computed_value(&self, context: &Context) -> computed_value::T {
-            match *self {
-                SpecifiedValue::CubicBezier(p1, p2) => {
-                    computed_value::T::CubicBezier(
-                        Point2D::new(p1.x.to_computed_value(context), p1.y.to_computed_value(context)),
-                        Point2D::new(p2.x.to_computed_value(context), p2.y.to_computed_value(context)))
-                },
-                SpecifiedValue::Steps(count, start_end) => {
-                    computed_value::T::Steps(count.to_computed_value(context) as u32, start_end)
-                },
-                SpecifiedValue::Frames(frames) => {
-                    computed_value::T::Frames(frames.to_computed_value(context) as u32)
-                },
-                SpecifiedValue::Keyword(keyword) => {
-                    computed_value::T::Keyword(keyword)
-                },
-            }
-        }
-        #[inline]
-        fn from_computed_value(computed: &computed_value::T) -> Self {
-            match *computed {
-                computed_value::T::CubicBezier(p1, p2) => {
-                    SpecifiedValue::CubicBezier(
-                        Point2D::new(Number::from_computed_value(&p1.x),
-                                     Number::from_computed_value(&p1.y)),
-                        Point2D::new(Number::from_computed_value(&p2.x),
-                                     Number::from_computed_value(&p2.y)))
-                },
-                computed_value::T::Steps(count, start_end) => {
-                    let int_count = count as i32;
-                    SpecifiedValue::Steps(specified::Integer::from_computed_value(&int_count), start_end)
-                },
-                computed_value::T::Frames(frames) => {
-                    let frames = frames as i32;
-                    SpecifiedValue::Frames(specified::Integer::from_computed_value(&frames))
-                },
-                computed_value::T::Keyword(keyword) => {
-                    SpecifiedValue::Keyword(keyword)
-                },
-            }
-        }
-    }
-
-    impl FunctionKeyword {
-        #[inline]
-        pub fn to_non_keyword_value(&self) -> computed_value::T {
-            match *self {
-                FunctionKeyword::Ease => ease(),
-                FunctionKeyword::Linear => linear(),
-                FunctionKeyword::EaseIn => ease_in(),
-                FunctionKeyword::EaseOut => ease_out(),
-                FunctionKeyword::EaseInOut => ease_in_out(),
-                FunctionKeyword::StepStart => STEP_START,
-                FunctionKeyword::StepEnd => STEP_END,
-            }
-        }
-    }
-
-    no_viewport_percentage!(SpecifiedValue);
-
-    #[inline]
-    pub fn get_initial_value() -> computed_value::T {
-        computed_value::T::Keyword(FunctionKeyword::Ease)
-    }
-
-    #[inline]
-    pub fn get_initial_specified_value() -> SpecifiedValue {
-        SpecifiedValue::Keyword(FunctionKeyword::Ease)
-    }
-
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
-        SpecifiedValue::parse(context, input)
-    }
-</%helpers:vector_longhand>
+${helpers.predefined_type("transition-timing-function",
+                          "TimingFunction",
+                          "computed::TimingFunction::ease()",
+                          initial_specified_value="specified::TimingFunction::ease()",
+                          vector=True,
+                          need_index=True,
+                          animation_value_type="none",
+                          extra_prefixes="moz webkit",
+                          spec="https://drafts.csswg.org/css-transitions/#propdef-transition-timing-function")}
 
 <%helpers:vector_longhand name="transition-property"
                           allow_empty="True"
                           need_index="True"
                           animation_value_type="none"
                           extra_prefixes="moz webkit"
                           spec="https://drafts.csswg.org/css-transitions/#propdef-transition-property">
 
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -34,17 +34,17 @@ pub use super::specified::{AlignItems, A
 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::text::{LetterSpacing, LineHeight, WordSpacing};
-pub use self::transform::TransformOrigin;
+pub use self::transform::{TimingFunction, TransformOrigin};
 
 pub mod background;
 pub mod basic_shape;
 pub mod border;
 pub mod image;
 pub mod length;
 pub mod position;
 pub mod rect;
--- a/servo/components/style/values/computed/transform.rs
+++ b/servo/components/style/values/computed/transform.rs
@@ -1,21 +1,25 @@
 /* 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::computed::{Length, LengthOrPercentage, Number};
+use values::generics::transform::TimingFunction as GenericTimingFunction;
 use values::generics::transform::TransformOrigin as GenericTransformOrigin;
 
 /// The computed value of a CSS `<transform-origin>`
 pub type TransformOrigin = GenericTransformOrigin<LengthOrPercentage, LengthOrPercentage, Length>;
 
+/// A computed timing function.
+pub type TimingFunction = GenericTimingFunction<u32, Number>;
+
 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)
--- a/servo/components/style/values/generics/transform.rs
+++ b/servo/components/style/values/generics/transform.rs
@@ -1,28 +1,130 @@
 /* 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 euclid::Point2D;
+use std::fmt;
+use style_traits::{HasViewportPercentage, ToCss};
+use values::CSSFloat;
+
 /// A generic transform origin.
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue, ToCss)]
 pub struct TransformOrigin<H, V, Depth> {
     /// The horizontal origin.
     pub horizontal: H,
     /// The vertical origin.
     pub vertical: V,
     /// The depth.
     pub depth: Depth,
 }
 
+/// A generic timing function.
+///
+/// https://drafts.csswg.org/css-timing-1/#single-timing-function-production
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum TimingFunction<Integer, Number> {
+    /// `linear | ease | ease-in | ease-out | ease-in-out`
+    Keyword(TimingKeyword),
+    /// `cubic-bezier(<number>, <number>, <number>, <number>)`
+    CubicBezier(Point2D<Number>, Point2D<Number>),
+    /// `step-start | step-end | steps(<integer>, [ start | end ]?)`
+    Steps(Integer, StepPosition),
+    /// `frames(<integer>)`
+    Frames(Integer),
+}
+
+impl<I, N> HasViewportPercentage for TimingFunction<I, N> {
+    fn has_viewport_percentage(&self) -> bool { false }
+}
+
+define_css_keyword_enum! { TimingKeyword:
+    "linear" => Linear,
+    "ease" => Ease,
+    "ease-in" => EaseIn,
+    "ease-out" => EaseOut,
+    "ease-in-out" => EaseInOut,
+}
+add_impls_for_keyword_enum!(TimingKeyword);
+
+define_css_keyword_enum! { StepPosition:
+    "start" => Start,
+    "end" => End,
+}
+add_impls_for_keyword_enum!(StepPosition);
+
 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<Integer, Number> TimingFunction<Integer, Number> {
+    /// `ease`
+    #[inline]
+    pub fn ease() -> Self {
+        TimingFunction::Keyword(TimingKeyword::Ease)
+    }
+}
+
+impl<Integer, Number> ToCss for TimingFunction<Integer, Number>
+where
+    Integer: ToCss,
+    Number: ToCss,
+{
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
+    where
+        W: fmt::Write,
+    {
+        match *self {
+            TimingFunction::Keyword(keyword) => keyword.to_css(dest),
+            TimingFunction::CubicBezier(ref p1, ref p2) => {
+                dest.write_str("cubic-bezier(")?;
+                p1.x.to_css(dest)?;
+                dest.write_str(", ")?;
+                p1.y.to_css(dest)?;
+                dest.write_str(", ")?;
+                p2.x.to_css(dest)?;
+                dest.write_str(", ")?;
+                p2.y.to_css(dest)?;
+                dest.write_str(")")
+            },
+            TimingFunction::Steps(ref intervals, position) => {
+                dest.write_str("steps(")?;
+                intervals.to_css(dest)?;
+                if position != StepPosition::End {
+                    dest.write_str(", ")?;
+                    position.to_css(dest)?;
+                }
+                dest.write_str(")")
+            },
+            TimingFunction::Frames(ref frames) => {
+                dest.write_str("frames(")?;
+                frames.to_css(dest)?;
+                dest.write_str(")")
+            },
+        }
+    }
+}
+
+impl TimingKeyword {
+    /// Returns this timing keyword as a pair of `cubic-bezier()` points.
+    #[inline]
+    pub fn to_bezier_points(self) -> (Point2D<CSSFloat>, Point2D<CSSFloat>) {
+        match self {
+            TimingKeyword::Linear => (Point2D::new(0., 0.), Point2D::new(1., 1.)),
+            TimingKeyword::Ease => (Point2D::new(0.25, 0.1), Point2D::new(0.25, 1.)),
+            TimingKeyword::EaseIn => (Point2D::new(0.42, 0.), Point2D::new(1., 1.)),
+            TimingKeyword::EaseOut => (Point2D::new(0., 0.), Point2D::new(0.58, 1.)),
+            TimingKeyword::EaseInOut => (Point2D::new(0.42, 0.), Point2D::new(0.58, 1.)),
+        }
+    }
+}
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -39,17 +39,17 @@ pub use self::image::{ColorStop, EndingS
 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::text::{LetterSpacing, LineHeight, WordSpacing};
-pub use self::transform::TransformOrigin;
+pub use self::transform::{TimingFunction, TransformOrigin};
 
 #[cfg(feature = "gecko")]
 pub mod align;
 pub mod background;
 pub mod basic_shape;
 pub mod border;
 pub mod calc;
 pub mod color;
--- a/servo/components/style/values/specified/transform.rs
+++ b/servo/components/style/values/specified/transform.rs
@@ -1,20 +1,24 @@
 /* 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 euclid::Point2D;
 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::computed::transform::TimingFunction as ComputedTimingFunction;
+use values::generics::transform::{StepPosition, TimingFunction as GenericTimingFunction};
+use values::generics::transform::{TimingKeyword, TransformOrigin as GenericTransformOrigin};
+use values::specified::{Integer, Number};
 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))]
@@ -23,16 +27,19 @@ pub enum OriginComponent<S> {
     /// `center`
     Center,
     /// `<lop>`
     Length(LengthOrPercentage),
     /// `<side>`
     Side(S),
 }
 
+/// A specified timing function.
+pub type TimingFunction = GenericTimingFunction<Integer, Number>;
+
 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)) {
@@ -125,8 +132,126 @@ impl<S> ToComputedValue for OriginCompon
             },
         }
     }
 
     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
         OriginComponent::Length(ToComputedValue::from_computed_value(computed))
     }
 }
+
+impl Parse for TimingFunction {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        if let Ok(keyword) = input.try(TimingKeyword::parse) {
+            return Ok(GenericTimingFunction::Keyword(keyword));
+        }
+        if let Ok(ident) = input.try(|i| i.expect_ident()) {
+            let position = match_ignore_ascii_case! { &ident,
+                "step-start" => StepPosition::Start,
+                "step-end" => StepPosition::End,
+                _ => return Err(()),
+            };
+            return Ok(GenericTimingFunction::Steps(Integer::new(1), position));
+        }
+        let function = input.expect_function()?;
+        input.parse_nested_block(|i| {
+            match_ignore_ascii_case! { &function,
+                "cubic-bezier" => {
+                    let p1x = Number::parse(context, i)?;
+                    i.expect_comma()?;
+                    let p1y = Number::parse(context, i)?;
+                    i.expect_comma()?;
+                    let p2x = Number::parse(context, i)?;
+                    i.expect_comma()?;
+                    let p2y = Number::parse(context, i)?;
+
+                    if p1x.get() < 0.0 || p1x.get() > 1.0 || p2x.get() < 0.0 || p2x.get() > 1.0 {
+                        return Err(());
+                    }
+
+                    let (p1, p2) = (Point2D::new(p1x, p1y), Point2D::new(p2x, p2y));
+                    Ok(GenericTimingFunction::CubicBezier(p1, p2))
+                },
+                "steps" => {
+                    let steps = Integer::parse_positive(context, i)?;
+                    let position = i.try(|i| {
+                        i.expect_comma()?;
+                        StepPosition::parse(i)
+                    }).unwrap_or(StepPosition::End);
+                    Ok(GenericTimingFunction::Steps(steps, position))
+                },
+                "frames" => {
+                    let frames = Integer::parse_with_minimum(context, i, 2)?;
+                    Ok(GenericTimingFunction::Frames(frames))
+                },
+                _ => Err(()),
+            }
+        })
+    }
+}
+
+impl ToComputedValue for TimingFunction {
+    type ComputedValue = ComputedTimingFunction;
+
+    #[inline]
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        match *self {
+            GenericTimingFunction::Keyword(keyword) => {
+                GenericTimingFunction::Keyword(keyword)
+            },
+            GenericTimingFunction::CubicBezier(p1, p2) => {
+                GenericTimingFunction::CubicBezier(
+                    Point2D::new(
+                        p1.x.to_computed_value(context),
+                        p1.y.to_computed_value(context),
+                    ),
+                    Point2D::new(
+                        p2.x.to_computed_value(context),
+                        p2.y.to_computed_value(context),
+                    ),
+                )
+            },
+            GenericTimingFunction::Steps(steps, position) => {
+                GenericTimingFunction::Steps(
+                    steps.to_computed_value(context) as u32,
+                    position,
+                )
+            },
+            GenericTimingFunction::Frames(frames) => {
+                GenericTimingFunction::Frames(
+                    frames.to_computed_value(context) as u32,
+                )
+            },
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        match *computed {
+            GenericTimingFunction::Keyword(keyword) => {
+                GenericTimingFunction::Keyword(keyword)
+            },
+            GenericTimingFunction::CubicBezier(p1, p2) => {
+                GenericTimingFunction::CubicBezier(
+                    Point2D::new(
+                        Number::from_computed_value(&p1.x),
+                        Number::from_computed_value(&p1.y),
+                    ),
+                    Point2D::new(
+                        Number::from_computed_value(&p2.x),
+                        Number::from_computed_value(&p2.y),
+                    ),
+                )
+            },
+            GenericTimingFunction::Steps(steps, position) => {
+                GenericTimingFunction::Steps(
+                    Integer::from_computed_value(&(steps as i32)),
+                    position,
+                )
+            },
+            GenericTimingFunction::Frames(frames) => {
+                GenericTimingFunction::Frames(
+                    Integer::from_computed_value(&(frames as i32)),
+                )
+            },
+        }
+    }
+}