servo: Merge #11766 - Add `@keyframes` and `animation-*` support (from emilio:keyframes-parsing); r=SimonSapin,pcwalton
authorEmilio Cobos Álvarez <me@emiliocobos.me>
Tue, 28 Jun 2016 17:31:01 -0500
changeset 339145 8f8d74fcee9dfd19c71e2f97561fa52729ec5889
parent 339144 55c1dafe3aaffc89bfba374989343a3e35ec23d4
child 339146 9741933ef78a3a61800828ffb1ab914845516198
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersSimonSapin, pcwalton
servo: Merge #11766 - Add `@keyframes` and `animation-*` support (from emilio:keyframes-parsing); r=SimonSapin,pcwalton - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors <!-- Either: --> - [x] There are tests for these changes <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> This adds support for parsing `@keyframes` rules, and animation properties. Stylo will need it sometime soonish, plus I want to make animations work in Servo. The remaining part is doin the math and trigger the animations correctly from servo. I don't expect it to be *that* hard, but probaby I'll need to learn a bit more about the current animation infra (e.g. why the heck is the `new_animations_sender` guarded by a `Mutex`?). I'd expect to land this, since this is already a bunch of work, this is the part exclusively required by stylo (at least if we don't use Servo's machinery), the media query parsing is tested, and the properties land after a flag, but if you prefer to wait until I finish this up it's fine for me too. r? @SimonSapin cc @pcwalton @bholley Source-Repo: https://github.com/servo/servo Source-Revision: d3a81373e44634c30d31da0457e1c1e86c0911ed
servo/components/constellation/constellation.rs
servo/components/layout/animation.rs
servo/components/layout_thread/lib.rs
servo/components/script/dom/webidls/CSSStyleDeclaration.webidl
servo/components/script/script_thread.rs
servo/components/style/animation.rs
servo/components/style/context.rs
servo/components/style/keyframes.rs
servo/components/style/lib.rs
servo/components/style/matching.rs
servo/components/style/parallel.rs
servo/components/style/properties/data.py
servo/components/style/properties/helpers.mako.rs
servo/components/style/properties/helpers/animated_properties.mako.rs
servo/components/style/properties/longhand/background.mako.rs
servo/components/style/properties/longhand/border.mako.rs
servo/components/style/properties/longhand/box.mako.rs
servo/components/style/properties/longhand/color.mako.rs
servo/components/style/properties/longhand/column.mako.rs
servo/components/style/properties/longhand/counters.mako.rs
servo/components/style/properties/longhand/effects.mako.rs
servo/components/style/properties/longhand/font.mako.rs
servo/components/style/properties/longhand/inherited_box.mako.rs
servo/components/style/properties/longhand/inherited_svg.mako.rs
servo/components/style/properties/longhand/inherited_table.mako.rs
servo/components/style/properties/longhand/inherited_text.mako.rs
servo/components/style/properties/longhand/list.mako.rs
servo/components/style/properties/longhand/margin.mako.rs
servo/components/style/properties/longhand/outline.mako.rs
servo/components/style/properties/longhand/padding.mako.rs
servo/components/style/properties/longhand/pointing.mako.rs
servo/components/style/properties/longhand/position.mako.rs
servo/components/style/properties/longhand/svg.mako.rs
servo/components/style/properties/longhand/table.mako.rs
servo/components/style/properties/longhand/text.mako.rs
servo/components/style/properties/longhand/ui.mako.rs
servo/components/style/properties/longhand/xul.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/properties/shorthand/box.mako.rs
servo/components/style/selector_impl.rs
servo/components/style/selector_matching.rs
servo/components/style/servo.rs
servo/components/style/stylesheets.rs
servo/components/style/traversal.rs
servo/components/style/values.rs
servo/components/style/viewport.rs
servo/ports/geckolib/data.rs
servo/ports/geckolib/properties.mako.rs
servo/ports/geckolib/selector_impl.rs
servo/ports/geckolib/string_cache/lib.rs
servo/tests/unit/style/stylesheets.rs
--- a/servo/components/constellation/constellation.rs
+++ b/servo/components/constellation/constellation.rs
@@ -1131,17 +1131,17 @@ impl<Message, LTF, STF> Constellation<Me
                     Some(pipeline) => pipeline.script_chan.send(msg),
                     None => return warn!("Pipeline {:?} got script tick after closure.", pipeline_id),
                 }
             }
             AnimationTickType::Layout => {
                 let msg = LayoutControlMsg::TickAnimations;
                 match self.pipelines.get(&pipeline_id) {
                     Some(pipeline) => pipeline.layout_chan.send(msg),
-                    None => return warn!("Pipeline {:?} got script tick after closure.", pipeline_id),
+                    None => return warn!("Pipeline {:?} got layout tick after closure.", pipeline_id),
                 }
             }
         };
         if let Err(e) = result {
             self.handle_send_error(pipeline_id, e);
         }
     }
 
--- a/servo/components/layout/animation.rs
+++ b/servo/components/layout/animation.rs
@@ -1,102 +1,146 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! CSS transitions and animations.
 
+use context::SharedLayoutContext;
 use flow::{self, Flow};
 use gfx::display_list::OpaqueNode;
 use ipc_channel::ipc::IpcSender;
 use msg::constellation_msg::PipelineId;
 use script_layout_interface::restyle_damage::RestyleDamage;
 use script_traits::{AnimationState, LayoutMsg as ConstellationMsg};
 use std::collections::HashMap;
-use std::collections::hash_map::Entry;
 use std::sync::mpsc::Receiver;
 use style::animation::{Animation, update_style_for_animation};
+use style::selector_impl::{SelectorImplExt, ServoSelectorImpl};
 use time;
 
 /// Processes any new animations that were discovered after style recalculation.
