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