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