aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout/animation.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/layout/animation.rs')
-rw-r--r--components/layout/animation.rs308
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)
}
}