-/// Also expire any old animations that have completed, inserting them into `expired_animations`.
-pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>,
-                              running_animations: &mut HashMap<OpaqueNode, Vec<Animation>>,
-                              expired_animations: &mut HashMap<OpaqueNode, Vec<Animation>>,
-                              new_animations_receiver: &Receiver<Animation>,
-                              pipeline_id: PipelineId) {
-    let mut new_running_animations = Vec::new();
+/// Also expire any old animations that have completed, inserting them into
+/// `expired_animations`.
+pub fn update_animation_state<Impl: SelectorImplExt>(constellation_chan: &IpcSender<ConstellationMsg>,
+                                                     running_animations: &mut HashMap<OpaqueNode, Vec<Animation<Impl>>>,
+                                                     expired_animations: &mut HashMap<OpaqueNode, Vec<Animation<Impl>>>,
+                                                     new_animations_receiver: &Receiver<Animation<Impl>>,
+                                                     pipeline_id: PipelineId) {
+    let mut new_running_animations = vec![];
     while let Ok(animation) = new_animations_receiver.try_recv() {
-        new_running_animations.push(animation)
+        let mut should_push = true;
+        if let Animation::Keyframes(ref node, ref name, ref state) = animation {
+            // If the animation was already present in the list for the
+            // node, just update its state, else push the new animation to
+            // run.
+            if let Some(ref mut animations) = running_animations.get_mut(node) {
+                // TODO: This being linear is probably not optimal.
+                for mut anim in animations.iter_mut() {
+                    if let Animation::Keyframes(_, ref anim_name, ref mut anim_state) = *anim {
+                        if *name == *anim_name {
+                            debug!("update_animation_state: Found other animation {}", name);
+                            anim_state.update_from_other(&state);
+                            should_push = false;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        if should_push {
+            new_running_animations.push(animation);
+        }
     }
 
     if running_animations.is_empty() && new_running_animations.is_empty() {
         // Nothing to do. Return early so we don't flood the compositor with
         // `ChangeRunningAnimationsState` messages.
         return
     }
 
+    let now = time::precise_time_s();
     // Expire old running animations.
-    let now = time::precise_time_s();
-    let mut keys_to_remove = Vec::new();
+    //
+    // TODO: Do not expunge Keyframes animations, since we need that state if
+    // the animation gets re-triggered. Probably worth splitting in two
+    // different maps, or at least using a linked list?
+    let mut keys_to_remove = vec![];
     for (key, running_animations) in running_animations.iter_mut() {
         let mut animations_still_running = vec![];
-        for running_animation in running_animations.drain(..) {
-            if now < running_animation.end_time {
+        for mut running_animation in running_animations.drain(..) {
+            let still_running = !running_animation.is_expired() && match running_animation {
+                Animation::Transition(_, started_at, ref frame, _expired) => {
+                    now < started_at + frame.duration
+                }
+                Animation::Keyframes(_, _, ref mut state) => {
+                    // This animation is still running, or we need to keep
+                    // iterating.
+                    now < state.started_at + state.duration || state.tick()
+                }
+            };
+
+            if still_running {
                 animations_still_running.push(running_animation);
                 continue
             }
-            match expired_animations.entry(*key) {
-                Entry::Vacant(entry) => {
-                    entry.insert(vec![running_animation]);
-                }
-                Entry::Occupied(mut entry) => entry.get_mut().push(running_animation),
-            }
+
+            expired_animations.entry(*key)
+                              .or_insert_with(Vec::new)
+                              .push(running_animation);
         }
-        if animations_still_running.len() == 0 {
+
+        if animations_still_running.is_empty() {
             keys_to_remove.push(*key);
         } else {
             *running_animations = animations_still_running
         }
     }
+
     for key in keys_to_remove {
         running_animations.remove(&key).unwrap();
     }
 
     // Add new running animations.
     for new_running_animation in new_running_animations {
-        match running_animations.entry(new_running_animation.node) {
-            Entry::Vacant(entry) => {
-                entry.insert(vec![new_running_animation]);
-            }
-            Entry::Occupied(mut entry) => entry.get_mut().push(new_running_animation),
-        }
+        running_animations.entry(*new_running_animation.node())
+                          .or_insert_with(Vec::new)
+                          .push(new_running_animation);
     }
 
-    let animation_state;
-    if running_animations.is_empty() {
-        animation_state = AnimationState::NoAnimationsPresent;
+    let animation_state = if running_animations.is_empty() {
+        AnimationState::NoAnimationsPresent
     } else {
-        animation_state = AnimationState::AnimationsPresent;
-    }
+        AnimationState::AnimationsPresent
+    };
 
     constellation_chan.send(ConstellationMsg::ChangeRunningAnimationsState(pipeline_id, animation_state))
                       .unwrap();
 }
 
-/// Recalculates style for a set of animations. This does *not* run with the DOM lock held.
-pub fn recalc_style_for_animations(flow: &mut Flow,
-                                   animations: &HashMap<OpaqueNode, Vec<Animation>>) {
+/// Recalculates style for a set of animations. This does *not* run with the DOM
+/// lock held.
+// NB: This is specific for ServoSelectorImpl, since the layout context and the
+// flows are ServoSelectorImpl specific too. If that goes away at some point,
+// this should be made generic.
+pub fn recalc_style_for_animations(context: &SharedLayoutContext,
+                                   flow: &mut Flow,
+                                   animations: &HashMap<OpaqueNode,
+                                                        Vec<Animation<ServoSelectorImpl>>>) {
     let mut damage = RestyleDamage::empty();
     flow.mutate_fragments(&mut |fragment| {
         if let Some(ref animations) = animations.get(&fragment.node) {
-            for animation in *animations {
-                update_style_for_animation(animation, &mut fragment.style, Some(&mut damage));
+            for animation in animations.iter() {
+                update_style_for_animation(&context.style_context,
+                                           animation,
+                                           &mut fragment.style,
+                                           Some(&mut damage));
             }
         }
     });
 
     let base = flow::mut_base(flow);
     base.restyle_damage.insert(damage);
     for kid in base.children.iter_mut() {
-        recalc_style_for_animations(kid, animations)
+        recalc_style_for_animations(context, kid, animations)
     }
 }
--- a/servo/components/layout_thread/lib.rs
+++ b/servo/components/layout_thread/lib.rs
@@ -93,28 +93,27 @@ use script_traits::{ConstellationControl
 use script_traits::{StackingContextScrollState, UntrustedNodeAddress};
 use std::borrow::ToOwned;
 use std::collections::HashMap;
 use std::hash::BuildHasherDefault;
 use std::ops::{Deref, DerefMut};
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::sync::mpsc::{channel, Sender, Receiver};
 use std::sync::{Arc, Mutex, MutexGuard, RwLock};
-use style::animation::Animation;
 use style::computed_values::{filter, mix_blend_mode};
 use style::context::ReflowGoal;
 use style::dom::{TDocument, TElement, TNode};
 use style::error_reporting::ParseErrorReporter;
 use style::logical_geometry::LogicalPoint;
 use style::media_queries::{Device, MediaType};
 use style::parallel::WorkQueueData;
 use style::properties::ComputedValues;
 use style::refcell::RefCell;
 use style::selector_matching::USER_OR_USER_AGENT_STYLESHEETS;
-use style::servo::{SharedStyleContext, Stylesheet, Stylist};
+use style::servo::{Animation, SharedStyleContext, Stylesheet, Stylist};
 use style::stylesheets::CSSRuleIteratorExt;
 use url::Url;
 use util::geometry::MAX_RECT;
 use util::ipc::OptionalIpcSender;
 use util::opts;
 use util::thread;
 use util::thread_state;
 use util::workqueue::WorkQueue;
@@ -1285,35 +1284,36 @@ impl LayoutThread {
         rw_data.stacking_context_scroll_offsets = layout_scroll_states
     }
 
     fn tick_all_animations<'a, 'b>(&mut self, possibly_locked_rw_data: &mut RwData<'a, 'b>) {
         let mut rw_data = possibly_locked_rw_data.lock();
         self.tick_animations(&mut rw_data);
     }
 
-    pub fn tick_animations(&mut self, rw_data: &mut LayoutThreadData) {
+    fn tick_animations(&mut self, rw_data: &mut LayoutThreadData) {
         let reflow_info = Reflow {
             goal: ReflowGoal::ForDisplay,
             page_clip_rect: MAX_RECT,
         };
 
         let mut layout_context = self.build_shared_layout_context(&*rw_data,
                                                                   false,
                                                                   reflow_info.goal);
 
         if let Some(mut root_flow) = self.root_flow.clone() {
             // Perform an abbreviated style recalc that operates without access to the DOM.
             let animations = self.running_animations.read().unwrap();
             profile(time::ProfilerCategory::LayoutStyleRecalc,
                     self.profiler_metadata(),
                     self.time_profiler_chan.clone(),
                     || {
-                        animation::recalc_style_for_animations(flow_ref::deref_mut(&mut root_flow),
-                                                               &*animations)
+                        animation::recalc_style_for_animations(&layout_context,
+                                                               flow_ref::deref_mut(&mut root_flow),
+                                                               &animations)
                     });
         }
 
         self.perform_post_style_recalc_layout_passes(&reflow_info,
                                                      &mut *rw_data,
                                                      &mut layout_context);
     }
 
--- a/servo/components/script/dom/webidls/CSSStyleDeclaration.webidl
+++ b/servo/components/script/dom/webidls/CSSStyleDeclaration.webidl
@@ -322,9 +322,26 @@ partial interface CSSStyleDeclaration {
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flexBasis;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flex-basis;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flexGrow;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flex-grow;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flexShrink;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flex-shrink;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString alignSelf;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString align-self;
+
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-name;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationName;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-duration;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationDuration;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-timing-function;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationTimingFunction;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-iteration-count;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationIterationCount;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-direction;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationDirection;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-play-state;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationPlayState;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-fill-mode;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationFillMode;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-delay;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationDelay;
 };
--- a/servo/components/script/script_thread.rs
+++ b/servo/components/script/script_thread.rs
@@ -666,17 +666,17 @@ impl ScriptThread {
             }
         }
 
         for (id, (size, size_type)) in resizes {
             self.handle_event(id, ResizeEvent(size, size_type));
         }
 
         // Store new resizes, and gather all other events.
-        let mut sequential = vec!();
+        let mut sequential = vec![];
 
         // Receive at least one message so we don't spinloop.
         let mut event = {
             let sel = Select::new();
             let mut script_port = sel.handle(&self.port);
             let mut control_port = sel.handle(&self.control_port);
             let mut timer_event_port = sel.handle(&self.timer_event_port);
             let mut devtools_port = sel.handle(&self.devtools_port);
--- a/servo/components/style/animation.rs
+++ b/servo/components/style/animation.rs
@@ -1,1014 +1,686 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! CSS transitions and animations.
 
-use app_units::Au;
 use bezier::Bezier;
-use cssparser::{Color, RGBA};
+use context::SharedStyleContext;
 use dom::{OpaqueNode, TRestyleDamage};
 use euclid::point::Point2D;
-use properties::longhands::background_position::computed_value::T as BackgroundPosition;
-use properties::longhands::border_spacing::computed_value::T as BorderSpacing;
-use properties::longhands::clip::computed_value::ClipRect;
-use properties::longhands::font_weight::computed_value::T as FontWeight;
-use properties::longhands::line_height::computed_value::T as LineHeight;
-use properties::longhands::text_shadow::computed_value::T as TextShadowList;
-use properties::longhands::text_shadow::computed_value::TextShadow;
-use properties::longhands::transform::computed_value::ComputedMatrix;
-use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation;
-use properties::longhands::transform::computed_value::T as TransformList;
-use properties::longhands::transition_property;
-use properties::longhands::transition_property::computed_value::TransitionProperty;
+use keyframes::{KeyframesStep, KeyframesStepValue};
+use properties::animated_properties::{AnimatedProperty, TransitionProperty};
+use properties::longhands::animation_direction::computed_value::AnimationDirection;
+use properties::longhands::animation_iteration_count::computed_value::AnimationIterationCount;
+use properties::longhands::animation_play_state::computed_value::AnimationPlayState;
 use properties::longhands::transition_timing_function::computed_value::StartEnd;
 use properties::longhands::transition_timing_function::computed_value::TransitionTimingFunction;
-use properties::longhands::vertical_align::computed_value::T as VerticalAlign;
-use properties::longhands::visibility::computed_value::T as Visibility;
-use properties::longhands::z_index::computed_value::T as ZIndex;
 use properties::style_struct_traits::Box;
-use properties::{ComputedValues, ServoComputedValues};
-use std::cmp::Ordering;
-use std::iter::repeat;
+use properties::{self, ComputedValues};
+use selector_impl::SelectorImplExt;
+use selectors::matching::DeclarationBlock;
 use std::sync::mpsc::Sender;
 use std::sync::{Arc, Mutex};
+use string_cache::Atom;
 use time;
-use values::CSSFloat;
-use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone};
-use values::computed::{CalcLengthOrPercentage, Length, LengthOrPercentage, Time};
+use values::computed::Time;
+
+/// 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,
+    // current, max
+    Finite(u32, u32),
+}
+
+/// This structure represents wether an animation is actually running.
+///
+/// An animation can be running, or paused at a given time.
+#[derive(Debug, Clone)]
+pub enum KeyframesRunningState {
+    /// This animation is paused. The inner field is the percentage of progress
+    /// when it was paused, from 0 to 1.
+    Paused(f64),
+    /// This animation is actually running.
+    Running,
+}
+
+/// This structure represents the current keyframe animation state, i.e., the
+/// duration, the current and maximum iteration count, and the state (either
+/// playing or paused).
+// TODO: unify the use of f32/f64 in this file.
+#[derive(Debug, Clone)]
+pub struct KeyframesAnimationState<Impl: SelectorImplExt> {
+    /// The time this animation started at.
+    pub started_at: f64,
+    /// The duration of this animation.
+    pub duration: f64,
+    /// The delay of the animation.
+    pub delay: f64,
+    /// The current iteration state for the animation.
+    pub iteration_state: KeyframesIterationState,
+    /// Werther this animation is paused.
+    pub running_state: KeyframesRunningState,
+    /// The declared animation direction of this animation.
+    pub direction: AnimationDirection,
+    /// The current animation direction. This can only be `normal` or `reverse`.
+    pub current_direction: AnimationDirection,
+    /// Werther this keyframe animation is outdated due to a restyle.
+    pub expired: bool,
+    /// The original cascade style, needed to compute the generated keyframes of
+    /// the animation.
+    pub cascade_style: Arc<Impl::ComputedValues>,
+}
+
+impl<Impl: SelectorImplExt> KeyframesAnimationState<Impl> {
+    /// Performs a tick in the animation state, i.e., increments the counter of
+    /// the current iteration count, updates times and then toggles the
+    /// direction if appropriate.
+    ///
+    /// Returns true if the animation should keep running.
+    pub fn tick(&mut self) -> bool {
+        debug!("KeyframesAnimationState::tick");
+
+        self.started_at += self.duration + self.delay;
+        match self.running_state {
+            // If it's paused, don't update direction or iteration count.
+            KeyframesRunningState::Paused(_) => return true,
+            KeyframesRunningState::Running => {},
+        }
+
+        if let KeyframesIterationState::Finite(ref mut current, ref max) = self.iteration_state {
+            *current += 1;
+            // NB: This prevent us from updating the direction, which might be
+            // needed for the correct handling of animation-fill-mode.
+            if *current >= *max {
+                return false;
+            }
+        }
+
+        // Update the next iteration direction if applicable.
+        match self.direction {
+            AnimationDirection::alternate |
+            AnimationDirection::alternate_reverse => {
+                self.current_direction = match self.current_direction {
+                    AnimationDirection::normal => AnimationDirection::reverse,
+                    AnimationDirection::reverse => AnimationDirection::normal,
+                    _ => unreachable!(),
+                };
+            }
+            _ => {},
+        }
+
+        true
+    }
+
+    /// Updates the appropiate state from other animation.
+    ///
+    /// This happens when an animation is re-submitted to layout, presumably
+    /// because of an state change.
+    ///
+    /// There are some bits of state we can't just replace, over all taking in
+    /// account times, so here's that logic.
+    pub fn update_from_other(&mut self, other: &Self) {
+        use self::KeyframesRunningState::*;
+
+        debug!("KeyframesAnimationState::update_from_other({:?}, {:?})", self, other);
+
+        // NB: We shall not touch the started_at field, since we don't want to
+        // restart the animation.
+        let old_started_at = self.started_at;
+        let old_duration = self.duration;
+        let old_direction = self.current_direction;
+        let old_running_state = self.running_state.clone();
+        let old_iteration_state = self.iteration_state.clone();
+        *self = other.clone();
+
+        let mut new_started_at = old_started_at;
+
+        // If we're unpausing the animation, fake the start time so we seem to
+        // restore it.
+        //
+        // If the animation keeps paused, keep the old value.
+        //
+        // If we're pausing the animation, compute the progress value.
+        match (&mut self.running_state, old_running_state) {
+            (&mut Running, Paused(progress))
+                => new_started_at = time::precise_time_s() - (self.duration * progress),
+            (&mut Paused(ref mut new), Paused(old))
+                => *new = old,
+            (&mut Paused(ref mut progress), Running)
+                => *progress = (time::precise_time_s() - old_started_at) / old_duration,
+            _ => {},
+        }
+
+        // Don't update the iteration count, just the iteration limit.
+        // TODO: see how changing the limit affects rendering in other browsers.
+        // We might need to keep the iteration count even when it's infinite.
+        match (&mut self.iteration_state, old_iteration_state) {
+            (&mut KeyframesIterationState::Finite(ref mut iters, _), KeyframesIterationState::Finite(old_iters, _))
+                => *iters = old_iters,
+            _ => {}
+        }
+
+        self.current_direction = old_direction;
+        self.started_at = new_started_at;
+    }
+
+    #[inline]
+    fn is_paused(&self) -> bool {
+        match self.running_state {
+            KeyframesRunningState::Paused(..) => true,
+            KeyframesRunningState::Running => false,
+        }
+    }
+}
 
 /// State relating to an animation.
-#[derive(Clone)]
-pub struct Animation {
-    /// An opaque reference to the DOM node participating in the animation.
-    pub node: OpaqueNode,
-    /// A description of the property animation that is occurring.
-    pub property_animation: PropertyAnimation,
-    /// The start time of the animation, as returned by `time::precise_time_s()`.
-    pub start_time: f64,
-    /// The end time of the animation, as returned by `time::precise_time_s()`.
-    pub end_time: f64,
+#[derive(Clone, Debug)]
+pub enum Animation<Impl: SelectorImplExt> {
+    /// A transition is just a single frame triggered at a time, with a reflow.
+    ///
+    /// the f64 field is the start time as returned by `time::precise_time_s()`.
+    ///
+    /// The `bool` field is werther this animation should no longer run.
+    Transition(OpaqueNode, f64, AnimationFrame, bool),
+    /// A keyframes animation is identified by a name, and can have a
+    /// node-dependent state (i.e. iteration count, etc.).
+    Keyframes(OpaqueNode, Atom, KeyframesAnimationState<Impl>),
 }
 
-impl Animation {
-    /// Returns the duration of this animation in seconds.
+impl<Impl: SelectorImplExt> Animation<Impl> {
+    #[inline]
+    pub fn mark_as_expired(&mut self) {
+        debug_assert!(!self.is_expired());
+        match *self {
+            Animation::Transition(_, _, _, ref mut expired) => *expired = true,
+            Animation::Keyframes(_, _, ref mut state) => state.expired = true,
+        }
+    }
+
     #[inline]
-    pub fn duration(&self) -> f64 {
-        self.end_time - self.start_time
+    pub fn is_expired(&self) -> bool {
+        match *self {
+            Animation::Transition(_, _, _, expired) => expired,
+            Animation::Keyframes(_, _, ref state) => state.expired,
+        }
+    }
+
+    #[inline]
+    pub fn node(&self) -> &OpaqueNode {
+        match *self {
+            Animation::Transition(ref node, _, _, _) => node,
+            Animation::Keyframes(ref node, _, _) => node,
+        }
+    }
+
+    #[inline]
+    pub fn is_paused(&self) -> bool {
+        match *self {
+            Animation::Transition(..) => false,
+            Animation::Keyframes(_, _, ref state) => state.is_paused(),
+        }
     }
 }
 
 
-#[derive(Clone, Debug)]
+/// A single animation frame of a single property.
+#[derive(Debug, Clone)]
+pub struct AnimationFrame {
+    /// A description of the property animation that is occurring.
+    pub property_animation: PropertyAnimation,
+    /// The duration of the animation. This is either relative in the keyframes
+    /// case (a number between 0 and 1), or absolute in the transition case.
+    pub duration: f64,
+}
+
+#[derive(Debug, Clone)]
 pub struct PropertyAnimation {
     property: AnimatedProperty,
     timing_function: TransitionTimingFunction,
-    duration: Time,
+    duration: Time, // TODO: isn't this just repeated?
 }
 
 impl PropertyAnimation {
     /// Creates a new property animation for the given transition index and old and new styles.
     /// Any number of animations may be returned, from zero (if the property did not animate) to
     /// one (for a single transition property) to arbitrarily many (for `all`).
-    pub fn from_transition(transition_index: usize,
-                           old_style: &ServoComputedValues,
-                           new_style: &mut ServoComputedValues)
-                           -> Vec<PropertyAnimation> {
-        let mut result = Vec::new();
-        let transition_property =
-            new_style.as_servo().get_box().transition_property.0[transition_index];
+    pub fn from_transition<C: ComputedValues>(transition_index: usize,
+                                              old_style: &C,
+                                              new_style: &mut C)
+                                              -> Vec<PropertyAnimation> {
+        let mut result = vec![];
+        let box_style = new_style.as_servo().get_box();
+        let transition_property = box_style.transition_property.0[transition_index];
+        let timing_function = *box_style.transition_timing_function.0.get_mod(transition_index);
+        let duration = *box_style.transition_duration.0.get_mod(transition_index);
+
+
         if transition_property != TransitionProperty::All {
             if let Some(property_animation) =
                     PropertyAnimation::from_transition_property(transition_property,
-                                                                transition_index,
+                                                                timing_function,
+                                                                duration,
                                                                 old_style,
                                                                 new_style) {
                 result.push(property_animation)
             }
             return result
         }
 
-        for transition_property in
-                transition_property::computed_value::ALL_TRANSITION_PROPERTIES.iter() {
+        TransitionProperty::each(|transition_property| {
             if let Some(property_animation) =
-                    PropertyAnimation::from_transition_property(*transition_property,
-                                                                transition_index,
+                    PropertyAnimation::from_transition_property(transition_property,
+                                                                timing_function,
+                                                                duration,
                                                                 old_style,
                                                                 new_style) {
                 result.push(property_animation)
             }
-        }
+        });
 
         result
     }
 
-    fn from_transition_property(transition_property: TransitionProperty,
-                                transition_index: usize,
-                                old_style: &ServoComputedValues,
-                                new_style: &mut ServoComputedValues)
-                                -> Option<PropertyAnimation> {
-        let box_style = new_style.get_box();
-        macro_rules! match_transition {
-                ( $( [$name:ident; $structname:ident; $field:ident] ),* ) => {
-                    match transition_property {
-                        TransitionProperty::All => {
-                            panic!("Don't use `TransitionProperty::All` with \
-                                   `PropertyAnimation::from_transition_property`!")
-                        }
-                        $(
-                            TransitionProperty::$name => {
-                                AnimatedProperty::$name(old_style.$structname().$field,
-                                                        new_style.$structname().$field)
-                            }
-                        )*
-                        TransitionProperty::Clip => {
-                            AnimatedProperty::Clip(old_style.get_effects().clip.0,
-                                                   new_style.get_effects().clip.0)
-                        }
-                        TransitionProperty::LetterSpacing => {
-                            AnimatedProperty::LetterSpacing(old_style.get_inheritedtext().letter_spacing.0,
-                                                            new_style.get_inheritedtext().letter_spacing.0)
-                        }
-                        TransitionProperty::TextShadow => {
-                            AnimatedProperty::TextShadow(old_style.get_inheritedtext().text_shadow.clone(),
-                                                         new_style.get_inheritedtext().text_shadow.clone())
-                        }
-                        TransitionProperty::Transform => {
-                            AnimatedProperty::Transform(old_style.get_effects().transform.clone(),
-                                                        new_style.get_effects().transform.clone())
-                        }
-                        TransitionProperty::WordSpacing => {
-                            AnimatedProperty::WordSpacing(old_style.get_inheritedtext().word_spacing.0,
-                                                          new_style.get_inheritedtext().word_spacing.0)
-                        }
-                    }
-                }
-        }
-        let animated_property = match_transition!(
-            [BackgroundColor; get_background; background_color],
-            [BackgroundPosition; get_background; background_position],
-            [BorderBottomColor; get_border; border_bottom_color],
-            [BorderBottomWidth; get_border; border_bottom_width],
-            [BorderLeftColor; get_border; border_left_color],
-            [BorderLeftWidth; get_border; border_left_width],
-            [BorderRightColor; get_border; border_right_color],
-            [BorderRightWidth; get_border; border_right_width],
-            [BorderSpacing; get_inheritedtable; border_spacing],
-            [BorderTopColor; get_border; border_top_color],
-            [BorderTopWidth; get_border; border_top_width],
-            [Bottom; get_position; bottom],
-            [Color; get_color; color],
-            [FontSize; get_font; font_size],
-            [FontWeight; get_font; font_weight],
-            [Height; get_position; height],
-            [Left; get_position; left],
-            [LineHeight; get_inheritedtext; line_height],
-            [MarginBottom; get_margin; margin_bottom],
-            [MarginLeft; get_margin; margin_left],
-            [MarginRight; get_margin; margin_right],
-            [MarginTop; get_margin; margin_top],
-            [MaxHeight; get_position; max_height],
-            [MaxWidth; get_position; max_width],
-            [MinHeight; get_position; min_height],
-            [MinWidth; get_position; min_width],
-            [Opacity; get_effects; opacity],
-            [OutlineColor; get_outline; outline_color],
-            [OutlineWidth; get_outline; outline_width],
-            [PaddingBottom; get_padding; padding_bottom],
-            [PaddingLeft; get_padding; padding_left],
-            [PaddingRight; get_padding; padding_right],
-            [PaddingTop; get_padding; padding_top],
-            [Right; get_position; right],
-            [TextIndent; get_inheritedtext; text_indent],
-            [Top; get_position; top],
-            [VerticalAlign; get_box; vertical_align],
-            [Visibility; get_inheritedbox; visibility],
-            [Width; get_position; width],
-            [ZIndex; get_position; z_index]);
+    fn from_transition_property<C: ComputedValues>(transition_property: TransitionProperty,
+                                                   timing_function: TransitionTimingFunction,
+                                                   duration: Time,
+                                                   old_style: &C,
+                                                   new_style: &C)
+                                                   -> Option<PropertyAnimation> {
+        let animated_property = AnimatedProperty::from_transition_property(&transition_property,
+                                                                           old_style,
+                                                                           new_style);
 
         let property_animation = PropertyAnimation {
             property: animated_property,
-            timing_function:
-                *box_style.transition_timing_function.0.get_mod(transition_index),
-            duration: *box_style.transition_duration.0.get_mod(transition_index),
+            timing_function: timing_function,
+            duration: duration,
         };
-        if property_animation.does_not_animate() {
+
+        if property_animation.does_animate() {
+            Some(property_animation)
+        } else {
             None
-        } else {
-            Some(property_animation)
         }
     }
 
-    pub fn update(&self, style: &mut ServoComputedValues, time: f64) {
+    pub fn update<C: ComputedValues>(&self, style: &mut C, time: f64) {
         let progress = match self.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)
             }
             TransitionTimingFunction::Steps(steps, StartEnd::Start) => {
                 (time * (steps as f64)).ceil() / (steps as f64)
             }
             TransitionTimingFunction::Steps(steps, StartEnd::End) => {
                 (time * (steps as f64)).floor() / (steps as f64)
             }
         };
 
-        macro_rules! match_property(
-            ( $( [$name:ident; $structname:ident; $field:ident] ),* ) => {
-                match self.property {
-                    $(
-                        AnimatedProperty::$name(ref start, ref end) => {
-                            if let Some(value) = start.interpolate(end, progress) {
-                                style.$structname().$field = value
-                            }
-                        }
-                    )*
-                    AnimatedProperty::Clip(ref start, ref end) => {
-                        if let Some(value) = start.interpolate(end, progress) {
-                            style.mutate_effects().clip.0 = value
-                        }
-                    }
-                    AnimatedProperty::LetterSpacing(ref start, ref end) => {
-                        if let Some(value) = start.interpolate(end, progress) {
-                            style.mutate_inheritedtext().letter_spacing.0 = value
-                        }
-                    }
-                    AnimatedProperty::WordSpacing(ref start, ref end) => {
-                        if let Some(value) = start.interpolate(end, progress) {
-                            style.mutate_inheritedtext().word_spacing.0 = value
-                        }
-                    }
-                 }
-            });
-        match_property!(
-            [BackgroundColor; mutate_background; background_color],
-            [BackgroundPosition; mutate_background; background_position],
-            [BorderBottomColor; mutate_border; border_bottom_color],
-            [BorderBottomWidth; mutate_border; border_bottom_width],
-            [BorderLeftColor; mutate_border; border_left_color],
-            [BorderLeftWidth; mutate_border; border_left_width],
-            [BorderRightColor; mutate_border; border_right_color],
-            [BorderRightWidth; mutate_border; border_right_width],
-            [BorderSpacing; mutate_inheritedtable; border_spacing],
-            [BorderTopColor; mutate_border; border_top_color],
-            [BorderTopWidth; mutate_border; border_top_width],
-            [Bottom; mutate_position; bottom],
-            [Color; mutate_color; color],
-            [FontSize; mutate_font; font_size],
-            [FontWeight; mutate_font; font_weight],
-            [Height; mutate_position; height],
-            [Left; mutate_position; left],
-            [LineHeight; mutate_inheritedtext; line_height],
-            [MarginBottom; mutate_margin; margin_bottom],
-            [MarginLeft; mutate_margin; margin_left],
-            [MarginRight; mutate_margin; margin_right],
-            [MarginTop; mutate_margin; margin_top],
-            [MaxHeight; mutate_position; max_height],
-            [MaxWidth; mutate_position; max_width],
-            [MinHeight; mutate_position; min_height],
-            [MinWidth; mutate_position; min_width],
-            [Opacity; mutate_effects; opacity],
-            [OutlineColor; mutate_outline; outline_color],
-            [OutlineWidth; mutate_outline; outline_width],
-            [PaddingBottom; mutate_padding; padding_bottom],
-            [PaddingLeft; mutate_padding; padding_left],
-            [PaddingRight; mutate_padding; padding_right],
-            [PaddingTop; mutate_padding; padding_top],
-            [Right; mutate_position; right],
-            [TextIndent; mutate_inheritedtext; text_indent],
-            [TextShadow; mutate_inheritedtext; text_shadow],
-            [Top; mutate_position; top],
-            [Transform; mutate_effects; transform],
-            [VerticalAlign; mutate_box; vertical_align],
-            [Visibility; mutate_inheritedbox; visibility],
-            [Width; mutate_position; width],
-            [ZIndex; mutate_position; z_index]);
+        self.property.update(style, progress);
     }
 
     #[inline]
-    fn does_not_animate(&self) -> bool {
-        self.property.does_not_animate() || self.duration == Time(0.0)
-    }
-}
-
-#[derive(Clone, Debug)]
-enum AnimatedProperty {
-    BackgroundColor(Color, Color),
-    BackgroundPosition(BackgroundPosition, BackgroundPosition),
-    BorderBottomColor(Color, Color),
-    BorderBottomWidth(Length, Length),
-    BorderLeftColor(Color, Color),
-    BorderLeftWidth(Length, Length),
-    BorderRightColor(Color, Color),
-    BorderRightWidth(Length, Length),
-    BorderSpacing(BorderSpacing, BorderSpacing),
-    BorderTopColor(Color, Color),
-    BorderTopWidth(Length, Length),
-    Bottom(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto),
-    Color(RGBA, RGBA),
-    Clip(Option<ClipRect>, Option<ClipRect>),
-    FontSize(Length, Length),
-    FontWeight(FontWeight, FontWeight),
-    Height(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto),
-    Left(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto),
-    LetterSpacing(Option<Au>, Option<Au>),
-    LineHeight(LineHeight, LineHeight),
-    MarginBottom(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto),
-    MarginLeft(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto),
-    MarginRight(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto),
-    MarginTop(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto),
-    MaxHeight(LengthOrPercentageOrNone, LengthOrPercentageOrNone),
-    MaxWidth(LengthOrPercentageOrNone, LengthOrPercentageOrNone),
-    MinHeight(LengthOrPercentage, LengthOrPercentage),
-    MinWidth(LengthOrPercentage, LengthOrPercentage),
-    Opacity(CSSFloat, CSSFloat),
-    OutlineColor(Color, Color),
-    OutlineWidth(Length, Length),
-    PaddingBottom(LengthOrPercentage, LengthOrPercentage),
-    PaddingLeft(LengthOrPercentage, LengthOrPercentage),
-    PaddingRight(LengthOrPercentage, LengthOrPercentage),
-    PaddingTop(LengthOrPercentage, LengthOrPercentage),
-    Right(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto),
-    TextIndent(LengthOrPercentage, LengthOrPercentage),
-    TextShadow(TextShadowList, TextShadowList),
-    Top(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto),
-    Transform(TransformList, TransformList),
-    VerticalAlign(VerticalAlign, VerticalAlign),
-    Visibility(Visibility, Visibility),
-    Width(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto),
-    WordSpacing(Option<Au>, Option<Au>),
-    ZIndex(ZIndex, ZIndex),
-}
-
-impl AnimatedProperty {
-    #[inline]
-    fn does_not_animate(&self) -> bool {
-        match *self {
-            AnimatedProperty::Top(ref a, ref b) |
-            AnimatedProperty::Right(ref a, ref b) |
-            AnimatedProperty::Bottom(ref a, ref b) |
-            AnimatedProperty::Left(ref a, ref b) |
-            AnimatedProperty::MarginTop(ref a, ref b) |
-            AnimatedProperty::MarginRight(ref a, ref b) |
-            AnimatedProperty::MarginBottom(ref a, ref b) |
-            AnimatedProperty::MarginLeft(ref a, ref b) |
-            AnimatedProperty::Width(ref a, ref b) |
-            AnimatedProperty::Height(ref a, ref b) => a == b,
-            AnimatedProperty::MaxWidth(ref a, ref b) |
-            AnimatedProperty::MaxHeight(ref a, ref b) => a == b,
-            AnimatedProperty::MinWidth(ref a, ref b) |
-            AnimatedProperty::MinHeight(ref a, ref b) |
-            AnimatedProperty::TextIndent(ref a, ref b) => a == b,
-            AnimatedProperty::FontSize(ref a, ref b) |
-            AnimatedProperty::BorderTopWidth(ref a, ref b) |
-            AnimatedProperty::BorderRightWidth(ref a, ref b) |
-            AnimatedProperty::BorderBottomWidth(ref a, ref b) |
-            AnimatedProperty::BorderLeftWidth(ref a, ref b) => a == b,
-            AnimatedProperty::BorderTopColor(ref a, ref b) |
-            AnimatedProperty::BorderRightColor(ref a, ref b) |
-            AnimatedProperty::BorderBottomColor(ref a, ref b) |
-            AnimatedProperty::BorderLeftColor(ref a, ref b) |
-            AnimatedProperty::OutlineColor(ref a, ref b) |
-            AnimatedProperty::BackgroundColor(ref a, ref b) => a == b,
-            AnimatedProperty::PaddingTop(ref a, ref b) |
-            AnimatedProperty::PaddingRight(ref a, ref b) |
-            AnimatedProperty::PaddingBottom(ref a, ref b) |
-            AnimatedProperty::PaddingLeft(ref a, ref b) => a == b,
-            AnimatedProperty::LineHeight(ref a, ref b) => a == b,
-            AnimatedProperty::LetterSpacing(ref a, ref b) => a == b,
-            AnimatedProperty::BackgroundPosition(ref a, ref b) => a == b,
-            AnimatedProperty::BorderSpacing(ref a, ref b) => a == b,
-            AnimatedProperty::Clip(ref a, ref b) => a == b,
-            AnimatedProperty::Color(ref a, ref b) => a == b,
-            AnimatedProperty::FontWeight(ref a, ref b) => a == b,
-            AnimatedProperty::Opacity(ref a, ref b) => a == b,
-            AnimatedProperty::OutlineWidth(ref a, ref b) => a == b,
-            AnimatedProperty::TextShadow(ref a, ref b) => a == b,
-            AnimatedProperty::VerticalAlign(ref a, ref b) => a == b,
-            AnimatedProperty::Visibility(ref a, ref b) => a == b,
-            AnimatedProperty::WordSpacing(ref a, ref b) => a == b,
-            AnimatedProperty::ZIndex(ref a, ref b) => a == b,
-            AnimatedProperty::Transform(ref a, ref b) => a == b,
-        }
-    }
-}
-
-/// A trait used to implement [interpolation][interpolated-types].
-///
-/// [interpolated-types]: https://drafts.csswg.org/css-transitions/#interpolated-types
-trait Interpolate: Sized {
-    fn interpolate(&self, other: &Self, time: f64) -> Option<Self>;
-}
-
-impl Interpolate for Au {
-    #[inline]
-    fn interpolate(&self, other: &Au, time: f64) -> Option<Au> {
-        Some(Au((self.0 as f64 + (other.0 as f64 - self.0 as f64) * time).round() as i32))
-    }
-}
-
-impl <T> Interpolate for Option<T> where T: Interpolate {
-    #[inline]
-    fn interpolate(&self, other: &Option<T>, time: f64) -> Option<Option<T>> {
-        match (self, other) {
-            (&Some(ref this), &Some(ref other)) => {
-                this.interpolate(other, time).and_then(|value| {
-                    Some(Some(value))
-                })
-            }
-            (_, _) => None
-        }
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-number
-impl Interpolate for f32 {
-    #[inline]
-    fn interpolate(&self, other: &f32, time: f64) -> Option<f32> {
-        Some(((*self as f64) + ((*other as f64) - (*self as f64)) * time) as f32)
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-number
-impl Interpolate for f64 {
-    #[inline]
-    fn interpolate(&self, other: &f64, time: f64) -> Option<f64> {
-        Some(*self + (*other - *self) * time)
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-integer
-impl Interpolate for i32 {
-    #[inline]
-    fn interpolate(&self, other: &i32, time: f64) -> Option<i32> {
-        let a = *self as f64;
-        let b = *other as f64;
-        Some((a + (b - a) * time).round() as i32)
-    }
-}
-
-impl Interpolate for Angle {
-    #[inline]
-    fn interpolate(&self, other: &Angle, time: f64) -> Option<Angle> {
-        self.radians().interpolate(&other.radians(), time).map(Angle)
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-visibility
-impl Interpolate for Visibility {
-    #[inline]
-    fn interpolate(&self, other: &Visibility, time: f64)
-                   -> Option<Visibility> {
-        match (*self, *other) {
-            (Visibility::visible, _) | (_, Visibility::visible) => {
-                if time >= 0.0 && time <= 1.0 {
-                    Some(Visibility::visible)
-                } else if time < 0.0 {
-                    Some(*self)
-                } else {
-                    Some(*other)
-                }
-            }
-            (_, _) => None,
-        }
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-integer
-impl Interpolate for ZIndex {
-    #[inline]
-    fn interpolate(&self, other: &ZIndex, time: f64)
-                   -> Option<ZIndex> {
-        match (*self, *other) {
-            (ZIndex::Number(ref this),
-             ZIndex::Number(ref other)) => {
-                this.interpolate(other, time).and_then(|value| {
-                    Some(ZIndex::Number(value))
-                })
-            }
-            (_, _) => None,
-        }
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-length
-impl Interpolate for VerticalAlign {
-    #[inline]
-    fn interpolate(&self, other: &VerticalAlign, time: f64)
-                   -> Option<VerticalAlign> {
-        match (*self, *other) {
-            (VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref this)),
-             VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref other))) => {
-                this.interpolate(other, time).and_then(|value| {
-                    Some(VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(value)))
-                })
-            }
-            (_, _) => None,
-        }
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-simple-list
-impl Interpolate for BorderSpacing {
-    #[inline]
-    fn interpolate(&self, other: &BorderSpacing, time: f64)
-                   -> Option<BorderSpacing> {
-        self.horizontal.interpolate(&other.horizontal, time).and_then(|horizontal| {
-            self.vertical.interpolate(&other.vertical, time).and_then(|vertical| {
-                Some(BorderSpacing { horizontal: horizontal, vertical: vertical })
-            })
-        })
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-color
-impl Interpolate for RGBA {
-    #[inline]
-    fn interpolate(&self, other: &RGBA, time: f64) -> Option<RGBA> {
-        match (self.red.interpolate(&other.red, time),
-               self.green.interpolate(&other.green, time),
-               self.blue.interpolate(&other.blue, time),
-               self.alpha.interpolate(&other.alpha, time)) {
-            (Some(red), Some(green), Some(blue), Some(alpha)) => {
-                Some(RGBA { red: red, green: green, blue: blue, alpha: alpha })
-            }
-            (_, _, _, _) => None
-        }
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-color
-impl Interpolate for Color {
-    #[inline]
-    fn interpolate(&self, other: &Color, time: f64) -> Option<Color> {
-        match (*self, *other) {
-            (Color::RGBA(ref this), Color::RGBA(ref other)) => {
-                this.interpolate(other, time).and_then(|value| {
-                    Some(Color::RGBA(value))
-                })
-            }
-            (_, _) => None,
-        }
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
-impl Interpolate for CalcLengthOrPercentage {
-    #[inline]
-    fn interpolate(&self, other: &CalcLengthOrPercentage, time: f64)
-                   -> Option<CalcLengthOrPercentage> {
-        Some(CalcLengthOrPercentage {
-            length: self.length().interpolate(&other.length(), time),
-            percentage: self.percentage().interpolate(&other.percentage(), time),
-        })
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
-impl Interpolate for LengthOrPercentage {
-    #[inline]
-    fn interpolate(&self, other: &LengthOrPercentage, time: f64)
-                   -> Option<LengthOrPercentage> {
-        match (*self, *other) {
-            (LengthOrPercentage::Length(ref this),
-             LengthOrPercentage::Length(ref other)) => {
-                this.interpolate(other, time).and_then(|value| {
-                    Some(LengthOrPercentage::Length(value))
-                })
-            }
-            (LengthOrPercentage::Percentage(ref this),
-             LengthOrPercentage::Percentage(ref other)) => {
-                this.interpolate(other, time).and_then(|value| {
-                    Some(LengthOrPercentage::Percentage(value))
-                })
-            }
-            (this, other) => {
-                let this: CalcLengthOrPercentage = From::from(this);
-                let other: CalcLengthOrPercentage = From::from(other);
-                this.interpolate(&other, time).and_then(|value| {
-                    Some(LengthOrPercentage::Calc(value))
-                })
-            }
-        }
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
-impl Interpolate for LengthOrPercentageOrAuto {
-    #[inline]
-    fn interpolate(&self, other: &LengthOrPercentageOrAuto, time: f64)
-                   -> Option<LengthOrPercentageOrAuto> {
-        match (*self, *other) {
-            (LengthOrPercentageOrAuto::Length(ref this),
-             LengthOrPercentageOrAuto::Length(ref other)) => {
-                this.interpolate(other, time).and_then(|value| {
-                    Some(LengthOrPercentageOrAuto::Length(value))
-                })
-            }
-            (LengthOrPercentageOrAuto::Percentage(ref this),
-             LengthOrPercentageOrAuto::Percentage(ref other)) => {
-                this.interpolate(other, time).and_then(|value| {
-                    Some(LengthOrPercentageOrAuto::Percentage(value))
-                })
-            }
-            (LengthOrPercentageOrAuto::Auto, LengthOrPercentageOrAuto::Auto) => {
-                Some(LengthOrPercentageOrAuto::Auto)
-            }
-            (this, other) => {
-                let this: Option<CalcLengthOrPercentage> = From::from(this);
-                let other: Option<CalcLengthOrPercentage> = From::from(other);
-                this.interpolate(&other, time).unwrap_or(None).and_then(|value| {
-                    Some(LengthOrPercentageOrAuto::Calc(value))
-                })
-            }
-        }
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
-impl Interpolate for LengthOrPercentageOrNone {
-    #[inline]
-    fn interpolate(&self, other: &LengthOrPercentageOrNone, time: f64)
-                   -> Option<LengthOrPercentageOrNone> {
-        match (*self, *other) {
-            (LengthOrPercentageOrNone::Length(ref this),
-             LengthOrPercentageOrNone::Length(ref other)) => {
-                this.interpolate(other, time).and_then(|value| {
-                    Some(LengthOrPercentageOrNone::Length(value))
-                })
-            }
-            (LengthOrPercentageOrNone::Percentage(ref this),
-             LengthOrPercentageOrNone::Percentage(ref other)) => {
-                this.interpolate(other, time).and_then(|value| {
-                    Some(LengthOrPercentageOrNone::Percentage(value))
-                })
-            }
-            (LengthOrPercentageOrNone::None, LengthOrPercentageOrNone::None) => {
-                Some(LengthOrPercentageOrNone::None)
-            }
-            (_, _) => None,
-        }
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-number
-/// https://drafts.csswg.org/css-transitions/#animtype-length
-impl Interpolate for LineHeight {
-    #[inline]
-    fn interpolate(&self, other: &LineHeight, time: f64)
-                   -> Option<LineHeight> {
-        match (*self, *other) {
-            (LineHeight::Length(ref this),
-             LineHeight::Length(ref other)) => {
-                this.interpolate(other, time).and_then(|value| {
-                    Some(LineHeight::Length(value))
-                })
-            }
-            (LineHeight::Number(ref this),
-             LineHeight::Number(ref other)) => {
-                this.interpolate(other, time).and_then(|value| {
-                    Some(LineHeight::Number(value))
-                })
-            }
-            (LineHeight::Normal, LineHeight::Normal) => {
-                Some(LineHeight::Normal)
-            }
-            (_, _) => None,
-        }
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-font-weight
-impl Interpolate for FontWeight {
-    #[inline]
-    fn interpolate(&self, other: &FontWeight, time: f64)
-                   -> Option<FontWeight> {
-        let a = (*self as u32) as f64;
-        let b = (*other as u32) as f64;
-        let weight = a + (b - a) * time;
-        Some(if weight < 150. {
-            FontWeight::Weight100
-        } else if weight < 250. {
-            FontWeight::Weight200
-        } else if weight < 350. {
-            FontWeight::Weight300
-        } else if weight < 450. {
-            FontWeight::Weight400
-        } else if weight < 550. {
-            FontWeight::Weight500
-        } else if weight < 650. {
-            FontWeight::Weight600
-        } else if weight < 750. {
-            FontWeight::Weight700
-        } else if weight < 850. {
-            FontWeight::Weight800
-        } else {
-            FontWeight::Weight900
-        })
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-rect
-impl Interpolate for ClipRect {
-    #[inline]
-    fn interpolate(&self, other: &ClipRect, time: f64)
-                   -> Option<ClipRect> {
-        match (self.top.interpolate(&other.top, time),
-               self.right.interpolate(&other.right, time),
-               self.bottom.interpolate(&other.bottom, time),
-               self.left.interpolate(&other.left, time)) {
-            (Some(top), Some(right), Some(bottom), Some(left)) => {
-                Some(ClipRect { top: top, right: right, bottom: bottom, left: left })
-            },
-            (_, _, _, _) => None,
-        }
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-simple-list
-impl Interpolate for BackgroundPosition {
-    #[inline]
-    fn interpolate(&self, other: &BackgroundPosition, time: f64)
-                   -> Option<BackgroundPosition> {
-        match (self.horizontal.interpolate(&other.horizontal, time),
-               self.vertical.interpolate(&other.vertical, time)) {
-            (Some(horizontal), Some(vertical)) => {
-                Some(BackgroundPosition { horizontal: horizontal, vertical: vertical })
-            },
-            (_, _) => None,
-        }
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list
-impl Interpolate for TextShadow {
-    #[inline]
-    fn interpolate(&self, other: &TextShadow, time: f64)
-                   -> Option<TextShadow> {
-        match (self.offset_x.interpolate(&other.offset_x, time),
-               self.offset_y.interpolate(&other.offset_y, time),
-               self.blur_radius.interpolate(&other.blur_radius, time),
-               self.color.interpolate(&other.color, time)) {
-            (Some(offset_x), Some(offset_y), Some(blur_radius), Some(color)) => {
-                Some(TextShadow { offset_x: offset_x, offset_y: offset_y, blur_radius: blur_radius, color: color })
-            },
-            (_, _, _, _) => None,
-        }
-    }
-}
-
-/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list
-impl Interpolate for TextShadowList {
-    #[inline]
-    fn interpolate(&self, other: &TextShadowList, time: f64)
-                   -> Option<TextShadowList> {
-        let zero = TextShadow {
-            offset_x: Au(0),
-            offset_y: Au(0),
-            blur_radius: Au(0),
-            color: Color::RGBA(RGBA {
-                red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0
-            })
-        };
-
-        let interpolate_each = |(a, b): (&TextShadow, &TextShadow)| {
-            a.interpolate(b, time).unwrap()
-        };
-
-        Some(TextShadowList(match self.0.len().cmp(&other.0.len()) {
-            Ordering::Less => other.0.iter().chain(repeat(&zero)).zip(other.0.iter()).map(interpolate_each).collect(),
-            _ => self.0.iter().zip(other.0.iter().chain(repeat(&zero))).map(interpolate_each).collect(),
-        }))
-    }
-}
-
-/// Check if it's possible to do a direct numerical interpolation
-/// between these two transform lists.
-/// http://dev.w3.org/csswg/css-transforms/#transform-transform-animation
-fn can_interpolate_list(from_list: &[TransformOperation],
-                        to_list: &[TransformOperation]) -> bool {
-    // Lists must be equal length
-    if from_list.len() != to_list.len() {
-        return false;
-    }
-
-    // Each transform operation must match primitive type in other list
-    for (from, to) in from_list.iter().zip(to_list) {
-        match (from, to) {
-            (&TransformOperation::Matrix(..), &TransformOperation::Matrix(..)) |
-            (&TransformOperation::Skew(..), &TransformOperation::Skew(..)) |
-            (&TransformOperation::Translate(..), &TransformOperation::Translate(..)) |
-            (&TransformOperation::Scale(..), &TransformOperation::Scale(..)) |
-            (&TransformOperation::Rotate(..), &TransformOperation::Rotate(..)) |
-            (&TransformOperation::Perspective(..), &TransformOperation::Perspective(..)) => {}
-            _ => {
-                return false;
-            }
-        }
-    }
-
-    true
-}
-
-/// Interpolate two transform lists.
-/// https://drafts.csswg.org/css-transforms/#interpolation-of-transforms
-fn interpolate_transform_list(from_list: &[TransformOperation],
-                              to_list: &[TransformOperation],
-                              time: f64) -> TransformList {
-    let mut result = vec!();
-
-    if can_interpolate_list(from_list, to_list) {
-        for (from, to) in from_list.iter().zip(to_list) {
-            match (from, to) {
-                (&TransformOperation::Matrix(from),
-                 &TransformOperation::Matrix(_to)) => {
-                    // TODO(gw): Implement matrix decomposition and interpolation
-                    result.push(TransformOperation::Matrix(from));
-                }
-                (&TransformOperation::Skew(fx, fy),
-                 &TransformOperation::Skew(tx, ty)) => {
-                    let ix = fx.interpolate(&tx, time).unwrap();
-                    let iy = fy.interpolate(&ty, time).unwrap();
-                    result.push(TransformOperation::Skew(ix, iy));
-                }
-                (&TransformOperation::Translate(fx, fy, fz),
-                 &TransformOperation::Translate(tx, ty, tz)) => {
-                    let ix = fx.interpolate(&tx, time).unwrap();
-                    let iy = fy.interpolate(&ty, time).unwrap();
-                    let iz = fz.interpolate(&tz, time).unwrap();
-                    result.push(TransformOperation::Translate(ix, iy, iz));
-                }
-                (&TransformOperation::Scale(fx, fy, fz),
-                 &TransformOperation::Scale(tx, ty, tz)) => {
-                    let ix = fx.interpolate(&tx, time).unwrap();
-                    let iy = fy.interpolate(&ty, time).unwrap();
-                    let iz = fz.interpolate(&tz, time).unwrap();
-                    result.push(TransformOperation::Scale(ix, iy, iz));
-                }
-                (&TransformOperation::Rotate(fx, fy, fz, fa),
-                 &TransformOperation::Rotate(_tx, _ty, _tz, _ta)) => {
-                    // TODO(gw): Implement matrix decomposition and interpolation
-                    result.push(TransformOperation::Rotate(fx, fy, fz, fa));
-                }
-                (&TransformOperation::Perspective(fd),
-                 &TransformOperation::Perspective(_td)) => {
-                    // TODO(gw): Implement matrix decomposition and interpolation
-                    result.push(TransformOperation::Perspective(fd));
-                }
-                _ => {
-                    // This should be unreachable due to the can_interpolate_list() call.
-                    unreachable!();
-                }
-            }
-        }
-    } else {
-        // TODO(gw): Implement matrix decomposition and interpolation
-        result.extend_from_slice(from_list);
-    }
-
-    TransformList(Some(result))
-}
-
-/// Build an equivalent 'identity transform function list' based
-/// on an existing transform list.
-/// https://drafts.csswg.org/css-transforms/#none-transform-animation
-fn build_identity_transform_list(list: &[TransformOperation]) -> Vec<TransformOperation> {
-    let mut result = vec!();
-
-    for operation in list {
-        match *operation {
-            TransformOperation::Matrix(..) => {
-                let identity = ComputedMatrix::identity();
-                result.push(TransformOperation::Matrix(identity));
-            }
-            TransformOperation::Skew(..) => {
-                result.push(TransformOperation::Skew(Angle(0.0), Angle(0.0)));
-            }
-            TransformOperation::Translate(..) => {
-                result.push(TransformOperation::Translate(LengthOrPercentage::zero(),
-                                                          LengthOrPercentage::zero(),
-                                                          Au(0)));
-            }
-            TransformOperation::Scale(..) => {
-                result.push(TransformOperation::Scale(1.0, 1.0, 1.0));
-            }
-            TransformOperation::Rotate(..) => {
-                result.push(TransformOperation::Rotate(0.0, 0.0, 1.0, Angle(0.0)));
-            }
-            TransformOperation::Perspective(..) => {
-                // http://dev.w3.org/csswg/css-transforms/#identity-transform-function
-                let identity = ComputedMatrix::identity();
-                result.push(TransformOperation::Matrix(identity));
-            }
-        }
-    }
-
-    result
-}
-
-/// https://drafts.csswg.org/css-transforms/#interpolation-of-transforms
-impl Interpolate for TransformList {
-    #[inline]
-    fn interpolate(&self, other: &TransformList, time: f64) -> Option<TransformList> {
-        let result = match (&self.0, &other.0) {
-            (&Some(ref from_list), &Some(ref to_list)) => {
-                // https://drafts.csswg.org/css-transforms/#transform-transform-animation
-                interpolate_transform_list(from_list, &to_list, time)
-            }
-            (&Some(ref from_list), &None) => {
-                // https://drafts.csswg.org/css-transforms/#none-transform-animation
-                let to_list = build_identity_transform_list(from_list);
-                interpolate_transform_list(from_list, &to_list, time)
-            }
-            (&None, &Some(ref to_list)) => {
-                // https://drafts.csswg.org/css-transforms/#none-transform-animation
-                let from_list = build_identity_transform_list(to_list);
-                interpolate_transform_list(&from_list, to_list, time)
-            }
-            _ => {
-                // https://drafts.csswg.org/css-transforms/#none-none-animation
-                TransformList(None)
-            }
-        };
-
-        Some(result)
+    fn does_animate(&self) -> bool {
+        self.property.does_animate() && self.duration != Time(0.0)
     }
 }
 
 /// Accesses an element of an array, "wrapping around" using modular arithmetic. This is needed
 /// to handle [repeatable lists][lists] of differing lengths.
 ///
 /// [lists]: https://drafts.csswg.org/css-transitions/#animtype-repeatable-list
 pub trait GetMod {
     type Item;
     fn get_mod(&self, i: usize) -> &Self::Item;
 }
 
 impl<T> GetMod for Vec<T> {
     type Item = T;
+    #[inline]
     fn get_mod(&self, i: usize) -> &T {
         &(*self)[i % self.len()]
     }
 }
 
-/// Inserts transitions into the queue of running animations as applicable for the given style
-/// difference. This is called from the layout worker threads. Returns true if any animations were
-/// kicked off and false otherwise.
-pub fn start_transitions_if_applicable<C: ComputedValues>(new_animations_sender: &Mutex<Sender<Animation>>,
-                                                          node: OpaqueNode,
-                                                          old_style: &C,
-                                                          new_style: &mut C)
-                                                          -> bool {
+/// Inserts transitions into the queue of running animations as applicable for
+/// the given style difference. This is called from the layout worker threads.
+/// Returns true if any animations were kicked off and false otherwise.
+//
+// TODO(emilio): Take rid of this mutex splitting SharedLayoutContex into a
+// cloneable part and a non-cloneable part..
+pub fn start_transitions_if_applicable<Impl: SelectorImplExt>(new_animations_sender: &Mutex<Sender<Animation<Impl>>>,
+                                                              node: OpaqueNode,
+                                                              old_style: &Impl::ComputedValues,
+                                                              new_style: &mut Arc<Impl::ComputedValues>)
+                                                              -> bool {
     let mut had_animations = false;
     for i in 0..new_style.get_box().transition_count() {
         // Create any property animations, if applicable.
-        let property_animations = PropertyAnimation::from_transition(i, old_style.as_servo(), new_style.as_servo_mut());
+        let property_animations = PropertyAnimation::from_transition(i, old_style, Arc::make_mut(new_style));
         for property_animation in property_animations {
             // Set the property to the initial value.
-            property_animation.update(new_style.as_servo_mut(), 0.0);
+            // NB: get_mut is guaranteed to succeed since we called make_mut()
+            // above.
+            property_animation.update(Arc::get_mut(new_style).unwrap(), 0.0);
 
             // Kick off the animation.
             let now = time::precise_time_s();
             let box_style = new_style.as_servo().get_box();
             let start_time =
                 now + (box_style.transition_delay.0.get_mod(i).seconds() as f64);
-            new_animations_sender.lock().unwrap().send(Animation {
-                node: node,
-                property_animation: property_animation,
-                start_time: start_time,
-                end_time: start_time +
-                    (box_style.transition_duration.0.get_mod(i).seconds() as f64),
-            }).unwrap();
+            new_animations_sender
+                .lock().unwrap()
+                .send(Animation::Transition(node, start_time, AnimationFrame {
+                    duration: box_style.transition_duration.0.get_mod(i).seconds() as f64,
+                    property_animation: property_animation,
+                }, /* is_expired = */ false)).unwrap();
+
+            had_animations = true;
+        }
+    }
+
+    had_animations
+}
+
+fn compute_style_for_animation_step<Impl: SelectorImplExt>(context: &SharedStyleContext<Impl>,
+                                                           step: &KeyframesStep,
+                                                           previous_style: &Impl::ComputedValues,
+                                                           style_from_cascade: &Impl::ComputedValues)
+                                                           -> Impl::ComputedValues {
+    match step.value {
+        // TODO: avoiding this spurious clone might involve having to create
+        // an Arc in the below (more common case).
+        KeyframesStepValue::ComputedValues => style_from_cascade.clone(),
+        KeyframesStepValue::Declarations(ref declarations) => {
+            let declaration_block = DeclarationBlock {
+                declarations: declarations.clone(),
+                source_order: 0,
+                specificity: ::std::u32::MAX,
+            };
+            let (computed, _) = properties::cascade(context.viewport_size,
+                                                    &[declaration_block],
+                                                    false,
+                                                    Some(previous_style),
+                                                    None,
+                                                    context.error_reporter.clone());
+            computed
+        }
+    }
+}
+
+pub fn maybe_start_animations<Impl: SelectorImplExt>(context: &SharedStyleContext<Impl>,
+                                                     node: OpaqueNode,
+                                                     new_style: &Arc<Impl::ComputedValues>) -> bool
+{
+    let mut had_animations = false;
 
-            had_animations = true
+    let box_style = new_style.as_servo().get_box();
+    for (i, name) in box_style.animation_name.0.iter().enumerate() {
+        debug!("maybe_start_animations: name={}", name);
+        let total_duration = box_style.animation_duration.0.get_mod(i).seconds();
+        if total_duration == 0. {
+            continue
+        }
+
+        if let Some(ref anim) = context.stylist.animations().get(&name) {
+            debug!("maybe_start_animations: animation {} found", name);
+
+            // If this animation doesn't have any keyframe, we can just continue
+            // without submitting it to the compositor, since both the first and
+            // the second keyframes would be synthetised from the computed
+            // values.
+            if anim.steps.is_empty() {
+                continue;
+            }
+
+            let delay = box_style.animation_delay.0.get_mod(i).seconds();
+            let now = time::precise_time_s();
+            let animation_start = now + delay as f64;
+            let duration = box_style.animation_duration.0.get_mod(i).seconds();
+            let iteration_state = match *box_style.animation_iteration_count.0.get_mod(i) {
+                AnimationIterationCount::Infinite => KeyframesIterationState::Infinite,
+                AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0, n),
+            };
+
+            let animation_direction = *box_style.animation_direction.0.get_mod(i);
+
+            let initial_direction = match animation_direction {
+                AnimationDirection::normal |
+                AnimationDirection::alternate => AnimationDirection::normal,
+                AnimationDirection::reverse |
+                AnimationDirection::alternate_reverse => AnimationDirection::reverse,
+            };
+
+            let running_state = match *box_style.animation_play_state.0.get_mod(i) {
+                AnimationPlayState::paused => KeyframesRunningState::Paused(0.),
+                AnimationPlayState::running => KeyframesRunningState::Running,
+            };
+
+
+            context.new_animations_sender
+                   .lock().unwrap()
+                   .send(Animation::Keyframes(node, name.clone(), KeyframesAnimationState {
+                       started_at: animation_start,
+                       duration: duration as f64,
+                       delay: delay as f64,
+                       iteration_state: iteration_state,
+                       running_state: running_state,
+                       direction: animation_direction,
+                       current_direction: initial_direction,
+                       expired: false,
+                       cascade_style: new_style.clone(),
+                   })).unwrap();
+            had_animations = true;
         }
     }
 
     had_animations
 }
 
-/// Updates a single animation and associated style based on the current time. If `damage` is
-/// provided, inserts the appropriate restyle damage.
-pub fn update_style_for_animation<C: ComputedValues,
-                                  Damage: TRestyleDamage<ConcreteComputedValues=C>>(animation: &Animation,
-                                                                                    style: &mut Arc<C>,
-                                                                                    damage: Option<&mut Damage>) {
-    let now = time::precise_time_s();
-    let mut progress = (now - animation.start_time) / animation.duration();
+/// Updates a given computed style for a given animation frame. Returns a bool
+/// representing if the style was indeed updated.
+pub fn update_style_for_animation_frame<C: ComputedValues>(mut new_style: &mut Arc<C>,
+                                                           now: f64,
+                                                           start_time: f64,
+                                                           frame: &AnimationFrame) -> bool {
+    let mut progress = (now - start_time) / frame.duration;
     if progress > 1.0 {
         progress = 1.0
     }
+
     if progress <= 0.0 {
-        return
+        return false;
     }
 
-    let mut new_style = (*style).clone();
-    animation.property_animation.update(Arc::make_mut(&mut new_style).as_servo_mut(), progress);
-    if let Some(damage) = damage {
-        *damage = *damage | Damage::compute(Some(style), &new_style);
+    frame.property_animation.update(Arc::make_mut(&mut new_style), progress);
+
+    true
+}
+/// Updates a single animation and associated style based on the current time.
+/// If `damage` is provided, inserts the appropriate restyle damage.
+pub fn update_style_for_animation<Damage, Impl>(context: &SharedStyleContext<Impl>,
+                                                animation: &Animation<Impl>,
+                                                style: &mut Arc<Damage::ConcreteComputedValues>,
+                                                damage: Option<&mut Damage>)
+where Impl: SelectorImplExt,
+      Damage: TRestyleDamage<ConcreteComputedValues = Impl::ComputedValues> {
+    debug!("update_style_for_animation: entering");
+    match *animation {
+        Animation::Transition(_, start_time, ref frame, expired) => {
+            debug_assert!(!expired);
+            debug!("update_style_for_animation: transition found");
+            let now = time::precise_time_s();
+            let mut new_style = (*style).clone();
+            let updated_style = update_style_for_animation_frame(&mut new_style,
+                                                                 now, start_time,
+                                                                 frame);
+            if updated_style {
+                if let Some(damage) = damage {
+                    *damage = *damage | Damage::compute(Some(style), &new_style);
+                }
+
+                *style = new_style
+            }
+        }
+        Animation::Keyframes(_, ref name, ref state) => {
+            debug_assert!(!state.expired);
+            debug!("update_style_for_animation: animation found: \"{}\", {:?}", name, state);
+            let duration = state.duration;
+            let started_at = state.started_at;
+
+            let now = match state.running_state {
+                KeyframesRunningState::Running => time::precise_time_s(),
+                KeyframesRunningState::Paused(progress) => started_at + duration * progress,
+            };
+
+            let animation = match context.stylist.animations().get(name) {
+                None => {
+                    warn!("update_style_for_animation: Animation {:?} not found", name);
+                    return;
+                }
+                Some(animation) => animation,
+            };
+
+            debug_assert!(!animation.steps.is_empty());
+
+            let maybe_index = style.as_servo()
+                                   .get_box().animation_name.0.iter()
+                                   .position(|animation_name| name == animation_name);
+
+            let index = match maybe_index {
+                Some(index) => index,
+                None => {
+                    warn!("update_style_for_animation: Animation {:?} not found in style", name);
+                    return;
+                }
+            };
+
+            let total_duration = style.as_servo().get_box().animation_duration.0.get_mod(index).seconds() as f64;
+            if total_duration == 0. {
+                debug!("update_style_for_animation: zero duration for animation {:?}", name);
+                return;
+            }
+
+            let mut total_progress = (now - started_at) / total_duration;
+            if total_progress < 0. {
+                warn!("Negative progress found for animation {:?}", name);
+                return;
+            }
+            if total_progress > 1. {
+                total_progress = 1.;
+            }
+
+            debug!("update_style_for_animation: anim \"{}\", steps: {:?}, state: {:?}, progress: {}",
+                   name, animation.steps, state, total_progress);
+
+            // Get the target and the last keyframe position.
+            let last_keyframe_position;
+            let target_keyframe_position;
+            match state.current_direction {
+                AnimationDirection::normal => {
+                    target_keyframe_position =
+                        animation.steps.iter().position(|step| {
+                            total_progress as f32 <= step.start_percentage.0
+                        });
+
+                    last_keyframe_position = target_keyframe_position.and_then(|pos| {
+                        if pos != 0 { Some(pos - 1) } else { None }
+                    }).unwrap_or(0);
+                }
+                AnimationDirection::reverse => {
+                    target_keyframe_position =
+                        animation.steps.iter().rev().position(|step| {
+                            total_progress as f32 <= 1. - step.start_percentage.0
+                        }).map(|pos| animation.steps.len() - pos - 1);
+
+                    last_keyframe_position = target_keyframe_position.and_then(|pos| {
+                        if pos != animation.steps.len() - 1 { Some(pos + 1) } else { None }
+                    }).unwrap_or(animation.steps.len() - 1);
+                }
+                _ => unreachable!(),
+            }
+
+            debug!("update_style_for_animation: keyframe from {:?} to {:?}",
+                   last_keyframe_position, target_keyframe_position);
+
+            let target_keyframe = match target_keyframe_position {
+                Some(target) => &animation.steps[target],
+                None => {
+                    warn!("update_style_for_animation: No current keyframe found for animation \"{}\" at progress {}",
+                          name, total_progress);
+                    return;
+                }
+            };
+
+            let last_keyframe = &animation.steps[last_keyframe_position];
+
+            let relative_timespan = (target_keyframe.start_percentage.0 - last_keyframe.start_percentage.0).abs();
+            let relative_duration = relative_timespan as f64 * duration;
+            let last_keyframe_ended_at = match state.current_direction {
+                AnimationDirection::normal => {
+                    state.started_at + (total_duration * last_keyframe.start_percentage.0 as f64)
+                }
+                AnimationDirection::reverse => {
+                    state.started_at + (total_duration * (1. - last_keyframe.start_percentage.0 as f64))
+                }
+                _ => unreachable!(),
+            };
+            let relative_progress = (now - last_keyframe_ended_at) / relative_duration;
+
+            // TODO: How could we optimise it? Is it such a big deal?
+            let from_style = compute_style_for_animation_step(context,
+                                                              last_keyframe,
+                                                              &**style,
+                                                              &state.cascade_style);
+
+            // NB: The spec says that the timing function can be overwritten
+            // from the keyframe style.
+            let mut timing_function = *style.as_servo().get_box().animation_timing_function.0.get_mod(index);
+            if !from_style.as_servo().get_box().animation_timing_function.0.is_empty() {
+                timing_function = from_style.as_servo().get_box().animation_timing_function.0[0];
+            }
+
+            let target_style = compute_style_for_animation_step(context,
+                                                                target_keyframe,
+                                                                &from_style,
+                                                                &state.cascade_style);
+
+            let mut new_style = (*style).clone();
+
+            for transition_property in &animation.properties_changed {
+                debug!("update_style_for_animation: scanning prop {:?} for animation \"{}\"",
+                       transition_property, name);
+                match PropertyAnimation::from_transition_property(*transition_property,
+                                                                  timing_function,
+                                                                  Time(relative_duration as f32),
+                                                                  &from_style,
+                                                                  &target_style) {
+                    Some(property_animation) => {
+                        debug!("update_style_for_animation: got property animation for prop {:?}", transition_property);
+                        debug!("update_style_for_animation: {:?}", property_animation);
+                        property_animation.update(Arc::make_mut(&mut new_style), relative_progress);
+                    }
+                    None => {
+                        debug!("update_style_for_animation: property animation {:?} not animating",
+                               transition_property);
+                    }
+                }
+            }
+
+            debug!("update_style_for_animation: got style change in animation \"{}\"", name);
+            if let Some(damage) = damage {
+                *damage = *damage | Damage::compute(Some(style), &new_style);
+            }
+
+            *style = new_style;
+        }
     }
-
-    *style = new_style
 }
--- a/servo/components/style/context.rs
+++ b/servo/components/style/context.rs
@@ -29,26 +29,26 @@ pub struct SharedStyleContext<Impl: Sele
     pub stylist: Arc<Stylist<Impl>>,
 
     /// Starts at zero, and increased by one every time a layout completes.
     /// This can be used to easily check for invalid stale data.
     pub generation: u32,
 
     /// A channel on which new animations that have been triggered by style recalculation can be
     /// sent.
-    pub new_animations_sender: Mutex<Sender<Animation>>,
+    pub new_animations_sender: Mutex<Sender<Animation<Impl>>>,
 
     /// Why is this reflow occurring
     pub goal: ReflowGoal,
 
     /// The animations that are currently running.
-    pub running_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>,
+    pub running_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation<Impl>>>>>,
 
     /// The list of animations that have expired since the last style recalculation.
-    pub expired_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>,
+    pub expired_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation<Impl>>>>>,
 
     ///The CSS error reporter for all CSS loaded in this layout thread
     pub error_reporter: Box<ParseErrorReporter + Sync>,
 }
 
 pub struct LocalStyleContext<C: ComputedValues> {
     pub applicable_declarations_cache: RefCell<ApplicableDeclarationsCache<C>>,
     pub style_sharing_candidate_cache: RefCell<StyleSharingCandidateCache<C>>,
new file mode 100644
--- /dev/null
+++ b/servo/components/style/keyframes.rs
@@ -0,0 +1,242 @@
+/* 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 cssparser::{AtRuleParser, Delimiter, Parser, QualifiedRuleParser, RuleListParser};
+use parser::{ParserContext, log_css_error};
+use properties::animated_properties::TransitionProperty;
+use properties::{PropertyDeclaration, parse_property_declaration_list};
+use std::sync::Arc;
+
+/// A number from 1 to 100, indicating the percentage of the animation where
+/// this keyframe should run.
+#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, HeapSizeOf)]
+pub struct KeyframePercentage(pub f32);
+
+impl ::std::cmp::Ord for KeyframePercentage {
+    #[inline]
+    fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
+        // We know we have a number from 0 to 1, so unwrap() here is safe.
+        self.0.partial_cmp(&other.0).unwrap()
+    }
+}
+
+impl ::std::cmp::Eq for KeyframePercentage { }
+
+impl KeyframePercentage {
+    #[inline]
+    pub fn new(value: f32) -> KeyframePercentage {
+        debug_assert!(value >= 0. && value <= 1.);
+        KeyframePercentage(value)
+    }
+
+    fn parse(input: &mut Parser) -> Result<KeyframePercentage, ()> {
+        let percentage = if input.try(|input| input.expect_ident_matching("from")).is_ok() {
+            KeyframePercentage::new(0.)
+        } else if input.try(|input| input.expect_ident_matching("to")).is_ok() {
+            KeyframePercentage::new(1.)
+        } else {
+            let percentage = try!(input.expect_percentage());
+            if percentage > 1. || percentage < 0. {
+                return Err(());
+            }
+            KeyframePercentage::new(percentage)
+        };
+
+        Ok(percentage)
+    }
+}
+
+/// A keyframes selector is a list of percentages or from/to symbols, which are
+/// converted at parse time to percentages.
+#[derive(Debug, Clone, PartialEq, HeapSizeOf)]
+pub struct KeyframeSelector(Vec<KeyframePercentage>);
+impl KeyframeSelector {
+    #[inline]
+    pub fn percentages(&self) -> &[KeyframePercentage] {
+        &self.0
+    }
+
+    /// A dummy public function so we can write a unit test for this.
+    pub fn new_for_unit_testing(percentages: Vec<KeyframePercentage>) -> KeyframeSelector {
+        KeyframeSelector(percentages)
+    }
+}
+
+/// A keyframe.
+#[derive(Debug, Clone, PartialEq, HeapSizeOf)]
+pub struct Keyframe {
+    pub selector: KeyframeSelector,
+    pub declarations: Arc<Vec<PropertyDeclaration>>,
+}
+
+impl Keyframe {
+    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<Keyframe, ()> {
+        let percentages = try!(input.parse_until_before(Delimiter::CurlyBracketBlock, |input| {
+            input.parse_comma_separated(|input| KeyframePercentage::parse(input))
+        }));
+        let selector = KeyframeSelector(percentages);
+
+        try!(input.expect_curly_bracket_block());
+
+        let declarations = input.parse_nested_block(|input| {
+            Ok(parse_property_declaration_list(context, input))
+        }).unwrap();
+
+        // NB: Important declarations are explicitely ignored in the spec.
+        Ok(Keyframe {
+            selector: selector,
+            declarations: declarations.normal,
+        })
+    }
+}
+
+/// A keyframes step value. This can be a synthetised keyframes animation, that
+/// is, one autogenerated from the current computed values, or a list of
+/// declarations to apply.
+// TODO: Find a better name for this?
+#[derive(Debug, Clone, PartialEq, HeapSizeOf)]
+pub enum KeyframesStepValue {
+    Declarations(Arc<Vec<PropertyDeclaration>>),
+    ComputedValues,
+}
+
+/// A single step from a keyframe animation.
+#[derive(Debug, Clone, PartialEq, HeapSizeOf)]
+pub struct KeyframesStep {
+    /// The percentage of the animation duration when this step starts.
+    pub start_percentage: KeyframePercentage,
+    /// Declarations that will determine the final style during the step, or
+    /// `ComputedValues` if this is an autogenerated step.
+    pub value: KeyframesStepValue,
+}
+
+impl KeyframesStep {
+    #[inline]
+    fn new(percentage: KeyframePercentage,
+           value: KeyframesStepValue) -> Self {
+        KeyframesStep {
+            start_percentage: percentage,
+            value: value,
+        }
+    }
+}
+
+/// This structure represents a list of animation steps computed from the list
+/// of keyframes, in order.
+///
+/// It only takes into account animable properties.
+#[derive(Debug, Clone, PartialEq, HeapSizeOf)]
+pub struct KeyframesAnimation {
+    pub steps: Vec<KeyframesStep>,
+    /// The properties that change in this animation.
+    pub properties_changed: Vec<TransitionProperty>,
+}
+
+/// Get all the animated properties in a keyframes animation. Note that it's not
+/// defined what happens when a property is not on a keyframe, so we only peek
+/// the props of the first one.
+///
+/// In practice, browsers seem to try to do their best job at it, so we might
+/// want to go through all the actual keyframes and deduplicate properties.
+fn get_animated_properties(keyframe: &Keyframe) -> Vec<TransitionProperty> {
+    let mut ret = vec![];
+    // NB: declarations are already deduplicated, so we don't have to check for
+    // it here.
+    for declaration in keyframe.declarations.iter() {
+        if let Some(property) = TransitionProperty::from_declaration(&declaration) {
+            ret.push(property);
+        }
+    }
+
+    ret
+}
+
+impl KeyframesAnimation {
+    pub fn from_keyframes(keyframes: &[Keyframe]) -> Option<Self> {
+        let animated_properties = get_animated_properties(&keyframes[0]);
+        if keyframes.is_empty() || animated_properties.is_empty() {
+            return None;
+        }
+
+        let mut steps = vec![];
+
+        for keyframe in keyframes {
+            for percentage in keyframe.selector.0.iter() {
+                steps.push(KeyframesStep::new(*percentage,
+                                              KeyframesStepValue::Declarations(keyframe.declarations.clone())));
+            }
+        }
+
+        // Sort by the start percentage, so we can easily find a frame.
+        steps.sort_by_key(|step| step.start_percentage);
+
+        // Prepend autogenerated keyframes if appropriate.
+        if steps[0].start_percentage.0 != 0. {
+            steps.insert(0, KeyframesStep::new(KeyframePercentage::new(0.),
+                                               KeyframesStepValue::ComputedValues));
+        }
+
+        if steps.last().unwrap().start_percentage.0 != 1. {
+            steps.push(KeyframesStep::new(KeyframePercentage::new(0.),
+                                          KeyframesStepValue::ComputedValues));
+        }
+
+        Some(KeyframesAnimation {
+            steps: steps,
+            properties_changed: animated_properties,
+        })
+    }
+}
+
+/// Parses a keyframes list, like:
+/// 0%, 50% {
+///     width: 50%;
+/// }
+///
+/// 40%, 60%, 100% {
+///     width: 100%;
+/// }
+struct KeyframeListParser<'a> {
+    context: &'a ParserContext<'a>,
+}
+
+pub fn parse_keyframe_list(context: &ParserContext, input: &mut Parser) -> Vec<Keyframe> {
+    RuleListParser::new_for_nested_rule(input, KeyframeListParser { context: context })
+        .filter_map(Result::ok)
+        .collect()
+}
+
+enum Void {}
+impl<'a> AtRuleParser for KeyframeListParser<'a> {
+    type Prelude = Void;
+    type AtRule = Keyframe;
+}
+
+impl<'a> QualifiedRuleParser for KeyframeListParser<'a> {
+    type Prelude = KeyframeSelector;
+    type QualifiedRule = Keyframe;
+
+    fn parse_prelude(&self, input: &mut Parser) -> Result<Self::Prelude, ()> {
+        let start = input.position();
+        match input.parse_comma_separated(|input| KeyframePercentage::parse(input)) {
+            Ok(percentages) => Ok(KeyframeSelector(percentages)),
+            Err(()) => {
+                let message = format!("Invalid keyframe rule: '{}'", input.slice_from(start));
+                log_css_error(input, start, &message, self.context);
+                Err(())
+            }
+        }
+    }
+
+    fn parse_block(&self, prelude: Self::Prelude, input: &mut Parser)
+                   -> Result<Self::QualifiedRule, ()> {
+        Ok(Keyframe {
+            selector: prelude,
+            // FIXME: needs parsing different from parse_property_declaration_list:
+            // https://drafts.csswg.org/css-animations/#keyframes
+            // Paragraph "The <declaration-list> inside of <keyframe-block> ..."
+            declarations: parse_property_declaration_list(self.context, input).normal,
+        })
+    }
+}
--- a/servo/components/style/lib.rs
+++ b/servo/components/style/lib.rs
@@ -71,16 +71,17 @@ pub mod attr;
 pub mod bezier;
 pub mod context;
 pub mod custom_properties;
 pub mod data;
 pub mod dom;
 pub mod element_state;
 pub mod error_reporting;
 pub mod font_face;
+pub mod keyframes;
 pub mod logical_geometry;
 pub mod matching;
 pub mod media_queries;
 pub mod parallel;
 pub mod parser;
 pub mod refcell;
 pub mod restyle_hints;
 pub mod selector_impl;
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -2,32 +2,31 @@
  * 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/. */
 
 //! High-level interface to CSS selector matching.
 
 #![allow(unsafe_code)]
 
 use animation::{self, Animation};
-use context::SharedStyleContext;
+use context::{SharedStyleContext, LocalStyleContext};
 use data::PrivateStyleData;
 use dom::{TElement, TNode, TRestyleDamage};
 use properties::{ComputedValues, PropertyDeclaration, cascade};
 use selector_impl::{ElementExt, SelectorImplExt};
 use selector_matching::{DeclarationBlock, Stylist};
 use selectors::Element;
 use selectors::bloom::BloomFilter;
 use selectors::matching::{CommonStyleAffectingAttributeMode, CommonStyleAffectingAttributes};
 use selectors::matching::{common_style_affecting_attributes, rare_style_affecting_attributes};
 use smallvec::SmallVec;
 use std::collections::HashMap;
 use std::hash::{BuildHasherDefault, Hash, Hasher};
 use std::slice::Iter;
-use std::sync::mpsc::Sender;
-use std::sync::{Arc, Mutex};
+use std::sync::Arc;
 use string_cache::{Atom, Namespace};
 use util::arc_ptr_eq;
 use util::cache::{LRUCache, SimpleHashCache};
 use util::opts;
 use util::vec::ForgetfulSink;
 
 fn create_common_style_affecting_attributes_from_element<E: TElement>(element: &E)
                                                          -> CommonStyleAffectingAttributes {
@@ -49,17 +48,19 @@ fn create_common_style_affecting_attribu
             }
         }
     }
     flags
 }
 
 pub struct ApplicableDeclarations<Impl: SelectorImplExt> {
     pub normal: SmallVec<[DeclarationBlock; 16]>,
-    pub per_pseudo: HashMap<Impl::PseudoElement, Vec<DeclarationBlock>, BuildHasherDefault<::fnv::FnvHasher>>,
+    pub per_pseudo: HashMap<Impl::PseudoElement,
+                            Vec<DeclarationBlock>,
+                            BuildHasherDefault<::fnv::FnvHasher>>,
 
     /// Whether the `normal` declarations are shareable with other nodes.
     pub normal_shareable: bool,
 }
 
 impl<Impl: SelectorImplExt> ApplicableDeclarations<Impl> {
     pub fn new() -> ApplicableDeclarations<Impl> {
         let mut applicable_declarations = ApplicableDeclarations {
@@ -360,41 +361,45 @@ pub enum StyleSharingResult<ConcreteRest
     /// We didn't find anybody to share the style with.
     CannotShare,
     /// The node's style can be shared. The integer specifies the index in the LRU cache that was
     /// hit and the damage that was done.
     StyleWasShared(usize, ConcreteRestyleDamage),
 }
 
 trait PrivateMatchMethods: TNode
-    where <Self::ConcreteElement as Element>::Impl: SelectorImplExt {
+    where <Self::ConcreteElement as Element>::Impl: SelectorImplExt<ComputedValues = Self::ConcreteComputedValues> {
+    /// Actually cascades style for a node or a pseudo-element of a node.
+    ///
+    /// Note that animations only apply to nodes or ::before or ::after
+    /// pseudo-elements.
     fn cascade_node_pseudo_element(&self,
                                    context: &SharedStyleContext<<Self::ConcreteElement as Element>::Impl>,
                                    parent_style: Option<&Arc<Self::ConcreteComputedValues>>,
                                    applicable_declarations: &[DeclarationBlock],
                                    mut style: Option<&mut Arc<Self::ConcreteComputedValues>>,
                                    applicable_declarations_cache:
                                     &mut ApplicableDeclarationsCache<Self::ConcreteComputedValues>,
-                                   new_animations_sender: &Mutex<Sender<Animation>>,
                                    shareable: bool,
                                    animate_properties: bool)
                                    -> (Self::ConcreteRestyleDamage, Arc<Self::ConcreteComputedValues>) {
         let mut cacheable = true;
         if animate_properties {
             cacheable = !self.update_animations_for_cascade(context, &mut style) && cacheable;
         }
 
-        let mut this_style;
+        let this_style;
         match parent_style {
             Some(ref parent_style) => {
                 let cache_entry = applicable_declarations_cache.find(applicable_declarations);
                 let cached_computed_values = match cache_entry {
+                    Some(ref style) => Some(&**style),
                     None => None,
-                    Some(ref style) => Some(&**style),
                 };
+
                 let (the_style, is_cacheable) = cascade(context.viewport_size,
                                                         applicable_declarations,
                                                         shareable,
                                                         Some(&***parent_style),
                                                         cached_computed_values,
                                                         context.error_reporter.clone());
                 cacheable = cacheable && is_cacheable;
                 this_style = the_style
@@ -406,32 +411,41 @@ trait PrivateMatchMethods: TNode
                                                         None,
                                                         None,
                                                         context.error_reporter.clone());
                 cacheable = cacheable && is_cacheable;
                 this_style = the_style
             }
         };
 
-        // Trigger transitions if necessary. This will reset `this_style` back to its old value if
-        // it did trigger a transition.
+        let mut this_style = Arc::new(this_style);
+
         if animate_properties {
+            let this_opaque = self.opaque();
+            // Trigger any present animations if necessary.
+            let mut animations_started = animation::maybe_start_animations::<<Self::ConcreteElement as Element>::Impl>(
+                &context,
+                this_opaque,
+                &this_style);
+
+            // Trigger transitions if necessary. This will reset `this_style` back
+            // to its old value if it did trigger a transition.
             if let Some(ref style) = style {
-                let animations_started =
-                    animation::start_transitions_if_applicable::<Self::ConcreteComputedValues>(
-                        new_animations_sender,
-                        self.opaque(),
+                animations_started |=
+                    animation::start_transitions_if_applicable::<<Self::ConcreteElement as Element>::Impl>(
+                        &context.new_animations_sender,
+                        this_opaque,
                         &**style,
                         &mut this_style);
-                cacheable = cacheable && !animations_started
             }
+
+            cacheable = cacheable && !animations_started
         }
 
         // Calculate style difference.
-        let this_style = Arc::new(this_style);
         let damage = Self::ConcreteRestyleDamage::compute(style.map(|s| &*s), &*this_style);
 
         // Cache the resolved style if it was cacheable.
         if cacheable {
             applicable_declarations_cache.insert(applicable_declarations.to_vec(),
                                                  this_style.clone());
         }
 
@@ -452,46 +466,51 @@ trait PrivateMatchMethods: TNode
         let this_opaque = self.opaque();
         let had_animations_to_expire;
         {
             let all_expired_animations = context.expired_animations.read().unwrap();
             let animations_to_expire = all_expired_animations.get(&this_opaque);
             had_animations_to_expire = animations_to_expire.is_some();
             if let Some(ref animations) = animations_to_expire {
                 for animation in *animations {
-                    animation.property_animation.update(Arc::make_mut(style).as_servo_mut(), 1.0);
+                    // NB: Expiring a keyframes animation is the same as not
+                    // applying the keyframes style to it, so we're safe.
+                    if let Animation::Transition(_, _, ref frame, _) = *animation {
+                        frame.property_animation.update(Arc::make_mut(style), 1.0);
+                    }
                 }
             }
         }
 
         if had_animations_to_expire {
             context.expired_animations.write().unwrap().remove(&this_opaque);
         }
 
         // Merge any running transitions into the current style, and cancel them.
         let had_running_animations = context.running_animations
                                             .read()
                                             .unwrap()
                                             .get(&this_opaque)
                                             .is_some();
         if had_running_animations {
             let mut all_running_animations = context.running_animations.write().unwrap();
-            for running_animation in all_running_animations.get(&this_opaque).unwrap() {
-                animation::update_style_for_animation::<Self::ConcreteComputedValues,
-                                                        Self::ConcreteRestyleDamage>(running_animation, style, None);
+            for mut running_animation in all_running_animations.get_mut(&this_opaque).unwrap() {
+                animation::update_style_for_animation::<Self::ConcreteRestyleDamage,
+                    <Self::ConcreteElement as Element>::Impl>(context, running_animation, style, None);
+                running_animation.mark_as_expired();
             }
-            all_running_animations.remove(&this_opaque);
         }
 
         had_animations_to_expire || had_running_animations
     }
 }
 
 impl<N: TNode> PrivateMatchMethods for N
-    where <N::ConcreteElement as Element>::Impl: SelectorImplExt {}
+    where <N::ConcreteElement as Element>::Impl:
+                SelectorImplExt<ComputedValues = N::ConcreteComputedValues> {}
 
 trait PrivateElementMatchMethods: TElement {
     fn share_style_with_candidate_if_possible(&self,
                                               parent_node: Option<Self::ConcreteNode>,
                                               candidate: &StyleSharingCandidate<<Self::ConcreteNode as
                                                                                  TNode>::ConcreteComputedValues>)
                                               -> Option<Arc<<Self::ConcreteNode as TNode>::ConcreteComputedValues>> {
         let parent_node = match parent_node {
@@ -636,35 +655,37 @@ pub trait MatchMethods : TNode {
 
             // TODO: case-sensitivity depends on the document type and quirks mode
             element.each_class(|class| bf.remove(class));
         }
     }
 
     unsafe fn cascade_node(&self,
                            context: &SharedStyleContext<<Self::ConcreteElement as Element>::Impl>,
+                           local_context: &LocalStyleContext<Self::ConcreteComputedValues>,
                            parent: Option<Self>,
-                           applicable_declarations: &ApplicableDeclarations<<Self::ConcreteElement as Element>::Impl>,
-                           applicable_declarations_cache:
-                             &mut ApplicableDeclarationsCache<Self::ConcreteComputedValues>,
-                           new_animations_sender: &Mutex<Sender<Animation>>)
-                           where <Self::ConcreteElement as Element>::Impl: SelectorImplExt {
+                           applicable_declarations: &ApplicableDeclarations<<Self::ConcreteElement as Element>::Impl>)
+    where <Self::ConcreteElement as Element>::Impl: SelectorImplExt<ComputedValues = Self::ConcreteComputedValues>
+    {
         // Get our parent's style. This must be unsafe so that we don't touch the parent's
         // borrow flags.
         //
         // FIXME(pcwalton): Isolate this unsafety into the `wrapper` module to allow
         // enforced safe, race-free access to the parent style.
         let parent_style = match parent {
-            None => None,
             Some(parent_node) => {
                 let parent_style = (*parent_node.borrow_data_unchecked().unwrap()).style.as_ref().unwrap();
                 Some(parent_style)
             }
+            None => None,
         };
 
+        let mut applicable_declarations_cache =
+            local_context.applicable_declarations_cache.borrow_mut();
+
         let damage;
         if self.is_text_node() {
             let mut data_ref = self.mutate_data().unwrap();
             let mut data = &mut *data_ref;
             let cloned_parent_style = Self::ConcreteComputedValues::style_for_child_text_node(parent_style.unwrap());
             damage = Self::ConcreteRestyleDamage::compute(data.style.as_ref(),
                                                           &*cloned_parent_style);
             data.style = Some(cloned_parent_style);
@@ -672,38 +693,40 @@ pub trait MatchMethods : TNode {
             damage = {
                 let mut data_ref = self.mutate_data().unwrap();
                 let mut data = &mut *data_ref;
                 let (mut damage, final_style) = self.cascade_node_pseudo_element(
                     context,
                     parent_style,
                     &applicable_declarations.normal,
                     data.style.as_mut(),
-                    applicable_declarations_cache,
-                    new_animations_sender,
+                    &mut applicable_declarations_cache,
                     applicable_declarations.normal_shareable,
                     true);
 
                 data.style = Some(final_style);
 
                 <Self::ConcreteElement as Element>::Impl::each_eagerly_cascaded_pseudo_element(|pseudo| {
                     let applicable_declarations_for_this_pseudo =
                         applicable_declarations.per_pseudo.get(&pseudo).unwrap();
 
 
                     if !applicable_declarations_for_this_pseudo.is_empty() {
+                        // NB: Transitions and animations should only work for
+                        // pseudo-elements ::before and ::after
+                        let should_animate_properties =
+                            <Self::ConcreteElement as Element>::Impl::pseudo_is_before_or_after(&pseudo);
                         let (new_damage, style) = self.cascade_node_pseudo_element(
                             context,
                             Some(data.style.as_ref().unwrap()),
                             &*applicable_declarations_for_this_pseudo,
                             data.per_pseudo.get_mut(&pseudo),
-                            applicable_declarations_cache,
-                            new_animations_sender,
+                            &mut applicable_declarations_cache,
                             false,
-                            false);
+                            should_animate_properties);
                         data.per_pseudo.insert(pseudo, style);
 
                         damage = damage | new_damage;
                     }
                 });
 
                 damage
             };
--- a/servo/components/style/parallel.rs
+++ b/servo/components/style/parallel.rs
@@ -53,17 +53,17 @@ pub fn traverse_dom<N, C>(root: N,
 
 /// A parallel top-down DOM traversal.
 #[inline(always)]
 fn top_down_dom<N, C>(unsafe_nodes: UnsafeNodeList,
                       proxy: &mut WorkerProxy<C::SharedContext, UnsafeNodeList>)
                       where N: TNode, C: DomTraversalContext<N> {
     let context = C::new(proxy.user_data(), unsafe_nodes.1);
 
-    let mut discovered_child_nodes = Vec::new();
+    let mut discovered_child_nodes = vec![];
     for unsafe_node in *unsafe_nodes.0 {
         // Get a real layout node.
         let node = unsafe { N::from_unsafe(&unsafe_node) };
 
         // Perform the appropriate traversal.
         context.process_preorder(node);
 
         let child_count = node.children_count();
--- a/servo/components/style/properties/data.py
+++ b/servo/components/style/properties/data.py
@@ -8,17 +8,17 @@ import re
 def to_rust_ident(name):
     name = name.replace("-", "_")
     if name in ["static", "super", "box", "move"]:  # Rust keywords
         name += "_"
     return name
 
 
 def to_camel_case(ident):
-    return re.sub("_([a-z])", lambda m: m.group(1).upper(), ident.strip("_").capitalize())
+    return re.sub("(^|_|-)([a-z])", lambda m: m.group(2).upper(), ident.strip("_").strip("-"))
 
 
 class Keyword(object):
     def __init__(self, name, values, gecko_constant_prefix=None,
                  extra_gecko_values=None, extra_servo_values=None):
         self.name = name
         self.values = values.split()
         self.gecko_constant_prefix = gecko_constant_prefix or \
@@ -40,32 +40,42 @@ class Keyword(object):
         else:
             raise Exception("Bad product: " + product)
 
     def gecko_constant(self, value):
         return self.gecko_constant_prefix + "_" + value.replace("-moz-", "").replace("-", "_").upper()
 
 
 class Longhand(object):
-    def __init__(self, style_struct, name, derived_from=None, keyword=None,
+    def __init__(self, style_struct, name, animatable=None, derived_from=None, keyword=None,
                  predefined_type=None, custom_cascade=False, experimental=False, internal=False,
                  need_clone=False, gecko_ffi_name=None):
         self.name = name
         self.keyword = keyword
         self.predefined_type = predefined_type
         self.ident = to_rust_ident(name)
         self.camel_case = to_camel_case(self.ident)
         self.style_struct = style_struct
         self.experimental = ("layout.%s.enabled" % name) if experimental else None
         self.custom_cascade = custom_cascade
         self.internal = internal
         self.need_clone = need_clone
         self.gecko_ffi_name = gecko_ffi_name or "m" + self.camel_case
         self.derived_from = (derived_from or "").split()
 
+        # This is done like this since just a plain bool argument seemed like
+        # really random.
+        if animatable is None:
+            raise TypeError("animatable should be specified for " + name + ")")
+        if isinstance(animatable, bool):
+            self.animatable = animatable
+        else:
+            assert animatable == "True" or animatable == "False"
+            self.animatable = animatable == "True"
+
 
 class Shorthand(object):
     def __init__(self, name, sub_properties, experimental=False, internal=False):
         self.name = name
         self.ident = to_rust_ident(name)
         self.camel_case = to_camel_case(self.ident)
         self.derived_from = None
         self.experimental = ("layout.%s.enabled" % name) if experimental else None
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.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/. */
 
-<%! from data import Keyword, to_rust_ident %>
+<%! from data import Keyword, to_rust_ident, to_camel_case %>
 
 <%def name="longhand(name, **kwargs)">
     <%call expr="raw_longhand(name, **kwargs)">
         ${caller.body()}
         % if not data.longhands_by_name[name].derived_from:
             pub fn parse_specified(context: &ParserContext, input: &mut Parser)
                                -> Result<DeclaredValue<SpecifiedValue>, ()> {
                 parse(context, input).map(DeclaredValue::Value)
@@ -176,26 +176,85 @@
         ${caller.body()}
         pub mod computed_value {
             define_css_keyword_enum! { T:
                 % for value in data.longhands_by_name[name].keyword.values_for(product):
                     "${value}" => ${to_rust_ident(value)},
                 % endfor
             }
         }
-        #[inline] pub fn get_initial_value() -> computed_value::T {
+        #[inline]
+        pub fn get_initial_value() -> computed_value::T {
             computed_value::T::${to_rust_ident(values.split()[0])}
         }
+        #[inline]
         pub fn parse(_context: &ParserContext, input: &mut Parser)
                      -> Result<SpecifiedValue, ()> {
             computed_value::T::parse(input)
         }
     </%call>
 </%def>
 
+<%def name="keyword_list(name, values, **kwargs)">
+    <%
+        keyword_kwargs = {a: kwargs.pop(a, None) for a in [
+            'gecko_constant_prefix', 'extra_gecko_values', 'extra_servo_values'
+        ]}
+    %>
+    <%call expr="longhand(name, keyword=Keyword(name, values, **keyword_kwargs), **kwargs)">
+        use values::computed::ComputedValueAsSpecified;
+        pub use self::computed_value::T as SpecifiedValue;
+        pub mod computed_value {
+            use cssparser::ToCss;
+            use std::fmt;
+
+            #[derive(Debug, Clone, PartialEq, HeapSizeOf)]
+            pub struct T(pub Vec<${to_camel_case(name)}>);
+
+            impl ToCss for T {
+                fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+                    debug_assert!(!self.0.is_empty(), "Always parses at least one");
+
+                    for (index, item) in self.0.iter().enumerate() {
+                        if index != 0 {
+                            try!(dest.write_str(", "));
+                        }
+
+                        try!(item.to_css(dest));
+                    }
+
+                    Ok(())
+                }
+            }
+
+            define_css_keyword_enum! { ${to_camel_case(name)}:
+                % for value in data.longhands_by_name[name].keyword.values_for(product):
+                    "${value}" => ${to_rust_ident(value)},
+                % endfor
+            }
+        }
+
+        #[inline]
+        pub fn get_initial_value() -> computed_value::T {
+            computed_value::T(vec![
+                computed_value::${to_camel_case(name)}::${to_rust_ident(values.split()[0])}
+            ])
+        }
+
+        #[inline]
+        pub fn parse(_context: &ParserContext, input: &mut Parser)
+                     -> Result<SpecifiedValue, ()> {
+            Ok(SpecifiedValue(try!(
+                input.parse_comma_separated(computed_value::${to_camel_case(name)}::parse))))
+        }
+
+        impl ComputedValueAsSpecified for SpecifiedValue {}
+    </%call>
+</%def>
+
 <%def name="shorthand(name, sub_properties, experimental=False, **kwargs)">
 <%
     shorthand = data.declare_shorthand(name, sub_properties.split(), experimental=experimental,
                                        **kwargs)
 %>
     % if shorthand:
     pub mod ${shorthand.ident} {
         use cssparser::Parser;
new file mode 100644
--- /dev/null
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -0,0 +1,878 @@
+/* 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 cssparser::{Color as CSSParserColor, Parser, RGBA, ToCss};
+use euclid::{Point2D, Size2D};
+use properties::PropertyDeclaration;
+use properties::longhands;
+use properties::longhands::background_position::computed_value::T as BackgroundPosition;
+use properties::longhands::background_size::computed_value::T as BackgroundSize;
+use properties::longhands::border_spacing::computed_value::T as BorderSpacing;
+use properties::longhands::clip::computed_value::ClipRect;
+use properties::longhands::font_weight::computed_value::T as FontWeight;
+use properties::longhands::line_height::computed_value::T as LineHeight;
+use properties::longhands::text_shadow::computed_value::T as TextShadowList;
+use properties::longhands::text_shadow::computed_value::TextShadow;
+use properties::longhands::box_shadow::computed_value::T as BoxShadowList;
+use properties::longhands::box_shadow::computed_value::BoxShadow;
+use properties::longhands::transform::computed_value::ComputedMatrix;
+use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation;
+use properties::longhands::transform::computed_value::T as TransformList;
+use properties::longhands::transform_origin::computed_value::T as TransformOrigin;
+use properties::longhands::vertical_align::computed_value::T as VerticalAlign;
+use properties::longhands::visibility::computed_value::T as Visibility;
+use properties::longhands::z_index::computed_value::T as ZIndex;
+use properties::style_struct_traits::*;
+use std::cmp::{self, Ordering};
+use std::fmt;
+use std::iter::repeat;
+use super::ComputedValues;
+use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone};
+use values::computed::{BorderRadiusSize, LengthOrNone};
+use values::computed::{CalcLengthOrPercentage, LengthOrPercentage};
+
+// NB: This needs to be here because it needs all the longhands generated
+// beforehand.
+#[derive(Copy, Clone, Debug, PartialEq, HeapSizeOf)]
+pub enum TransitionProperty {
+    All,
+    % for prop in data.longhands:
+        % if prop.animatable:
+            ${prop.camel_case},
+        % endif
+    % endfor
+}
+
+impl TransitionProperty {
+    /// Iterates over each property that is not `All`.
+    pub fn each<F: FnMut(TransitionProperty) -> ()>(mut cb: F) {
+        % for prop in data.longhands:
+            % if prop.animatable:
+                cb(TransitionProperty::${prop.camel_case});
+            % endif
+        % endfor
+    }
+
+    pub fn parse(input: &mut Parser) -> Result<Self, ()> {
+        match_ignore_ascii_case! { try!(input.expect_ident()),
+            "all" => Ok(TransitionProperty::All),
+            % for prop in data.longhands:
+                % if prop.animatable:
+                    "${prop.name}" => Ok(TransitionProperty::${prop.camel_case}),
+                % endif
+            % endfor
+            _ => Err(())
+        }
+    }
+
+    pub fn from_declaration(declaration: &PropertyDeclaration) -> Option<Self> {
+        match *declaration {
+            % for prop in data.longhands:
+                % if prop.animatable:
+                    PropertyDeclaration::${prop.camel_case}(..)
+                        => Some(TransitionProperty::${prop.camel_case}),
+                % endif
+            % endfor
+            _ => None,
+        }
+    }
+}
+
+impl ToCss for TransitionProperty {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            TransitionProperty::All => dest.write_str("all"),
+            % for prop in data.longhands:
+                % if prop.animatable:
+                    TransitionProperty::${prop.camel_case} => dest.write_str("${prop.name}"),
+                % endif
+            % endfor
+        }
+    }
+}
+
+#[derive(Clone, Debug, PartialEq, HeapSizeOf)]
+pub enum AnimatedProperty {
+    % for prop in data.longhands:
+        % if prop.animatable:
+            ${prop.camel_case}(longhands::${prop.ident}::computed_value::T,
+                               longhands::${prop.ident}::computed_value::T),
+        % endif
+    % endfor
+}
+
+impl AnimatedProperty {
+    pub fn does_animate(&self) -> bool {
+        match *self {
+            % for prop in data.longhands:
+                % if prop.animatable:
+                    AnimatedProperty::${prop.camel_case}(ref from, ref to) => from != to,
+                % endif
+            % endfor
+        }
+    }
+
+    pub fn update<C: ComputedValues>(&self, style: &mut C, progress: f64) {
+        match *self {
+            % for prop in data.longhands:
+                % if prop.animatable:
+                    AnimatedProperty::${prop.camel_case}(ref from, ref to) => {
+                        if let Some(value) = from.interpolate(to, progress) {
+                            style.mutate_${prop.style_struct.ident.strip("_")}().set_${prop.ident}(value);
+                        }
+                    }
+                % endif
+            % endfor
+        }
+    }
+
+    // NB: Transition properties need clone
+    pub fn from_transition_property<C: ComputedValues>(transition_property: &TransitionProperty,
+                                                       old_style: &C,
+                                                       new_style: &C) -> AnimatedProperty {
+        // TODO: Generalise this for GeckoLib, adding clone_xxx to the
+        // appropiate longhands.
+        let old_style = old_style.as_servo();
+        let new_style = new_style.as_servo();
+        match *transition_property {
+            TransitionProperty::All => panic!("Can't use TransitionProperty::All here."),
+            % for prop in data.longhands:
+                % if prop.animatable:
+                    TransitionProperty::${prop.camel_case} => {
+                        AnimatedProperty::${prop.camel_case}(
+                            old_style.get_${prop.style_struct.ident.strip("_")}().${prop.ident}.clone(),
+                            new_style.get_${prop.style_struct.ident.strip("_")}().${prop.ident}.clone())
+                    }
+                % endif
+            % endfor
+        }
+    }
+}
+
+/// A trait used to implement [interpolation][interpolated-types].
+///
+/// [interpolated-types]: https://drafts.csswg.org/css-transitions/#interpolated-types
+pub trait Interpolate: Sized {
+    fn interpolate(&self, other: &Self, time: f64) -> Option<Self>;
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-number
+impl Interpolate for Au {
+    #[inline]
+    fn interpolate(&self, other: &Au, time: f64) -> Option<Au> {
+        Some(Au((self.0 as f64 + (other.0 as f64 - self.0 as f64) * time).round() as i32))
+    }
+}
+
+impl <T> Interpolate for Option<T> where T: Interpolate {
+    #[inline]
+    fn interpolate(&self, other: &Option<T>, time: f64) -> Option<Option<T>> {
+        match (self, other) {
+            (&Some(ref this), &Some(ref other)) => {
+                this.interpolate(other, time).and_then(|value| {
+                    Some(Some(value))
+                })
+            }
+            (_, _) => None
+        }
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-number
+impl Interpolate for f32 {
+    #[inline]
+    fn interpolate(&self, other: &f32, time: f64) -> Option<f32> {
+        Some(((*self as f64) + ((*other as f64) - (*self as f64)) * time) as f32)
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-number
+impl Interpolate for f64 {
+    #[inline]
+    fn interpolate(&self, other: &f64, time: f64) -> Option<f64> {
+        Some(*self + (*other - *self) * time)
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-number
+impl Interpolate for i32 {
+    #[inline]
+    fn interpolate(&self, other: &i32, time: f64) -> Option<i32> {
+        let a = *self as f64;
+        let b = *other as f64;
+        Some((a + (b - a) * time).round() as i32)
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-number
+impl Interpolate for Angle {
+    #[inline]
+    fn interpolate(&self, other: &Angle, time: f64) -> Option<Angle> {
+        self.radians().interpolate(&other.radians(), time).map(Angle)
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-visibility
+impl Interpolate for Visibility {
+    #[inline]
+    fn interpolate(&self, other: &Visibility, time: f64)
+                   -> Option<Visibility> {
+        match (*self, *other) {
+            (Visibility::visible, _) | (_, Visibility::visible) => {
+                if time >= 0.0 && time <= 1.0 {
+                    Some(Visibility::visible)
+                } else if time < 0.0 {
+                    Some(*self)
+                } else {
+                    Some(*other)
+                }
+            }
+            (_, _) => None,
+        }
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-integer
+impl Interpolate for ZIndex {
+    #[inline]
+    fn interpolate(&self, other: &ZIndex, time: f64)
+                   -> Option<ZIndex> {
+        match (*self, *other) {
+            (ZIndex::Number(ref this),
+             ZIndex::Number(ref other)) => {
+                this.interpolate(other, time).and_then(|value| {
+                    Some(ZIndex::Number(value))
+                })
+            }
+            (_, _) => None,
+        }
+    }
+}
+
+impl<T: Interpolate + Clone> Interpolate for Size2D<T> {
+    #[inline]
+    fn interpolate(&self, other: &Self, time: f64) -> Option<Self> {
+        let width = match self.width.interpolate(&other.width, time) {
+            Some(width) => width,
+            None => return None,
+        };
+
+        let height = match self.height.interpolate(&other.height, time) {
+            Some(height) => height,
+            None => return None,
+        };
+        Some(Size2D::new(width, height))
+    }
+}
+
+impl<T: Interpolate + Clone> Interpolate for Point2D<T> {
+    #[inline]
+    fn interpolate(&self, other: &Self, time: f64) -> Option<Self> {
+        let x = match self.x.interpolate(&other.x, time) {
+            Some(x) => x,
+            None => return None,
+        };
+
+        let y = match self.y.interpolate(&other.y, time) {
+            Some(y) => y,
+            None => return None,
+        };
+
+        Some(Point2D::new(x, y))
+    }
+}
+
+impl Interpolate for BorderRadiusSize {
+    #[inline]
+    fn interpolate(&self, other: &Self, time: f64) -> Option<Self> {
+        self.0.interpolate(&other.0, time).map(BorderRadiusSize)
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-length
+impl Interpolate for VerticalAlign {
+    #[inline]
+    fn interpolate(&self, other: &VerticalAlign, time: f64)
+                   -> Option<VerticalAlign> {
+        match (*self, *other) {
+            (VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref this)),
+             VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref other))) => {
+                this.interpolate(other, time).and_then(|value| {
+                    Some(VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(value)))
+                })
+            }
+            (_, _) => None,
+        }
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-simple-list
+impl Interpolate for BorderSpacing {
+    #[inline]
+    fn interpolate(&self, other: &BorderSpacing, time: f64)
+                   -> Option<BorderSpacing> {
+        self.horizontal.interpolate(&other.horizontal, time).and_then(|horizontal| {
+            self.vertical.interpolate(&other.vertical, time).and_then(|vertical| {
+                Some(BorderSpacing { horizontal: horizontal, vertical: vertical })
+            })
+        })
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-color
+impl Interpolate for RGBA {
+    #[inline]
+    fn interpolate(&self, other: &RGBA, time: f64) -> Option<RGBA> {
+        match (self.red.interpolate(&other.red, time),
+               self.green.interpolate(&other.green, time),
+               self.blue.interpolate(&other.blue, time),
+               self.alpha.interpolate(&other.alpha, time)) {
+            (Some(red), Some(green), Some(blue), Some(alpha)) => {
+                Some(RGBA { red: red, green: green, blue: blue, alpha: alpha })
+            }
+            (_, _, _, _) => None
+        }
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-color
+impl Interpolate for CSSParserColor {
+    #[inline]
+    fn interpolate(&self, other: &Self, time: f64) -> Option<Self> {
+        match (*self, *other) {
+            (CSSParserColor::RGBA(ref this), CSSParserColor::RGBA(ref other)) => {
+                this.interpolate(other, time).and_then(|value| {
+                    Some(CSSParserColor::RGBA(value))
+                })
+            }
+            (_, _) => None,
+        }
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
+impl Interpolate for CalcLengthOrPercentage {
+    #[inline]
+    fn interpolate(&self, other: &CalcLengthOrPercentage, time: f64)
+                   -> Option<CalcLengthOrPercentage> {
+        Some(CalcLengthOrPercentage {
+            length: self.length().interpolate(&other.length(), time),
+            percentage: self.percentage().interpolate(&other.percentage(), time),
+        })
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
+impl Interpolate for LengthOrPercentage {
+    #[inline]
+    fn interpolate(&self, other: &LengthOrPercentage, time: f64)
+                   -> Option<LengthOrPercentage> {
+        match (*self, *other) {
+            (LengthOrPercentage::Length(ref this),
+             LengthOrPercentage::Length(ref other)) => {
+                this.interpolate(other, time).and_then(|value| {
+                    Some(LengthOrPercentage::Length(value))
+                })
+            }
+            (LengthOrPercentage::Percentage(ref this),
+             LengthOrPercentage::Percentage(ref other)) => {
+                this.interpolate(other, time).and_then(|value| {
+                    Some(LengthOrPercentage::Percentage(value))
+                })
+            }
+            (this, other) => {
+                let this: CalcLengthOrPercentage = From::from(this);
+                let other: CalcLengthOrPercentage = From::from(other);
+                this.interpolate(&other, time).and_then(|value| {
+                    Some(LengthOrPercentage::Calc(value))
+                })
+            }
+        }
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
+impl Interpolate for LengthOrPercentageOrAuto {
+    #[inline]
+    fn interpolate(&self, other: &LengthOrPercentageOrAuto, time: f64)
+                   -> Option<LengthOrPercentageOrAuto> {
+        match (*self, *other) {
+            (LengthOrPercentageOrAuto::Length(ref this),
+             LengthOrPercentageOrAuto::Length(ref other)) => {
+                this.interpolate(other, time).and_then(|value| {
+                    Some(LengthOrPercentageOrAuto::Length(value))
+                })
+            }
+            (LengthOrPercentageOrAuto::Percentage(ref this),
+             LengthOrPercentageOrAuto::Percentage(ref other)) => {
+                this.interpolate(other, time).and_then(|value| {
+                    Some(LengthOrPercentageOrAuto::Percentage(value))
+                })
+            }
+            (LengthOrPercentageOrAuto::Auto, LengthOrPercentageOrAuto::Auto) => {
+                Some(LengthOrPercentageOrAuto::Auto)
+            }
+            (this, other) => {
+                let this: Option<CalcLengthOrPercentage> = From::from(this);
+                let other: Option<CalcLengthOrPercentage> = From::from(other);
+                this.interpolate(&other, time).unwrap_or(None).and_then(|value| {
+                    Some(LengthOrPercentageOrAuto::Calc(value))
+                })
+            }
+        }
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
+impl Interpolate for LengthOrPercentageOrNone {
+    #[inline]
+    fn interpolate(&self, other: &LengthOrPercentageOrNone, time: f64)
+                   -> Option<LengthOrPercentageOrNone> {
+        match (*self, *other) {
+            (LengthOrPercentageOrNone::Length(ref this),
+             LengthOrPercentageOrNone::Length(ref other)) => {
+                this.interpolate(other, time).and_then(|value| {
+                    Some(LengthOrPercentageOrNone::Length(value))
+                })
+            }
+            (LengthOrPercentageOrNone::Percentage(ref this),
+             LengthOrPercentageOrNone::Percentage(ref other)) => {
+                this.interpolate(other, time).and_then(|value| {
+                    Some(LengthOrPercentageOrNone::Percentage(value))
+                })
+            }
+            (LengthOrPercentageOrNone::None, LengthOrPercentageOrNone::None) => {
+                Some(LengthOrPercentageOrNone::None)
+            }
+            (_, _) => None,
+        }
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-number
+/// https://drafts.csswg.org/css-transitions/#animtype-length
+impl Interpolate for LineHeight {
+    #[inline]
+    fn interpolate(&self, other: &LineHeight, time: f64)
+                   -> Option<LineHeight> {
+        match (*self, *other) {
+            (LineHeight::Length(ref this),
+             LineHeight::Length(ref other)) => {
+                this.interpolate(other, time).and_then(|value| {
+                    Some(LineHeight::Length(value))
+                })
+            }
+            (LineHeight::Number(ref this),
+             LineHeight::Number(ref other)) => {
+                this.interpolate(other, time).and_then(|value| {
+                    Some(LineHeight::Number(value))
+                })
+            }
+            (LineHeight::Normal, LineHeight::Normal) => {
+                Some(LineHeight::Normal)
+            }
+            (_, _) => None,
+        }
+    }
+}
+
+/// http://dev.w3.org/csswg/css-transitions/#animtype-font-weight
+impl Interpolate for FontWeight {
+    #[inline]
+    fn interpolate(&self, other: &FontWeight, time: f64)
+                   -> Option<FontWeight> {
+        let a = (*self as u32) as f64;
+        let b = (*other as u32) as f64;
+        let weight = a + (b - a) * time;
+        Some(if weight < 150. {
+            FontWeight::Weight100
+        } else if weight < 250. {
+            FontWeight::Weight200
+        } else if weight < 350. {
+            FontWeight::Weight300
+        } else if weight < 450. {
+            FontWeight::Weight400
+        } else if weight < 550. {
+            FontWeight::Weight500
+        } else if weight < 650. {
+            FontWeight::Weight600
+        } else if weight < 750. {
+            FontWeight::Weight700
+        } else if weight < 850. {
+            FontWeight::Weight800
+        } else {
+            FontWeight::Weight900
+        })
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-rect
+impl Interpolate for ClipRect {
+    #[inline]
+    fn interpolate(&self, other: &ClipRect, time: f64)
+                   -> Option<ClipRect> {
+        match (self.top.interpolate(&other.top, time),
+               self.right.interpolate(&other.right, time),
+               self.bottom.interpolate(&other.bottom, time),
+               self.left.interpolate(&other.left, time)) {
+            (Some(top), Some(right), Some(bottom), Some(left)) => {
+                Some(ClipRect { top: top, right: right, bottom: bottom, left: left })
+            },
+            (_, _, _, _) => None,
+        }
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-simple-list
+impl Interpolate for BackgroundPosition {
+    #[inline]
+    fn interpolate(&self, other: &BackgroundPosition, time: f64)
+                   -> Option<BackgroundPosition> {
+        match (self.horizontal.interpolate(&other.horizontal, time),
+               self.vertical.interpolate(&other.vertical, time)) {
+            (Some(horizontal), Some(vertical)) => {
+                Some(BackgroundPosition { horizontal: horizontal, vertical: vertical })
+            },
+            (_, _) => None,
+        }
+    }
+}
+
+impl Interpolate for BackgroundSize {
+    fn interpolate(&self, other: &Self, time: f64) -> Option<Self> {
+        use properties::longhands::background_size::computed_value::ExplicitSize;
+        match (self, other) {
+            (&BackgroundSize::Explicit(ref me), &BackgroundSize::Explicit(ref other))
+                => match (me.width.interpolate(&other.width, time),
+                          me.height.interpolate(&other.height, time)) {
+                       (Some(width), Some(height))
+                           => Some(BackgroundSize::Explicit(
+                               ExplicitSize { width: width, height: height })),
+                        _ => None,
+                   },
+            _ => None
+        }
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list
+impl Interpolate for TextShadow {
+    #[inline]
+    fn interpolate(&self, other: &TextShadow, time: f64)
+                   -> Option<TextShadow> {
+        match (self.offset_x.interpolate(&other.offset_x, time),
+               self.offset_y.interpolate(&other.offset_y, time),
+               self.blur_radius.interpolate(&other.blur_radius, time),
+               self.color.interpolate(&other.color, time)) {
+            (Some(offset_x), Some(offset_y), Some(blur_radius), Some(color)) => {
+                Some(TextShadow { offset_x: offset_x, offset_y: offset_y, blur_radius: blur_radius, color: color })
+            },
+            _ => None,
+        }
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list
+impl Interpolate for TextShadowList {
+    #[inline]
+    fn interpolate(&self, other: &TextShadowList, time: f64)
+                   -> Option<TextShadowList> {
+        let zero = TextShadow {
+            offset_x: Au(0),
+            offset_y: Au(0),
+            blur_radius: Au(0),
+            color: CSSParserColor::RGBA(RGBA {
+                red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0
+            })
+        };
+
+        let interpolate_each = |(a, b): (&TextShadow, &TextShadow)| {
+            a.interpolate(b, time).unwrap()
+        };
+
+        Some(TextShadowList(match self.0.len().cmp(&other.0.len()) {
+            Ordering::Less => other.0.iter().chain(repeat(&zero)).zip(other.0.iter()).map(interpolate_each).collect(),
+            _ => self.0.iter().zip(other.0.iter().chain(repeat(&zero))).map(interpolate_each).collect(),
+        }))
+    }
+}
+
+/// Check if it's possible to do a direct numerical interpolation
+/// between these two transform lists.
+/// http://dev.w3.org/csswg/css-transforms/#transform-transform-animation
+fn can_interpolate_list(from_list: &[TransformOperation],
+                        to_list: &[TransformOperation]) -> bool {
+    // Lists must be equal length
+    if from_list.len() != to_list.len() {
+        return false;
+    }
+
+    // Each transform operation must match primitive type in other list
+    for (from, to) in from_list.iter().zip(to_list) {
+        match (from, to) {
+            (&TransformOperation::Matrix(..), &TransformOperation::Matrix(..)) |
+            (&TransformOperation::Skew(..), &TransformOperation::Skew(..)) |
+            (&TransformOperation::Translate(..), &TransformOperation::Translate(..)) |
+            (&TransformOperation::Scale(..), &TransformOperation::Scale(..)) |
+            (&TransformOperation::Rotate(..), &TransformOperation::Rotate(..)) |
+            (&TransformOperation::Perspective(..), &TransformOperation::Perspective(..)) => {}
+            _ => {
+                return false;
+            }
+        }
+    }
+
+    true
+}
+
+/// Interpolate two transform lists.
+/// http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms
+fn interpolate_transform_list(from_list: &[TransformOperation],
+                              to_list: &[TransformOperation],
+                              time: f64) -> TransformList {
+    let mut result = vec![];
+
+    if can_interpolate_list(from_list, to_list) {
+        for (from, to) in from_list.iter().zip(to_list) {
+            match (from, to) {
+                (&TransformOperation::Matrix(from),
+                 &TransformOperation::Matrix(_to)) => {
+                    // TODO(gw): Implement matrix decomposition and interpolation
+                    result.push(TransformOperation::Matrix(from));
+                }
+                (&TransformOperation::Skew(fx, fy),
+                 &TransformOperation::Skew(tx, ty)) => {
+                    let ix = fx.interpolate(&tx, time).unwrap();
+                    let iy = fy.interpolate(&ty, time).unwrap();
+                    result.push(TransformOperation::Skew(ix, iy));
+                }
+                (&TransformOperation::Translate(fx, fy, fz),
+                 &TransformOperation::Translate(tx, ty, tz)) => {
+                    let ix = fx.interpolate(&tx, time).unwrap();
+                    let iy = fy.interpolate(&ty, time).unwrap();
+                    let iz = fz.interpolate(&tz, time).unwrap();
+                    result.push(TransformOperation::Translate(ix, iy, iz));
+                }
+                (&TransformOperation::Scale(fx, fy, fz),
+                 &TransformOperation::Scale(tx, ty, tz)) => {
+                    let ix = fx.interpolate(&tx, time).unwrap();
+                    let iy = fy.interpolate(&ty, time).unwrap();
+                    let iz = fz.interpolate(&tz, time).unwrap();
+                    result.push(TransformOperation::Scale(ix, iy, iz));
+                }
+                (&TransformOperation::Rotate(fx, fy, fz, fa),
+                 &TransformOperation::Rotate(_tx, _ty, _tz, _ta)) => {
+                    // TODO(gw): Implement matrix decomposition and interpolation
+                    result.push(TransformOperation::Rotate(fx, fy, fz, fa));
+                }
+                (&TransformOperation::Perspective(fd),
+                 &TransformOperation::Perspective(_td)) => {
+                    // TODO(gw): Implement matrix decomposition and interpolation
+                    result.push(TransformOperation::Perspective(fd));
+                }
+                _ => {
+                    // This should be unreachable due to the can_interpolate_list() call.
+                    unreachable!();
+                }
+            }
+        }
+    } else {
+        // TODO(gw): Implement matrix decomposition and interpolation
+        result.extend_from_slice(from_list);
+    }
+
+    TransformList(Some(result))
+}
+
+/// Build an equivalent 'identity transform function list' based
+/// on an existing transform list.
+/// http://dev.w3.org/csswg/css-transforms/#none-transform-animation
+fn build_identity_transform_list(list: &[TransformOperation]) -> Vec<TransformOperation> {
+    let mut result = vec!();
+
+    for operation in list {
+        match *operation {
+            TransformOperation::Matrix(..) => {
+                let identity = ComputedMatrix::identity();
+                result.push(TransformOperation::Matrix(identity));
+            }
+            TransformOperation::Skew(..) => {
+                result.push(TransformOperation::Skew(Angle(0.0), Angle(0.0)));
+            }
+            TransformOperation::Translate(..) => {
+                result.push(TransformOperation::Translate(LengthOrPercentage::zero(),
+                                                          LengthOrPercentage::zero(),
+                                                          Au(0)));
+            }
+            TransformOperation::Scale(..) => {
+                result.push(TransformOperation::Scale(1.0, 1.0, 1.0));
+            }
+            TransformOperation::Rotate(..) => {
+                result.push(TransformOperation::Rotate(0.0, 0.0, 1.0, Angle(0.0)));
+            }
+            TransformOperation::Perspective(..) => {
+                // http://dev.w3.org/csswg/css-transforms/#identity-transform-function
+                let identity = ComputedMatrix::identity();
+                result.push(TransformOperation::Matrix(identity));
+            }
+        }
+    }
+
+    result
+}
+
+impl Interpolate for BoxShadowList {
+    #[inline]
+    fn interpolate(&self, other: &Self, time: f64)
+                   -> Option<Self> {
+        // The inset value must change
+        let mut zero = BoxShadow {
+            offset_x: Au(0),
+            offset_y: Au(0),
+            spread_radius: Au(0),
+            blur_radius: Au(0),
+            color: CSSParserColor::RGBA(RGBA {
+                red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0
+            }),
+            inset: false,
+        };
+
+        let max_len = cmp::max(self.0.len(), other.0.len());
+        let mut result = Vec::with_capacity(max_len);
+
+        for i in 0..max_len {
+            let shadow = match (self.0.get(i), other.0.get(i)) {
+                (Some(shadow), Some(other)) => {
+                    match shadow.interpolate(other, time) {
+                        Some(shadow) => shadow,
+                        None => return None,
+                    }
+                }
+                (Some(shadow), None) => {
+                    zero.inset = shadow.inset;
+                    shadow.interpolate(&zero, time).unwrap()
+                }
+                (None, Some(shadow)) => {
+                    zero.inset = shadow.inset;
+                    zero.interpolate(&shadow, time).unwrap()
+                }
+                (None, None) => unreachable!(),
+            };
+            result.push(shadow);
+        }
+
+        Some(BoxShadowList(result))
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list
+impl Interpolate for BoxShadow {
+    #[inline]
+    fn interpolate(&self, other: &Self, time: f64)
+                  -> Option<Self> {
+        if self.inset != other.inset {
+            return None;
+        }
+
+        let x = match self.offset_x.interpolate(&other.offset_x, time) {
+            Some(x) => x,
+            None => return None,
+        };
+
+        let y = match self.offset_y.interpolate(&other.offset_y, time) {
+            Some(y) => y,
+            None => return None,
+        };
+
+        let color = match self.color.interpolate(&other.color, time) {
+            Some(c) => c,
+            None => return None,
+        };
+
+        let spread = match self.spread_radius.interpolate(&other.spread_radius, time) {
+            Some(s) => s,
+            None => return None,
+        };
+
+        let blur = match self.blur_radius.interpolate(&other.blur_radius, time) {
+            Some(r) => r,
+            None => return None,
+        };
+
+        Some(BoxShadow {
+            offset_x: x,
+            offset_y: y,
+            blur_radius: blur,
+            spread_radius: spread,
+            color: color,
+            inset: self.inset,
+        })
+    }
+}
+
+impl Interpolate for LengthOrNone {
+    fn interpolate(&self, other: &Self, time: f64) -> Option<Self> {
+        match (*self, *other) {
+            (LengthOrNone::Length(ref len), LengthOrNone::Length(ref other)) =>
+                len.interpolate(&other, time).map(LengthOrNone::Length),
+            _ => None,
+        }
+    }
+}
+
+impl Interpolate for TransformOrigin {
+    fn interpolate(&self, other: &Self, time: f64) -> Option<Self> {
+        let horizontal = match self.horizontal.interpolate(&other.horizontal, time) {
+            Some(h) => h,
+            None => return None,
+        };
+
+        let vertical = match self.vertical.interpolate(&other.vertical, time) {
+            Some(v) => v,
+            None => return None,
+        };
+
+        let depth = match self.depth.interpolate(&other.depth, time) {
+            Some(d) => d,
+            None => return None,
+        };
+
+        Some(TransformOrigin {
+            horizontal: horizontal,
+            vertical: vertical,
+            depth: depth,
+        })
+    }
+}
+
+/// https://drafts.csswg.org/css-transforms/#interpolation-of-transforms
+impl Interpolate for TransformList {
+    #[inline]
+    fn interpolate(&self, other: &TransformList, time: f64) -> Option<TransformList> {
+        // http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms
+        let result = match (&self.0, &other.0) {
+            (&Some(ref from_list), &Some(ref to_list)) => {
+                // Two lists of transforms
+                interpolate_transform_list(from_list, &to_list, time)
+            }
+            (&Some(ref from_list), &None) => {
+                // http://dev.w3.org/csswg/css-transforms/#none-transform-animation
+                let to_list = build_identity_transform_list(from_list);
+                interpolate_transform_list(from_list, &to_list, time)
+            }
+            (&None, &Some(ref to_list)) => {
+                // http://dev.w3.org/csswg/css-transforms/#none-transform-animation
+                let from_list = build_identity_transform_list(to_list);
+                interpolate_transform_list(&from_list, to_list, time)
+            }
+            _ => {
+                // http://dev.w3.org/csswg/css-transforms/#none-none-animation
+                TransformList(None)
+            }
+        };
+
+        Some(result)
+    }
+}
--- a/servo/components/style/properties/longhand/background.mako.rs
+++ b/servo/components/style/properties/longhand/background.mako.rs
@@ -1,20 +1,21 @@
 /* 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/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("Background", inherited=False) %>
-${helpers.predefined_type(
-    "background-color", "CSSColor",
-    "::cssparser::Color::RGBA(::cssparser::RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */")}
 
-<%helpers:longhand name="background-image">
+${helpers.predefined_type("background-color", "CSSColor",
+    "::cssparser::Color::RGBA(::cssparser::RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */",
+    animatable=True)}
+
+<%helpers:longhand name="background-image" animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::specified::Image;
     use values::LocalToCss;
 
     pub mod computed_value {
         use values::computed;
         #[derive(Debug, Clone, PartialEq)]
@@ -66,17 +67,17 @@
                 SpecifiedValue(None) => computed_value::T(None),
                 SpecifiedValue(Some(ref image)) =>
                     computed_value::T(Some(image.to_computed_value(context))),
             }
         }
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="background-position">
+<%helpers:longhand name="background-position" animatable="True">
         use cssparser::ToCss;
         use std::fmt;
         use values::LocalToCss;
 
         pub mod computed_value {
             use values::computed::LengthOrPercentage;
 
             #[derive(PartialEq, Copy, Clone, Debug)]
@@ -181,25 +182,33 @@
                      -> Result<SpecifiedValue, ()> {
             let first = try!(specified::PositionComponent::parse(input));
             let second = input.try(specified::PositionComponent::parse)
                 .unwrap_or(specified::PositionComponent::Center);
             SpecifiedValue::new(first, second)
         }
 </%helpers:longhand>
 
-${helpers.single_keyword("background-repeat", "repeat repeat-x repeat-y no-repeat")}
+${helpers.single_keyword("background-repeat",
+                         "repeat repeat-x repeat-y no-repeat",
+                         animatable=False)}
 
-${helpers.single_keyword("background-attachment", "scroll fixed" + (" local" if product == "gecko" else ""))}
+${helpers.single_keyword("background-attachment",
+                         "scroll fixed" + (" local" if product == "gecko" else ""),
+                         animatable=False)}
 
-${helpers.single_keyword("background-clip", "border-box padding-box content-box")}
+${helpers.single_keyword("background-clip",
+                         "border-box padding-box content-box",
+                         animatable=False)}
 
-${helpers.single_keyword("background-origin", "padding-box border-box content-box")}
+${helpers.single_keyword("background-origin",
+                         "padding-box border-box content-box",
+                         animatable=False)}
 
-<%helpers:longhand name="background-size">
+<%helpers:longhand name="background-size" animatable="True">
     use cssparser::{ToCss, Token};
     use std::ascii::AsciiExt;
     use std::fmt;
 
     pub mod computed_value {
         use values::computed::LengthOrPercentageOrAuto;
 
         #[derive(PartialEq, Clone, Debug)]
--- a/servo/components/style/properties/longhand/border.mako.rs
+++ b/servo/components/style/properties/longhand/border.mako.rs
@@ -5,25 +5,29 @@
 <%namespace name="helpers" file="/helpers.mako.rs" />
 <% from data import Method %>
 
 <% data.new_style_struct("Border", inherited=False,
                    additional_methods=[Method("border_" + side + "_has_nonzero_width",
                                               "bool") for side in ["top", "right", "bottom", "left"]]) %>
 
 % for side in ["top", "right", "bottom", "left"]:
-    ${helpers.predefined_type("border-%s-color" % side, "CSSColor", "::cssparser::Color::CurrentColor")}
+    ${helpers.predefined_type("border-%s-color" % side, "CSSColor",
+                              "::cssparser::Color::CurrentColor",
+                              animatable=True)}
 % endfor
 
 % for side in ["top", "right", "bottom", "left"]:
-    ${helpers.predefined_type("border-%s-style" % side, "BorderStyle", "specified::BorderStyle::none", need_clone=True)}
+    ${helpers.predefined_type("border-%s-style" % side, "BorderStyle",
+                              "specified::BorderStyle::none",
+                              need_clone=True, animatable=False)}
 % endfor
 
 % for side in ["top", "right", "bottom", "left"]:
-    <%helpers:longhand name="border-${side}-width">
+    <%helpers:longhand name="border-${side}-width" animatable="True">
         use app_units::Au;
         use cssparser::ToCss;
         use std::fmt;
 
         impl ToCss for SpecifiedValue {
             fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
                 self.0.to_css(dest)
             }
@@ -55,18 +59,20 @@
         }
     </%helpers:longhand>
 % endfor
 
 // FIXME(#4126): when gfx supports painting it, make this Size2D<LengthOrPercentage>
 % for corner in ["top-left", "top-right", "bottom-right", "bottom-left"]:
     ${helpers.predefined_type("border-" + corner + "-radius", "BorderRadiusSize",
                               "computed::BorderRadiusSize::zero()",
-                              "parse")}
+                              "parse",
+                              animatable=True)}
 % endfor
 
-${helpers.single_keyword("box-decoration-break", "slice clone", products="gecko")}
+${helpers.single_keyword("box-decoration-break", "slice clone",
+                         products="gecko", animatable=False)}
 
-${helpers.single_keyword("-moz-float-edge",
-                         "content-box margin-box",
+${helpers.single_keyword("-moz-float-edge", "content-box margin-box",
                          gecko_ffi_name="mFloatEdge",
                          gecko_constant_prefix="NS_STYLE_FLOAT_EDGE",
-                         products="gecko")}
+                         products="gecko",
+                         animatable=False)}
--- a/servo/components/style/properties/longhand/box.mako.rs
+++ b/servo/components/style/properties/longhand/box.mako.rs
@@ -6,17 +6,20 @@
 <% from data import Keyword, Method, to_rust_ident %>
 
 <% data.new_style_struct("Box",
                          inherited=False,
                          gecko_name="Display",
                          additional_methods=[Method("transition_count", "usize")]) %>
 
 // TODO(SimonSapin): don't parse `inline-table`, since we don't support it
-<%helpers:longhand name="display" need_clone="True" custom_cascade="${product == 'servo'}">
+<%helpers:longhand name="display"
+                   need_clone="True"
+                   animatable="False"
+                   custom_cascade="${product == 'servo'}">
     <%
         values = """inline block inline-block
             table inline-table table-row-group table-header-group table-footer-group
             table-row table-column-group table-column table-cell table-caption
             list-item flex
             none
         """.split()
         if product == "gecko":
@@ -81,19 +84,24 @@
             longhands::_servo_display_for_hypothetical_box::derive_from_display(context);
             longhands::_servo_text_decorations_in_effect::derive_from_display(context);
             longhands::_servo_under_display_none::derive_from_display(context);
         }
     % endif
 
 </%helpers:longhand>
 
-${helpers.single_keyword("position", "static absolute relative fixed", need_clone=True, extra_gecko_values="sticky")}
+${helpers.single_keyword("position", "static absolute relative fixed",
+                         need_clone=True, extra_gecko_values="sticky", animatable=False)}
 
-<%helpers:single_keyword_computed name="float" values="none left right" need_clone="True" gecko_ffi_name="mFloats">
+<%helpers:single_keyword_computed name="float"
+                                  values="none left right"
+                                  animatable="False"
+                                  need_clone="True"
+                                  gecko_ffi_name="mFloats">
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
 
         #[inline]
         fn to_computed_value<Cx: TContext>(&self, context: &Cx) -> computed_value::T {
             let positioned = matches!(context.style().get_box().clone_position(),
                 longhands::position::SpecifiedValue::absolute |
                 longhands::position::SpecifiedValue::fixed);
@@ -102,35 +110,40 @@
             } else {
                 *self
             }
         }
     }
 
 </%helpers:single_keyword_computed>
 
-${helpers.single_keyword("clear", "none left right both", gecko_ffi_name="mBreakType")}
+${helpers.single_keyword("clear", "none left right both",
+                         animatable=False, gecko_ffi_name="mBreakType")}
 
-<%helpers:longhand name="-servo-display-for-hypothetical-box" derived_from="display" products="servo">
+<%helpers:longhand name="-servo-display-for-hypothetical-box"
+                   animatable="False"
+                   derived_from="display"
+                   products="servo">
     pub use super::display::{SpecifiedValue, get_initial_value};
     pub use super::display::{parse};
 
     pub mod computed_value {
         pub type T = super::SpecifiedValue;
     }
 
     #[inline]
     pub fn derive_from_display<Cx: TContext>(context: &mut Cx) {
         let d = context.style().get_box().clone_display();
         context.mutate_style().mutate_box().set__servo_display_for_hypothetical_box(d);
     }
 
 </%helpers:longhand>
 
-<%helpers:longhand name="vertical-align">
+<%helpers:longhand name="vertical-align"
+                   animatable="True">
   use cssparser::ToCss;
   use std::fmt;
 
   <% vertical_align = data.longhands_by_name["vertical-align"] %>
   <% vertical_align.keyword = Keyword("vertical-align",
                                       "baseline sub super top text-top middle bottom text-bottom",
                                       extra_gecko_values="middle-with-baseline") %>
   <% vertical_align_keywords = vertical_align.keyword.values_for(product) %>
@@ -214,28 +227,31 @@
       }
   }
 </%helpers:longhand>
 
 
 // CSS 2.1, Section 11 - Visual effects
 
 // Non-standard, see https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-box#Specifications
-${helpers.single_keyword("-servo-overflow-clip-box", "padding-box content-box", products="servo",
-               internal=True)}
+${helpers.single_keyword("-servo-overflow-clip-box", "padding-box content-box",
+                         products="servo", animatable=False, internal=True)}
 
-${helpers.single_keyword("overflow-clip-box", "padding-box content-box", products="gecko",
-               internal=True)}
+${helpers.single_keyword("overflow-clip-box", "padding-box content-box",
+                         products="gecko", animatable=False, internal=True)}
 
 // FIXME(pcwalton, #2742): Implement scrolling for `scroll` and `auto`.
-${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone=True,
-                       gecko_constant_prefix="NS_STYLE_OVERFLOW")}
+${helpers.single_keyword("overflow-x", "visible hidden scroll auto",
+                         need_clone=True, animatable=False,
+                         gecko_constant_prefix="NS_STYLE_OVERFLOW")}
 
 // FIXME(pcwalton, #2742): Implement scrolling for `scroll` and `auto`.
-<%helpers:longhand name="overflow-y" need_clone="True">
+<%helpers:longhand name="overflow-y"
+                   need_clone="True"
+                   animatable="False">
   use super::overflow_x;
 
   use cssparser::ToCss;
   use std::fmt;
 
   pub use self::computed_value::T as SpecifiedValue;
 
   impl ToCss for SpecifiedValue {
@@ -264,58 +280,52 @@
   }
 
   pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
       overflow_x::parse(context, input).map(SpecifiedValue)
   }
 </%helpers:longhand>
 
 // TODO(pcwalton): Multiple transitions.
-<%helpers:longhand name="transition-duration">
+<%helpers:longhand name="transition-duration" animatable="False">
+    use values::computed::ComputedValueAsSpecified;
     use values::specified::Time;
 
     pub use self::computed_value::T as SpecifiedValue;
     pub use values::specified::Time as SingleSpecifiedValue;
 
     pub mod computed_value {
         use cssparser::ToCss;
         use std::fmt;
         use values::computed::{TContext, ToComputedValue};
 
         pub use values::computed::Time as SingleComputedValue;
 
         #[derive(Debug, Clone, PartialEq)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub struct T(pub Vec<SingleComputedValue>);
 
-        impl ToComputedValue for T {
-            type ComputedValue = T;
-
-            #[inline]
-            fn to_computed_value<Cx: TContext>(&self, _: &Cx) -> T {
-                (*self).clone()
-            }
-        }
-
         impl ToCss for T {
             fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
                 if self.0.is_empty() {
                     return dest.write_str("none")
                 }
                 for (i, value) in self.0.iter().enumerate() {
                     if i != 0 {
                         try!(dest.write_str(", "))
                     }
                     try!(value.to_css(dest))
                 }
                 Ok(())
             }
         }
     }
 
+    impl ComputedValueAsSpecified for SpecifiedValue {}
+
     #[inline]
     pub fn parse_one(input: &mut Parser) -> Result<SingleSpecifiedValue,()> {
         Time::parse(input)
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T(vec![get_initial_single_value()])
@@ -328,17 +338,17 @@
 
     pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
         Ok(SpecifiedValue(try!(input.parse_comma_separated(parse_one))))
     }
 </%helpers:longhand>
 
 // TODO(pcwalton): Lots more timing functions.
 // TODO(pcwalton): Multiple transitions.
-<%helpers:longhand name="transition-timing-function">
+<%helpers:longhand name="transition-timing-function" animatable="False">
     use self::computed_value::{StartEnd, TransitionTimingFunction};
 
     use euclid::point::Point2D;
 
     pub use self::computed_value::SingleComputedValue as SingleSpecifiedValue;
     pub use self::computed_value::T as SpecifiedValue;
 
     static EASE: TransitionTimingFunction = TransitionTimingFunction::CubicBezier(Point2D {
@@ -526,180 +536,27 @@
         }
     }
 
     pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
         Ok(SpecifiedValue(try!(input.parse_comma_separated(parse_one))))
     }
 </%helpers:longhand>
 
-// TODO(pcwalton): Lots more properties.
-<%helpers:longhand name="transition-property">
-    use self::computed_value::TransitionProperty;
-
+<%helpers:longhand name="transition-property" animatable="False">
     pub use self::computed_value::SingleComputedValue as SingleSpecifiedValue;
     pub use self::computed_value::T as SpecifiedValue;
 
     pub mod computed_value {
         use cssparser::ToCss;
         use std::fmt;
-
-        pub use self::TransitionProperty as SingleComputedValue;
-
-        #[derive(Copy, Clone, Debug, PartialEq)]
-        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-        pub enum TransitionProperty {
-            All,
-            BackgroundColor,
-            BackgroundPosition,
-            BorderBottomColor,
-            BorderBottomWidth,
-            BorderLeftColor,
-            BorderLeftWidth,
-            BorderRightColor,
-            BorderRightWidth,
-            BorderSpacing,
-            BorderTopColor,
-            BorderTopWidth,
-            Bottom,
-            Color,
-            Clip,
-            FontSize,
-            FontWeight,
-            Height,
-            Left,
-            LetterSpacing,
-            LineHeight,
-            MarginBottom,
-            MarginLeft,
-            MarginRight,
-            MarginTop,
-            MaxHeight,
-            MaxWidth,
-            MinHeight,
-            MinWidth,
-            Opacity,
-            OutlineColor,
-            OutlineWidth,
-            PaddingBottom,
-            PaddingLeft,
-            PaddingRight,
-            PaddingTop,
-            Right,
-            TextIndent,
-            TextShadow,
-            Top,
-            Transform,
-            VerticalAlign,
-            Visibility,
-            Width,
-            WordSpacing,
-            ZIndex,
-        }
-
-        pub static ALL_TRANSITION_PROPERTIES: [TransitionProperty; 45] = [
-            TransitionProperty::BackgroundColor,
-            TransitionProperty::BackgroundPosition,
-            TransitionProperty::BorderBottomColor,
-            TransitionProperty::BorderBottomWidth,
-            TransitionProperty::BorderLeftColor,
-            TransitionProperty::BorderLeftWidth,
-            TransitionProperty::BorderRightColor,
-            TransitionProperty::BorderRightWidth,
-            TransitionProperty::BorderSpacing,
-            TransitionProperty::BorderTopColor,
-            TransitionProperty::BorderTopWidth,
-            TransitionProperty::Bottom,
-            TransitionProperty::Color,
-            TransitionProperty::Clip,
-            TransitionProperty::FontSize,
-            TransitionProperty::FontWeight,
-            TransitionProperty::Height,
-            TransitionProperty::Left,
-            TransitionProperty::LetterSpacing,
-            TransitionProperty::LineHeight,
-            TransitionProperty::MarginBottom,
-            TransitionProperty::MarginLeft,
-            TransitionProperty::MarginRight,
-            TransitionProperty::MarginTop,
-            TransitionProperty::MaxHeight,
-            TransitionProperty::MaxWidth,
-            TransitionProperty::MinHeight,
-            TransitionProperty::MinWidth,
-            TransitionProperty::Opacity,
-            TransitionProperty::OutlineColor,
-            TransitionProperty::OutlineWidth,
-            TransitionProperty::PaddingBottom,
-            TransitionProperty::PaddingLeft,
-            TransitionProperty::PaddingRight,
-            TransitionProperty::PaddingTop,
-            TransitionProperty::Right,
-            TransitionProperty::TextIndent,
-            TransitionProperty::TextShadow,
-            TransitionProperty::Top,
-            TransitionProperty::Transform,
-            TransitionProperty::VerticalAlign,
-            TransitionProperty::Visibility,
-            TransitionProperty::Width,
-            TransitionProperty::WordSpacing,
-            TransitionProperty::ZIndex,
-        ];
-
-        impl ToCss for TransitionProperty {
-            fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-                match *self {
-                    TransitionProperty::All => dest.write_str("all"),
-                    TransitionProperty::BackgroundColor => dest.write_str("background-color"),
-                    TransitionProperty::BackgroundPosition => dest.write_str("background-position"),
-                    TransitionProperty::BorderBottomColor => dest.write_str("border-bottom-color"),
-                    TransitionProperty::BorderBottomWidth => dest.write_str("border-bottom-width"),
-                    TransitionProperty::BorderLeftColor => dest.write_str("border-left-color"),
-                    TransitionProperty::BorderLeftWidth => dest.write_str("border-left-width"),
-                    TransitionProperty::BorderRightColor => dest.write_str("border-right-color"),
-                    TransitionProperty::BorderRightWidth => dest.write_str("border-right-width"),
-                    TransitionProperty::BorderSpacing => dest.write_str("border-spacing"),
-                    TransitionProperty::BorderTopColor => dest.write_str("border-top-color"),
-                    TransitionProperty::BorderTopWidth => dest.write_str("border-top-width"),
-                    TransitionProperty::Bottom => dest.write_str("bottom"),
-                    TransitionProperty::Color => dest.write_str("color"),
-                    TransitionProperty::Clip => dest.write_str("clip"),
-                    TransitionProperty::FontSize => dest.write_str("font-size"),
-                    TransitionProperty::FontWeight => dest.write_str("font-weight"),
-                    TransitionProperty::Height => dest.write_str("height"),
-                    TransitionProperty::Left => dest.write_str("left"),
-                    TransitionProperty::LetterSpacing => dest.write_str("letter-spacing"),
-                    TransitionProperty::LineHeight => dest.write_str("line-height"),
-                    TransitionProperty::MarginBottom => dest.write_str("margin-bottom"),
-                    TransitionProperty::MarginLeft => dest.write_str("margin-left"),
-                    TransitionProperty::MarginRight => dest.write_str("margin-right"),
-                    TransitionProperty::MarginTop => dest.write_str("margin-top"),
-                    TransitionProperty::MaxHeight => dest.write_str("max-height"),
-                    TransitionProperty::MaxWidth => dest.write_str("max-width"),
-                    TransitionProperty::MinHeight => dest.write_str("min-height"),
-                    TransitionProperty::MinWidth => dest.write_str("min-width"),
-                    TransitionProperty::Opacity => dest.write_str("opacity"),
-                    TransitionProperty::OutlineColor => dest.write_str("outline-color"),
-                    TransitionProperty::OutlineWidth => dest.write_str("outline-width"),
-                    TransitionProperty::PaddingBottom => dest.write_str("padding-bottom"),
-                    TransitionProperty::PaddingLeft => dest.write_str("padding-left"),
-                    TransitionProperty::PaddingRight => dest.write_str("padding-right"),
-                    TransitionProperty::PaddingTop => dest.write_str("padding-top"),
-                    TransitionProperty::Right => dest.write_str("right"),
-                    TransitionProperty::TextIndent => dest.write_str("text-indent"),
-                    TransitionProperty::TextShadow => dest.write_str("text-shadow"),
-                    TransitionProperty::Top => dest.write_str("top"),
-                    TransitionProperty::Transform => dest.write_str("transform"),
-                    TransitionProperty::VerticalAlign => dest.write_str("vertical-align"),
-                    TransitionProperty::Visibility => dest.write_str("visibility"),
-                    TransitionProperty::Width => dest.write_str("width"),
-                    TransitionProperty::WordSpacing => dest.write_str("word-spacing"),
-                    TransitionProperty::ZIndex => dest.write_str("z-index"),
-                }
-            }
-        }
+        // NB: Can't generate the type here because it needs all the longhands
+        // generated beforehand.
+        pub use properties::animated_properties::TransitionProperty;
+        pub use properties::animated_properties::TransitionProperty as SingleComputedValue;
 
         #[derive(Clone, Debug, PartialEq)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub struct T(pub Vec<SingleComputedValue>);
 
         impl ToCss for T {
             fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
                 if self.0.is_empty() {
@@ -716,131 +573,232 @@
         }
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T(Vec::new())
     }
 
-    pub fn parse_one(input: &mut Parser) -> Result<SingleSpecifiedValue,()> {
-        match_ignore_ascii_case! {
-            try!(input.expect_ident()),
-            "all" => Ok(TransitionProperty::All),
-            "background-color" => Ok(TransitionProperty::BackgroundColor),
-            "background-position" => Ok(TransitionProperty::BackgroundPosition),
-            "border-bottom-color" => Ok(TransitionProperty::BorderBottomColor),
-            "border-bottom-width" => Ok(TransitionProperty::BorderBottomWidth),
-            "border-left-color" => Ok(TransitionProperty::BorderLeftColor),
-            "border-left-width" => Ok(TransitionProperty::BorderLeftWidth),
-            "border-right-color" => Ok(TransitionProperty::BorderRightColor),
-            "border-right-width" => Ok(TransitionProperty::BorderRightWidth),
-            "border-spacing" => Ok(TransitionProperty::BorderSpacing),
-            "border-top-color" => Ok(TransitionProperty::BorderTopColor),
-            "border-top-width" => Ok(TransitionProperty::BorderTopWidth),
-            "bottom" => Ok(TransitionProperty::Bottom),
-            "color" => Ok(TransitionProperty::Color),
-            "clip" => Ok(TransitionProperty::Clip),
-            "font-size" => Ok(TransitionProperty::FontSize),
-            "font-weight" => Ok(TransitionProperty::FontWeight),
-            "height" => Ok(TransitionProperty::Height),
-            "left" => Ok(TransitionProperty::Left),
-            "letter-spacing" => Ok(TransitionProperty::LetterSpacing),
-            "line-height" => Ok(TransitionProperty::LineHeight),
-            "margin-bottom" => Ok(TransitionProperty::MarginBottom),
-            "margin-left" => Ok(TransitionProperty::MarginLeft),
-            "margin-right" => Ok(TransitionProperty::MarginRight),
-            "margin-top" => Ok(TransitionProperty::MarginTop),
-            "max-height" => Ok(TransitionProperty::MaxHeight),
-            "max-width" => Ok(TransitionProperty::MaxWidth),
-            "min-height" => Ok(TransitionProperty::MinHeight),
-            "min-width" => Ok(TransitionProperty::MinWidth),
-            "opacity" => Ok(TransitionProperty::Opacity),
-            "outline-color" => Ok(TransitionProperty::OutlineColor),
-            "outline-width" => Ok(TransitionProperty::OutlineWidth),
-            "padding-bottom" => Ok(TransitionProperty::PaddingBottom),
-            "padding-left" => Ok(TransitionProperty::PaddingLeft),
-            "padding-right" => Ok(TransitionProperty::PaddingRight),
-            "padding-top" => Ok(TransitionProperty::PaddingTop),
-            "right" => Ok(TransitionProperty::Right),
-            "text-indent" => Ok(TransitionProperty::TextIndent),
-            "text-shadow" => Ok(TransitionProperty::TextShadow),
-            "top" => Ok(TransitionProperty::Top),
-            "transform" => Ok(TransitionProperty::Transform),
-            "vertical-align" => Ok(TransitionProperty::VerticalAlign),
-            "visibility" => Ok(TransitionProperty::Visibility),
-            "width" => Ok(TransitionProperty::Width),
-            "word-spacing" => Ok(TransitionProperty::WordSpacing),
-            "z-index" => Ok(TransitionProperty::ZIndex),
-            _ => Err(())
-        }
-    }
-
     pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
-        Ok(SpecifiedValue(try!(input.parse_comma_separated(parse_one))))
+        Ok(SpecifiedValue(try!(input.parse_comma_separated(SingleSpecifiedValue::parse))))
     }
 
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
 
         #[inline]
         fn to_computed_value<Cx: TContext>(&self, _: &Cx) -> computed_value::T {
             (*self).clone()
         }
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="transition-delay">
+<%helpers:longhand name="transition-delay" animatable="False">
     pub use properties::longhands::transition_duration::{SingleSpecifiedValue, SpecifiedValue};
     pub use properties::longhands::transition_duration::{computed_value};
     pub use properties::longhands::transition_duration::{get_initial_single_value};
     pub use properties::longhands::transition_duration::{get_initial_value, parse, parse_one};
 </%helpers:longhand>
 
+<%helpers:longhand name="animation-name" animatable="False">
+    use values::computed::ComputedValueAsSpecified;
+
+    pub mod computed_value {
+        use cssparser::ToCss;
+        use std::fmt;
+        use string_cache::Atom;
+
+        #[derive(Debug, Clone, PartialEq, HeapSizeOf)]
+        pub struct T(pub Vec<Atom>);
+
+        impl ToCss for T {
+            fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+                if self.0.is_empty() {
+                    return dest.write_str("none")
+                }
+
+                for (i, name) in self.0.iter().enumerate() {
+                    if i != 0 {
+                        try!(dest.write_str(", "));
+                    }
+                    // NB: to_string() needed due to geckolib backend.
+                    try!(dest.write_str(&*name.to_string()));
+                }
+                Ok(())
+            }
+        }
+    }
+
+    pub use self::computed_value::T as SpecifiedValue;
+
+    #[inline]
+    pub fn get_initial_value() -> computed_value::T {
+        computed_value::T(vec![])
+    }
+
+    pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
+        use std::borrow::Cow;
+        Ok(SpecifiedValue(try!(input.parse_comma_separated(|input| {
+            input.expect_ident().map(Atom::from)
+        }))))
+    }
+
+    impl ComputedValueAsSpecified for SpecifiedValue {}
+</%helpers:longhand>
+
+<%helpers:longhand name="animation-duration" animatable="False">
+    pub use super::transition_duration::computed_value;
+    pub use super::transition_duration::{parse, get_initial_value};
+    pub use super::transition_duration::SpecifiedValue;
+</%helpers:longhand>
+
+<%helpers:longhand name="animation-timing-function" animatable="False">
+    pub use super::transition_timing_function::computed_value;
+    pub use super::transition_timing_function::{parse, get_initial_value};
+    pub use super::transition_timing_function::SpecifiedValue;
+</%helpers:longhand>
+
+<%helpers:longhand name="animation-iteration-count" animatable="False">
+    use values::computed::ComputedValueAsSpecified;
+
+    pub mod computed_value {
+        use cssparser::ToCss;
+        use std::fmt;
+
+        #[derive(Debug, Clone, PartialEq, HeapSizeOf)]
+        pub enum AnimationIterationCount {
+            Number(u32),
+            Infinite,
+        }
+
+        impl ToCss for AnimationIterationCount {
+            fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+                match *self {
+                    AnimationIterationCount::Number(n) => write!(dest, "{}", n),
+                    AnimationIterationCount::Infinite => dest.write_str("infinite"),
+                }
+            }
+        }
+
+        #[derive(Debug, Clone, PartialEq, HeapSizeOf)]
+        pub struct T(pub Vec<AnimationIterationCount>);
+
+        impl ToCss for T {
+            fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+                if self.0.is_empty() {
+                    return dest.write_str("none")
+                }
+                for (i, value) in self.0.iter().enumerate() {
+                    if i != 0 {
+                        try!(dest.write_str(", "))
+                    }
+                    try!(value.to_css(dest))
+                }
+                Ok(())
+            }
+        }
+    }
+
+    pub use self::computed_value::AnimationIterationCount;
+    pub use self::computed_value::T as SpecifiedValue;
+
+    pub fn parse_one(input: &mut Parser) -> Result<AnimationIterationCount, ()> {
+        if input.try(|input| input.expect_ident_matching("infinite")).is_ok() {
+            Ok(AnimationIterationCount::Infinite)
+        } else {
+            let number = try!(input.expect_integer());
+            if number < 0 {
+                return Err(());
+            }
+            Ok(AnimationIterationCount::Number(number as u32))
+        }
+    }
+
+
+    #[inline]
+    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+        Ok(SpecifiedValue(try!(input.parse_comma_separated(parse_one))))
+    }
+
+    pub fn get_initial_value() -> computed_value::T {
+        computed_value::T(vec![AnimationIterationCount::Number(1)])
+    }
+
+    impl ComputedValueAsSpecified for SpecifiedValue {}
+</%helpers:longhand>
+
+${helpers.keyword_list("animation-direction",
+                       "normal reverse alternate alternate-reverse",
+                       animatable=False)}
+
+${helpers.keyword_list("animation-play-state",
+                       "running paused",
+                       need_clone=True,
+                       animatable=False)}
+
+${helpers.keyword_list("animation-fill-mode",
+                       "none forwards backwards both",
+                       animatable=False)}
+
+<%helpers:longhand name="animation-delay" animatable="False">
+    pub use super::transition_duration::computed_value;
+    pub use super::transition_duration::{parse, get_initial_value};
+    pub use super::transition_duration::SpecifiedValue;
+</%helpers:longhand>
+
 // CSSOM View Module
 // https://www.w3.org/TR/cssom-view-1/
 ${helpers.single_keyword("scroll-behavior",
                          "auto smooth",
-                         products="gecko")}
+                         products="gecko",
+                         animatable=False)}
 
 // Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type-x
 ${helpers.single_keyword("scroll-snap-type-x",
                          "none mandatory proximity",
                          products="gecko",
-                         gecko_constant_prefix="NS_STYLE_SCROLL_SNAP_TYPE")}
+                         gecko_constant_prefix="NS_STYLE_SCROLL_SNAP_TYPE",
+                         animatable=False)}
 
 // Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type-y
 ${helpers.single_keyword("scroll-snap-type-y",
                          "none mandatory proximity",
                          products="gecko",
-                         gecko_constant_prefix="NS_STYLE_SCROLL_SNAP_TYPE")}
+                         gecko_constant_prefix="NS_STYLE_SCROLL_SNAP_TYPE",
+                         animatable=False)}
 
 // Compositing and Blending Level 1
 // http://www.w3.org/TR/compositing-1/
 ${helpers.single_keyword("isolation",
                          "auto isolate",
-                         products="gecko")}
+                         products="gecko",
+                         animatable=False)}
 
 ${helpers.single_keyword("page-break-after",
                          "auto always avoid left right",
-                         products="gecko")}
+                         products="gecko",
+                         animatable=False)}
 ${helpers.single_keyword("page-break-before",
                          "auto always avoid left right",
-                         products="gecko")}
+                         products="gecko",
+                         animatable=False)}
 ${helpers.single_keyword("page-break-inside",
                          "auto avoid",
                          products="gecko",
                          gecko_ffi_name="mBreakInside",
-                         gecko_constant_prefix="NS_STYLE_PAGE_BREAK")}
+                         gecko_constant_prefix="NS_STYLE_PAGE_BREAK",
+                         animatable=False)}
 
 // CSS Basic User Interface Module Level 3
 // http://dev.w3.org/csswg/css-ui/
 ${helpers.single_keyword("resize",
                          "none both horizontal vertical",
-                         products="gecko")}
+                         products="gecko",
+                         animatable=False)}
 
 // Non-standard
 ${helpers.single_keyword("-moz-appearance",
                          """none button button-arrow-down button-arrow-next button-arrow-previous button-arrow-up
                             button-bevel button-focus caret checkbox checkbox-container checkbox-label checkmenuitem
                             dualbutton groupbox listbox listitem menuarrow menubar menucheckbox menuimage menuitem
                             menuitemtext menulist menulist-button menulist-text menulist-textfield menupopup menuradio
                             menuseparator meterbar meterchunk progressbar progressbar-vertical progresschunk
@@ -857,20 +815,21 @@
                             -moz-win-communications-toolbox -moz-win-exclude-glass -moz-win-glass -moz-win-media-toolbox
                             -moz-window-button-box -moz-window-button-box-maximized -moz-window-button-close
                             -moz-window-button-maximize -moz-window-button-minimize -moz-window-button-restore
                             -moz-window-frame-bottom -moz-window-frame-left -moz-window-frame-right -moz-window-titlebar
                             -moz-window-titlebar-maximized
                          """,
                          gecko_ffi_name="mAppearance",
                          gecko_constant_prefix="NS_THEME",
-                         products="gecko")}
+                         products="gecko",
+                         animatable=False)}
 
 // Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-binding
-<%helpers:longhand name="-moz-binding" products="gecko">
+<%helpers:longhand name="-moz-binding" products="gecko" animatable="False">
     use cssparser::{CssStringWriter, ToCss};
     use gecko_bindings::ptr::{GeckoArcPrincipal, GeckoArcURI};
     use std::fmt::{self, Write};
     use url::Url;
     use values::computed::ComputedValueAsSpecified;
 
     #[derive(PartialEq, Clone, Debug)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
--- a/servo/components/style/properties/longhand/color.mako.rs
+++ b/servo/components/style/properties/longhand/color.mako.rs
@@ -1,17 +1,17 @@
 /* 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/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("Color", inherited=True) %>
 
-<%helpers:raw_longhand name="color" need_clone="True">
+<%helpers:raw_longhand name="color" need_clone="True" animatable="True">
     use cssparser::Color as CSSParserColor;
     use cssparser::RGBA;
     use values::specified::{CSSColor, CSSRGBA};
 
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
 
         #[inline]
--- a/servo/components/style/properties/longhand/column.mako.rs
+++ b/servo/components/style/properties/longhand/column.mako.rs
@@ -1,17 +1,18 @@
 /* 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/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("Column", inherited=False) %>
 
-<%helpers:longhand name="column-width" experimental="True">
+// FIXME: This prop should be animatable.
+<%helpers:longhand name="column-width" experimental="True" animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
 
     #[derive(Debug, Clone, Copy, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
         Auto,
@@ -65,17 +66,18 @@
         if input.try(|input| input.expect_ident_matching("auto")).is_ok() {
             Ok(SpecifiedValue::Auto)
         } else {
             specified::Length::parse_non_negative(input).map(SpecifiedValue::Specified)
         }
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="column-count" experimental="True">
+// FIXME: This prop should be animatable.
+<%helpers:longhand name="column-count" experimental="True" animatable="False">
     use cssparser::ToCss;
     use std::fmt;
 
     #[derive(Debug, Clone, Copy, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
         Auto,
         Specified(u32),
@@ -132,17 +134,18 @@
             if count <= 0 {
                 return Err(())
             }
             Ok(SpecifiedValue::Specified(count as u32))
         }
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="column-gap" experimental="True">
+// FIXME: This prop should be animatable.
+<%helpers:longhand name="column-gap" experimental="True" animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
 
     #[derive(Debug, Clone, Copy, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
         Normal,
--- a/servo/components/style/properties/longhand/counters.mako.rs
+++ b/servo/components/style/properties/longhand/counters.mako.rs
@@ -1,17 +1,17 @@
 /* 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/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("Counters", inherited=False, gecko_name="Content") %>
 
-<%helpers:longhand name="content">
+<%helpers:longhand name="content" animatable="False">
     use cssparser::Token;
     use std::ascii::AsciiExt;
     use values::computed::ComputedValueAsSpecified;
 
     use super::list_style_type;
 
     pub use self::computed_value::T as SpecifiedValue;
     pub use self::computed_value::ContentItem;
@@ -166,17 +166,17 @@
         if !content.is_empty() {
             Ok(SpecifiedValue::Content(content))
         } else {
             Err(())
         }
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="counter-increment">
+<%helpers:longhand name="counter-increment" animatable="False">
     use std::fmt;
     use super::content;
     use values::computed::ComputedValueAsSpecified;
 
     use cssparser::{ToCss, Token, serialize_identifier};
     use std::borrow::{Cow, ToOwned};
 
     pub use self::computed_value::T as SpecifiedValue;
@@ -236,16 +236,16 @@
         if !counters.is_empty() {
             Ok(SpecifiedValue(counters))
         } else {
             Err(())
         }
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="counter-reset">
+<%helpers:longhand name="counter-reset" animatable="False">
     pub use super::counter_increment::{SpecifiedValue, computed_value, get_initial_value};
     use super::counter_increment::{parse_common};
 
     pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
         parse_common(0, input)
     }
 </%helpers:longhand>
--- a/servo/components/style/properties/longhand/effects.mako.rs
+++ b/servo/components/style/properties/longhand/effects.mako.rs
@@ -4,19 +4,20 @@
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 // Box-shadow, etc.
 <% data.new_style_struct("Effects", inherited=False) %>
 
 ${helpers.predefined_type("opacity",
                           "Opacity",
-                          "1.0")}
+                          "1.0",
+                          animatable=True)}
 
-<%helpers:longhand name="box-shadow">
+<%helpers:longhand name="box-shadow" animatable="True">
     use cssparser::{self, ToCss};
     use std::fmt;
     use values::LocalToCss;
 
     #[derive(Debug, Clone, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct SpecifiedValue(Vec<SpecifiedBoxShadow>);
 
@@ -218,17 +219,18 @@
             blur_radius: lengths[2],
             spread_radius: lengths[3],
             color: color,
             inset: inset,
         })
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="clip">
+// FIXME: This prop should be animatable
+<%helpers:longhand name="clip" animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
 
     // NB: `top` and `left` are 0 if `auto` per CSS 2.1 11.1.2.
 
     pub mod computed_value {
         use app_units::Au;
@@ -389,17 +391,18 @@
                 right: right,
                 bottom: bottom,
                 left: left.unwrap_or(Length::Absolute(Au(0))),
             })))
         })
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="filter">
+// FIXME: This prop should be animatable
+<%helpers:longhand name="filter" animatable="False">
     //pub use self::computed_value::T as SpecifiedValue;
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
     use values::CSSFloat;
     use values::specified::{Angle, Length};
 
     #[derive(Debug, Clone, PartialEq)]
@@ -625,17 +628,17 @@
                     SpecifiedFilter::Saturate(factor) => computed_value::Filter::Saturate(factor),
                     SpecifiedFilter::Sepia(factor) => computed_value::Filter::Sepia(factor),
                 }
             }).collect() }
         }
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="transform">
+<%helpers:longhand name="transform" animatable="True">
     use app_units::Au;
     use values::CSSFloat;
 
     use cssparser::ToCss;
     use std::fmt;
 
     pub mod computed_value {
         use values::CSSFloat;
@@ -1169,23 +1172,30 @@ pub fn parse_origin(_: &ParserContext, i
             vertical: vertical,
             depth: depth,
         })
     } else {
         Err(())
     }
 }
 
-${helpers.single_keyword("backface-visibility", "visible hidden")}
-
-${helpers.single_keyword("transform-box", "border-box fill-box view-box", products="gecko")}
+${helpers.single_keyword("backface-visibility",
+                         "visible hidden",
+                         animatable=False)}
 
-${helpers.single_keyword("transform-style", "auto flat preserve-3d")}
+${helpers.single_keyword("transform-box",
+                         "border-box fill-box view-box",
+                         products="gecko",
+                         animatable=False)}
 
-<%helpers:longhand name="transform-origin">
+${helpers.single_keyword("transform-style",
+                         "auto flat preserve-3d",
+                         animatable=False)}
+
+<%helpers:longhand name="transform-origin" animatable="True">
     use app_units::Au;
     use values::LocalToCss;
     use values::specified::{Length, LengthOrPercentage, Percentage};
 
     use cssparser::ToCss;
     use std::fmt;
 
     pub mod computed_value {
@@ -1256,20 +1266,22 @@ pub fn parse_origin(_: &ParserContext, i
                 vertical: self.vertical.to_computed_value(context),
                 depth: self.depth.to_computed_value(context),
             }
         }
     }
 </%helpers:longhand>
 
 ${helpers.predefined_type("perspective",
-                  "LengthOrNone",
-                  "computed::LengthOrNone::None")}
+                          "LengthOrNone",
+                          "computed::LengthOrNone::None",
+                          animatable=True)}
 
-<%helpers:longhand name="perspective-origin">
+// FIXME: This prop should be animatable
+<%helpers:longhand name="perspective-origin" animatable="False">
     use values::specified::{LengthOrPercentage, Percentage};
 
     use cssparser::ToCss;
     use std::fmt;
 
     pub mod computed_value {
         use values::computed::LengthOrPercentage;
 
@@ -1332,11 +1344,12 @@ pub fn parse_origin(_: &ParserContext, i
                 horizontal: self.horizontal.to_computed_value(context),
                 vertical: self.vertical.to_computed_value(context),
             }
         }
     }
 </%helpers:longhand>
 
 ${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")}
+                         """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",
+                         animatable=False)}
--- a/servo/components/style/properties/longhand/font.mako.rs
+++ b/servo/components/style/properties/longhand/font.mako.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 <% from data import Method %>
 
 <% data.new_style_struct("Font",
                          inherited=True,
                          additional_methods=[Method("compute_font_hash", is_mut=True)]) %>
-<%helpers:longhand name="font-family">
+<%helpers:longhand name="font-family" animatable="False">
     use self::computed_value::FontFamily;
     use values::computed::ComputedValueAsSpecified;
     pub use self::computed_value::T as SpecifiedValue;
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
     pub mod computed_value {
         use cssparser::ToCss;
         use std::fmt;
@@ -112,20 +112,25 @@
             value.push_str(" ");
             value.push_str(&ident);
         }
         Ok(FontFamily::FamilyName(Atom::from(value)))
     }
 </%helpers:longhand>
 
 
-${helpers.single_keyword("font-style", "normal italic oblique", gecko_constant_prefix="NS_FONT_STYLE")}
-${helpers.single_keyword("font-variant", "normal small-caps")}
+${helpers.single_keyword("font-style",
+                         "normal italic oblique",
+                         gecko_constant_prefix="NS_FONT_STYLE",
+                         animatable=False)}
+${helpers.single_keyword("font-variant",
+                         "normal small-caps",
+                         animatable=False)}
 
-<%helpers:longhand name="font-weight" need_clone="True">
+<%helpers:longhand name="font-weight" need_clone="True" animatable="True">
     use cssparser::ToCss;
     use std::fmt;
 
     #[derive(Debug, Clone, PartialEq, Eq, Copy)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
         Bolder,
         Lighter,
@@ -236,17 +241,17 @@
                     computed_value::T::Weight800 => computed_value::T::Weight700,
                     computed_value::T::Weight900 => computed_value::T::Weight700,
                 },
             }
         }
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="font-size" need_clone="True">
+<%helpers:longhand name="font-size" need_clone="True" animatable="True">
     use app_units::Au;
     use cssparser::ToCss;
     use std::fmt;
     use values::FONT_MEDIUM_PX;
     use values::specified::{LengthOrPercentage, Length, Percentage};
 
     impl ToCss for SpecifiedValue {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
@@ -302,13 +307,19 @@
             specified::Length::from_str(&ident as &str)
                 .ok_or(())
                 .map(specified::LengthOrPercentage::Length)
         })
         .map(SpecifiedValue)
     }
 </%helpers:longhand>
 
+// FIXME: This prop should be animatable
 ${helpers.single_keyword("font-stretch",
-                 "normal ultra-condensed extra-condensed condensed semi-condensed semi-expanded \
-                 expanded extra-expanded ultra-expanded")}
+                         "normal ultra-condensed extra-condensed condensed \
+                          semi-condensed semi-expanded expanded extra-expanded \
+                          ultra-expanded",
+                         animatable=False)}
 
-${helpers.single_keyword("font-kerning", "auto none normal", products="gecko")}
+${helpers.single_keyword("font-kerning",
+                         "auto none normal",
+                         products="gecko",
+                         animatable=False)}
--- a/servo/components/style/properties/longhand/inherited_box.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_box.mako.rs
@@ -1,46 +1,51 @@
 /* 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/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("InheritedBox", inherited=True, gecko_name="Visibility") %>
 
-${helpers.single_keyword("direction", "ltr rtl", need_clone=True)}
+${helpers.single_keyword("direction", "ltr rtl", need_clone=True, animatable=False)}
 
 // TODO: collapse. Well, do tables first.
 ${helpers.single_keyword("visibility",
                          "visible hidden",
                          extra_gecko_values="collapse",
-                         gecko_ffi_name="mVisible")}
+                         gecko_ffi_name="mVisible",
+                         animatable=True)}
 
 // CSS Writing Modes Level 3
 // http://dev.w3.org/csswg/css-writing-modes/
 ${helpers.single_keyword("writing-mode",
                          "horizontal-tb vertical-rl vertical-lr",
                          experimental=True,
-                         need_clone=True)}
+                         need_clone=True,
+                         animatable=False)}
 
 // FIXME(SimonSapin): Add 'mixed' and 'upright' (needs vertical text support)
 // FIXME(SimonSapin): initial (first) value should be 'mixed', when that's implemented
 // FIXME(bholley): sideways-right is needed as an alias to sideways in gecko.
 ${helpers.single_keyword("text-orientation",
                          "sideways",
                          experimental=True,
                          need_clone=True,
                          extra_gecko_values="mixed upright",
-                         extra_servo_values="sideways-right sideways-left")}
+                         extra_servo_values="sideways-right sideways-left",
+                         animatable=False)}
 
 // CSS Color Module Level 4
 // https://drafts.csswg.org/css-color/
-${helpers.single_keyword("color-adjust", "economy exact", products="gecko")}
+${helpers.single_keyword("color-adjust",
+                         "economy exact", products="gecko",
+                         animatable=False)}
 
-<%helpers:longhand name="image-rendering">
+<%helpers:longhand name="image-rendering" animatable="False">
     pub mod computed_value {
         use cssparser::ToCss;
         use std::fmt;
 
         #[derive(Copy, Clone, Debug, PartialEq)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
         pub enum T {
             Auto,
@@ -87,17 +92,20 @@
         fn to_computed_value<Cx: TContext>(&self, _: &Cx) -> computed_value::T {
             *self
         }
     }
 </%helpers:longhand>
 
 // Used in the bottom-up flow construction traversal to avoid constructing flows for
 // descendants of nodes with `display: none`.
-<%helpers:longhand name="-servo-under-display-none" derived_from="display" products="servo">
+<%helpers:longhand name="-servo-under-display-none"
+                   derived_from="display"
+                   products="servo"
+                   animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::computed::ComputedValueAsSpecified;
 
     #[derive(Copy, Clone, Debug, Eq, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
     pub struct SpecifiedValue(pub bool);
 
--- a/servo/components/style/properties/longhand/inherited_svg.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_svg.mako.rs
@@ -5,41 +5,57 @@
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 // SVG 1.1 (Second Edition)
 // https://www.w3.org/TR/SVG/
 <% data.new_style_struct("InheritedSVG",
                          inherited=True,
                          gecko_name="SVG") %>
 
+// TODO(emilio): Should some of these types be animatable?
+
 // Section 10 - Text
 
-${helpers.single_keyword("text-anchor", "start middle end", products="gecko")}
+${helpers.single_keyword("text-anchor",
+                         "start middle end",
+                         products="gecko",
+                         animatable=False)}
 
 // Section 11 - Painting: Filling, Stroking and Marker Symbols
-${helpers.single_keyword("color-interpolation", "auto sRGB linearRGB", products="gecko")}
-
-${helpers.single_keyword("color-interpolation-filters",
+${helpers.single_keyword("color-interpolation",
                          "auto sRGB linearRGB",
                          products="gecko",
-                         gecko_constant_prefix="NS_STYLE_COLOR_INTERPOLATION")}
+                         animatable=False)}
 
-${helpers.predefined_type("fill-opacity", "Opacity", "1.0", products="gecko")}
+${helpers.single_keyword("color-interpolation-filters", "auto sRGB linearRGB",
+                         products="gecko",
+                         gecko_constant_prefix="NS_STYLE_COLOR_INTERPOLATION",
+                         animatable=False)}
 
-${helpers.single_keyword("fill-rule", "nonzero evenodd", products="gecko")}
+${helpers.predefined_type("fill-opacity", "Opacity", "1.0",
+                          products="gecko", animatable=False)}
+
+${helpers.single_keyword("fill-rule", "nonzero evenodd",
+                         products="gecko", animatable=False)}
 
 ${helpers.single_keyword("shape-rendering",
                          "auto optimizeSpeed crispEdges geometricPrecision",
-                         products="gecko")}
+                         products="gecko",
+                         animatable=False)}
 
-${helpers.single_keyword("stroke-linecap", "butt round square", products="gecko")}
+${helpers.single_keyword("stroke-linecap", "butt round square",
+                         products="gecko", animatable=False)}
 
-${helpers.single_keyword("stroke-linejoin", "miter round bevel", products="gecko")}
+${helpers.single_keyword("stroke-linejoin", "miter round bevel",
+                         products="gecko", animatable=False)}
 
-${helpers.predefined_type("stroke-miterlimit", "Number", "4.0", "parse_at_least_one",
-                          products="gecko")}
+${helpers.predefined_type("stroke-miterlimit", "Number", "4.0",
+                          "parse_at_least_one", products="gecko",
+                          animatable=False)}
 
-${helpers.predefined_type("stroke-opacity", "Opacity", "1.0", products="gecko")}
+${helpers.predefined_type("stroke-opacity", "Opacity", "1.0",
+                          products="gecko", animatable=False)}
 
 // Section 14 - Clipping, Masking and Compositing
 ${helpers.single_keyword("clip-rule", "nonzero evenodd",
                          products="gecko",
-                         gecko_constant_prefix="NS_STYLE_FILL_RULE")}
+                         gecko_constant_prefix="NS_STYLE_FILL_RULE",
+                         animatable=False)}
--- a/servo/components/style/properties/longhand/inherited_table.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_table.mako.rs
@@ -1,21 +1,27 @@
 /* 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/. */
 
  <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("InheritedTable", inherited=True, gecko_name="TableBorder") %>
 
-${helpers.single_keyword("border-collapse", "separate collapse", gecko_constant_prefix="NS_STYLE_BORDER")}
-${helpers.single_keyword("empty-cells", "show hide", gecko_constant_prefix="NS_STYLE_TABLE_EMPTY_CELLS")}
-${helpers.single_keyword("caption-side", "top bottom", extra_gecko_values="right left top-outside bottom-outside")}
+${helpers.single_keyword("border-collapse", "separate collapse",
+                         gecko_constant_prefix="NS_STYLE_BORDER",
+                         animatable=False)}
+${helpers.single_keyword("empty-cells", "show hide",
+                         gecko_constant_prefix="NS_STYLE_TABLE_EMPTY_CELLS",
+                         animatable=False)}
+${helpers.single_keyword("caption-side", "top bottom",
+                         extra_gecko_values="right left top-outside bottom-outside",
+                         animatable=False)}
 
-<%helpers:longhand name="border-spacing">
+<%helpers:longhand name="border-spacing" animatable="False">
     use app_units::Au;
     use values::LocalToCss;
 
     use cssparser::ToCss;
     use std::fmt;
 
     pub mod computed_value {
         use app_units::Au;
--- a/servo/components/style/properties/longhand/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_text.mako.rs
@@ -1,17 +1,17 @@
 /* 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/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("InheritedText", inherited=True, gecko_name="Text") %>
 
-<%helpers:longhand name="line-height">
+<%helpers:longhand name="line-height" animatable="True">
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
     use values::CSSFloat;
 
     #[derive(Debug, Clone, PartialEq, Copy)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
@@ -115,17 +115,17 @@
                         }
                     }
                 }
             }
         }
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="text-align">
+<%helpers:longhand name="text-align" animatable="False">
     pub use self::computed_value::T as SpecifiedValue;
     use values::computed::ComputedValueAsSpecified;
     impl ComputedValueAsSpecified for SpecifiedValue {}
     pub mod computed_value {
         macro_rules! define_text_align {
             ( $( $name: ident ( $string: expr ) => $discriminant: expr, )+ ) => {
                 define_css_keyword_enum! { T:
                     $(
@@ -174,17 +174,18 @@
         computed_value::T::start
     }
     pub fn parse(_context: &ParserContext, input: &mut Parser)
                  -> Result<SpecifiedValue, ()> {
         computed_value::T::parse(input)
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="letter-spacing">
+// FIXME: This prop should be animatable.
+<%helpers:longhand name="letter-spacing" animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
 
     #[derive(Debug, Clone, Copy, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
         Normal,
@@ -238,17 +239,17 @@
         if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
             Ok(SpecifiedValue::Normal)
         } else {
             specified::Length::parse_non_negative(input).map(SpecifiedValue::Specified)
         }
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="word-spacing">
+<%helpers:longhand name="word-spacing" animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
 
     #[derive(Debug, Clone, Copy, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
         Normal,
@@ -304,37 +305,43 @@
         } else {
             specified::Length::parse_non_negative(input).map(SpecifiedValue::Specified)
         }
     }
 </%helpers:longhand>
 
 ${helpers.predefined_type("text-indent",
                           "LengthOrPercentage",
-                          "computed::LengthOrPercentage::Length(Au(0))")}
+                          "computed::LengthOrPercentage::Length(Au(0))",
+                          animatable=True)}
 
 // Also known as "word-wrap" (which is more popular because of IE), but this is the preferred
 // name per CSS-TEXT 6.2.
 ${helpers.single_keyword("overflow-wrap",
                          "normal break-word",
-                         gecko_constant_prefix="NS_STYLE_OVERFLOWWRAP")}
+                         gecko_constant_prefix="NS_STYLE_OVERFLOWWRAP",
+                         animatable=False)}
 
 // TODO(pcwalton): Support `word-break: keep-all` once we have better CJK support.
 ${helpers.single_keyword("word-break",
                          "normal break-all",
                          extra_gecko_values="keep-all",
-                         gecko_constant_prefix="NS_STYLE_WORDBREAK")}
+                         gecko_constant_prefix="NS_STYLE_WORDBREAK",
+                         animatable=False)}
 
 // TODO(pcwalton): Support `text-justify: distribute`.
 ${helpers.single_keyword("text-justify",
                          "auto none inter-word",
-                         products="servo")}
+                         products="servo",
+                         animatable=False)}
 
 <%helpers:longhand name="-servo-text-decorations-in-effect"
-                derived_from="display text-decoration" need_clone="True" products="servo">
+                   derived_from="display text-decoration"
+                   need_clone="True" products="servo"
+                   animatable="False">
     use cssparser::{RGBA, ToCss};
     use std::fmt;
 
     use values::computed::ComputedValueAsSpecified;
     use properties::style_struct_traits::{Box, Color, Text};
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
 
@@ -405,18 +412,20 @@
 
     #[inline]
     pub fn derive_from_display<Cx: TContext>(context: &mut Cx) {
         let derived = derive(context);
         context.mutate_style().mutate_inheritedtext().set__servo_text_decorations_in_effect(derived);
     }
 </%helpers:longhand>
 
-<%helpers:single_keyword_computed name="white-space" values="normal pre nowrap pre-wrap pre-line",
-                                  gecko_constant_prefix="NS_STYLE_WHITESPACE">
+<%helpers:single_keyword_computed name="white-space"
+                                  values="normal pre nowrap pre-wrap pre-line"
+                                  gecko_constant_prefix="NS_STYLE_WHITESPACE"
+                                  animatable="False">
     use values::computed::ComputedValueAsSpecified;
     impl ComputedValueAsSpecified for SpecifiedValue {}
 
     impl SpecifiedValue {
         pub fn allow_wrap(&self) -> bool {
             match *self {
                 SpecifiedValue::nowrap |
                 SpecifiedValue::pre => false,
@@ -443,17 +452,17 @@
                 SpecifiedValue::pre_line => false,
                 SpecifiedValue::pre |
                 SpecifiedValue::pre_wrap => true,
             }
         }
     }
 </%helpers:single_keyword_computed>
 
-<%helpers:longhand name="text-shadow">
+<%helpers:longhand name="text-shadow" animatable="True">
     use cssparser::{self, ToCss};
     use std::fmt;
     use values::LocalToCss;
 
     #[derive(Clone, PartialEq, Debug)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct SpecifiedValue(Vec<SpecifiedTextShadow>);
 
@@ -630,21 +639,27 @@
     }
 </%helpers:longhand>
 
 
 
 // TODO(pcwalton): `full-width`
 ${helpers.single_keyword("text-transform",
                          "none capitalize uppercase lowercase",
-                         extra_gecko_values="full-width")}
+                         extra_gecko_values="full-width",
+                         animatable=False)}
 
-${helpers.single_keyword("text-rendering", "auto optimizespeed optimizelegibility geometricprecision")}
+${helpers.single_keyword("text-rendering",
+                         "auto optimizespeed optimizelegibility geometricprecision",
+                         animatable=False)}
 
 // CSS Text Module Level 3
 // https://www.w3.org/TR/css-text-3/
-${helpers.single_keyword("hyphens", "none manual auto", products="gecko")}
+${helpers.single_keyword("hyphens", "none manual auto",
+                         products="gecko", animatable=False)}
 
 // CSS Ruby Layout Module Level 1
 // https://www.w3.org/TR/css-ruby-1/
-${helpers.single_keyword("ruby-align", "start center space-between space-around", products="gecko")}
+${helpers.single_keyword("ruby-align", "start center space-between space-around",
+                         products="gecko", animatable=False)}
 
-${helpers.single_keyword("ruby-position", "over under", products="gecko")}
+${helpers.single_keyword("ruby-position", "over under",
+                         products="gecko", animatable=False)}
--- a/servo/components/style/properties/longhand/list.mako.rs
+++ b/servo/components/style/properties/longhand/list.mako.rs
@@ -1,36 +1,37 @@
 /* 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/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("List", inherited=True) %>
 
-${helpers.single_keyword("list-style-position", "outside inside")}
+${helpers.single_keyword("list-style-position", "outside inside", animatable=False)}
 
 // TODO(pcwalton): Implement the full set of counter styles per CSS-COUNTER-STYLES [1] 6.1:
 //
 //     decimal-leading-zero, armenian, upper-armenian, lower-armenian, georgian, lower-roman,
 //     upper-roman
 //
 // TODO(bholley): Missing quite a few gecko properties here as well.
 //
 // [1]: http://dev.w3.org/csswg/css-counter-styles/
 ${helpers.single_keyword("list-style-type", """
     disc none circle square decimal lower-alpha upper-alpha  disclosure-open disclosure-closed
 """, extra_servo_values="""arabic-indic bengali cambodian cjk-decimal devanagari
                            gujarati gurmukhi kannada khmer lao malayalam mongolian
                            myanmar oriya persian telugu thai tibetan cjk-earthly-branch
                            cjk-heavenly-stem lower-greek hiragana hiragana-iroha katakana
                            katakana-iroha""",
-    gecko_constant_prefix="NS_STYLE_LIST_STYLE")}
+    gecko_constant_prefix="NS_STYLE_LIST_STYLE",
+    animatable=False)}
 
-<%helpers:longhand name="list-style-image">
+<%helpers:longhand name="list-style-image" animatable="False">
     use cssparser::{ToCss, Token};
     use std::fmt;
     use url::Url;
     use values::LocalToCss;
 
     #[derive(Debug, Clone, PartialEq, Eq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
@@ -87,17 +88,17 @@
         }
     }
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T(None)
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="quotes">
+<%helpers:longhand name="quotes" animatable="False">
     use std::borrow::Cow;
     use std::fmt;
     use values::computed::ComputedValueAsSpecified;
 
     use cssparser::{ToCss, Token};
 
     pub use self::computed_value::T as SpecifiedValue;
 
--- a/servo/components/style/properties/longhand/margin.mako.rs
+++ b/servo/components/style/properties/longhand/margin.mako.rs
@@ -3,10 +3,11 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("Margin", inherited=False) %>
 
 % for side in ["top", "right", "bottom", "left"]:
     ${helpers.predefined_type("margin-" + side, "LengthOrPercentageOrAuto",
-                              "computed::LengthOrPercentageOrAuto::Length(Au(0))")}
+                              "computed::LengthOrPercentageOrAuto::Length(Au(0))",
+                              animatable=True)}
 % endfor
--- a/servo/components/style/properties/longhand/outline.mako.rs
+++ b/servo/components/style/properties/longhand/outline.mako.rs
@@ -5,33 +5,34 @@
 <%namespace name="helpers" file="/helpers.mako.rs" />
 <% from data import Method %>
 
 <% data.new_style_struct("Outline",
                          inherited=False,
                          additional_methods=[Method("outline_has_nonzero_width", "bool")]) %>
 
 // TODO(pcwalton): `invert`
-${helpers.predefined_type("outline-color", "CSSColor", "::cssparser::Color::CurrentColor")}
+${helpers.predefined_type("outline-color", "CSSColor", "::cssparser::Color::CurrentColor",
+                          animatable=True)}
 
-<%helpers:longhand name="outline-style" need_clone="True">
+<%helpers:longhand name="outline-style" need_clone="True" animatable="False">
     pub use values::specified::BorderStyle as SpecifiedValue;
     pub fn get_initial_value() -> SpecifiedValue { SpecifiedValue::none }
     pub mod computed_value {
         pub use values::specified::BorderStyle as T;
     }
     pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
         match SpecifiedValue::parse(input) {
             Ok(SpecifiedValue::hidden) => Err(()),
             result => result
         }
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="outline-width">
+<%helpers:longhand name="outline-width" animatable="True">
     use app_units::Au;
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
 
     impl ToCss for SpecifiedValue {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             self.0.to_css(dest)
@@ -55,15 +56,17 @@
         #[inline]
         fn to_computed_value<Cx: TContext>(&self, context: &Cx) -> computed_value::T {
             self.0.to_computed_value(context)
         }
     }
 </%helpers:longhand>
 
 // The -moz-outline-radius-* properties are non-standard and not on a standards track.
+// TODO: Should they animate?
 % for corner in ["topleft", "topright", "bottomright", "bottomleft"]:
     ${helpers.predefined_type("-moz-outline-radius-" + corner, "BorderRadiusSize",
                               "computed::BorderRadiusSize::zero()",
-                              "parse", products="gecko")}
+                              "parse", products="gecko",
+                              animatable=False)}
 % endfor
 
-${helpers.predefined_type("outline-offset", "Length", "Au(0)")}
+${helpers.predefined_type("outline-offset", "Length", "Au(0)", animatable=True)}
--- a/servo/components/style/properties/longhand/padding.mako.rs
+++ b/servo/components/style/properties/longhand/padding.mako.rs
@@ -4,10 +4,11 @@
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("Padding", inherited=False) %>
 
 % for side in ["top", "right", "bottom", "left"]:
     ${helpers.predefined_type("padding-" + side, "LengthOrPercentage",
                                "computed::LengthOrPercentage::Length(Au(0))",
-                               "parse_non_negative")}
+                               "parse_non_negative",
+                               animatable=True)}
 % endfor
--- a/servo/components/style/properties/longhand/pointing.mako.rs
+++ b/servo/components/style/properties/longhand/pointing.mako.rs
@@ -1,17 +1,17 @@
 /* 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/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("Pointing", inherited=True, gecko_name="UserInterface") %>
 
-<%helpers:longhand name="cursor">
+<%helpers:longhand name="cursor" animatable="False">
     pub use self::computed_value::T as SpecifiedValue;
     use values::computed::ComputedValueAsSpecified;
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
 
     pub mod computed_value {
         use cssparser::ToCss;
         use std::fmt;
@@ -49,21 +49,25 @@
             .map(SpecifiedValue::SpecifiedCursor)
         }
     }
 </%helpers:longhand>
 
 // NB: `pointer-events: auto` (and use of `pointer-events` in anything that isn't SVG, in fact)
 // is nonstandard, slated for CSS4-UI.
 // TODO(pcwalton): SVG-only values.
-${helpers.single_keyword("pointer-events", "auto none")}
+${helpers.single_keyword("pointer-events", "auto none", animatable=False)}
 
-${helpers.single_keyword("-moz-user-input", "none enabled disabled", products="gecko",
-                         gecko_ffi_name="mUserInput", gecko_constant_prefix="NS_STYLE_USER_INPUT")}
+${helpers.single_keyword("-moz-user-input", "none enabled disabled",
+                         products="gecko", gecko_ffi_name="mUserInput",
+                         gecko_constant_prefix="NS_STYLE_USER_INPUT",
+                         animatable=False)}
 
-${helpers.single_keyword("-moz-user-modify", "read-only read-write write-only", products="gecko",
-                         gecko_ffi_name="mUserModify", gecko_constant_prefix="NS_STYLE_USER_MODIFY")}
+${helpers.single_keyword("-moz-user-modify", "read-only read-write write-only",
+                         products="gecko", gecko_ffi_name="mUserModify",
+                         gecko_constant_prefix="NS_STYLE_USER_MODIFY",
+                         animatable=False)}
 
 ${helpers.single_keyword("-moz-user-focus",
                          "ignore normal select-after select-before select-menu select-same select-all none",
-                         products="gecko",
-                         gecko_ffi_name="mUserFocus",
-                         gecko_constant_prefix="NS_STYLE_USER_FOCUS")}
+                         products="gecko", gecko_ffi_name="mUserFocus",
+                         gecko_constant_prefix="NS_STYLE_USER_FOCUS",
+                         animatable=False)}
--- a/servo/components/style/properties/longhand/position.mako.rs
+++ b/servo/components/style/properties/longhand/position.mako.rs
@@ -3,20 +3,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("Position", inherited=False) %>
 
 % for side in ["top", "right", "bottom", "left"]:
     ${helpers.predefined_type(side, "LengthOrPercentageOrAuto",
-                              "computed::LengthOrPercentageOrAuto::Auto")}
+                              "computed::LengthOrPercentageOrAuto::Auto",
+                              animatable=True)}
 % endfor
 
-<%helpers:longhand name="z-index">
+<%helpers:longhand name="z-index" animatable="True">
     use values::computed::ComputedValueAsSpecified;
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
     pub type SpecifiedValue = computed_value::T;
     pub mod computed_value {
         use cssparser::ToCss;
         use std::fmt;
 
@@ -57,49 +58,59 @@
         }
     }
 </%helpers:longhand>
 
 // CSS Flexible Box Layout Module Level 1
 // http://www.w3.org/TR/css3-flexbox/
 
 // Flex container properties
-${helpers.single_keyword("flex-direction", "row row-reverse column column-reverse", experimental=True)}
+${helpers.single_keyword("flex-direction", "row row-reverse column column-reverse",
+                         experimental=True, animatable=False)}
 
-${helpers.single_keyword("flex-wrap", "nowrap wrap wrap-reverse", experimental=True)}
+${helpers.single_keyword("flex-wrap", "nowrap wrap wrap-reverse",
+                         experimental=True, animatable=False)}
 
 // FIXME(stshine): The type of 'justify-content' and 'align-content' is uint16_t in gecko
 // FIXME(stshine): Its higher bytes are used to store fallback value. Disable them in geckolib for now
 ${helpers.single_keyword("justify-content", "flex-start flex-end center space-between space-around",
                          experimental=True,
                          gecko_constant_prefix="NS_STYLE_JUSTIFY",
-                         products="servo")}
+                         products="servo",
+                         animatable=False)}
 
 ${helpers.single_keyword("align-items", "stretch flex-start flex-end center baseline",
                          experimental=True,
                          need_clone=True,
-                         gecko_constant_prefix="NS_STYLE_ALIGN")}
+                         gecko_constant_prefix="NS_STYLE_ALIGN",
+                         animatable=False)}
 
 ${helpers.single_keyword("align-content", "stretch flex-start flex-end center space-between space-around",
                          experimental=True,
                          gecko_constant_prefix="NS_STYLE_ALIGN",
-                         products="servo")}
+                         products="servo",
+                         animatable=False)}
 
 // Flex item properties
-${helpers.predefined_type("flex-grow", "Number", "0.0", "parse_non_negative", experimental=True)}
+${helpers.predefined_type("flex-grow", "Number",
+                          "0.0", "parse_non_negative",
+                          experimental=True, animatable=True)}
 
-${helpers.predefined_type("flex-shrink", "Number", "1.0", "parse_non_negative", experimental=True)}
+${helpers.predefined_type("flex-shrink", "Number",
+                          "1.0", "parse_non_negative",
+                          experimental=True, animatable=True)}
 
 ${helpers.single_keyword("align-self", "auto stretch flex-start flex-end center baseline",
                          experimental=True,
                          need_clone=True,
-                         gecko_constant_prefix="NS_STYLE_ALIGN")}
+                         gecko_constant_prefix="NS_STYLE_ALIGN",
+                         animatable=False)}
 
 // https://drafts.csswg.org/css-flexbox/#propdef-order
-<%helpers:longhand name="order">
+<%helpers:longhand name="order" animatable="True">
     use values::computed::ComputedValueAsSpecified;
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
 
     pub type SpecifiedValue = computed_value::T;
 
     pub mod computed_value {
         pub type T = i32;
@@ -110,46 +121,58 @@
         0
     }
 
     fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
         specified::parse_integer(input)
     }
 </%helpers:longhand>
 
+// FIXME: This property should be animatable.
 ${helpers.predefined_type("flex-basis",
                           "LengthOrPercentageOrAutoOrContent",
-                          "computed::LengthOrPercentageOrAutoOrContent::Auto")}
+                          "computed::LengthOrPercentageOrAutoOrContent::Auto",
+                          animatable=False)}
 
 ${helpers.predefined_type("width",
                           "LengthOrPercentageOrAuto",
                           "computed::LengthOrPercentageOrAuto::Auto",
-                          "parse_non_negative")}
+                          "parse_non_negative",
+                          animatable=True)}
 
 ${helpers.predefined_type("height",
                           "LengthOrPercentageOrAuto",
                           "computed::LengthOrPercentageOrAuto::Auto",
-                          "parse_non_negative")}
+                          "parse_non_negative",
+                          animatable=True)}
 
 ${helpers.predefined_type("min-width",
                           "LengthOrPercentage",
                           "computed::LengthOrPercentage::Length(Au(0))",
-                          "parse_non_negative")}
+                          "parse_non_negative",
+                          animatable=True)}
+
 ${helpers.predefined_type("max-width",
                           "LengthOrPercentageOrNone",
                           "computed::LengthOrPercentageOrNone::None",
-                          "parse_non_negative")}
+                          "parse_non_negative",
+                          animatable=True)}
 
 ${helpers.predefined_type("min-height",
                           "LengthOrPercentage",
                           "computed::LengthOrPercentage::Length(Au(0))",
-                          "parse_non_negative")}
+                          "parse_non_negative",
+                          animatable=True)}
+
 ${helpers.predefined_type("max-height",
                           "LengthOrPercentageOrNone",
                           "computed::LengthOrPercentageOrNone::None",
-                          "parse_non_negative")}
+                          "parse_non_negative",
+                          animatable=True)}
 
 ${helpers.single_keyword("box-sizing",
-                         "content-box border-box")}
+                         "content-box border-box",
+                         animatable=False)}
 
 // CSS Image Values and Replaced Content Module Level 3
 // https://drafts.csswg.org/css-images-3/
-${helpers.single_keyword("object-fit", "fill contain cover none scale-down", products="gecko")}
+${helpers.single_keyword("object-fit", "fill contain cover none scale-down",
+                         products="gecko", animatable=False)}
--- a/servo/components/style/properties/longhand/svg.mako.rs
+++ b/servo/components/style/properties/longhand/svg.mako.rs
@@ -1,41 +1,51 @@
 /* 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/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("SVG", inherited=False, gecko_name="SVGReset") %>
 
+// TODO: Which of these should be animatable properties?
 ${helpers.single_keyword("dominant-baseline",
                  """auto use-script no-change reset-size ideographic alphabetic hanging
                     mathematical central middle text-after-edge text-before-edge""",
-                 products="gecko")}
+                 products="gecko",
+                 animatable=False)}
 
-${helpers.single_keyword("vector-effect", "none non-scaling-stroke", products="gecko")}
+${helpers.single_keyword("vector-effect", "none non-scaling-stroke",
+                         products="gecko", animatable=False)}
 
 // Section 13 - Gradients and Patterns
 
 ${helpers.predefined_type(
     "stop-color", "CSSColor",
     "CSSParserColor::RGBA(RGBA { red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0 })",
-    products="gecko")}
+    products="gecko",
+    animatable=False)}
 
-${helpers.predefined_type("stop-opacity", "Opacity", "1.0", products="gecko")}
+${helpers.predefined_type("stop-opacity", "Opacity", "1.0",
+                          products="gecko",
+                          animatable=False)}
 
 // Section 15 - Filter Effects
 
 ${helpers.predefined_type(
     "flood-color", "CSSColor",
     "CSSParserColor::RGBA(RGBA { red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0 })",
-    products="gecko")}
+    products="gecko",
+    animatable=False)}
 
-${helpers.predefined_type("flood-opacity", "Opacity", "1.0", products="gecko")}
+${helpers.predefined_type("flood-opacity", "Opacity",
+                          "1.0", products="gecko", animatable=False)}
 
 ${helpers.predefined_type(
     "lighting-color", "CSSColor",
     "CSSParserColor::RGBA(RGBA { red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0 })",
-    products="gecko")}
+    products="gecko",
+    animatable=False)}
 
 // CSS Masking Module Level 1
 // https://www.w3.org/TR/css-masking-1/
-${helpers.single_keyword("mask-type", "luminance alpha", products="gecko")}
+${helpers.single_keyword("mask-type", "luminance alpha",
+                         products="gecko", animatable=False)}
--- a/servo/components/style/properties/longhand/table.mako.rs
+++ b/servo/components/style/properties/longhand/table.mako.rs
@@ -1,9 +1,10 @@
 /* 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/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("Table", inherited=False) %>
 
-${helpers.single_keyword("table-layout", "auto fixed", gecko_ffi_name="mLayoutStrategy")}
+${helpers.single_keyword("table-layout", "auto fixed",
+                         gecko_ffi_name="mLayoutStrategy", animatable=False)}
--- a/servo/components/style/properties/longhand/text.mako.rs
+++ b/servo/components/style/properties/longhand/text.mako.rs
@@ -7,22 +7,26 @@
 
 <% data.new_style_struct("Text",
                          inherited=False,
                          gecko_name="TextReset",
                          additional_methods=[Method("has_underline", "bool"),
                                              Method("has_overline", "bool"),
                                              Method("has_line_through", "bool")]) %>
 
-${helpers.single_keyword("text-overflow", "clip ellipsis")}
+${helpers.single_keyword("text-overflow", "clip ellipsis", animatable=False)}
 
-${helpers.single_keyword("unicode-bidi", "normal embed isolate bidi-override isolate-override plaintext")}
+${helpers.single_keyword("unicode-bidi",
+                         "normal embed isolate bidi-override isolate-override plaintext",
+                         animatable=False)}
 
+// FIXME: This prop should be animatable.
 <%helpers:longhand name="${'text-decoration' if product == 'servo' else 'text-decoration-line'}"
-                   custom_cascade="${product == 'servo'}">
+                   custom_cascade="${product == 'servo'}"
+                   animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::computed::ComputedValueAsSpecified;
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
 
     #[derive(PartialEq, Eq, Copy, Clone, Debug)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
@@ -111,14 +115,16 @@
                                    _error_reporter: &mut StdBox<ParseErrorReporter + Send>) {
                 longhands::_servo_text_decorations_in_effect::derive_from_text_decoration(context);
         }
     % endif
 </%helpers:longhand>
 
 ${helpers.single_keyword("text-decoration-style",
                          "solid double dotted dashed wavy -moz-none",
-                         products="gecko")}
+                         products="gecko",
+                         animatable=False)}
 
 ${helpers.predefined_type(
     "text-decoration-color", "CSSColor",
     "CSSParserColor::RGBA(RGBA { red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0 })",
-    products="gecko")}
+    products="gecko",
+    animatable=True)}
--- a/servo/components/style/properties/longhand/ui.mako.rs
+++ b/servo/components/style/properties/longhand/ui.mako.rs
@@ -4,13 +4,16 @@
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 <% from data import Method %>
 
 // CSS Basic User Interface Module Level 1
 // https://drafts.csswg.org/css-ui-3/
 <% data.new_style_struct("UI", inherited=False, gecko_name="UIReset") %>
 
-${helpers.single_keyword("ime-mode", "normal auto active disabled inactive", products="gecko",
-                         gecko_ffi_name="mIMEMode")}
+${helpers.single_keyword("ime-mode", "normal auto active disabled inactive",
+                         products="gecko", gecko_ffi_name="mIMEMode",
+                         animatable=False)}
 
 ${helpers.single_keyword("-moz-user-select", "auto text none all", products="gecko",
-                         gecko_ffi_name="mUserSelect", gecko_constant_prefix="NS_STYLE_USER_SELECT")}
+                         gecko_ffi_name="mUserSelect",
+                         gecko_constant_prefix="NS_STYLE_USER_SELECT",
+                         animatable=False)}
--- a/servo/components/style/properties/longhand/xul.mako.rs
+++ b/servo/components/style/properties/longhand/xul.mako.rs
@@ -3,13 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 <% from data import Method %>
 
 // Non-standard properties that Gecko uses for XUL elements.
 <% data.new_style_struct("XUL", inherited=False) %>
 
-${helpers.single_keyword("-moz-box-align", "stretch start center baseline end", products="gecko",
-                         gecko_ffi_name="mBoxAlign", gecko_constant_prefix="NS_STYLE_BOX_ALIGN")}
+${helpers.single_keyword("-moz-box-align", "stretch start center baseline end",
+                         products="gecko", gecko_ffi_name="mBoxAlign",
+                         gecko_constant_prefix="NS_STYLE_BOX_ALIGN",
+                         animatable=False)}
 
-${helpers.predefined_type("-moz-box-flex", "Number", "0.0", "parse_non_negative", products="gecko",
-                          gecko_ffi_name="mBoxFlex")}
+${helpers.predefined_type("-moz-box-flex", "Number", "0.0", "parse_non_negative",
+                          products="gecko", gecko_ffi_name="mBoxFlex",
+                          animatable=False)}
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -9,17 +9,17 @@
 // can be escaped. In the above example, Vec<<&Foo> achieves the desired result of Vec<&Foo>.
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 use std::ascii::AsciiExt;
 use std::boxed::Box as StdBox;
 use std::collections::HashSet;
 use std::fmt;
-use std::fmt::Write;
+use std::fmt::{Debug, Write};
 use std::sync::Arc;
 
 use app_units::Au;
 use cssparser::Color as CSSParserColor;
 use cssparser::{Parser, RGBA, AtRuleParser, DeclarationParser, Delimiter,
                 DeclarationListParser, parse_important, ToCss, TokenSerializationType};
 use error_reporting::ParseErrorReporter;
 use url::Url;
@@ -127,16 +127,20 @@ pub mod shorthands {
     <%include file="/shorthand/inherited_text.mako.rs" />
     <%include file="/shorthand/list.mako.rs" />
     <%include file="/shorthand/margin.mako.rs" />
     <%include file="/shorthand/outline.mako.rs" />
     <%include file="/shorthand/padding.mako.rs" />
     <%include file="/shorthand/text.mako.rs" />
 }
 
+pub mod animated_properties {
+    <%include file="/helpers/animated_properties.mako.rs" />
+}
+
 
 // TODO(SimonSapin): Convert this to a syntax extension rather than a Mako template.
 // Maybe submit for inclusion in libstd?
 mod property_bit_field {
 
     pub struct PropertyBitField {
         storage: [u32; (${len(data.longhands)} - 1 + 32) / 32]
     }
@@ -1039,23 +1043,41 @@ impl PropertyDeclaration {
 
         match *self {
             % for property in data.longhands:
                 PropertyDeclaration::${property.camel_case}(_) => ${property.ident.upper()},
             % endfor
             PropertyDeclaration::Custom(_, _) => &[]
         }
     }
+
+    /// Returns true if this property is one of the animable properties, false
+    /// otherwise.
+    pub fn is_animatable(&self) -> bool {
+        match *self {
+            % for property in data.longhands:
+            PropertyDeclaration::${property.camel_case}(_) => {
+                % if property.animatable:
+                    true
+                % else:
+                    false
+                % endif
+            }
+            % endfor
+            PropertyDeclaration::Custom(..) => false,
+        }
+    }
 }
 
 pub mod style_struct_traits {
     use super::longhands;
+    use std::fmt::Debug;
 
     % for style_struct in data.active_style_structs():
-        pub trait ${style_struct.trait_name}: Clone {
+        pub trait ${style_struct.trait_name}: Debug + Clone {
             % for longhand in style_struct.longhands:
                 #[allow(non_snake_case)]
                 fn set_${longhand.ident}(&mut self, v: longhands::${longhand.ident}::computed_value::T);
                 #[allow(non_snake_case)]
                 fn copy_${longhand.ident}_from(&mut self, other: &Self);
                 % if longhand.need_clone:
                     #[allow(non_snake_case)]
                     fn clone_${longhand.ident}(&self) -> longhands::${longhand.ident}::computed_value::T;
@@ -1074,17 +1096,17 @@ pub mod style_structs {
     use super::longhands;
     use std::hash::{Hash, Hasher};
 
     % for style_struct in data.active_style_structs():
         % if style_struct.trait_name == "Font":
         #[derive(Clone, Debug)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         % else:
-        #[derive(PartialEq, Clone)]
+        #[derive(PartialEq, Clone, Debug)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         % endif
         pub struct ${style_struct.servo_struct_name} {
             % for longhand in style_struct.longhands:
                 pub ${longhand.ident}: longhands::${longhand.ident}::computed_value::T,
             % endfor
             % if style_struct.trait_name == "Font":
                 pub hash: u64,
@@ -1116,100 +1138,124 @@ pub mod style_structs {
                 fn clone_border_${side}_style(&self) -> longhands::border_${side}_style::computed_value::T {
                     self.border_${side}_style.clone()
                 }
                 fn border_${side}_has_nonzero_width(&self) -> bool {
                     self.border_${side}_width != ::app_units::Au(0)
                 }
                 % endfor
             % elif style_struct.trait_name == "Box":
+                #[inline]
                 fn clone_display(&self) -> longhands::display::computed_value::T {
                     self.display.clone()
                 }
+                #[inline]
                 fn clone_position(&self) -> longhands::position::computed_value::T {
                     self.position.clone()
                 }
+                #[inline]
                 fn clone_float(&self) -> longhands::float::computed_value::T {
                     self.float.clone()
                 }
+                #[inline]
                 fn clone_overflow_x(&self) -> longhands::overflow_x::computed_value::T {
                     self.overflow_x.clone()
                 }
+                #[inline]
                 fn clone_overflow_y(&self) -> longhands::overflow_y::computed_value::T {
                     self.overflow_y.clone()
                 }
+                #[inline]
+                fn clone_animation_play_state(&self) -> longhands::animation_play_state::computed_value::T {
+                    self.animation_play_state.clone()
+                }
+                #[inline]
                 fn transition_count(&self) -> usize {
                     self.transition_property.0.len()
                 }
             % elif style_struct.trait_name == "Color":
+                #[inline]
                 fn clone_color(&self) -> longhands::color::computed_value::T {
                     self.color.clone()
                 }
             % elif style_struct.trait_name == "Font":
+                #[inline]
                 fn clone_font_size(&self) -> longhands::font_size::computed_value::T {
                     self.font_size.clone()
                 }
+                #[inline]
                 fn clone_font_weight(&self) -> longhands::font_weight::computed_value::T {
                     self.font_weight.clone()
                 }
                 fn compute_font_hash(&mut self) {
                     // Corresponds to the fields in `gfx::font_template::FontTemplateDescriptor`.
                     let mut hasher: FnvHasher = Default::default();
                     hasher.write_u16(self.font_weight as u16);
                     self.font_stretch.hash(&mut hasher);
                     self.font_family.hash(&mut hasher);
                     self.hash = hasher.finish()
                 }
             % elif style_struct.trait_name == "InheritedBox":
+                #[inline]
                 fn clone_direction(&self) -> longhands::direction::computed_value::T {
                     self.direction.clone()
                 }
+                #[inline]
                 fn clone_writing_mode(&self) -> longhands::writing_mode::computed_value::T {
                     self.writing_mode.clone()
                 }
+                #[inline]
                 fn clone_text_orientation(&self) -> longhands::text_orientation::computed_value::T {
                     self.text_orientation.clone()
                 }
             % elif style_struct.trait_name == "InheritedText" and product == "servo":
+                #[inline]
                 fn clone__servo_text_decorations_in_effect(&self) ->
                     longhands::_servo_text_decorations_in_effect::computed_value::T {
                     self._servo_text_decorations_in_effect.clone()
                 }
             % elif style_struct.trait_name == "Outline":
+                #[inline]
                 fn clone_outline_style(&self) -> longhands::outline_style::computed_value::T {
                     self.outline_style.clone()
                 }
+                #[inline]
                 fn outline_has_nonzero_width(&self) -> bool {
                     self.outline_width != ::app_units::Au(0)
                 }
             % elif style_struct.trait_name == "Position":
+                #[inline]
                 fn clone_align_items(&self) -> longhands::align_items::computed_value::T {
                     self.align_items.clone()
                 }
+                #[inline]
                 fn clone_align_self(&self) -> longhands::align_self::computed_value::T {
                     self.align_self.clone()
                 }
             % elif style_struct.trait_name == "Text":
                 <% text_decoration_field = 'text_decoration' if product == 'servo' else 'text_decoration_line' %>
+                #[inline]
                 fn has_underline(&self) -> bool {
                     self.${text_decoration_field}.underline
                 }
+                #[inline]
                 fn has_overline(&self) -> bool {
                     self.${text_decoration_field}.overline
                 }
+                #[inline]
                 fn has_line_through(&self) -> bool {
                     self.${text_decoration_field}.line_through
                 }
             % endif
         }
 
     % endfor
 }
 
-pub trait ComputedValues : Clone + Send + Sync + 'static {
+pub trait ComputedValues : Debug + Clone + Send + Sync + 'static {
     % for style_struct in data.active_style_structs():
         type Concrete${style_struct.trait_name}: style_struct_traits::${style_struct.trait_name};
     % endfor
 
         // Temporary bailout case for stuff we haven't made work with the trait
         // yet - panics for non-Servo implementations.
         //
         // Used only for animations. Don't use it in other places.
@@ -1242,17 +1288,17 @@ pub trait ComputedValues : Clone + Send 
 
     fn custom_properties(&self) -> Option<Arc<::custom_properties::ComputedValuesMap>>;
     fn root_font_size(&self) -> Au;
     fn set_root_font_size(&mut self, size: Au);
     fn set_writing_mode(&mut self, mode: WritingMode);
     fn is_multicol(&self) -> bool;
 }
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct ServoComputedValues {
     % for style_struct in data.active_style_structs():
         ${style_struct.ident}: Arc<style_structs::${style_struct.servo_struct_name}>,
     % endfor
     custom_properties: Option<Arc<::custom_properties::ComputedValuesMap>>,
     shareable: bool,
     pub writing_mode: WritingMode,
--- a/servo/components/style/properties/shorthand/box.mako.rs
+++ b/servo/components/style/properties/shorthand/box.mako.rs
@@ -27,17 +27,17 @@
         transition_delay: transition_delay::SingleSpecifiedValue,
     }
 
     fn parse_one_transition(input: &mut Parser) -> Result<SingleTransition,()> {
         let (mut property, mut duration) = (None, None);
         let (mut timing_function, mut delay) = (None, None);
         loop {
             if property.is_none() {
-                if let Ok(value) = input.try(|input| transition_property::parse_one(input)) {
+                if let Ok(value) = input.try(transition_property::SingleSpecifiedValue::parse) {
                     property = Some(value);
                     continue
                 }
             }
 
             if duration.is_none() {
                 if let Ok(value) = input.try(|input| transition_duration::parse_one(input)) {
                     duration = Some(value);
--- a/servo/components/style/selector_impl.rs
+++ b/servo/components/style/selector_impl.rs
@@ -4,16 +4,17 @@
 
 //! The pseudo-classes and pseudo-elements supported by the style system.
 
 use element_state::ElementState;
 use properties::{self, ServoComputedValues};
 use selector_matching::{USER_OR_USER_AGENT_STYLESHEETS, QUIRKS_MODE_STYLESHEET};
 use selectors::Element;
 use selectors::parser::{ParserContext, SelectorImpl};
+use std::fmt::Debug;
 use stylesheets::Stylesheet;
 
 /// This function determines if a pseudo-element is eagerly cascaded or not.
 ///
 /// Eagerly cascaded pseudo-elements are "normal" pseudo-elements (i.e.
 /// `::before` and `::after`). They inherit styles normally as another
 /// selector would do, and they're part of the cascade.
 ///
@@ -57,17 +58,19 @@ impl PseudoElementCascadeType {
         *self == PseudoElementCascadeType::Precomputed
     }
 }
 
 pub trait ElementExt: Element {
     fn is_link(&self) -> bool;
 }
 
-pub trait SelectorImplExt : SelectorImpl + Sized {
+// NB: The `Clone` trait is here for convenience due to:
+// https://github.com/rust-lang/rust/issues/26925
+pub trait SelectorImplExt : SelectorImpl + Clone + Debug + Sized {
     type ComputedValues: properties::ComputedValues;
 
     fn pseudo_element_cascade_type(pseudo: &Self::PseudoElement) -> PseudoElementCascadeType;
 
     fn each_pseudo_element<F>(mut fun: F)
         where F: FnMut(Self::PseudoElement);
 
     #[inline]
@@ -85,16 +88,17 @@ pub trait SelectorImplExt : SelectorImpl
         where F: FnMut(<Self as SelectorImpl>::PseudoElement) {
         Self::each_pseudo_element(|pseudo| {
             if Self::pseudo_element_cascade_type(&pseudo).is_precomputed() {
                 fun(pseudo)
             }
         })
     }
 
+    fn pseudo_is_before_or_after(pseudo: &Self::PseudoElement) -> bool;
 
     fn pseudo_class_state_flag(pc: &Self::NonTSPseudoClass) -> ElementState;
 
     fn get_user_or_user_agent_stylesheets() -> &'static [Stylesheet<Self>];
 
     fn get_quirks_mode_stylesheet() -> Option<&'static Stylesheet<Self>>;
 }
 
@@ -105,16 +109,25 @@ pub enum PseudoElement {
     After,
     Selection,
     DetailsSummary,
     DetailsContent,
 }
 
 impl PseudoElement {
     #[inline]
+    pub fn is_before_or_after(&self) -> bool {
+        match *self {
+            PseudoElement::Before |
+            PseudoElement::After => true,
+            _ => false,
+        }
+    }
+
+    #[inline]
     pub fn cascade_type(&self) -> PseudoElementCascadeType {
         match *self {
             PseudoElement::Before |
             PseudoElement::After |
             PseudoElement::Selection => PseudoElementCascadeType::Eager,
             PseudoElement::DetailsSummary => PseudoElementCascadeType::Lazy,
             PseudoElement::DetailsContent => PseudoElementCascadeType::Precomputed,
         }
@@ -245,16 +258,21 @@ impl SelectorImplExt for ServoSelectorIm
     }
 
     #[inline]
     fn pseudo_class_state_flag(pc: &NonTSPseudoClass) -> ElementState {
         pc.state_flag()
     }
 
     #[inline]
+    fn pseudo_is_before_or_after(pseudo: &PseudoElement) -> bool {
+        pseudo.is_before_or_after()
+    }
+
+    #[inline]
     fn get_user_or_user_agent_stylesheets() -> &'static [Stylesheet<Self>] {
         &*USER_OR_USER_AGENT_STYLESHEETS
     }
 
     #[inline]
     fn get_quirks_mode_stylesheet() -> Option<&'static Stylesheet<Self>> {
         Some(&*QUIRKS_MODE_STYLESHEET)
     }
--- a/servo/components/style/selector_matching.rs
+++ b/servo/components/style/selector_matching.rs
@@ -2,33 +2,35 @@
  * 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/. */
 
 //! Selector matching.
 
 use dom::PresentationalHintsSynthetizer;
 use element_state::*;
 use error_reporting::StdoutErrorReporter;
+use keyframes::KeyframesAnimation;
 use media_queries::{Device, MediaType};
 use parser::ParserContextExtraData;
 use properties::{self, PropertyDeclaration, PropertyDeclarationBlock};
 use restyle_hints::{ElementSnapshot, RestyleHint, DependencySet};
 use selector_impl::{SelectorImplExt, ServoSelectorImpl};
 use selectors::Element;
 use selectors::bloom::BloomFilter;
 use selectors::matching::DeclarationBlock as GenericDeclarationBlock;
 use selectors::matching::{Rule, SelectorMap};
 use selectors::parser::SelectorImpl;
 use smallvec::VecLike;
 use std::collections::HashMap;
 use std::hash::BuildHasherDefault;
 use std::process;
 use std::sync::Arc;
+use string_cache::Atom;
 use style_traits::viewport::ViewportConstraints;
-use stylesheets::{CSSRuleIteratorExt, Origin, Stylesheet};
+use stylesheets::{CSSRule, CSSRuleIteratorExt, Origin, Stylesheet};
 use url::Url;
 use util::opts;
 use util::resource_files::read_resource_file;
 use viewport::{MaybeNew, ViewportRuleCascade};
 
 
 pub type DeclarationBlock = GenericDeclarationBlock<Vec<PropertyDeclaration>>;
 
@@ -121,16 +123,19 @@ pub struct Stylist<Impl: SelectorImplExt
     element_map: PerPseudoElementSelectorMap<Impl>,
 
     /// The selector maps corresponding to a given pseudo-element
     /// (depending on the implementation)
     pseudos_map: HashMap<Impl::PseudoElement,
                          PerPseudoElementSelectorMap<Impl>,
                          BuildHasherDefault<::fnv::FnvHasher>>,
 
+    /// A map with all the animations indexed by name.
+    animations: HashMap<Atom, KeyframesAnimation>,
+
     /// Applicable declarations for a given non-eagerly cascaded pseudo-element.
     /// These are eagerly computed once, and then used to resolve the new
     /// computed values on the fly on layout.
     precomputed_pseudo_element_decls: HashMap<Impl::PseudoElement,
                                               Vec<DeclarationBlock>,
                                               BuildHasherDefault<::fnv::FnvHasher>>,
 
     rules_source_order: usize,
@@ -145,16 +150,17 @@ impl<Impl: SelectorImplExt> Stylist<Impl
         let mut stylist = Stylist {
             viewport_constraints: None,
             device: device,
             is_device_dirty: true,
             quirks_mode: false,
 
             element_map: PerPseudoElementSelectorMap::new(),
             pseudos_map: HashMap::with_hasher(Default::default()),
+            animations: HashMap::with_hasher(Default::default()),
             precomputed_pseudo_element_decls: HashMap::with_hasher(Default::default()),
             rules_source_order: 0,
             state_deps: DependencySet::new(),
         };
 
         Impl::each_eagerly_cascaded_pseudo_element(|pseudo| {
             stylist.pseudos_map.insert(pseudo, PerPseudoElementSelectorMap::new());
         });
@@ -168,16 +174,17 @@ impl<Impl: SelectorImplExt> Stylist<Impl
                   stylesheets_changed: bool) -> bool
                   where Impl: 'static {
         if !(self.is_device_dirty || stylesheets_changed) {
             return false;
         }
 
         self.element_map = PerPseudoElementSelectorMap::new();
         self.pseudos_map = HashMap::with_hasher(Default::default());
+        self.animations = HashMap::with_hasher(Default::default());
         Impl::each_eagerly_cascaded_pseudo_element(|pseudo| {
             self.pseudos_map.insert(pseudo, PerPseudoElementSelectorMap::new());
         });
 
         self.precomputed_pseudo_element_decls = HashMap::with_hasher(Default::default());
         self.rules_source_order = 0;
         self.state_deps.clear();
 
@@ -228,27 +235,46 @@ impl<Impl: SelectorImplExt> Stylist<Impl
                                 source_order: rules_source_order,
                             },
                         });
                     }
                 }
             };
         );
 
-        for style_rule in stylesheet.effective_rules(&self.device).style() {
-            append!(style_rule, normal);
-            append!(style_rule, important);
-            rules_source_order += 1;
-            for selector in &style_rule.selectors {
-                self.state_deps.note_selector(selector.compound_selectors.clone());
+        for rule in stylesheet.effective_rules(&self.device) {
+            match *rule {
+                CSSRule::Style(ref style_rule) => {
+                    append!(style_rule, normal);
+                    append!(style_rule, important);
+                    rules_source_order += 1;
+                    for selector in &style_rule.selectors {
+                        self.state_deps.note_selector(selector.compound_selectors.clone());
+                    }
+
+                    self.rules_source_order = rules_source_order;
+                }
+                CSSRule::Keyframes(ref keyframes_rule) => {
+                    debug!("Found valid keyframes rule: {:?}", keyframes_rule);
+                    if let Some(animation) = KeyframesAnimation::from_keyframes(&keyframes_rule.keyframes) {
+                        debug!("Found valid keyframe animation: {:?}", animation);
+                        self.animations.insert(keyframes_rule.name.clone(),
+                                               animation);
+                    } else {
+                        // If there's a valid keyframes rule, even if it doesn't
+                        // produce an animation, should shadow other animations
+                        // with the same name.
+                        self.animations.remove(&keyframes_rule.name);
+                    }
+                }
+                // We don't care about any other rule.
+                _ => {}
             }
         }
 
-        self.rules_source_order = rules_source_order;
-
         Impl::each_precomputed_pseudo_element(|pseudo| {
             // TODO: Consider not doing this and just getting the rules on the
             // fly. It should be a bit slower, but we'd take rid of the
             // extra field, and avoid this precomputation entirely.
             if let Some(map) = self.pseudos_map.remove(&pseudo) {
                 let mut declarations = vec![];
 
                 map.user_agent.normal.get_universal_rules(&mut declarations);
@@ -265,17 +291,18 @@ impl<Impl: SelectorImplExt> Stylist<Impl
                                          pseudo: &Impl::PseudoElement,
                                          parent: Option<&Arc<Impl::ComputedValues>>)
                                          -> Option<Arc<Impl::ComputedValues>> {
         debug_assert!(Impl::pseudo_element_cascade_type(pseudo).is_precomputed());
         if let Some(declarations) = self.precomputed_pseudo_element_decls.get(pseudo) {
             let (computed, _) =
                 properties::cascade(self.device.au_viewport_size(),
                                     &declarations, false,
-                                    parent.map(|p| &**p), None,
+                                    parent.map(|p| &**p),
+                                    None,
                                     Box::new(StdoutErrorReporter));
             Some(Arc::new(computed))
         } else {
             parent.map(|p| p.clone())
         }
     }
 
     pub fn lazily_compute_pseudo_element_style<E>(&self,
@@ -432,16 +459,21 @@ impl<Impl: SelectorImplExt> Stylist<Impl
 
         shareable
     }
 
     #[inline]
     pub fn is_device_dirty(&self) -> bool {
         self.is_device_dirty
     }
+
+    #[inline]
+    pub fn animations(&self) -> &HashMap<Atom, KeyframesAnimation> {
+        &self.animations
+    }
 }
 
 /// Map that contains the CSS rules for a given origin.
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 struct PerOriginSelectorMap<Impl: SelectorImpl> {
     /// Rules that contains at least one property declararion with
     /// normal importance.
     normal: SelectorMap<Vec<PropertyDeclaration>, Impl>,
--- a/servo/components/style/servo.rs
+++ b/servo/components/style/servo.rs
@@ -1,17 +1,18 @@
 /* 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/. */
-
 //! Concrete types for servo Style implementation
 
+use animation;
 use context;
 use data;
 use properties::ServoComputedValues;
 use selector_impl::ServoSelectorImpl;
 use selector_matching;
 use stylesheets;
 
 pub type Stylesheet = stylesheets::Stylesheet<ServoSelectorImpl>;
 pub type PrivateStyleData = data::PrivateStyleData<ServoSelectorImpl, ServoComputedValues>;
 pub type Stylist = selector_matching::Stylist<ServoSelectorImpl>;
 pub type SharedStyleContext = context::SharedStyleContext<ServoSelectorImpl>;
+pub type Animation = animation::Animation<ServoSelectorImpl>;
--- a/servo/components/style/stylesheets.rs
+++ b/servo/components/style/stylesheets.rs
@@ -1,19 +1,20 @@
 /* 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/. */
 
 //! Style sheets and their CSS rules.
 
 use cssparser::{AtRuleParser, Parser, QualifiedRuleParser, decode_stylesheet_bytes};
-use cssparser::{AtRuleType, RuleListParser};
+use cssparser::{AtRuleType, RuleListParser, Token};
 use encoding::EncodingRef;
 use error_reporting::ParseErrorReporter;
 use font_face::{FontFaceRule, parse_font_face_block};
+use keyframes::{Keyframe, parse_keyframe_list};
 use media_queries::{Device, MediaQueryList, parse_media_query_list};
 use parser::{ParserContext, ParserContextExtraData, log_css_error};
 use properties::{PropertyDeclarationBlock, parse_property_declaration_list};
 use selectors::parser::{Selector, SelectorImpl, parse_selector_list};
 use smallvec::SmallVec;
 use std::cell::Cell;
 use std::iter::Iterator;
 use std::marker::PhantomData;
@@ -57,25 +58,34 @@ pub struct Stylesheet<Impl: SelectorImpl
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum CSSRule<Impl: SelectorImpl> {
     Charset(String),
     Namespace(Option<String>, Namespace),
     Style(StyleRule<Impl>),
     Media(MediaRule<Impl>),
     FontFace(FontFaceRule),
     Viewport(ViewportRule),
+    Keyframes(KeyframesRule),
+}
+
+
+#[derive(Debug, HeapSizeOf, PartialEq)]
+pub struct KeyframesRule {
+    pub name: Atom,
+    pub keyframes: Vec<Keyframe>,
 }
 
 #[derive(Debug, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct MediaRule<Impl: SelectorImpl> {
     pub media_queries: MediaQueryList,
     pub rules: Vec<CSSRule<Impl>>,
 }
 
+
 impl<Impl: SelectorImpl> MediaRule<Impl> {
     #[inline]
     pub fn evaluate(&self, device: &Device) -> bool {
         self.media_queries.evaluate(device)
     }
 }
 
 #[derive(Debug, PartialEq)]
@@ -122,42 +132,44 @@ impl<Impl: SelectorImpl> Stylesheet<Impl
             context: ParserContext::new_with_extra_data(origin, &base_url, error_reporter.clone(),
                                                         extra_data),
             state: Cell::new(State::Start),
             _impl: PhantomData,
         };
         let mut input = Parser::new(css);
         input.look_for_viewport_percentages();
 
-        let mut rules = Vec::new();
+        let mut rules = vec![];
         {
             let mut iter = RuleListParser::new_for_stylesheet(&mut input, rule_parser);
             while let Some(result) = iter.next() {
                 match result {
                     Ok(rule) => {
                         if let CSSRule::Namespace(ref prefix, ref namespace) = rule {
                             if let Some(prefix) = prefix.as_ref() {
                                 iter.parser.context.selector_context.namespace_prefixes.insert(
                                     prefix.clone(), namespace.clone());
                             } else {
                                 iter.parser.context.selector_context.default_namespace =
                                     Some(namespace.clone());
                             }
                         }
+
                         rules.push(rule);
                     }
                     Err(range) => {
                         let pos = range.start;
                         let message = format!("Invalid rule: '{}'", iter.input.slice(range));
                         let context = ParserContext::new(origin, &base_url, error_reporter.clone());
                         log_css_error(iter.input, pos, &*message, &context);
                     }
                 }
             }
         }
+
         Stylesheet {
             origin: origin,
             rules: rules,
             media: None,
             dirty_on_viewport_size_change: input.seen_viewport_percentages(),
         }
     }
 
@@ -248,29 +260,30 @@ impl<'a, Impl: SelectorImpl + 'a> Iterat
 
 pub mod rule_filter {
     //! Specific `CSSRule` variant iterators.
 
     use selectors::parser::SelectorImpl;
     use std::marker::PhantomData;
     use super::super::font_face::FontFaceRule;
     use super::super::viewport::ViewportRule;
-    use super::{CSSRule, MediaRule, StyleRule};
+    use super::{CSSRule, KeyframesRule, MediaRule, StyleRule};
 
     macro_rules! rule_filter {
         ($variant:ident -> $value:ty) => {
             /// An iterator that only yields rules that are of the synonymous `CSSRule` variant.
             #[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
             pub struct $variant<'a, I> {
                 iter: I,
                 _lifetime: PhantomData<&'a ()>
             }
 
             impl<'a, I, Impl: SelectorImpl + 'a> $variant<'a, I>
                 where I: Iterator<Item=&'a CSSRule<Impl>> {
+                #[inline]
                 pub fn new(iter: I) -> $variant<'a, I> {
                     $variant {
                         iter: iter,
                         _lifetime: PhantomData
                     }
                 }
             }
 
@@ -295,31 +308,35 @@ pub mod rule_filter {
             }
         }
     }
 
     rule_filter!(Media -> MediaRule<Impl>);
     rule_filter!(Style -> StyleRule<Impl>);
     rule_filter!(FontFace -> FontFaceRule);
     rule_filter!(Viewport -> ViewportRule);
+    rule_filter!(Keyframes -> KeyframesRule);
 }
 
 /// Extension methods for `CSSRule` iterators.
 pub trait CSSRuleIteratorExt<'a, Impl: SelectorImpl + 'a>: Iterator<Item=&'a CSSRule<Impl>> + Sized {
     /// Yield only @font-face rules.
     fn font_face(self) -> rule_filter::FontFace<'a, Self>;
 
     /// Yield only @media rules.
     fn media(self) -> rule_filter::Media<'a, Self>;
 
     /// Yield only style rules.
     fn style(self) -> rule_filter::Style<'a, Self>;
 
     /// Yield only @viewport rules.
     fn viewport(self) -> rule_filter::Viewport<'a, Self>;
+
+    /// Yield only @keyframes rules.
+    fn keyframes(self) -> rule_filter::Keyframes<'a, Self>;
 }
 
 impl<'a, I, Impl: SelectorImpl + 'a> CSSRuleIteratorExt<'a, Impl> for I where I: Iterator<Item=&'a CSSRule<Impl>> {
     #[inline]
     fn font_face(self) -> rule_filter::FontFace<'a, I> {
         rule_filter::FontFace::new(self)
     }
 
@@ -332,16 +349,21 @@ impl<'a, I, Impl: SelectorImpl + 'a> CSS
     fn style(self) -> rule_filter::Style<'a, I> {
         rule_filter::Style::new(self)
     }
 
     #[inline]
     fn viewport(self) -> rule_filter::Viewport<'a, I> {
         rule_filter::Viewport::new(self)
     }
+
+    #[inline]
+    fn keyframes(self) -> rule_filter::Keyframes<'a, I> {
+        rule_filter::Keyframes::new(self)
+    }
 }
 
 fn parse_nested_rules<Impl: SelectorImpl>(context: &ParserContext, input: &mut Parser) -> Vec<CSSRule<Impl>> {
     let mut iter = RuleListParser::new_for_nested_rule(input,
                                                        NestedRuleParser {
                                                            context: context,
                                                            _impl: PhantomData
                                                        });
@@ -371,19 +393,24 @@ enum State {
     Start = 1,
     Imports = 2,
     Namespaces = 3,
     Body = 4,
 }
 
 
 enum AtRulePrelude {
+    /// A @font-face rule prelude.
     FontFace,
+    /// A @media rule prelude, with its media queries.
     Media(MediaQueryList),
+    /// A @viewport rule prelude.
     Viewport,
+    /// A @keyframes rule, with its animation name.
+    Keyframes(Atom),
 }
 
 
 impl<'a, Impl: SelectorImpl> AtRuleParser for TopLevelRuleParser<'a, Impl> {
     type Prelude = AtRulePrelude;
     type AtRule = CSSRule<Impl>;
 
     fn parse_prelude(&self, name: &str, input: &mut Parser)
@@ -473,16 +500,25 @@ impl<'a, 'b, Impl: SelectorImpl> AtRuleP
             },
             "viewport" => {
                 if ::util::prefs::get_pref("layout.viewport.enabled").as_boolean().unwrap_or(false) {
                     Ok(AtRuleType::WithBlock(AtRulePrelude::Viewport))
                 } else {
                     Err(())
                 }
             },
+            "keyframes" => {
+                let name = match input.next() {
+                    Ok(Token::Ident(ref value)) if value != "none" => Atom::from(&**value),
+                    Ok(Token::QuotedString(value)) => Atom::from(&*value),
+                    _ => return Err(())
+                };
+
+                Ok(AtRuleType::WithBlock(AtRulePrelude::Keyframes(Atom::from(name))))
+            },
             _ => Err(())
         }
     }
 
     fn parse_block(&self, prelude: AtRulePrelude, input: &mut Parser) -> Result<CSSRule<Impl>, ()> {
         match prelude {
             AtRulePrelude::FontFace => {
                 parse_font_face_block(self.context, input).map(CSSRule::FontFace)
@@ -491,21 +527,26 @@ impl<'a, 'b, Impl: SelectorImpl> AtRuleP
                 Ok(CSSRule::Media(MediaRule {
                     media_queries: media_queries,
                     rules: parse_nested_rules(self.context, input),
                 }))
             }
             AtRulePrelude::Viewport => {
                 ViewportRule::parse(input, self.context).map(CSSRule::Viewport)
             }
+            AtRulePrelude::Keyframes(name) => {
+                Ok(CSSRule::Keyframes(KeyframesRule {
+                    name: name,
+                    keyframes: parse_keyframe_list(&self.context, input),
+                }))
+            }
         }
     }
 }
 
-
 impl<'a, 'b, Impl: SelectorImpl> QualifiedRuleParser for NestedRuleParser<'a, 'b, Impl> {
     type Prelude = Vec<Selector<Impl>>;
     type QualifiedRule = CSSRule<Impl>;
 
     fn parse_prelude(&self, input: &mut Parser) -> Result<Vec<Selector<Impl>>, ()> {
         parse_selector_list(&self.context.selector_context, input)
     }
 
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -214,20 +214,19 @@ pub fn recalc_style_at<'a, N, C>(context
                         }
                         None
                     },
                 };
 
                 // Perform the CSS cascade.
                 unsafe {
                     node.cascade_node(&context.shared_context(),
+                                      &context.local_context(),
                                       parent_opt,
-                                      &applicable_declarations,
-                                      &mut context.local_context().applicable_declarations_cache.borrow_mut(),
-                                      &context.shared_context().new_animations_sender);
+                                      &applicable_declarations);
                 }
 
                 // Add ourselves to the LRU cache.
                 if let Some(element) = shareable_element {
                     style_sharing_candidate_cache.insert_if_possible::<'ln, N>(&element);
                 }
             }
             StyleSharingResult::StyleWasShared(index, damage) => {
--- a/servo/components/style/values.rs
+++ b/servo/components/style/values.rs
@@ -1386,26 +1386,23 @@ pub mod specified {
                 stops: stops,
             })
         }
     }
 
     pub fn parse_border_radius(input: &mut Parser) -> Result<BorderRadiusSize, ()> {
         input.try(BorderRadiusSize::parse).or_else(|()| {
                 match_ignore_ascii_case! { try!(input.expect_ident()),
-                                           "thin" =>
-                                           Ok(BorderRadiusSize::circle(
-                                               LengthOrPercentage::Length(Length::from_px(1.)))),
-                                           "medium" =>
-                                           Ok(BorderRadiusSize::circle(
-                                               LengthOrPercentage::Length(Length::from_px(3.)))),
-                                           "thick" =>
-                                           Ok(BorderRadiusSize::circle(
-                                               LengthOrPercentage::Length(Length::from_px(5.)))),
-                                           _ => Err(())
+                    "thin" => Ok(BorderRadiusSize::circle(
+                                     LengthOrPercentage::Length(Length::from_px(1.)))),
+                    "medium" => Ok(BorderRadiusSize::circle(
+                                       LengthOrPercentage::Length(Length::from_px(3.)))),
+                    "thick" => Ok(BorderRadiusSize::circle(
+                                      LengthOrPercentage::Length(Length::from_px(5.)))),
+                    _ => Err(())
                 }
             })
     }
 
     pub fn parse_border_width(input: &mut Parser) -> Result<Length, ()> {
         input.try(Length::parse_non_negative).or_else(|()| {
             match_ignore_ascii_case! { try!(input.expect_ident()),
                 "thin" => Ok(Length::from_px(1.)),
@@ -1747,17 +1744,17 @@ pub mod computed {
                 }
             }
 
             CalcLengthOrPercentage { length: length, percentage: self.percentage.map(|p| p.0) }
         }
     }
 
 
-    #[derive(PartialEq, Clone, Copy)]
+    #[derive(Debug, PartialEq, Clone, Copy)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct BorderRadiusSize(pub Size2D<LengthOrPercentage>);
 
     impl BorderRadiusSize {
         pub fn zero() -> BorderRadiusSize {
             BorderRadiusSize(Size2D::new(LengthOrPercentage::Length(Au(0)), LengthOrPercentage::Length(Au(0))))
         }
     }
--- a/servo/components/style/viewport.rs
+++ b/servo/components/style/viewport.rs
@@ -557,18 +557,18 @@ impl<'a, I> ViewportDescriptorDeclaratio
 pub trait MaybeNew {
     fn maybe_new(initial_viewport: TypedSize2D<ViewportPx, f32>,
                      rule: &ViewportRule)
                      -> Option<ViewportConstraints>;
 }
 
 impl MaybeNew for ViewportConstraints {
     fn maybe_new(initial_viewport: TypedSize2D<ViewportPx, f32>,
-                     rule: &ViewportRule)
-                     -> Option<ViewportConstraints>
+                 rule: &ViewportRule)
+                 -> Option<ViewportConstraints>
     {
         use std::cmp;
 
         if rule.declarations.is_empty() {
             return None
         }
 
         let mut min_width = None;
--- a/servo/ports/geckolib/data.rs
+++ b/servo/ports/geckolib/data.rs
@@ -1,29 +1,30 @@
 /* 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::Size2D;
 use euclid::size::TypedSize2D;
 use gecko_bindings::bindings::RawServoStyleSet;
 use num_cpus;
-use selector_impl::{Stylist, Stylesheet, SharedStyleContext};
+use selector_impl::{GeckoSelectorImpl, Stylist, Stylesheet, SharedStyleContext};
 use std::cmp;
 use std::collections::HashMap;
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::sync::{Arc, RwLock};
-use style::animation::Animation;
 use style::dom::OpaqueNode;
 use style::media_queries::{Device, MediaType};
 use style::parallel::WorkQueueData;
 use util::geometry::ViewportPx;
 use util::thread_state;
 use util::workqueue::WorkQueue;
 
+pub type Animation = ::style::animation::Animation<GeckoSelectorImpl>;
+
 pub struct PerDocumentStyleData {
     /// Rule processor.
     pub stylist: Arc<Stylist>,
 
     /// List of stylesheets, mirrored from Gecko.
     pub stylesheets: Vec<Arc<Stylesheet>>,
 
     /// Whether the stylesheets list above has changed since the last restyle.
--- a/servo/ports/geckolib/properties.mako.rs
+++ b/servo/ports/geckolib/properties.mako.rs
@@ -34,17 +34,17 @@ use style::logical_geometry::WritingMode
 use style::properties::{CascadePropertyFn, ServoComputedValues, ComputedValues};
 use style::properties::longhands;
 use style::properties::make_cascade_vec;
 use style::properties::style_struct_traits::*;
 use values::{StyleCoordHelpers, ToGeckoStyleCoord, convert_nscolor_to_rgba};
 use values::{convert_rgba_to_nscolor, debug_assert_unit_is_safe_to_copy};
 use values::round_border_to_device_pixels;
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct GeckoComputedValues {
     % for style_struct in data.style_structs:
     ${style_struct.ident}: Arc<${style_struct.gecko_struct_name}>,
     % endfor
 
     custom_properties: Option<Arc<ComputedValuesMap>>,
     shareable: bool,
     pub writing_mode: WritingMode,
@@ -384,16 +384,22 @@ impl Debug for ${style_struct.gecko_stru
    # These are currently being shuffled to a different style struct on the gecko side.
    force_stub += ["backface-visibility", "transform-box", "transform-style"]
    # These live in an nsFont member in Gecko. Should be straightforward to do manually.
    force_stub += ["font-kerning", "font-stretch", "font-variant"]
    # These have unusual representations in gecko.
    force_stub += ["list-style-type", "text-overflow"]
    # These are booleans.
    force_stub += ["page-break-after", "page-break-before"]
+   # In a nsTArray, have to be done manually, but probably not too much work
+   # (the "filling them", not the "making them work")
+   force_stub += ["animation-name", "animation-duration",
+                  "animation-timing-function", "animation-iteration-count",
+                  "animation-direction", "animation-play-state",
+                  "animation-fill-mode", "animation-delay"]
 
    # Types used with predefined_type()-defined properties that we can auto-generate.
    predefined_types = {
        "LengthOrPercentage": impl_style_coord,
        "LengthOrPercentageOrAuto": impl_style_coord,
        "LengthOrPercentageOrNone": impl_style_coord,
        "Number": impl_simple,
        "Opacity": impl_simple,
--- a/servo/ports/geckolib/selector_impl.rs
+++ b/servo/ports/geckolib/selector_impl.rs
@@ -11,16 +11,17 @@ use style::selector_impl::{PseudoElement
 pub type Stylist = style::selector_matching::Stylist<GeckoSelectorImpl>;
 pub type Stylesheet = style::stylesheets::Stylesheet<GeckoSelectorImpl>;
 pub type SharedStyleContext = style::context::SharedStyleContext<GeckoSelectorImpl>;
 pub type PrivateStyleData = style::data::PrivateStyleData<GeckoSelectorImpl, GeckoComputedValues>;
 
 #[cfg(feature = "servo_features")]
 known_heap_size!(0, GeckoSelectorImpl, PseudoElement, NonTSPseudoClass);
 
+#[derive(Debug, Clone)]
 pub struct GeckoSelectorImpl;
 
 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
 pub enum PseudoElement {
     Before,
     After,
 
     Backdrop,
@@ -375,16 +376,25 @@ impl SelectorImplExt for GeckoSelectorIm
 
         fun(AnonBox(MozSVGMarkerAnonChild));
         fun(AnonBox(MozSVGOuterSVGAnonChild));
         fun(AnonBox(MozSVGForeignContent));
         fun(AnonBox(MozSVGText));
     }
 
     #[inline]
+    fn pseudo_is_before_or_after(pseudo: &PseudoElement) -> bool {
+        match *pseudo {
+            PseudoElement::Before |
+            PseudoElement::After => true,
+            _ => false,
+        }
+    }
+
+    #[inline]
     fn pseudo_class_state_flag(pc: &NonTSPseudoClass) -> ElementState {
         pc.state_flag()
     }
 
     #[inline]
     fn get_user_or_user_agent_stylesheets() -> &'static [Stylesheet] {
         &[]
     }
--- a/servo/ports/geckolib/string_cache/lib.rs
+++ b/servo/ports/geckolib/string_cache/lib.rs
@@ -10,16 +10,17 @@ use gecko_bindings::bindings::Gecko_AddR
 use gecko_bindings::bindings::Gecko_AtomEqualsUTF8IgnoreCase;
 use gecko_bindings::bindings::Gecko_Atomize;
 use gecko_bindings::bindings::Gecko_GetAtomAsUTF16;
 use gecko_bindings::bindings::Gecko_ReleaseAtom;
 use gecko_bindings::structs::nsIAtom;
 use heapsize::HeapSizeOf;
 use serde::{Deserialize, Deserializer, Serialize, Serializer};
 use std::borrow::Cow;
+use std::char;
 use std::fmt;
 use std::hash::{Hash, Hasher};
 use std::marker::PhantomData;
 use std::mem::transmute;
 use std::ops::Deref;
 use std::slice;
 
 #[macro_use]
@@ -201,16 +202,25 @@ impl Deserialize for Atom {
 }
 
 impl fmt::Debug for Atom {
     fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
         write!(w, "Gecko Atom {:p}", self.0)
     }
 }
 
+impl fmt::Display for Atom {
+    fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
+        for c in char::decode_utf16(self.as_slice().iter().cloned()) {
+            try!(write!(w, "{}", c.unwrap_or(char::REPLACEMENT_CHARACTER)))
+        }
+        Ok(())
+    }
+}
+
 impl<'a> From<&'a str> for Atom {
     #[inline]
     fn from(string: &str) -> Atom {
         assert!(string.len() <= u32::max_value() as usize);
         Atom(unsafe {
             Gecko_Atomize(string.as_ptr() as *const _, string.len() as u32)
         })
     }
--- a/servo/tests/unit/style/stylesheets.rs
+++ b/servo/tests/unit/style/stylesheets.rs
@@ -4,32 +4,37 @@
 
 use cssparser::{self, Parser, SourcePosition};
 use media_queries::CSSErrorReporterTest;
 use selectors::parser::*;
 use std::borrow::ToOwned;
 use std::sync::Arc;
 use std::sync::Mutex;
 use string_cache::{Atom, Namespace};
+use style::error_reporting::ParseErrorReporter;
+use style::keyframes::{Keyframe, KeyframeSelector, KeyframePercentage};
 use style::parser::ParserContextExtraData;
 use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, DeclaredValue, longhands};
-use style::stylesheets::{CSSRule, StyleRule, Origin};
-use style::error_reporting::ParseErrorReporter;
 use style::servo::Stylesheet;
+use style::stylesheets::{CSSRule, StyleRule, KeyframesRule, Origin};
+use style::values::specified::{LengthOrPercentageOrAuto, Percentage};
 use url::Url;
 
 #[test]
 fn test_parse_stylesheet() {
     let css = r"
         @namespace url(http://www.w3.org/1999/xhtml);
         /* FIXME: only if scripting is enabled */
         input[type=hidden i] { display: none !important; }
         html , body /**/ { display: block; }
         #d1 > .ok { background: blue; }
-    ";
+        @keyframes foo {
+            from { width: 0% }
+            to { width: 100%}
+        }";
     let url = Url::parse("about::test").unwrap();
     let stylesheet = Stylesheet::from_str(css, url, Origin::UserAgent,
                                           Box::new(CSSErrorReporterTest),
                                           ParserContextExtraData::default());
     assert_eq!(stylesheet, Stylesheet {
         origin: Origin::UserAgent,
         media: None,
         dirty_on_viewport_size_change: false,
@@ -140,16 +145,38 @@ fn test_parse_stylesheet() {
                                     red: 0., green: 0., blue: 1., alpha: 1.
                                 }),
                             }
                         )),
                     ]),
                     important: Arc::new(vec![]),
                 },
             }),
+            CSSRule::Keyframes(KeyframesRule {
+                name: "foo".into(),
+                keyframes: vec![
+                    Keyframe {
+                        selector: KeyframeSelector::new_for_unit_testing(
+                                      vec![KeyframePercentage::new(0.)]),
+                        declarations: Arc::new(vec![
+                            PropertyDeclaration::Width(DeclaredValue::Value(
+                                LengthOrPercentageOrAuto::Percentage(Percentage(0.)))),
+                        ]),
+                    },
+                    Keyframe {
+                        selector: KeyframeSelector::new_for_unit_testing(
+                                      vec![KeyframePercentage::new(1.)]),
+                        declarations: Arc::new(vec![
+                            PropertyDeclaration::Width(DeclaredValue::Value(
+                                LengthOrPercentageOrAuto::Percentage(Percentage(1.)))),
+                        ]),
+                    },
+                ]
+            })
+
         ],
     });
 }
 
 struct CSSError {
     pub line: usize,
     pub column: usize,
     pub message: String