1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 //! CSS transitions and animations.
6
7 use context::LayoutContext;
8 use flow::{Flow, GetBaseFlow};
9 use fnv::FnvHashMap;
10 use gfx::display_list::OpaqueNode;
11 use ipc_channel::ipc::IpcSender;
12 use msg::constellation_msg::PipelineId;
13 use opaque_node::OpaqueNodeMethods;
14 use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg};
15 use script_traits::UntrustedNodeAddress;
16 use std::sync::mpsc::Receiver;
17 use style::animation::{Animation, update_style_for_animation};
18 use style::dom::TElement;
19 use style::font_metrics::ServoMetricsProvider;
20 use style::selector_parser::RestyleDamage;
21 use style::timer::Timer;
22
23 /// Processes any new animations that were discovered after style recalculation.
24 /// Also expire any old animations that have completed, inserting them into
25 /// `expired_animations`.
update_animation_state<E>( constellation_chan: &IpcSender<ConstellationMsg>, script_chan: &IpcSender<ConstellationControlMsg>, running_animations: &mut FnvHashMap<OpaqueNode, Vec<Animation>>, expired_animations: &mut FnvHashMap<OpaqueNode, Vec<Animation>>, mut newly_transitioning_nodes: Option<&mut Vec<UntrustedNodeAddress>>, new_animations_receiver: &Receiver<Animation>, pipeline_id: PipelineId, timer: &Timer, ) where E: TElement,26 pub fn update_animation_state<E>(
27 constellation_chan: &IpcSender<ConstellationMsg>,
28 script_chan: &IpcSender<ConstellationControlMsg>,
29 running_animations: &mut FnvHashMap<OpaqueNode, Vec<Animation>>,
30 expired_animations: &mut FnvHashMap<OpaqueNode, Vec<Animation>>,
31 mut newly_transitioning_nodes: Option<&mut Vec<UntrustedNodeAddress>>,
32 new_animations_receiver: &Receiver<Animation>,
33 pipeline_id: PipelineId,
34 timer: &Timer,
35 )
36 where
37 E: TElement,
38 {
39 let mut new_running_animations = vec![];
40 while let Ok(animation) = new_animations_receiver.try_recv() {
41 let mut should_push = true;
42 if let Animation::Keyframes(ref node, ref name, ref state) = animation {
43 // If the animation was already present in the list for the
44 // node, just update its state, else push the new animation to
45 // run.
46 if let Some(ref mut animations) = running_animations.get_mut(node) {
47 // TODO: This being linear is probably not optimal.
48 for anim in animations.iter_mut() {
49 if let Animation::Keyframes(_, ref anim_name, ref mut anim_state) = *anim {
50 if *name == *anim_name {
51 debug!("update_animation_state: Found other animation {}", name);
52 anim_state.update_from_other(&state, timer);
53 should_push = false;
54 break;
55 }
56 }
57 }
58 }
59 }
60
61 if should_push {
62 new_running_animations.push(animation);
63 }
64 }
65
66 if running_animations.is_empty() && new_running_animations.is_empty() {
67 // Nothing to do. Return early so we don't flood the compositor with
68 // `ChangeRunningAnimationsState` messages.
69 return
70 }
71
72 let now = timer.seconds();
73 // Expire old running animations.
74 //
75 // TODO: Do not expunge Keyframes animations, since we need that state if
76 // the animation gets re-triggered. Probably worth splitting in two
77 // different maps, or at least using a linked list?
78 let mut keys_to_remove = vec![];
79 for (key, running_animations) in running_animations.iter_mut() {
80 let mut animations_still_running = vec![];
81 for mut running_animation in running_animations.drain(..) {
82 let still_running = !running_animation.is_expired() && match running_animation {
83 Animation::Transition(_, started_at, ref frame, _expired) => {
84 now < started_at + frame.duration
85 }
86 Animation::Keyframes(_, _, ref mut state) => {
87 // This animation is still running, or we need to keep
88 // iterating.
89 now < state.started_at + state.duration || state.tick()
90 }
91 };
92
93 if still_running {
94 animations_still_running.push(running_animation);
95 continue
96 }
97
98 if let Animation::Transition(node, _, ref frame, _) = running_animation {
99 script_chan.send(ConstellationControlMsg::TransitionEnd(node.to_untrusted_node_address(),
100 frame.property_animation
101 .property_name().into(),
102 frame.duration))
103 .unwrap();
104 }
105
106 expired_animations.entry(*key)
107 .or_insert_with(Vec::new)
108 .push(running_animation);
109 }
110
111 if animations_still_running.is_empty() {
112 keys_to_remove.push(*key);
113 } else {
114 *running_animations = animations_still_running
115 }
116 }
117
118 for key in keys_to_remove {
119 running_animations.remove(&key).unwrap();
120 }
121
122 // Add new running animations.
123 for new_running_animation in new_running_animations {
124 if new_running_animation.is_transition() {
125 match newly_transitioning_nodes {
126 Some(ref mut nodes) => {
127 nodes.push(new_running_animation.node().to_untrusted_node_address());
128 }
129 None => {
130 warn!("New transition encountered from compositor-initiated layout.");
131 }
132 }
133 }
134
135 running_animations.entry(*new_running_animation.node())
136 .or_insert_with(Vec::new)
137 .push(new_running_animation)
138 }
139
140 let animation_state = if running_animations.is_empty() {
141 AnimationState::NoAnimationsPresent
142 } else {
143 AnimationState::AnimationsPresent
144 };
145
146 constellation_chan.send(ConstellationMsg::ChangeRunningAnimationsState(pipeline_id,
147 animation_state))
148 .unwrap();
149 }
150
151 /// Recalculates style for a set of animations. This does *not* run with the DOM
152 /// lock held.
recalc_style_for_animations<E>( context: &LayoutContext, flow: &mut Flow, animations: &FnvHashMap<OpaqueNode, Vec<Animation>>, ) where E: TElement,153 pub fn recalc_style_for_animations<E>(
154 context: &LayoutContext,
155 flow: &mut Flow,
156 animations: &FnvHashMap<OpaqueNode, Vec<Animation>>,
157 )
158 where
159 E: TElement,
160 {
161 let mut damage = RestyleDamage::empty();
162 flow.mutate_fragments(&mut |fragment| {
163 if let Some(ref animations) = animations.get(&fragment.node) {
164 for animation in animations.iter() {
165 let old_style = fragment.style.clone();
166 update_style_for_animation::<E>(
167 &context.style_context,
168 animation,
169 &mut fragment.style,
170 &ServoMetricsProvider,
171 );
172 let difference =
173 RestyleDamage::compute_style_difference(
174 &old_style,
175 &fragment.style,
176 );
177 damage |= difference.damage;
178 }
179 }
180 });
181
182 let base = flow.mut_base();
183 base.restyle_damage.insert(damage);
184 for kid in base.children.iter_mut() {
185 recalc_style_for_animations::<E>(context, kid, animations)
186 }
187 }
188