diff options
Diffstat (limited to 'components/layout/animation.rs')
-rw-r--r-- | components/layout/animation.rs | 308 |
1 files changed, 168 insertions, 140 deletions
diff --git a/components/layout/animation.rs b/components/layout/animation.rs index 9dc59da1937..79a90f397a7 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -8,157 +8,183 @@ use crate::context::LayoutContext; use crate::display_list::items::OpaqueNode; use crate::flow::{Flow, GetBaseFlow}; use crate::opaque_node::OpaqueNodeMethods; -use crossbeam_channel::Receiver; use fxhash::{FxHashMap, FxHashSet}; use ipc_channel::ipc::IpcSender; use msg::constellation_msg::PipelineId; use script_traits::UntrustedNodeAddress; -use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg}; -use style::animation::{update_style_for_animation, Animation}; +use script_traits::{ + AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg, TransitionEventType, +}; +use servo_arc::Arc; +use style::animation::{ + update_style_for_animation, Animation, ElementAnimationState, PropertyAnimation, +}; use style::dom::TElement; use style::font_metrics::ServoMetricsProvider; use style::selector_parser::RestyleDamage; use style::timer::Timer; -/// 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<E>( +/// Collect newly transitioning nodes, which is used by the script process during +/// forced, synchronous reflows to root DOM nodes for the duration of their transitions. +pub fn collect_newly_transitioning_nodes( + animation_states: &FxHashMap<OpaqueNode, ElementAnimationState>, + mut out: Option<&mut Vec<UntrustedNodeAddress>>, +) { + // 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. + if let Some(ref mut out) = out { + out.extend(animation_states.iter().flat_map(|(node, state)| { + let num_transitions = state + .new_animations + .iter() + .filter(|animation| animation.is_transition()) + .count(); + std::iter::repeat(node.to_untrusted_node_address()).take(num_transitions) + })); + } +} + +/// Processes any new animations that were discovered after style recalculation. Also +/// finish any animations that have completed, inserting them into `finished_animations`. +pub fn update_animation_states( constellation_chan: &IpcSender<ConstellationMsg>, script_chan: &IpcSender<ConstellationControlMsg>, - running_animations: &mut FxHashMap<OpaqueNode, Vec<Animation>>, - expired_animations: &mut FxHashMap<OpaqueNode, Vec<Animation>>, - mut keys_to_remove: FxHashSet<OpaqueNode>, - mut newly_transitioning_nodes: Option<&mut Vec<UntrustedNodeAddress>>, - new_animations_receiver: &Receiver<Animation>, + animation_states: &mut FxHashMap<OpaqueNode, ElementAnimationState>, + invalid_nodes: FxHashSet<OpaqueNode>, pipeline_id: PipelineId, timer: &Timer, -) where - E: TElement, -{ - let mut new_running_animations = vec![]; - while let Ok(animation) = new_animations_receiver.try_recv() { - 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 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, timer); - should_push = false; - break; - } - } - } - } - } - - if should_push { - new_running_animations.push(animation); +) { + let had_running_animations = animation_states + .values() + .any(|state| !state.running_animations.is_empty()); + + // Cancel all animations on any invalid nodes. These entries will later + // be removed from the list of states, because their states will become + // empty. + for node in &invalid_nodes { + if let Some(mut state) = animation_states.remove(node) { + state.cancel_all_animations(); } } - 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 = timer.seconds(); + 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(); } - let now = timer.seconds(); - // Expire old running animations. - // - // 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? - for (key, running_animations) in running_animations.iter_mut() { - let mut animations_still_running = vec![]; - for mut running_animation in running_animations.drain(..) { - let still_running = !running_animation.is_expired() && - match running_animation { - Animation::Transition(_, started_at, ref property_animation) => { - now < started_at + (property_animation.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() - }, - }; - - debug!( - "update_animation_state({:?}): {:?}", - still_running, running_animation - ); + // Remove empty states from our collection of states in order to free + // up space as soon as we are no longer tracking any animations for + // a node. + animation_states.retain(|_, state| !state.is_empty()); - if still_running { - animations_still_running.push(running_animation); - continue; - } + let present = match (had_running_animations, have_running_animations) { + (true, false) => AnimationState::NoAnimationsPresent, + (false, true) => AnimationState::AnimationsPresent, + _ => return, + }; + constellation_chan + .send(ConstellationMsg::ChangeRunningAnimationsState( + pipeline_id, + present, + )) + .unwrap(); +} - if let Animation::Transition(node, _, ref property_animation) = running_animation { - script_chan - .send(ConstellationControlMsg::TransitionEnd( - node.to_untrusted_node_address(), - property_animation.property_name().into(), - property_animation.duration, - )) - .unwrap(); - } +pub fn update_animation_state( + script_channel: &IpcSender<ConstellationControlMsg>, + animation_state: &mut ElementAnimationState, + pipeline_id: PipelineId, + now: f64, + node: OpaqueNode, +) { + let send_transition_event = |property_animation: &PropertyAnimation, event_type| { + script_channel + .send(ConstellationControlMsg::TransitionEvent { + pipeline_id, + event_type, + node: node.to_untrusted_node_address(), + property_name: property_animation.property_name().into(), + elapsed_time: property_animation.duration, + }) + .unwrap() + }; - debug!("expiring animation for {:?}", running_animation); - expired_animations - .entry(*key) - .or_insert_with(Vec::new) - .push(running_animation); - } + handle_cancelled_animations(animation_state, send_transition_event); + handle_running_animations(animation_state, now, send_transition_event); + handle_new_animations(animation_state, send_transition_event); +} + +/// Walk through the list of running animations and remove all of the ones that +/// have ended. +pub fn handle_running_animations( + animation_state: &mut ElementAnimationState, + now: f64, + mut send_transition_event: impl FnMut(&PropertyAnimation, TransitionEventType), +) { + let mut running_animations = + std::mem::replace(&mut animation_state.running_animations, Vec::new()); + for mut running_animation in running_animations.drain(..) { + let still_running = !running_animation.is_expired() && + match running_animation { + Animation::Transition(_, started_at, ref property_animation) => { + now < started_at + (property_animation.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 animations_still_running.is_empty() { - keys_to_remove.insert(*key); + // If the animation is still running, add it back to the list of running animations. + if still_running { + animation_state.running_animations.push(running_animation); } else { - *running_animations = animations_still_running + debug!("Finishing transition: {:?}", running_animation); + if let Animation::Transition(_, _, ref property_animation) = running_animation { + send_transition_event(property_animation, TransitionEventType::TransitionEnd); + } + animation_state.finished_animations.push(running_animation); } } +} - for key in keys_to_remove { - running_animations.remove(&key).unwrap(); +/// Send events for cancelled animations. Currently this only handles cancelled +/// transitions, but eventually this should handle cancelled CSS animations as +/// well. +pub fn handle_cancelled_animations( + animation_state: &mut ElementAnimationState, + mut send_transition_event: impl FnMut(&PropertyAnimation, TransitionEventType), +) { + for animation in animation_state.cancelled_animations.drain(..) { + match animation { + Animation::Transition(_, _, ref property_animation) => { + send_transition_event(property_animation, TransitionEventType::TransitionCancel) + }, + Animation::Keyframes(..) => { + warn!("Got unexpected animation in finished transitions list.") + }, + } } +} - // Add new running animations. - for new_running_animation in new_running_animations { - if new_running_animation.is_transition() { - match newly_transitioning_nodes { - Some(ref mut nodes) => { - nodes.push(new_running_animation.node().to_untrusted_node_address()); - }, - None => { - warn!("New transition encountered from compositor-initiated layout."); - }, - } +pub fn handle_new_animations( + animation_state: &mut ElementAnimationState, + mut send_transition_event: impl FnMut(&PropertyAnimation, TransitionEventType), +) { + for animation in animation_state.new_animations.drain(..) { + match animation { + Animation::Transition(_, _, ref property_animation) => { + send_transition_event(property_animation, TransitionEventType::TransitionRun) + }, + Animation::Keyframes(..) => {}, } - - running_animations - .entry(*new_running_animation.node()) - .or_insert_with(Vec::new) - .push(new_running_animation) + animation_state.running_animations.push(animation); } - - let animation_state = if running_animations.is_empty() { - AnimationState::NoAnimationsPresent - } else { - 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 @@ -167,46 +193,48 @@ pub fn update_animation_state<E>( pub fn recalc_style_for_animations<E>( context: &LayoutContext, flow: &mut dyn Flow, - animations: &FxHashMap<OpaqueNode, Vec<Animation>>, + animation_states: &FxHashMap<OpaqueNode, ElementAnimationState>, ) -> FxHashSet<OpaqueNode> where E: TElement, { - let mut invalid_nodes = animations.keys().cloned().collect(); - do_recalc_style_for_animations::<E>(context, flow, animations, &mut invalid_nodes); + let mut invalid_nodes = animation_states.keys().cloned().collect(); + do_recalc_style_for_animations::<E>(context, flow, animation_states, &mut invalid_nodes); invalid_nodes } fn do_recalc_style_for_animations<E>( context: &LayoutContext, flow: &mut dyn Flow, - animations: &FxHashMap<OpaqueNode, Vec<Animation>>, + animation_states: &FxHashMap<OpaqueNode, ElementAnimationState>, invalid_nodes: &mut FxHashSet<OpaqueNode>, ) where E: TElement, { let mut damage = RestyleDamage::empty(); flow.mutate_fragments(&mut |fragment| { - if let Some(ref animations) = animations.get(&fragment.node) { - invalid_nodes.remove(&fragment.node); - for animation in animations.iter() { - let old_style = fragment.style.clone(); - update_style_for_animation::<E>( - &context.style_context, - animation, - &mut fragment.style, - &ServoMetricsProvider, - ); - let difference = - RestyleDamage::compute_style_difference(&old_style, &fragment.style); - damage |= difference.damage; - } + let animations = match animation_states.get(&fragment.node) { + Some(state) => &state.running_animations, + None => return, + }; + + invalid_nodes.remove(&fragment.node); + for animation in animations.iter() { + let old_style = fragment.style.clone(); + update_style_for_animation::<E>( + &context.style_context, + animation, + Arc::make_mut(&mut fragment.style), + &ServoMetricsProvider, + ); + let difference = RestyleDamage::compute_style_difference(&old_style, &fragment.style); + damage |= difference.damage; } }); let base = flow.mut_base(); base.restyle_damage.insert(damage); for kid in base.children.iter_mut() { - do_recalc_style_for_animations::<E>(context, kid, animations, invalid_nodes) + do_recalc_style_for_animations::<E>(context, kid, animation_states, invalid_nodes) } } |