servo/components/style/animation.rs
author Bastien Orivel <eijebong@bananium.fr>
Wed, 07 Nov 2018 19:48:07 +0100
changeset 508481 eeef84fe603dacc8421c14ac951cffe569c8c815
parent 508475 f038d9fa2026c01bc7a76ba799c9a1fcc16e67ae
child 508482 bc9324a80fac8d94452ee278c2332f23ce04fbdf
permissions -rw-r--r--
Bug 1511811 - Update crossbeam-channel to 0.3. Cherry-picks https://github.com/servo/servo/pull/22142

/* 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 https://mozilla.org/MPL/2.0/. */

//! CSS transitions and animations.

// NOTE(emilio): This code isn't really executed in Gecko, but we don't want to
// compile it out so that people remember it exists, thus the cfg'd Sender
// import.

use crate::bezier::Bezier;
use crate::context::SharedStyleContext;
use crate::dom::{OpaqueNode, TElement};
use crate::font_metrics::FontMetricsProvider;
use crate::properties::animated_properties::AnimatedProperty;
use crate::properties::longhands::animation_direction::computed_value::single_value::T as AnimationDirection;
use crate::properties::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState;
use crate::properties::{self, CascadeMode, ComputedValues, LonghandId};
use crate::rule_tree::CascadeLevel;
use crate::stylesheets::keyframes_rule::{KeyframesAnimation, KeyframesStep, KeyframesStepValue};
use crate::timer::Timer;
use crate::values::computed::box_::TransitionProperty;
use crate::values::computed::Time;
use crate::values::computed::TimingFunction;
use crate::values::generics::box_::AnimationIterationCount;
use crate::values::generics::easing::{StepPosition, TimingFunction as GenericTimingFunction};
use crate::Atom;
use crossbeam_channel::Sender;
use servo_arc::Arc;
use std::fmt;
#[cfg(feature = "gecko")]
use std::sync::mpsc::Sender;

/// 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(Clone, Debug)]
pub enum KeyframesIterationState {
    /// Infinite iterations, so no need to track a state.
    Infinite,
    /// Current and max iterations.
    Finite(f32, f32),
}

/// This structure represents wether an animation is actually running.
///
/// An animation can be running, or paused at a given time.
#[derive(Clone, Debug)]
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(Clone)]
pub struct KeyframesAnimationState {
    /// 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<ComputedValues>,
}

impl KeyframesAnimationState {
    /// 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");
        debug_assert!(!self.expired);

        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.0;
            // 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::AlternateReverse => {
                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, timer: &Timer) {
        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 = timer.seconds() - (self.duration * progress)
            },
            (&mut Paused(ref mut new), Paused(old)) => *new = old,
            (&mut Paused(ref mut progress), Running) => {
                *progress = (timer.seconds() - 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,
        }
    }
}

impl fmt::Debug for KeyframesAnimationState {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("KeyframesAnimationState")
            .field("started_at", &self.started_at)
            .field("duration", &self.duration)
            .field("delay", &self.delay)
            .field("iteration_state", &self.iteration_state)
            .field("running_state", &self.running_state)
            .field("direction", &self.direction)
            .field("current_direction", &self.current_direction)
            .field("expired", &self.expired)
            .field("cascade_style", &())
            .finish()
    }
}

/// State relating to an animation.
#[derive(Clone, Debug)]
pub enum Animation {
    /// 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()`.
    Transition(OpaqueNode, f64, AnimationFrame),
    /// A keyframes animation is identified by a name, and can have a
    /// node-dependent state (i.e. iteration count, etc.).
    ///
    /// TODO(emilio): The animation object could be refcounted.
    Keyframes(
        OpaqueNode,
        KeyframesAnimation,
        Atom,
        KeyframesAnimationState,
    ),
}

impl Animation {
    /// Whether this animation is expired.
    #[inline]
    pub fn is_expired(&self) -> bool {
        match *self {
            Animation::Transition(..) => false,
            Animation::Keyframes(_, _, _, ref state) => state.expired,
        }
    }

    /// The opaque node that owns the animation.
    #[inline]
    pub fn node(&self) -> &OpaqueNode {
        match *self {
            Animation::Transition(ref node, _, _) => node,
            Animation::Keyframes(ref node, _, _, _) => node,
        }
    }

    /// Whether this animation is paused. A transition can never be paused.
    #[inline]
    pub fn is_paused(&self) -> bool {
        match *self {
            Animation::Transition(..) => false,
            Animation::Keyframes(_, _, _, ref state) => state.is_paused(),
        }
    }

    /// Whether this animation is a transition.
    #[inline]
    pub fn is_transition(&self) -> bool {
        match *self {
            Animation::Transition(..) => true,
            Animation::Keyframes(..) => false,
        }
    }
}

