1use std::cell::{RefCell, RefMut};
6use std::collections::hash_map::Entry::{Occupied, Vacant};
7use std::default::Default;
8use std::ffi::CString;
9use std::mem;
10use std::ops::{Deref, DerefMut};
11use std::rc::Rc;
12use std::sync::LazyLock;
13
14use deny_public_fields::DenyPublicFields;
15use devtools_traits::EventListenerInfo;
16use dom_struct::dom_struct;
17use js::context::JSContext;
18use js::jsapi::{JS_GetFunctionObject, SupportUnscopables};
19use js::jsval::JSVal;
20use js::rust::wrappers2::CompileFunction;
21use js::rust::{CompileOptionsWrapper, HandleObject, transform_u16_to_source_text};
22use libc::c_char;
23use rustc_hash::{FxBuildHasher, FxHashSet};
24use script_bindings::callback::{HasCallbackHolder, OwnerWindow, RootedCallback, TracedCallback};
25use script_bindings::cell::DomRefCell;
26use script_bindings::cformat;
27use script_bindings::reflector::{DomObject, Reflector, reflect_dom_object_with_proto};
28use servo_constellation_traits::ConstellationInterest;
29use servo_url::ServoUrl;
30use style::str::HTML_SPACE_CHARACTERS;
31use stylo_atoms::Atom;
32
33use crate::conversions::Convert;
34use crate::dom::abortsignal::{AbortAlgorithm, RemovableDomEventListener};
35use crate::dom::beforeunloadevent::BeforeUnloadEvent;
36use crate::dom::bindings::callback::{CallbackContainer, CallbackFunction, ExceptionHandling};
37use crate::dom::bindings::codegen::Bindings::BeforeUnloadEventBinding::BeforeUnloadEventMethods;
38use crate::dom::bindings::codegen::Bindings::ErrorEventBinding::ErrorEventMethods;
39use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
40use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::{
41 EventHandlerNonNull, OnBeforeUnloadEventHandlerNonNull, OnErrorEventHandlerNonNull,
42};
43use crate::dom::bindings::codegen::Bindings::EventListenerBinding::EventListener;
44use crate::dom::bindings::codegen::Bindings::EventTargetBinding::{
45 AddEventListenerOptions, EventListenerOptions, EventTargetMethods,
46};
47use crate::dom::bindings::codegen::Bindings::NodeBinding::GetRootNodeOptions;
48use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
49use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
50use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
51use crate::dom::bindings::codegen::GenericBindings::DocumentBinding::Document_Binding::DocumentMethods;
52use crate::dom::bindings::codegen::UnionTypes::{
53 AddEventListenerOptionsOrBoolean, EventListenerOptionsOrBoolean, EventOrString,
54};
55use crate::dom::bindings::error::{Error, Fallible, report_pending_exception};
56use crate::dom::bindings::inheritance::Castable;
57use crate::dom::bindings::reflector::DomGlobal;
58use crate::dom::bindings::root::{Dom, DomRoot};
59use crate::dom::bindings::str::DOMString;
60use crate::dom::bindings::trace::HashMapTracedValues;
61use crate::dom::csp::{CspReporting, InlineCheckType};
62use crate::dom::document::Document;
63use crate::dom::element::Element;
64use crate::dom::errorevent::ErrorEvent;
65use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed};
66use crate::dom::globalscope::GlobalScope;
67use crate::dom::html::htmlformelement::FormControlElementHelpers;
68use crate::dom::indexeddb::idbdatabase::IDBDatabase;
69use crate::dom::indexeddb::idbrequest::IDBRequest;
70use crate::dom::indexeddb::idbtransaction::IDBTransaction;
71use crate::dom::node::virtualmethods::VirtualMethods;
72use crate::dom::node::{Node, NodeTraits};
73use crate::dom::shadowroot::shadowroot::ShadowRoot;
74use crate::dom::window::Window;
75use crate::dom::workerglobalscope::WorkerGlobalScope;
76use crate::realms::enter_auto_realm;
77use crate::runtime::script_runtime::IntroductionType;
78
79pub(crate) static CONTENT_EVENT_HANDLER_NAMES: LazyLock<FxHashSet<&str>> = LazyLock::new(|| {
83 FxHashSet::from_iter(include!(concat!(
84 env!("OUT_DIR"),
85 "/ContentEventHandlerNames.rs"
86 )))
87});
88
89#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
90#[expect(clippy::enum_variant_names)]
91#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
92pub(crate) enum CommonEventHandler {
93 EventHandler(TracedCallback<EventHandlerNonNull>),
94
95 ErrorEventHandler(TracedCallback<OnErrorEventHandlerNonNull>),
96
97 BeforeUnloadEventHandler(TracedCallback<OnBeforeUnloadEventHandlerNonNull>),
98}
99
100impl js::gc::Rootable for CommonEventHandler {}
101
102impl CommonEventHandler {
103 fn parent(&self) -> &CallbackFunction<crate::DomTypeHolder> {
104 match *self {
105 CommonEventHandler::EventHandler(ref handler) => &handler.parent,
106 CommonEventHandler::ErrorEventHandler(ref handler) => &handler.parent,
107 CommonEventHandler::BeforeUnloadEventHandler(ref handler) => &handler.parent,
108 }
109 }
110}
111
112#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
113pub(crate) enum ListenerPhase {
114 Capturing,
115 Bubbling,
116}
117
118#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
120struct InternalRawUncompiledHandler {
121 source: DOMString,
122 #[no_trace]
123 url: ServoUrl,
124 line: usize,
125}
126
127#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
129#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
130enum InlineEventListener {
131 Uncompiled(InternalRawUncompiledHandler),
132 Compiled(CommonEventHandler),
133 Null,
134}
135
136impl js::gc::Rootable for InlineEventListener {}
137
138fn get_compiled_handler(
142 cx: &mut JSContext,
143 inline_listener: &RefCell<InlineEventListener>,
144 owner: &EventTarget,
145 ty: &Atom,
146) -> Option<CommonEventHandler> {
147 rooted!(&in(cx) let listener = mem::replace(
148 &mut *inline_listener.borrow_mut(),
149 InlineEventListener::Null,
150 ));
151 rooted!(&in(cx) let mut compiled = match &*listener {
152 InlineEventListener::Null => None,
153 InlineEventListener::Uncompiled(handler) => {
154 owner.get_compiled_event_handler(cx, handler, ty)
155 },
156 InlineEventListener::Compiled(handler) => Some(handler.clone()),
157 });
158 if let Some(ref compiled) = *compiled {
159 *inline_listener.borrow_mut() = InlineEventListener::Compiled(compiled.clone());
160 }
161 compiled.take()
162}
163
164#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
165#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
166enum EventListenerType {
167 Additive(TracedCallback<EventListener>),
168 Inline(RefCell<InlineEventListener>),
169}
170
171impl js::gc::Rootable for EventListenerType {}
172
173impl EventListenerType {
174 fn get_compiled_listener(
175 &self,
176 cx: &mut JSContext,
177 owner: &EventTarget,
178 ty: &Atom,
179 ) -> Option<CompiledEventListener> {
180 match *self {
181 EventListenerType::Inline(ref inline) => {
182 get_compiled_handler(cx, inline, owner, ty).map(CompiledEventListener::Handler)
183 },
184 EventListenerType::Additive(ref listener) => {
185 Some(CompiledEventListener::Listener(listener.clone()))
186 },
187 }
188 }
189
190 fn matches_additive(&self, callback: &EventListener) -> bool {
191 matches!(self, Self::Additive(listener) if &**listener == callback)
192 }
193}
194
195#[derive(JSTraceable)]
198#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
199pub(crate) enum CompiledEventListener {
200 Listener(TracedCallback<EventListener>),
201 Handler(CommonEventHandler),
202}
203
204impl js::gc::Rootable for CompiledEventListener {}
205
206impl CompiledEventListener {
207 #[expect(unsafe_code)]
208 pub(crate) fn associated_global(&self) -> DomRoot<GlobalScope> {
209 let obj = match self {
210 CompiledEventListener::Listener(listener) => listener.callback(),
211 CompiledEventListener::Handler(CommonEventHandler::EventHandler(handler)) => {
212 handler.callback()
213 },
214 CompiledEventListener::Handler(CommonEventHandler::ErrorEventHandler(handler)) => {
215 handler.callback()
216 },
217 CompiledEventListener::Handler(CommonEventHandler::BeforeUnloadEventHandler(
218 handler,
219 )) => handler.callback(),
220 };
221 unsafe { GlobalScope::from_object(obj) }
222 }
223
224 pub(crate) fn call_or_handle_event(
226 &self,
227 cx: &mut JSContext,
228 object: &EventTarget,
229 event: &Event,
230 exception_handle: ExceptionHandling,
231 ) -> Fallible<()> {
232 match *self {
234 CompiledEventListener::Listener(ref listener) => {
235 listener.HandleEvent_(cx, object, event, exception_handle)
236 },
237 CompiledEventListener::Handler(ref handler) => {
238 match *handler {
239 CommonEventHandler::ErrorEventHandler(ref handler) => {
240 if let Some(event) = event.downcast::<ErrorEvent>() &&
241 (object.is::<Window>() || object.is::<WorkerGlobalScope>())
242 {
243 rooted!(&in(cx) let mut error: JSVal);
244 event.Error(error.handle_mut());
245 rooted!(&in(cx) let mut rooted_return_value: JSVal);
246 let return_value = handler.Call_(
247 cx,
248 object,
249 EventOrString::String(event.Message()),
250 Some(event.Filename()),
251 Some(event.Lineno()),
252 Some(event.Colno()),
253 Some(error.handle()),
254 rooted_return_value.handle_mut(),
255 exception_handle,
256 );
257 if let Ok(()) = return_value &&
259 rooted_return_value.handle().is_boolean() &&
260 rooted_return_value.handle().to_boolean()
261 {
262 event.upcast::<Event>().PreventDefault();
263 }
264 return return_value;
265 }
266
267 rooted!(&in(cx) let mut rooted_return_value: JSVal);
268 handler.Call_(
269 cx,
270 object,
271 EventOrString::Event(DomRoot::from_ref(event)),
272 None,
273 None,
274 None,
275 None,
276 rooted_return_value.handle_mut(),
277 exception_handle,
278 )
279 },
280
281 CommonEventHandler::BeforeUnloadEventHandler(ref handler) => {
282 if let Some(event) = event.downcast::<BeforeUnloadEvent>() {
283 match handler.Call_(
285 cx,
286 object,
287 event.upcast::<Event>(),
288 exception_handle,
289 ) {
290 Ok(value) => {
291 let rv = event.ReturnValue();
292 if let Some(v) = value {
293 if rv.is_empty() {
294 event.SetReturnValue(v);
295 }
296 event.upcast::<Event>().PreventDefault();
297 }
298 Ok(())
299 },
300 Err(err) => Err(err),
301 }
302 } else {
303 handler
305 .Call_(cx, object, event.upcast::<Event>(), exception_handle)
306 .map(|_| ())
307 }
308 },
309
310 CommonEventHandler::EventHandler(ref handler) => {
311 rooted!(&in(cx) let mut rooted_return_value: JSVal);
312 match handler.Call_(
313 cx,
314 object,
315 event,
316 rooted_return_value.handle_mut(),
317 exception_handle,
318 ) {
319 Ok(()) => {
320 let value = rooted_return_value.handle();
321
322 let should_cancel = value.is_boolean() && !value.to_boolean();
324
325 if should_cancel {
326 event.PreventDefault();
330 }
331 Ok(())
332 },
333 Err(err) => Err(err),
334 }
335 },
336 }
337 },
338 }
339 }
340}
341
342#[derive(Clone, DenyPublicFields, JSTraceable, MallocSizeOf)]
345#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
346pub(crate) struct EventListenerEntry {
348 phase: ListenerPhase,
349 listener: EventListenerType,
350 once: bool,
351 passive: bool,
352 removed: bool,
353}
354
355impl EventListenerEntry {
356 pub(crate) fn phase(&self) -> ListenerPhase {
357 self.phase
358 }
359
360 pub(crate) fn once(&self) -> bool {
361 self.once
362 }
363
364 pub(crate) fn removed(&self) -> bool {
365 self.removed
366 }
367
368 pub(crate) fn get_compiled_listener(
370 &self,
371 cx: &mut JSContext,
372 owner: &EventTarget,
373 ty: &Atom,
374 ) -> Option<CompiledEventListener> {
375 self.listener.get_compiled_listener(cx, owner, ty)
376 }
377
378 fn matches_additive(&self, phase: ListenerPhase, callback: &EventListener) -> bool {
379 self.phase == phase && self.listener.matches_additive(callback)
380 }
381}
382
383impl std::cmp::PartialEq for EventListenerEntry {
384 fn eq(&self, other: &Self) -> bool {
385 self.phase == other.phase && self.listener == other.listener
386 }
387}
388
389#[derive(Clone, JSTraceable, MallocSizeOf)]
390#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
391pub(crate) struct EventListeners(
393 #[conditional_malloc_size_of] Vec<Rc<RefCell<EventListenerEntry>>>,
394);
395
396impl js::gc::Rootable for EventListeners {}
397
398impl Deref for EventListeners {
399 type Target = Vec<Rc<RefCell<EventListenerEntry>>>;
400 fn deref(&self) -> &Vec<Rc<RefCell<EventListenerEntry>>> {
401 &self.0
402 }
403}
404
405impl DerefMut for EventListeners {
406 fn deref_mut(&mut self) -> &mut Vec<Rc<RefCell<EventListenerEntry>>> {
407 &mut self.0
408 }
409}
410
411impl EventListeners {
412 fn get_inline_listener(
414 &self,
415 cx: &mut JSContext,
416 owner: &EventTarget,
417 ty: &Atom,
418 ) -> Option<CommonEventHandler> {
419 for entry in &self.0 {
420 if let EventListenerType::Inline(ref inline) = entry.borrow().listener {
421 return get_compiled_handler(cx, inline, owner, ty);
423 }
424 }
425
426 None
428 }
429
430 fn has_listeners(&self) -> bool {
431 !self.0.is_empty()
432 }
433}
434
435type EventListenerMap = HashMapTracedValues<Atom, EventListeners, FxBuildHasher>;
437
438#[dom_struct]
439pub struct EventTarget {
440 reflector_: Reflector,
441 handlers: DomRefCell<Option<Box<EventListenerMap>>>,
442}
443
444impl EventTarget {
445 pub(crate) fn new_inherited() -> EventTarget {
446 EventTarget {
447 reflector_: Reflector::new(),
448 handlers: DomRefCell::new(None),
449 }
450 }
451
452 fn ensure_handlers(&self) -> RefMut<'_, Box<EventListenerMap>> {
454 RefMut::map(self.handlers.borrow_mut(), |handlers| {
455 handlers.get_or_insert_with(Box::default)
456 })
457 }
458
459 fn new(
460 cx: &mut JSContext,
461 global: &GlobalScope,
462 proto: Option<HandleObject>,
463 ) -> DomRoot<EventTarget> {
464 reflect_dom_object_with_proto(cx, Box::new(EventTarget::new_inherited()), global, proto)
465 }
466
467 fn interest_for_event_type(&self, ty: &Atom) -> Option<ConstellationInterest> {
471 if self.is::<Window>() && *ty == atom!("storage") {
472 return Some(ConstellationInterest::StorageEvent);
473 }
474 None
475 }
476
477 fn notify_listener_added(&self, ty: &Atom) {
479 if let Some(interest) = self.interest_for_event_type(ty) {
480 self.global().register_interest(interest);
481 }
482 }
483
484 fn notify_listener_removed(&self, ty: &Atom) {
486 if let Some(interest) = self.interest_for_event_type(ty) {
487 self.global().unregister_interest(interest);
488 }
489 }
490
491 pub(crate) fn has_listeners_for(&self, type_: &Atom) -> bool {
494 match self
495 .handlers
496 .borrow()
497 .as_ref()
498 .and_then(|handlers| handlers.get(type_))
499 {
500 Some(listeners) => listeners.has_listeners(),
501 None => false,
502 }
503 }
504
505 pub(crate) fn get_listeners_for(&self, type_: &Atom) -> EventListeners {
506 self.handlers
507 .borrow()
508 .as_ref()
509 .and_then(|handlers| handlers.get(type_))
510 .map_or(EventListeners(vec![]), |listeners| listeners.clone())
511 }
512
513 pub(crate) fn remove_all_listeners(&self, cx: &JSContext) {
514 rooted!(&in(cx) let handlers = self.handlers.borrow_mut().take());
517 let Some(ref handlers) = *handlers else {
518 return;
519 };
520
521 for (ty, entries) in handlers.iter() {
522 entries
523 .iter()
524 .for_each(|entry| entry.borrow_mut().removed = true);
525 self.notify_listener_removed(ty);
526 }
527 }
528
529 fn default_passive_value(&self, ty: &Atom) -> bool {
531 let event_type = ty.trim_matches(HTML_SPACE_CHARACTERS);
533
534 let matches_event_type = event_type.eq_ignore_ascii_case("touchstart") ||
536 event_type.eq_ignore_ascii_case("touchmove") ||
537 event_type.eq_ignore_ascii_case("wheel") ||
538 event_type.eq_ignore_ascii_case("mousewheel");
539
540 if !matches_event_type {
541 return false;
542 }
543
544 if self.is::<Window>() {
546 return true;
547 }
548
549 if let Some(node) = self.downcast::<Node>() {
551 let node_document = node.owner_document();
552 let event_target = self.upcast::<EventTarget>();
553
554 return event_target == node_document.upcast::<EventTarget>()
556 || node_document.GetDocumentElement().is_some_and(|n| n.upcast::<EventTarget>() == event_target)
558 || node_document.GetBody().is_some_and(|n| n.upcast::<EventTarget>() == event_target);
560 }
561
562 false
563 }
564
565 fn set_inline_event_listener(&self, ty: Atom, listener: &mut Option<InlineEventListener>) {
567 let mut handlers = self.ensure_handlers();
568 let entries = match handlers.entry(ty.clone()) {
569 Occupied(entry) => entry.into_mut(),
570 Vacant(entry) => entry.insert(EventListeners(vec![])),
571 };
572
573 let index = entries
574 .iter()
575 .position(|entry| matches!(entry.borrow().listener, EventListenerType::Inline(_)));
576 match index {
577 Some(idx) => {
578 if listener.is_some() {
580 entries[idx].borrow_mut().listener = EventListenerType::Inline(
581 listener.take().expect("Guaranteed above").into(),
582 );
583 } else {
584 entries.remove(idx).borrow_mut().removed = true;
585 self.notify_listener_removed(&ty);
586 }
587 },
588 None => {
589 if listener.is_some() {
590 entries.push(Rc::new(RefCell::new(EventListenerEntry {
591 phase: ListenerPhase::Bubbling,
592 listener: EventListenerType::Inline(
593 listener.take().expect("Guaranteed above").into(),
594 ),
595 once: false,
596 passive: self.default_passive_value(&ty),
597 removed: false,
598 })));
599 self.notify_listener_added(&ty)
600 }
601 },
602 }
603 }
604
605 pub(crate) fn remove_listener(&self, ty: &Atom, entry: &Rc<RefCell<EventListenerEntry>>) {
606 let mut handlers = self.handlers.borrow_mut();
607
608 if let Some(handlers) = handlers.as_mut() &&
609 let Some(entries) = handlers.get_mut(ty) &&
610 let Some(position) = entries.iter().position(|e| *e == *entry)
611 {
612 entries.remove(position).borrow_mut().removed = true;
613 self.notify_listener_removed(ty);
614 }
615 }
616
617 pub(crate) fn is_passive(&self, listener: &Rc<RefCell<EventListenerEntry>>) -> bool {
619 listener.borrow().passive
620 }
621
622 pub(crate) fn has_non_passive_listener(&self, type_: &Atom) -> bool {
624 self.get_listeners_for(type_)
625 .iter()
626 .any(|listener| !self.is_passive(listener))
627 }
628
629 fn get_inline_event_listener(
630 &self,
631 cx: &mut JSContext,
632 ty: &Atom,
633 ) -> Option<CommonEventHandler> {
634 let handlers = self.handlers.borrow();
635 handlers
636 .as_ref()?
637 .get(ty)
638 .and_then(|entry| entry.get_inline_listener(cx, self, ty))
639 }
640
641 pub(crate) fn set_event_handler_uncompiled(
644 &self,
645 cx: &mut JSContext,
646 url: ServoUrl,
647 line: usize,
648 ty: &str,
649 source: &str,
650 ) {
651 if let Some(element) = self.downcast::<Element>() {
652 let doc = element.owner_document();
653 let global = &doc.global();
654 if global
655 .get_csp_list()
656 .should_elements_inline_type_behavior_be_blocked(
657 cx,
658 global,
659 element.upcast(),
660 InlineCheckType::ScriptAttribute,
661 source,
662 line as u32,
663 )
664 {
665 return;
666 }
667 };
668
669 let handler = InternalRawUncompiledHandler {
670 source: DOMString::from(source),
671 line,
672 url,
673 };
674 self.set_inline_event_listener(
675 Atom::from(ty),
676 &mut Some(InlineEventListener::Uncompiled(handler)),
677 );
678 }
679
680 #[expect(unsafe_code)]
683 fn get_compiled_event_handler(
684 &self,
685 cx: &mut JSContext,
686 handler: &InternalRawUncompiledHandler,
687 ty: &Atom,
688 ) -> Option<CommonEventHandler> {
689 let element = self.downcast::<Element>();
691 let document = match element {
692 Some(element) => element.owner_document(),
693 None => self.downcast::<Window>().unwrap().Document(),
694 };
695
696 if !document.scripting_enabled() {
698 return None;
699 }
700
701 let body: Vec<u16> = handler.source.str().encode_utf16().collect();
703
704 let form_owner = element
708 .and_then(|e| e.as_maybe_form_control())
709 .and_then(|f| f.form_owner());
710
711 let window = document.window();
719 let mut realm = enter_auto_realm(cx, window);
720 let cx = &mut realm.current_realm();
721
722 let name = CString::new(format!("on{}", &**ty)).unwrap();
725
726 const ARG_NAMES: &[*const c_char] = &[c"event".as_ptr()];
728 const ERROR_ARG_NAMES: &[*const c_char] = &[
729 c"event".as_ptr(),
730 c"source".as_ptr(),
731 c"lineno".as_ptr(),
732 c"colno".as_ptr(),
733 c"error".as_ptr(),
734 ];
735 let is_error = ty == &atom!("error") && self.is::<Window>();
736 let args = if is_error { ERROR_ARG_NAMES } else { ARG_NAMES };
737
738 let url = cformat!("{}", handler.url);
739 let mut options = CompileOptionsWrapper::new(cx, url, handler.line as u32);
740 options.set_introduction_type(IntroductionType::EVENT_HANDLER);
741
742 let scopechain = js::rust::EnvironmentChain::new(cx, SupportUnscopables::Yes);
744
745 if let Some(element) = element {
746 scopechain.append(document.reflector().get_jsobject().get());
747 if let Some(form_owner) = form_owner {
748 scopechain.append(form_owner.reflector().get_jsobject().get());
749 }
750 scopechain.append(element.reflector().get_jsobject().get());
751 }
752
753 rooted!(&in(cx) let mut handler = unsafe {
754 CompileFunction(
755 cx,
756 scopechain.get(),
757 options.ptr,
758 name.as_ptr(),
759 args.len() as u32,
760 args.as_ptr(),
761 &mut transform_u16_to_source_text(&body),
762 )
763 });
764 if handler.get().is_null() {
765 let mut realm = enter_auto_realm(cx, self);
767 report_pending_exception(&mut realm.current_realm());
768 return None;
769 }
770
771 let funobj = unsafe { JS_GetFunctionObject(handler.get()) };
777 assert!(!funobj.is_null());
778 if is_error {
780 Some(CommonEventHandler::ErrorEventHandler(unsafe {
781 OnErrorEventHandlerNonNull::new(cx, funobj).to_traced()
782 }))
783 } else if ty == &atom!("beforeunload") {
784 Some(CommonEventHandler::BeforeUnloadEventHandler(unsafe {
785 OnBeforeUnloadEventHandlerNonNull::new(cx, funobj).to_traced()
786 }))
787 } else {
788 Some(CommonEventHandler::EventHandler(unsafe {
789 EventHandlerNonNull::new(cx, funobj).to_traced()
790 }))
791 }
792 }
793
794 #[expect(unsafe_code)]
795 pub(crate) fn set_event_handler_common<T: HasCallbackHolder>(
796 &self,
797 cx: &mut JSContext,
798 ty: &str,
799 listener: Option<RootedCallback<T>>,
800 ) {
801 rooted!(&in(cx) let mut event_listener = listener.map(|listener| {
802 InlineEventListener::Compiled(CommonEventHandler::EventHandler(unsafe {
803 EventHandlerNonNull::new(cx, listener.callback()).to_traced()
804 }))
805 }));
806 self.set_inline_event_listener(Atom::from(ty), event_listener.as_mut_ref(cx.no_gc()));
807 }
808
809 #[expect(unsafe_code)]
810 pub(crate) fn set_error_event_handler<T: HasCallbackHolder>(
811 &self,
812 cx: &mut JSContext,
813 ty: &str,
814 listener: Option<RootedCallback<T>>,
815 ) {
816 rooted!(&in(cx) let mut event_listener = listener.map(|listener| {
817 InlineEventListener::Compiled(CommonEventHandler::ErrorEventHandler(unsafe {
818 OnErrorEventHandlerNonNull::new(cx, listener.callback()).to_traced()
819 }))
820 }));
821 self.set_inline_event_listener(Atom::from(ty), event_listener.as_mut_ref(cx.no_gc()));
822 }
823
824 #[expect(unsafe_code)]
825 pub(crate) fn set_beforeunload_event_handler<T: HasCallbackHolder>(
826 &self,
827 cx: &mut JSContext,
828 ty: &str,
829 listener: Option<RootedCallback<T>>,
830 ) {
831 rooted!(&in(cx) let mut event_listener = listener.map(|listener| {
832 InlineEventListener::Compiled(CommonEventHandler::BeforeUnloadEventHandler(unsafe {
833 OnBeforeUnloadEventHandlerNonNull::new(cx, listener.callback()).to_traced()
834 }))
835 }));
836 self.set_inline_event_listener(Atom::from(ty), event_listener.as_mut_ref(cx.no_gc()));
837 }
838
839 #[expect(unsafe_code)]
840 pub(crate) fn get_event_handler_common<T: CallbackContainer>(
841 &self,
842 cx: &mut JSContext,
843 ty: &str,
844 ) -> Option<RootedCallback<T>> {
845 rooted!(&in(cx) let listener = self.get_inline_event_listener(cx, &Atom::from(ty)));
846 unsafe {
847 listener
848 .as_ref(cx.no_gc())
849 .as_ref()
850 .map(|listener| T::new(cx, listener.parent().callback_holder().get()))
851 }
852 }
853
854 pub(crate) fn has_handlers(&self) -> bool {
855 self.handlers
856 .borrow()
857 .as_ref()
858 .is_some_and(|handlers| !handlers.is_empty())
859 }
860
861 pub(crate) fn fire_event(&self, cx: &mut js::context::JSContext, name: Atom) -> bool {
863 self.fire_event_with_params(
864 cx,
865 name,
866 EventBubbles::DoesNotBubble,
867 EventCancelable::NotCancelable,
868 EventComposed::NotComposed,
869 )
870 }
871
872 pub(crate) fn fire_bubbling_event(&self, cx: &mut js::context::JSContext, name: Atom) -> bool {
874 self.fire_event_with_params(
875 cx,
876 name,
877 EventBubbles::Bubbles,
878 EventCancelable::NotCancelable,
879 EventComposed::NotComposed,
880 )
881 }
882
883 pub(crate) fn fire_cancelable_event(
885 &self,
886 cx: &mut js::context::JSContext,
887 name: Atom,
888 ) -> bool {
889 self.fire_event_with_params(
890 cx,
891 name,
892 EventBubbles::DoesNotBubble,
893 EventCancelable::Cancelable,
894 EventComposed::NotComposed,
895 )
896 }
897
898 pub(crate) fn fire_bubbling_cancelable_event(
900 &self,
901 cx: &mut js::context::JSContext,
902 name: Atom,
903 ) -> bool {
904 self.fire_event_with_params(
905 cx,
906 name,
907 EventBubbles::Bubbles,
908 EventCancelable::Cancelable,
909 EventComposed::NotComposed,
910 )
911 }
912
913 pub(crate) fn fire_event_with_params(
915 &self,
916 cx: &mut js::context::JSContext,
917 name: Atom,
918 bubbles: EventBubbles,
919 cancelable: EventCancelable,
920 composed: EventComposed,
921 ) -> bool {
922 let event = Event::new(cx, &self.global(), name, bubbles, cancelable);
923 event.set_composed(composed.into());
924 event.fire(cx, self)
925 }
926
927 pub(crate) fn add_event_listener(
930 &self,
931 ty: DOMString,
932 listener: Option<RootedCallback<EventListener>>,
933 options: AddEventListenerOptions,
934 ) {
935 if let Some(signal) = options.signal.as_ref() {
936 if signal.aborted() {
938 return;
939 }
940 signal.add(&AbortAlgorithm::DomEventListener(
942 RemovableDomEventListener {
943 event_target: Dom::from_ref(self),
944 ty: ty.clone(),
945 listener: listener.as_ref().map(|listener| listener.to_traced()),
946 options: options.parent.clone(),
947 },
948 ));
949 }
950 let listener = match listener {
952 Some(l) => l,
953 None => return,
954 };
955 let ty = Atom::from(ty);
956 let mut handlers = self.ensure_handlers();
957 let entries = match handlers.entry(ty.clone()) {
958 Occupied(entry) => entry.into_mut(),
959 Vacant(entry) => entry.insert(EventListeners(vec![])),
960 };
961
962 let phase = if options.parent.capture {
963 ListenerPhase::Capturing
964 } else {
965 ListenerPhase::Bubbling
966 };
967
968 let passive = options.passive.unwrap_or(self.default_passive_value(&ty));
971
972 if entries
976 .iter()
977 .all(|entry| !entry.borrow().matches_additive(phase, &listener))
978 {
979 entries.push(Rc::new(RefCell::new(EventListenerEntry {
980 phase,
981 listener: EventListenerType::Additive(listener.to_traced()),
982 once: options.once,
983 passive,
984 removed: false,
985 })));
986 self.notify_listener_added(&ty);
987 }
988 }
989
990 pub(crate) fn remove_event_listener(
993 &self,
994 ty: DOMString,
995 listener: Option<&EventListener>,
996 options: &EventListenerOptions,
997 ) {
998 let Some(listener) = listener else {
999 return;
1000 };
1001 let ty_atom = Atom::from(ty);
1002 let mut handlers = self.handlers.borrow_mut();
1003 if let Some(handlers) = handlers.as_mut() &&
1004 let Some(entries) = handlers.get_mut(&ty_atom)
1005 {
1006 let phase = if options.capture {
1007 ListenerPhase::Capturing
1008 } else {
1009 ListenerPhase::Bubbling
1010 };
1011 if let Some(position) = entries
1012 .iter()
1013 .position(|entry| entry.borrow().matches_additive(phase, listener))
1014 {
1015 entries.remove(position).borrow_mut().removed = true;
1017 self.notify_listener_removed(&ty_atom);
1018 }
1019 }
1020 }
1021
1022 pub(crate) fn get_the_parent(&self, event: &Event) -> Option<DomRoot<EventTarget>> {
1024 if let Some(document) = self.downcast::<Document>() {
1025 if event.type_() == atom!("load") || !document.has_browsing_context() {
1026 return None;
1027 } else {
1028 return Some(DomRoot::from_ref(document.window().upcast::<EventTarget>()));
1029 }
1030 }
1031
1032 if let Some(shadow_root) = self.downcast::<ShadowRoot>() {
1033 if event.should_pass_shadow_boundary(shadow_root) {
1034 let host = shadow_root.Host();
1035 return Some(DomRoot::from_ref(host.upcast::<EventTarget>()));
1036 } else {
1037 return None;
1038 }
1039 }
1040
1041 if let Some(node) = self.downcast::<Node>() {
1042 return node.assigned_slot().map(DomRoot::upcast).or_else(|| {
1045 node.GetParentNode()
1046 .map(|parent| DomRoot::from_ref(parent.upcast::<EventTarget>()))
1047 });
1048 }
1049
1050 if let Some(request) = self.downcast::<IDBRequest>() {
1053 return request
1054 .transaction()
1055 .map(|tx| DomRoot::from_ref(tx.upcast::<EventTarget>()));
1056 }
1057
1058 if let Some(transaction) = self.downcast::<IDBTransaction>() {
1060 return Some(DomRoot::from_ref(
1061 transaction.get_db().upcast::<EventTarget>(),
1062 ));
1063 }
1064
1065 if self.is::<IDBDatabase>() {
1067 return None;
1068 }
1069
1070 None
1071 }
1072
1073 pub(crate) fn retarget(&self, b: &Self) -> DomRoot<EventTarget> {
1077 let mut a = DomRoot::from_ref(self);
1079 loop {
1080 let Some(a_node) = a.downcast::<Node>() else {
1086 return a;
1087 };
1088 let a_root = a_node.GetRootNode(&GetRootNodeOptions::empty());
1089 if !a_root.is::<ShadowRoot>() {
1090 return a;
1091 }
1092 if let Some(b_node) = b.downcast::<Node>() &&
1093 a_root.is_shadow_including_inclusive_ancestor_of(b_node)
1094 {
1095 return a;
1096 }
1097
1098 a = DomRoot::from_ref(
1100 a_root
1101 .downcast::<ShadowRoot>()
1102 .unwrap()
1103 .Host()
1104 .upcast::<EventTarget>(),
1105 );
1106 }
1107 }
1108
1109 pub(crate) fn is_content_event_handler(name: &str) -> bool {
1111 CONTENT_EVENT_HANDLER_NAMES.contains(&name)
1112 }
1113
1114 pub(crate) fn summarize_event_listeners_for_devtools(&self) -> Vec<EventListenerInfo> {
1115 let handlers = self.handlers.borrow();
1116 let Some(handlers) = handlers.as_ref() else {
1117 return Vec::new();
1118 };
1119 let mut listener_infos = Vec::with_capacity(handlers.0.len());
1120 for (event_type, event_listeners) in &handlers.0 {
1121 for event_listener in event_listeners.iter() {
1122 let event_listener_entry = event_listener.borrow();
1123 listener_infos.push(EventListenerInfo {
1124 event_type: event_type.to_string(),
1125 capturing: event_listener_entry.phase() == ListenerPhase::Capturing,
1126 });
1127 }
1128 }
1129
1130 listener_infos
1131 }
1132}
1133
1134impl EventTargetMethods<crate::DomTypeHolder> for EventTarget {
1135 fn Constructor(
1137 cx: &mut JSContext,
1138 global: &GlobalScope,
1139 proto: Option<HandleObject>,
1140 ) -> Fallible<DomRoot<EventTarget>> {
1141 Ok(EventTarget::new(cx, global, proto))
1142 }
1143
1144 fn AddEventListener(
1146 &self,
1147 ty: DOMString,
1148 listener: Option<RootedCallback<EventListener>>,
1149 options: AddEventListenerOptionsOrBoolean,
1150 ) {
1151 self.add_event_listener(ty, listener, options.convert())
1152 }
1153
1154 fn RemoveEventListener(
1156 &self,
1157 ty: DOMString,
1158 listener: Option<RootedCallback<EventListener>>,
1159 options: EventListenerOptionsOrBoolean,
1160 ) {
1161 self.remove_event_listener(ty, listener.as_deref(), &options.convert())
1162 }
1163
1164 fn DispatchEvent(&self, cx: &mut JSContext, event: &Event) -> Fallible<bool> {
1166 if event.dispatching() || !event.initialized() {
1167 return Err(Error::InvalidState(None));
1168 }
1169 event.set_trusted(false);
1170 Ok(event.dispatch(cx, self, false))
1171 }
1172}
1173
1174impl VirtualMethods for EventTarget {
1175 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1176 None
1177 }
1178}
1179
1180impl Convert<AddEventListenerOptions> for AddEventListenerOptionsOrBoolean {
1181 fn convert(self) -> AddEventListenerOptions {
1183 match self {
1186 AddEventListenerOptionsOrBoolean::AddEventListenerOptions(options) => options,
1188 AddEventListenerOptionsOrBoolean::Boolean(capture) => AddEventListenerOptions {
1189 parent: EventListenerOptions { capture },
1190 once: false,
1192 passive: None,
1194 signal: None,
1195 },
1196 }
1197 }
1198}
1199
1200impl Convert<EventListenerOptions> for EventListenerOptionsOrBoolean {
1201 fn convert(self) -> EventListenerOptions {
1202 match self {
1203 EventListenerOptionsOrBoolean::EventListenerOptions(options) => options,
1204 EventListenerOptionsOrBoolean::Boolean(capture) => EventListenerOptions { capture },
1205 }
1206 }
1207}
1208
1209impl OwnerWindow<crate::DomTypeHolder> for EventTarget {
1210 fn owner_window(&self) -> Option<DomRoot<Window>> {
1211 self.downcast::<Node>().map(|node| node.owner_window())
1212 }
1213}