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