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