/// A single animation frame of a single property.
#[derive(Clone, Debug)]
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,
}

/// Represents an animation for a given property.
#[derive(Clone, Debug)]
pub struct PropertyAnimation {
    property: AnimatedProperty,
    timing_function: TimingFunction,
    duration: Time, // TODO: isn't this just repeated?
}

impl PropertyAnimation {
    /// Returns the given property name.
    pub fn property_name(&self) -> &'static str {
        self.property.name()
    }

    /// 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: &ComputedValues,
        new_style: &mut ComputedValues,
    ) -> Vec<PropertyAnimation> {
        let mut result = vec![];
        let box_style = new_style.get_box();
        let transition_property = box_style.transition_property_at(transition_index);
        let timing_function = box_style.transition_timing_function_mod(transition_index);
        let duration = box_style.transition_duration_mod(transition_index);

        match transition_property {
            TransitionProperty::Custom(..) | TransitionProperty::Unsupported(..) => result,
            TransitionProperty::Shorthand(ref shorthand_id) => shorthand_id
                .longhands()
                .filter_map(|longhand| {
                    PropertyAnimation::from_longhand(
                        longhand,
                        timing_function,
                        duration,
                        old_style,
                        new_style,
                    )
                })
                .collect(),
            TransitionProperty::Longhand(longhand_id) => {
                let animation = PropertyAnimation::from_longhand(
                    longhand_id,
                    timing_function,
                    duration,
                    old_style,
                    new_style,
                );

                if let Some(animation) = animation {
                    result.push(animation);
                }
                result
            },
        }
    }

    fn from_longhand(
        longhand: LonghandId,
        timing_function: TimingFunction,
        duration: Time,
        old_style: &ComputedValues,
        new_style: &ComputedValues,
    ) -> Option<PropertyAnimation> {
        let animated_property = AnimatedProperty::from_longhand(longhand, old_style, new_style)?;

        let property_animation = PropertyAnimation {
            property: animated_property,
            timing_function: timing_function,
            duration: duration,
        };

        if property_animation.does_animate() {
            Some(property_animation)
        } else {
            None
        }
    }

    /// Update the given animation at a given point of progress.
    pub fn update(&self, style: &mut ComputedValues, time: f64) {
        let epsilon = 1. / (200. * (self.duration.seconds() as f64));
        let progress = match self.timing_function {
            GenericTimingFunction::CubicBezier { x1, y1, x2, y2 } => {
                Bezier::new(x1, y1, x2, y2).solve(time, epsilon)
            },
            GenericTimingFunction::Steps(steps, pos) => {
                let mut current_step = (time * (steps as f64)).floor() as i32;

                if pos == StepPosition::Start ||
                    pos == StepPosition::JumpStart ||
                    pos == StepPosition::JumpBoth
                {
                    current_step = current_step + 1;
                }

                // FIXME: We should update current_step according to the "before flag".
                // In order to get the before flag, we have to know the current animation phase
                // and whether the iteration is reversed. For now, we skip this calculation.
                // (i.e. Treat before_flag is unset,)
                // https://drafts.csswg.org/css-easing/#step-timing-function-algo

                if time >= 0.0 && current_step < 0 {
                    current_step = 0;
                }

                let jumps = match pos {
                    StepPosition::JumpBoth => steps + 1,
                    StepPosition::JumpNone => steps - 1,
                    StepPosition::JumpStart |
                    StepPosition::JumpEnd |
                    StepPosition::Start |
                    StepPosition::End => steps,
                };

                if time <= 1.0 && current_step > jumps {
                    current_step = jumps;
                }

                (current_step as f64) / (jumps as f64)
            },
            GenericTimingFunction::Keyword(keyword) => {
                let (x1, x2, y1, y2) = keyword.to_bezier();
                Bezier::new(x1, x2, y1, y2).solve(time, epsilon)
            },
        };

        self.property.update(style, progress);
    }

    #[inline]
    fn does_animate(&self) -> bool {
        self.property.does_animate() && self.duration.seconds() != 0.0
    }

    /// Whether this animation has the same end value as another one.
    #[inline]
    pub fn has_the_same_end_value_as(&self, other: &Self) -> bool {
        self.property.has_the_same_end_value_as(&other.property)
    }
}

