Skip to main content

script/dom/event/
event.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
5use std::cell::Cell;
6use std::default::Default;
7
8use bitflags::bitflags;
9use devtools_traits::{TimelineMarker, TimelineMarkerType};
10use dom_struct::dom_struct;
11use embedder_traits::InputEventResult;
12use js::context::JSContext;
13use js::rust::HandleObject;
14use keyboard_types::{Key, NamedKey};
15use script_bindings::cell::DomRefCell;
16use script_bindings::codegen::GenericBindings::PointerEventBinding::PointerEventMethods;
17use script_bindings::match_domstring_ascii;
18use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
19use servo_base::cross_process_instant::CrossProcessInstant;
20use stylo_atoms::Atom;
21
22use crate::dom::bindings::callback::ExceptionHandling;
23use crate::dom::bindings::codegen::Bindings::EventBinding;
24use crate::dom::bindings::codegen::Bindings::EventBinding::{EventConstants, EventMethods};
25use crate::dom::bindings::codegen::Bindings::NodeBinding::GetRootNodeOptions;
26use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
27use crate::dom::bindings::codegen::Bindings::PerformanceBinding::DOMHighResTimeStamp;
28use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{
29    ShadowRootMethods, ShadowRootMode,
30};
31use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
32use crate::dom::bindings::error::Fallible;
33use crate::dom::bindings::inheritance::Castable;
34use crate::dom::bindings::refcounted::Trusted;
35use crate::dom::bindings::reflector::DomGlobal;
36use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
37use crate::dom::bindings::str::DOMString;
38use crate::dom::element::Element;
39use crate::dom::eventtarget::{EventListeners, EventTarget, ListenerPhase};
40use crate::dom::globalscope::GlobalScope;
41use crate::dom::html::htmlslotelement::HTMLSlotElement;
42use crate::dom::html::input_element::InputActivationState;
43use crate::dom::mouseevent::MouseEvent;
44use crate::dom::node::{Node, NodeTraits};
45use crate::dom::shadowroot::ShadowRoot;
46use crate::dom::types::{KeyboardEvent, PointerEvent, UserActivation};
47use crate::dom::virtualmethods::vtable_for;
48use crate::dom::window::Window;
49use crate::script_runtime::CanGc;
50use crate::task::TaskOnce;
51
52/// <https://dom.spec.whatwg.org/#concept-event>
53#[dom_struct]
54pub(crate) struct Event {
55    reflector_: Reflector,
56
57    /// <https://dom.spec.whatwg.org/#dom-event-currenttarget>
58    current_target: MutNullableDom<EventTarget>,
59
60    /// <https://dom.spec.whatwg.org/#event-target>
61    target: MutNullableDom<EventTarget>,
62
63    /// <https://dom.spec.whatwg.org/#dom-event-type>
64    #[no_trace]
65    type_: DomRefCell<Atom>,
66
67    /// <https://dom.spec.whatwg.org/#dom-event-eventphase>
68    phase: Cell<EventPhase>,
69
70    /// The various specification-defined flags set on this event.
71    flags: Cell<EventFlags>,
72
73    /// <https://dom.spec.whatwg.org/#dom-event-cancelable>
74    cancelable: Cell<bool>,
75
76    /// <https://dom.spec.whatwg.org/#dom-event-bubbles>
77    bubbles: Cell<bool>,
78
79    /// <https://dom.spec.whatwg.org/#dom-event-istrusted>
80    is_trusted: Cell<bool>,
81
82    /// <https://dom.spec.whatwg.org/#dom-event-timestamp>
83    #[no_trace]
84    time_stamp: CrossProcessInstant,
85
86    /// <https://dom.spec.whatwg.org/#event-path>
87    path: DomRefCell<Vec<EventPathSegment>>,
88
89    /// <https://dom.spec.whatwg.org/#event-relatedtarget>
90    related_target: MutNullableDom<EventTarget>,
91}
92
93/// An element on an [event path](https://dom.spec.whatwg.org/#event-path)
94#[derive(JSTraceable, MallocSizeOf)]
95#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
96pub(crate) struct EventPathSegment {
97    /// <https://dom.spec.whatwg.org/#event-path-invocation-target>
98    invocation_target: Dom<EventTarget>,
99
100    /// <https://dom.spec.whatwg.org/#event-path-invocation-target-in-shadow-tree>
101    invocation_target_in_shadow_tree: bool,
102
103    /// <https://dom.spec.whatwg.org/#event-path-shadow-adjusted-target>
104    shadow_adjusted_target: Option<Dom<EventTarget>>,
105
106    /// <https://dom.spec.whatwg.org/#event-path-relatedtarget>
107    related_target: Option<Dom<EventTarget>>,
108
109    /// <https://dom.spec.whatwg.org/#event-path-root-of-closed-tree>
110    root_of_closed_tree: bool,
111
112    /// <https://dom.spec.whatwg.org/#event-path-slot-in-closed-tree>
113    slot_in_closed_tree: bool,
114}
115
116impl Event {
117    pub(crate) fn new_inherited() -> Event {
118        Event {
119            reflector_: Reflector::new(),
120            current_target: Default::default(),
121            target: Default::default(),
122            type_: DomRefCell::new(atom!("")),
123            phase: Cell::new(EventPhase::None),
124            flags: Cell::new(EventFlags::empty()),
125            cancelable: Cell::new(false),
126            bubbles: Cell::new(false),
127            is_trusted: Cell::new(false),
128            time_stamp: CrossProcessInstant::now(),
129            path: DomRefCell::default(),
130            related_target: Default::default(),
131        }
132    }
133
134    pub(crate) fn new_uninitialized(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Event> {
135        Self::new_uninitialized_with_proto(global, None, can_gc)
136    }
137
138    pub(crate) fn new_uninitialized_with_proto(
139        global: &GlobalScope,
140        proto: Option<HandleObject>,
141        can_gc: CanGc,
142    ) -> DomRoot<Event> {
143        reflect_dom_object_with_proto(Box::new(Event::new_inherited()), global, proto, can_gc)
144    }
145
146    pub(crate) fn new(
147        global: &GlobalScope,
148        type_: Atom,
149        bubbles: EventBubbles,
150        cancelable: EventCancelable,
151        can_gc: CanGc,
152    ) -> DomRoot<Event> {
153        Self::new_with_proto(global, None, type_, bubbles, cancelable, can_gc)
154    }
155
156    fn new_with_proto(
157        global: &GlobalScope,
158        proto: Option<HandleObject>,
159        type_: Atom,
160        bubbles: EventBubbles,
161        cancelable: EventCancelable,
162        can_gc: CanGc,
163    ) -> DomRoot<Event> {
164        let event = Event::new_uninitialized_with_proto(global, proto, can_gc);
165
166        // NOTE: The spec doesn't tell us to call init event here, it just happens to do what we need.
167        event.init_event(type_, bool::from(bubbles), bool::from(cancelable));
168        event
169    }
170
171    /// <https://dom.spec.whatwg.org/#dom-event-initevent>
172    /// and <https://dom.spec.whatwg.org/#concept-event-initialize>
173    pub(crate) fn init_event(&self, type_: Atom, bubbles: bool, cancelable: bool) {
174        // https://dom.spec.whatwg.org/#dom-event-initevent
175        if self.has_flag(EventFlags::Dispatch) {
176            return;
177        }
178
179        // https://dom.spec.whatwg.org/#concept-event-initialize
180        // Step 1. Set event’s initialized flag.
181        self.set_flags(EventFlags::Initialized);
182
183        // Step 2. Unset event’s stop propagation flag, stop immediate propagation flag, and canceled flag.
184        self.unset_flags(EventFlags::StopPropagation);
185        self.unset_flags(EventFlags::StopImmediatePropagation);
186        self.unset_flags(EventFlags::Canceled);
187
188        // This flag isn't in the specification, but we need to unset it anyway.
189        self.unset_flags(EventFlags::Handled);
190
191        // Step 3. Set event’s isTrusted attribute to false.
192        self.is_trusted.set(false);
193
194        // Step 4. Set event’s target to null.
195        self.target.set(None);
196
197        // Step 5. Set event’s type attribute to type.
198        *self.type_.borrow_mut() = type_;
199
200        // Step 6. Set event’s bubbles attribute to bubbles.
201        self.bubbles.set(bubbles);
202
203        // Step 7. Set event’s cancelable attribute to cancelable.
204        self.cancelable.set(cancelable);
205    }
206
207    fn set_flags(&self, flags_to_set: EventFlags) {
208        self.flags.set(self.flags.get().union(flags_to_set))
209    }
210
211    fn unset_flags(&self, flags_to_unset: EventFlags) {
212        let mut flags = self.flags.get();
213        flags.remove(flags_to_unset);
214        self.flags.set(flags);
215    }
216
217    fn has_flag(&self, flag: EventFlags) -> bool {
218        self.flags.get().contains(flag)
219    }
220
221    pub(crate) fn set_target(&self, target_: Option<&EventTarget>) {
222        self.target.set(target_);
223    }
224
225    pub(crate) fn set_related_target(&self, related_target: Option<&EventTarget>) {
226        self.related_target.set(related_target);
227    }
228
229    pub(crate) fn related_target(&self) -> Option<DomRoot<EventTarget>> {
230        self.related_target.get()
231    }
232
233    fn set_in_passive_listener(&self, value: bool) {
234        if value {
235            self.set_flags(EventFlags::InPassiveListener);
236        } else {
237            self.unset_flags(EventFlags::InPassiveListener);
238        }
239    }
240
241    /// <https://dom.spec.whatwg.org/#concept-event-path-append>
242    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
243    pub(crate) fn append_to_path(
244        &self,
245        invocation_target: &EventTarget,
246        shadow_adjusted_target: Option<&EventTarget>,
247        related_target: Option<&EventTarget>,
248        slot_in_closed_tree: bool,
249    ) {
250        // Step 1. Let invocationTargetInShadowTree be false.
251        let mut invocation_target_in_shadow_tree = false;
252
253        // Step 2. If invocationTarget is a node and its root is a shadow root,
254        // then set invocationTargetInShadowTree to true.
255        if invocation_target
256            .downcast::<Node>()
257            .is_some_and(Node::is_in_a_shadow_tree)
258        {
259            invocation_target_in_shadow_tree = true;
260        }
261
262        // Step 3. Let root-of-closed-tree be false.
263        let mut root_of_closed_tree = false;
264
265        // Step 4. If invocationTarget is a shadow root whose mode is "closed", then set root-of-closed-tree to true.
266        if invocation_target
267            .downcast::<ShadowRoot>()
268            .is_some_and(|shadow_root| shadow_root.Mode() == ShadowRootMode::Closed)
269        {
270            root_of_closed_tree = true;
271        }
272
273        // Step 5. Append a new struct to event’s path whose invocation target is invocationTarget,
274        // invocation-target-in-shadow-tree is invocationTargetInShadowTree, shadow-adjusted target is
275        // shadowAdjustedTarget, relatedTarget is relatedTarget, touch target list is touchTargets,
276        // root-of-closed-tree is root-of-closed-tree, and slot-in-closed-tree is slot-in-closed-tree.
277        let event_path_segment = EventPathSegment {
278            invocation_target: Dom::from_ref(invocation_target),
279            shadow_adjusted_target: shadow_adjusted_target.map(Dom::from_ref),
280            related_target: related_target.map(Dom::from_ref),
281            invocation_target_in_shadow_tree,
282            root_of_closed_tree,
283            slot_in_closed_tree,
284        };
285        self.path.borrow_mut().push(event_path_segment);
286    }
287
288    /// <https://dom.spec.whatwg.org/#concept-event-dispatch>
289    pub(crate) fn dispatch(
290        &self,
291        cx: &mut JSContext,
292        target: &EventTarget,
293        legacy_target_override: bool,
294    ) -> bool {
295        self.dispatch_inner(cx, target, legacy_target_override, None)
296    }
297
298    pub(crate) fn dispatch_with_legacy_output_did_listeners_throw(
299        &self,
300        cx: &mut JSContext,
301        target: &EventTarget,
302        legacy_target_override: bool,
303        legacy_output_did_listeners_throw: &Cell<bool>,
304    ) -> bool {
305        self.dispatch_inner(
306            cx,
307            target,
308            legacy_target_override,
309            Some(legacy_output_did_listeners_throw),
310        )
311    }
312
313    fn dispatch_inner(
314        &self,
315        cx: &mut JSContext,
316        target: &EventTarget,
317        legacy_target_override: bool,
318        legacy_output_did_listeners_throw: Option<&Cell<bool>>,
319    ) -> bool {
320        // > When a user interaction causes firing of an activation triggering input event in a Document document, the user agent
321        // > must perform the following activation notification steps before dispatching the event:
322        // <https://html.spec.whatwg.org/multipage/#user-activation-processing-model>
323        if self.is_an_activation_triggering_input_event() {
324            // TODO: it is not quite clear what does the spec mean by in a `Document`. https://github.com/whatwg/html/issues/12126
325            if let Some(document) = target.downcast::<Node>().map(|node| node.owner_doc()) {
326                UserActivation::handle_user_activation_notification(&document);
327            }
328        }
329
330        let mut target = DomRoot::from_ref(target);
331
332        // Save the original dispatch target. Keyboard default actions need the
333        // element the event was originally fired on, not the retargeted host.
334        let original_target = target.clone();
335
336        // Step 1. Set event’s dispatch flag.
337        self.set_flags(EventFlags::Dispatch);
338
339        // Step 2. Let targetOverride be target, if legacy target override flag is not given,
340        // and target’s associated Document otherwise.
341        let target_override_document; // upcasted EventTarget's lifetime depends on this
342        let target_override = if legacy_target_override {
343            target_override_document = target
344                .downcast::<Window>()
345                .expect("legacy_target_override must be true only when target is a Window")
346                .Document();
347            DomRoot::from_ref(target_override_document.upcast::<EventTarget>())
348        } else {
349            target.clone()
350        };
351
352        // Step 3. Let activationTarget be null.
353        let mut activation_target = None;
354
355        // Step 4. Let relatedTarget be the result of retargeting event’s relatedTarget against target.
356        let related_target = self
357            .related_target
358            .get()
359            .map(|related_target| related_target.retarget(&target));
360
361        // Step 5. Let clearTargets be false.
362        let mut clear_targets = false;
363
364        // Step 6. If target is not relatedTarget or target is event’s relatedTarget:
365        let mut pre_activation_result: Option<InputActivationState> = None;
366        if related_target.as_ref() != Some(&target) ||
367            self.related_target.get().as_ref() == Some(&target)
368        {
369            // Step 6.1. Let touchTargets be a new list.
370            // TODO
371
372            // Step 6.2. For each touchTarget of event’s touch target list, append the result of retargeting
373            // TODO
374
375            // touchTarget against target to touchTargets.
376
377            // Step 6.3. Append to an event path with event, target, targetOverride, relatedTarget,
378            // touchTargets, and false.
379            self.append_to_path(
380                &target,
381                Some(target_override.upcast::<EventTarget>()),
382                related_target.as_deref(),
383                false,
384            );
385
386            // Step 6.4. Let isActivationEvent be true, if event is a MouseEvent object and
387            // event’s type attribute is "click"; otherwise false.
388            let is_activation_event = self.is::<MouseEvent>() && self.type_() == atom!("click");
389
390            // Step 6.5. If isActivationEvent is true and target has activation behavior,
391            // then set activationTarget to target.
392            if is_activation_event &&
393                let Some(element) = target.downcast::<Element>() &&
394                element.as_maybe_activatable().is_some()
395            {
396                activation_target = Some(DomRoot::from_ref(element));
397            }
398
399            // Step 6.6. Let slottable be target, if target is a slottable and is assigned, and null otherwise.
400            let mut slottable = if target
401                .downcast::<Node>()
402                .and_then(Node::assigned_slot)
403                .is_some()
404            {
405                Some(target.clone())
406            } else {
407                None
408            };
409
410            // Step 6.7. Let slot-in-closed-tree be false
411            let mut slot_in_closed_tree = false;
412
413            // Step 6.8. Let parent be the result of invoking target’s get the parent with event.
414            let mut parent_or_none = target.get_the_parent(self);
415            let mut done = false;
416
417            // Step 6.9. While parent is non-null:
418            while let Some(parent) = parent_or_none.clone() {
419                // Step 6.9.1. If slottable is non-null:
420                if slottable.is_some() {
421                    // Step 6.9.1.1. Assert: parent is a slot.
422                    let slot = parent
423                        .downcast::<HTMLSlotElement>()
424                        .expect("parent of slottable is not a slot");
425
426                    // Step 6.9.1.2. Set slottable to null.
427                    slottable = None;
428
429                    // Step 6.9.1.3. If parent’s root is a shadow root whose mode is "closed",
430                    // then set slot-in-closed-tree to true.
431                    if slot
432                        .containing_shadow_root()
433                        .is_some_and(|root| root.Mode() == ShadowRootMode::Closed)
434                    {
435                        slot_in_closed_tree = true;
436                    }
437                }
438
439                // Step 6.9.2. If parent is a slottable and is assigned, then set slottable to parent.
440                if parent
441                    .downcast::<Node>()
442                    .and_then(Node::assigned_slot)
443                    .is_some()
444                {
445                    slottable = Some(parent.clone());
446                }
447
448                // Step 6.9.3. Let relatedTarget be the result of retargeting event’s relatedTarget against parent.
449                let related_target = self
450                    .related_target
451                    .get()
452                    .map(|related_target| related_target.retarget(&parent));
453
454                // Step 6.9.4. Let touchTargets be a new list.
455                // TODO
456
457                // Step 6.9.5. For each touchTarget of event’s touch target list, append the result of retargeting
458                // touchTarget against parent to touchTargets.
459                // TODO
460
461                // Step 6.9.6. If parent is a Window object, or parent is a node and target’s root is a
462                // shadow-including inclusive ancestor of parent:
463                let root_is_shadow_inclusive_ancestor = parent
464                    .downcast::<Node>()
465                    .zip(target.downcast::<Node>())
466                    .is_some_and(|(parent, target)| {
467                        target
468                            .GetRootNode(&GetRootNodeOptions::empty())
469                            .is_shadow_including_inclusive_ancestor_of(parent)
470                    });
471                if parent.is::<Window>() || root_is_shadow_inclusive_ancestor {
472                    // Step 6.9.6.1. If isActivationEvent is true, event’s bubbles attribute is true, activationTarget
473                    // is null, and parent has activation behavior, then set activationTarget to parent.
474                    if is_activation_event &&
475                        activation_target.is_none() &&
476                        self.bubbles.get() &&
477                        let Some(element) = parent.downcast::<Element>() &&
478                        element.as_maybe_activatable().is_some()
479                    {
480                        activation_target = Some(DomRoot::from_ref(element));
481                    }
482
483                    // Step 6.9.6.2. Append to an event path with event, parent, null, relatedTarget, touchTargets,
484                    // and slot-in-closed-tree.
485                    self.append_to_path(
486                        &parent,
487                        None,
488                        related_target.as_deref(),
489                        slot_in_closed_tree,
490                    );
491                }
492                // Step 6.9.7. Otherwise, if parent is relatedTarget, then set parent to null.
493                else if Some(&parent) == related_target.as_ref() {
494                    // NOTE: This causes some lifetime shenanigans. Instead of making things complicated,
495                    // we just remember to treat parent as null later
496                    done = true;
497                }
498                // Step 6.9.8. Otherwise:
499                else {
500                    // Step 6.9.8.1. Set target to parent.
501                    target = parent.clone();
502
503                    // Step 6.9.8.2. If isActivationEvent is true, activationTarget is null, and target has
504                    // activation behavior, then set activationTarget to target.
505                    if is_activation_event &&
506                        activation_target.is_none() &&
507                        let Some(element) = parent.downcast::<Element>() &&
508                        element.as_maybe_activatable().is_some()
509                    {
510                        activation_target = Some(DomRoot::from_ref(element));
511                    }
512
513                    // Step 6.9.8.3. Append to an event path with event, parent, target, relatedTarget,
514                    // touchTargets, and slot-in-closed-tree.
515                    self.append_to_path(
516                        &parent,
517                        Some(&target),
518                        related_target.as_deref(),
519                        slot_in_closed_tree,
520                    );
521                }
522
523                // Step 6.9.9. If parent is non-null, then set parent to the result of invoking parent’s
524                // get the parent with event
525                if !done {
526                    parent_or_none = parent.get_the_parent(self);
527                } else {
528                    parent_or_none = None;
529                }
530
531                // Step 6.9.10. Set slot-in-closed-tree to false.
532                slot_in_closed_tree = false;
533            }
534
535            // Step 6.10. Let clearTargetsStruct be the last struct in event’s path whose shadow-adjusted target
536            // is non-null.
537            // Step 6.11. Let clearTargets be true if clearTargetsStruct’s shadow-adjusted target,
538            // clearTargetsStruct’s relatedTarget, or an EventTarget object in clearTargetsStruct’s
539            // touch target list is a node and its root is a shadow root; otherwise false.
540            // TODO: Handle touch target list
541            clear_targets = self
542                .path
543                .borrow()
544                .iter()
545                .rev()
546                .find(|segment| segment.shadow_adjusted_target.is_some())
547                // This is "clearTargetsStruct"
548                .is_some_and(|clear_targets| {
549                    clear_targets
550                        .shadow_adjusted_target
551                        .as_ref()
552                        .and_then(|target| target.downcast::<Node>())
553                        .is_some_and(Node::is_in_a_shadow_tree) ||
554                        clear_targets
555                            .related_target
556                            .as_ref()
557                            .and_then(|target| target.downcast::<Node>())
558                            .is_some_and(Node::is_in_a_shadow_tree)
559                });
560
561            // Step 6.12. If activationTarget is non-null and activationTarget has legacy-pre-activation behavior,
562            // then run activationTarget’s legacy-pre-activation behavior.
563            if let Some(activation_target) = activation_target.as_ref() {
564                // Not specified in dispatch spec overtly; this is because
565                // the legacy canceled activation behavior of a checkbox
566                // or radio button needs to know what happened in the
567                // corresponding pre-activation behavior.
568                pre_activation_result = activation_target
569                    .as_maybe_activatable()
570                    .and_then(|activatable| activatable.legacy_pre_activation_behavior(cx));
571            }
572
573            let timeline_window = DomRoot::downcast::<Window>(target.global())
574                .filter(|window| window.need_emit_timeline_marker(TimelineMarkerType::DOMEvent));
575
576            // Step 6.13. For each struct in event’s path, in reverse order:
577            for (index, segment) in self.path.borrow().iter().enumerate().rev() {
578                // Step 6.13.1. If struct’s shadow-adjusted target is non-null, then set event’s
579                // eventPhase attribute to AT_TARGET.
580                if segment.shadow_adjusted_target.is_some() {
581                    self.phase.set(EventPhase::AtTarget);
582                }
583                // Step 6.13.2. Otherwise, set event’s eventPhase attribute to CAPTURING_PHASE.
584                else {
585                    self.phase.set(EventPhase::Capturing);
586                }
587
588                // Step 6.13.3. Invoke with struct, event, "capturing", and legacyOutputDidListenersThrowFlag if given.
589                invoke(
590                    cx,
591                    segment,
592                    index,
593                    self,
594                    ListenerPhase::Capturing,
595                    timeline_window.as_deref(),
596                    legacy_output_did_listeners_throw,
597                )
598            }
599
600            // Step 6.14. For each struct in event’s path:
601            for (index, segment) in self.path.borrow().iter().enumerate() {
602                // Step 6.14.1. If struct’s shadow-adjusted target is non-null, then set event’s
603                // eventPhase attribute to AT_TARGET.
604                if segment.shadow_adjusted_target.is_some() {
605                    self.phase.set(EventPhase::AtTarget);
606                }
607                // Step 6.14.2. Otherwise:
608                else {
609                    // Step 6.14.2.1. If event’s bubbles attribute is false, then continue.
610                    if !self.bubbles.get() {
611                        continue;
612                    }
613
614                    // Step 6.14.2.2. Set event’s eventPhase attribute to BUBBLING_PHASE.
615                    self.phase.set(EventPhase::Bubbling);
616                }
617
618                // Step 6.14.3. Invoke with struct, event, "bubbling", and legacyOutputDidListenersThrowFlag if given.
619                invoke(
620                    cx,
621                    segment,
622                    index,
623                    self,
624                    ListenerPhase::Bubbling,
625                    timeline_window.as_deref(),
626                    legacy_output_did_listeners_throw,
627                );
628            }
629        }
630
631        // Step 7. Set event’s eventPhase attribute to NONE.
632        self.phase.set(EventPhase::None);
633
634        // FIXME: The UIEvents spec still expects firing an event
635        // to carry a "default action" semantic, but the HTML spec
636        // has removed this concept. Nothing in either spec currently
637        // (as of Jan 11 2020) says that, e.g., a keydown event on an
638        // input element causes a character to be typed; the UIEvents
639        // spec assumes the HTML spec is covering it, and the HTML spec
640        // no longer specifies any UI event other than mouse click as
641        // causing an element to perform an action.
642        // Compare:
643        // https://w3c.github.io/uievents/#default-action
644        // https://dom.spec.whatwg.org/#action-versus-occurance
645        if !self.DefaultPrevented() {
646            if self.is::<KeyboardEvent>() {
647                // For keyboard events, use the original dispatch target rather than
648                // event.GetTarget(). Composed keyboard events may retarget across
649                // shadow boundaries, but the default action (character input, Tab
650                // navigation) should use the element the event was originally fired on.
651                if let Some(node) = original_target.downcast::<Node>() {
652                    let vtable = vtable_for(node);
653                    vtable.handle_event(cx, self);
654                }
655            } else if let Some(target) = self.GetTarget() &&
656                let Some(node) = target.downcast::<Node>()
657            {
658                let vtable = vtable_for(node);
659                vtable.handle_event(cx, self);
660            }
661        }
662
663        // Step 8. Set event’s currentTarget attribute to null.
664        self.current_target.set(None);
665
666        // Step 9. Set event’s path to the empty list.
667        self.path.borrow_mut().clear();
668
669        // Step 10. Unset event’s dispatch flag, stop propagation flag, and stop immediate propagation flag.
670        self.unset_flags(EventFlags::Dispatch);
671        self.unset_flags(EventFlags::StopPropagation);
672        self.unset_flags(EventFlags::StopImmediatePropagation);
673
674        // Step 11. If clearTargets is true:
675        if clear_targets {
676            // Step 11.1. Set event’s target to null.
677            self.target.set(None);
678
679            // Step 11.2. Set event’s relatedTarget to null.
680            self.related_target.set(None);
681
682            // Step 11.3. Set event’s touch target list to the empty list.
683            // TODO
684        }
685
686        // Step 12. If activationTarget is non-null:
687        if let Some(activation_target) = activation_target {
688            // NOTE: The activation target may have been disabled by an event handler
689            if let Some(activatable) = activation_target.as_maybe_activatable() {
690                // Step 12.1. If event’s canceled flag is unset, then run activationTarget’s
691                // activation behavior with event.
692                if !self.DefaultPrevented() {
693                    activatable.activation_behavior(cx, self, &target);
694                }
695                // Step 12.2. Otherwise, if activationTarget has legacy-canceled-activation behavior, then run
696                // activationTarget’s legacy-canceled-activation behavior.
697                else {
698                    activatable.legacy_canceled_activation_behavior(cx, pre_activation_result);
699                }
700            }
701        }
702
703        // Step 13. Return false if event’s canceled flag is set; otherwise true.
704        !self.DefaultPrevented()
705    }
706
707    #[inline]
708    pub(crate) fn dispatching(&self) -> bool {
709        self.has_flag(EventFlags::Dispatch)
710    }
711
712    #[inline]
713    pub(crate) fn initialized(&self) -> bool {
714        self.has_flag(EventFlags::Initialized)
715    }
716
717    #[inline]
718    pub(crate) fn type_(&self) -> Atom {
719        self.type_.borrow().clone()
720    }
721
722    #[inline]
723    pub(crate) fn mark_as_handled(&self) {
724        self.set_flags(EventFlags::Handled);
725    }
726
727    #[inline]
728    pub(crate) fn flags(&self) -> EventFlags {
729        self.flags.get()
730    }
731
732    pub(crate) fn set_trusted(&self, trusted: bool) {
733        self.is_trusted.set(trusted);
734    }
735
736    pub(crate) fn set_composed(&self, composed: bool) {
737        if composed {
738            self.set_flags(EventFlags::Composed);
739        } else {
740            self.unset_flags(EventFlags::Composed);
741        }
742    }
743
744    /// <https://html.spec.whatwg.org/multipage/#activation-triggering-input-event>
745    fn is_an_activation_triggering_input_event(&self) -> bool {
746        // > An activation triggering input event is any event whose isTrusted attribute is true ..
747        if !self.is_trusted.get() {
748            return false;
749        }
750
751        // > and whose type is one of:
752        let event_type = self.Type();
753        match_domstring_ascii!(event_type,
754            // > - "keydown", provided the key is neither the Esc key nor a shortcut key reserved by the user agent;
755            "keydown" => self.downcast::<KeyboardEvent>().expect("`Event` with type `keydown` should be a `KeyboardEvent` interface").key() != Key::Named(NamedKey::Escape),
756            // > - "mousedown";
757            "mousedown" => true,
758            // > - "pointerdown", provided the event's pointerType is "mouse";
759            "pointerdown" => self.downcast::<PointerEvent>().expect("`Event` with type `pointerdown` should be a `PointerEvent` interface").PointerType().eq("mouse"),
760            // > - "pointerup", provided the event's pointerType is not "mouse"; or
761            "pointerup" => !self.downcast::<PointerEvent>().expect("`Event` with type `pointerup` should be a `PointerEvent` interface").PointerType().eq("mouse"),
762            // > - "touchend".
763            "touchend" => true,
764            _ => false,
765        )
766    }
767
768    /// <https://dom.spec.whatwg.org/#firing-events>
769    #[expect(unsafe_code)]
770    pub(crate) fn fire(&self, target: &EventTarget, _can_gc: CanGc) -> bool {
771        self.set_trusted(true);
772
773        // TODO https://github.com/servo/servo/issues/44499
774        let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
775        let cx = &mut cx;
776
777        target.dispatch_event(cx, self)
778    }
779
780    pub(crate) fn fire_with_cx(&self, cx: &mut JSContext, target: &EventTarget) -> bool {
781        self.set_trusted(true);
782
783        target.dispatch_event(cx, self)
784    }
785
786    pub(crate) fn fire_with_legacy_output_did_listeners_throw(
787        &self,
788        cx: &mut JSContext,
789        target: &EventTarget,
790        legacy_output_did_listeners_throw: &Cell<bool>,
791    ) -> bool {
792        self.set_trusted(true);
793        self.dispatch_with_legacy_output_did_listeners_throw(
794            cx,
795            target,
796            false,
797            legacy_output_did_listeners_throw,
798        )
799    }
800
801    /// <https://dom.spec.whatwg.org/#inner-event-creation-steps>
802    fn inner_creation_steps(
803        global: &GlobalScope,
804        proto: Option<HandleObject>,
805        init: &EventBinding::EventInit,
806        can_gc: CanGc,
807    ) -> DomRoot<Event> {
808        // Step 1. Let event be the result of creating a new object using eventInterface.
809        // If realm is non-null, then use that realm; otherwise, use the default behavior defined in Web IDL.
810        let event = Event::new_uninitialized_with_proto(global, proto, can_gc);
811
812        // Step 2. Set event’s initialized flag.
813        event.set_flags(EventFlags::Initialized);
814
815        // Step 3. Initialize event’s timeStamp attribute to the relative high resolution
816        // coarse time given time and event’s relevant global object.
817        // NOTE: This is done inside Event::new_inherited
818
819        // Step 3. For each member → value in dictionary, if event has an attribute whose
820        // identifier is member, then initialize that attribute to value.#
821        event.bubbles.set(init.bubbles);
822        event.cancelable.set(init.cancelable);
823        event.set_composed(init.composed);
824
825        // Step 5. Run the event constructing steps with event and dictionary.
826        // NOTE: Event construction steps may be defined by subclasses
827
828        // Step 6. Return event.
829        event
830    }
831
832    /// Implements the logic behind the [get the parent](https://dom.spec.whatwg.org/#get-the-parent)
833    /// algorithm for shadow roots.
834    pub(crate) fn should_pass_shadow_boundary(&self, shadow_root: &ShadowRoot) -> bool {
835        debug_assert!(self.dispatching());
836
837        // > A shadow root’s get the parent algorithm, given an event, returns null if event’s composed flag
838        // > is unset and shadow root is the root of event’s path’s first struct’s invocation target;
839        // > otherwise shadow root’s host.
840        if self.Composed() {
841            return true;
842        }
843
844        let path = self.path.borrow();
845        let first_invocation_target = &path
846            .first()
847            .expect("Event path is empty despite event currently being dispatched")
848            .invocation_target
849            .as_rooted();
850
851        // The spec doesn't tell us what should happen if the invocation target is not a node
852        let Some(target_node) = first_invocation_target.downcast::<Node>() else {
853            return false;
854        };
855
856        &*target_node.GetRootNode(&GetRootNodeOptions::empty()) != shadow_root.upcast::<Node>()
857    }
858
859    /// <https://dom.spec.whatwg.org/#set-the-canceled-flag>
860    fn set_the_cancelled_flag(&self) {
861        if self.cancelable.get() && !self.has_flag(EventFlags::InPassiveListener) {
862            self.set_flags(EventFlags::Canceled);
863        }
864    }
865}
866
867impl EventMethods<crate::DomTypeHolder> for Event {
868    /// <https://dom.spec.whatwg.org/#concept-event-constructor>
869    fn Constructor(
870        global: &GlobalScope,
871        proto: Option<HandleObject>,
872        can_gc: CanGc,
873        type_: DOMString,
874        init: &EventBinding::EventInit,
875    ) -> Fallible<DomRoot<Event>> {
876        // Step 1. Let event be the result of running the inner event creation steps with
877        // this interface, null, now, and eventInitDict.
878        let event = Event::inner_creation_steps(global, proto, init, can_gc);
879
880        // Step 2. Initialize event’s type attribute to type.
881        *event.type_.borrow_mut() = Atom::from(type_);
882
883        // Step 3. Return event.
884        Ok(event)
885    }
886
887    /// <https://dom.spec.whatwg.org/#dom-event-eventphase>
888    fn EventPhase(&self) -> u16 {
889        self.phase.get() as u16
890    }
891
892    /// <https://dom.spec.whatwg.org/#dom-event-type>
893    fn Type(&self) -> DOMString {
894        DOMString::from(&*self.type_()) // FIXME(ajeffrey): Directly convert from Atom to DOMString
895    }
896
897    /// <https://dom.spec.whatwg.org/#dom-event-target>
898    fn GetTarget(&self) -> Option<DomRoot<EventTarget>> {
899        self.target.get()
900    }
901
902    /// <https://dom.spec.whatwg.org/#dom-event-srcelement>
903    fn GetSrcElement(&self) -> Option<DomRoot<EventTarget>> {
904        self.target.get()
905    }
906
907    /// <https://dom.spec.whatwg.org/#dom-event-currenttarget>
908    fn GetCurrentTarget(&self) -> Option<DomRoot<EventTarget>> {
909        self.current_target.get()
910    }
911
912    /// <https://dom.spec.whatwg.org/#dom-event-composedpath>
913    fn ComposedPath(&self) -> Vec<DomRoot<EventTarget>> {
914        // Step 1. Let composedPath be an empty list.
915        let mut composed_path = vec![];
916
917        // Step 2. Let path be this’s path.
918        let path = self.path.borrow();
919
920        // Step 3. If path is empty, then return composedPath.
921        if path.is_empty() {
922            return composed_path;
923        }
924
925        // Step 4. Let currentTarget be this’s currentTarget attribute value.
926        let current_target = self.GetCurrentTarget();
927
928        // Step 5. Append currentTarget to composedPath.
929        // TODO: https://github.com/whatwg/dom/issues/1343
930        composed_path.push(current_target.clone().expect(
931            "Since the event's path is not empty it is being dispatched and must have a current target",
932        ));
933
934        // Step 6. Let currentTargetIndex be 0.
935        let mut current_target_index = 0;
936
937        // Step 7. Let currentTargetHiddenSubtreeLevel be 0.
938        let mut current_target_hidden_subtree_level = 0;
939
940        // Step 8. Let index be path’s size − 1.
941        // Step 9. While index is greater than or equal to 0:
942        // NOTE: This is just iterating the path in reverse
943        for (index, element) in path.iter().enumerate().rev() {
944            // Step 9.1 If path[index]'s root-of-closed-tree is true, then increase
945            // currentTargetHiddenSubtreeLevel by 1.
946            if element.root_of_closed_tree {
947                current_target_hidden_subtree_level += 1;
948            }
949
950            // Step 9.2 If path[index]'s invocation target is currentTarget, then set
951            // currentTargetIndex to index and break.
952            if current_target
953                .as_ref()
954                .is_some_and(|target| target.as_traced() == element.invocation_target)
955            {
956                current_target_index = index;
957                break;
958            }
959
960            // Step 9.3 If path[index]'s slot-in-closed-tree is true, then decrease
961            // currentTargetHiddenSubtreeLevel by 1.
962            if element.slot_in_closed_tree {
963                current_target_hidden_subtree_level -= 1;
964            }
965
966            // Step 9.4 Decrease index by 1.
967        }
968
969        // Step 10. Let currentHiddenLevel and maxHiddenLevel be currentTargetHiddenSubtreeLevel.
970        let mut current_hidden_level = current_target_hidden_subtree_level;
971        let mut max_hidden_level = current_target_hidden_subtree_level;
972
973        // Step 11. Set index to currentTargetIndex − 1.
974        // Step 12. While index is greater than or equal to 0:
975        // NOTE: This is just iterating part of the path in reverse
976        for element in path.iter().take(current_target_index).rev() {
977            // Step 12.1 If path[index]'s root-of-closed-tree is true, then increase currentHiddenLevel by 1.
978            if element.root_of_closed_tree {
979                current_hidden_level += 1;
980            }
981
982            // Step 12.2 If currentHiddenLevel is less than or equal to maxHiddenLevel,
983            // then prepend path[index]'s invocation target to composedPath.
984            if current_hidden_level <= max_hidden_level {
985                composed_path.insert(0, element.invocation_target.as_rooted());
986            }
987
988            // Step 12.3 If path[index]'s slot-in-closed-tree is true:
989            if element.slot_in_closed_tree {
990                // Step 12.3.1 Decrease currentHiddenLevel by 1.
991                current_hidden_level -= 1;
992
993                // Step 12.3.2 If currentHiddenLevel is less than maxHiddenLevel, then set
994                // maxHiddenLevel to currentHiddenLevel.
995                if current_hidden_level < max_hidden_level {
996                    max_hidden_level = current_hidden_level;
997                }
998            }
999
1000            // Step 12.4 Decrease index by 1.
1001        }
1002
1003        // Step 13. Set currentHiddenLevel and maxHiddenLevel to currentTargetHiddenSubtreeLevel.
1004        current_hidden_level = current_target_hidden_subtree_level;
1005        max_hidden_level = current_target_hidden_subtree_level;
1006
1007        // Step 14. Set index to currentTargetIndex + 1.
1008        // Step 15. While index is less than path’s size:
1009        // NOTE: This is just iterating the list and skipping the first current_target_index + 1 elements
1010        //       (The +1 is necessary because the index is 0-based and the skip method is not)
1011        for element in path.iter().skip(current_target_index + 1) {
1012            // Step 15.1 If path[index]'s slot-in-closed-tree is true, then increase currentHiddenLevel by 1.
1013            if element.slot_in_closed_tree {
1014                current_hidden_level += 1;
1015            }
1016
1017            // Step 15.2 If currentHiddenLevel is less than or equal to maxHiddenLevel,
1018            // then append path[index]'s invocation target to composedPath.
1019            if current_hidden_level <= max_hidden_level {
1020                composed_path.push(element.invocation_target.as_rooted());
1021            }
1022
1023            // Step 15.3 If path[index]'s root-of-closed-tree is true:
1024            if element.root_of_closed_tree {
1025                // Step 15.3.1 Decrease currentHiddenLevel by 1.
1026                current_hidden_level -= 1;
1027
1028                // Step 15.3.2 If currentHiddenLevel is less than maxHiddenLevel, then set
1029                // maxHiddenLevel to currentHiddenLevel.
1030                if current_hidden_level < max_hidden_level {
1031                    max_hidden_level = current_hidden_level;
1032                }
1033            }
1034
1035            // Step 15.4 Increase index by 1.
1036        }
1037
1038        // Step 16. Return composedPath.
1039        composed_path
1040    }
1041
1042    /// <https://dom.spec.whatwg.org/#dom-event-defaultprevented>
1043    fn DefaultPrevented(&self) -> bool {
1044        self.has_flag(EventFlags::Canceled)
1045    }
1046
1047    /// <https://dom.spec.whatwg.org/#dom-event-composed>
1048    fn Composed(&self) -> bool {
1049        self.has_flag(EventFlags::Composed)
1050    }
1051
1052    /// <https://dom.spec.whatwg.org/#dom-event-preventdefault>
1053    fn PreventDefault(&self) {
1054        self.set_the_cancelled_flag();
1055    }
1056
1057    /// <https://dom.spec.whatwg.org/#dom-event-stoppropagation>
1058    fn StopPropagation(&self) {
1059        self.set_flags(EventFlags::StopPropagation);
1060    }
1061
1062    /// <https://dom.spec.whatwg.org/#dom-event-stopimmediatepropagation>
1063    fn StopImmediatePropagation(&self) {
1064        self.set_flags(EventFlags::StopPropagation | EventFlags::StopImmediatePropagation);
1065    }
1066
1067    /// <https://dom.spec.whatwg.org/#dom-event-bubbles>
1068    fn Bubbles(&self) -> bool {
1069        self.bubbles.get()
1070    }
1071
1072    /// <https://dom.spec.whatwg.org/#dom-event-cancelable>
1073    fn Cancelable(&self) -> bool {
1074        self.cancelable.get()
1075    }
1076
1077    /// <https://dom.spec.whatwg.org/#dom-event-returnvalue>
1078    fn ReturnValue(&self) -> bool {
1079        !self.has_flag(EventFlags::Canceled)
1080    }
1081
1082    /// <https://dom.spec.whatwg.org/#dom-event-returnvalue>
1083    fn SetReturnValue(&self, val: bool) {
1084        if !val {
1085            self.set_the_cancelled_flag();
1086        }
1087    }
1088
1089    /// <https://dom.spec.whatwg.org/#dom-event-cancelbubble>
1090    fn CancelBubble(&self) -> bool {
1091        self.has_flag(EventFlags::StopPropagation)
1092    }
1093
1094    /// <https://dom.spec.whatwg.org/#dom-event-cancelbubble>
1095    fn SetCancelBubble(&self, value: bool) {
1096        if value {
1097            self.set_flags(EventFlags::StopPropagation);
1098        }
1099    }
1100
1101    /// <https://dom.spec.whatwg.org/#dom-event-timestamp>
1102    fn TimeStamp(&self) -> DOMHighResTimeStamp {
1103        self.global()
1104            .performance()
1105            .to_dom_high_res_time_stamp(self.time_stamp)
1106    }
1107
1108    /// <https://dom.spec.whatwg.org/#dom-event-initevent>
1109    fn InitEvent(&self, type_: DOMString, bubbles: bool, cancelable: bool) {
1110        self.init_event(Atom::from(type_), bubbles, cancelable)
1111    }
1112
1113    /// <https://dom.spec.whatwg.org/#dom-event-istrusted>
1114    fn IsTrusted(&self) -> bool {
1115        self.is_trusted.get()
1116    }
1117}
1118
1119#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
1120pub(crate) enum EventBubbles {
1121    Bubbles,
1122    DoesNotBubble,
1123}
1124
1125impl From<bool> for EventBubbles {
1126    fn from(boolean: bool) -> Self {
1127        if boolean {
1128            EventBubbles::Bubbles
1129        } else {
1130            EventBubbles::DoesNotBubble
1131        }
1132    }
1133}
1134
1135impl From<EventBubbles> for bool {
1136    fn from(bubbles: EventBubbles) -> Self {
1137        match bubbles {
1138            EventBubbles::Bubbles => true,
1139            EventBubbles::DoesNotBubble => false,
1140        }
1141    }
1142}
1143
1144#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
1145pub(crate) enum EventCancelable {
1146    Cancelable,
1147    NotCancelable,
1148}
1149
1150impl From<bool> for EventCancelable {
1151    fn from(boolean: bool) -> Self {
1152        if boolean {
1153            EventCancelable::Cancelable
1154        } else {
1155            EventCancelable::NotCancelable
1156        }
1157    }
1158}
1159
1160impl From<EventCancelable> for bool {
1161    fn from(cancelable: EventCancelable) -> Self {
1162        match cancelable {
1163            EventCancelable::Cancelable => true,
1164            EventCancelable::NotCancelable => false,
1165        }
1166    }
1167}
1168
1169#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
1170pub(crate) enum EventComposed {
1171    Composed,
1172    NotComposed,
1173}
1174
1175impl From<bool> for EventComposed {
1176    fn from(boolean: bool) -> Self {
1177        if boolean {
1178            EventComposed::Composed
1179        } else {
1180            EventComposed::NotComposed
1181        }
1182    }
1183}
1184
1185impl From<EventComposed> for bool {
1186    fn from(composed: EventComposed) -> Self {
1187        match composed {
1188            EventComposed::Composed => true,
1189            EventComposed::NotComposed => false,
1190        }
1191    }
1192}
1193
1194#[derive(Clone, Copy, Debug, Eq, JSTraceable, PartialEq)]
1195#[repr(u16)]
1196#[derive(MallocSizeOf)]
1197pub(crate) enum EventPhase {
1198    None = EventConstants::NONE,
1199    Capturing = EventConstants::CAPTURING_PHASE,
1200    AtTarget = EventConstants::AT_TARGET,
1201    Bubbling = EventConstants::BUBBLING_PHASE,
1202}
1203
1204/// [`EventFlags`] tracks which specification-defined flags in an [`Event`] are enabled.
1205#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
1206pub(crate) struct EventFlags(u8);
1207
1208bitflags! {
1209    impl EventFlags: u8 {
1210        /// <https://dom.spec.whatwg.org/#canceled-flag>
1211        const Canceled = 1 << 0;
1212        /// <https://dom.spec.whatwg.org/#composed-flag>
1213        const Composed = 1 << 1;
1214        /// <https://dom.spec.whatwg.org/#dispatch-flag>
1215        const Dispatch =  1 << 2;
1216        /// The event has been handled somewhere in the DOM, and it should be prevented from being
1217        /// re-handled elsewhere. This doesn't affect the judgement of `DefaultPrevented`
1218        const Handled =  1 << 3;
1219        /// <https://dom.spec.whatwg.org/#in-passive-listener-flag>
1220        const InPassiveListener =  1 << 4;
1221        /// <https://dom.spec.whatwg.org/#initialized-flag>
1222        const Initialized =  1 << 5;
1223        /// <https://dom.spec.whatwg.org/#stop-propagation-flag>
1224        const StopPropagation = 1 << 6;
1225        /// <https://dom.spec.whatwg.org/#stop-immediate-propagation-flag>
1226        const StopImmediatePropagation = 1 << 7;
1227    }
1228}
1229
1230impl From<EventFlags> for InputEventResult {
1231    fn from(event_flags: EventFlags) -> Self {
1232        let mut result = Self::default();
1233        if event_flags.contains(EventFlags::Canceled) {
1234            result |= Self::DefaultPrevented;
1235        }
1236        if event_flags.contains(EventFlags::Handled) {
1237            result |= Self::Consumed;
1238        }
1239        result
1240    }
1241}
1242
1243/// <https://dom.spec.whatwg.org/#concept-event-fire>
1244pub(crate) struct EventTask {
1245    pub(crate) target: Trusted<EventTarget>,
1246    pub(crate) name: Atom,
1247    pub(crate) bubbles: EventBubbles,
1248    pub(crate) cancelable: EventCancelable,
1249}
1250
1251impl TaskOnce for EventTask {
1252    fn run_once(self, cx: &mut JSContext) {
1253        let target = self.target.root();
1254        let bubbles = self.bubbles;
1255        let cancelable = self.cancelable;
1256        target.fire_event_with_params(
1257            cx,
1258            self.name,
1259            bubbles,
1260            cancelable,
1261            EventComposed::NotComposed,
1262        );
1263    }
1264}
1265
1266/// <https://html.spec.whatwg.org/multipage/#fire-a-simple-event>
1267pub(crate) struct SimpleEventTask {
1268    pub(crate) target: Trusted<EventTarget>,
1269    pub(crate) name: Atom,
1270}
1271
1272impl TaskOnce for SimpleEventTask {
1273    fn run_once(self, cx: &mut JSContext) {
1274        let target = self.target.root();
1275        target.fire_event(cx, self.name);
1276    }
1277}
1278
1279/// <https://dom.spec.whatwg.org/#concept-event-listener-invoke>
1280fn invoke(
1281    cx: &mut JSContext,
1282    segment: &EventPathSegment,
1283    segment_index_in_path: usize,
1284    event: &Event,
1285    phase: ListenerPhase,
1286    timeline_window: Option<&Window>,
1287    legacy_output_did_listeners_throw: Option<&Cell<bool>>,
1288) {
1289    // Step 1. Set event’s target to the shadow-adjusted target of the last struct in event’s path,
1290    // that is either struct or preceding struct, whose shadow-adjusted target is non-null.
1291    event.target.set(
1292        event.path.borrow()[..segment_index_in_path + 1]
1293            .iter()
1294            .rev()
1295            .flat_map(|segment| segment.shadow_adjusted_target.clone())
1296            .next()
1297            .as_deref(),
1298    );
1299
1300    // Step 2. Set event’s relatedTarget to struct’s relatedTarget.
1301    event.related_target.set(segment.related_target.as_deref());
1302
1303    // TODO: Set event’s touch target list to struct’s touch target list.
1304
1305    // Step 4. If event’s stop propagation flag is set, then return.
1306    if event.has_flag(EventFlags::StopPropagation) {
1307        return;
1308    }
1309
1310    // Step 5. Initialize event’s currentTarget attribute to struct’s invocation target.
1311    event.current_target.set(Some(&segment.invocation_target));
1312
1313    // Step 6. Let listeners be a clone of event’s currentTarget attribute value’s event listener list.
1314    let listeners = segment.invocation_target.get_listeners_for(&event.type_());
1315
1316    // Step 7. Let invocationTargetInShadowTree be struct’s invocation-target-in-shadow-tree.
1317    let invocation_target_in_shadow_tree = segment.invocation_target_in_shadow_tree;
1318
1319    // Step 8. Let found be the result of running inner invoke with event, listeners, phase,
1320    // invocationTargetInShadowTree, and legacyOutputDidListenersThrowFlag if given.
1321    let found = inner_invoke(
1322        cx,
1323        event,
1324        &listeners,
1325        phase,
1326        invocation_target_in_shadow_tree,
1327        timeline_window,
1328        legacy_output_did_listeners_throw,
1329    );
1330
1331    // Step 9. If found is false and event’s isTrusted attribute is true:
1332    if !found && event.is_trusted.get() {
1333        // Step 9.1 Let originalEventType be event’s type attribute value.
1334        let original_type = event.type_();
1335
1336        // Step 9.2 If event’s type attribute value is a match for any of the strings in the first column
1337        // in the following table, set event’s type attribute value to the string in the second column on
1338        // the same row as the matching string, and return otherwise.
1339        let legacy_type = match event.type_() {
1340            atom!("animationend") => atom!("webkitAnimationEnd"),
1341            atom!("animationiteration") => atom!("webkitAnimationIteration"),
1342            atom!("animationstart") => atom!("webkitAnimationStart"),
1343            atom!("transitionend") => atom!("webkitTransitionEnd"),
1344            atom!("transitionrun") => atom!("webkitTransitionRun"),
1345            _ => return,
1346        };
1347        *event.type_.borrow_mut() = legacy_type;
1348
1349        // Step 9.3 Inner invoke with event, listeners, phase, invocationTargetInShadowTree,
1350        // and legacyOutputDidListenersThrowFlag if given.
1351        inner_invoke(
1352            cx,
1353            event,
1354            &listeners,
1355            phase,
1356            invocation_target_in_shadow_tree,
1357            timeline_window,
1358            legacy_output_did_listeners_throw,
1359        );
1360
1361        // Step 9.4 Set event’s type attribute value to originalEventType.
1362        *event.type_.borrow_mut() = original_type;
1363    }
1364}
1365
1366/// <https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke>
1367fn inner_invoke(
1368    cx: &mut JSContext,
1369    event: &Event,
1370    listeners: &EventListeners,
1371    phase: ListenerPhase,
1372    invocation_target_in_shadow_tree: bool,
1373    timeline_window: Option<&Window>,
1374    legacy_output_did_listeners_throw: Option<&Cell<bool>>,
1375) -> bool {
1376    // Step 1. Let found be false.
1377    let mut found = false;
1378
1379    // Step 2. For each listener in listeners, whose removed is false:
1380    for listener in listeners.iter() {
1381        if listener.borrow().removed() {
1382            continue;
1383        }
1384
1385        // Step 2.1 If event’s type attribute value is not listener’s type, then continue.
1386
1387        // Step 2.2. Set found to true.
1388        found = true;
1389
1390        // Step 2.3 If phase is "capturing" and listener’s capture is false, then continue.
1391        // Step 2.4 If phase is "bubbling" and listener’s capture is true, then continue.
1392        if listener.borrow().phase() != phase {
1393            continue;
1394        }
1395
1396        let event_target = event
1397            .GetCurrentTarget()
1398            .expect("event target was initialized as part of \"invoke\"");
1399
1400        // Step 2.5 If listener’s once is true, then remove an event listener given event’s currentTarget
1401        // attribute value and listener.
1402        if listener.borrow().once() {
1403            event_target.remove_listener(&event.type_(), listener);
1404        }
1405
1406        let Some(compiled_listener) =
1407            listener
1408                .borrow()
1409                .get_compiled_listener(cx, &event_target, &event.type_())
1410        else {
1411            continue;
1412        };
1413
1414        // Step 2.6 Let global be listener callback’s associated realm’s global object.
1415        let global = compiled_listener.associated_global();
1416
1417        // Step 2.7 Let currentEvent be undefined.
1418        let mut current_event = None;
1419        // Step 2.8 If global is a Window object:
1420        if let Some(window) = global.downcast::<Window>() {
1421            // Step 2.8.1 Set currentEvent to global’s current event.
1422            current_event = window.current_event();
1423
1424            // Step 2.8.2 If invocationTargetInShadowTree is false, then set global’s current event to event.
1425            if !invocation_target_in_shadow_tree {
1426                current_event = window.set_current_event(Some(event))
1427            }
1428        }
1429
1430        // Step 2.9 If listener’s passive is true, then set event's in passive listener flag.
1431        event.set_in_passive_listener(event_target.is_passive(listener));
1432
1433        // Step 2.10 If global is a Window object, then record timing info for event listener
1434        // given event and listener.
1435        // Step 2.11 Call a user object’s operation with listener’s callback, "handleEvent", « event »,
1436        // and event’s currentTarget attribute value. If this throws an exception exception:
1437        //     Step 2.10.1 Report exception for listener’s callback’s corresponding JavaScript object’s
1438        //     associated realm’s global object.
1439        //     Step 2.10.2 Set legacyOutputDidListenersThrowFlag if given.
1440        let marker = TimelineMarker::start("DOMEvent".to_owned());
1441        if compiled_listener
1442            .call_or_handle_event(cx, &event_target, event, ExceptionHandling::Report)
1443            .is_err() &&
1444            let Some(flag) = legacy_output_did_listeners_throw
1445        {
1446            flag.set(true);
1447        }
1448        if let Some(window) = timeline_window {
1449            window.emit_timeline_marker(marker.end());
1450        }
1451
1452        // Step 2.12 Unset event’s in passive listener flag.
1453        event.set_in_passive_listener(false);
1454
1455        // Step 2.13 If global is a Window object, then set global’s current event to currentEvent.
1456        if let Some(window) = global.downcast::<Window>() {
1457            window.set_current_event(current_event.as_deref());
1458        }
1459
1460        // Step 2.13: If event’s stop immediate propagation flag is set, then break.
1461        if event.has_flag(EventFlags::StopImmediatePropagation) {
1462            break;
1463        }
1464    }
1465
1466    // Step 3.
1467    found
1468}