From b8874ad6acb8a829cd83d9b0484199b4d8c0e261 Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Wed, 6 May 2020 14:57:23 +0200 Subject: [PATCH] Split animations and transitions into separate lists This change splits the list of animations and transitions, which are almost always handled differently. It also renames `ElementAnimationState` to `ElementAnimationSet` and establishes an `AnimationState` for every transition and animation. This allows us to stop continually reallocating lists every time a transition or animation needs to be canceled. Fixes #14419. --- components/layout/animation.rs | 167 +++-- components/layout_thread/lib.rs | 6 +- components/style/animation.rs | 1179 +++++++++++++++---------------- components/style/context.rs | 4 +- components/style/matching.rs | 6 +- 5 files changed, 670 insertions(+), 692 deletions(-) diff --git a/components/layout/animation.rs b/components/layout/animation.rs index c85d962008c..a331b3b3bbc 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -12,10 +12,10 @@ use ipc_channel::ipc::IpcSender; use msg::constellation_msg::PipelineId; use script_traits::UntrustedNodeAddress; use script_traits::{ - AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg, - TransitionOrAnimationEvent, TransitionOrAnimationEventType, + AnimationState as AnimationsPresentState, ConstellationControlMsg, + LayoutMsg as ConstellationMsg, TransitionOrAnimationEvent, TransitionOrAnimationEventType, }; -use style::animation::{Animation, ElementAnimationState}; +use style::animation::{AnimationState, ElementAnimationSet}; /// Processes any new animations that were discovered after style recalculation and /// remove animations for any disconnected nodes. Send messages that trigger events @@ -23,7 +23,7 @@ use style::animation::{Animation, ElementAnimationState}; pub fn do_post_style_animations_update( constellation_chan: &IpcSender, script_chan: &IpcSender, - animation_states: &mut FxHashMap, + animation_states: &mut FxHashMap, pipeline_id: PipelineId, now: f64, out: &mut Vec, @@ -31,16 +31,13 @@ pub fn do_post_style_animations_update( ) { let had_running_animations = animation_states .values() - .any(|state| !state.running_animations.is_empty()); + .any(|state| state.needs_animation_ticks()); cancel_animations_for_disconnected_nodes(animation_states, root_flow); collect_newly_animating_nodes(animation_states, out); - let mut have_running_animations = false; for (node, animation_state) in animation_states.iter_mut() { update_animation_state(script_chan, animation_state, pipeline_id, now, *node); - have_running_animations = - have_running_animations || !animation_state.running_animations.is_empty(); } // Remove empty states from our collection of states in order to free @@ -48,9 +45,12 @@ pub fn do_post_style_animations_update( // a node. animation_states.retain(|_, state| !state.is_empty()); + let have_running_animations = animation_states + .values() + .any(|state| state.needs_animation_ticks()); let present = match (had_running_animations, have_running_animations) { - (true, false) => AnimationState::NoAnimationsPresent, - (false, true) => AnimationState::AnimationsPresent, + (true, false) => AnimationsPresentState::NoAnimationsPresent, + (false, true) => AnimationsPresentState::AnimationsPresent, _ => return, }; constellation_chan @@ -65,14 +65,25 @@ pub fn do_post_style_animations_update( /// forced, synchronous reflows to root DOM nodes for the duration of their /// animations or transitions. pub fn collect_newly_animating_nodes( - animation_states: &FxHashMap, + animation_states: &FxHashMap, out: &mut Vec, ) { // This extends the output vector with an iterator that contains a copy of the node - // address for every new animation. This is a bit goofy, but the script thread - // currently stores a rooted node for every property that is transitioning. + // address for every new animation. The script thread currently stores a rooted node + // for every property that is transitioning. The current strategy of repeating the + // node address is a holdover from when the code here looked different. out.extend(animation_states.iter().flat_map(|(node, state)| { - std::iter::repeat(node.to_untrusted_node_address()).take(state.new_animations.len()) + let mut num_new_animations = state + .animations + .iter() + .filter(|animation| animation.is_new) + .count(); + num_new_animations += state + .transitions + .iter() + .filter(|transition| transition.is_new) + .count(); + std::iter::repeat(node.to_untrusted_node_address()).take(num_new_animations) })); } @@ -81,7 +92,7 @@ pub fn collect_newly_animating_nodes( /// TODO(mrobinson): We should look into a way of doing this during flow tree construction. /// This also doesn't yet handles nodes that have been reparented. pub fn cancel_animations_for_disconnected_nodes( - animation_states: &mut FxHashMap, + animation_states: &mut FxHashMap, root_flow: &mut dyn Flow, ) { // Assume all nodes have been removed until proven otherwise. @@ -106,19 +117,12 @@ pub fn cancel_animations_for_disconnected_nodes( fn update_animation_state( script_channel: &IpcSender, - animation_state: &mut ElementAnimationState, + animation_state: &mut ElementAnimationSet, pipeline_id: PipelineId, now: f64, node: OpaqueNode, ) { - let send_event = |animation: &Animation, event_type, elapsed_time| { - let property_or_animation_name = match *animation { - Animation::Transition(_, _, ref property_animation) => { - property_animation.property_name().into() - }, - Animation::Keyframes(_, _, ref name, _) => name.to_string(), - }; - + let send_event = |property_or_animation_name, event_type, elapsed_time| { script_channel .send(ConstellationControlMsg::TransitionOrAnimationEvent( TransitionOrAnimationEvent { @@ -132,88 +136,83 @@ fn update_animation_state( .unwrap() }; - handle_cancelled_animations(animation_state, now, send_event); - handle_running_animations(animation_state, now, send_event); + handle_canceled_animations(animation_state, now, send_event); + finish_running_animations(animation_state, now, send_event); handle_new_animations(animation_state, send_event); } /// Walk through the list of running animations and remove all of the ones that /// have ended. -fn handle_running_animations( - animation_state: &mut ElementAnimationState, +fn finish_running_animations( + animation_state: &mut ElementAnimationSet, now: f64, - mut send_event: impl FnMut(&Animation, TransitionOrAnimationEventType, f64), + mut send_event: impl FnMut(String, TransitionOrAnimationEventType, f64), ) { - if animation_state.running_animations.is_empty() { - return; + for animation in animation_state.animations.iter_mut() { + if animation.state == AnimationState::Running && animation.has_ended(now) { + animation.state = AnimationState::Finished; + send_event( + animation.name.to_string(), + TransitionOrAnimationEventType::AnimationEnd, + animation.active_duration(), + ); + } } - let mut running_animations = - std::mem::replace(&mut animation_state.running_animations, Vec::new()); - for running_animation in running_animations.drain(..) { - // If the animation is still running, add it back to the list of running animations. - if !running_animation.has_ended(now) { - animation_state.running_animations.push(running_animation); - } else { - let (event_type, elapsed_time) = match running_animation { - Animation::Transition(_, _, ref property_animation) => ( - TransitionOrAnimationEventType::TransitionEnd, - property_animation.duration, - ), - Animation::Keyframes(_, _, _, ref state) => ( - TransitionOrAnimationEventType::AnimationEnd, - state.active_duration(), - ), - }; - - send_event(&running_animation, event_type, elapsed_time); - animation_state.finished_animations.push(running_animation); + for transition in animation_state.transitions.iter_mut() { + if transition.state == AnimationState::Running && transition.has_ended(now) { + transition.state = AnimationState::Finished; + send_event( + transition.property_animation.property_name().into(), + TransitionOrAnimationEventType::TransitionEnd, + transition.property_animation.duration, + ); } } } -/// Send events for cancelled animations. Currently this only handles cancelled -/// transitions, but eventually this should handle cancelled CSS animations as +/// Send events for canceled animations. Currently this only handles canceled +/// transitions, but eventually this should handle canceled CSS animations as /// well. -fn handle_cancelled_animations( - animation_state: &mut ElementAnimationState, +fn handle_canceled_animations( + animation_state: &mut ElementAnimationSet, now: f64, - mut send_event: impl FnMut(&Animation, TransitionOrAnimationEventType, f64), + mut send_event: impl FnMut(String, TransitionOrAnimationEventType, f64), ) { - for animation in animation_state.cancelled_animations.drain(..) { - match animation { - Animation::Transition(_, start_time, _) => { - // TODO(mrobinson): We need to properly compute the elapsed_time here - // according to https://drafts.csswg.org/css-transitions/#event-transitionevent - send_event( - &animation, - TransitionOrAnimationEventType::TransitionCancel, - (now - start_time).max(0.), - ); - }, - // TODO(mrobinson): We should send animationcancel events. - Animation::Keyframes(..) => {}, + for transition in &animation_state.transitions { + if transition.state == AnimationState::Canceled { + // TODO(mrobinson): We need to properly compute the elapsed_time here + // according to https://drafts.csswg.org/css-transitions/#event-transitionevent + send_event( + transition.property_animation.property_name().into(), + TransitionOrAnimationEventType::TransitionCancel, + (now - transition.start_time).max(0.), + ); } } + + // TODO(mrobinson): We need to send animationcancel events. + animation_state.clear_canceled_animations(); } fn handle_new_animations( - animation_state: &mut ElementAnimationState, - mut send_event: impl FnMut(&Animation, TransitionOrAnimationEventType, f64), + animation_state: &mut ElementAnimationSet, + mut send_event: impl FnMut(String, TransitionOrAnimationEventType, f64), ) { - for animation in animation_state.new_animations.drain(..) { - match animation { - Animation::Transition(..) => { - // TODO(mrobinson): We need to properly compute the elapsed_time here - // according to https://drafts.csswg.org/css-transitions/#event-transitionevent - send_event( - &animation, - TransitionOrAnimationEventType::TransitionRun, - 0., - ) - }, - Animation::Keyframes(..) => {}, + for animation in animation_state.animations.iter_mut() { + animation.is_new = false; + } + + for transition in animation_state.transitions.iter_mut() { + if transition.is_new { + // TODO(mrobinson): We need to properly compute the elapsed_time here + // according to https://drafts.csswg.org/css-transitions/#event-transitionevent + send_event( + transition.property_animation.property_name().into(), + TransitionOrAnimationEventType::TransitionRun, + 0., + ); + transition.is_new = false; } - animation_state.running_animations.push(animation); } } diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index dfc3a2966df..ff0b11875f7 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -97,7 +97,7 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Mutex, MutexGuard}; use std::thread; use std::time::Duration; -use style::animation::ElementAnimationState; +use style::animation::ElementAnimationSet; use style::context::SharedStyleContext; use style::context::{QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters}; use style::dom::{ShowSubtree, ShowSubtreeDataAndPrimaryValues, TDocument, TElement, TNode}; @@ -192,7 +192,7 @@ pub struct LayoutThread { document_shared_lock: Option, /// The animation state for all of our nodes. - animation_states: ServoArc>>, + animation_states: ServoArc>>, /// A counter for epoch messages epoch: Cell, @@ -829,7 +829,7 @@ impl LayoutThread { .animation_states .read() .values() - .map(|state| state.running_animations.len()) + .map(|state| state.running_animation_and_transition_count()) .sum(); let _ = sender.send(running_animation_count); }, diff --git a/components/style/animation.rs b/components/style/animation.rs index cde31dc2b3a..e928d81de88 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -26,283 +26,6 @@ use crate::Atom; use servo_arc::Arc; use std::fmt; -/// 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, - /// The original cascade style, needed to compute the generated keyframes of - /// the animation. - pub cascade_style: Arc, -} - -impl KeyframesAnimationState { - /// Given the current time, advances this animation to the next iteration, - /// updates times, and then toggles the direction if appropriate. Otherwise - /// does nothing. - pub fn iterate_if_necessary(&mut self, time: f64) { - if !self.iteration_over(time) { - return; - } - - match self.running_state { - KeyframesRunningState::Paused(_) => return, - KeyframesRunningState::Running => {}, - } - - if let KeyframesIterationState::Finite(ref mut current, max) = self.iteration_state { - // If we are already on the final iteration, just exit now. - // NB: This prevent us from updating the direction, which might be - // needed for the correct handling of animation-fill-mode. - if (max - *current) <= 1.0 { - return; - } - - *current += 1.0; - } - - // Update the next iteration direction if applicable. - // TODO(mrobinson): The duration might now be wrong for floating point iteration counts. - self.started_at += self.duration + self.delay; - match self.direction { - AnimationDirection::Alternate | AnimationDirection::AlternateReverse => { - self.current_direction = match self.current_direction { - AnimationDirection::Normal => AnimationDirection::Reverse, - AnimationDirection::Reverse => AnimationDirection::Normal, - _ => unreachable!(), - }; - }, - _ => {}, - } - } - - fn iteration_over(&self, time: f64) -> bool { - time > (self.started_at + self.duration) - } - - fn has_ended(&self, time: f64) -> bool { - if !self.iteration_over(time) { - return false; - } - - // If we are paused then we haven't ended. - match self.running_state { - KeyframesRunningState::Paused(_) => return false, - KeyframesRunningState::Running => {}, - } - - // If we have a limited number of iterations and we cannot advance to another - // iteration, then we have ended. - return match self.iteration_state { - KeyframesIterationState::Finite(current, max) if (max - current) <= 1.0 => true, - KeyframesIterationState::Finite(..) | KeyframesIterationState::Infinite => false, - }; - } - - /// 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, now: f64) { - 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 = now - (self.duration * progress), - (&mut Paused(ref mut new), Paused(old)) => *new = old, - (&mut Paused(ref mut progress), Running) => { - *progress = (now - 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; - } - - /// Calculate the active-duration of this animation according to - /// https://drafts.csswg.org/css-animations/#active-duration. active-duration - /// is not really meaningful for infinite animations so we just return 0 - /// here in that case. - pub fn active_duration(&self) -> f64 { - match self.iteration_state { - KeyframesIterationState::Finite(_, max) => self.duration * (max as f64), - KeyframesIterationState::Infinite => 0., - } - } -} - -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("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, PropertyAnimation), - - /// 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 { - /// 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 a transition. - #[inline] - pub fn is_transition(&self) -> bool { - match *self { - Animation::Transition(..) => true, - Animation::Keyframes(..) => false, - } - } - - /// Whether or not this animation has ended at the provided time. This does - /// not take into account canceling i.e. when an animation or transition is - /// canceled due to changes in the style. - pub fn has_ended(&self, time: f64) -> bool { - match *self { - Animation::Transition(_, started_at, ref property_animation) => { - time >= started_at + (property_animation.duration) - }, - Animation::Keyframes(_, _, _, ref state) => state.has_ended(time), - } - } - - /// Whether this animation has the same end value as another one. - #[inline] - fn is_transition_with_same_end_value(&self, other_animation: &PropertyAnimation) -> bool { - match *self { - Animation::Transition(_, _, ref animation) => { - animation.has_the_same_end_value_as(other_animation) - }, - Animation::Keyframes(..) => false, - } - } - - /// Whether or not this animation is cancelled by changes from a new style. - fn is_animation_cancelled_in_new_style(&self, new_style: &Arc) -> bool { - let name = match *self { - Animation::Transition(..) => return false, - Animation::Keyframes(_, _, ref name, _) => name, - }; - - let index = new_style - .get_box() - .animation_name_iter() - .position(|animation_name| Some(name) == animation_name.as_atom()); - let index = match index { - Some(index) => index, - None => return true, - }; - - new_style.get_box().animation_duration_mod(index).seconds() == 0. - } -} - /// Represents an animation for a given property. #[derive(Clone, Debug)] pub struct PropertyAnimation { @@ -412,91 +135,490 @@ impl PropertyAnimation { } } -/// Holds the animation state for a particular element. -#[derive(Default)] -pub struct ElementAnimationState { - /// The animations running for this element. - pub running_animations: Vec, - - /// The animations that have finished for this element, but not canceled. These are cleared - /// upon triggering a DOM event for each finished animation. - pub finished_animations: Vec, - - /// The animations that have been cancelled for this element. These are cleared - /// upon triggering a DOM event for each cancelled animation. - pub cancelled_animations: Vec, - - /// New animations created for this element. - pub new_animations: Vec, +/// This structure represents the state of an animation. +#[derive(Clone, Debug, PartialEq)] +pub enum AnimationState { + /// 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 currently running. + Running, + /// This animation has finished. + Finished, + /// This animation has been canceled. + Canceled, } -impl ElementAnimationState { - /// Cancel all animations in this `ElementAnimationState`. This is typically called - /// when the element has been removed from the DOM. - pub fn cancel_all_animations(&mut self) { - self.cancelled_animations.extend( - self.finished_animations - .drain(..) - .chain(self.running_animations.drain(..)) - .chain(self.new_animations.drain(..)), - ); +/// 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. + /// TODO: Everything else in this file uses f64, so perhaps this should + /// be as well. + Finite(f32, f32), +} + +/// A CSS Animation +#[derive(Clone)] +pub struct Animation { + /// The node associated with this animation. + pub node: OpaqueNode, + + /// The name of this animation as defined by the style. + pub name: Atom, + + /// The internal animation from the style system. + pub keyframes_animation: KeyframesAnimation, + + /// 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, + + /// Whether this animation is paused. + pub state: AnimationState, + + /// 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, + + /// The original cascade style, needed to compute the generated keyframes of + /// the animation. + pub cascade_style: Arc, + + /// Whether or not this animation is new and or has already been tracked + /// by the script thread. + pub is_new: bool, +} + +impl Animation { + /// Whether or not this animation is cancelled by changes from a new style. + fn is_cancelled_in_new_style(&self, new_style: &Arc) -> bool { + let index = new_style + .get_box() + .animation_name_iter() + .position(|animation_name| Some(&self.name) == animation_name.as_atom()); + let index = match index { + Some(index) => index, + None => return true, + }; + + new_style.get_box().animation_duration_mod(index).seconds() == 0. } - pub(crate) fn cancel_transitions_with_nontransitioning_properties( - &mut self, - properties_that_transition: &LonghandIdSet, - ) { - if self.running_animations.is_empty() { + /// Given the current time, advances this animation to the next iteration, + /// updates times, and then toggles the direction if appropriate. Otherwise + /// does nothing. + pub fn iterate_if_necessary(&mut self, time: f64) { + if !self.iteration_over(time) { return; } - // TODO(mrobinson): We should make this more efficient perhaps by using - // a linked-list or by using something like `partition`. - let animation_count = self.running_animations.len(); - let mut previously_running_animations = std::mem::replace( - &mut self.running_animations, - Vec::with_capacity(animation_count), - ); - for running_animation in previously_running_animations.drain(..) { - if let Animation::Transition(_, _, ref property_animation) = running_animation { - if !properties_that_transition.contains(property_animation.property_id()) { - self.cancelled_animations.push(running_animation); - continue; - } + // Only iterate animations that are currently running. + if self.state != AnimationState::Running { + return; + } + + if let KeyframesIterationState::Finite(ref mut current, max) = self.iteration_state { + // If we are already on the final iteration, just exit now. + // NB: This prevent us from updating the direction, which might be + // needed for the correct handling of animation-fill-mode. + if (max - *current) <= 1.0 { + return; } - self.running_animations.push(running_animation); + + *current += 1.0; + } + + // Update the next iteration direction if applicable. + // TODO(mrobinson): The duration might now be wrong for floating point iteration counts. + self.started_at += self.duration + self.delay; + match self.direction { + AnimationDirection::Alternate | AnimationDirection::AlternateReverse => { + self.current_direction = match self.current_direction { + AnimationDirection::Normal => AnimationDirection::Reverse, + AnimationDirection::Reverse => AnimationDirection::Normal, + _ => unreachable!(), + }; + }, + _ => {}, } } - fn has_transition_with_same_end_value(&self, property_animation: &PropertyAnimation) -> bool { - if self - .running_animations - .iter() - .any(|animation| animation.is_transition_with_same_end_value(&property_animation)) - { - debug!( - "Running transition found with the same end value for {:?}", - property_animation, - ); - return true; - } - - if self - .finished_animations - .iter() - .any(|animation| animation.is_transition_with_same_end_value(&property_animation)) - { - debug!( - "Expired transition found with the same end value for {:?}", - property_animation, - ); - return true; - } - - false + fn iteration_over(&self, time: f64) -> bool { + time > (self.started_at + self.duration) } - pub(crate) fn apply_new_and_running_animations( + /// Whether or not this animation has finished at the provided time. This does + /// not take into account canceling i.e. when an animation or transition is + /// canceled due to changes in the style. + pub fn has_ended(&self, time: f64) -> bool { + match self.state { + AnimationState::Canceled | AnimationState::Paused(_) => return false, + AnimationState::Finished => return true, + AnimationState::Running => {}, + } + + if !self.iteration_over(time) { + return false; + } + + // If we have a limited number of iterations and we cannot advance to another + // iteration, then we have ended. + return match self.iteration_state { + KeyframesIterationState::Finite(current, max) if (max - current) <= 1.0 => true, + KeyframesIterationState::Finite(..) | KeyframesIterationState::Infinite => false, + }; + } + + /// 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, now: f64) { + use self::AnimationState::*; + + 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_state = self.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.state, old_state) { + (&mut Running, Paused(progress)) => new_started_at = now - (self.duration * progress), + (&mut Paused(ref mut new), Paused(old)) => *new = old, + (&mut Paused(ref mut progress), Running) => { + *progress = (now - old_started_at) / old_duration + }, + // TODO(mrobinson): We should handle the case where a new animation replaces + // a finished one. + (_, Finished) | (Finished, _) => unreachable!("Did not expect Finished animation."), + _ => {}, + } + + // 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; + } + + /// Calculate the active-duration of this animation according to + /// https://drafts.csswg.org/css-animations/#active-duration. active-duration + /// is not really meaningful for infinite animations so we just return 0 + /// here in that case. + pub fn active_duration(&self) -> f64 { + match self.iteration_state { + KeyframesIterationState::Finite(_, max) => self.duration * (max as f64), + KeyframesIterationState::Infinite => 0., + } + } + + /// Update the given style to reflect the values specified by this `Animation` + /// at the time provided by the given `SharedStyleContext`. + fn update_style( + &self, + context: &SharedStyleContext, + style: &mut ComputedValues, + font_metrics_provider: &dyn FontMetricsProvider, + ) where + E: TElement, + { + let duration = self.duration; + let started_at = self.started_at; + + let now = match self.state { + AnimationState::Running => context.current_time_for_animations, + AnimationState::Paused(progress) => started_at + duration * progress, + AnimationState::Canceled | AnimationState::Finished => return, + }; + + debug_assert!(!self.keyframes_animation.steps.is_empty()); + let mut total_progress = (now - started_at) / duration; + if total_progress < 0. { + warn!("Negative progress found for animation {:?}", self.name); + return; + } + if total_progress > 1. { + total_progress = 1.; + } + + // Get the target and the last keyframe position. + let last_keyframe_position; + let target_keyframe_position; + match self.current_direction { + AnimationDirection::Normal => { + target_keyframe_position = self + .keyframes_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 = self + .keyframes_animation + .steps + .iter() + .rev() + .position(|step| total_progress as f32 <= 1. - step.start_percentage.0) + .map(|pos| self.keyframes_animation.steps.len() - pos - 1); + + last_keyframe_position = target_keyframe_position + .and_then(|pos| { + if pos != self.keyframes_animation.steps.len() - 1 { + Some(pos + 1) + } else { + None + } + }) + .unwrap_or(self.keyframes_animation.steps.len() - 1); + }, + _ => unreachable!(), + } + + debug!( + "Animation::update_style: keyframe from {:?} to {:?}", + last_keyframe_position, target_keyframe_position + ); + + let target_keyframe = match target_keyframe_position { + Some(target) => &self.keyframes_animation.steps[target], + None => return, + }; + + let last_keyframe = &self.keyframes_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 self.current_direction { + AnimationDirection::Normal => { + self.started_at + (duration * last_keyframe.start_percentage.0 as f64) + }, + AnimationDirection::Reverse => { + self.started_at + (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, + &self.cascade_style, + font_metrics_provider, + ); + + // NB: The spec says that the timing function can be overwritten + // from the keyframe style. + let timing_function = if last_keyframe.declared_timing_function { + // NB: animation_timing_function can never be empty, always has + // at least the default value (`ease`). + from_style.get_box().animation_timing_function_at(0) + } else { + // TODO(mrobinson): It isn't optimal to have to walk this list every + // time. Perhaps this should be stored in the animation. + let index = match style + .get_box() + .animation_name_iter() + .position(|animation_name| Some(&self.name) == animation_name.as_atom()) + { + Some(index) => index, + None => return warn!("Tried to update a style with a cancelled animation."), + }; + style.get_box().animation_timing_function_mod(index) + }; + + let target_style = compute_style_for_animation_step::( + context, + target_keyframe, + &from_style, + &self.cascade_style, + font_metrics_provider, + ); + + let mut new_style = (*style).clone(); + + for property in self.keyframes_animation.properties_changed.iter() { + debug!( + "Animation::update_style: scanning prop {:?} for animation \"{}\"", + property, self.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!( + "Animation::update_style: got property animation for prop {:?}", + property + ); + debug!("Animation::update_style: {:?}", property_animation); + property_animation.update(&mut new_style, relative_progress); + }, + None => { + debug!( + "Animation::update_style: property animation {:?} not animating", + property + ); + }, + } + } + + debug!( + "Animation::update_style: got style change in animation \"{}\"", + self.name + ); + *style = new_style; + } +} + +impl fmt::Debug for Animation { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Animation") + .field("name", &self.name) + .field("started_at", &self.started_at) + .field("duration", &self.duration) + .field("delay", &self.delay) + .field("iteration_state", &self.iteration_state) + .field("state", &self.state) + .field("direction", &self.direction) + .field("current_direction", &self.current_direction) + .field("cascade_style", &()) + .finish() + } +} + +/// A CSS Transition +#[derive(Clone, Debug)] +pub struct Transition { + /// The node associated with this animation. + pub node: OpaqueNode, + + /// The start time of this transition, which is the current value of the animation + /// timeline when this transition created. + pub start_time: f64, + + /// The internal style `PropertyAnimation` for this transition. + pub property_animation: PropertyAnimation, + + /// The state of this transition. + pub state: AnimationState, + + /// Whether or not this transition is new and or has already been tracked + /// by the script thread. + pub is_new: bool, +} + +impl Transition { + /// Whether or not this animation has ended at the provided time. This does + /// not take into account canceling i.e. when an animation or transition is + /// canceled due to changes in the style. + pub fn has_ended(&self, time: f64) -> bool { + time >= self.start_time + (self.property_animation.duration) + } + + /// Whether this animation has the same end value as another one. + #[inline] + fn has_same_end_value(&self, other_animation: &PropertyAnimation) -> bool { + if self.state == AnimationState::Canceled { + return false; + } + self.property_animation + .has_the_same_end_value_as(other_animation) + } + + /// Update a style to the value specified by this `Transition` given a `SharedStyleContext`. + fn update_style(&self, context: &SharedStyleContext, style: &mut ComputedValues) { + // Never apply canceled transitions to a style. + if self.state == AnimationState::Canceled { + return; + } + + let now = context.current_time_for_animations; + let progress = (now - self.start_time) / (self.property_animation.duration); + let progress = progress.min(1.0); + if progress >= 0.0 { + self.property_animation.update(style, progress); + } + } +} + +/// Holds the animation state for a particular element. +#[derive(Debug, Default)] +pub struct ElementAnimationSet { + /// The animations for this element. + pub animations: Vec, + + /// The transitions for this element. + pub transitions: Vec, +} + +impl ElementAnimationSet { + /// Cancel all animations in this `ElementAnimationSet`. This is typically called + /// when the element has been removed from the DOM. + pub fn cancel_all_animations(&mut self) { + for animation in self.animations.iter_mut() { + animation.state = AnimationState::Canceled; + } + for transition in self.transitions.iter_mut() { + transition.state = AnimationState::Canceled; + } + } + + pub(crate) fn apply_active_animations( &mut self, context: &SharedStyleContext, style: &mut Arc, @@ -504,32 +626,75 @@ impl ElementAnimationState { ) where E: TElement, { - if !self.running_animations.is_empty() { - let style = Arc::make_mut(style); - for animation in self.running_animations.iter_mut() { - update_style_for_animation::(context, animation, style, font_metrics); - } + // Return early to avoid potentially copying the style. + if self.animations.is_empty() && self.transitions.is_empty() { + return; } - if !self.new_animations.is_empty() { - let style = Arc::make_mut(style); - for animation in self.new_animations.iter_mut() { - update_style_for_animation::(context, animation, style, font_metrics); - } + let style = Arc::make_mut(style); + for animation in &self.animations { + animation.update_style::(context, style, font_metrics); + } + + for transition in &self.transitions { + transition.update_style(context, style); } } - /// Whether this `ElementAnimationState` is empty, which means it doesn't + pub(crate) fn clear_finished_animations(&mut self) { + // TODO(mrobinson): This should probably not clear finished animations + // because of `animation-fill-mode`. + self.animations + .retain(|animation| animation.state != AnimationState::Finished); + self.transitions + .retain(|animation| animation.state != AnimationState::Finished); + } + + /// Clear all canceled animations and transitions from this `ElementAnimationSet`. + pub fn clear_canceled_animations(&mut self) { + self.animations + .retain(|animation| animation.state != AnimationState::Canceled); + self.transitions + .retain(|animation| animation.state != AnimationState::Canceled); + } + + /// Whether this `ElementAnimationSet` is empty, which means it doesn't /// hold any animations in any state. pub fn is_empty(&self) -> bool { - self.running_animations.is_empty() && - self.finished_animations.is_empty() && - self.cancelled_animations.is_empty() && - self.new_animations.is_empty() + self.animations.is_empty() && self.transitions.is_empty() } - fn add_new_animation(&mut self, animation: Animation) { - self.new_animations.push(animation); + /// Whether or not this state needs animation ticks for its transitions + /// or animations. New animations don't need ticks until they are no + /// longer marked as new. + pub fn needs_animation_ticks(&self) -> bool { + self.animations + .iter() + .any(|animation| animation.state == AnimationState::Running && !animation.is_new) || + self.transitions.iter().any(|transition| { + transition.state == AnimationState::Running && !transition.is_new + }) + } + + /// The number of running animations and transitions for this `ElementAnimationSet`. + pub fn running_animation_and_transition_count(&self) -> usize { + self.animations + .iter() + .filter(|animation| animation.state == AnimationState::Running) + .count() + + self.transitions + .iter() + .filter(|transition| transition.state == AnimationState::Running) + .count() + } + + fn has_active_transition_or_animation(&self) -> bool { + self.animations + .iter() + .any(|animation| animation.state != AnimationState::Canceled) || + self.transitions + .iter() + .any(|transition| transition.state != AnimationState::Canceled) } /// Update our animations given a new style, canceling or starting new animations @@ -542,27 +707,18 @@ impl ElementAnimationState { ) where E: TElement, { - // Cancel any animations that no longer exist in the style. - // TODO(mrobinson): We should make this more efficient perhaps by using - // a linked-list or by using something like `partition`. - if !self.running_animations.is_empty() { - let animation_count = self.running_animations.len(); - let mut previously_running_animations = std::mem::replace( - &mut self.running_animations, - Vec::with_capacity(animation_count), - ); - for animation in previously_running_animations.drain(..) { - if animation.is_animation_cancelled_in_new_style(new_style) { - self.cancelled_animations.push(animation); - } else { - self.running_animations.push(animation); - } + for animation in self.animations.iter_mut() { + if animation.is_cancelled_in_new_style(new_style) { + animation.state = AnimationState::Canceled; } } maybe_start_animations(element, &context, &new_style, self); - self.iterate_running_animations_if_necessary(context.current_time_for_animations); + // When necessary, iterate our running animations to the next iteration. + for animation in self.animations.iter_mut() { + animation.iterate_if_necessary(context.current_time_for_animations); + } } /// Update our transitions given a new style, canceling or starting new animations @@ -588,13 +744,9 @@ impl ElementAnimationState { // See https://drafts.csswg.org/css-transitions/#starting. We need to clone the // style because this might still be a reference to the original `old_style` and // we want to preserve that so that we can later properly calculate restyle damage. - if self.running_animations.is_empty() || self.new_animations.is_empty() { + if self.has_active_transition_or_animation() { before_change_style = before_change_style.clone(); - self.apply_new_and_running_animations::( - context, - &mut before_change_style, - font_metrics, - ); + self.apply_active_animations::(context, &mut before_change_style, font_metrics); } let transitioning_properties = start_transitions_if_applicable( @@ -604,16 +756,16 @@ impl ElementAnimationState { after_change_style, self, ); - self.cancel_transitions_with_nontransitioning_properties(&transitioning_properties); - } - /// When necessary, iterate our running animations to the next iteration. - pub fn iterate_running_animations_if_necessary(&mut self, time: f64) { - for animation in self.running_animations.iter_mut() { - match *animation { - Animation::Keyframes(_, _, _, ref mut state) => state.iterate_if_necessary(time), - _ => {}, + // Cancel any non-finished transitions that have properties which no longer transition. + for transition in self.transitions.iter_mut() { + if transition.state == AnimationState::Finished { + continue; } + if transitioning_properties.contains(transition.property_animation.property_id()) { + continue; + } + transition.state = AnimationState::Canceled; } } } @@ -625,7 +777,7 @@ pub fn start_transitions_if_applicable( opaque_node: OpaqueNode, old_style: &ComputedValues, new_style: &Arc, - animation_state: &mut ElementAnimationState, + animation_state: &mut ElementAnimationSet, ) -> LonghandIdSet { // If the style of this element is display:none, then we don't start any transitions // and we cancel any currently running transitions by returning an empty LonghandIdSet. @@ -661,7 +813,11 @@ pub fn start_transitions_if_applicable( // transition is the same as that of a transition that's running or // completed. // [1]: https://drafts.csswg.org/css-transitions/#starting - if animation_state.has_transition_with_same_end_value(&property_animation) { + if animation_state + .transitions + .iter() + .any(|transition| transition.has_same_end_value(&property_animation)) + { continue; } @@ -670,11 +826,13 @@ pub fn start_transitions_if_applicable( let box_style = new_style.get_box(); let start_time = context.current_time_for_animations + (box_style.transition_delay_mod(transition.index).seconds() as f64); - animation_state.add_new_animation(Animation::Transition( - opaque_node, + animation_state.transitions.push(Transition { + node: opaque_node, start_time, property_animation, - )); + state: AnimationState::Running, + is_new: true, + }); } properties_that_transition @@ -739,7 +897,7 @@ pub fn maybe_start_animations( element: E, context: &SharedStyleContext, new_style: &Arc, - animation_state: &mut ElementAnimationState, + animation_state: &mut ElementAnimationSet, ) where E: TElement, { @@ -789,218 +947,39 @@ pub fn maybe_start_animations( }, }; - let running_state = match box_style.animation_play_state_mod(i) { - AnimationPlayState::Paused => KeyframesRunningState::Paused(0.), - AnimationPlayState::Running => KeyframesRunningState::Running, + let state = match box_style.animation_play_state_mod(i) { + AnimationPlayState::Paused => AnimationState::Paused(0.), + AnimationPlayState::Running => AnimationState::Running, }; - let new_state = KeyframesAnimationState { + let new_animation = Animation { + node: element.as_node().opaque(), + name: name.clone(), + keyframes_animation: anim.clone(), started_at: animation_start, duration: duration as f64, delay: delay as f64, iteration_state, - running_state, + state, direction: animation_direction, current_direction: initial_direction, cascade_style: new_style.clone(), + is_new: true, }; // If the animation was already present in the list for the node, just update its state. - for existing_animation in animation_state.running_animations.iter_mut() { - match existing_animation { - Animation::Keyframes(_, _, ref old_name, ref mut old_state) - if *old_name == *name => - { - old_state.update_from_other(&new_state, context.current_time_for_animations); - return; - } - _ => {}, + for existing_animation in animation_state.animations.iter_mut() { + if existing_animation.state != AnimationState::Running { + continue; + } + + if new_animation.name == existing_animation.name { + existing_animation + .update_from_other(&new_animation, context.current_time_for_animations); + return; } } - animation_state.add_new_animation(Animation::Keyframes( - element.as_node().opaque(), - anim.clone(), - name.clone(), - new_state, - )); - } -} - -/// Updates a single animation and associated style based on the current time. -pub fn update_style_for_animation( - context: &SharedStyleContext, - animation: &Animation, - style: &mut ComputedValues, - font_metrics_provider: &dyn FontMetricsProvider, -) where - E: TElement, -{ - debug!("update_style_for_animation: {:?}", animation); - match *animation { - Animation::Transition(_, start_time, ref property_animation) => { - let now = context.current_time_for_animations; - let progress = (now - start_time) / (property_animation.duration); - let progress = progress.min(1.0); - if progress >= 0.0 { - property_animation.update(style, progress); - } - }, - 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.current_time_for_animations, - KeyframesRunningState::Paused(progress) => started_at + duration * progress, - }; - - debug_assert!(!animation.steps.is_empty()); - let mut total_progress = (now - started_at) / duration; - if total_progress < 0. { - warn!("Negative progress found for animation {:?}", name); - return; - } - 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, - }; - - 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 + (duration * last_keyframe.start_percentage.0 as f64) - }, - AnimationDirection::Reverse => { - state.started_at + (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, - font_metrics_provider, - ); - - // NB: The spec says that the timing function can be overwritten - // from the keyframe style. - let timing_function = if last_keyframe.declared_timing_function { - // NB: animation_timing_function can never be empty, always has - // at least the default value (`ease`). - from_style.get_box().animation_timing_function_at(0) - } else { - // TODO(mrobinson): It isn't optimal to have to walk this list every - // time. Perhaps this should be stored in the animation. - let index = match style - .get_box() - .animation_name_iter() - .position(|animation_name| Some(name) == animation_name.as_atom()) - { - Some(index) => index, - None => return warn!("Tried to update a style with a cancelled animation."), - }; - style.get_box().animation_timing_function_mod(index) - }; - - let target_style = compute_style_for_animation_step::( - 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(&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; - }, + animation_state.animations.push(new_animation); } } diff --git a/components/style/context.rs b/components/style/context.rs index 03b16ab5171..832fcd10986 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -5,7 +5,7 @@ //! The context within which style is calculated. #[cfg(feature = "servo")] -use crate::animation::ElementAnimationState; +use crate::animation::ElementAnimationSet; use crate::bloom::StyleBloom; use crate::data::{EagerPseudoStyles, ElementData}; #[cfg(feature = "servo")] @@ -167,7 +167,7 @@ pub struct SharedStyleContext<'a> { /// The state of all animations for our styled elements. #[cfg(feature = "servo")] - pub animation_states: Arc>>, + pub animation_states: Arc>>, /// Paint worklets #[cfg(feature = "servo")] diff --git a/components/style/matching.rs b/components/style/matching.rs index 02a7fcfe76f..e01adda303e 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -452,15 +452,15 @@ trait PrivateMatchMethods: TElement { &context.thread_local.font_metrics_provider, ); - animation_state.apply_new_and_running_animations::( + animation_state.apply_active_animations::( shared_context, new_values, &context.thread_local.font_metrics_provider, ); - // If the ElementAnimationState is empty, and don't store it in order to + // If the ElementAnimationSet is empty, and don't store it in order to // save memory and to avoid extra processing later. - animation_state.finished_animations.clear(); + animation_state.clear_finished_animations(); if !animation_state.is_empty() { animation_states.insert(this_opaque, animation_state); }