/// 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(
    new_animations_sender: &Sender<Animation>,
    opaque_node: OpaqueNode,
    old_style: &ComputedValues,
    new_style: &mut Arc<ComputedValues>,
    timer: &Timer,
    possibly_expired_animations: &[PropertyAnimation],
) -> bool {
    let mut had_animations = false;
    for i in 0..new_style.get_box().transition_property_count() {
        // Create any property animations, if applicable.
        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.
            //
            // NB: get_mut is guaranteed to succeed since we called make_mut()
            // above.
            property_animation.update(Arc::get_mut(new_style).unwrap(), 0.0);

            // Per [1], don't trigger a new transition if the end state for that
            // transition is the same as that of a transition that's already
            // running on the same node.
            //
            // [1]: https://drafts.csswg.org/css-transitions/#starting
            if possibly_expired_animations
                .iter()
                .any(|animation| animation.has_the_same_end_value_as(&property_animation))
            {
                debug!(
                    "Not initiating transition for {}, other transition \
                     found with the same end value",
                    property_animation.property_name()
                );
                continue;
            }

            debug!("Kicking off transition of {:?}", property_animation);

            // Kick off the animation.
            let box_style = new_style.get_box();
            let now = timer.seconds();
            let start_time = now + (box_style.transition_delay_mod(i).seconds() as f64);
            new_animations_sender
                .send(Animation::Transition(
                    opaque_node,
                    start_time,
                    AnimationFrame {
                        duration: box_style.transition_duration_mod(i).seconds() as f64,
                        property_animation,
                    },
                ))
                .unwrap();

            had_animations = true;
        }
    }

    had_animations
}

fn compute_style_for_animation_step<E>(
    context: &SharedStyleContext,
    step: &KeyframesStep,
    previous_style: &ComputedValues,
    style_from_cascade: &Arc<ComputedValues>,
    font_metrics_provider: &FontMetricsProvider,
) -> Arc<ComputedValues>
where
    E: TElement,
{
    match step.value {
        KeyframesStepValue::ComputedValues => style_from_cascade.clone(),
        KeyframesStepValue::Declarations {
            block: ref declarations,
        } => {
            let guard = declarations.read_with(context.guards.author);

            let iter = || {
                // It's possible to have !important properties in keyframes
                // so we have to filter them out.
                // See the spec issue https://github.com/w3c/csswg-drafts/issues/1824
                // Also we filter our non-animatable properties.
                guard
                    .normal_declaration_iter()
                    .filter(|declaration| declaration.is_animatable())
                    .map(|decl| (decl, CascadeLevel::Animations))
            };

            // This currently ignores visited styles, which seems acceptable,
            // as existing browsers don't appear to animate visited styles.
            let computed = properties::apply_declarations::<E, _, _>(
                context.stylist.device(),
                /* pseudo = */ None,
                previous_style.rules(),
                &context.guards,
                iter,
                Some(previous_style),
                Some(previous_style),
                Some(previous_style),
                font_metrics_provider,
                CascadeMode::Unvisited {
                    visited_rules: None,
                },
                context.quirks_mode(),
                /* rule_cache = */ None,
                &mut Default::default(),
                /* element = */ None,
            );
            computed
        },
    }
}

