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