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