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