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