servo/components/script/dom/eventtarget.rs
Josh Matthews c94ac5bccb
Move various reflector types and traits to script_bindings (#35279)
* script: Move Reflector to script_bindings.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* script: Extract global() helper from DomObject into new trait. Move DomObject and related traits to script_bindings.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

---------

Signed-off-by: Josh Matthews <josh@joshmatthews.net>
2025-02-04 06:58:08 +00:00

955 lines
34 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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::RefCell;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::default::Default;
use std::ffi::CString;
use std::hash::BuildHasherDefault;
use std::mem;
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use deny_public_fields::DenyPublicFields;
use dom_struct::dom_struct;
use fnv::FnvHasher;
use js::jsapi::JS_GetFunctionObject;
use js::jsval::JSVal;
use js::rust::wrappers::CompileFunction;
use js::rust::{
transform_u16_to_source_text, CompileOptionsWrapper, HandleObject, RootedObjectVectorWrapper,
};
use libc::c_char;
use servo_atoms::Atom;
use servo_url::ServoUrl;
use crate::dom::beforeunloadevent::BeforeUnloadEvent;
use crate::dom::bindings::callback::{CallbackContainer, CallbackFunction, ExceptionHandling};
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::BeforeUnloadEventBinding::BeforeUnloadEventMethods;
use crate::dom::bindings::codegen::Bindings::ErrorEventBinding::ErrorEventMethods;
use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::{
EventHandlerNonNull, OnBeforeUnloadEventHandlerNonNull, OnErrorEventHandlerNonNull,
};
use crate::dom::bindings::codegen::Bindings::EventListenerBinding::EventListener;
use crate::dom::bindings::codegen::Bindings::EventTargetBinding::{
AddEventListenerOptions, EventListenerOptions, EventTargetMethods,
};
use crate::dom::bindings::codegen::Bindings::NodeBinding::GetRootNodeOptions;
use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::codegen::InheritTypes::{CharacterDataTypeId, NodeTypeId};
use crate::dom::bindings::codegen::UnionTypes::{
AddEventListenerOptionsOrBoolean, EventListenerOptionsOrBoolean, EventOrString,
};
use crate::dom::bindings::error::{report_pending_exception, Error, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{
reflect_dom_object_with_proto, DomGlobal, DomObject, Reflector,
};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::trace::HashMapTracedValues;
use crate::dom::document::Document;
use crate::dom::element::Element;
use crate::dom::errorevent::ErrorEvent;
use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus};
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlformelement::FormControlElementHelpers;
use crate::dom::node::{Node, NodeTraits};
use crate::dom::shadowroot::ShadowRoot;
use crate::dom::text::Text;
use crate::dom::virtualmethods::VirtualMethods;
use crate::dom::window::Window;
use crate::dom::workerglobalscope::WorkerGlobalScope;
use crate::realms::{enter_realm, InRealm};
use crate::script_runtime::CanGc;
#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
#[allow(clippy::enum_variant_names)]
pub(crate) enum CommonEventHandler {
EventHandler(#[ignore_malloc_size_of = "Rc"] Rc<EventHandlerNonNull>),
ErrorEventHandler(#[ignore_malloc_size_of = "Rc"] Rc<OnErrorEventHandlerNonNull>),
BeforeUnloadEventHandler(#[ignore_malloc_size_of = "Rc"] Rc<OnBeforeUnloadEventHandlerNonNull>),
}
impl CommonEventHandler {
fn parent(&self) -> &CallbackFunction {
match *self {
CommonEventHandler::EventHandler(ref handler) => &handler.parent,
CommonEventHandler::ErrorEventHandler(ref handler) => &handler.parent,
CommonEventHandler::BeforeUnloadEventHandler(ref handler) => &handler.parent,
}
}
}
#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
pub(crate) enum ListenerPhase {
Capturing,
Bubbling,
}
/// <https://html.spec.whatwg.org/multipage/#internal-raw-uncompiled-handler>
#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
struct InternalRawUncompiledHandler {
source: DOMString,
#[no_trace]
url: ServoUrl,
line: usize,
}
/// A representation of an event handler, either compiled or uncompiled raw source, or null.
#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
enum InlineEventListener {
Uncompiled(InternalRawUncompiledHandler),
Compiled(CommonEventHandler),
Null,
}
/// Get a compiled representation of this event handler, compiling it from its
/// raw source if necessary.
/// <https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler>
fn get_compiled_handler(
inline_listener: &RefCell<InlineEventListener>,
owner: &EventTarget,
ty: &Atom,
can_gc: CanGc,
) -> Option<CommonEventHandler> {
let listener = mem::replace(
&mut *inline_listener.borrow_mut(),
InlineEventListener::Null,
);
let compiled = match listener {
InlineEventListener::Null => None,
InlineEventListener::Uncompiled(handler) => {
owner.get_compiled_event_handler(handler, ty, can_gc)
},
InlineEventListener::Compiled(handler) => Some(handler),
};
if let Some(ref compiled) = compiled {
*inline_listener.borrow_mut() = InlineEventListener::Compiled(compiled.clone());
}
compiled
}
#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
enum EventListenerType {
Additive(#[ignore_malloc_size_of = "Rc"] Rc<EventListener>),
Inline(RefCell<InlineEventListener>),
}
impl EventListenerType {
fn get_compiled_listener(
&self,
owner: &EventTarget,
ty: &Atom,
can_gc: CanGc,
) -> Option<CompiledEventListener> {
match *self {
EventListenerType::Inline(ref inline) => {
get_compiled_handler(inline, owner, ty, can_gc).map(CompiledEventListener::Handler)
},
EventListenerType::Additive(ref listener) => {
Some(CompiledEventListener::Listener(listener.clone()))
},
}
}
}
/// A representation of an EventListener/EventHandler object that has previously
/// been compiled successfully, if applicable.
pub(crate) enum CompiledEventListener {
Listener(Rc<EventListener>),
Handler(CommonEventHandler),
}
impl CompiledEventListener {
#[allow(unsafe_code)]
pub(crate) fn associated_global(&self) -> DomRoot<GlobalScope> {
let obj = match self {
CompiledEventListener::Listener(listener) => listener.callback(),
CompiledEventListener::Handler(CommonEventHandler::EventHandler(handler)) => {
handler.callback()
},
CompiledEventListener::Handler(CommonEventHandler::ErrorEventHandler(handler)) => {
handler.callback()
},
CompiledEventListener::Handler(CommonEventHandler::BeforeUnloadEventHandler(
handler,
)) => handler.callback(),
};
unsafe { GlobalScope::from_object(obj) }
}
// https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm
pub(crate) fn call_or_handle_event(
&self,
object: &EventTarget,
event: &Event,
exception_handle: ExceptionHandling,
) {
// Step 3
match *self {
CompiledEventListener::Listener(ref listener) => {
let _ = listener.HandleEvent_(object, event, exception_handle);
},
CompiledEventListener::Handler(ref handler) => {
match *handler {
CommonEventHandler::ErrorEventHandler(ref handler) => {
if let Some(event) = event.downcast::<ErrorEvent>() {
if object.is::<Window>() || object.is::<WorkerGlobalScope>() {
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut error: JSVal);
event.Error(cx, error.handle_mut());
rooted!(in(*cx) let mut rooted_return_value: JSVal);
let return_value = handler.Call_(
object,
EventOrString::String(event.Message()),
Some(event.Filename()),
Some(event.Lineno()),
Some(event.Colno()),
Some(error.handle()),
rooted_return_value.handle_mut(),
exception_handle,
);
// Step 4
if let Ok(()) = return_value {
if rooted_return_value.handle().is_boolean() &&
rooted_return_value.handle().to_boolean()
{
event.upcast::<Event>().PreventDefault();
}
}
return;
}
}
rooted!(in(*GlobalScope::get_cx()) let mut rooted_return_value: JSVal);
let _ = handler.Call_(
object,
EventOrString::Event(DomRoot::from_ref(event)),
None,
None,
None,
None,
rooted_return_value.handle_mut(),
exception_handle,
);
},
CommonEventHandler::BeforeUnloadEventHandler(ref handler) => {
if let Some(event) = event.downcast::<BeforeUnloadEvent>() {
// Step 5
if let Ok(value) =
handler.Call_(object, event.upcast::<Event>(), exception_handle)
{
let rv = event.ReturnValue();
if let Some(v) = value {
if rv.is_empty() {
event.SetReturnValue(v);
}
event.upcast::<Event>().PreventDefault();
}
}
} else {
// Step 5, "Otherwise" clause
let _ =
handler.Call_(object, event.upcast::<Event>(), exception_handle);
}
},
CommonEventHandler::EventHandler(ref handler) => {
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut rooted_return_value: JSVal);
if let Ok(()) = handler.Call_(
object,
event,
rooted_return_value.handle_mut(),
exception_handle,
) {
let value = rooted_return_value.handle();
//Step 5
let should_cancel = value.is_boolean() && !value.to_boolean();
if should_cancel {
// FIXME: spec says to set the cancelled flag directly
// here, not just to prevent default;
// can that ever make a difference?
event.PreventDefault();
}
}
},
}
},
}
}
}
// https://dom.spec.whatwg.org/#concept-event-listener
// (as distinct from https://dom.spec.whatwg.org/#callbackdef-eventlistener)
#[derive(Clone, DenyPublicFields, JSTraceable, MallocSizeOf)]
/// A listener in a collection of event listeners.
struct EventListenerEntry {
phase: ListenerPhase,
listener: EventListenerType,
once: bool,
}
impl std::cmp::PartialEq for EventListenerEntry {
fn eq(&self, other: &Self) -> bool {
self.phase == other.phase && self.listener == other.listener
}
}
#[derive(JSTraceable, MallocSizeOf)]
/// A mix of potentially uncompiled and compiled event listeners of the same type.
struct EventListeners(Vec<EventListenerEntry>);
impl Deref for EventListeners {
type Target = Vec<EventListenerEntry>;
fn deref(&self) -> &Vec<EventListenerEntry> {
&self.0
}
}
impl DerefMut for EventListeners {
fn deref_mut(&mut self) -> &mut Vec<EventListenerEntry> {
&mut self.0
}
}
impl EventListeners {
// https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler
fn get_inline_listener(
&self,
owner: &EventTarget,
ty: &Atom,
can_gc: CanGc,
) -> Option<CommonEventHandler> {
for entry in &self.0 {
if let EventListenerType::Inline(ref inline) = entry.listener {
// Step 1.1-1.8 and Step 2
return get_compiled_handler(inline, owner, ty, can_gc);
}
}
// Step 2
None
}
// https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler
fn get_listeners(
&self,
phase: Option<ListenerPhase>,
owner: &EventTarget,
ty: &Atom,
can_gc: CanGc,
) -> Vec<CompiledEventListener> {
self.0
.iter()
.filter_map(|entry| {
if phase.is_none() || Some(entry.phase) == phase {
// Step 1.1-1.8, 2
entry.listener.get_compiled_listener(owner, ty, can_gc)
} else {
None
}
})
.collect()
}
fn has_listeners(&self) -> bool {
// TODO: add, and take into account, a 'removed' field?
// https://dom.spec.whatwg.org/#event-listener-removed
!self.0.is_empty()
}
}
#[dom_struct]
pub(crate) struct EventTarget {
reflector_: Reflector,
handlers: DomRefCell<HashMapTracedValues<Atom, EventListeners, BuildHasherDefault<FnvHasher>>>,
}
impl EventTarget {
pub(crate) fn new_inherited() -> EventTarget {
EventTarget {
reflector_: Reflector::new(),
handlers: DomRefCell::new(Default::default()),
}
}
fn new(
global: &GlobalScope,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<EventTarget> {
reflect_dom_object_with_proto(
Box::new(EventTarget::new_inherited()),
global,
proto,
can_gc,
)
}
/// Determine if there are any listeners for a given event type.
/// See <https://github.com/whatwg/dom/issues/453>.
pub(crate) fn has_listeners_for(&self, type_: &Atom) -> bool {
match self.handlers.borrow().get(type_) {
Some(listeners) => listeners.has_listeners(),
None => false,
}
}
pub(crate) fn get_listeners_for(
&self,
type_: &Atom,
specific_phase: Option<ListenerPhase>,
can_gc: CanGc,
) -> Vec<CompiledEventListener> {
self.handlers
.borrow()
.get(type_)
.map_or(vec![], |listeners| {
listeners.get_listeners(specific_phase, self, type_, can_gc)
})
}
pub(crate) fn dispatch_event(&self, event: &Event, can_gc: CanGc) -> EventStatus {
event.dispatch(self, false, can_gc)
}
pub(crate) fn remove_all_listeners(&self) {
*self.handlers.borrow_mut() = Default::default();
}
/// <https://html.spec.whatwg.org/multipage/#event-handler-attributes:event-handlers-11>
fn set_inline_event_listener(&self, ty: Atom, listener: Option<InlineEventListener>) {
let mut handlers = self.handlers.borrow_mut();
let entries = match handlers.entry(ty) {
Occupied(entry) => entry.into_mut(),
Vacant(entry) => entry.insert(EventListeners(vec![])),
};
let idx = entries
.iter()
.position(|entry| matches!(entry.listener, EventListenerType::Inline(_)));
match idx {
Some(idx) => match listener {
// Replace if there's something to replace with,
// but remove entirely if there isn't.
Some(listener) => {
entries[idx].listener = EventListenerType::Inline(listener.into());
},
None => {
entries.remove(idx);
},
},
None => {
if let Some(listener) = listener {
entries.push(EventListenerEntry {
phase: ListenerPhase::Bubbling,
listener: EventListenerType::Inline(listener.into()),
once: false,
});
}
},
}
}
pub(crate) fn remove_listener_if_once(&self, ty: &Atom, listener: &Rc<EventListener>) {
let mut handlers = self.handlers.borrow_mut();
let listener = EventListenerType::Additive(listener.clone());
if let Some(entries) = handlers.get_mut(ty) {
entries.retain(|e| e.listener != listener || !e.once)
}
}
fn get_inline_event_listener(&self, ty: &Atom, can_gc: CanGc) -> Option<CommonEventHandler> {
let handlers = self.handlers.borrow();
handlers
.get(ty)
.and_then(|entry| entry.get_inline_listener(self, ty, can_gc))
}
/// Store the raw uncompiled event handler for on-demand compilation later.
/// <https://html.spec.whatwg.org/multipage/#event-handler-attributes:event-handler-content-attributes-3>
pub(crate) fn set_event_handler_uncompiled(
&self,
url: ServoUrl,
line: usize,
ty: &str,
source: DOMString,
) {
let handler = InternalRawUncompiledHandler { source, line, url };
self.set_inline_event_listener(
Atom::from(ty),
Some(InlineEventListener::Uncompiled(handler)),
);
}
// https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler
// step 3
// While the CanGc argument appears unused, it reflects the fact that the CompileFunction
// API call can trigger a GC operation.
#[allow(unsafe_code)]
fn get_compiled_event_handler(
&self,
handler: InternalRawUncompiledHandler,
ty: &Atom,
can_gc: CanGc,
) -> Option<CommonEventHandler> {
// Step 3.1
let element = self.downcast::<Element>();
let document = match element {
Some(element) => element.owner_document(),
None => self.downcast::<Window>().unwrap().Document(),
};
// Step 3.2
if !document.is_scripting_enabled() {
return None;
}
// Step 3.3
let body: Vec<u16> = handler.source.encode_utf16().collect();
// Step 3.4 is handler.line
// Step 3.5
let form_owner = element
.and_then(|e| e.as_maybe_form_control())
.and_then(|f| f.form_owner());
// Step 3.6 TODO: settings objects not implemented
// Step 3.7 is written as though we call the parser separately
// from the compiler; since we just call CompileFunction with
// source text, we handle parse errors later
// Step 3.8 TODO: settings objects not implemented
let window = document.window();
let _ac = enter_realm(window);
// Step 3.9
let name = CString::new(format!("on{}", &**ty)).unwrap();
// Step 3.9, subsection ParameterList
const ARG_NAMES: &[*const c_char] = &[c"event".as_ptr()];
const ERROR_ARG_NAMES: &[*const c_char] = &[
c"event".as_ptr(),
c"source".as_ptr(),
c"lineno".as_ptr(),
c"colno".as_ptr(),
c"error".as_ptr(),
];
let is_error = ty == &atom!("error") && self.is::<Window>();
let args = if is_error { ERROR_ARG_NAMES } else { ARG_NAMES };
let cx = GlobalScope::get_cx();
let options = unsafe {
CompileOptionsWrapper::new(*cx, &handler.url.to_string(), handler.line as u32)
};
// Step 3.9, subsection Scope steps 1-6
let scopechain = RootedObjectVectorWrapper::new(*cx);
if let Some(element) = element {
scopechain.append(document.reflector().get_jsobject().get());
if let Some(form_owner) = form_owner {
scopechain.append(form_owner.reflector().get_jsobject().get());
}
scopechain.append(element.reflector().get_jsobject().get());
}
rooted!(in(*cx) let mut handler = unsafe {
CompileFunction(
*cx,
scopechain.handle(),
options.ptr,
name.as_ptr(),
args.len() as u32,
args.as_ptr(),
&mut transform_u16_to_source_text(&body),
)
});
if handler.get().is_null() {
// Step 3.7
unsafe {
let ar = enter_realm(self);
// FIXME(#13152): dispatch error event.
report_pending_exception(*cx, false, InRealm::Entered(&ar), can_gc);
}
return None;
}
// Step 3.10 happens when we drop _ac
// TODO Step 3.11
// Step 3.12
let funobj = unsafe { JS_GetFunctionObject(handler.get()) };
assert!(!funobj.is_null());
// Step 1.14
if is_error {
Some(CommonEventHandler::ErrorEventHandler(unsafe {
OnErrorEventHandlerNonNull::new(cx, funobj)
}))
} else if ty == &atom!("beforeunload") {
Some(CommonEventHandler::BeforeUnloadEventHandler(unsafe {
OnBeforeUnloadEventHandlerNonNull::new(cx, funobj)
}))
} else {
Some(CommonEventHandler::EventHandler(unsafe {
EventHandlerNonNull::new(cx, funobj)
}))
}
}
#[allow(unsafe_code)]
pub(crate) fn set_event_handler_common<T: CallbackContainer>(
&self,
ty: &str,
listener: Option<Rc<T>>,
) {
let cx = GlobalScope::get_cx();
let event_listener = listener.map(|listener| {
InlineEventListener::Compiled(CommonEventHandler::EventHandler(unsafe {
EventHandlerNonNull::new(cx, listener.callback())
}))
});
self.set_inline_event_listener(Atom::from(ty), event_listener);
}
#[allow(unsafe_code)]
pub(crate) fn set_error_event_handler<T: CallbackContainer>(
&self,
ty: &str,
listener: Option<Rc<T>>,
) {
let cx = GlobalScope::get_cx();
let event_listener = listener.map(|listener| {
InlineEventListener::Compiled(CommonEventHandler::ErrorEventHandler(unsafe {
OnErrorEventHandlerNonNull::new(cx, listener.callback())
}))
});
self.set_inline_event_listener(Atom::from(ty), event_listener);
}
#[allow(unsafe_code)]
pub(crate) fn set_beforeunload_event_handler<T: CallbackContainer>(
&self,
ty: &str,
listener: Option<Rc<T>>,
) {
let cx = GlobalScope::get_cx();
let event_listener = listener.map(|listener| {
InlineEventListener::Compiled(CommonEventHandler::BeforeUnloadEventHandler(unsafe {
OnBeforeUnloadEventHandlerNonNull::new(cx, listener.callback())
}))
});
self.set_inline_event_listener(Atom::from(ty), event_listener);
}
#[allow(unsafe_code)]
pub(crate) fn get_event_handler_common<T: CallbackContainer>(
&self,
ty: &str,
can_gc: CanGc,
) -> Option<Rc<T>> {
let cx = GlobalScope::get_cx();
let listener = self.get_inline_event_listener(&Atom::from(ty), can_gc);
unsafe {
listener.map(|listener| {
CallbackContainer::new(cx, listener.parent().callback_holder().get())
})
}
}
pub(crate) fn has_handlers(&self) -> bool {
!self.handlers.borrow().is_empty()
}
// https://dom.spec.whatwg.org/#concept-event-fire
pub(crate) fn fire_event(&self, name: Atom, can_gc: CanGc) -> DomRoot<Event> {
self.fire_event_with_params(
name,
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
can_gc,
)
}
// https://dom.spec.whatwg.org/#concept-event-fire
pub(crate) fn fire_bubbling_event(&self, name: Atom, can_gc: CanGc) -> DomRoot<Event> {
self.fire_event_with_params(
name,
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
can_gc,
)
}
// https://dom.spec.whatwg.org/#concept-event-fire
pub(crate) fn fire_cancelable_event(&self, name: Atom, can_gc: CanGc) -> DomRoot<Event> {
self.fire_event_with_params(
name,
EventBubbles::DoesNotBubble,
EventCancelable::Cancelable,
can_gc,
)
}
// https://dom.spec.whatwg.org/#concept-event-fire
pub(crate) fn fire_bubbling_cancelable_event(
&self,
name: Atom,
can_gc: CanGc,
) -> DomRoot<Event> {
self.fire_event_with_params(
name,
EventBubbles::Bubbles,
EventCancelable::Cancelable,
can_gc,
)
}
// https://dom.spec.whatwg.org/#concept-event-fire
pub(crate) fn fire_event_with_params(
&self,
name: Atom,
bubbles: EventBubbles,
cancelable: EventCancelable,
can_gc: CanGc,
) -> DomRoot<Event> {
let event = Event::new(&self.global(), name, bubbles, cancelable, can_gc);
event.fire(self, can_gc);
event
}
// https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener
pub(crate) fn add_event_listener(
&self,
ty: DOMString,
listener: Option<Rc<EventListener>>,
options: AddEventListenerOptions,
) {
let listener = match listener {
Some(l) => l,
None => return,
};
let mut handlers = self.handlers.borrow_mut();
let entry = match handlers.entry(Atom::from(ty)) {
Occupied(entry) => entry.into_mut(),
Vacant(entry) => entry.insert(EventListeners(vec![])),
};
let phase = if options.parent.capture {
ListenerPhase::Capturing
} else {
ListenerPhase::Bubbling
};
let new_entry = EventListenerEntry {
phase,
listener: EventListenerType::Additive(listener),
once: options.once,
};
if !entry.contains(&new_entry) {
entry.push(new_entry);
}
}
// https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener
pub(crate) fn remove_event_listener(
&self,
ty: DOMString,
listener: Option<Rc<EventListener>>,
options: EventListenerOptions,
) {
let Some(ref listener) = listener else {
return;
};
let mut handlers = self.handlers.borrow_mut();
let entry = handlers.get_mut(&Atom::from(ty));
if let Some(entry) = entry {
let phase = if options.capture {
ListenerPhase::Capturing
} else {
ListenerPhase::Bubbling
};
let old_entry = EventListenerEntry {
phase,
listener: EventListenerType::Additive(listener.clone()),
once: false,
};
if let Some(position) = entry.iter().position(|e| *e == old_entry) {
entry.remove(position);
}
}
}
/// <https://dom.spec.whatwg.org/#get-the-parent>
pub(crate) fn get_the_parent(&self, event: &Event) -> Option<DomRoot<EventTarget>> {
if let Some(document) = self.downcast::<Document>() {
if event.type_() == atom!("load") || !document.has_browsing_context() {
return None;
} else {
return Some(DomRoot::from_ref(document.window().upcast::<EventTarget>()));
}
}
if let Some(shadow_root) = self.downcast::<ShadowRoot>() {
if event.should_pass_shadow_boundary(shadow_root) {
let host = shadow_root.Host();
return Some(DomRoot::from_ref(host.upcast::<EventTarget>()));
} else {
return None;
}
}
if let Some(node) = self.downcast::<Node>() {
// > A nodes get the parent algorithm, given an event, returns the nodes assigned slot,
// > if node is assigned; otherwise nodes parent.
let assigned_slot = match node.type_id() {
NodeTypeId::Element(_) => {
let element = node.downcast::<Element>().unwrap();
element
.assigned_slot()
.map(|slot| DomRoot::from_ref(slot.upcast::<EventTarget>()))
},
NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) => {
let text = node.downcast::<Text>().unwrap();
text.slottable_data()
.borrow()
.assigned_slot
.as_ref()
.map(|slot| DomRoot::from_ref(slot.upcast::<EventTarget>()))
},
_ => None,
};
return assigned_slot.or_else(|| {
node.GetParentNode()
.map(|parent| DomRoot::from_ref(parent.upcast::<EventTarget>()))
});
}
None
}
// FIXME: This algorithm operates on "objects", which may not be event targets.
// All our current use-cases only work on event targets, but this might change in the future
/// <https://dom.spec.whatwg.org/#retarget>
pub(crate) fn retarget(&self, b: &Self) -> DomRoot<EventTarget> {
// To retarget an object A against an object B, repeat these steps until they return an object:
let mut a = DomRoot::from_ref(self);
loop {
// Step 1. If one of the following is true
// * A is not a node
// * As root is not a shadow root
// * B is a node and As root is a shadow-including inclusive ancestor of B
let Some(a_node) = a.downcast::<Node>() else {
return a;
};
let a_root = a_node.GetRootNode(&GetRootNodeOptions::empty());
if !a_root.is::<ShadowRoot>() {
return a;
}
if let Some(b_node) = b.downcast::<Node>() {
if a_root.is_shadow_including_inclusive_ancestor_of(b_node) {
return a;
}
}
// Step 2. Set A to As roots host.
a = DomRoot::from_ref(
a_root
.downcast::<ShadowRoot>()
.unwrap()
.Host()
.upcast::<EventTarget>(),
);
}
}
}
impl EventTargetMethods<crate::DomTypeHolder> for EventTarget {
// https://dom.spec.whatwg.org/#dom-eventtarget-eventtarget
fn Constructor(
global: &GlobalScope,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> Fallible<DomRoot<EventTarget>> {
Ok(EventTarget::new(global, proto, can_gc))
}
// https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener
fn AddEventListener(
&self,
ty: DOMString,
listener: Option<Rc<EventListener>>,
options: AddEventListenerOptionsOrBoolean,
) {
self.add_event_listener(ty, listener, options.into())
}
// https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener
fn RemoveEventListener(
&self,
ty: DOMString,
listener: Option<Rc<EventListener>>,
options: EventListenerOptionsOrBoolean,
) {
self.remove_event_listener(ty, listener, options.into())
}
// https://dom.spec.whatwg.org/#dom-eventtarget-dispatchevent
fn DispatchEvent(&self, event: &Event, can_gc: CanGc) -> Fallible<bool> {
if event.dispatching() || !event.initialized() {
return Err(Error::InvalidState);
}
event.set_trusted(false);
Ok(match self.dispatch_event(event, can_gc) {
EventStatus::Canceled => false,
EventStatus::NotCanceled => true,
})
}
}
impl VirtualMethods for EventTarget {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
None
}
}
impl From<AddEventListenerOptionsOrBoolean> for AddEventListenerOptions {
fn from(options: AddEventListenerOptionsOrBoolean) -> Self {
match options {
AddEventListenerOptionsOrBoolean::AddEventListenerOptions(options) => options,
AddEventListenerOptionsOrBoolean::Boolean(capture) => Self {
parent: EventListenerOptions { capture },
once: false,
},
}
}
}
impl From<EventListenerOptionsOrBoolean> for EventListenerOptions {
fn from(options: EventListenerOptionsOrBoolean) -> Self {
match options {
EventListenerOptionsOrBoolean::EventListenerOptions(options) => options,
EventListenerOptionsOrBoolean::Boolean(capture) => Self { capture },
}
}
}