script/
animations.rs

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 https://mozilla.org/MPL/2.0/. */
4
5//! The set of animations for a document.
6
7use std::cell::Cell;
8
9use base::id::PipelineId;
10use constellation_traits::ScriptToConstellationMessage;
11use cssparser::ToCss;
12use embedder_traits::{AnimationState as AnimationsPresentState, UntrustedNodeAddress};
13use fxhash::{FxHashMap, FxHashSet};
14use libc::c_void;
15use serde::{Deserialize, Serialize};
16use style::animation::{
17    Animation, AnimationSetKey, AnimationState, DocumentAnimationSet, ElementAnimationSet,
18    KeyframesIterationState, Transition,
19};
20use style::dom::OpaqueNode;
21use style::selector_parser::PseudoElement;
22
23use crate::dom::animationevent::AnimationEvent;
24use crate::dom::bindings::cell::DomRefCell;
25use crate::dom::bindings::codegen::Bindings::AnimationEventBinding::AnimationEventInit;
26use crate::dom::bindings::codegen::Bindings::EventBinding::EventInit;
27use crate::dom::bindings::codegen::Bindings::TransitionEventBinding::TransitionEventInit;
28use crate::dom::bindings::inheritance::Castable;
29use crate::dom::bindings::num::Finite;
30use crate::dom::bindings::root::{Dom, DomRoot};
31use crate::dom::bindings::str::DOMString;
32use crate::dom::bindings::trace::NoTrace;
33use crate::dom::event::Event;
34use crate::dom::node::{Node, NodeDamage, NodeTraits, from_untrusted_node_address};
35use crate::dom::transitionevent::TransitionEvent;
36use crate::dom::window::Window;
37use crate::script_runtime::CanGc;
38
39/// The set of animations for a document.
40#[derive(Default, JSTraceable, MallocSizeOf)]
41#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
42pub(crate) struct Animations {
43    /// The map of nodes to their animation states.
44    #[no_trace]
45    pub(crate) sets: DocumentAnimationSet,
46
47    /// Whether or not we have animations that are running.
48    has_running_animations: Cell<bool>,
49
50    /// A list of nodes with in-progress CSS transitions or pending events.
51    rooted_nodes: DomRefCell<FxHashMap<NoTrace<OpaqueNode>, Dom<Node>>>,
52
53    /// A list of pending animation-related events.
54    pending_events: DomRefCell<Vec<TransitionOrAnimationEvent>>,
55
56    /// The timeline value at the last time all animations were marked dirty.
57    /// This is used to prevent marking animations dirty when the timeline
58    /// has not changed.
59    timeline_value_at_last_dirty: Cell<f64>,
60}
61
62impl Animations {
63    pub(crate) fn new() -> Self {
64        Animations {
65            sets: Default::default(),
66            has_running_animations: Cell::new(false),
67            rooted_nodes: Default::default(),
68            pending_events: Default::default(),
69            timeline_value_at_last_dirty: Cell::new(0.0),
70        }
71    }
72
73    pub(crate) fn clear(&self) {
74        self.sets.sets.write().clear();
75        self.rooted_nodes.borrow_mut().clear();
76        self.pending_events.borrow_mut().clear();
77    }
78
79    // Mark all animations dirty, if they haven't been marked dirty since the
80    // specified `current_timeline_value`. Returns true if animations were marked
81    // dirty or false otherwise.
82    pub(crate) fn mark_animating_nodes_as_dirty(&self, current_timeline_value: f64) -> bool {
83        if current_timeline_value <= self.timeline_value_at_last_dirty.get() {
84            return false;
85        }
86        self.timeline_value_at_last_dirty
87            .set(current_timeline_value);
88
89        let sets = self.sets.sets.read();
90        let rooted_nodes = self.rooted_nodes.borrow();
91        for node in sets
92            .keys()
93            .filter_map(|key| rooted_nodes.get(&NoTrace(key.node)))
94        {
95            node.dirty(NodeDamage::Style);
96        }
97
98        true
99    }
100
101    pub(crate) fn update_for_new_timeline_value(&self, window: &Window, now: f64) {
102        let pipeline_id = window.pipeline_id();
103        let mut sets = self.sets.sets.write();
104
105        for (key, set) in sets.iter_mut() {
106            self.start_pending_animations(key, set, now, pipeline_id);
107
108            // When necessary, iterate our running animations to the next iteration.
109            for animation in set.animations.iter_mut() {
110                if animation.iterate_if_necessary(now) {
111                    self.add_animation_event(
112                        key,
113                        animation,
114                        TransitionOrAnimationEventType::AnimationIteration,
115                        now,
116                        pipeline_id,
117                    );
118                }
119            }
120
121            self.finish_running_animations(key, set, now, pipeline_id);
122        }
123
124        self.unroot_unused_nodes(&sets);
125    }
126
127    /// Cancel animations for the given node, if any exist.
128    pub(crate) fn cancel_animations_for_node(&self, node: &Node) {
129        let mut animations = self.sets.sets.write();
130        let mut cancel_animations_for = |key| {
131            if let Some(set) = animations.get_mut(&key) {
132                set.cancel_all_animations();
133            }
134        };
135
136        let opaque_node = node.to_opaque();
137        cancel_animations_for(AnimationSetKey::new_for_non_pseudo(opaque_node));
138        cancel_animations_for(AnimationSetKey::new_for_pseudo(
139            opaque_node,
140            PseudoElement::Before,
141        ));
142        cancel_animations_for(AnimationSetKey::new_for_pseudo(
143            opaque_node,
144            PseudoElement::After,
145        ));
146    }
147
148    /// Processes any new animations that were discovered after reflow. Collect messages
149    /// that trigger events for any animations that changed state.
150    pub(crate) fn do_post_reflow_update(&self, window: &Window, now: f64) {
151        let pipeline_id = window.pipeline_id();
152        let mut sets = self.sets.sets.write();
153        self.root_newly_animating_dom_nodes(&sets);
154
155        for (key, set) in sets.iter_mut() {
156            self.handle_canceled_animations(key, set, now, pipeline_id);
157            self.handle_new_animations(key, set, now, pipeline_id);
158        }
159
160        // Remove empty states from our collection of states in order to free
161        // up space as soon as we are no longer tracking any animations for
162        // a node.
163        sets.retain(|_, state| !state.is_empty());
164        let have_running_animations = sets.values().any(|state| state.needs_animation_ticks());
165
166        self.update_running_animations_presence(window, have_running_animations);
167    }
168
169    fn update_running_animations_presence(&self, window: &Window, new_value: bool) {
170        let had_running_animations = self.has_running_animations.get();
171        if new_value == had_running_animations {
172            return;
173        }
174
175        self.has_running_animations.set(new_value);
176        self.handle_animation_presence_or_pending_events_change(window);
177    }
178
179    fn handle_animation_presence_or_pending_events_change(&self, window: &Window) {
180        let has_running_animations = self.has_running_animations.get();
181        let has_pending_events = !self.pending_events.borrow().is_empty();
182
183        // Do not send the NoAnimationCallbacksPresent state until all pending
184        // animation events are delivered.
185        let state = match has_running_animations || has_pending_events {
186            true => AnimationsPresentState::AnimationsPresent,
187            false => AnimationsPresentState::NoAnimationsPresent,
188        };
189        window.send_to_constellation(ScriptToConstellationMessage::ChangeRunningAnimationsState(
190            state,
191        ));
192    }
193
194    pub(crate) fn running_animation_count(&self) -> usize {
195        self.sets
196            .sets
197            .read()
198            .values()
199            .map(|state| state.running_animation_and_transition_count())
200            .sum()
201    }
202
203    /// Walk through the list of pending animations and start all of the ones that
204    /// have left the delay phase.
205    fn start_pending_animations(
206        &self,
207        key: &AnimationSetKey,
208        set: &mut ElementAnimationSet,
209        now: f64,
210        pipeline_id: PipelineId,
211    ) {
212        for animation in set.animations.iter_mut() {
213            if animation.state == AnimationState::Pending && animation.started_at <= now {
214                animation.state = AnimationState::Running;
215                self.add_animation_event(
216                    key,
217                    animation,
218                    TransitionOrAnimationEventType::AnimationStart,
219                    now,
220                    pipeline_id,
221                );
222            }
223        }
224
225        for transition in set.transitions.iter_mut() {
226            if transition.state == AnimationState::Pending && transition.start_time <= now {
227                transition.state = AnimationState::Running;
228                self.add_transition_event(
229                    key,
230                    transition,
231                    TransitionOrAnimationEventType::TransitionStart,
232                    now,
233                    pipeline_id,
234                );
235            }
236        }
237    }
238
239    /// Walk through the list of running animations and remove all of the ones that
240    /// have ended.
241    fn finish_running_animations(
242        &self,
243        key: &AnimationSetKey,
244        set: &mut ElementAnimationSet,
245        now: f64,
246        pipeline_id: PipelineId,
247    ) {
248        for animation in set.animations.iter_mut() {
249            if animation.state == AnimationState::Running && animation.has_ended(now) {
250                animation.state = AnimationState::Finished;
251                self.add_animation_event(
252                    key,
253                    animation,
254                    TransitionOrAnimationEventType::AnimationEnd,
255                    now,
256                    pipeline_id,
257                );
258            }
259        }
260
261        for transition in set.transitions.iter_mut() {
262            if transition.state == AnimationState::Running && transition.has_ended(now) {
263                transition.state = AnimationState::Finished;
264                self.add_transition_event(
265                    key,
266                    transition,
267                    TransitionOrAnimationEventType::TransitionEnd,
268                    now,
269                    pipeline_id,
270                );
271            }
272        }
273    }
274
275    /// Send events for canceled animations. Currently this only handles canceled
276    /// transitions, but eventually this should handle canceled CSS animations as
277    /// well.
278    fn handle_canceled_animations(
279        &self,
280        key: &AnimationSetKey,
281        set: &mut ElementAnimationSet,
282        now: f64,
283        pipeline_id: PipelineId,
284    ) {
285        for transition in &set.transitions {
286            if transition.state == AnimationState::Canceled {
287                self.add_transition_event(
288                    key,
289                    transition,
290                    TransitionOrAnimationEventType::TransitionCancel,
291                    now,
292                    pipeline_id,
293                );
294            }
295        }
296
297        for animation in &set.animations {
298            if animation.state == AnimationState::Canceled {
299                self.add_animation_event(
300                    key,
301                    animation,
302                    TransitionOrAnimationEventType::AnimationCancel,
303                    now,
304                    pipeline_id,
305                );
306            }
307        }
308
309        set.clear_canceled_animations();
310    }
311
312    fn handle_new_animations(
313        &self,
314        key: &AnimationSetKey,
315        set: &mut ElementAnimationSet,
316        now: f64,
317        pipeline_id: PipelineId,
318    ) {
319        for animation in set.animations.iter_mut() {
320            animation.is_new = false;
321        }
322
323        for transition in set.transitions.iter_mut() {
324            if transition.is_new {
325                self.add_transition_event(
326                    key,
327                    transition,
328                    TransitionOrAnimationEventType::TransitionRun,
329                    now,
330                    pipeline_id,
331                );
332                transition.is_new = false;
333            }
334        }
335    }
336
337    /// Ensure that all nodes with new animations are rooted. This should be called
338    /// immediately after a restyle, to ensure that these addresses are still valid.
339    #[allow(unsafe_code)]
340    fn root_newly_animating_dom_nodes(
341        &self,
342        sets: &FxHashMap<AnimationSetKey, ElementAnimationSet>,
343    ) {
344        let mut rooted_nodes = self.rooted_nodes.borrow_mut();
345        for (key, set) in sets.iter() {
346            let opaque_node = key.node;
347            if rooted_nodes.contains_key(&NoTrace(opaque_node)) {
348                continue;
349            }
350
351            if set.animations.iter().any(|animation| animation.is_new) ||
352                set.transitions.iter().any(|transition| transition.is_new)
353            {
354                let address = UntrustedNodeAddress(opaque_node.0 as *const c_void);
355                unsafe {
356                    rooted_nodes.insert(
357                        NoTrace(opaque_node),
358                        Dom::from_ref(&*from_untrusted_node_address(address)),
359                    )
360                };
361            }
362        }
363    }
364
365    // Unroot any nodes that we have rooted but are no longer tracking animations for.
366    fn unroot_unused_nodes(&self, sets: &FxHashMap<AnimationSetKey, ElementAnimationSet>) {
367        let pending_events = self.pending_events.borrow();
368        let nodes: FxHashSet<OpaqueNode> = sets.keys().map(|key| key.node).collect();
369        self.rooted_nodes.borrow_mut().retain(|node, _| {
370            nodes.contains(&node.0) || pending_events.iter().any(|event| event.node == node.0)
371        });
372    }
373
374    fn add_transition_event(
375        &self,
376        key: &AnimationSetKey,
377        transition: &Transition,
378        event_type: TransitionOrAnimationEventType,
379        now: f64,
380        pipeline_id: PipelineId,
381    ) {
382        // Calculate the `elapsed-time` property of the event and take the absolute
383        // value to prevent -0 values.
384        let elapsed_time = match event_type {
385            TransitionOrAnimationEventType::TransitionRun |
386            TransitionOrAnimationEventType::TransitionStart => transition
387                .property_animation
388                .duration
389                .min((-transition.delay).max(0.)),
390            TransitionOrAnimationEventType::TransitionEnd => transition.property_animation.duration,
391            TransitionOrAnimationEventType::TransitionCancel => {
392                (now - transition.start_time).max(0.)
393            },
394            _ => unreachable!(),
395        }
396        .abs();
397
398        self.pending_events
399            .borrow_mut()
400            .push(TransitionOrAnimationEvent {
401                pipeline_id,
402                event_type,
403                node: key.node,
404                pseudo_element: key.pseudo_element,
405                property_or_animation_name: transition
406                    .property_animation
407                    .property_id()
408                    .name()
409                    .into(),
410                elapsed_time,
411            });
412    }
413
414    fn add_animation_event(
415        &self,
416        key: &AnimationSetKey,
417        animation: &Animation,
418        event_type: TransitionOrAnimationEventType,
419        now: f64,
420        pipeline_id: PipelineId,
421    ) {
422        let iteration_index = match animation.iteration_state {
423            KeyframesIterationState::Finite(current, _) |
424            KeyframesIterationState::Infinite(current) => current,
425        };
426
427        let active_duration = match animation.iteration_state {
428            KeyframesIterationState::Finite(_, max) => max * animation.duration,
429            KeyframesIterationState::Infinite(_) => f64::MAX,
430        };
431
432        // Calculate the `elapsed-time` property of the event and take the absolute
433        // value to prevent -0 values.
434        let elapsed_time = match event_type {
435            TransitionOrAnimationEventType::AnimationStart => {
436                (-animation.delay).max(0.).min(active_duration)
437            },
438            TransitionOrAnimationEventType::AnimationIteration => {
439                iteration_index * animation.duration
440            },
441            TransitionOrAnimationEventType::AnimationEnd => {
442                (iteration_index * animation.duration) + animation.current_iteration_duration()
443            },
444            TransitionOrAnimationEventType::AnimationCancel => {
445                (iteration_index * animation.duration) + (now - animation.started_at).max(0.)
446            },
447            _ => unreachable!(),
448        }
449        .abs();
450
451        self.pending_events
452            .borrow_mut()
453            .push(TransitionOrAnimationEvent {
454                pipeline_id,
455                event_type,
456                node: key.node,
457                pseudo_element: key.pseudo_element,
458                property_or_animation_name: animation.name.to_string(),
459                elapsed_time,
460            });
461    }
462
463    /// An implementation of the final steps of
464    /// <https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events>.
465    pub(crate) fn send_pending_events(&self, window: &Window, can_gc: CanGc) {
466        // > 4. Let events to dispatch be a copy of doc’s pending animation event queue.
467        // > 5. Clear doc’s pending animation event queue.
468        //
469        // Take all of the events here, in case sending one of these events
470        // triggers adding new events by forcing a layout.
471        let events = std::mem::take(&mut *self.pending_events.borrow_mut());
472        if events.is_empty() {
473            return;
474        }
475
476        // > 6. Perform a stable sort of the animation events in events to dispatch as follows:
477        // >    1. Sort the events by their scheduled event time such that events that were
478        // >       scheduled to occur earlier sort before events scheduled to occur later, and
479        // >       events whose scheduled event time is unresolved sort before events with a
480        // >       resolved scheduled event time.
481        // >    2. Within events with equal scheduled event times, sort by their composite
482        // >       order.
483        //
484        // TODO: Sorting of animation events isn't done yet.
485
486        // 7. Dispatch each of the events in events to dispatch at their corresponding
487        // target using the order established in the previous step.
488        for event in events.into_iter() {
489            // We root the node here to ensure that sending this event doesn't
490            // unroot it as a side-effect.
491            let node = match self.rooted_nodes.borrow().get(&NoTrace(event.node)) {
492                Some(node) => DomRoot::from_ref(&**node),
493                None => {
494                    warn!("Tried to send an event for an unrooted node");
495                    continue;
496                },
497            };
498
499            let event_atom = match event.event_type {
500                TransitionOrAnimationEventType::AnimationEnd => atom!("animationend"),
501                TransitionOrAnimationEventType::AnimationStart => atom!("animationstart"),
502                TransitionOrAnimationEventType::AnimationCancel => atom!("animationcancel"),
503                TransitionOrAnimationEventType::AnimationIteration => atom!("animationiteration"),
504                TransitionOrAnimationEventType::TransitionCancel => atom!("transitioncancel"),
505                TransitionOrAnimationEventType::TransitionEnd => atom!("transitionend"),
506                TransitionOrAnimationEventType::TransitionRun => atom!("transitionrun"),
507                TransitionOrAnimationEventType::TransitionStart => atom!("transitionstart"),
508            };
509            let parent = EventInit {
510                bubbles: true,
511                cancelable: false,
512                composed: false,
513            };
514
515            let property_or_animation_name =
516                DOMString::from(event.property_or_animation_name.clone());
517            let pseudo_element = event
518                .pseudo_element
519                .map_or_else(DOMString::new, |pseudo_element| {
520                    DOMString::from(pseudo_element.to_css_string())
521                });
522            let elapsed_time = Finite::new(event.elapsed_time as f32).unwrap();
523            let window = node.owner_window();
524
525            if event.event_type.is_transition_event() {
526                let event_init = TransitionEventInit {
527                    parent,
528                    propertyName: property_or_animation_name,
529                    elapsedTime: elapsed_time,
530                    pseudoElement: pseudo_element,
531                };
532                TransitionEvent::new(&window, event_atom, &event_init, can_gc)
533                    .upcast::<Event>()
534                    .fire(node.upcast(), can_gc);
535            } else {
536                let event_init = AnimationEventInit {
537                    parent,
538                    animationName: property_or_animation_name,
539                    elapsedTime: elapsed_time,
540                    pseudoElement: pseudo_element,
541                };
542                AnimationEvent::new(&window, event_atom, &event_init, can_gc)
543                    .upcast::<Event>()
544                    .fire(node.upcast(), can_gc);
545            }
546        }
547
548        if self.pending_events.borrow().is_empty() {
549            self.handle_animation_presence_or_pending_events_change(window);
550        }
551    }
552}
553
554/// The type of transition event to trigger. These are defined by
555/// CSS Transitions § 6.1 and CSS Animations § 4.2
556#[derive(Clone, Debug, Deserialize, JSTraceable, MallocSizeOf, Serialize)]
557pub(crate) enum TransitionOrAnimationEventType {
558    /// "The transitionrun event occurs when a transition is created (i.e., when it
559    /// is added to the set of running transitions)."
560    TransitionRun,
561    /// "The transitionstart event occurs when a transition’s delay phase ends."
562    TransitionStart,
563    /// "The transitionend event occurs at the completion of the transition. In the
564    /// case where a transition is removed before completion, such as if the
565    /// transition-property is removed, then the event will not fire."
566    TransitionEnd,
567    /// "The transitioncancel event occurs when a transition is canceled."
568    TransitionCancel,
569    /// "The animationstart event occurs at the start of the animation. If there is
570    /// an animation-delay then this event will fire once the delay period has expired."
571    AnimationStart,
572    /// "The animationiteration event occurs at the end of each iteration of an
573    /// animation, except when an animationend event would fire at the same time."
574    AnimationIteration,
575    /// "The animationend event occurs when the animation finishes"
576    AnimationEnd,
577    /// "The animationcancel event occurs when the animation stops running in a way
578    /// that does not fire an animationend event..."
579    AnimationCancel,
580}
581
582impl TransitionOrAnimationEventType {
583    /// Whether or not this event is a transition-related event.
584    pub(crate) fn is_transition_event(&self) -> bool {
585        match *self {
586            Self::TransitionRun |
587            Self::TransitionEnd |
588            Self::TransitionCancel |
589            Self::TransitionStart => true,
590            Self::AnimationEnd |
591            Self::AnimationIteration |
592            Self::AnimationStart |
593            Self::AnimationCancel => false,
594        }
595    }
596}
597
598#[derive(Deserialize, JSTraceable, MallocSizeOf, Serialize)]
599/// A transition or animation event.
600pub(crate) struct TransitionOrAnimationEvent {
601    /// The pipeline id of the layout task that sent this message.
602    #[no_trace]
603    pub(crate) pipeline_id: PipelineId,
604    /// The type of transition event this should trigger.
605    pub(crate) event_type: TransitionOrAnimationEventType,
606    /// The address of the node which owns this transition.
607    #[no_trace]
608    pub(crate) node: OpaqueNode,
609    /// The pseudo element for this transition or animation, if applicable.
610    #[no_trace]
611    pub(crate) pseudo_element: Option<PseudoElement>,
612    /// The name of the property that is transitioning (in the case of a transition)
613    /// or the name of the animation (in the case of an animation).
614    pub(crate) property_or_animation_name: String,
615    /// The elapsed time property to send with this transition event.
616    pub(crate) elapsed_time: f64,
617}