1use std::cell::RefCell;
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::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::{Node, NodeTraits};
71use crate::dom::shadowroot::ShadowRoot;
72use crate::dom::virtualmethods::VirtualMethods;
73use crate::dom::window::Window;
74use crate::dom::workerglobalscope::WorkerGlobalScope;
75use crate::realms::{InRealm, enter_realm};
76use crate::script_runtime::CanGc;
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)]
90pub(crate) enum CommonEventHandler {
91 EventHandler(#[conditional_malloc_size_of] Rc<EventHandlerNonNull>),
92
93 ErrorEventHandler(#[conditional_malloc_size_of] Rc<OnErrorEventHandlerNonNull>),
94
95 BeforeUnloadEventHandler(#[conditional_malloc_size_of] Rc<OnBeforeUnloadEventHandlerNonNull>),
96}
97
98impl CommonEventHandler {
99 fn parent(&self) -> &CallbackFunction<crate::DomTypeHolder> {
100 match *self {
101 CommonEventHandler::EventHandler(ref handler) => &handler.parent,
102 CommonEventHandler::ErrorEventHandler(ref handler) => &handler.parent,
103 CommonEventHandler::BeforeUnloadEventHandler(ref handler) => &handler.parent,
104 }
105 }
106}
107
108#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
109pub(crate) enum ListenerPhase {
110 Capturing,
111 Bubbling,
112}
113
114#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
116struct InternalRawUncompiledHandler {
117 source: DOMString,
118 #[no_trace]
119 url: ServoUrl,
120 line: usize,
121}
122
123#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
125enum InlineEventListener {
126 Uncompiled(InternalRawUncompiledHandler),
127 Compiled(CommonEventHandler),
128 Null,
129}
130
131fn get_compiled_handler(
135 cx: &mut JSContext,
136 inline_listener: &RefCell<InlineEventListener>,
137 owner: &EventTarget,
138 ty: &Atom,
139) -> Option<CommonEventHandler> {
140 let listener = mem::replace(
141 &mut *inline_listener.borrow_mut(),
142 InlineEventListener::Null,
143 );
144 let compiled = match listener {
145 InlineEventListener::Null => None,
146 InlineEventListener::Uncompiled(handler) => {
147 owner.get_compiled_event_handler(cx, handler, ty)
148 },
149 InlineEventListener::Compiled(handler) => Some(handler),
150 };
151 if let Some(ref compiled) = compiled {
152 *inline_listener.borrow_mut() = InlineEventListener::Compiled(compiled.clone());
153 }
154 compiled
155}
156
157#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
158enum EventListenerType {
159 Additive(#[conditional_malloc_size_of] Rc<EventListener>),
160 Inline(RefCell<InlineEventListener>),
161}
162
163impl EventListenerType {
164 fn get_compiled_listener(
165 &self,
166 cx: &mut JSContext,
167 owner: &EventTarget,
168 ty: &Atom,
169 ) -> Option<CompiledEventListener> {
170 match *self {
171 EventListenerType::Inline(ref inline) => {
172 get_compiled_handler(cx, inline, owner, ty).map(CompiledEventListener::Handler)
173 },
174 EventListenerType::Additive(ref listener) => {
175 Some(CompiledEventListener::Listener(listener.clone()))
176 },
177 }
178 }
179}
180
181pub(crate) enum CompiledEventListener {
184 Listener(Rc<EventListener>),
185 Handler(CommonEventHandler),
186}
187
188impl CompiledEventListener {
189 #[expect(unsafe_code)]
190 pub(crate) fn associated_global(&self) -> DomRoot<GlobalScope> {
191 let obj = match self {
192 CompiledEventListener::Listener(listener) => listener.callback(),
193 CompiledEventListener::Handler(CommonEventHandler::EventHandler(handler)) => {
194 handler.callback()
195 },
196 CompiledEventListener::Handler(CommonEventHandler::ErrorEventHandler(handler)) => {
197 handler.callback()
198 },
199 CompiledEventListener::Handler(CommonEventHandler::BeforeUnloadEventHandler(
200 handler,
201 )) => handler.callback(),
202 };
203 unsafe { GlobalScope::from_object(obj) }
204 }
205
206 pub(crate) fn call_or_handle_event(
208 &self,
209 cx: &mut JSContext,
210 object: &EventTarget,
211 event: &Event,
212 exception_handle: ExceptionHandling,
213 ) -> Fallible<()> {
214 match *self {
216 CompiledEventListener::Listener(ref listener) => {
217 listener.HandleEvent_(cx, object, event, exception_handle)
218 },
219 CompiledEventListener::Handler(ref handler) => {
220 match *handler {
221 CommonEventHandler::ErrorEventHandler(ref handler) => {
222 if let Some(event) = event.downcast::<ErrorEvent>() &&
223 (object.is::<Window>() || object.is::<WorkerGlobalScope>())
224 {
225 rooted!(&in(cx) let mut error: JSVal);
226 event.Error(cx.into(), error.handle_mut());
227 rooted!(&in(cx) let mut rooted_return_value: JSVal);
228 let return_value = handler.Call_(
229 cx,
230 object,
231 EventOrString::String(event.Message()),
232 Some(event.Filename()),
233 Some(event.Lineno()),
234 Some(event.Colno()),
235 Some(error.handle()),
236 rooted_return_value.handle_mut(),
237 exception_handle,
238 );
239 if let Ok(()) = return_value &&
241 rooted_return_value.handle().is_boolean() &&
242 rooted_return_value.handle().to_boolean()
243 {
244 event.upcast::<Event>().PreventDefault();
245 }
246 return return_value;
247 }
248
249 rooted!(&in(cx) let mut rooted_return_value: JSVal);
250 handler.Call_(
251 cx,
252 object,
253 EventOrString::Event(DomRoot::from_ref(event)),
254 None,
255 None,
256 None,
257 None,
258 rooted_return_value.handle_mut(),
259 exception_handle,
260 )
261 },
262
263 CommonEventHandler::BeforeUnloadEventHandler(ref handler) => {
264 if let Some(event) = event.downcast::<BeforeUnloadEvent>() {
265 match handler.Call_(
267 cx,
268 object,
269 event.upcast::<Event>(),
270 exception_handle,
271 ) {
272 Ok(value) => {
273 let rv = event.ReturnValue();
274 if let Some(v) = value {
275 if rv.is_empty() {
276 event.SetReturnValue(v);
277 }
278 event.upcast::<Event>().PreventDefault();
279 }
280 Ok(())
281 },
282 Err(err) => Err(err),
283 }
284 } else {
285 handler
287 .Call_(cx, object, event.upcast::<Event>(), exception_handle)
288 .map(|_| ())
289 }
290 },
291
292 CommonEventHandler::EventHandler(ref handler) => {
293 rooted!(&in(cx) let mut rooted_return_value: JSVal);
294 match handler.Call_(
295 cx,
296 object,
297 event,
298 rooted_return_value.handle_mut(),
299 exception_handle,
300 ) {
301 Ok(()) => {
302 let value = rooted_return_value.handle();
303
304 let should_cancel = value.is_boolean() && !value.to_boolean();
306
307 if should_cancel {
308 event.PreventDefault();
312 }
313 Ok(())
314 },
315 Err(err) => Err(err),
316 }
317 },
318 }
319 },
320 }
321 }
322}
323
324#[derive(Clone, DenyPublicFields, JSTraceable, MallocSizeOf)]
327pub(crate) struct EventListenerEntry {
329 phase: ListenerPhase,
330 listener: EventListenerType,
331 once: bool,
332 passive: bool,
333 removed: bool,
334}
335
336impl EventListenerEntry {
337 pub(crate) fn phase(&self) -> ListenerPhase {
338 self.phase
339 }
340
341 pub(crate) fn once(&self) -> bool {
342 self.once
343 }
344
345 pub(crate) fn removed(&self) -> bool {
346 self.removed
347 }
348
349 pub(crate) fn get_compiled_listener(
351 &self,
352 cx: &mut JSContext,
353 owner: &EventTarget,
354 ty: &Atom,
355 ) -> Option<CompiledEventListener> {
356 self.listener.get_compiled_listener(cx, owner, ty)
357 }
358}
359
360impl std::cmp::PartialEq for EventListenerEntry {
361 fn eq(&self, other: &Self) -> bool {
362 self.phase == other.phase && self.listener == other.listener
363 }
364}
365
366#[derive(Clone, JSTraceable, MallocSizeOf)]
367pub(crate) struct EventListeners(
369 #[conditional_malloc_size_of] Vec<Rc<RefCell<EventListenerEntry>>>,
370);
371
372impl Deref for EventListeners {
373 type Target = Vec<Rc<RefCell<EventListenerEntry>>>;
374 fn deref(&self) -> &Vec<Rc<RefCell<EventListenerEntry>>> {
375 &self.0
376 }
377}
378
379impl DerefMut for EventListeners {
380 fn deref_mut(&mut self) -> &mut Vec<Rc<RefCell<EventListenerEntry>>> {
381 &mut self.0
382 }
383}
384
385impl EventListeners {
386 fn get_inline_listener(
388 &self,
389 cx: &mut JSContext,
390 owner: &EventTarget,
391 ty: &Atom,
392 ) -> Option<CommonEventHandler> {
393 for entry in &self.0 {
394 if let EventListenerType::Inline(ref inline) = entry.borrow().listener {
395 return get_compiled_handler(cx, inline, owner, ty);
397 }
398 }
399
400 None
402 }
403
404 fn has_listeners(&self) -> bool {
405 !self.0.is_empty()
406 }
407}
408
409#[dom_struct]
410pub struct EventTarget {
411 reflector_: Reflector,
412 handlers: DomRefCell<HashMapTracedValues<Atom, EventListeners, FxBuildHasher>>,
413}
414
415impl EventTarget {
416 pub(crate) fn new_inherited() -> EventTarget {
417 EventTarget {
418 reflector_: Reflector::new(),
419 handlers: DomRefCell::new(Default::default()),
420 }
421 }
422
423 fn new(
424 global: &GlobalScope,
425 proto: Option<HandleObject>,
426 can_gc: CanGc,
427 ) -> DomRoot<EventTarget> {
428 reflect_dom_object_with_proto(
429 Box::new(EventTarget::new_inherited()),
430 global,
431 proto,
432 can_gc,
433 )
434 }
435
436 fn interest_for_event_type(&self, ty: &Atom) -> Option<ConstellationInterest> {
440 if self.is::<Window>() && *ty == atom!("storage") {
441 return Some(ConstellationInterest::StorageEvent);
442 }
443 None
444 }
445
446 fn notify_listener_added(&self, ty: &Atom) {
448 if let Some(interest) = self.interest_for_event_type(ty) {
449 self.global().register_interest(interest);
450 }
451 }
452
453 fn notify_listener_removed(&self, ty: &Atom) {
455 if let Some(interest) = self.interest_for_event_type(ty) {
456 self.global().unregister_interest(interest);
457 }
458 }
459
460 pub(crate) fn has_listeners_for(&self, type_: &Atom) -> bool {
463 match self.handlers.borrow().get(type_) {
464 Some(listeners) => listeners.has_listeners(),
465 None => false,
466 }
467 }
468
469 pub(crate) fn get_listeners_for(&self, type_: &Atom) -> EventListeners {
470 self.handlers
471 .borrow()
472 .get(type_)
473 .map_or(EventListeners(vec![]), |listeners| listeners.clone())
474 }
475
476 pub(crate) fn dispatch_event(&self, cx: &mut JSContext, event: &Event) -> bool {
477 event.dispatch(cx, self, false)
478 }
479
480 pub(crate) fn remove_all_listeners(&self) {
481 let mut handlers = self.handlers.borrow_mut();
482 for (ty, entries) in handlers.iter() {
483 entries
484 .iter()
485 .for_each(|entry| entry.borrow_mut().removed = true);
486 self.notify_listener_removed(ty);
487 }
488
489 *handlers = Default::default();
490 }
491
492 fn default_passive_value(&self, ty: &Atom) -> bool {
494 let event_type = ty.to_ascii_lowercase();
496
497 let matches_event_type = matches!(
499 event_type.trim_matches(HTML_SPACE_CHARACTERS),
500 "touchstart" | "touchmove" | "wheel" | "mousewheel"
501 );
502
503 if !matches_event_type {
504 return false;
505 }
506
507 if self.is::<Window>() {
509 return true;
510 }
511
512 if let Some(node) = self.downcast::<Node>() {
514 let node_document = node.owner_document();
515 let event_target = self.upcast::<EventTarget>();
516
517 return event_target == node_document.upcast::<EventTarget>()
519 || node_document.GetDocumentElement().is_some_and(|n| n.upcast::<EventTarget>() == event_target)
521 || node_document.GetBody().is_some_and(|n| n.upcast::<EventTarget>() == event_target);
523 }
524
525 false
526 }
527
528 fn set_inline_event_listener(&self, ty: Atom, listener: Option<InlineEventListener>) {
530 let mut handlers = self.handlers.borrow_mut();
531 let entries = match handlers.entry(ty.clone()) {
532 Occupied(entry) => entry.into_mut(),
533 Vacant(entry) => entry.insert(EventListeners(vec![])),
534 };
535
536 let idx = entries
537 .iter()
538 .position(|entry| matches!(entry.borrow().listener, EventListenerType::Inline(_)));
539
540 match idx {
541 Some(idx) => match listener {
542 Some(listener) => {
545 entries[idx].borrow_mut().listener = EventListenerType::Inline(listener.into());
546 },
547 None => {
548 entries.remove(idx).borrow_mut().removed = true;
549 self.notify_listener_removed(&ty);
550 },
551 },
552 None => {
553 if let Some(listener) = listener {
554 entries.push(Rc::new(RefCell::new(EventListenerEntry {
555 phase: ListenerPhase::Bubbling,
556 listener: EventListenerType::Inline(listener.into()),
557 once: false,
558 passive: self.default_passive_value(&ty),
559 removed: false,
560 })));
561 self.notify_listener_added(&ty)
562 }
563 },
564 }
565 }
566
567 pub(crate) fn remove_listener(&self, ty: &Atom, entry: &Rc<RefCell<EventListenerEntry>>) {
568 let mut handlers = self.handlers.borrow_mut();
569
570 if let Some(entries) = handlers.get_mut(ty) &&
571 let Some(position) = entries.iter().position(|e| *e == *entry)
572 {
573 entries.remove(position).borrow_mut().removed = true;
574 self.notify_listener_removed(ty);
575 }
576 }
577
578 pub(crate) fn is_passive(&self, listener: &Rc<RefCell<EventListenerEntry>>) -> bool {
580 listener.borrow().passive
581 }
582
583 fn get_inline_event_listener(
584 &self,
585 cx: &mut JSContext,
586 ty: &Atom,
587 ) -> Option<CommonEventHandler> {
588 let handlers = self.handlers.borrow();
589 handlers
590 .get(ty)
591 .and_then(|entry| entry.get_inline_listener(cx, self, ty))
592 }
593
594 pub(crate) fn set_event_handler_uncompiled(
597 &self,
598 url: ServoUrl,
599 line: usize,
600 ty: &str,
601 source: &str,
602 ) {
603 if let Some(element) = self.downcast::<Element>() {
604 let doc = element.owner_document();
605 let global = &doc.global();
606 if global
607 .get_csp_list()
608 .should_elements_inline_type_behavior_be_blocked(
609 global,
610 element.upcast(),
611 InlineCheckType::ScriptAttribute,
612 source,
613 line as u32,
614 )
615 {
616 return;
617 }
618 };
619
620 let handler = InternalRawUncompiledHandler {
621 source: DOMString::from(source),
622 line,
623 url,
624 };
625 self.set_inline_event_listener(
626 Atom::from(ty),
627 Some(InlineEventListener::Uncompiled(handler)),
628 );
629 }
630
631 #[expect(unsafe_code)]
634 fn get_compiled_event_handler(
635 &self,
636 cx: &mut JSContext,
637 handler: InternalRawUncompiledHandler,
638 ty: &Atom,
639 ) -> Option<CommonEventHandler> {
640 let element = self.downcast::<Element>();
642 let document = match element {
643 Some(element) => element.owner_document(),
644 None => self.downcast::<Window>().unwrap().Document(),
645 };
646
647 if !document.scripting_enabled() {
649 return None;
650 }
651
652 let body: Vec<u16> = handler.source.str().encode_utf16().collect();
654
655 let form_owner = element
659 .and_then(|e| e.as_maybe_form_control())
660 .and_then(|f| f.form_owner());
661
662 let window = document.window();
670 let _ac = enter_realm(window);
671
672 let name = CString::new(format!("on{}", &**ty)).unwrap();
675
676 const ARG_NAMES: &[*const c_char] = &[c"event".as_ptr()];
678 const ERROR_ARG_NAMES: &[*const c_char] = &[
679 c"event".as_ptr(),
680 c"source".as_ptr(),
681 c"lineno".as_ptr(),
682 c"colno".as_ptr(),
683 c"error".as_ptr(),
684 ];
685 let is_error = ty == &atom!("error") && self.is::<Window>();
686 let args = if is_error { ERROR_ARG_NAMES } else { ARG_NAMES };
687
688 let url = cformat!("{}", handler.url);
689 let options =
690 unsafe { CompileOptionsWrapper::new_raw(cx.raw_cx(), url, handler.line as u32) };
691
692 let scopechain =
694 js::rust::EnvironmentChain::new(unsafe { cx.raw_cx() }, SupportUnscopables::Yes);
695
696 if let Some(element) = element {
697 scopechain.append(document.reflector().get_jsobject().get());
698 if let Some(form_owner) = form_owner {
699 scopechain.append(form_owner.reflector().get_jsobject().get());
700 }
701 scopechain.append(element.reflector().get_jsobject().get());
702 }
703
704 rooted!(&in(cx) let mut handler = unsafe {
705 CompileFunction(
706 cx,
707 scopechain.get(),
708 options.ptr,
709 name.as_ptr(),
710 args.len() as u32,
711 args.as_ptr(),
712 &mut transform_u16_to_source_text(&body),
713 )
714 });
715 if handler.get().is_null() {
716 let ar = enter_realm(self);
718 report_pending_exception(cx.into(), InRealm::Entered(&ar), CanGc::from_cx(cx));
719 return None;
720 }
721
722 let funobj = unsafe { JS_GetFunctionObject(handler.get()) };
728 assert!(!funobj.is_null());
729 if is_error {
731 Some(CommonEventHandler::ErrorEventHandler(unsafe {
732 OnErrorEventHandlerNonNull::new(cx.into(), funobj)
733 }))
734 } else if ty == &atom!("beforeunload") {
735 Some(CommonEventHandler::BeforeUnloadEventHandler(unsafe {
736 OnBeforeUnloadEventHandlerNonNull::new(cx.into(), funobj)
737 }))
738 } else {
739 Some(CommonEventHandler::EventHandler(unsafe {
740 EventHandlerNonNull::new(cx.into(), funobj)
741 }))
742 }
743 }
744
745 #[expect(unsafe_code)]
746 pub(crate) fn set_event_handler_common<T: CallbackContainer<crate::DomTypeHolder>>(
747 &self,
748 cx: &mut JSContext,
749 ty: &str,
750 listener: Option<Rc<T>>,
751 ) {
752 let event_listener = listener.map(|listener| {
753 InlineEventListener::Compiled(CommonEventHandler::EventHandler(unsafe {
754 EventHandlerNonNull::new(cx.into(), listener.callback())
755 }))
756 });
757 self.set_inline_event_listener(Atom::from(ty), event_listener);
758 }
759
760 #[expect(unsafe_code)]
761 pub(crate) fn set_error_event_handler<T: CallbackContainer<crate::DomTypeHolder>>(
762 &self,
763 cx: &mut JSContext,
764 ty: &str,
765 listener: Option<Rc<T>>,
766 ) {
767 let event_listener = listener.map(|listener| {
768 InlineEventListener::Compiled(CommonEventHandler::ErrorEventHandler(unsafe {
769 OnErrorEventHandlerNonNull::new(cx.into(), listener.callback())
770 }))
771 });
772 self.set_inline_event_listener(Atom::from(ty), event_listener);
773 }
774
775 #[expect(unsafe_code)]
776 pub(crate) fn set_beforeunload_event_handler<T: CallbackContainer<crate::DomTypeHolder>>(
777 &self,
778 cx: &mut JSContext,
779 ty: &str,
780 listener: Option<Rc<T>>,
781 ) {
782 let event_listener = listener.map(|listener| {
783 InlineEventListener::Compiled(CommonEventHandler::BeforeUnloadEventHandler(unsafe {
784 OnBeforeUnloadEventHandlerNonNull::new(cx.into(), listener.callback())
785 }))
786 });
787 self.set_inline_event_listener(Atom::from(ty), event_listener);
788 }
789
790 #[expect(unsafe_code)]
791 pub(crate) fn get_event_handler_common<T: CallbackContainer<crate::DomTypeHolder>>(
792 &self,
793 cx: &mut JSContext,
794 ty: &str,
795 ) -> Option<Rc<T>> {
796 let listener = self.get_inline_event_listener(cx, &Atom::from(ty));
797 unsafe {
798 listener.map(|listener| {
799 CallbackContainer::new(cx.into(), listener.parent().callback_holder().get())
800 })
801 }
802 }
803
804 pub(crate) fn has_handlers(&self) -> bool {
805 !self.handlers.borrow().is_empty()
806 }
807
808 pub(crate) fn fire_event(&self, cx: &mut js::context::JSContext, name: Atom) -> bool {
810 self.fire_event_with_params(
811 cx,
812 name,
813 EventBubbles::DoesNotBubble,
814 EventCancelable::NotCancelable,
815 EventComposed::NotComposed,
816 )
817 }
818
819 pub(crate) fn fire_bubbling_event(&self, cx: &mut js::context::JSContext, name: Atom) -> bool {
821 self.fire_event_with_params(
822 cx,
823 name,
824 EventBubbles::Bubbles,
825 EventCancelable::NotCancelable,
826 EventComposed::NotComposed,
827 )
828 }
829
830 pub(crate) fn fire_cancelable_event(
832 &self,
833 cx: &mut js::context::JSContext,
834 name: Atom,
835 ) -> bool {
836 self.fire_event_with_params(
837 cx,
838 name,
839 EventBubbles::DoesNotBubble,
840 EventCancelable::Cancelable,
841 EventComposed::NotComposed,
842 )
843 }
844
845 pub(crate) fn fire_bubbling_cancelable_event(
847 &self,
848 cx: &mut js::context::JSContext,
849 name: Atom,
850 ) -> bool {
851 self.fire_event_with_params(
852 cx,
853 name,
854 EventBubbles::Bubbles,
855 EventCancelable::Cancelable,
856 EventComposed::NotComposed,
857 )
858 }
859
860 pub(crate) fn fire_event_with_params(
862 &self,
863 cx: &mut js::context::JSContext,
864 name: Atom,
865 bubbles: EventBubbles,
866 cancelable: EventCancelable,
867 composed: EventComposed,
868 ) -> bool {
869 let event = Event::new(
870 &self.global(),
871 name,
872 bubbles,
873 cancelable,
874 CanGc::from_cx(cx),
875 );
876 event.set_composed(composed.into());
877 event.fire(self, CanGc::from_cx(cx))
878 }
879
880 pub(crate) fn add_event_listener(
883 &self,
884 ty: DOMString,
885 listener: Option<Rc<EventListener>>,
886 options: AddEventListenerOptions,
887 ) {
888 if let Some(signal) = options.signal.as_ref() {
889 if signal.aborted() {
891 return;
892 }
893 signal.add(&AbortAlgorithm::DomEventListener(
895 RemovableDomEventListener {
896 event_target: Dom::from_ref(self),
897 ty: ty.clone(),
898 listener: listener.clone(),
899 options: options.parent.clone(),
900 },
901 ));
902 }
903 let listener = match listener {
905 Some(l) => l,
906 None => return,
907 };
908 let ty = Atom::from(ty);
909 let mut handlers = self.handlers.borrow_mut();
910 let entries = match handlers.entry(ty.clone()) {
911 Occupied(entry) => entry.into_mut(),
912 Vacant(entry) => entry.insert(EventListeners(vec![])),
913 };
914
915 let phase = if options.parent.capture {
916 ListenerPhase::Capturing
917 } else {
918 ListenerPhase::Bubbling
919 };
920 let new_entry = Rc::new(RefCell::new(EventListenerEntry {
922 phase,
923 listener: EventListenerType::Additive(listener),
924 once: options.once,
925 passive: options.passive.unwrap_or(self.default_passive_value(&ty)),
926 removed: false,
927 }));
928
929 if !entries.contains(&new_entry) {
933 entries.push(new_entry);
934 self.notify_listener_added(&ty);
935 }
936 }
937
938 pub(crate) fn remove_event_listener(
941 &self,
942 ty: DOMString,
943 listener: &Option<Rc<EventListener>>,
944 options: &EventListenerOptions,
945 ) {
946 let Some(listener) = listener else {
947 return;
948 };
949 let ty_atom = Atom::from(ty);
950 let mut handlers = self.handlers.borrow_mut();
951 if let Some(entries) = handlers.get_mut(&ty_atom) {
952 let phase = if options.capture {
953 ListenerPhase::Capturing
954 } else {
955 ListenerPhase::Bubbling
956 };
957 let listener_type = EventListenerType::Additive(listener.clone());
958 if let Some(position) = entries
959 .iter()
960 .position(|e| e.borrow().listener == listener_type && e.borrow().phase == phase)
961 {
962 entries.remove(position).borrow_mut().removed = true;
964 self.notify_listener_removed(&ty_atom);
965 }
966 }
967 }
968
969 pub(crate) fn get_the_parent(&self, event: &Event) -> Option<DomRoot<EventTarget>> {
971 if let Some(document) = self.downcast::<Document>() {
972 if event.type_() == atom!("load") || !document.has_browsing_context() {
973 return None;
974 } else {
975 return Some(DomRoot::from_ref(document.window().upcast::<EventTarget>()));
976 }
977 }
978
979 if let Some(shadow_root) = self.downcast::<ShadowRoot>() {
980 if event.should_pass_shadow_boundary(shadow_root) {
981 let host = shadow_root.Host();
982 return Some(DomRoot::from_ref(host.upcast::<EventTarget>()));
983 } else {
984 return None;
985 }
986 }
987
988 if let Some(node) = self.downcast::<Node>() {
989 return node.assigned_slot().map(DomRoot::upcast).or_else(|| {
992 node.GetParentNode()
993 .map(|parent| DomRoot::from_ref(parent.upcast::<EventTarget>()))
994 });
995 }
996
997 if let Some(request) = self.downcast::<IDBRequest>() {
1000 return request
1001 .transaction()
1002 .map(|tx| DomRoot::from_ref(tx.upcast::<EventTarget>()));
1003 }
1004
1005 if let Some(transaction) = self.downcast::<IDBTransaction>() {
1007 return Some(DomRoot::from_ref(
1008 transaction.get_db().upcast::<EventTarget>(),
1009 ));
1010 }
1011
1012 if self.downcast::<IDBDatabase>().is_some() {
1014 return None;
1015 }
1016
1017 None
1018 }
1019
1020 pub(crate) fn retarget(&self, b: &Self) -> DomRoot<EventTarget> {
1024 let mut a = DomRoot::from_ref(self);
1026 loop {
1027 let Some(a_node) = a.downcast::<Node>() else {
1033 return a;
1034 };
1035 let a_root = a_node.GetRootNode(&GetRootNodeOptions::empty());
1036 if !a_root.is::<ShadowRoot>() {
1037 return a;
1038 }
1039 if let Some(b_node) = b.downcast::<Node>() &&
1040 a_root.is_shadow_including_inclusive_ancestor_of(b_node)
1041 {
1042 return a;
1043 }
1044
1045 a = DomRoot::from_ref(
1047 a_root
1048 .downcast::<ShadowRoot>()
1049 .unwrap()
1050 .Host()
1051 .upcast::<EventTarget>(),
1052 );
1053 }
1054 }
1055
1056 pub(crate) fn is_content_event_handler(name: &str) -> bool {
1058 CONTENT_EVENT_HANDLER_NAMES.contains(&name)
1059 }
1060
1061 pub(crate) fn summarize_event_listeners_for_devtools(&self) -> Vec<EventListenerInfo> {
1062 let handlers = self.handlers.borrow();
1063 let mut listener_infos = Vec::with_capacity(handlers.0.len());
1064 for (event_type, event_listeners) in &handlers.0 {
1065 for event_listener in event_listeners.iter() {
1066 let event_listener_entry = event_listener.borrow();
1067 listener_infos.push(EventListenerInfo {
1068 event_type: event_type.to_string(),
1069 capturing: event_listener_entry.phase() == ListenerPhase::Capturing,
1070 });
1071 }
1072 }
1073
1074 listener_infos
1075 }
1076}
1077
1078impl EventTargetMethods<crate::DomTypeHolder> for EventTarget {
1079 fn Constructor(
1081 global: &GlobalScope,
1082 proto: Option<HandleObject>,
1083 can_gc: CanGc,
1084 ) -> Fallible<DomRoot<EventTarget>> {
1085 Ok(EventTarget::new(global, proto, can_gc))
1086 }
1087
1088 fn AddEventListener(
1090 &self,
1091 ty: DOMString,
1092 listener: Option<Rc<EventListener>>,
1093 options: AddEventListenerOptionsOrBoolean,
1094 ) {
1095 self.add_event_listener(ty, listener, options.convert())
1096 }
1097
1098 fn RemoveEventListener(
1100 &self,
1101 ty: DOMString,
1102 listener: Option<Rc<EventListener>>,
1103 options: EventListenerOptionsOrBoolean,
1104 ) {
1105 self.remove_event_listener(ty, &listener, &options.convert())
1106 }
1107
1108 fn DispatchEvent(&self, cx: &mut JSContext, event: &Event) -> Fallible<bool> {
1110 if event.dispatching() || !event.initialized() {
1111 return Err(Error::InvalidState(None));
1112 }
1113 event.set_trusted(false);
1114 Ok(self.dispatch_event(cx, event))
1115 }
1116}
1117
1118impl VirtualMethods for EventTarget {
1119 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1120 None
1121 }
1122}
1123
1124impl Convert<AddEventListenerOptions> for AddEventListenerOptionsOrBoolean {
1125 fn convert(self) -> AddEventListenerOptions {
1127 match self {
1130 AddEventListenerOptionsOrBoolean::AddEventListenerOptions(options) => options,
1132 AddEventListenerOptionsOrBoolean::Boolean(capture) => AddEventListenerOptions {
1133 parent: EventListenerOptions { capture },
1134 once: false,
1136 passive: None,
1138 signal: None,
1139 },
1140 }
1141 }
1142}
1143
1144impl Convert<EventListenerOptions> for EventListenerOptionsOrBoolean {
1145 fn convert(self) -> EventListenerOptions {
1146 match self {
1147 EventListenerOptionsOrBoolean::EventListenerOptions(options) => options,
1148 EventListenerOptionsOrBoolean::Boolean(capture) => EventListenerOptions { capture },
1149 }
1150 }
1151}