Skip to main content

script/dom/event/
eventtarget.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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::OwnerWindow;
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;
74use crate::dom::window::Window;
75use crate::dom::workerglobalscope::WorkerGlobalScope;
76use crate::realms::enter_auto_realm;
77use crate::runtime::script_runtime::IntroductionType;
78
79/// <https://html.spec.whatwg.org/multipage/#event-handler-content-attributes>
80/// Generated from WebIDL definitions of EventHandler attributes on interfaces
81/// that inherit from Node.
82pub(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)]
91pub(crate) enum CommonEventHandler {
92    EventHandler(#[conditional_malloc_size_of] Rc<EventHandlerNonNull>),
93
94    ErrorEventHandler(#[conditional_malloc_size_of] Rc<OnErrorEventHandlerNonNull>),
95
96    BeforeUnloadEventHandler(#[conditional_malloc_size_of] Rc<OnBeforeUnloadEventHandlerNonNull>),
97}
98
99impl CommonEventHandler {
100    fn parent(&self) -> &CallbackFunction<crate::DomTypeHolder> {
101        match *self {
102            CommonEventHandler::EventHandler(ref handler) => &handler.parent,
103            CommonEventHandler::ErrorEventHandler(ref handler) => &handler.parent,
104            CommonEventHandler::BeforeUnloadEventHandler(ref handler) => &handler.parent,
105        }
106    }
107}
108
109#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
110pub(crate) enum ListenerPhase {
111    Capturing,
112    Bubbling,
113}
114
115/// <https://html.spec.whatwg.org/multipage/#internal-raw-uncompiled-handler>
116#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
117struct InternalRawUncompiledHandler {
118    source: DOMString,
119    #[no_trace]
120    url: ServoUrl,
121    line: usize,
122}
123
124/// A representation of an event handler, either compiled or uncompiled raw source, or null.
125#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
126enum InlineEventListener {
127    Uncompiled(InternalRawUncompiledHandler),
128    Compiled(CommonEventHandler),
129    Null,
130}
131
132/// Get a compiled representation of this event handler, compiling it from its
133/// raw source if necessary.
134/// <https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler>
135fn get_compiled_handler(
136    cx: &mut JSContext,
137    inline_listener: &RefCell<InlineEventListener>,
138    owner: &EventTarget,
139    ty: &Atom,
140) -> Option<CommonEventHandler> {
141    let listener = mem::replace(
142        &mut *inline_listener.borrow_mut(),
143        InlineEventListener::Null,
144    );
145    let compiled = match listener {
146        InlineEventListener::Null => None,
147        InlineEventListener::Uncompiled(handler) => {
148            owner.get_compiled_event_handler(cx, handler, ty)
149        },
150        InlineEventListener::Compiled(handler) => Some(handler),
151    };
152    if let Some(ref compiled) = compiled {
153        *inline_listener.borrow_mut() = InlineEventListener::Compiled(compiled.clone());
154    }
155    compiled
156}
157
158#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
159enum EventListenerType {
160    Additive(#[conditional_malloc_size_of] Rc<EventListener>),
161    Inline(RefCell<InlineEventListener>),
162}
163
164impl EventListenerType {
165    fn get_compiled_listener(
166        &self,
167        cx: &mut JSContext,
168        owner: &EventTarget,
169        ty: &Atom,
170    ) -> Option<CompiledEventListener> {
171        match *self {
172            EventListenerType::Inline(ref inline) => {
173                get_compiled_handler(cx, inline, owner, ty).map(CompiledEventListener::Handler)
174            },
175            EventListenerType::Additive(ref listener) => {
176                Some(CompiledEventListener::Listener(listener.clone()))
177            },
178        }
179    }
180}
181
182/// A representation of an EventListener/EventHandler object that has previously
183/// been compiled successfully, if applicable.
184pub(crate) enum CompiledEventListener {
185    Listener(Rc<EventListener>),
186    Handler(CommonEventHandler),
187}
188
189impl CompiledEventListener {
190    #[expect(unsafe_code)]
191    pub(crate) fn associated_global(&self) -> DomRoot<GlobalScope> {
192        let obj = match self {
193            CompiledEventListener::Listener(listener) => listener.callback(),
194            CompiledEventListener::Handler(CommonEventHandler::EventHandler(handler)) => {
195                handler.callback()
196            },
197            CompiledEventListener::Handler(CommonEventHandler::ErrorEventHandler(handler)) => {
198                handler.callback()
199            },
200            CompiledEventListener::Handler(CommonEventHandler::BeforeUnloadEventHandler(
201                handler,
202            )) => handler.callback(),
203        };
204        unsafe { GlobalScope::from_object(obj) }
205    }
206
207    // https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm
208    pub(crate) fn call_or_handle_event(
209        &self,
210        cx: &mut JSContext,
211        object: &EventTarget,
212        event: &Event,
213        exception_handle: ExceptionHandling,
214    ) -> Fallible<()> {
215        // Step 3
216        match *self {
217            CompiledEventListener::Listener(ref listener) => {
218                listener.HandleEvent_(cx, object, event, exception_handle)
219            },
220            CompiledEventListener::Handler(ref handler) => {
221                match *handler {
222                    CommonEventHandler::ErrorEventHandler(ref handler) => {
223                        if let Some(event) = event.downcast::<ErrorEvent>() &&
224                            (object.is::<Window>() || object.is::<WorkerGlobalScope>())
225                        {
226                            rooted!(&in(cx) let mut error: JSVal);
227                            event.Error(error.handle_mut());
228                            rooted!(&in(cx) let mut rooted_return_value: JSVal);
229                            let return_value = handler.Call_(
230                                cx,
231                                object,
232                                EventOrString::String(event.Message()),
233                                Some(event.Filename()),
234                                Some(event.Lineno()),
235                                Some(event.Colno()),
236                                Some(error.handle()),
237                                rooted_return_value.handle_mut(),
238                                exception_handle,
239                            );
240                            // Step 4
241                            if let Ok(()) = return_value &&
242                                rooted_return_value.handle().is_boolean() &&
243                                rooted_return_value.handle().to_boolean()
244                            {
245                                event.upcast::<Event>().PreventDefault();
246                            }
247                            return return_value;
248                        }
249
250                        rooted!(&in(cx) let mut rooted_return_value: JSVal);
251                        handler.Call_(
252                            cx,
253                            object,
254                            EventOrString::Event(DomRoot::from_ref(event)),
255                            None,
256                            None,
257                            None,
258                            None,
259                            rooted_return_value.handle_mut(),
260                            exception_handle,
261                        )
262                    },
263
264                    CommonEventHandler::BeforeUnloadEventHandler(ref handler) => {
265                        if let Some(event) = event.downcast::<BeforeUnloadEvent>() {
266                            // Step 5
267                            match handler.Call_(
268                                cx,
269                                object,
270                                event.upcast::<Event>(),
271                                exception_handle,
272                            ) {
273                                Ok(value) => {
274                                    let rv = event.ReturnValue();
275                                    if let Some(v) = value {
276                                        if rv.is_empty() {
277                                            event.SetReturnValue(v);
278                                        }
279                                        event.upcast::<Event>().PreventDefault();
280                                    }
281                                    Ok(())
282                                },
283                                Err(err) => Err(err),
284                            }
285                        } else {
286                            // Step 5, "Otherwise" clause
287                            handler
288                                .Call_(cx, object, event.upcast::<Event>(), exception_handle)
289                                .map(|_| ())
290                        }
291                    },
292
293                    CommonEventHandler::EventHandler(ref handler) => {
294                        rooted!(&in(cx) let mut rooted_return_value: JSVal);
295                        match handler.Call_(
296                            cx,
297                            object,
298                            event,
299                            rooted_return_value.handle_mut(),
300                            exception_handle,
301                        ) {
302                            Ok(()) => {
303                                let value = rooted_return_value.handle();
304
305                                // Step 5
306                                let should_cancel = value.is_boolean() && !value.to_boolean();
307
308                                if should_cancel {
309                                    // FIXME: spec says to set the cancelled flag directly
310                                    // here, not just to prevent default;
311                                    // can that ever make a difference?
312                                    event.PreventDefault();
313                                }
314                                Ok(())
315                            },
316                            Err(err) => Err(err),
317                        }
318                    },
319                }
320            },
321        }
322    }
323}
324
325// https://dom.spec.whatwg.org/#concept-event-listener
326// (as distinct from https://dom.spec.whatwg.org/#callbackdef-eventlistener)
327#[derive(Clone, DenyPublicFields, JSTraceable, MallocSizeOf)]
328/// A listener in a collection of event listeners.
329pub(crate) struct EventListenerEntry {
330    phase: ListenerPhase,
331    listener: EventListenerType,
332    once: bool,
333    passive: bool,
334    removed: bool,
335}
336
337impl EventListenerEntry {
338    pub(crate) fn phase(&self) -> ListenerPhase {
339        self.phase
340    }
341
342    pub(crate) fn once(&self) -> bool {
343        self.once
344    }
345
346    pub(crate) fn removed(&self) -> bool {
347        self.removed
348    }
349
350    /// <https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler>
351    pub(crate) fn get_compiled_listener(
352        &self,
353        cx: &mut JSContext,
354        owner: &EventTarget,
355        ty: &Atom,
356    ) -> Option<CompiledEventListener> {
357        self.listener.get_compiled_listener(cx, owner, ty)
358    }
359}
360
361impl std::cmp::PartialEq for EventListenerEntry {
362    fn eq(&self, other: &Self) -> bool {
363        self.phase == other.phase && self.listener == other.listener
364    }
365}
366
367#[derive(Clone, JSTraceable, MallocSizeOf)]
368/// A mix of potentially uncompiled and compiled event listeners of the same type.
369pub(crate) struct EventListeners(
370    #[conditional_malloc_size_of] Vec<Rc<RefCell<EventListenerEntry>>>,
371);
372
373impl Deref for EventListeners {
374    type Target = Vec<Rc<RefCell<EventListenerEntry>>>;
375    fn deref(&self) -> &Vec<Rc<RefCell<EventListenerEntry>>> {
376        &self.0
377    }
378}
379
380impl DerefMut for EventListeners {
381    fn deref_mut(&mut self) -> &mut Vec<Rc<RefCell<EventListenerEntry>>> {
382        &mut self.0
383    }
384}
385
386impl EventListeners {
387    /// <https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler>
388    fn get_inline_listener(
389        &self,
390        cx: &mut JSContext,
391        owner: &EventTarget,
392        ty: &Atom,
393    ) -> Option<CommonEventHandler> {
394        for entry in &self.0 {
395            if let EventListenerType::Inline(ref inline) = entry.borrow().listener {
396                // Step 1.1-1.8 and Step 2
397                return get_compiled_handler(cx, inline, owner, ty);
398            }
399        }
400
401        // Step 2
402        None
403    }
404
405    fn has_listeners(&self) -> bool {
406        !self.0.is_empty()
407    }
408}
409
410/// The set of event listeners registered on an [`EventTarget`], keyed by event type.
411type EventListenerMap = HashMapTracedValues<Atom, EventListeners, FxBuildHasher>;
412
413#[dom_struct]
414pub struct EventTarget {
415    reflector_: Reflector,
416    handlers: DomRefCell<Option<Box<EventListenerMap>>>,
417}
418
419impl EventTarget {
420    pub(crate) fn new_inherited() -> EventTarget {
421        EventTarget {
422            reflector_: Reflector::new(),
423            handlers: DomRefCell::new(None),
424        }
425    }
426
427    /// Mutably borrow the listener map, allocating it if this target has none yet.
428    fn ensure_handlers(&self) -> RefMut<'_, Box<EventListenerMap>> {
429        RefMut::map(self.handlers.borrow_mut(), |handlers| {
430            handlers.get_or_insert_with(Box::default)
431        })
432    }
433
434    fn new(
435        cx: &mut JSContext,
436        global: &GlobalScope,
437        proto: Option<HandleObject>,
438    ) -> DomRoot<EventTarget> {
439        reflect_dom_object_with_proto(cx, Box::new(EventTarget::new_inherited()), global, proto)
440    }
441
442    /// Returns the [`ConstellationInterest`] associated with a given event type
443    /// on this specific target, if any. The mapping depends on the concrete type
444    /// of the EventTarget since the same event name can be used in multiple contexts.
445    fn interest_for_event_type(&self, ty: &Atom) -> Option<ConstellationInterest> {
446        if self.is::<Window>() && *ty == atom!("storage") {
447            return Some(ConstellationInterest::StorageEvent);
448        }
449        None
450    }
451
452    /// Notify the global about a listener being added for a given event type.
453    fn notify_listener_added(&self, ty: &Atom) {
454        if let Some(interest) = self.interest_for_event_type(ty) {
455            self.global().register_interest(interest);
456        }
457    }
458
459    /// Notify the global about a listener being removed for a given event type.
460    fn notify_listener_removed(&self, ty: &Atom) {
461        if let Some(interest) = self.interest_for_event_type(ty) {
462            self.global().unregister_interest(interest);
463        }
464    }
465
466    /// Determine if there are any listeners for a given event type.
467    /// See <https://github.com/whatwg/dom/issues/453>.
468    pub(crate) fn has_listeners_for(&self, type_: &Atom) -> bool {
469        match self
470            .handlers
471            .borrow()
472            .as_ref()
473            .and_then(|handlers| handlers.get(type_))
474        {
475            Some(listeners) => listeners.has_listeners(),
476            None => false,
477        }
478    }
479
480    pub(crate) fn get_listeners_for(&self, type_: &Atom) -> EventListeners {
481        self.handlers
482            .borrow()
483            .as_ref()
484            .and_then(|handlers| handlers.get(type_))
485            .map_or(EventListeners(vec![]), |listeners| listeners.clone())
486    }
487
488    pub(crate) fn remove_all_listeners(&self) {
489        // Take the whole map out first, so the listener-removed notifications
490        // below run without holding a borrow on `handlers`.
491        let Some(handlers) = self.handlers.borrow_mut().take() else {
492            return;
493        };
494
495        for (ty, entries) in handlers.iter() {
496            entries
497                .iter()
498                .for_each(|entry| entry.borrow_mut().removed = true);
499            self.notify_listener_removed(ty);
500        }
501    }
502
503    /// <https://dom.spec.whatwg.org/#default-passive-value>
504    fn default_passive_value(&self, ty: &Atom) -> bool {
505        // Return true if all of the following are true:
506        let event_type = ty.trim_matches(HTML_SPACE_CHARACTERS);
507
508        // type is one of "touchstart", "touchmove", "wheel", or "mousewheel"
509        let matches_event_type = event_type.eq_ignore_ascii_case("touchstart") ||
510            event_type.eq_ignore_ascii_case("touchmove") ||
511            event_type.eq_ignore_ascii_case("wheel") ||
512            event_type.eq_ignore_ascii_case("mousewheel");
513
514        if !matches_event_type {
515            return false;
516        }
517
518        // eventTarget is a Window object
519        if self.is::<Window>() {
520            return true;
521        }
522
523        // or ...
524        if let Some(node) = self.downcast::<Node>() {
525            let node_document = node.owner_document();
526            let event_target = self.upcast::<EventTarget>();
527
528            // is a node whose node document is eventTarget
529            return event_target == node_document.upcast::<EventTarget>()
530                // or is a node whose node document’s document element is eventTarget
531                || node_document.GetDocumentElement().is_some_and(|n| n.upcast::<EventTarget>() == event_target)
532                // or is a node whose node document’s body element is eventTarget
533                || node_document.GetBody().is_some_and(|n| n.upcast::<EventTarget>() == event_target);
534        }
535
536        false
537    }
538
539    /// <https://html.spec.whatwg.org/multipage/#event-handler-attributes:event-handlers-11>
540    fn set_inline_event_listener(&self, ty: Atom, listener: Option<InlineEventListener>) {
541        let mut handlers = self.ensure_handlers();
542        let entries = match handlers.entry(ty.clone()) {
543            Occupied(entry) => entry.into_mut(),
544            Vacant(entry) => entry.insert(EventListeners(vec![])),
545        };
546
547        let idx = entries
548            .iter()
549            .position(|entry| matches!(entry.borrow().listener, EventListenerType::Inline(_)));
550
551        match idx {
552            Some(idx) => match listener {
553                // Replace if there's something to replace with,
554                // but remove entirely if there isn't.
555                Some(listener) => {
556                    entries[idx].borrow_mut().listener = EventListenerType::Inline(listener.into());
557                },
558                None => {
559                    entries.remove(idx).borrow_mut().removed = true;
560                    self.notify_listener_removed(&ty);
561                },
562            },
563            None => {
564                if let Some(listener) = listener {
565                    entries.push(Rc::new(RefCell::new(EventListenerEntry {
566                        phase: ListenerPhase::Bubbling,
567                        listener: EventListenerType::Inline(listener.into()),
568                        once: false,
569                        passive: self.default_passive_value(&ty),
570                        removed: false,
571                    })));
572                    self.notify_listener_added(&ty)
573                }
574            },
575        }
576    }
577
578    pub(crate) fn remove_listener(&self, ty: &Atom, entry: &Rc<RefCell<EventListenerEntry>>) {
579        let mut handlers = self.handlers.borrow_mut();
580
581        if let Some(handlers) = handlers.as_mut() &&
582            let Some(entries) = handlers.get_mut(ty) &&
583            let Some(position) = entries.iter().position(|e| *e == *entry)
584        {
585            entries.remove(position).borrow_mut().removed = true;
586            self.notify_listener_removed(ty);
587        }
588    }
589
590    /// Determines the `passive` attribute of an associated event listener
591    pub(crate) fn is_passive(&self, listener: &Rc<RefCell<EventListenerEntry>>) -> bool {
592        listener.borrow().passive
593    }
594
595    /// Determines if there are any non-passive listeners for a given event type.
596    pub(crate) fn has_non_passive_listener(&self, type_: &Atom) -> bool {
597        self.get_listeners_for(type_)
598            .iter()
599            .any(|listener| !self.is_passive(listener))
600    }
601
602    fn get_inline_event_listener(
603        &self,
604        cx: &mut JSContext,
605        ty: &Atom,
606    ) -> Option<CommonEventHandler> {
607        let handlers = self.handlers.borrow();
608        handlers
609            .as_ref()?
610            .get(ty)
611            .and_then(|entry| entry.get_inline_listener(cx, self, ty))
612    }
613
614    /// Store the raw uncompiled event handler for on-demand compilation later.
615    /// <https://html.spec.whatwg.org/multipage/#event-handler-attributes:event-handler-content-attributes-3>
616    pub(crate) fn set_event_handler_uncompiled(
617        &self,
618        cx: &mut JSContext,
619        url: ServoUrl,
620        line: usize,
621        ty: &str,
622        source: &str,
623    ) {
624        if let Some(element) = self.downcast::<Element>() {
625            let doc = element.owner_document();
626            let global = &doc.global();
627            if global
628                .get_csp_list()
629                .should_elements_inline_type_behavior_be_blocked(
630                    cx,
631                    global,
632                    element.upcast(),
633                    InlineCheckType::ScriptAttribute,
634                    source,
635                    line as u32,
636                )
637            {
638                return;
639            }
640        };
641
642        let handler = InternalRawUncompiledHandler {
643            source: DOMString::from(source),
644            line,
645            url,
646        };
647        self.set_inline_event_listener(
648            Atom::from(ty),
649            Some(InlineEventListener::Uncompiled(handler)),
650        );
651    }
652
653    // https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler
654    // step 3
655    #[expect(unsafe_code)]
656    fn get_compiled_event_handler(
657        &self,
658        cx: &mut JSContext,
659        handler: InternalRawUncompiledHandler,
660        ty: &Atom,
661    ) -> Option<CommonEventHandler> {
662        // Step 3.1
663        let element = self.downcast::<Element>();
664        let document = match element {
665            Some(element) => element.owner_document(),
666            None => self.downcast::<Window>().unwrap().Document(),
667        };
668
669        // Step 3.2
670        if !document.scripting_enabled() {
671            return None;
672        }
673
674        // Step 3.3
675        let body: Vec<u16> = handler.source.str().encode_utf16().collect();
676
677        // Step 3.4 is handler.line
678
679        // Step 3.5
680        let form_owner = element
681            .and_then(|e| e.as_maybe_form_control())
682            .and_then(|f| f.form_owner());
683
684        // Step 3.6 TODO: settings objects not implemented
685
686        // Step 3.7 is written as though we call the parser separately
687        // from the compiler; since we just call CompileFunction with
688        // source text, we handle parse errors later
689
690        // Step 3.8 TODO: settings objects not implemented
691        let window = document.window();
692        let mut realm = enter_auto_realm(cx, window);
693        let cx = &mut realm.current_realm();
694
695        // Step 3.9
696
697        let name = CString::new(format!("on{}", &**ty)).unwrap();
698
699        // Step 3.9, subsection ParameterList
700        const ARG_NAMES: &[*const c_char] = &[c"event".as_ptr()];
701        const ERROR_ARG_NAMES: &[*const c_char] = &[
702            c"event".as_ptr(),
703            c"source".as_ptr(),
704            c"lineno".as_ptr(),
705            c"colno".as_ptr(),
706            c"error".as_ptr(),
707        ];
708        let is_error = ty == &atom!("error") && self.is::<Window>();
709        let args = if is_error { ERROR_ARG_NAMES } else { ARG_NAMES };
710
711        let url = cformat!("{}", handler.url);
712        let mut options = CompileOptionsWrapper::new(cx, url, handler.line as u32);
713        options.set_introduction_type(IntroductionType::EVENT_HANDLER);
714
715        // Step 3.9, subsection Scope steps 1-6
716        let scopechain = js::rust::EnvironmentChain::new(cx, SupportUnscopables::Yes);
717
718        if let Some(element) = element {
719            scopechain.append(document.reflector().get_jsobject().get());
720            if let Some(form_owner) = form_owner {
721                scopechain.append(form_owner.reflector().get_jsobject().get());
722            }
723            scopechain.append(element.reflector().get_jsobject().get());
724        }
725
726        rooted!(&in(cx) let mut handler = unsafe {
727            CompileFunction(
728                cx,
729                scopechain.get(),
730                options.ptr,
731                name.as_ptr(),
732                args.len() as u32,
733                args.as_ptr(),
734                &mut transform_u16_to_source_text(&body),
735            )
736        });
737        if handler.get().is_null() {
738            // Step 3.7
739            let mut realm = enter_auto_realm(cx, self);
740            report_pending_exception(&mut realm.current_realm());
741            return None;
742        }
743
744        // Step 3.10 happens when we drop _ac
745
746        // TODO Step 3.11
747
748        // Step 3.12
749        let funobj = unsafe { JS_GetFunctionObject(handler.get()) };
750        assert!(!funobj.is_null());
751        // Step 1.14
752        if is_error {
753            Some(CommonEventHandler::ErrorEventHandler(unsafe {
754                OnErrorEventHandlerNonNull::new(cx, funobj)
755            }))
756        } else if ty == &atom!("beforeunload") {
757            Some(CommonEventHandler::BeforeUnloadEventHandler(unsafe {
758                OnBeforeUnloadEventHandlerNonNull::new(cx, funobj)
759            }))
760        } else {
761            Some(CommonEventHandler::EventHandler(unsafe {
762                EventHandlerNonNull::new(cx, funobj)
763            }))
764        }
765    }
766
767    #[expect(unsafe_code)]
768    pub(crate) fn set_event_handler_common<T: CallbackContainer<crate::DomTypeHolder>>(
769        &self,
770        cx: &mut JSContext,
771        ty: &str,
772        listener: Option<Rc<T>>,
773    ) {
774        let event_listener = listener.map(|listener| {
775            InlineEventListener::Compiled(CommonEventHandler::EventHandler(unsafe {
776                EventHandlerNonNull::new(cx, listener.callback())
777            }))
778        });
779        self.set_inline_event_listener(Atom::from(ty), event_listener);
780    }
781
782    #[expect(unsafe_code)]
783    pub(crate) fn set_error_event_handler<T: CallbackContainer<crate::DomTypeHolder>>(
784        &self,
785        cx: &mut JSContext,
786        ty: &str,
787        listener: Option<Rc<T>>,
788    ) {
789        let event_listener = listener.map(|listener| {
790            InlineEventListener::Compiled(CommonEventHandler::ErrorEventHandler(unsafe {
791                OnErrorEventHandlerNonNull::new(cx, listener.callback())
792            }))
793        });
794        self.set_inline_event_listener(Atom::from(ty), event_listener);
795    }
796
797    #[expect(unsafe_code)]
798    pub(crate) fn set_beforeunload_event_handler<T: CallbackContainer<crate::DomTypeHolder>>(
799        &self,
800        cx: &mut JSContext,
801        ty: &str,
802        listener: Option<Rc<T>>,
803    ) {
804        let event_listener = listener.map(|listener| {
805            InlineEventListener::Compiled(CommonEventHandler::BeforeUnloadEventHandler(unsafe {
806                OnBeforeUnloadEventHandlerNonNull::new(cx, listener.callback())
807            }))
808        });
809        self.set_inline_event_listener(Atom::from(ty), event_listener);
810    }
811
812    #[expect(unsafe_code)]
813    pub(crate) fn get_event_handler_common<T: CallbackContainer<crate::DomTypeHolder>>(
814        &self,
815        cx: &mut JSContext,
816        ty: &str,
817    ) -> Option<Rc<T>> {
818        let listener = self.get_inline_event_listener(cx, &Atom::from(ty));
819        unsafe {
820            listener.map(|listener| {
821                CallbackContainer::new(cx, listener.parent().callback_holder().get())
822            })
823        }
824    }
825
826    pub(crate) fn has_handlers(&self) -> bool {
827        self.handlers
828            .borrow()
829            .as_ref()
830            .is_some_and(|handlers| !handlers.is_empty())
831    }
832
833    // https://dom.spec.whatwg.org/#concept-event-fire
834    pub(crate) fn fire_event(&self, cx: &mut js::context::JSContext, name: Atom) -> bool {
835        self.fire_event_with_params(
836            cx,
837            name,
838            EventBubbles::DoesNotBubble,
839            EventCancelable::NotCancelable,
840            EventComposed::NotComposed,
841        )
842    }
843
844    // https://dom.spec.whatwg.org/#concept-event-fire
845    pub(crate) fn fire_bubbling_event(&self, cx: &mut js::context::JSContext, name: Atom) -> bool {
846        self.fire_event_with_params(
847            cx,
848            name,
849            EventBubbles::Bubbles,
850            EventCancelable::NotCancelable,
851            EventComposed::NotComposed,
852        )
853    }
854
855    // https://dom.spec.whatwg.org/#concept-event-fire
856    pub(crate) fn fire_cancelable_event(
857        &self,
858        cx: &mut js::context::JSContext,
859        name: Atom,
860    ) -> bool {
861        self.fire_event_with_params(
862            cx,
863            name,
864            EventBubbles::DoesNotBubble,
865            EventCancelable::Cancelable,
866            EventComposed::NotComposed,
867        )
868    }
869
870    // https://dom.spec.whatwg.org/#concept-event-fire
871    pub(crate) fn fire_bubbling_cancelable_event(
872        &self,
873        cx: &mut js::context::JSContext,
874        name: Atom,
875    ) -> bool {
876        self.fire_event_with_params(
877            cx,
878            name,
879            EventBubbles::Bubbles,
880            EventCancelable::Cancelable,
881            EventComposed::NotComposed,
882        )
883    }
884
885    /// <https://dom.spec.whatwg.org/#concept-event-fire>
886    pub(crate) fn fire_event_with_params(
887        &self,
888        cx: &mut js::context::JSContext,
889        name: Atom,
890        bubbles: EventBubbles,
891        cancelable: EventCancelable,
892        composed: EventComposed,
893    ) -> bool {
894        let event = Event::new(cx, &self.global(), name, bubbles, cancelable);
895        event.set_composed(composed.into());
896        event.fire(cx, self)
897    }
898
899    /// <https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener>
900    /// and <https://dom.spec.whatwg.org/#add-an-event-listener>
901    pub(crate) fn add_event_listener(
902        &self,
903        ty: DOMString,
904        listener: Option<Rc<EventListener>>,
905        options: AddEventListenerOptions,
906    ) {
907        if let Some(signal) = options.signal.as_ref() {
908            // Step 2. If listener’s signal is not null and is aborted, then return.
909            if signal.aborted() {
910                return;
911            }
912            // Step 6. If listener’s signal is not null, then add the following abort steps to it:
913            signal.add(&AbortAlgorithm::DomEventListener(
914                RemovableDomEventListener {
915                    event_target: Dom::from_ref(self),
916                    ty: ty.clone(),
917                    listener: listener.clone(),
918                    options: options.parent.clone(),
919                },
920            ));
921        }
922        // Step 3. If listener’s callback is null, then return.
923        let listener = match listener {
924            Some(l) => l,
925            None => return,
926        };
927        let ty = Atom::from(ty);
928        let mut handlers = self.ensure_handlers();
929        let entries = match handlers.entry(ty.clone()) {
930            Occupied(entry) => entry.into_mut(),
931            Vacant(entry) => entry.insert(EventListeners(vec![])),
932        };
933
934        let phase = if options.parent.capture {
935            ListenerPhase::Capturing
936        } else {
937            ListenerPhase::Bubbling
938        };
939        // Step 4. If listener’s passive is null, then set it to the default passive value given listener’s type and eventTarget.
940        let new_entry = Rc::new(RefCell::new(EventListenerEntry {
941            phase,
942            listener: EventListenerType::Additive(listener),
943            once: options.once,
944            passive: options.passive.unwrap_or(self.default_passive_value(&ty)),
945            removed: false,
946        }));
947
948        // Step 5. If eventTarget’s event listener list does not contain
949        // an event listener whose type is listener’s type, callback is listener’s callback,
950        // and capture is listener’s capture, then append listener to eventTarget’s event listener list.
951        if !entries.contains(&new_entry) {
952            entries.push(new_entry);
953            self.notify_listener_added(&ty);
954        }
955    }
956
957    /// <https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener>
958    /// and <https://dom.spec.whatwg.org/#remove-an-event-listener>
959    pub(crate) fn remove_event_listener(
960        &self,
961        ty: DOMString,
962        listener: &Option<Rc<EventListener>>,
963        options: &EventListenerOptions,
964    ) {
965        let Some(listener) = listener else {
966            return;
967        };
968        let ty_atom = Atom::from(ty);
969        let mut handlers = self.handlers.borrow_mut();
970        if let Some(handlers) = handlers.as_mut() &&
971            let Some(entries) = handlers.get_mut(&ty_atom)
972        {
973            let phase = if options.capture {
974                ListenerPhase::Capturing
975            } else {
976                ListenerPhase::Bubbling
977            };
978            let listener_type = EventListenerType::Additive(listener.clone());
979            if let Some(position) = entries
980                .iter()
981                .position(|e| e.borrow().listener == listener_type && e.borrow().phase == phase)
982            {
983                // Step 2. Set listener’s removed to true and remove listener from eventTarget’s event listener list.
984                entries.remove(position).borrow_mut().removed = true;
985                self.notify_listener_removed(&ty_atom);
986            }
987        }
988    }
989
990    /// <https://dom.spec.whatwg.org/#get-the-parent>
991    pub(crate) fn get_the_parent(&self, event: &Event) -> Option<DomRoot<EventTarget>> {
992        if let Some(document) = self.downcast::<Document>() {
993            if event.type_() == atom!("load") || !document.has_browsing_context() {
994                return None;
995            } else {
996                return Some(DomRoot::from_ref(document.window().upcast::<EventTarget>()));
997            }
998        }
999
1000        if let Some(shadow_root) = self.downcast::<ShadowRoot>() {
1001            if event.should_pass_shadow_boundary(shadow_root) {
1002                let host = shadow_root.Host();
1003                return Some(DomRoot::from_ref(host.upcast::<EventTarget>()));
1004            } else {
1005                return None;
1006            }
1007        }
1008
1009        if let Some(node) = self.downcast::<Node>() {
1010            // > A node’s get the parent algorithm, given an event, returns the node’s assigned slot,
1011            // > if node is assigned; otherwise node’s parent.
1012            return node.assigned_slot().map(DomRoot::upcast).or_else(|| {
1013                node.GetParentNode()
1014                    .map(|parent| DomRoot::from_ref(parent.upcast::<EventTarget>()))
1015            });
1016        }
1017
1018        // https://w3c.github.io/IndexedDB/#events
1019        // The get the parent algorithm for an IDBRequest returns the request's transaction.
1020        if let Some(request) = self.downcast::<IDBRequest>() {
1021            return request
1022                .transaction()
1023                .map(|tx| DomRoot::from_ref(tx.upcast::<EventTarget>()));
1024        }
1025
1026        // The get the parent algorithm for an IDBTransaction returns the transaction's connection.
1027        if let Some(transaction) = self.downcast::<IDBTransaction>() {
1028            return Some(DomRoot::from_ref(
1029                transaction.get_db().upcast::<EventTarget>(),
1030            ));
1031        }
1032
1033        // The get the parent algorithm for an IDBDatabase returns null.
1034        if self.is::<IDBDatabase>() {
1035            return None;
1036        }
1037
1038        None
1039    }
1040
1041    // FIXME: This algorithm operates on "objects", which may not be event targets.
1042    // All our current use-cases only work on event targets, but this might change in the future
1043    /// <https://dom.spec.whatwg.org/#retarget>
1044    pub(crate) fn retarget(&self, b: &Self) -> DomRoot<EventTarget> {
1045        // To retarget an object A against an object B, repeat these steps until they return an object:
1046        let mut a = DomRoot::from_ref(self);
1047        loop {
1048            // Step 1. If one of the following is true
1049            // * A is not a node
1050            // * A’s root is not a shadow root
1051            // * B is a node and A’s root is a shadow-including inclusive ancestor of B
1052            // then return A.
1053            let Some(a_node) = a.downcast::<Node>() else {
1054                return a;
1055            };
1056            let a_root = a_node.GetRootNode(&GetRootNodeOptions::empty());
1057            if !a_root.is::<ShadowRoot>() {
1058                return a;
1059            }
1060            if let Some(b_node) = b.downcast::<Node>() &&
1061                a_root.is_shadow_including_inclusive_ancestor_of(b_node)
1062            {
1063                return a;
1064            }
1065
1066            // Step 2. Set A to A’s root’s host.
1067            a = DomRoot::from_ref(
1068                a_root
1069                    .downcast::<ShadowRoot>()
1070                    .unwrap()
1071                    .Host()
1072                    .upcast::<EventTarget>(),
1073            );
1074        }
1075    }
1076
1077    /// <https://html.spec.whatwg.org/multipage/#event-handler-content-attributes>
1078    pub(crate) fn is_content_event_handler(name: &str) -> bool {
1079        CONTENT_EVENT_HANDLER_NAMES.contains(&name)
1080    }
1081
1082    pub(crate) fn summarize_event_listeners_for_devtools(&self) -> Vec<EventListenerInfo> {
1083        let handlers = self.handlers.borrow();
1084        let Some(handlers) = handlers.as_ref() else {
1085            return Vec::new();
1086        };
1087        let mut listener_infos = Vec::with_capacity(handlers.0.len());
1088        for (event_type, event_listeners) in &handlers.0 {
1089            for event_listener in event_listeners.iter() {
1090                let event_listener_entry = event_listener.borrow();
1091                listener_infos.push(EventListenerInfo {
1092                    event_type: event_type.to_string(),
1093                    capturing: event_listener_entry.phase() == ListenerPhase::Capturing,
1094                });
1095            }
1096        }
1097
1098        listener_infos
1099    }
1100}
1101
1102impl EventTargetMethods<crate::DomTypeHolder> for EventTarget {
1103    /// <https://dom.spec.whatwg.org/#dom-eventtarget-eventtarget>
1104    fn Constructor(
1105        cx: &mut JSContext,
1106        global: &GlobalScope,
1107        proto: Option<HandleObject>,
1108    ) -> Fallible<DomRoot<EventTarget>> {
1109        Ok(EventTarget::new(cx, global, proto))
1110    }
1111
1112    /// <https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener>
1113    fn AddEventListener(
1114        &self,
1115        ty: DOMString,
1116        listener: Option<Rc<EventListener>>,
1117        options: AddEventListenerOptionsOrBoolean,
1118    ) {
1119        self.add_event_listener(ty, listener, options.convert())
1120    }
1121
1122    /// <https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener>
1123    fn RemoveEventListener(
1124        &self,
1125        ty: DOMString,
1126        listener: Option<Rc<EventListener>>,
1127        options: EventListenerOptionsOrBoolean,
1128    ) {
1129        self.remove_event_listener(ty, &listener, &options.convert())
1130    }
1131
1132    /// <https://dom.spec.whatwg.org/#dom-eventtarget-dispatchevent>
1133    fn DispatchEvent(&self, cx: &mut JSContext, event: &Event) -> Fallible<bool> {
1134        if event.dispatching() || !event.initialized() {
1135            return Err(Error::InvalidState(None));
1136        }
1137        event.set_trusted(false);
1138        Ok(event.dispatch(cx, self, false))
1139    }
1140}
1141
1142impl VirtualMethods for EventTarget {
1143    fn super_type(&self) -> Option<&dyn VirtualMethods> {
1144        None
1145    }
1146}
1147
1148impl Convert<AddEventListenerOptions> for AddEventListenerOptionsOrBoolean {
1149    /// <https://dom.spec.whatwg.org/#event-flatten-more>
1150    fn convert(self) -> AddEventListenerOptions {
1151        // Step 1. Let capture be the result of flattening options.
1152        // Step 5. Return capture, passive, once, and signal.
1153        match self {
1154            // Step 4. If options is a dictionary:
1155            AddEventListenerOptionsOrBoolean::AddEventListenerOptions(options) => options,
1156            AddEventListenerOptionsOrBoolean::Boolean(capture) => AddEventListenerOptions {
1157                parent: EventListenerOptions { capture },
1158                // Step 2. Let once be false.
1159                once: false,
1160                // Step 3. Let passive and signal be null.
1161                passive: None,
1162                signal: None,
1163            },
1164        }
1165    }
1166}
1167
1168impl Convert<EventListenerOptions> for EventListenerOptionsOrBoolean {
1169    fn convert(self) -> EventListenerOptions {
1170        match self {
1171            EventListenerOptionsOrBoolean::EventListenerOptions(options) => options,
1172            EventListenerOptionsOrBoolean::Boolean(capture) => EventListenerOptions { capture },
1173        }
1174    }
1175}
1176
1177impl OwnerWindow<crate::DomTypeHolder> for EventTarget {
1178    fn owner_window(&self) -> Option<DomRoot<Window>> {
1179        self.downcast::<Node>().map(|node| node.owner_window())
1180    }
1181}