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