| /* 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 base::cross_process_instant::CrossProcessInstant; |
| use devtools_traits::{TimelineMarker, TimelineMarkerType}; |
| use dom_struct::dom_struct; |
| use js::rust::HandleObject; |
| use stylo_atoms::Atom; |
| |
| use crate::dom::bindings::callback::ExceptionHandling; |
| use crate::dom::bindings::cell::DomRefCell; |
| 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, Reflector, reflect_dom_object_with_proto}; |
| 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::htmlinputelement::InputActivationState; |
| use crate::dom::html::htmlslotelement::HTMLSlotElement; |
| use crate::dom::mouseevent::MouseEvent; |
| use crate::dom::node::{Node, NodeTraits}; |
| use crate::dom::shadowroot::ShadowRoot; |
| use crate::dom::virtualmethods::vtable_for; |
| use crate::dom::window::Window; |
| use crate::script_runtime::CanGc; |
| use crate::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>, |
| |
| /// <https://dom.spec.whatwg.org/#canceled-flag> |
| canceled: Cell<EventDefault>, |
| |
| /// <https://dom.spec.whatwg.org/#stop-propagation-flag> |
| stop_propagation: Cell<bool>, |
| |
| /// <https://dom.spec.whatwg.org/#stop-immediate-propagation-flag> |
| stop_immediate_propagation: Cell<bool>, |
| |
| /// <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-composed> |
| composed: Cell<bool>, |
| |
| /// <https://dom.spec.whatwg.org/#dom-event-istrusted> |
| is_trusted: Cell<bool>, |
| |
| /// <https://dom.spec.whatwg.org/#dispatch-flag> |
| dispatch: Cell<bool>, |
| |
| /// <https://dom.spec.whatwg.org/#initialized-flag> |
| initialized: 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>, |
| |
| /// <https://dom.spec.whatwg.org/#in-passive-listener-flag> |
| in_passive_listener: Cell<bool>, |
| } |
| |
| /// 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), |
| canceled: Cell::new(EventDefault::Allowed), |
| stop_propagation: Cell::new(false), |
| stop_immediate_propagation: Cell::new(false), |
| cancelable: Cell::new(false), |
| bubbles: Cell::new(false), |
| composed: Cell::new(false), |
| is_trusted: Cell::new(false), |
| dispatch: Cell::new(false), |
| initialized: Cell::new(false), |
| time_stamp: CrossProcessInstant::now(), |
| path: DomRefCell::default(), |
| related_target: Default::default(), |
| in_passive_listener: Cell::new(false), |
| } |
| } |
| |
| pub(crate) fn new_uninitialized(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Event> { |
| Self::new_uninitialized_with_proto(global, None, can_gc) |
| } |
| |
| pub(crate) fn new_uninitialized_with_proto( |
| global: &GlobalScope, |
| proto: Option<HandleObject>, |
| can_gc: CanGc, |
| ) -> DomRoot<Event> { |
| reflect_dom_object_with_proto(Box::new(Event::new_inherited()), global, proto, can_gc) |
| } |
| |
| pub(crate) fn new( |
| global: &GlobalScope, |
| type_: Atom, |
| bubbles: EventBubbles, |
| cancelable: EventCancelable, |
| can_gc: CanGc, |
| ) -> DomRoot<Event> { |
| Self::new_with_proto(global, None, type_, bubbles, cancelable, can_gc) |
| } |
| |
| fn new_with_proto( |
| global: &GlobalScope, |
| proto: Option<HandleObject>, |
| type_: Atom, |
| bubbles: EventBubbles, |
| cancelable: EventCancelable, |
| can_gc: CanGc, |
| ) -> DomRoot<Event> { |
| let event = Event::new_uninitialized_with_proto(global, proto, can_gc); |
| |
| // 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.dispatch.get() { |
| return; |
| } |
| |
| // https://dom.spec.whatwg.org/#concept-event-initialize |
| // Step 1. Set event’s initialized flag. |
| self.initialized.set(true); |
| |
| // Step 2. Unset event’s stop propagation flag, stop immediate propagation flag, and canceled flag. |
| self.stop_propagation.set(false); |
| self.stop_immediate_propagation.set(false); |
| self.canceled.set(EventDefault::Allowed); |
| |
| // 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); |
| } |
| |
| pub(crate) fn set_target(&self, target_: Option<&EventTarget>) { |
| self.target.set(target_); |
| } |
| |
| pub(crate) fn set_in_passive_listener(&self, value: bool) { |
| self.in_passive_listener.set(value); |
| } |
| |
| /// <https://dom.spec.whatwg.org/#concept-event-path-append> |
| #[cfg_attr(crown, allow(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, |
| target: &EventTarget, |
| legacy_target_override: bool, |
| can_gc: CanGc, |
| // TODO legacy_did_output_listeners_throw_flag for indexeddb |
| ) -> bool { |
| let mut target = DomRoot::from_ref(target); |
| |
| // Step 1. Set event’s dispatch flag. |
| self.dispatch.set(true); |
| |
| // 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. If target is not relatedTarget or target is event’s relatedTarget: |
| // Variables declared by the spec inside Step 5 but used later: |
| // TODO: https://github.com/whatwg/dom/issues/1344 |
| let mut clear_targets = false; |
| let mut pre_activation_result: Option<InputActivationState> = None; |
| if related_target.as_ref() != Some(&target) || |
| self.related_target.get().as_ref() == Some(&target) |
| { |
| // TODO Step 5.1 Let touchTargets be a new list. |
| // TODO Step 5.2 For each touchTarget of event’s touch target list, append the result of retargeting |
| // touchTarget against target to touchTargets. |
| |
| // Step 5.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 5.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 5.5 If isActivationEvent is true and target has activation behavior, |
| // then set activationTarget to target. |
| if is_activation_event { |
| if let Some(element) = target.downcast::<Element>() { |
| if element.as_maybe_activatable().is_some() { |
| activation_target = Some(DomRoot::from_ref(element)); |
| } |
| } |
| } |
| |
| // Step 5.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 5.7 Let slot-in-closed-tree be false |
| let mut slot_in_closed_tree = false; |
| |
| // Step 5.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 5.9 While parent is non-null: |
| while let Some(parent) = parent_or_none.clone() { |
| // Step 5.9.1 If slottable is non-null: |
| if slottable.is_some() { |
| // Step 5.9.1.1 Assert: parent is a slot. |
| let slot = parent |
| .downcast::<HTMLSlotElement>() |
| .expect("parent of slottable is not a slot"); |
| |
| // Step 5.9.1.2 Set slottable to null. |
| slottable = None; |
| |
| // Step 5.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 5.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 5.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(&target)); |
| |
| // TODO: Step 5.9.4 Let touchTargets be a new list. |
| // Step 5.9.5 For each touchTarget of event’s touch target list, append the result of retargeting |
| // touchTarget against parent to touchTargets. |
| |
| // Step 5.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 5.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() { |
| if let Some(element) = parent.downcast::<Element>() { |
| if element.as_maybe_activatable().is_some() { |
| activation_target = Some(DomRoot::from_ref(element)); |
| } |
| } |
| } |
| |
| // Step 5.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 5.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 5.9.8 Otherwise: |
| else { |
| // Step 5.9.8.1 Set target to parent. |
| target = parent.clone(); |
| |
| // Step 5.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() { |
| if let Some(element) = parent.downcast::<Element>() { |
| if element.as_maybe_activatable().is_some() { |
| activation_target = Some(DomRoot::from_ref(element)); |
| } |
| } |
| } |
| |
| // Step 5.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 5.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); |
| } |
| |
| // Step 5.9.10 Set slot-in-closed-tree to false. |
| slot_in_closed_tree = false; |
| } |
| |
| // Step 5.10 Let clearTargetsStruct be the last struct in event’s path whose shadow-adjusted target |
| // is non-null. |
| // Step 5.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 5.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(can_gc)); |
| } |
| |
| let timeline_window = DomRoot::downcast::<Window>(target.global()) |
| .filter(|window| window.need_emit_timeline_marker(TimelineMarkerType::DOMEvent)); |
| |
| // Step 5.13 For each struct in event’s path, in reverse order: |
| for (index, segment) in self.path.borrow().iter().enumerate().rev() { |
| // Step 5.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 5.13.2 Otherwise, set event’s eventPhase attribute to CAPTURING_PHASE. |
| else { |
| self.phase.set(EventPhase::Capturing); |
| } |
| |
| // Step 5.13.3 Invoke with struct, event, "capturing", and legacyOutputDidListenersThrowFlag if given. |
| invoke( |
| segment, |
| index, |
| self, |
| ListenerPhase::Capturing, |
| timeline_window.as_deref(), |
| can_gc, |
| ) |
| } |
| |
| // Step 5.14 For each struct in event’s path: |
| for (index, segment) in self.path.borrow().iter().enumerate() { |
| // Step 5.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 5.14.2 Otherwise: |
| else { |
| // Step 5.14.2.1 If event’s bubbles attribute is false, then continue. |
| if !self.bubbles.get() { |
| continue; |
| } |
| |
| // Step 5.14.2.2 Set event’s eventPhase attribute to BUBBLING_PHASE. |
| self.phase.set(EventPhase::Bubbling); |
| } |
| |
| // Step 5.14.3 Invoke with struct, event, "bubbling", and legacyOutputDidListenersThrowFlag if given. |
| invoke( |
| segment, |
| index, |
| self, |
| ListenerPhase::Bubbling, |
| timeline_window.as_deref(), |
| can_gc, |
| ); |
| } |
| } |
| |
| // Step 6. 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 let Some(target) = self.GetTarget() { |
| if let Some(node) = target.downcast::<Node>() { |
| let vtable = vtable_for(node); |
| vtable.handle_event(self, can_gc); |
| } |
| } |
| } |
| |
| // Step 7. Set event’s currentTarget attribute to null. |
| self.current_target.set(None); |
| |
| // Step 8. Set event’s path to the empty list. |
| self.path.borrow_mut().clear(); |
| |
| // Step 9. Unset event’s dispatch flag, stop propagation flag, and stop immediate propagation flag. |
| self.dispatch.set(false); |
| self.stop_propagation.set(false); |
| self.stop_immediate_propagation.set(false); |
| |
| // Step 10. If clearTargets is true: |
| if clear_targets { |
| // Step 10.1 Set event’s target to null. |
| self.target.set(None); |
| |
| // Step 10.2 Set event’s relatedTarget to null. |
| self.related_target.set(None); |
| |
| // TODO Step 10.3 Set event’s touch target list to the empty list. |
| } |
| |
| // Step 11. 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 11.1 If event’s canceled flag is unset, then run activationTarget’s |
| // activation behavior with event. |
| if !self.DefaultPrevented() { |
| activatable.activation_behavior(self, &target, can_gc); |
| } |
| // Step 11.2 Otherwise, if activationTarget has legacy-canceled-activation behavior, then run |
| // activationTarget’s legacy-canceled-activation behavior. |
| else { |
| activatable.legacy_canceled_activation_behavior(pre_activation_result, can_gc); |
| } |
| } |
| } |
| |
| // Step 12 Return false if event’s canceled flag is set; otherwise true. |
| !self.DefaultPrevented() |
| } |
| |
| #[inline] |
| pub(crate) fn dispatching(&self) -> bool { |
| self.dispatch.get() |
| } |
| |
| #[inline] |
| pub(crate) fn initialized(&self) -> bool { |
| self.initialized.get() |
| } |
| |
| #[inline] |
| pub(crate) fn type_(&self) -> Atom { |
| self.type_.borrow().clone() |
| } |
| |
| #[inline] |
| pub(crate) fn mark_as_handled(&self) { |
| self.canceled.set(EventDefault::Handled); |
| } |
| |
| #[inline] |
| pub(crate) fn get_cancel_state(&self) -> EventDefault { |
| self.canceled.get() |
| } |
| |
| pub(crate) fn set_trusted(&self, trusted: bool) { |
| self.is_trusted.set(trusted); |
| } |
| |
| pub(crate) fn set_composed(&self, composed: bool) { |
| self.composed.set(composed); |
| } |
| |
| /// <https://dom.spec.whatwg.org/#firing-events> |
| pub(crate) fn fire(&self, target: &EventTarget, can_gc: CanGc) -> bool { |
| self.set_trusted(true); |
| target.dispatch_event(self, can_gc) |
| } |
| |
| /// <https://dom.spec.whatwg.org/#inner-event-creation-steps> |
| fn inner_creation_steps( |
| global: &GlobalScope, |
| proto: Option<HandleObject>, |
| init: &EventBinding::EventInit, |
| can_gc: CanGc, |
| ) -> 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(global, proto, can_gc); |
| |
| // Step 2. Set event’s initialized flag. |
| event.initialized.set(true); |
| |
| // 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.composed.set(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.in_passive_listener.get() { |
| self.canceled.set(EventDefault::Prevented) |
| } |
| } |
| } |
| |
| impl EventMethods<crate::DomTypeHolder> for Event { |
| /// <https://dom.spec.whatwg.org/#concept-event-constructor> |
| fn Constructor( |
| global: &GlobalScope, |
| proto: Option<HandleObject>, |
| can_gc: CanGc, |
| 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(global, proto, init, can_gc); |
| |
| // 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.canceled.get() == EventDefault::Prevented |
| } |
| |
| /// <https://dom.spec.whatwg.org/#dom-event-composed> |
| fn Composed(&self) -> bool { |
| self.composed.get() |
| } |
| |
| /// <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.stop_propagation.set(true); |
| } |
| |
| /// <https://dom.spec.whatwg.org/#dom-event-stopimmediatepropagation> |
| fn StopImmediatePropagation(&self) { |
| self.stop_immediate_propagation.set(true); |
| self.stop_propagation.set(true); |
| } |
| |
| /// <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.canceled.get() == EventDefault::Allowed |
| } |
| |
| /// <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.stop_propagation.get() |
| } |
| |
| /// <https://dom.spec.whatwg.org/#dom-event-cancelbubble> |
| fn SetCancelBubble(&self, value: bool) { |
| if value { |
| self.stop_propagation.set(true) |
| } |
| } |
| |
| /// <https://dom.spec.whatwg.org/#dom-event-timestamp> |
| fn TimeStamp(&self) -> DOMHighResTimeStamp { |
| self.global() |
| .performance() |
| .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, |
| } |
| |
| /// An enum to indicate whether the default action of an event is allowed. |
| /// |
| /// This should've been a bool. Instead, it's an enum, because, aside from the allowed/canceled |
| /// states, we also need something to stop the event from being handled again (without cancelling |
| /// the event entirely). For example, an Up/Down `KeyEvent` inside a `textarea` element will |
| /// trigger the cursor to go up/down if the text inside the element spans multiple lines. This enum |
| /// helps us to prevent such events from being [sent to the constellation][msg] where it will be |
| /// handled once again for page scrolling (which is definitely not what we'd want). |
| /// |
| /// [msg]: https://doc.servo.org/compositing/enum.ConstellationMsg.html#variant.KeyEvent |
| /// |
| #[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] |
| pub(crate) enum EventDefault { |
| /// The default action of the event is allowed (constructor's default) |
| Allowed, |
| /// The default action has been prevented by calling `PreventDefault` |
| Prevented, |
| /// 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` |
| Handled, |
| } |
| |
| /// <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) { |
| let target = self.target.root(); |
| let bubbles = self.bubbles; |
| let cancelable = self.cancelable; |
| target.fire_event_with_params( |
| self.name, |
| bubbles, |
| cancelable, |
| EventComposed::NotComposed, |
| CanGc::note(), |
| ); |
| } |
| } |
| |
| /// <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) { |
| let target = self.target.root(); |
| target.fire_event(self.name, CanGc::note()); |
| } |
| } |
| |
| /// <https://dom.spec.whatwg.org/#concept-event-listener-invoke> |
| fn invoke( |
| segment: &EventPathSegment, |
| segment_index_in_path: usize, |
| event: &Event, |
| phase: ListenerPhase, |
| timeline_window: Option<&Window>, |
| can_gc: CanGc, |
| // TODO legacy_output_did_listeners_throw for indexeddb |
| ) { |
| // 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.stop_propagation.get() { |
| 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( |
| event, |
| &listeners, |
| phase, |
| invocation_target_in_shadow_tree, |
| timeline_window, |
| can_gc, |
| ); |
| |
| // 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( |
| event, |
| &listeners, |
| phase, |
| invocation_target_in_shadow_tree, |
| timeline_window, |
| can_gc, |
| ); |
| |
| // 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( |
| event: &Event, |
| listeners: &EventListeners, |
| phase: ListenerPhase, |
| invocation_target_in_shadow_tree: bool, |
| timeline_window: Option<&Window>, |
| can_gc: CanGc, |
| ) -> 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(&event_target, &event.type_(), can_gc) |
| 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(&event.type_(), 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. |
| // TODO Step 2.10.2 Set legacyOutputDidListenersThrowFlag if given. |
| let marker = TimelineMarker::start("DOMEvent".to_owned()); |
| compiled_listener.call_or_handle_event( |
| &event_target, |
| event, |
| ExceptionHandling::Report, |
| can_gc, |
| ); |
| 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.stop_immediate_propagation.get() { |
| break; |
| } |
| } |
| |
| // Step 3. |
| found |
| } |