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