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