Skip to main content

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