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