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