/// Triggers animations for a given node looking at the animation property
/// values.
pub fn maybe_start_animations<E>(
    element: E,
    context: &SharedStyleContext,
    new_animations_sender: &Sender<Animation>,
    node: OpaqueNode,
    new_style: &Arc<ComputedValues>,
) -> bool
where
    E: TElement,
{
    let mut had_animations = false;

    let box_style = new_style.get_box();
    for (i, name) in box_style.animation_name_iter().enumerate() {
        let name = match name.as_atom() {
            Some(atom) => atom,
            None => continue,
        };

        debug!("maybe_start_animations: name={}", name);
        let total_duration = box_style.animation_duration_mod(i).seconds();
        if total_duration == 0. {
            continue;
        }

        let anim = match context.stylist.get_animation(name, element) {
            Some(animation) => animation,
            None => continue,
        };

        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_mod(i).seconds();
        let now = context.timer.seconds();
        let animation_start = now + delay as f64;
        let duration = box_style.animation_duration_mod(i).seconds();
        let iteration_state = match box_style.animation_iteration_count_mod(i) {
            AnimationIterationCount::Infinite => KeyframesIterationState::Infinite,
            AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0.0, n),
        };

        let animation_direction = box_style.animation_direction_mod(i);

        let initial_direction = match animation_direction {
            AnimationDirection::Normal | AnimationDirection::Alternate => {
                AnimationDirection::Normal
            },
            AnimationDirection::Reverse | AnimationDirection::AlternateReverse => {
                AnimationDirection::Reverse
            },
        };

        let running_state = match box_style.animation_play_state_mod(i) {
            AnimationPlayState::Paused => KeyframesRunningState::Paused(0.),
            AnimationPlayState::Running => KeyframesRunningState::Running,
        };

        new_animations_sender
            .send(Animation::Keyframes(
                node,
                anim.clone(),
                name.clone(),
                KeyframesAnimationState {
                    started_at: animation_start,
                    duration: duration as f64,
                    delay: delay as f64,
                    iteration_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 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(
    mut new_style: &mut Arc<ComputedValues>,
    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 false;
    }

    frame
        .property_animation
        .update(Arc::make_mut(&mut new_style), progress);

    true
}

/// Returns the kind of animation update that happened.
pub enum AnimationUpdate {
    /// The style was successfully updated, the animation is still running.
    Regular,
    /// A style change canceled this animation.
    AnimationCanceled,
}

/// Updates a single animation and associated style based on the current time.
///
/// FIXME(emilio): This doesn't handle any kind of dynamic change to the
/// animation or transition properties in any reasonable way.
///
/// This should probably be split in two, one from updating animations and
/// transitions in response to a style change (that is,
/// consider_starting_transitions + maybe_start_animations, but handling
/// canceled animations, duration changes, etc, there instead of here), and this
/// function should be only about the style update in response of a transition.
pub fn update_style_for_animation<E>(
    context: &SharedStyleContext,
    animation: &Animation,
    style: &mut Arc<ComputedValues>,
    font_metrics_provider: &FontMetricsProvider,
) -> AnimationUpdate
where
    E: TElement,
{
    debug!("update_style_for_animation: {:?}", animation);
    debug_assert!(!animation.is_expired());

    match *animation {
        Animation::Transition(_, start_time, ref frame) => {
            let now = context.timer.seconds();
            let mut new_style = (*style).clone();
            let updated_style =
                update_style_for_animation_frame(&mut new_style, now, start_time, frame);
            if updated_style {
                *style = new_style
            }
            // FIXME(emilio): Should check before updating the style that the
            // transition_property still transitions this, or bail out if not.
            //
            // Or doing it in process_animations, only if transition_property
            // changed somehow (even better).
            AnimationUpdate::Regular
        },
        Animation::Keyframes(_, ref animation, ref name, ref state) => {
            let duration = state.duration;
            let started_at = state.started_at;

            let now = match state.running_state {
                KeyframesRunningState::Running => context.timer.seconds(),
                KeyframesRunningState::Paused(progress) => started_at + duration * progress,
            };

            debug_assert!(!animation.steps.is_empty());

            let maybe_index = style
                .get_box()
                .animation_name_iter()
                .position(|animation_name| Some(name) == animation_name.as_atom());

            let index = match maybe_index {
                Some(index) => index,
                None => return AnimationUpdate::AnimationCanceled,
            };

            let total_duration = style.get_box().animation_duration_mod(index).seconds() as f64;
            if total_duration == 0. {
                return AnimationUpdate::AnimationCanceled;
            }

            let mut total_progress = (now - started_at) / total_duration;
            if total_progress < 0. {
                warn!("Negative progress found for animation {:?}", name);
                return AnimationUpdate::Regular;
            }
            if total_progress > 1. {
                total_progress = 1.;
            }

            // 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 => return AnimationUpdate::Regular,
            };

            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::<E>(
                context,
                last_keyframe,
                &**style,
                &state.cascade_style,
                font_metrics_provider,
            );

            // NB: The spec says that the timing function can be overwritten
            // from the keyframe style.
            let mut timing_function = style.get_box().animation_timing_function_mod(index);
            if last_keyframe.declared_timing_function {
                // NB: animation_timing_function can never be empty, always has
                // at least the default value (`ease`).
                timing_function = from_style.get_box().animation_timing_function_at(0);
            }

            let target_style = compute_style_for_animation_step::<E>(
                context,
                target_keyframe,
                &from_style,
                &state.cascade_style,
                font_metrics_provider,
            );

            let mut new_style = (*style).clone();

            for property in animation.properties_changed.iter() {
                debug!(
                    "update_style_for_animation: scanning prop {:?} for animation \"{}\"",
                    property, name
                );
                let animation = PropertyAnimation::from_longhand(
                    property,
                    timing_function,
                    Time::from_seconds(relative_duration as f32),
                    &from_style,
                    &target_style,
                );

                match animation {
                    Some(property_animation) => {
                        debug!(
                            "update_style_for_animation: got property animation for prop {:?}",
                            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",
                            property
                        );
                    },
                }
            }

            debug!(
                "update_style_for_animation: got style change in animation \"{}\"",
                name
            );
            *style = new_style;
            AnimationUpdate::Regular
        },
    }
}

/// Update the style in the node when it finishes.
#[cfg(feature = "servo")]
pub fn complete_expired_transitions(
    node: OpaqueNode,
    style: &mut Arc<ComputedValues>,
    context: &SharedStyleContext,
) -> bool {
    let had_animations_to_expire;
    {
        let all_expired_animations = context.expired_animations.read();
        let animations_to_expire = all_expired_animations.get(&node);
        had_animations_to_expire = animations_to_expire.is_some();
        if let Some(ref animations) = animations_to_expire {
            for animation in *animations {
                debug!("Updating expired animation {:?}", animation);
                // TODO: support animation-fill-mode
                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().remove(&node);
    }

    had_animations_to_expire
}