Skip to main content

script/dom/document/
animation_manager.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
7#![cfg_attr(crown, allow(crown::jscontext_first_arg))]
8
9use std::cell::Cell;
10use std::sync::Arc;
11use std::time::Duration;
12
13use cssparser::ToCss;
14use embedder_traits::{AnimationState as AnimationsPresentState, UntrustedNodeAddress};
15use js::context::NoGC;
16use layout_api::AnimatingImages;
17use libc::c_void;
18use paint_api::ImageUpdate;
19use parking_lot::RwLock;
20use rustc_hash::{FxHashMap, FxHashSet};
21use script_bindings::cell::DomRefCell;
22use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
23use script_bindings::refcounted::Trusted;
24use serde::{Deserialize, Serialize};
25use servo_base::id::PipelineId;
26use servo_constellation_traits::ScriptToConstellationMessage;
27use style::animation::{
28    Animation, AnimationSetKey, AnimationState, DocumentAnimationSet, ElementAnimationSet,
29    KeyframesIterationState, Transition,
30};
31use style::dom::OpaqueNode;
32use style::selector_parser::PseudoElement;
33use timers::TimerId;
34
35use crate::dom::animationevent::AnimationEvent;
36use crate::dom::bindings::codegen::Bindings::AnimationEventBinding::AnimationEventInit;
37use crate::dom::bindings::codegen::Bindings::EventBinding::EventInit;
38use crate::dom::bindings::codegen::Bindings::TransitionEventBinding::TransitionEventInit;
39use crate::dom::bindings::inheritance::Castable;
40use crate::dom::bindings::num::Finite;
41use crate::dom::bindings::root::{Dom, DomRoot};
42use crate::dom::bindings::str::DOMString;
43use crate::dom::bindings::trace::NoTrace;
44use crate::dom::event::Event;
45use crate::dom::node::{Node, NodeDamage, NodeTraits, from_untrusted_node_address};
46use crate::dom::transitionevent::TransitionEvent;
47use crate::dom::window::Window;
48use crate::event_loop::script_thread::with_script_thread;
49
50/// The set of animations for a document.
51#[derive(Default, JSTraceable, MallocSizeOf)]
52#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
53pub(crate) struct AnimationManager {
54    /// The map of nodes to their animation states.
55    #[no_trace]
56    sets: DocumentAnimationSet,
57
58    /// The set of [`AnimatingImages`] which is used to communicate the addition
59    /// and removal of animating images from layout.
60    #[no_trace]
61    #[conditional_malloc_size_of]
62    animating_images: Arc<RwLock<AnimatingImages>>,
63
64    /// Whether or not we have animations that are running.
65    has_running_animations: Cell<bool>,
66
67    /// A map of nodes with in-progress CSS transitions or pending events.
68    rooted_animation_nodes: DomRefCell<FxHashMap<NoTrace<OpaqueNode>, Dom<Node>>>,
69
70    /// A map of nodes with in-progress image animations. This is kept outside
71    /// of [`Self::animating_images`] as that data structure is shared with layout.
72    rooted_image_nodes: DomRefCell<FxHashMap<NoTrace<OpaqueNode>, Dom<Node>>>,
73
74    /// A list of pending animation-related events.
75    pending_events: DomRefCell<Vec<TransitionOrAnimationEvent>>,
76
77    /// The timeline value at the last time all animations were marked dirty.
78    /// This is used to prevent marking animations dirty when the timeline
79    /// has not changed.
80    timeline_value_at_last_dirty: Cell<f64>,
81
82    /// The [`TimerId`] of the currently scheduled animated image update callback.
83    #[no_trace]
84    image_callback_timer_id: Cell<Option<TimerId>>,
85}
86
87impl AnimationManager {
88    pub(crate) fn new() -> Self {
89        AnimationManager {
90            sets: Default::default(),
91            animating_images: Default::default(),
92            has_running_animations: Cell::new(false),
93            rooted_animation_nodes: Default::default(),
94            rooted_image_nodes: Default::default(),
95            pending_events: Default::default(),
96            timeline_value_at_last_dirty: Cell::new(0.0),
97            image_callback_timer_id: Default::default(),
98        }
99    }
100
101    pub(crate) fn animating_images(&self) -> Arc<RwLock<AnimatingImages>> {
102        self.animating_images.clone()
103    }
104
105    pub(crate) fn sets(&self) -> DocumentAnimationSet {
106        self.sets.clone()
107    }
108
109    pub(crate) fn clear(&self) {
110        self.sets.sets.write().clear();
111        self.animating_images.write().node_to_state_map.clear();
112        self.rooted_animation_nodes.borrow_mut().clear();
113        self.rooted_image_nodes.borrow_mut().clear();
114        self.pending_events.borrow_mut().clear();
115    }
116
117    // Mark all animations dirty, if they haven't been marked dirty since the
118    // specified `current_timeline_value`. Returns true if animations were marked
119    // dirty or false otherwise.
120    pub(crate) fn mark_animating_nodes_as_dirty(
121        &self,
122        no_gc: &NoGC,
123        current_timeline_value: f64,
124    ) -> bool {
125        if current_timeline_value <= self.timeline_value_at_last_dirty.get() {
126            return false;
127        }
128        self.timeline_value_at_last_dirty
129            .set(current_timeline_value);
130
131        let sets = self.sets.sets.read();
132        let rooted_nodes = self.rooted_animation_nodes.borrow();
133        for node in sets
134            .keys()
135            .filter_map(|key| rooted_nodes.get(&NoTrace(key.node)))
136        {
137            node.dirty(no_gc, NodeDamage::Style);
138        }
139
140        true
141    }
142
143    pub(crate) fn update_for_new_timeline_value(&self, window: &Window, now: f64) {
144        let pipeline_id = window.pipeline_id();
145        let mut sets = self.sets.sets.write();
146
147        for (key, set) in sets.iter_mut() {
148            self.start_pending_animations(key, set, now, pipeline_id);
149
150            // When necessary, iterate our running animations to the next iteration.
151            if now > self.timeline_value_at_last_dirty.get() {
152                for animation in set.animations.iter_mut() {
153                    if animation.iterate_if_necessary(now) {
154                        self.add_animation_event(
155                            key,
156                            animation,
157                            TransitionOrAnimationEventType::AnimationIteration,
158                            now,
159                            pipeline_id,
160                        );
161                    }
162                }
163            }
164
165            self.finish_running_animations(key, set, now, pipeline_id);
166        }
167
168        self.unroot_unused_nodes(&sets);
169    }
170
171    /// Cancel animations for the given node, if any exist.
172    pub(crate) fn cancel_animations_for_node(&self, node: &Node) {
173        let opaque_node = node.to_opaque();
174
175        let animation_keys = [
176            AnimationSetKey::new_for_non_pseudo(opaque_node),
177            AnimationSetKey::new_for_pseudo(opaque_node, PseudoElement::Before),
178            AnimationSetKey::new_for_pseudo(opaque_node, PseudoElement::After),
179        ];
180
181        let mut animations = self.sets.sets.write();
182        for key in animation_keys {
183            if let Some(set) = animations.get_mut(&key) {
184                set.cancel_all_animations();
185            }
186        }
187
188        self.animating_images().write().remove(opaque_node);
189        self.rooted_image_nodes
190            .borrow_mut()
191            .remove(&NoTrace(opaque_node));
192    }
193
194    /// This does five things:
195    ///  - Cancel animations for any nodes that are no longer being rendered or delegating rendering.
196    ///  - Process any new animations that were discovered after reflow.
197    ///  - Collect pending events for any animations that changed state.
198    ///  - Root any nodes with newly animating images
199    ///  - Schedule an image update for newly animating images
200    pub(crate) fn do_post_reflow_update(&self, window: &Window, now: f64) {
201        let mut sets = self.sets.sets.write();
202        {
203            let rooted_nodes = self.rooted_animation_nodes.borrow();
204            for (key, set) in sets.iter_mut() {
205                if rooted_nodes.get(&NoTrace(key.node)).is_some_and(|node| {
206                    !node.is_being_rendered_or_delegates_rendering(key.pseudo_element)
207                }) {
208                    set.cancel_all_animations();
209                }
210            }
211        }
212
213        let pipeline_id = window.pipeline_id();
214        self.root_newly_animating_dom_nodes(&sets);
215
216        for (key, set) in sets.iter_mut() {
217            self.handle_canceled_animations(key, set, now, pipeline_id);
218            self.handle_new_animations(key, set, now, pipeline_id);
219        }
220
221        // Remove empty states from our collection of states in order to free
222        // up space as soon as we are no longer tracking any animations for
223        // a node.
224        sets.retain(|_, state| !state.is_empty());
225        let have_running_animations = sets.values().any(|state| state.needs_animation_ticks());
226
227        self.update_running_animations_presence(window, have_running_animations);
228
229        // Cancel animations for any images that are no longer rendering.
230        self.rooted_image_nodes
231            .borrow_mut()
232            .retain(|opaque_node, node| {
233                if node.is_being_rendered(None) {
234                    return true;
235                }
236                self.animating_images.write().remove(opaque_node.0);
237                false
238            });
239
240        if self.animating_images().write().clear_dirty() {
241            self.root_nodes_with_newly_animating_images();
242            self.maybe_schedule_image_animation_update(window, now);
243        }
244    }
245
246    pub(crate) fn update_active_image_animation_frames(&self, window: &Window, now: f64) {
247        if self.animating_images.read().is_empty() {
248            return;
249        }
250
251        let updates = self
252            .animating_images
253            .write()
254            .node_to_state_map
255            .values_mut()
256            .filter_map(|state| {
257                if !state.update_frame_for_animation_timeline_value(now) {
258                    return None;
259                }
260
261                let image = &state.image;
262                let frame = image
263                    .frame_data(state.active_frame)
264                    .expect("No frame found")
265                    .clone();
266                if let Some(mut descriptor) =
267                    image.webrender_image_descriptor_and_offset_for_frame()
268                {
269                    descriptor.offset = frame.byte_range.start as i32;
270                    Some(ImageUpdate::UpdateImageForAnimation(
271                        image.id.unwrap(),
272                        descriptor,
273                    ))
274                } else {
275                    error!("Doing normal image update which will be slow!");
276                    None
277                }
278            })
279            .collect();
280        window
281            .paint_api()
282            .update_images(window.webview_id().into(), updates);
283
284        self.maybe_schedule_image_animation_update(window, now);
285    }
286
287    fn root_nodes_with_newly_animating_images(&self) {
288        let mut rooted_nodes = self.rooted_image_nodes.borrow_mut();
289        for opaque_node in self.animating_images.read().node_to_state_map.keys() {
290            root_node(&mut rooted_nodes, *opaque_node);
291        }
292    }
293
294    fn maybe_schedule_image_animation_update(&self, window: &Window, now: f64) {
295        with_script_thread(|script_thread| {
296            if let Some(current_timer_id) = self.image_callback_timer_id.take() {
297                self.image_callback_timer_id.set(None);
298                script_thread.cancel_timer(current_timer_id);
299            }
300
301            if let Some(duration) = self.duration_to_next_image_animation_frame(now) {
302                let trusted_window = Trusted::new(window);
303                let timer_id = script_thread.schedule_timer(timers::TimerEventRequest {
304                    callback: Box::new(move || {
305                        let window = trusted_window.root();
306                        window.Document().set_has_pending_animated_image_update();
307                    }),
308                    duration,
309                });
310                self.image_callback_timer_id.set(Some(timer_id));
311            }
312        })
313    }
314
315    fn duration_to_next_image_animation_frame(&self, now: f64) -> Option<Duration> {
316        self.animating_images
317            .read()
318            .node_to_state_map
319            .values()
320            .filter_map(|state| state.duration_to_next_frame(now))
321            .min()
322    }
323
324    fn update_running_animations_presence(&self, window: &Window, new_value: bool) {
325        let had_running_animations = self.has_running_animations.get();
326        if new_value == had_running_animations {
327            return;
328        }
329
330        self.has_running_animations.set(new_value);
331        self.handle_animation_presence_or_pending_events_change(window);
332    }
333
334    fn handle_animation_presence_or_pending_events_change(&self, window: &Window) {
335        let has_running_animations = self.has_running_animations.get();
336        let has_pending_events = !self.pending_events.borrow().is_empty();
337
338        // Do not send the AnimationCallbacksAbsent state until all pending
339        // animation events are delivered.
340        let state = match has_running_animations || has_pending_events {
341            true => AnimationsPresentState::AnimationsPresent,
342            false => AnimationsPresentState::NoAnimationsPresent,
343        };
344        window.send_to_constellation(ScriptToConstellationMessage::ChangeRunningAnimationsState(
345            state,
346        ));
347    }
348
349    pub(crate) fn running_animation_count(&self) -> usize {
350        self.sets
351            .sets
352            .read()
353            .values()
354            .map(|state| state.running_animation_and_transition_count())
355            .sum()
356    }
357
358    /// Walk through the list of pending animations and start all of the ones that
359    /// have left the delay phase.
360    fn start_pending_animations(
361        &self,
362        key: &AnimationSetKey,
363        set: &mut ElementAnimationSet,
364        now: f64,
365        pipeline_id: PipelineId,
366    ) {
367        for animation in set.animations.iter_mut() {
368            if animation.state == AnimationState::Pending && animation.started_at <= now {
369                animation.state = AnimationState::Running;
370                self.add_animation_event(
371                    key,
372                    animation,
373                    TransitionOrAnimationEventType::AnimationStart,
374                    now,
375                    pipeline_id,
376                );
377            }
378        }
379
380        for transition in set.transitions.iter_mut() {
381            if transition.state == AnimationState::Pending && transition.start_time <= now {
382                transition.state = AnimationState::Running;
383                self.add_transition_event(
384                    key,
385                    transition,
386                    TransitionOrAnimationEventType::TransitionStart,
387                    now,
388                    pipeline_id,
389                );
390            }
391        }
392    }
393
394    /// Walk through the list of running animations and remove all of the ones that
395    /// have ended.
396    fn finish_running_animations(
397        &self,
398        key: &AnimationSetKey,
399        set: &mut ElementAnimationSet,
400        now: f64,
401        pipeline_id: PipelineId,
402    ) {
403        for animation in set.animations.iter_mut() {
404            if animation.state == AnimationState::Running && animation.has_ended(now) {
405                animation.state = AnimationState::Finished;
406                self.add_animation_event(
407                    key,
408                    animation,
409                    TransitionOrAnimationEventType::AnimationEnd,
410                    now,
411                    pipeline_id,
412                );
413            }
414        }
415
416        for transition in set.transitions.iter_mut() {
417            if transition.state == AnimationState::Running && transition.has_ended(now) {
418                transition.state = AnimationState::Finished;
419                self.add_transition_event(
420                    key,
421                    transition,
422                    TransitionOrAnimationEventType::TransitionEnd,
423                    now,
424                    pipeline_id,
425                );
426            }
427        }
428    }
429
430    /// Send events for canceled animations. Currently this only handles canceled
431    /// transitions, but eventually this should handle canceled CSS animations as
432    /// well.
433    fn handle_canceled_animations(
434        &self,
435        key: &AnimationSetKey,
436        set: &mut ElementAnimationSet,
437        now: f64,
438        pipeline_id: PipelineId,
439    ) {
440        for transition in &set.transitions {
441            if transition.state == AnimationState::Canceled {
442                self.add_transition_event(
443                    key,
444                    transition,
445                    TransitionOrAnimationEventType::TransitionCancel,
446                    now,
447                    pipeline_id,
448                );
449            }
450        }
451
452        for animation in &set.animations {
453            if animation.state == AnimationState::Canceled {
454                self.add_animation_event(
455                    key,
456                    animation,
457                    TransitionOrAnimationEventType::AnimationCancel,
458                    now,
459                    pipeline_id,
460                );
461            }
462        }
463
464        set.clear_canceled_animations();
465    }
466
467    fn handle_new_animations(
468        &self,
469        key: &AnimationSetKey,
470        set: &mut ElementAnimationSet,
471        now: f64,
472        pipeline_id: PipelineId,
473    ) {
474        for animation in set.animations.iter_mut() {
475            animation.is_new = false;
476        }
477
478        for transition in set.transitions.iter_mut() {
479            if transition.is_new {
480                self.add_transition_event(
481                    key,
482                    transition,
483                    TransitionOrAnimationEventType::TransitionRun,
484                    now,
485                    pipeline_id,
486                );
487                transition.is_new = false;
488            }
489        }
490    }
491
492    /// Ensure that all nodes with new animations are rooted. This should be called
493    /// immediately after a restyle, to ensure that these addresses are still valid.
494    fn root_newly_animating_dom_nodes(
495        &self,
496        sets: &FxHashMap<AnimationSetKey, ElementAnimationSet>,
497    ) {
498        let mut rooted_nodes = self.rooted_animation_nodes.borrow_mut();
499        for (key, set) in sets.iter() {
500            let opaque_node = key.node;
501            if rooted_nodes.contains_key(&NoTrace(opaque_node)) {
502                continue;
503            }
504
505            if set.animations.iter().any(|animation| animation.is_new) ||
506                set.transitions.iter().any(|transition| transition.is_new)
507            {
508                root_node(&mut rooted_nodes, opaque_node);
509            }
510        }
511    }
512
513    // Unroot any nodes that we have rooted but are no longer tracking animations for.
514    fn unroot_unused_nodes(&self, sets: &FxHashMap<AnimationSetKey, ElementAnimationSet>) {
515        let pending_events = self.pending_events.borrow();
516        let nodes: FxHashSet<OpaqueNode> = sets.keys().map(|key| key.node).collect();
517        self.rooted_animation_nodes.borrow_mut().retain(|node, _| {
518            nodes.contains(&node.0) || pending_events.iter().any(|event| event.node == node.0)
519        });
520    }
521
522    fn add_transition_event(
523        &self,
524        key: &AnimationSetKey,
525        transition: &Transition,
526        event_type: TransitionOrAnimationEventType,
527        now: f64,
528        pipeline_id: PipelineId,
529    ) {
530        // Calculate the `elapsed-time` property of the event and take the absolute
531        // value to prevent -0 values.
532        let elapsed_time = match event_type {
533            TransitionOrAnimationEventType::TransitionRun |
534            TransitionOrAnimationEventType::TransitionStart => transition
535                .property_animation
536                .duration
537                .min((-transition.delay).max(0.)),
538            TransitionOrAnimationEventType::TransitionEnd => transition.property_animation.duration,
539            TransitionOrAnimationEventType::TransitionCancel => {
540                (now - transition.start_time).max(0.)
541            },
542            _ => unreachable!(),
543        }
544        .abs();
545
546        self.pending_events
547            .borrow_mut()
548            .push(TransitionOrAnimationEvent {
549                pipeline_id,
550                event_type,
551                node: key.node,
552                pseudo_element: key.pseudo_element,
553                property_or_animation_name: transition
554                    .property_animation
555                    .property_id()
556                    .name()
557                    .into(),
558                elapsed_time,
559            });
560    }
561
562    fn add_animation_event(
563        &self,
564        key: &AnimationSetKey,
565        animation: &Animation,
566        event_type: TransitionOrAnimationEventType,
567        now: f64,
568        pipeline_id: PipelineId,
569    ) {
570        let iteration_index = match animation.iteration_state {
571            KeyframesIterationState::Finite(current, _) |
572            KeyframesIterationState::Infinite(current) => current,
573        };
574
575        let active_duration = match animation.iteration_state {
576            KeyframesIterationState::Finite(_, max) => max * animation.duration,
577            KeyframesIterationState::Infinite(_) => f64::MAX,
578        };
579
580        // Calculate the `elapsed-time` property of the event and take the absolute
581        // value to prevent -0 values.
582        let elapsed_time = match event_type {
583            TransitionOrAnimationEventType::AnimationStart => {
584                (-animation.delay).max(0.).min(active_duration)
585            },
586            TransitionOrAnimationEventType::AnimationIteration => {
587                iteration_index * animation.duration
588            },
589            TransitionOrAnimationEventType::AnimationEnd => {
590                (iteration_index * animation.duration) + animation.current_iteration_duration()
591            },
592            TransitionOrAnimationEventType::AnimationCancel => {
593                (iteration_index * animation.duration) + (now - animation.started_at).max(0.)
594            },
595            _ => unreachable!(),
596        }
597        .abs();
598
599        self.pending_events
600            .borrow_mut()
601            .push(TransitionOrAnimationEvent {
602                pipeline_id,
603                event_type,
604                node: key.node,
605                pseudo_element: key.pseudo_element,
606                property_or_animation_name: animation.name.to_string(),
607                elapsed_time,
608            });
609    }
610
611    /// An implementation of the final steps of
612    /// <https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events>.
613    pub(crate) fn send_pending_events(&self, window: &Window, cx: &mut js::context::JSContext) {
614        // > 4. Let events to dispatch be a copy of doc’s pending animation event queue.
615        // > 5. Clear doc’s pending animation event queue.
616        //
617        // Take all of the events here, in case sending one of these events
618        // triggers adding new events by forcing a layout.
619        let events = std::mem::take(&mut *self.pending_events.safe_borrow_mut(cx.no_gc()));
620        if events.is_empty() {
621            return;
622        }
623
624        // > 6. Perform a stable sort of the animation events in events to dispatch as follows:
625        // >    1. Sort the events by their scheduled event time such that events that were
626        // >       scheduled to occur earlier sort before events scheduled to occur later, and
627        // >       events whose scheduled event time is unresolved sort before events with a
628        // >       resolved scheduled event time.
629        // >    2. Within events with equal scheduled event times, sort by their composite
630        // >       order.
631        //
632        // TODO: Sorting of animation events isn't done yet.
633
634        // 7. Dispatch each of the events in events to dispatch at their corresponding
635        // target using the order established in the previous step.
636        for event in events.into_iter() {
637            // We root the node here to ensure that sending this event doesn't
638            // unroot it as a side-effect.
639            let node = match self
640                .rooted_animation_nodes
641                .borrow()
642                .get(&NoTrace(event.node))
643            {
644                Some(node) => DomRoot::from_ref(&**node),
645                None => {
646                    warn!("Tried to send an event for an unrooted node");
647                    continue;
648                },
649            };
650
651            let event_atom = match event.event_type {
652                TransitionOrAnimationEventType::AnimationEnd => atom!("animationend"),
653                TransitionOrAnimationEventType::AnimationStart => atom!("animationstart"),
654                TransitionOrAnimationEventType::AnimationCancel => atom!("animationcancel"),
655                TransitionOrAnimationEventType::AnimationIteration => atom!("animationiteration"),
656                TransitionOrAnimationEventType::TransitionCancel => atom!("transitioncancel"),
657                TransitionOrAnimationEventType::TransitionEnd => atom!("transitionend"),
658                TransitionOrAnimationEventType::TransitionRun => atom!("transitionrun"),
659                TransitionOrAnimationEventType::TransitionStart => atom!("transitionstart"),
660            };
661            let parent = EventInit {
662                bubbles: true,
663                cancelable: false,
664                composed: false,
665            };
666
667            let property_or_animation_name =
668                DOMString::from(event.property_or_animation_name.clone());
669            let pseudo_element = event
670                .pseudo_element
671                .map_or_else(DOMString::new, |pseudo_element| {
672                    DOMString::from(pseudo_element.to_css_string())
673                });
674            let elapsed_time = Finite::new(event.elapsed_time as f32).unwrap();
675            let window = node.owner_window();
676
677            if event.event_type.is_transition_event() {
678                let event_init = TransitionEventInit {
679                    parent,
680                    propertyName: property_or_animation_name,
681                    elapsedTime: elapsed_time,
682                    pseudoElement: pseudo_element,
683                };
684                TransitionEvent::new(cx, &window, event_atom, &event_init)
685                    .upcast::<Event>()
686                    .fire(cx, node.upcast());
687            } else {
688                let event_init = AnimationEventInit {
689                    parent,
690                    animationName: property_or_animation_name,
691                    elapsedTime: elapsed_time,
692                    pseudoElement: pseudo_element,
693                };
694                AnimationEvent::new(cx, &window, event_atom, &event_init)
695                    .upcast::<Event>()
696                    .fire(cx, node.upcast());
697            }
698        }
699
700        if self.pending_events.borrow().is_empty() {
701            self.handle_animation_presence_or_pending_events_change(window);
702        }
703    }
704}
705
706/// The type of transition event to trigger. These are defined by
707/// CSS Transitions § 6.1 and CSS Animations § 4.2
708#[derive(Clone, Debug, Deserialize, JSTraceable, MallocSizeOf, Serialize)]
709pub(crate) enum TransitionOrAnimationEventType {
710    /// "The transitionrun event occurs when a transition is created (i.e., when it
711    /// is added to the set of running transitions)."
712    TransitionRun,
713    /// "The transitionstart event occurs when a transition’s delay phase ends."
714    TransitionStart,
715    /// "The transitionend event occurs at the completion of the transition. In the
716    /// case where a transition is removed before completion, such as if the
717    /// transition-property is removed, then the event will not fire."
718    TransitionEnd,
719    /// "The transitioncancel event occurs when a transition is canceled."
720    TransitionCancel,
721    /// "The animationstart event occurs at the start of the animation. If there is
722    /// an animation-delay then this event will fire once the delay period has expired."
723    AnimationStart,
724    /// "The animationiteration event occurs at the end of each iteration of an
725    /// animation, except when an animationend event would fire at the same time."
726    AnimationIteration,
727    /// "The animationend event occurs when the animation finishes"
728    AnimationEnd,
729    /// "The animationcancel event occurs when the animation stops running in a way
730    /// that does not fire an animationend event..."
731    AnimationCancel,
732}
733
734impl TransitionOrAnimationEventType {
735    /// Whether or not this event is a transition-related event.
736    pub(crate) fn is_transition_event(&self) -> bool {
737        match *self {
738            Self::TransitionRun |
739            Self::TransitionEnd |
740            Self::TransitionCancel |
741            Self::TransitionStart => true,
742            Self::AnimationEnd |
743            Self::AnimationIteration |
744            Self::AnimationStart |
745            Self::AnimationCancel => false,
746        }
747    }
748}
749
750#[derive(Deserialize, JSTraceable, MallocSizeOf, Serialize)]
751/// A transition or animation event.
752pub(crate) struct TransitionOrAnimationEvent {
753    /// The pipeline id of the layout task that sent this message.
754    #[no_trace]
755    pub(crate) pipeline_id: PipelineId,
756    /// The type of transition event this should trigger.
757    pub(crate) event_type: TransitionOrAnimationEventType,
758    /// The address of the node which owns this transition.
759    #[no_trace]
760    pub(crate) node: OpaqueNode,
761    /// The pseudo element for this transition or animation, if applicable.
762    #[no_trace]
763    pub(crate) pseudo_element: Option<PseudoElement>,
764    /// The name of the property that is transitioning (in the case of a transition)
765    /// or the name of the animation (in the case of an animation).
766    pub(crate) property_or_animation_name: String,
767    /// The elapsed time property to send with this transition event.
768    pub(crate) elapsed_time: f64,
769}
770
771/// Inserts [opaque_node] into [rooted_nodes], if it isn't already present. This should be
772/// called immediately after a restyle, to ensure that these addresses are still valid.
773fn root_node(
774    rooted_nodes: &mut FxHashMap<NoTrace<OpaqueNode>, Dom<Node>>,
775    opaque_node: OpaqueNode,
776) {
777    #[expect(unsafe_code)]
778    rooted_nodes.entry(NoTrace(opaque_node)).or_insert_with(|| {
779        // SAFETY: This should be safe as this method is run directly after layout,
780        // which should not remove any nodes.
781        let address = UntrustedNodeAddress(opaque_node.0 as *const c_void);
782        unsafe { Dom::from_ref(&*from_untrusted_node_address(address)) }
783    });
784}