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