use std::cell::RefCell;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::default::Default;
use std::ffi::CString;
use std::mem;
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use std::sync::LazyLock;
use deny_public_fields::DenyPublicFields;
use devtools_traits::EventListenerInfo;
use dom_struct::dom_struct;
use js::context::JSContext;
use js::jsapi::{JS_GetFunctionObject, SupportUnscopables};
use js::jsval::JSVal;
use js::rust::wrappers2::CompileFunction;
use js::rust::{CompileOptionsWrapper, HandleObject, transform_u16_to_source_text};
use libc::c_char;
use rustc_hash::{FxBuildHasher, FxHashSet};
use script_bindings::callback::OwnerWindow;
use script_bindings::cell::DomRefCell;
use script_bindings::cformat;
use script_bindings::reflector::{DomObject, Reflector, reflect_dom_object_with_proto};
use servo_constellation_traits::ConstellationInterest;
use servo_url::ServoUrl;
use style::str::HTML_SPACE_CHARACTERS;
use stylo_atoms::Atom;
use crate::conversions::Convert;
use crate::dom::abortsignal::{AbortAlgorithm, RemovableDomEventListener};
use crate::dom::beforeunloadevent::BeforeUnloadEvent;
use crate::dom::bindings::callback::{CallbackContainer, CallbackFunction, ExceptionHandling};
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::GenericBindings::DocumentBinding::Document_Binding::DocumentMethods;
use crate::dom::bindings::codegen::UnionTypes::{
AddEventListenerOptionsOrBoolean, EventListenerOptionsOrBoolean, EventOrString,
};
use crate::dom::bindings::error::{Error, Fallible, report_pending_exception};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::DomGlobal;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::trace::HashMapTracedValues;
use crate::dom::csp::{CspReporting, InlineCheckType};
use crate::dom::document::Document;
use crate::dom::element::Element;
use crate::dom::errorevent::ErrorEvent;
use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed};
use crate::dom::globalscope::GlobalScope;
use crate::dom::html::htmlformelement::FormControlElementHelpers;
use crate::dom::indexeddb::idbdatabase::IDBDatabase;
use crate::dom::indexeddb::idbrequest::IDBRequest;
use crate::dom::indexeddb::idbtransaction::IDBTransaction;
use crate::dom::node::virtualmethods::VirtualMethods;
use crate::dom::node::{Node, NodeTraits};
use crate::dom::shadowroot::ShadowRoot;
use crate::dom::window::Window;
use crate::dom::workerglobalscope::WorkerGlobalScope;
use crate::realms::enter_auto_realm;
use crate::script_runtime::IntroductionType;
pub(crate) static CONTENT_EVENT_HANDLER_NAMES: LazyLock<FxHashSet<&str>> = LazyLock::new(|| {
FxHashSet::from_iter(include!(concat!(
env!("OUT_DIR"),
"/ContentEventHandlerNames.rs"
)))
});
#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
#[expect(clippy::enum_variant_names)]
pub(crate) enum CommonEventHandler {
EventHandler(#[conditional_malloc_size_of] Rc<EventHandlerNonNull>),
ErrorEventHandler(#[conditional_malloc_size_of] Rc<OnErrorEventHandlerNonNull>),
BeforeUnloadEventHandler(#[conditional_malloc_size_of] Rc<OnBeforeUnloadEventHandlerNonNull>),
}
impl CommonEventHandler {
fn parent(&self) -> &CallbackFunction<crate::DomTypeHolder> {
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,
}
#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
struct InternalRawUncompiledHandler {
source: DOMString,
#[no_trace]
url: ServoUrl,
line: usize,
}
#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
enum InlineEventListener {
Uncompiled(InternalRawUncompiledHandler),
Compiled(CommonEventHandler),
Null,
}
fn get_compiled_handler(
cx: &mut JSContext,
inline_listener: &RefCell<InlineEventListener>,
owner: &EventTarget,
ty: &Atom,
) -> 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(cx, handler, ty)
},
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(#[conditional_malloc_size_of] Rc<EventListener>),
Inline(RefCell<InlineEventListener>),
}
impl EventListenerType {
fn get_compiled_listener(
&self,
cx: &mut JSContext,
owner: &EventTarget,
ty: &Atom,
) -> Option<CompiledEventListener> {
match *self {
EventListenerType::Inline(ref inline) => {
get_compiled_handler(cx, inline, owner, ty).map(CompiledEventListener::Handler)
},
EventListenerType::Additive(ref listener) => {
Some(CompiledEventListener::Listener(listener.clone()))
},
}
}
}
pub(crate) enum CompiledEventListener {
Listener(Rc<EventListener>),
Handler(CommonEventHandler),
}
impl CompiledEventListener {
#[expect(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) }
}
pub(crate) fn call_or_handle_event(
&self,
cx: &mut JSContext,
object: &EventTarget,
event: &Event,
exception_handle: ExceptionHandling,
) -> Fallible<()> {
match *self {
CompiledEventListener::Listener(ref listener) => {
listener.HandleEvent_(cx, object, event, exception_handle)
},
CompiledEventListener::Handler(ref handler) => {
match *handler {
CommonEventHandler::ErrorEventHandler(ref handler) => {
if let Some(event) = event.downcast::<ErrorEvent>() &&
(object.is::<Window>() || object.is::<WorkerGlobalScope>())
{
rooted!(&in(cx) let mut error: JSVal);
event.Error(error.handle_mut());
rooted!(&in(cx) let mut rooted_return_value: JSVal);
let return_value = handler.Call_(
cx,
object,
EventOrString::String(event.Message()),
Some(event.Filename()),
Some(event.Lineno()),
Some(event.Colno()),
Some(error.handle()),
rooted_return_value.handle_mut(),
exception_handle,
);
if let Ok(()) = return_value &&
rooted_return_value.handle().is_boolean() &&
rooted_return_value.handle().to_boolean()
{
event.upcast::<Event>().PreventDefault();
}
return return_value;
}
rooted!(&in(cx) let mut rooted_return_value: JSVal);
handler.Call_(
cx,
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>() {
match handler.Call_(
cx,
object,
event.upcast::<Event>(),
exception_handle,
) {
Ok(value) => {
let rv = event.ReturnValue();
if let Some(v) = value {
if rv.is_empty() {
event.SetReturnValue(v);
}
event.upcast::<Event>().PreventDefault();
}
Ok(())
},
Err(err) => Err(err),
}
} else {
handler
.Call_(cx, object, event.upcast::<Event>(), exception_handle)
.map(|_| ())
}
},
CommonEventHandler::EventHandler(ref handler) => {
rooted!(&in(cx) let mut rooted_return_value: JSVal);
match handler.Call_(
cx,
object,
event,
rooted_return_value.handle_mut(),
exception_handle,
) {
Ok(()) => {
let value = rooted_return_value.handle();
let should_cancel = value.is_boolean() && !value.to_boolean();
if should_cancel {
event.PreventDefault();
}
Ok(())
},
Err(err) => Err(err),
}
},
}
},
}
}
}
#[derive(Clone, DenyPublicFields, JSTraceable, MallocSizeOf)]
pub(crate) struct EventListenerEntry {
phase: ListenerPhase,
listener: EventListenerType,
once: bool,
passive: bool,
removed: bool,
}
impl EventListenerEntry {
pub(crate) fn phase(&self) -> ListenerPhase {
self.phase
}
pub(crate) fn once(&self) -> bool {
self.once
}
pub(crate) fn removed(&self) -> bool {
self.removed
}
pub(crate) fn get_compiled_listener(
&self,
cx: &mut JSContext,
owner: &EventTarget,
ty: &Atom,
) -> Option<CompiledEventListener> {
self.listener.get_compiled_listener(cx, owner, ty)
}
}
impl std::cmp::PartialEq for EventListenerEntry {
fn eq(&self, other: &Self) -> bool {
self.phase == other.phase && self.listener == other.listener
}
}
#[derive(Clone, JSTraceable, MallocSizeOf)]
pub(crate) struct EventListeners(
#[conditional_malloc_size_of] Vec<Rc<RefCell<EventListenerEntry>>>,
);
impl Deref for EventListeners {
type Target = Vec<Rc<RefCell<EventListenerEntry>>>;
fn deref(&self) -> &Vec<Rc<RefCell<EventListenerEntry>>> {
&self.0
}
}
impl DerefMut for EventListeners {
fn deref_mut(&mut self) -> &mut Vec<Rc<RefCell<EventListenerEntry>>> {
&mut self.0
}
}
impl EventListeners {
fn get_inline_listener(
&self,
cx: &mut JSContext,
owner: &EventTarget,
ty: &Atom,
) -> Option<CommonEventHandler> {
for entry in &self.0 {
if let EventListenerType::Inline(ref inline) = entry.borrow().listener {
return get_compiled_handler(cx, inline, owner, ty);
}
}
None
}
fn has_listeners(&self) -> bool {
!self.0.is_empty()
}
}
#[dom_struct]
pub struct EventTarget {
reflector_: Reflector,
handlers: DomRefCell<HashMapTracedValues<Atom, EventListeners, FxBuildHasher>>,
}
impl EventTarget {
pub(crate) fn new_inherited() -> EventTarget {
EventTarget {
reflector_: Reflector::new(),
handlers: DomRefCell::new(Default::default()),
}
}
fn new(
cx: &mut JSContext,
global: &GlobalScope,
proto: Option<HandleObject>,
) -> DomRoot<EventTarget> {
reflect_dom_object_with_proto(cx, Box::new(EventTarget::new_inherited()), global, proto)
}
fn interest_for_event_type(&self, ty: &Atom) -> Option<ConstellationInterest> {
if self.is::<Window>() && *ty == atom!("storage") {
return Some(ConstellationInterest::StorageEvent);
}
None
}
fn notify_listener_added(&self, ty: &Atom) {
if let Some(interest) = self.interest_for_event_type(ty) {
self.global().register_interest(interest);
}
}
fn notify_listener_removed(&self, ty: &Atom) {
if let Some(interest) = self.interest_for_event_type(ty) {
self.global().unregister_interest(interest);
}
}
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) -> EventListeners {
self.handlers
.borrow()
.get(type_)
.map_or(EventListeners(vec![]), |listeners| listeners.clone())
}
pub(crate) fn remove_all_listeners(&self) {
let mut handlers = self.handlers.borrow_mut();
for (ty, entries) in handlers.iter() {
entries
.iter()
.for_each(|entry| entry.borrow_mut().removed = true);
self.notify_listener_removed(ty);
}
*handlers = Default::default();
}
fn default_passive_value(&self, ty: &Atom) -> bool {
let event_type = ty.to_ascii_lowercase();
let matches_event_type = matches!(
event_type.trim_matches(HTML_SPACE_CHARACTERS),
"touchstart" | "touchmove" | "wheel" | "mousewheel"
);
if !matches_event_type {
return false;
}
if self.is::<Window>() {
return true;
}
if let Some(node) = self.downcast::<Node>() {
let node_document = node.owner_document();
let event_target = self.upcast::<EventTarget>();
return event_target == node_document.upcast::<EventTarget>()
|| node_document.GetDocumentElement().is_some_and(|n| n.upcast::<EventTarget>() == event_target)
|| node_document.GetBody().is_some_and(|n| n.upcast::<EventTarget>() == event_target);
}
false
}
fn set_inline_event_listener(&self, ty: Atom, listener: Option<InlineEventListener>) {
let mut handlers = self.handlers.borrow_mut();
let entries = match handlers.entry(ty.clone()) {
Occupied(entry) => entry.into_mut(),
Vacant(entry) => entry.insert(EventListeners(vec![])),
};
let idx = entries
.iter()
.position(|entry| matches!(entry.borrow().listener, EventListenerType::Inline(_)));
match idx {
Some(idx) => match listener {
Some(listener) => {
entries[idx].borrow_mut().listener = EventListenerType::Inline(listener.into());
},
None => {
entries.remove(idx).borrow_mut().removed = true;
self.notify_listener_removed(&ty);
},
},
None => {
if let Some(listener) = listener {
entries.push(Rc::new(RefCell::new(EventListenerEntry {
phase: ListenerPhase::Bubbling,
listener: EventListenerType::Inline(listener.into()),
once: false,
passive: self.default_passive_value(&ty),
removed: false,
})));
self.notify_listener_added(&ty)
}
},
}
}
pub(crate) fn remove_listener(&self, ty: &Atom, entry: &Rc<RefCell<EventListenerEntry>>) {
let mut handlers = self.handlers.borrow_mut();
if let Some(entries) = handlers.get_mut(ty) &&
let Some(position) = entries.iter().position(|e| *e == *entry)
{
entries.remove(position).borrow_mut().removed = true;
self.notify_listener_removed(ty);
}
}
pub(crate) fn is_passive(&self, listener: &Rc<RefCell<EventListenerEntry>>) -> bool {
listener.borrow().passive
}
pub(crate) fn has_non_passive_listener(&self, type_: &Atom) -> bool {
self.get_listeners_for(type_)
.iter()
.any(|listener| !self.is_passive(listener))
}
fn get_inline_event_listener(
&self,
cx: &mut JSContext,
ty: &Atom,
) -> Option<CommonEventHandler> {
let handlers = self.handlers.borrow();
handlers
.get(ty)
.and_then(|entry| entry.get_inline_listener(cx, self, ty))
}
pub(crate) fn set_event_handler_uncompiled(
&self,
cx: &mut JSContext,
url: ServoUrl,
line: usize,
ty: &str,
source: &str,
) {
if let Some(element) = self.downcast::<Element>() {
let doc = element.owner_document();
let global = &doc.global();
if global
.get_csp_list()
.should_elements_inline_type_behavior_be_blocked(
cx,
global,
element.upcast(),
InlineCheckType::ScriptAttribute,
source,
line as u32,
)
{
return;
}
};
let handler = InternalRawUncompiledHandler {
source: DOMString::from(source),
line,
url,
};
self.set_inline_event_listener(
Atom::from(ty),
Some(InlineEventListener::Uncompiled(handler)),
);
}
#[expect(unsafe_code)]
fn get_compiled_event_handler(
&self,
cx: &mut JSContext,
handler: InternalRawUncompiledHandler,
ty: &Atom,
) -> Option<CommonEventHandler> {
let element = self.downcast::<Element>();
let document = match element {
Some(element) => element.owner_document(),
None => self.downcast::<Window>().unwrap().Document(),
};
if !document.scripting_enabled() {
return None;
}
let body: Vec<u16> = handler.source.str().encode_utf16().collect();
let form_owner = element
.and_then(|e| e.as_maybe_form_control())
.and_then(|f| f.form_owner());
let window = document.window();
let mut realm = enter_auto_realm(cx, window);
let cx = &mut realm.current_realm();
let name = CString::new(format!("on{}", &**ty)).unwrap();
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 url = cformat!("{}", handler.url);
let mut options = CompileOptionsWrapper::new(cx, url, handler.line as u32);
options.set_introduction_type(IntroductionType::EVENT_HANDLER);
let scopechain =
js::rust::EnvironmentChain::new(unsafe { cx.raw_cx() }, SupportUnscopables::Yes);
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.get(),
options.ptr,
name.as_ptr(),
args.len() as u32,
args.as_ptr(),
&mut transform_u16_to_source_text(&body),
)
});
if handler.get().is_null() {
let mut realm = enter_auto_realm(cx, self);
report_pending_exception(&mut realm.current_realm());
return None;
}
let funobj = unsafe { JS_GetFunctionObject(handler.get()) };
assert!(!funobj.is_null());
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)
}))
}
}
#[expect(unsafe_code)]
pub(crate) fn set_event_handler_common<T: CallbackContainer<crate::DomTypeHolder>>(
&self,
cx: &mut JSContext,
ty: &str,
listener: Option<Rc<T>>,
) {
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);
}
#[expect(unsafe_code)]
pub(crate) fn set_error_event_handler<T: CallbackContainer<crate::DomTypeHolder>>(
&self,
cx: &mut JSContext,
ty: &str,
listener: Option<Rc<T>>,
) {
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);
}
#[expect(unsafe_code)]
pub(crate) fn set_beforeunload_event_handler<T: CallbackContainer<crate::DomTypeHolder>>(
&self,
cx: &mut JSContext,
ty: &str,
listener: Option<Rc<T>>,
) {
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);
}
#[expect(unsafe_code)]
pub(crate) fn get_event_handler_common<T: CallbackContainer<crate::DomTypeHolder>>(
&self,
cx: &mut JSContext,
ty: &str,
) -> Option<Rc<T>> {
let listener = self.get_inline_event_listener(cx, &Atom::from(ty));
unsafe {
listener.map(|listener| {
CallbackContainer::new(cx, listener.parent().callback_holder().get())
})
}
}
pub(crate) fn has_handlers(&self) -> bool {
!self.handlers.borrow().is_empty()
}
pub(crate) fn fire_event(&self, cx: &mut js::context::JSContext, name: Atom) -> bool {
self.fire_event_with_params(
cx,
name,
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable,
EventComposed::NotComposed,
)
}
pub(crate) fn fire_bubbling_event(&self, cx: &mut js::context::JSContext, name: Atom) -> bool {
self.fire_event_with_params(
cx,
name,
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
EventComposed::NotComposed,
)
}
pub(crate) fn fire_cancelable_event(
&self,
cx: &mut js::context::JSContext,
name: Atom,
) -> bool {
self.fire_event_with_params(
cx,
name,
EventBubbles::DoesNotBubble,
EventCancelable::Cancelable,
EventComposed::NotComposed,
)
}
pub(crate) fn fire_bubbling_cancelable_event(
&self,
cx: &mut js::context::JSContext,
name: Atom,
) -> bool {
self.fire_event_with_params(
cx,
name,
EventBubbles::Bubbles,
EventCancelable::Cancelable,
EventComposed::NotComposed,
)
}
pub(crate) fn fire_event_with_params(
&self,
cx: &mut js::context::JSContext,
name: Atom,
bubbles: EventBubbles,
cancelable: EventCancelable,
composed: EventComposed,
) -> bool {
let event = Event::new(cx, &self.global(), name, bubbles, cancelable);
event.set_composed(composed.into());
event.fire(cx, self)
}
pub(crate) fn add_event_listener(
&self,
ty: DOMString,
listener: Option<Rc<EventListener>>,
options: AddEventListenerOptions,
) {
if let Some(signal) = options.signal.as_ref() {
if signal.aborted() {
return;
}
signal.add(&AbortAlgorithm::DomEventListener(
RemovableDomEventListener {
event_target: Dom::from_ref(self),
ty: ty.clone(),
listener: listener.clone(),
options: options.parent.clone(),
},
));
}
let listener = match listener {
Some(l) => l,
None => return,
};
let ty = Atom::from(ty);
let mut handlers = self.handlers.borrow_mut();
let entries = match handlers.entry(ty.clone()) {
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 = Rc::new(RefCell::new(EventListenerEntry {
phase,
listener: EventListenerType::Additive(listener),
once: options.once,
passive: options.passive.unwrap_or(self.default_passive_value(&ty)),
removed: false,
}));
if !entries.contains(&new_entry) {
entries.push(new_entry);
self.notify_listener_added(&ty);
}
}
pub(crate) fn remove_event_listener(
&self,
ty: DOMString,
listener: &Option<Rc<EventListener>>,
options: &EventListenerOptions,
) {
let Some(listener) = listener else {
return;
};
let ty_atom = Atom::from(ty);
let mut handlers = self.handlers.borrow_mut();
if let Some(entries) = handlers.get_mut(&ty_atom) {
let phase = if options.capture {
ListenerPhase::Capturing
} else {
ListenerPhase::Bubbling
};
let listener_type = EventListenerType::Additive(listener.clone());
if let Some(position) = entries
.iter()
.position(|e| e.borrow().listener == listener_type && e.borrow().phase == phase)
{
entries.remove(position).borrow_mut().removed = true;
self.notify_listener_removed(&ty_atom);
}
}
}
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>() {
return node.assigned_slot().map(DomRoot::upcast).or_else(|| {
node.GetParentNode()
.map(|parent| DomRoot::from_ref(parent.upcast::<EventTarget>()))
});
}
if let Some(request) = self.downcast::<IDBRequest>() {
return request
.transaction()
.map(|tx| DomRoot::from_ref(tx.upcast::<EventTarget>()));
}
if let Some(transaction) = self.downcast::<IDBTransaction>() {
return Some(DomRoot::from_ref(
transaction.get_db().upcast::<EventTarget>(),
));
}
if self.is::<IDBDatabase>() {
return None;
}
None
}
pub(crate) fn retarget(&self, b: &Self) -> DomRoot<EventTarget> {
let mut a = DomRoot::from_ref(self);
loop {
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>() &&
a_root.is_shadow_including_inclusive_ancestor_of(b_node)
{
return a;
}
a = DomRoot::from_ref(
a_root
.downcast::<ShadowRoot>()
.unwrap()
.Host()
.upcast::<EventTarget>(),
);
}
}
pub(crate) fn is_content_event_handler(name: &str) -> bool {
CONTENT_EVENT_HANDLER_NAMES.contains(&name)
}
pub(crate) fn summarize_event_listeners_for_devtools(&self) -> Vec<EventListenerInfo> {
let handlers = self.handlers.borrow();
let mut listener_infos = Vec::with_capacity(handlers.0.len());
for (event_type, event_listeners) in &handlers.0 {
for event_listener in event_listeners.iter() {
let event_listener_entry = event_listener.borrow();
listener_infos.push(EventListenerInfo {
event_type: event_type.to_string(),
capturing: event_listener_entry.phase() == ListenerPhase::Capturing,
});
}
}
listener_infos
}
}
impl EventTargetMethods<crate::DomTypeHolder> for EventTarget {
fn Constructor(
cx: &mut JSContext,
global: &GlobalScope,
proto: Option<HandleObject>,
) -> Fallible<DomRoot<EventTarget>> {
Ok(EventTarget::new(cx, global, proto))
}
fn AddEventListener(
&self,
ty: DOMString,
listener: Option<Rc<EventListener>>,
options: AddEventListenerOptionsOrBoolean,
) {
self.add_event_listener(ty, listener, options.convert())
}
fn RemoveEventListener(
&self,
ty: DOMString,
listener: Option<Rc<EventListener>>,
options: EventListenerOptionsOrBoolean,
) {
self.remove_event_listener(ty, &listener, &options.convert())
}
fn DispatchEvent(&self, cx: &mut JSContext, event: &Event) -> Fallible<bool> {
if event.dispatching() || !event.initialized() {
return Err(Error::InvalidState(None));
}
event.set_trusted(false);
Ok(event.dispatch(cx, self, false))
}
}
impl VirtualMethods for EventTarget {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
None
}
}
impl Convert<AddEventListenerOptions> for AddEventListenerOptionsOrBoolean {
fn convert(self) -> AddEventListenerOptions {
match self {
AddEventListenerOptionsOrBoolean::AddEventListenerOptions(options) => options,
AddEventListenerOptionsOrBoolean::Boolean(capture) => AddEventListenerOptions {
parent: EventListenerOptions { capture },
once: false,
passive: None,
signal: None,
},
}
}
}
impl Convert<EventListenerOptions> for EventListenerOptionsOrBoolean {
fn convert(self) -> EventListenerOptions {
match self {
EventListenerOptionsOrBoolean::EventListenerOptions(options) => options,
EventListenerOptionsOrBoolean::Boolean(capture) => EventListenerOptions { capture },
}
}
}
impl OwnerWindow<crate::DomTypeHolder> for EventTarget {
fn owner_window(&self) -> Option<DomRoot<Window>> {
self.downcast::<Node>().map(|node| node.owner_window())
}
}