/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/script/dom/event/event.rs
1 435 строк
55 KB
Tim van der Lippe
script: Remove `reflect_dom_object_with_proto_and_cx` (#47092)
09 авг 2026, 00:46
Не верифицирован
09 авг 2026, 00:46
9978b48
Код
Авторство
О чём код?
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::Cell; use std::default::Default; use bitflags::bitflags; use devtools_traits::{TimelineMarker, TimelineMarkerType}; use dom_struct::dom_struct; use embedder_traits::InputEventResult; use js::context::JSContext; use js::rust::HandleObject; use keyboard_types::{Key, NamedKey}; use script_bindings::cell::DomRefCell; use script_bindings::codegen::GenericBindings::PointerEventBinding::PointerEventMethods; use script_bindings::match_domstring_ascii; use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto}; use servo_base::cross_process_instant::CrossProcessInstant; use stylo_atoms::Atom; use crate::dom::bindings::callback::ExceptionHandling; use crate::dom::bindings::codegen::Bindings::EventBinding; use crate::dom::bindings::codegen::Bindings::EventBinding::{EventConstants, EventMethods}; use crate::dom::bindings::codegen::Bindings::NodeBinding::GetRootNodeOptions; use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods; use crate::dom::bindings::codegen::Bindings::PerformanceBinding::DOMHighResTimeStamp; use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{ ShadowRootMethods, ShadowRootMode, }; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::error::Fallible; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::DomGlobal; use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; use crate::dom::bindings::str::DOMString; use crate::dom::element::Element; use crate::dom::eventtarget::{EventListeners, EventTarget, ListenerPhase}; use crate::dom::globalscope::GlobalScope; use crate::dom::html::form_controls::htmlinputelement::InputActivationState; use crate::dom::html::htmlslotelement::HTMLSlotElement; use crate::dom::mouseevent::MouseEvent; use crate::dom::node::virtualmethods::vtable_for; use crate::dom::node::{Node, NodeTraits}; use crate::dom::shadowroot::ShadowRoot; use crate::dom::types::{KeyboardEvent, PointerEvent, UserActivation}; use crate::dom::window::Window; use crate::tasks::task::TaskOnce; /// <https://dom.spec.whatwg.org/#concept-event> #[dom_struct] pub(crate) struct Event { reflector_: Reflector, /// <https://dom.spec.whatwg.org/#dom-event-currenttarget> current_target: MutNullableDom<EventTarget>, /// <https://dom.spec.whatwg.org/#event-target> target: MutNullableDom<EventTarget>, /// <https://dom.spec.whatwg.org/#dom-event-type> #[no_trace] type_: DomRefCell<Atom>, /// <https://dom.spec.whatwg.org/#dom-event-eventphase> phase: Cell<EventPhase>, /// The various specification-defined flags set on this event. flags: Cell<EventFlags>, /// <https://dom.spec.whatwg.org/#dom-event-cancelable> cancelable: Cell<bool>, /// <https://dom.spec.whatwg.org/#dom-event-bubbles> bubbles: Cell<bool>, /// <https://dom.spec.whatwg.org/#dom-event-istrusted> is_trusted: Cell<bool>, /// <https://dom.spec.whatwg.org/#dom-event-timestamp> #[no_trace] time_stamp: CrossProcessInstant, /// <https://dom.spec.whatwg.org/#event-path> path: DomRefCell<Vec<EventPathSegment>>, /// <https://dom.spec.whatwg.org/#event-relatedtarget> related_target: MutNullableDom<EventTarget>, } /// An element on an [event path](https://dom.spec.whatwg.org/#event-path) #[derive(JSTraceable, MallocSizeOf)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] pub(crate) struct EventPathSegment { /// <https://dom.spec.whatwg.org/#event-path-invocation-target> invocation_target: Dom<EventTarget>, /// <https://dom.spec.whatwg.org/#event-path-invocation-target-in-shadow-tree> invocation_target_in_shadow_tree: bool, /// <https://dom.spec.whatwg.org/#event-path-shadow-adjusted-target> shadow_adjusted_target: Option<Dom<EventTarget>>, /// <https://dom.spec.whatwg.org/#event-path-relatedtarget> related_target: Option<Dom<EventTarget>>, /// <https://dom.spec.whatwg.org/#event-path-root-of-closed-tree> root_of_closed_tree: bool, /// <https://dom.spec.whatwg.org/#event-path-slot-in-closed-tree> slot_in_closed_tree: bool, } impl Event { pub(crate) fn new_inherited() -> Event { Event { reflector_: Reflector::new(), current_target: Default::default(), target: Default::default(), type_: DomRefCell::new(atom!("")), phase: Cell::new(EventPhase::None), flags: Cell::new(EventFlags::empty()), cancelable: Cell::new(false), bubbles: Cell::new(false), is_trusted: Cell::new(false), time_stamp: CrossProcessInstant::now(), path: DomRefCell::default(), related_target: Default::default(), } } pub(crate) fn new_uninitialized(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<Event> { Self::new_uninitialized_with_proto(cx, global, None) } pub(crate) fn new_uninitialized_with_proto( cx: &mut JSContext, global: &GlobalScope, proto: Option<HandleObject>, ) -> DomRoot<Event> { reflect_dom_object_with_proto(cx, Box::new(Event::new_inherited()), global, proto) } pub(crate) fn new( cx: &mut JSContext, global: &GlobalScope, type_: Atom, bubbles: EventBubbles, cancelable: EventCancelable, ) -> DomRoot<Event> { Self::new_with_proto(cx, global, None, type_, bubbles, cancelable) } fn new_with_proto( cx: &mut JSContext, global: &GlobalScope, proto: Option<HandleObject>, type_: Atom, bubbles: EventBubbles, cancelable: EventCancelable, ) -> DomRoot<Event> { let event = Event::new_uninitialized_with_proto(cx, global, proto); // NOTE: The spec doesn't tell us to call init event here, it just happens to do what we need. event.init_event(type_, bool::from(bubbles), bool::from(cancelable)); event } /// <https://dom.spec.whatwg.org/#dom-event-initevent> /// and <https://dom.spec.whatwg.org/#concept-event-initialize> pub(crate) fn init_event(&self, type_: Atom, bubbles: bool, cancelable: bool) { // https://dom.spec.whatwg.org/#dom-event-initevent if self.has_flag(EventFlags::Dispatch) { return; } // https://dom.spec.whatwg.org/#concept-event-initialize // Step 1. Set event’s initialized flag. self.set_flags(EventFlags::Initialized); // Step 2. Unset event’s stop propagation flag, stop immediate propagation flag, and canceled flag. self.unset_flags(EventFlags::StopPropagation); self.unset_flags(EventFlags::StopImmediatePropagation); self.unset_flags(EventFlags::Canceled); // This flag isn't in the specification, but we need to unset it anyway. self.unset_flags(EventFlags::Handled); // Step 3. Set event’s isTrusted attribute to false. self.is_trusted.set(false); // Step 4. Set event’s target to null. self.target.set(None); // Step 5. Set event’s type attribute to type. *self.type_.borrow_mut() = type_; // Step 6. Set event’s bubbles attribute to bubbles. self.bubbles.set(bubbles); // Step 7. Set event’s cancelable attribute to cancelable. self.cancelable.set(cancelable); } fn set_flags(&self, flags_to_set: EventFlags) { self.flags.set(self.flags.get().union(flags_to_set)) } fn unset_flags(&self, flags_to_unset: EventFlags) { let mut flags = self.flags.get(); flags.remove(flags_to_unset); self.flags.set(flags); } fn has_flag(&self, flag: EventFlags) -> bool { self.flags.get().contains(flag) } pub(crate) fn set_target(&self, target_: Option<&EventTarget>) { self.target.set(target_); } pub(crate) fn set_related_target(&self, related_target: Option<&EventTarget>) { self.related_target.set(related_target); } pub(crate) fn related_target(&self) -> Option<DomRoot<EventTarget>> { self.related_target.get() } fn set_in_passive_listener(&self, value: bool) { if value { self.set_flags(EventFlags::InPassiveListener); } else { self.unset_flags(EventFlags::InPassiveListener); } } /// <https://dom.spec.whatwg.org/#concept-event-path-append> #[cfg_attr(crown, expect(crown::unrooted_must_root))] pub(crate) fn append_to_path( &self, invocation_target: &EventTarget, shadow_adjusted_target: Option<&EventTarget>, related_target: Option<&EventTarget>, slot_in_closed_tree: bool, ) { // Step 1. Let invocationTargetInShadowTree be false. let mut invocation_target_in_shadow_tree = false; // Step 2. If invocationTarget is a node and its root is a shadow root, // then set invocationTargetInShadowTree to true. if invocation_target .downcast::<Node>() .is_some_and(Node::is_in_a_shadow_tree) { invocation_target_in_shadow_tree = true; } // Step 3. Let root-of-closed-tree be false. let mut root_of_closed_tree = false; // Step 4. If invocationTarget is a shadow root whose mode is "closed", then set root-of-closed-tree to true. if invocation_target .downcast::<ShadowRoot>() .is_some_and(|shadow_root| shadow_root.Mode() == ShadowRootMode::Closed) { root_of_closed_tree = true; } // Step 5. Append a new struct to event’s path whose invocation target is invocationTarget, // invocation-target-in-shadow-tree is invocationTargetInShadowTree, shadow-adjusted target is // shadowAdjustedTarget, relatedTarget is relatedTarget, touch target list is touchTargets, // root-of-closed-tree is root-of-closed-tree, and slot-in-closed-tree is slot-in-closed-tree. let event_path_segment = EventPathSegment { invocation_target: Dom::from_ref(invocation_target), shadow_adjusted_target: shadow_adjusted_target.map(Dom::from_ref), related_target: related_target.map(Dom::from_ref), invocation_target_in_shadow_tree, root_of_closed_tree, slot_in_closed_tree, }; self.path.borrow_mut().push(event_path_segment); } /// <https://dom.spec.whatwg.org/#concept-event-dispatch> pub(crate) fn dispatch( &self, cx: &mut JSContext, target: &EventTarget, legacy_target_override: bool, ) -> bool { self.dispatch_inner(cx, target, legacy_target_override, None) } fn dispatch_inner( &self, cx: &mut JSContext, target: &EventTarget, legacy_target_override: bool, legacy_output_did_listeners_throw: Option<&Cell<bool>>, ) -> bool { // > When a user interaction causes firing of an activation triggering input event in a Document document, the user agent // > must perform the following activation notification steps before dispatching the event: // <https://html.spec.whatwg.org/multipage/#user-activation-processing-model> if self.is_an_activation_triggering_input_event() { // TODO: it is not quite clear what does the spec mean by in a `Document`. https://github.com/whatwg/html/issues/12126 if let Some(document) = target.downcast::<Node>().map(|node| node.owner_doc()) { UserActivation::handle_user_activation_notification(&document); } } let mut target = DomRoot::from_ref(target); // Save the original dispatch target. Keyboard default actions need the // element the event was originally fired on, not the retargeted host. let original_target = target.clone(); // Step 1. Set event’s dispatch flag. self.set_flags(EventFlags::Dispatch); // Step 2. Let targetOverride be target, if legacy target override flag is not given, // and target’s associated Document otherwise. let target_override_document; // upcasted EventTarget's lifetime depends on this let target_override = if legacy_target_override { target_override_document = target .downcast::<Window>() .expect("legacy_target_override must be true only when target is a Window") .Document(); DomRoot::from_ref(target_override_document.upcast::<EventTarget>()) } else { target.clone() }; // Step 3. Let activationTarget be null. let mut activation_target = None; // Step 4. Let relatedTarget be the result of retargeting event’s relatedTarget against target. let related_target = self .related_target .get() .map(|related_target| related_target.retarget(&target)); // Step 5. Let clearTargets be false. let mut clear_targets = false; // Step 6. If target is not relatedTarget or target is event’s relatedTarget: let mut pre_activation_result: Option<InputActivationState> = None; if related_target.as_ref() != Some(&target) || self.related_target.get().as_ref() == Some(&target) { // Step 6.1. Let touchTargets be a new list. // TODO // Step 6.2. For each touchTarget of event’s touch target list, append the result of retargeting // TODO // touchTarget against target to touchTargets. // Step 6.3. Append to an event path with event, target, targetOverride, relatedTarget, // touchTargets, and false. self.append_to_path( &target, Some(target_override.upcast::<EventTarget>()), related_target.as_deref(), false, ); // Step 6.4. Let isActivationEvent be true, if event is a MouseEvent object and // event’s type attribute is "click"; otherwise false. let is_activation_event = self.is::<MouseEvent>() && self.type_() == atom!("click"); // Step 6.5. If isActivationEvent is true and target has activation behavior, // then set activationTarget to target. if is_activation_event && let Some(element) = target.downcast::<Element>() && element.as_maybe_activatable().is_some() { activation_target = Some(DomRoot::from_ref(element)); } // Step 6.6. Let slottable be target, if target is a slottable and is assigned, and null otherwise. let mut slottable = if target .downcast::<Node>() .and_then(Node::assigned_slot) .is_some() { Some(target.clone()) } else { None }; // Step 6.7. Let slot-in-closed-tree be false let mut slot_in_closed_tree = false; // Step 6.8. Let parent be the result of invoking target’s get the parent with event. let mut parent_or_none = target.get_the_parent(self); let mut done = false; // Step 6.9. While parent is non-null: while let Some(parent) = parent_or_none.clone() { // Step 6.9.1. If slottable is non-null: if slottable.is_some() { // Step 6.9.1.1. Assert: parent is a slot. let slot = parent .downcast::<HTMLSlotElement>() .expect("parent of slottable is not a slot"); // Step 6.9.1.2. Set slottable to null. slottable = None; // Step 6.9.1.3. If parent’s root is a shadow root whose mode is "closed", // then set slot-in-closed-tree to true. if slot .containing_shadow_root() .is_some_and(|root| root.Mode() == ShadowRootMode::Closed) { slot_in_closed_tree = true; } } // Step 6.9.2. If parent is a slottable and is assigned, then set slottable to parent. if parent .downcast::<Node>() .and_then(Node::assigned_slot) .is_some() { slottable = Some(parent.clone()); } // Step 6.9.3. Let relatedTarget be the result of retargeting event’s relatedTarget against parent. let related_target = self .related_target .get() .map(|related_target| related_target.retarget(&parent)); // Step 6.9.4. Let touchTargets be a new list. // TODO // Step 6.9.5. For each touchTarget of event’s touch target list, append the result of retargeting // touchTarget against parent to touchTargets. // TODO // Step 6.9.6. If parent is a Window object, or parent is a node and target’s root is a // shadow-including inclusive ancestor of parent: let root_is_shadow_inclusive_ancestor = parent .downcast::<Node>() .zip(target.downcast::<Node>()) .is_some_and(|(parent, target)| { target .GetRootNode(&GetRootNodeOptions::empty()) .is_shadow_including_inclusive_ancestor_of(parent) }); if parent.is::<Window>() || root_is_shadow_inclusive_ancestor { // Step 6.9.6.1. If isActivationEvent is true, event’s bubbles attribute is true, activationTarget // is null, and parent has activation behavior, then set activationTarget to parent. if is_activation_event && activation_target.is_none() && self.bubbles.get() && let Some(element) = parent.downcast::<Element>() && element.as_maybe_activatable().is_some() { activation_target = Some(DomRoot::from_ref(element)); } // Step 6.9.6.2. Append to an event path with event, parent, null, relatedTarget, touchTargets, // and slot-in-closed-tree. self.append_to_path( &parent, None, related_target.as_deref(), slot_in_closed_tree, ); } // Step 6.9.7. Otherwise, if parent is relatedTarget, then set parent to null. else if Some(&parent) == related_target.as_ref() { // NOTE: This causes some lifetime shenanigans. Instead of making things complicated, // we just remember to treat parent as null later done = true; } // Step 6.9.8. Otherwise: else { // Step 6.9.8.1. Set target to parent. target = parent.clone(); // Step 6.9.8.2. If isActivationEvent is true, activationTarget is null, and target has // activation behavior, then set activationTarget to target. if is_activation_event && activation_target.is_none() && let Some(element) = parent.downcast::<Element>() && element.as_maybe_activatable().is_some() { activation_target = Some(DomRoot::from_ref(element)); } // Step 6.9.8.3. Append to an event path with event, parent, target, relatedTarget, // touchTargets, and slot-in-closed-tree. self.append_to_path( &parent, Some(&target), related_target.as_deref(), slot_in_closed_tree, ); } // Step 6.9.9. If parent is non-null, then set parent to the result of invoking parent’s // get the parent with event if !done { parent_or_none = parent.get_the_parent(self); } else { parent_or_none = None; } // Step 6.9.10. Set slot-in-closed-tree to false. slot_in_closed_tree = false; } // Step 6.10. Let clearTargetsStruct be the last struct in event’s path whose shadow-adjusted target // is non-null. // Step 6.11. Let clearTargets be true if clearTargetsStruct’s shadow-adjusted target, // clearTargetsStruct’s relatedTarget, or an EventTarget object in clearTargetsStruct’s // touch target list is a node and its root is a shadow root; otherwise false. // TODO: Handle touch target list clear_targets = self .path .borrow() .iter() .rev() .find(|segment| segment.shadow_adjusted_target.is_some()) // This is "clearTargetsStruct" .is_some_and(|clear_targets| { clear_targets .shadow_adjusted_target .as_ref() .and_then(|target| target.downcast::<Node>()) .is_some_and(Node::is_in_a_shadow_tree) || clear_targets .related_target .as_ref() .and_then(|target| target.downcast::<Node>()) .is_some_and(Node::is_in_a_shadow_tree) }); // Step 6.12. If activationTarget is non-null and activationTarget has legacy-pre-activation behavior, // then run activationTarget’s legacy-pre-activation behavior. if let Some(activation_target) = activation_target.as_ref() { // Not specified in dispatch spec overtly; this is because // the legacy canceled activation behavior of a checkbox // or radio button needs to know what happened in the // corresponding pre-activation behavior. pre_activation_result = activation_target .as_maybe_activatable() .and_then(|activatable| activatable.legacy_pre_activation_behavior(cx)); } let timeline_window = DomRoot::downcast::<Window>(target.global()) .filter(|window| window.need_emit_timeline_marker(TimelineMarkerType::DOMEvent)); // Step 6.13. For each struct in event’s path, in reverse order: for (index, segment) in self.path.borrow().iter().enumerate().rev() { // Step 6.13.1. If struct’s shadow-adjusted target is non-null, then set event’s // eventPhase attribute to AT_TARGET. if segment.shadow_adjusted_target.is_some() { self.phase.set(EventPhase::AtTarget); } // Step 6.13.2. Otherwise, set event’s eventPhase attribute to CAPTURING_PHASE. else { self.phase.set(EventPhase::Capturing); } // Step 6.13.3. Invoke with struct, event, "capturing", and legacyOutputDidListenersThrowFlag if given. invoke( cx, segment, index, self, ListenerPhase::Capturing, timeline_window.as_deref(), legacy_output_did_listeners_throw, ) } // Step 6.14. For each struct in event’s path: for (index, segment) in self.path.borrow().iter().enumerate() { // Step 6.14.1. If struct’s shadow-adjusted target is non-null, then set event’s // eventPhase attribute to AT_TARGET. if segment.shadow_adjusted_target.is_some() { self.phase.set(EventPhase::AtTarget); } // Step 6.14.2. Otherwise: else { // Step 6.14.2.1. If event’s bubbles attribute is false, then continue. if !self.bubbles.get() { continue; } // Step 6.14.2.2. Set event’s eventPhase attribute to BUBBLING_PHASE. self.phase.set(EventPhase::Bubbling); } // Step 6.14.3. Invoke with struct, event, "bubbling", and legacyOutputDidListenersThrowFlag if given. invoke( cx, segment, index, self, ListenerPhase::Bubbling, timeline_window.as_deref(), legacy_output_did_listeners_throw, ); } } // Step 7. Set event’s eventPhase attribute to NONE. self.phase.set(EventPhase::None); // FIXME: The UIEvents spec still expects firing an event // to carry a "default action" semantic, but the HTML spec // has removed this concept. Nothing in either spec currently // (as of Jan 11 2020) says that, e.g., a keydown event on an // input element causes a character to be typed; the UIEvents // spec assumes the HTML spec is covering it, and the HTML spec // no longer specifies any UI event other than mouse click as // causing an element to perform an action. // Compare: // https://w3c.github.io/uievents/#default-action // https://dom.spec.whatwg.org/#action-versus-occurance if !self.DefaultPrevented() { if self.is::<KeyboardEvent>() { // For keyboard events, use the original dispatch target rather than // event.GetTarget(). Composed keyboard events may retarget across // shadow boundaries, but the default action (character input, Tab // navigation) should use the element the event was originally fired on. if let Some(node) = original_target.downcast::<Node>() { let vtable = vtable_for(node); vtable.handle_event(cx, self); } } else if let Some(target) = self.GetTarget() && let Some(node) = target.downcast::<Node>() { let vtable = vtable_for(node); vtable.handle_event(cx, self); } } // Step 8. Set event’s currentTarget attribute to null. self.current_target.set(None); // Step 9. Set event’s path to the empty list. self.path.borrow_mut().clear(); // Step 10. Unset event’s dispatch flag, stop propagation flag, and stop immediate propagation flag. self.unset_flags(EventFlags::Dispatch); self.unset_flags(EventFlags::StopPropagation); self.unset_flags(EventFlags::StopImmediatePropagation); // Step 11. If clearTargets is true: if clear_targets { // Step 11.1. Set event’s target to null. self.target.set(None); // Step 11.2. Set event’s relatedTarget to null. self.related_target.set(None); // Step 11.3. Set event’s touch target list to the empty list. // TODO } // Step 12. If activationTarget is non-null: if let Some(activation_target) = activation_target { // NOTE: The activation target may have been disabled by an event handler if let Some(activatable) = activation_target.as_maybe_activatable() { // Step 12.1. If event’s canceled flag is unset, then run activationTarget’s // activation behavior with event. if !self.DefaultPrevented() { activatable.activation_behavior(cx, self, &target); } // Step 12.2. Otherwise, if activationTarget has legacy-canceled-activation behavior, then run // activationTarget’s legacy-canceled-activation behavior. else { activatable.legacy_canceled_activation_behavior(cx, pre_activation_result); } } } // Step 13. Return false if event’s canceled flag is set; otherwise true. !self.DefaultPrevented() } #[inline] pub(crate) fn dispatching(&self) -> bool { self.has_flag(EventFlags::Dispatch) } #[inline] pub(crate) fn initialized(&self) -> bool { self.has_flag(EventFlags::Initialized) } #[inline] pub(crate) fn type_(&self) -> Atom { self.type_.borrow().clone() } #[inline] pub(crate) fn mark_as_handled(&self) { self.set_flags(EventFlags::Handled); } #[inline] pub(crate) fn flags(&self) -> EventFlags { self.flags.get() } pub(crate) fn set_trusted(&self, trusted: bool) { self.is_trusted.set(trusted); } pub(crate) fn set_composed(&self, composed: bool) { if composed { self.set_flags(EventFlags::Composed); } else { self.unset_flags(EventFlags::Composed); } } /// <https://html.spec.whatwg.org/multipage/#activation-triggering-input-event> fn is_an_activation_triggering_input_event(&self) -> bool { // > An activation triggering input event is any event whose isTrusted attribute is true .. if !self.is_trusted.get() { return false; } // > and whose type is one of: let event_type = self.Type(); match_domstring_ascii!(event_type, // > - "keydown", provided the key is neither the Esc key nor a shortcut key reserved by the user agent; "keydown" => self.downcast::<KeyboardEvent>().expect("`Event` with type `keydown` should be a `KeyboardEvent` interface").key() != Key::Named(NamedKey::Escape), // > - "mousedown"; "mousedown" => true, // > - "pointerdown", provided the event's pointerType is "mouse"; "pointerdown" => self.downcast::<PointerEvent>().expect("`Event` with type `pointerdown` should be a `PointerEvent` interface").PointerType().eq("mouse"), // > - "pointerup", provided the event's pointerType is not "mouse"; or "pointerup" => !self.downcast::<PointerEvent>().expect("`Event` with type `pointerup` should be a `PointerEvent` interface").PointerType().eq("mouse"), // > - "touchend". "touchend" => true, _ => false, ) } /// <https://dom.spec.whatwg.org/#firing-events> pub(crate) fn fire(&self, cx: &mut JSContext, target: &EventTarget) -> bool { self.set_trusted(true); self.dispatch(cx, target, false) } pub(crate) fn fire_with_legacy_output_did_listeners_throw( &self, cx: &mut JSContext, target: &EventTarget, legacy_output_did_listeners_throw: &Cell<bool>, ) -> bool { self.set_trusted(true); self.dispatch_inner(cx, target, false, Some(legacy_output_did_listeners_throw)) } /// <https://dom.spec.whatwg.org/#inner-event-creation-steps> fn inner_creation_steps( cx: &mut JSContext, global: &GlobalScope, proto: Option<HandleObject>, init: &EventBinding::EventInit, ) -> DomRoot<Event> { // Step 1. Let event be the result of creating a new object using eventInterface. // If realm is non-null, then use that realm; otherwise, use the default behavior defined in Web IDL. let event = Event::new_uninitialized_with_proto(cx, global, proto); // Step 2. Set event’s initialized flag. event.set_flags(EventFlags::Initialized); // Step 3. Initialize event’s timeStamp attribute to the relative high resolution // coarse time given time and event’s relevant global object. // NOTE: This is done inside Event::new_inherited // Step 3. For each member → value in dictionary, if event has an attribute whose // identifier is member, then initialize that attribute to value.# event.bubbles.set(init.bubbles); event.cancelable.set(init.cancelable); event.set_composed(init.composed); // Step 5. Run the event constructing steps with event and dictionary. // NOTE: Event construction steps may be defined by subclasses // Step 6. Return event. event } /// Implements the logic behind the [get the parent](https://dom.spec.whatwg.org/#get-the-parent) /// algorithm for shadow roots. pub(crate) fn should_pass_shadow_boundary(&self, shadow_root: &ShadowRoot) -> bool { debug_assert!(self.dispatching()); // > A shadow root’s get the parent algorithm, given an event, returns null if event’s composed flag // > is unset and shadow root is the root of event’s path’s first struct’s invocation target; // > otherwise shadow root’s host. if self.Composed() { return true; } let path = self.path.borrow(); let first_invocation_target = &path .first() .expect("Event path is empty despite event currently being dispatched") .invocation_target .as_rooted(); // The spec doesn't tell us what should happen if the invocation target is not a node let Some(target_node) = first_invocation_target.downcast::<Node>() else { return false; }; &*target_node.GetRootNode(&GetRootNodeOptions::empty()) != shadow_root.upcast::<Node>() } /// <https://dom.spec.whatwg.org/#set-the-canceled-flag> fn set_the_cancelled_flag(&self) { if self.cancelable.get() && !self.has_flag(EventFlags::InPassiveListener) { self.set_flags(EventFlags::Canceled); } } } impl EventMethods<crate::DomTypeHolder> for Event { /// <https://dom.spec.whatwg.org/#concept-event-constructor> fn Constructor( cx: &mut JSContext, global: &GlobalScope, proto: Option<HandleObject>, type_: DOMString, init: &EventBinding::EventInit, ) -> Fallible<DomRoot<Event>> { // Step 1. Let event be the result of running the inner event creation steps with // this interface, null, now, and eventInitDict. let event = Event::inner_creation_steps(cx, global, proto, init); // Step 2. Initialize event’s type attribute to type. *event.type_.borrow_mut() = Atom::from(type_); // Step 3. Return event. Ok(event) } /// <https://dom.spec.whatwg.org/#dom-event-eventphase> fn EventPhase(&self) -> u16 { self.phase.get() as u16 } /// <https://dom.spec.whatwg.org/#dom-event-type> fn Type(&self) -> DOMString { DOMString::from(&*self.type_()) // FIXME(ajeffrey): Directly convert from Atom to DOMString } /// <https://dom.spec.whatwg.org/#dom-event-target> fn GetTarget(&self) -> Option<DomRoot<EventTarget>> { self.target.get() } /// <https://dom.spec.whatwg.org/#dom-event-srcelement> fn GetSrcElement(&self) -> Option<DomRoot<EventTarget>> { self.target.get() } /// <https://dom.spec.whatwg.org/#dom-event-currenttarget> fn GetCurrentTarget(&self) -> Option<DomRoot<EventTarget>> { self.current_target.get() } /// <https://dom.spec.whatwg.org/#dom-event-composedpath> fn ComposedPath(&self) -> Vec<DomRoot<EventTarget>> { // Step 1. Let composedPath be an empty list. let mut composed_path = vec![]; // Step 2. Let path be this’s path. let path = self.path.borrow(); // Step 3. If path is empty, then return composedPath. if path.is_empty() { return composed_path; } // Step 4. Let currentTarget be this’s currentTarget attribute value. let current_target = self.GetCurrentTarget(); // Step 5. Append currentTarget to composedPath. // TODO: https://github.com/whatwg/dom/issues/1343 composed_path.push(current_target.clone().expect( "Since the event's path is not empty it is being dispatched and must have a current target", )); // Step 6. Let currentTargetIndex be 0. let mut current_target_index = 0; // Step 7. Let currentTargetHiddenSubtreeLevel be 0. let mut current_target_hidden_subtree_level = 0; // Step 8. Let index be path’s size − 1. // Step 9. While index is greater than or equal to 0: // NOTE: This is just iterating the path in reverse for (index, element) in path.iter().enumerate().rev() { // Step 9.1 If path[index]'s root-of-closed-tree is true, then increase // currentTargetHiddenSubtreeLevel by 1. if element.root_of_closed_tree { current_target_hidden_subtree_level += 1; } // Step 9.2 If path[index]'s invocation target is currentTarget, then set // currentTargetIndex to index and break. if current_target .as_ref() .is_some_and(|target| target.as_traced() == element.invocation_target) { current_target_index = index; break; } // Step 9.3 If path[index]'s slot-in-closed-tree is true, then decrease // currentTargetHiddenSubtreeLevel by 1. if element.slot_in_closed_tree { current_target_hidden_subtree_level -= 1; } // Step 9.4 Decrease index by 1. } // Step 10. Let currentHiddenLevel and maxHiddenLevel be currentTargetHiddenSubtreeLevel. let mut current_hidden_level = current_target_hidden_subtree_level; let mut max_hidden_level = current_target_hidden_subtree_level; // Step 11. Set index to currentTargetIndex − 1. // Step 12. While index is greater than or equal to 0: // NOTE: This is just iterating part of the path in reverse for element in path.iter().take(current_target_index).rev() { // Step 12.1 If path[index]'s root-of-closed-tree is true, then increase currentHiddenLevel by 1. if element.root_of_closed_tree { current_hidden_level += 1; } // Step 12.2 If currentHiddenLevel is less than or equal to maxHiddenLevel, // then prepend path[index]'s invocation target to composedPath. if current_hidden_level <= max_hidden_level { composed_path.insert(0, element.invocation_target.as_rooted()); } // Step 12.3 If path[index]'s slot-in-closed-tree is true: if element.slot_in_closed_tree { // Step 12.3.1 Decrease currentHiddenLevel by 1. current_hidden_level -= 1; // Step 12.3.2 If currentHiddenLevel is less than maxHiddenLevel, then set // maxHiddenLevel to currentHiddenLevel. if current_hidden_level < max_hidden_level { max_hidden_level = current_hidden_level; } } // Step 12.4 Decrease index by 1. } // Step 13. Set currentHiddenLevel and maxHiddenLevel to currentTargetHiddenSubtreeLevel. current_hidden_level = current_target_hidden_subtree_level; max_hidden_level = current_target_hidden_subtree_level; // Step 14. Set index to currentTargetIndex + 1. // Step 15. While index is less than path’s size: // NOTE: This is just iterating the list and skipping the first current_target_index + 1 elements // (The +1 is necessary because the index is 0-based and the skip method is not) for element in path.iter().skip(current_target_index + 1) { // Step 15.1 If path[index]'s slot-in-closed-tree is true, then increase currentHiddenLevel by 1. if element.slot_in_closed_tree { current_hidden_level += 1; } // Step 15.2 If currentHiddenLevel is less than or equal to maxHiddenLevel, // then append path[index]'s invocation target to composedPath. if current_hidden_level <= max_hidden_level { composed_path.push(element.invocation_target.as_rooted()); } // Step 15.3 If path[index]'s root-of-closed-tree is true: if element.root_of_closed_tree { // Step 15.3.1 Decrease currentHiddenLevel by 1. current_hidden_level -= 1; // Step 15.3.2 If currentHiddenLevel is less than maxHiddenLevel, then set // maxHiddenLevel to currentHiddenLevel. if current_hidden_level < max_hidden_level { max_hidden_level = current_hidden_level; } } // Step 15.4 Increase index by 1. } // Step 16. Return composedPath. composed_path } /// <https://dom.spec.whatwg.org/#dom-event-defaultprevented> fn DefaultPrevented(&self) -> bool { self.has_flag(EventFlags::Canceled) } /// <https://dom.spec.whatwg.org/#dom-event-composed> fn Composed(&self) -> bool { self.has_flag(EventFlags::Composed) } /// <https://dom.spec.whatwg.org/#dom-event-preventdefault> fn PreventDefault(&self) { self.set_the_cancelled_flag(); } /// <https://dom.spec.whatwg.org/#dom-event-stoppropagation> fn StopPropagation(&self) { self.set_flags(EventFlags::StopPropagation); } /// <https://dom.spec.whatwg.org/#dom-event-stopimmediatepropagation> fn StopImmediatePropagation(&self) { self.set_flags(EventFlags::StopPropagation | EventFlags::StopImmediatePropagation); } /// <https://dom.spec.whatwg.org/#dom-event-bubbles> fn Bubbles(&self) -> bool { self.bubbles.get() } /// <https://dom.spec.whatwg.org/#dom-event-cancelable> fn Cancelable(&self) -> bool { self.cancelable.get() } /// <https://dom.spec.whatwg.org/#dom-event-returnvalue> fn ReturnValue(&self) -> bool { !self.has_flag(EventFlags::Canceled) } /// <https://dom.spec.whatwg.org/#dom-event-returnvalue> fn SetReturnValue(&self, val: bool) { if !val { self.set_the_cancelled_flag(); } } /// <https://dom.spec.whatwg.org/#dom-event-cancelbubble> fn CancelBubble(&self) -> bool { self.has_flag(EventFlags::StopPropagation) } /// <https://dom.spec.whatwg.org/#dom-event-cancelbubble> fn SetCancelBubble(&self, value: bool) { if value { self.set_flags(EventFlags::StopPropagation); } } /// <https://dom.spec.whatwg.org/#dom-event-timestamp> fn TimeStamp(&self, cx: &mut JSContext) -> DOMHighResTimeStamp { self.global() .performance(cx) .to_dom_high_res_time_stamp(self.time_stamp) } /// <https://dom.spec.whatwg.org/#dom-event-initevent> fn InitEvent(&self, type_: DOMString, bubbles: bool, cancelable: bool) { self.init_event(Atom::from(type_), bubbles, cancelable) } /// <https://dom.spec.whatwg.org/#dom-event-istrusted> fn IsTrusted(&self) -> bool { self.is_trusted.get() } } #[derive(Clone, Copy, MallocSizeOf, PartialEq)] pub(crate) enum EventBubbles { Bubbles, DoesNotBubble, } impl From<bool> for EventBubbles { fn from(boolean: bool) -> Self { if boolean { EventBubbles::Bubbles } else { EventBubbles::DoesNotBubble } } } impl From<EventBubbles> for bool { fn from(bubbles: EventBubbles) -> Self { match bubbles { EventBubbles::Bubbles => true, EventBubbles::DoesNotBubble => false, } } } #[derive(Clone, Copy, MallocSizeOf, PartialEq)] pub(crate) enum EventCancelable { Cancelable, NotCancelable, } impl From<bool> for EventCancelable { fn from(boolean: bool) -> Self { if boolean { EventCancelable::Cancelable } else { EventCancelable::NotCancelable } } } impl From<EventCancelable> for bool { fn from(cancelable: EventCancelable) -> Self { match cancelable { EventCancelable::Cancelable => true, EventCancelable::NotCancelable => false, } } } #[derive(Clone, Copy, MallocSizeOf, PartialEq)] pub(crate) enum EventComposed { Composed, NotComposed, } impl From<bool> for EventComposed { fn from(boolean: bool) -> Self { if boolean { EventComposed::Composed } else { EventComposed::NotComposed } } } impl From<EventComposed> for bool { fn from(composed: EventComposed) -> Self { match composed { EventComposed::Composed => true, EventComposed::NotComposed => false, } } } #[derive(Clone, Copy, Debug, Eq, JSTraceable, PartialEq)] #[repr(u16)] #[derive(MallocSizeOf)] pub(crate) enum EventPhase { None = EventConstants::NONE, Capturing = EventConstants::CAPTURING_PHASE, AtTarget = EventConstants::AT_TARGET, Bubbling = EventConstants::BUBBLING_PHASE, } /// [`EventFlags`] tracks which specification-defined flags in an [`Event`] are enabled. #[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] pub(crate) struct EventFlags(u8); bitflags! { impl EventFlags: u8 { /// <https://dom.spec.whatwg.org/#canceled-flag> const Canceled = 1 << 0; /// <https://dom.spec.whatwg.org/#composed-flag> const Composed = 1 << 1; /// <https://dom.spec.whatwg.org/#dispatch-flag> const Dispatch = 1 << 2; /// The event has been handled somewhere in the DOM, and it should be prevented from being /// re-handled elsewhere. This doesn't affect the judgement of `DefaultPrevented` const Handled = 1 << 3; /// <https://dom.spec.whatwg.org/#in-passive-listener-flag> const InPassiveListener = 1 << 4; /// <https://dom.spec.whatwg.org/#initialized-flag> const Initialized = 1 << 5; /// <https://dom.spec.whatwg.org/#stop-propagation-flag> const StopPropagation = 1 << 6; /// <https://dom.spec.whatwg.org/#stop-immediate-propagation-flag> const StopImmediatePropagation = 1 << 7; } } impl From<EventFlags> for InputEventResult { fn from(event_flags: EventFlags) -> Self { let mut result = Self::default(); if event_flags.contains(EventFlags::Canceled) { result |= Self::DefaultPrevented; } if event_flags.contains(EventFlags::Handled) { result |= Self::Consumed; } result } } /// <https://dom.spec.whatwg.org/#concept-event-fire> pub(crate) struct EventTask { pub(crate) target: Trusted<EventTarget>, pub(crate) name: Atom, pub(crate) bubbles: EventBubbles, pub(crate) cancelable: EventCancelable, } impl TaskOnce for EventTask { fn run_once(self, cx: &mut JSContext) { let target = self.target.root(); let bubbles = self.bubbles; let cancelable = self.cancelable; target.fire_event_with_params( cx, self.name, bubbles, cancelable, EventComposed::NotComposed, ); } } /// <https://html.spec.whatwg.org/multipage/#fire-a-simple-event> pub(crate) struct SimpleEventTask { pub(crate) target: Trusted<EventTarget>, pub(crate) name: Atom, } impl TaskOnce for SimpleEventTask { fn run_once(self, cx: &mut JSContext) { let target = self.target.root(); target.fire_event(cx, self.name); } } /// <https://dom.spec.whatwg.org/#concept-event-listener-invoke> fn invoke( cx: &mut JSContext, segment: &EventPathSegment, segment_index_in_path: usize, event: &Event, phase: ListenerPhase, timeline_window: Option<&Window>, legacy_output_did_listeners_throw: Option<&Cell<bool>>, ) { // Step 1. Set event’s target to the shadow-adjusted target of the last struct in event’s path, // that is either struct or preceding struct, whose shadow-adjusted target is non-null. event.target.set( event.path.borrow()[..segment_index_in_path + 1] .iter() .rev() .flat_map(|segment| segment.shadow_adjusted_target.clone()) .next() .as_deref(), ); // Step 2. Set event’s relatedTarget to struct’s relatedTarget. event.related_target.set(segment.related_target.as_deref()); // TODO: Set event’s touch target list to struct’s touch target list. // Step 4. If event’s stop propagation flag is set, then return. if event.has_flag(EventFlags::StopPropagation) { return; } // Step 5. Initialize event’s currentTarget attribute to struct’s invocation target. event.current_target.set(Some(&segment.invocation_target)); // Step 6. Let listeners be a clone of event’s currentTarget attribute value’s event listener list. let listeners = segment.invocation_target.get_listeners_for(&event.type_()); // Step 7. Let invocationTargetInShadowTree be struct’s invocation-target-in-shadow-tree. let invocation_target_in_shadow_tree = segment.invocation_target_in_shadow_tree; // Step 8. Let found be the result of running inner invoke with event, listeners, phase, // invocationTargetInShadowTree, and legacyOutputDidListenersThrowFlag if given. let found = inner_invoke( cx, event, &listeners, phase, invocation_target_in_shadow_tree, timeline_window, legacy_output_did_listeners_throw, ); // Step 9. If found is false and event’s isTrusted attribute is true: if !found && event.is_trusted.get() { // Step 9.1 Let originalEventType be event’s type attribute value. let original_type = event.type_(); // Step 9.2 If event’s type attribute value is a match for any of the strings in the first column // in the following table, set event’s type attribute value to the string in the second column on // the same row as the matching string, and return otherwise. let legacy_type = match event.type_() { atom!("animationend") => atom!("webkitAnimationEnd"), atom!("animationiteration") => atom!("webkitAnimationIteration"), atom!("animationstart") => atom!("webkitAnimationStart"), atom!("transitionend") => atom!("webkitTransitionEnd"), atom!("transitionrun") => atom!("webkitTransitionRun"), _ => return, }; *event.type_.borrow_mut() = legacy_type; // Step 9.3 Inner invoke with event, listeners, phase, invocationTargetInShadowTree, // and legacyOutputDidListenersThrowFlag if given. inner_invoke( cx, event, &listeners, phase, invocation_target_in_shadow_tree, timeline_window, legacy_output_did_listeners_throw, ); // Step 9.4 Set event’s type attribute value to originalEventType. *event.type_.borrow_mut() = original_type; } } /// <https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke> fn inner_invoke( cx: &mut JSContext, event: &Event, listeners: &EventListeners, phase: ListenerPhase, invocation_target_in_shadow_tree: bool, timeline_window: Option<&Window>, legacy_output_did_listeners_throw: Option<&Cell<bool>>, ) -> bool { // Step 1. Let found be false. let mut found = false; // Step 2. For each listener in listeners, whose removed is false: for listener in listeners.iter() { if listener.borrow().removed() { continue; } // Step 2.1 If event’s type attribute value is not listener’s type, then continue. // Step 2.2. Set found to true. found = true; // Step 2.3 If phase is "capturing" and listener’s capture is false, then continue. // Step 2.4 If phase is "bubbling" and listener’s capture is true, then continue. if listener.borrow().phase() != phase { continue; } let event_target = event .GetCurrentTarget() .expect("event target was initialized as part of \"invoke\""); // Step 2.5 If listener’s once is true, then remove an event listener given event’s currentTarget // attribute value and listener. if listener.borrow().once() { event_target.remove_listener(&event.type_(), listener); } let Some(compiled_listener) = listener .borrow() .get_compiled_listener(cx, &event_target, &event.type_()) else { continue; }; // Step 2.6 Let global be listener callback’s associated realm’s global object. let global = compiled_listener.associated_global(); // Step 2.7 Let currentEvent be undefined. let mut current_event = None; // Step 2.8 If global is a Window object: if let Some(window) = global.downcast::<Window>() { // Step 2.8.1 Set currentEvent to global’s current event. current_event = window.current_event(); // Step 2.8.2 If invocationTargetInShadowTree is false, then set global’s current event to event. if !invocation_target_in_shadow_tree { current_event = window.set_current_event(Some(event)) } } // Step 2.9 If listener’s passive is true, then set event's in passive listener flag. event.set_in_passive_listener(event_target.is_passive(listener)); // Step 2.10 If global is a Window object, then record timing info for event listener // given event and listener. // Step 2.11 Call a user object’s operation with listener’s callback, "handleEvent", « event », // and event’s currentTarget attribute value. If this throws an exception exception: // Step 2.10.1 Report exception for listener’s callback’s corresponding JavaScript object’s // associated realm’s global object. // Step 2.10.2 Set legacyOutputDidListenersThrowFlag if given. let marker = TimelineMarker::start("DOMEvent".to_owned()); if compiled_listener .call_or_handle_event(cx, &event_target, event, ExceptionHandling::Report) .is_err() && let Some(flag) = legacy_output_did_listeners_throw { flag.set(true); } if let Some(window) = timeline_window { window.emit_timeline_marker(marker.end()); } // Step 2.12 Unset event’s in passive listener flag. event.set_in_passive_listener(false); // Step 2.13 If global is a Window object, then set global’s current event to currentEvent. if let Some(window) = global.downcast::<Window>() { window.set_current_event(current_event.as_deref()); } // Step 2.13: If event’s stop immediate propagation flag is set, then break. if event.has_flag(EventFlags::StopImmediatePropagation) { break; } } // Step 3. found }