/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! CSS transitions and animations. use context::LayoutContext; use display_list::items::OpaqueNode; use flow::{Flow, GetBaseFlow}; use fxhash::FxHashMap; use ipc_channel::ipc::IpcSender; use msg::constellation_msg::PipelineId; use opaque_node::OpaqueNodeMethods; use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg}; use script_traits::UntrustedNodeAddress; use std::sync::mpsc::Receiver; use style::animation::{Animation, update_style_for_animation}; 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( constellation_chan: &IpcSender, script_chan: &IpcSender, running_animations: &mut FxHashMap>, expired_animations: &mut FxHashMap>, mut newly_transitioning_nodes: Option<&mut Vec>, new_animations_receiver: &Receiver, 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); } } 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(); // 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? let mut keys_to_remove = vec![]; 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 frame, _expired) => { now < started_at + frame.duration }, Animation::Keyframes(_, _, _, ref mut state) => { // This animation is still running, or we need to keep // iterating. now < state.started_at + state.duration || state.tick() }, }; if still_running { animations_still_running.push(running_animation); continue; } if let Animation::Transition(node, _, ref frame, _) = running_animation { script_chan .send(ConstellationControlMsg::TransitionEnd( node.to_untrusted_node_address(), frame.property_animation.property_name().into(), frame.duration, )).unwrap(); } expired_animations .entry(*key) .or_insert_with(Vec::new) .push(running_animation); } if animations_still_running.is_empty() { keys_to_remove.push(*key); } else { *running_animations = animations_still_running } } for key in keys_to_remove { running_animations.remove(&key).unwrap(); } // Add new running animations. for new_running_animation in new_running_animations { 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."); }, } } running_animations .entry(*new_running_animation.node()) .or_insert_with(Vec::new) .push(new_running_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 /// lock held. pub fn recalc_style_for_animations( context: &LayoutContext, flow: &mut Flow, animations: &FxHashMap>, ) where E: TElement, { let mut damage = RestyleDamage::empty(); flow.mutate_fragments(&mut |fragment| { if let Some(ref animations) = animations.get(&fragment.node) { for animation in animations.iter() { let old_style = fragment.style.clone(); update_style_for_animation::( &context.style_context, animation, &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() { recalc_style_for_animations::(context, kid, animations) } }