Skip to main content

script/dom/event/
event.rs

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