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;
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_and_cx};
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;
73use crate::dom::window::Window;
74use crate::dom::workerglobalscope::WorkerGlobalScope;
75use crate::realms::enter_auto_realm;
76use crate::script_runtime::IntroductionType;
77
78/// <https://html.spec.whatwg.org/multipage/#event-handler-content-attributes>
79/// Generated from WebIDL definitions of EventHandler attributes on interfaces
80/// that inherit from Node.
81pub(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/// <https://html.spec.whatwg.org/multipage/#internal-raw-uncompiled-handler>
115#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
116struct InternalRawUncompiledHandler {
117    source: DOMString,
118    #[no_trace]
119    url: ServoUrl,
120    line: usize,
121}
122
123/// A representation of an event handler, either compiled or uncompiled raw source, or null.
124#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
125enum InlineEventListener {
126    Uncompiled(InternalRawUncompiledHandler),
127    Compiled(CommonEventHandler),
128    Null,
129}
130
131/// Get a compiled representation of this event handler, compiling it from its
132/// raw source if necessary.
133/// <https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler>
134fn 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
181/// A representation of an EventListener/EventHandler object that has previously
182/// been compiled successfully, if applicable.
183pub(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    // https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm
207    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        // Step 3
215        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(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                            // Step 4
240                            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                            // Step 5
266                            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                            // Step 5, "Otherwise" clause
286                            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                                // Step 5
305                                let should_cancel = value.is_boolean() && !value.to_boolean();
306
307                                if should_cancel {
308                                    // FIXME: spec says to set the cancelled flag directly
309                                    // here, not just to prevent default;
310                                    // can that ever make a difference?
311                                    event.PreventDefault();
312                                }
313                                Ok(())
314                            },
315                            Err(err) => Err(err),
316                        }
317                    },
318                }
319            },
320        }
321    }
322}
323
324// https://dom.spec.whatwg.org/#concept-event-listener
325// (as distinct from https://dom.spec.whatwg.org/#callbackdef-eventlistener)
326#[derive(Clone, DenyPublicFields, JSTraceable, MallocSizeOf)]
327/// A listener in a collection of event listeners.
328pub(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    /// <https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler>
350    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)]
367/// A mix of potentially uncompiled and compiled event listeners of the same type.
368pub(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    /// <https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler>
387    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                // Step 1.1-1.8 and Step 2
396                return get_compiled_handler(cx, inline, owner, ty);
397            }
398        }
399
400        // Step 2
401        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        cx: &mut JSContext,
425        global: &GlobalScope,
426        proto: Option<HandleObject>,
427    ) -> DomRoot<EventTarget> {
428        reflect_dom_object_with_proto_and_cx(
429            Box::new(EventTarget::new_inherited()),
430            global,
431            proto,
432            cx,
433        )
434    }
435
436    /// Returns the [`ConstellationInterest`] associated with a given event type
437    /// on this specific target, if any. The mapping depends on the concrete type
438    /// of the EventTarget since the same event name can be used in multiple contexts.
439    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    /// Notify the global about a listener being added for a given event type.
447    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    /// Notify the global about a listener being removed for a given event type.
454    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    /// Determine if there are any listeners for a given event type.
461    /// See <https://github.com/whatwg/dom/issues/453>.
462    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 remove_all_listeners(&self) {
477        let mut handlers = self.handlers.borrow_mut();
478        for (ty, entries) in handlers.iter() {
479            entries
480                .iter()
481                .for_each(|entry| entry.borrow_mut().removed = true);
482            self.notify_listener_removed(ty);
483        }
484
485        *handlers = Default::default();
486    }
487
488    /// <https://dom.spec.whatwg.org/#default-passive-value>
489    fn default_passive_value(&self, ty: &Atom) -> bool {
490        // Return true if all of the following are true:
491        let event_type = ty.to_ascii_lowercase();
492
493        // type is one of "touchstart", "touchmove", "wheel", or "mousewheel"
494        let matches_event_type = matches!(
495            event_type.trim_matches(HTML_SPACE_CHARACTERS),
496            "touchstart" | "touchmove" | "wheel" | "mousewheel"
497        );
498
499        if !matches_event_type {
500            return false;
501        }
502
503        // eventTarget is a Window object
504        if self.is::<Window>() {
505            return true;
506        }
507
508        // or ...
509        if let Some(node) = self.downcast::<Node>() {
510            let node_document = node.owner_document();
511            let event_target = self.upcast::<EventTarget>();
512
513            // is a node whose node document is eventTarget
514            return event_target == node_document.upcast::<EventTarget>()
515                // or is a node whose node document’s document element is eventTarget
516                || node_document.GetDocumentElement().is_some_and(|n| n.upcast::<EventTarget>() == event_target)
517                // or is a node whose node document’s body element is eventTarget
518                || node_document.GetBody().is_some_and(|n| n.upcast::<EventTarget>() == event_target);
519        }
520
521        false
522    }
523
524    /// <https://html.spec.whatwg.org/multipage/#event-handler-attributes:event-handlers-11>
525    fn set_inline_event_listener(&self, ty: Atom, listener: Option<InlineEventListener>) {
526        let mut handlers = self.handlers.borrow_mut();
527        let entries = match handlers.entry(ty.clone()) {
528            Occupied(entry) => entry.into_mut(),
529            Vacant(entry) => entry.insert(EventListeners(vec![])),
530        };
531
532        let idx = entries
533            .iter()
534            .position(|entry| matches!(entry.borrow().listener, EventListenerType::Inline(_)));
535
536        match idx {
537            Some(idx) => match listener {
538                // Replace if there's something to replace with,
539                // but remove entirely if there isn't.
540                Some(listener) => {
541                    entries[idx].borrow_mut().listener = EventListenerType::Inline(listener.into());
542                },
543                None => {
544                    entries.remove(idx).borrow_mut().removed = true;
545                    self.notify_listener_removed(&ty);
546                },
547            },
548            None => {
549                if let Some(listener) = listener {
550                    entries.push(Rc::new(RefCell::new(EventListenerEntry {
551                        phase: ListenerPhase::Bubbling,
552                        listener: EventListenerType::Inline(listener.into()),
553                        once: false,
554                        passive: self.default_passive_value(&ty),
555                        removed: false,
556                    })));
557                    self.notify_listener_added(&ty)
558                }
559            },
560        }
561    }
562
563    pub(crate) fn remove_listener(&self, ty: &Atom, entry: &Rc<RefCell<EventListenerEntry>>) {
564        let mut handlers = self.handlers.borrow_mut();
565
566        if let Some(entries) = handlers.get_mut(ty) &&
567            let Some(position) = entries.iter().position(|e| *e == *entry)
568        {
569            entries.remove(position).borrow_mut().removed = true;
570            self.notify_listener_removed(ty);
571        }
572    }
573
574    /// Determines the `passive` attribute of an associated event listener
575    pub(crate) fn is_passive(&self, listener: &Rc<RefCell<EventListenerEntry>>) -> bool {
576        listener.borrow().passive
577    }
578
579    /// Determines if there are any non-passive listeners for a given event type.
580    pub(crate) fn has_non_passive_listener(&self, type_: &Atom) -> bool {
581        self.get_listeners_for(type_)
582            .iter()
583            .any(|listener| !self.is_passive(listener))
584    }
585
586    fn get_inline_event_listener(
587        &self,
588        cx: &mut JSContext,
589        ty: &Atom,
590    ) -> Option<CommonEventHandler> {
591        let handlers = self.handlers.borrow();
592        handlers
593            .get(ty)
594            .and_then(|entry| entry.get_inline_listener(cx, self, ty))
595    }
596
597    /// Store the raw uncompiled event handler for on-demand compilation later.
598    /// <https://html.spec.whatwg.org/multipage/#event-handler-attributes:event-handler-content-attributes-3>
599    pub(crate) fn set_event_handler_uncompiled(
600        &self,
601        cx: &mut JSContext,
602        url: ServoUrl,
603        line: usize,
604        ty: &str,
605        source: &str,
606    ) {
607        if let Some(element) = self.downcast::<Element>() {
608            let doc = element.owner_document();
609            let global = &doc.global();
610            if global
611                .get_csp_list()
612                .should_elements_inline_type_behavior_be_blocked(
613                    cx,
614                    global,
615                    element.upcast(),
616                    InlineCheckType::ScriptAttribute,
617                    source,
618                    line as u32,
619                )
620            {
621                return;
622            }
623        };
624
625        let handler = InternalRawUncompiledHandler {
626            source: DOMString::from(source),
627            line,
628            url,
629        };
630        self.set_inline_event_listener(
631            Atom::from(ty),
632            Some(InlineEventListener::Uncompiled(handler)),
633        );
634    }
635
636    // https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler
637    // step 3
638    #[expect(unsafe_code)]
639    fn get_compiled_event_handler(
640        &self,
641        cx: &mut JSContext,
642        handler: InternalRawUncompiledHandler,
643        ty: &Atom,
644    ) -> Option<CommonEventHandler> {
645        // Step 3.1
646        let element = self.downcast::<Element>();
647        let document = match element {
648            Some(element) => element.owner_document(),
649            None => self.downcast::<Window>().unwrap().Document(),
650        };
651
652        // Step 3.2
653        if !document.scripting_enabled() {
654            return None;
655        }
656
657        // Step 3.3
658        let body: Vec<u16> = handler.source.str().encode_utf16().collect();
659
660        // Step 3.4 is handler.line
661
662        // Step 3.5
663        let form_owner = element
664            .and_then(|e| e.as_maybe_form_control())
665            .and_then(|f| f.form_owner());
666
667        // Step 3.6 TODO: settings objects not implemented
668
669        // Step 3.7 is written as though we call the parser separately
670        // from the compiler; since we just call CompileFunction with
671        // source text, we handle parse errors later
672
673        // Step 3.8 TODO: settings objects not implemented
674        let window = document.window();
675        let mut realm = enter_auto_realm(cx, window);
676        let cx = &mut realm.current_realm();
677
678        // Step 3.9
679
680        let name = CString::new(format!("on{}", &**ty)).unwrap();
681
682        // Step 3.9, subsection ParameterList
683        const ARG_NAMES: &[*const c_char] = &[c"event".as_ptr()];
684        const ERROR_ARG_NAMES: &[*const c_char] = &[
685            c"event".as_ptr(),
686            c"source".as_ptr(),
687            c"lineno".as_ptr(),
688            c"colno".as_ptr(),
689            c"error".as_ptr(),
690        ];
691        let is_error = ty == &atom!("error") && self.is::<Window>();
692        let args = if is_error { ERROR_ARG_NAMES } else { ARG_NAMES };
693
694        let url = cformat!("{}", handler.url);
695        let mut options = CompileOptionsWrapper::new(cx, url, handler.line as u32);
696        options.set_introduction_type(IntroductionType::EVENT_HANDLER);
697
698        // Step 3.9, subsection Scope steps 1-6
699        let scopechain =
700            js::rust::EnvironmentChain::new(unsafe { cx.raw_cx() }, SupportUnscopables::Yes);
701
702        if let Some(element) = element {
703            scopechain.append(document.reflector().get_jsobject().get());
704            if let Some(form_owner) = form_owner {
705                scopechain.append(form_owner.reflector().get_jsobject().get());
706            }
707            scopechain.append(element.reflector().get_jsobject().get());
708        }
709
710        rooted!(&in(cx) let mut handler = unsafe {
711            CompileFunction(
712                cx,
713                scopechain.get(),
714                options.ptr,
715                name.as_ptr(),
716                args.len() as u32,
717                args.as_ptr(),
718                &mut transform_u16_to_source_text(&body),
719            )
720        });
721        if handler.get().is_null() {
722            // Step 3.7
723            let mut realm = enter_auto_realm(cx, self);
724            report_pending_exception(&mut realm.current_realm());
725            return None;
726        }
727
728        // Step 3.10 happens when we drop _ac
729
730        // TODO Step 3.11
731
732        // Step 3.12
733        let funobj = unsafe { JS_GetFunctionObject(handler.get()) };
734        assert!(!funobj.is_null());
735        // Step 1.14
736        if is_error {
737            Some(CommonEventHandler::ErrorEventHandler(unsafe {
738                OnErrorEventHandlerNonNull::new(cx, funobj)
739            }))
740        } else if ty == &atom!("beforeunload") {
741            Some(CommonEventHandler::BeforeUnloadEventHandler(unsafe {
742                OnBeforeUnloadEventHandlerNonNull::new(cx, funobj)
743            }))
744        } else {
745            Some(CommonEventHandler::EventHandler(unsafe {
746                EventHandlerNonNull::new(cx, funobj)
747            }))
748        }
749    }
750
751    #[expect(unsafe_code)]
752    pub(crate) fn set_event_handler_common<T: CallbackContainer<crate::DomTypeHolder>>(
753        &self,
754        cx: &mut JSContext,
755        ty: &str,
756        listener: Option<Rc<T>>,
757    ) {
758        let event_listener = listener.map(|listener| {
759            InlineEventListener::Compiled(CommonEventHandler::EventHandler(unsafe {
760                EventHandlerNonNull::new(cx, listener.callback())
761            }))
762        });
763        self.set_inline_event_listener(Atom::from(ty), event_listener);
764    }
765
766    #[expect(unsafe_code)]
767    pub(crate) fn set_error_event_handler<T: CallbackContainer<crate::DomTypeHolder>>(
768        &self,
769        cx: &mut JSContext,
770        ty: &str,
771        listener: Option<Rc<T>>,
772    ) {
773        let event_listener = listener.map(|listener| {
774            InlineEventListener::Compiled(CommonEventHandler::ErrorEventHandler(unsafe {
775                OnErrorEventHandlerNonNull::new(cx, listener.callback())
776            }))
777        });
778        self.set_inline_event_listener(Atom::from(ty), event_listener);
779    }
780
781    #[expect(unsafe_code)]
782    pub(crate) fn set_beforeunload_event_handler<T: CallbackContainer<crate::DomTypeHolder>>(
783        &self,
784        cx: &mut JSContext,
785        ty: &str,
786        listener: Option<Rc<T>>,
787    ) {
788        let event_listener = listener.map(|listener| {
789            InlineEventListener::Compiled(CommonEventHandler::BeforeUnloadEventHandler(unsafe {
790                OnBeforeUnloadEventHandlerNonNull::new(cx, listener.callback())
791            }))
792        });
793        self.set_inline_event_listener(Atom::from(ty), event_listener);
794    }
795
796    #[expect(unsafe_code)]
797    pub(crate) fn get_event_handler_common<T: CallbackContainer<crate::DomTypeHolder>>(
798        &self,
799        cx: &mut JSContext,
800        ty: &str,
801    ) -> Option<Rc<T>> {
802        let listener = self.get_inline_event_listener(cx, &Atom::from(ty));
803        unsafe {
804            listener.map(|listener| {
805                CallbackContainer::new(cx, listener.parent().callback_holder().get())
806            })
807        }
808    }
809
810    pub(crate) fn has_handlers(&self) -> bool {
811        !self.handlers.borrow().is_empty()
812    }
813
814    // https://dom.spec.whatwg.org/#concept-event-fire
815    pub(crate) fn fire_event(&self, cx: &mut js::context::JSContext, name: Atom) -> bool {
816        self.fire_event_with_params(
817            cx,
818            name,
819            EventBubbles::DoesNotBubble,
820            EventCancelable::NotCancelable,
821            EventComposed::NotComposed,
822        )
823    }
824
825    // https://dom.spec.whatwg.org/#concept-event-fire
826    pub(crate) fn fire_bubbling_event(&self, cx: &mut js::context::JSContext, name: Atom) -> bool {
827        self.fire_event_with_params(
828            cx,
829            name,
830            EventBubbles::Bubbles,
831            EventCancelable::NotCancelable,
832            EventComposed::NotComposed,
833        )
834    }
835
836    // https://dom.spec.whatwg.org/#concept-event-fire
837    pub(crate) fn fire_cancelable_event(
838        &self,
839        cx: &mut js::context::JSContext,
840        name: Atom,
841    ) -> bool {
842        self.fire_event_with_params(
843            cx,
844            name,
845            EventBubbles::DoesNotBubble,
846            EventCancelable::Cancelable,
847            EventComposed::NotComposed,
848        )
849    }
850
851    // https://dom.spec.whatwg.org/#concept-event-fire
852    pub(crate) fn fire_bubbling_cancelable_event(
853        &self,
854        cx: &mut js::context::JSContext,
855        name: Atom,
856    ) -> bool {
857        self.fire_event_with_params(
858            cx,
859            name,
860            EventBubbles::Bubbles,
861            EventCancelable::Cancelable,
862            EventComposed::NotComposed,
863        )
864    }
865
866    /// <https://dom.spec.whatwg.org/#concept-event-fire>
867    pub(crate) fn fire_event_with_params(
868        &self,
869        cx: &mut js::context::JSContext,
870        name: Atom,
871        bubbles: EventBubbles,
872        cancelable: EventCancelable,
873        composed: EventComposed,
874    ) -> bool {
875        let event = Event::new(cx, &self.global(), name, bubbles, cancelable);
876        event.set_composed(composed.into());
877        event.fire(cx, self)
878    }
879
880    /// <https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener>
881    /// and <https://dom.spec.whatwg.org/#add-an-event-listener>
882    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            // Step 2. If listener’s signal is not null and is aborted, then return.
890            if signal.aborted() {
891                return;
892            }
893            // Step 6. If listener’s signal is not null, then add the following abort steps to it:
894            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        // Step 3. If listener’s callback is null, then return.
904        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        // Step 4. If listener’s passive is null, then set it to the default passive value given listener’s type and eventTarget.
921        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        // Step 5. If eventTarget’s event listener list does not contain
930        // an event listener whose type is listener’s type, callback is listener’s callback,
931        // and capture is listener’s capture, then append listener to eventTarget’s event listener list.
932        if !entries.contains(&new_entry) {
933            entries.push(new_entry);
934            self.notify_listener_added(&ty);
935        }
936    }
937
938    /// <https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener>
939    /// and <https://dom.spec.whatwg.org/#remove-an-event-listener>
940    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                // Step 2. Set listener’s removed to true and remove listener from eventTarget’s event listener list.
963                entries.remove(position).borrow_mut().removed = true;
964                self.notify_listener_removed(&ty_atom);
965            }
966        }
967    }
968
969    /// <https://dom.spec.whatwg.org/#get-the-parent>
970    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            // > A node’s get the parent algorithm, given an event, returns the node’s assigned slot,
990            // > if node is assigned; otherwise node’s parent.
991            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        // https://w3c.github.io/IndexedDB/#events
998        // The get the parent algorithm for an IDBRequest returns the request's transaction.
999        if let Some(request) = self.downcast::<IDBRequest>() {
1000            return request
1001                .transaction()
1002                .map(|tx| DomRoot::from_ref(tx.upcast::<EventTarget>()));
1003        }
1004
1005        // The get the parent algorithm for an IDBTransaction returns the transaction's connection.
1006        if let Some(transaction) = self.downcast::<IDBTransaction>() {
1007            return Some(DomRoot::from_ref(
1008                transaction.get_db().upcast::<EventTarget>(),
1009            ));
1010        }
1011
1012        // The get the parent algorithm for an IDBDatabase returns null.
1013        if self.is::<IDBDatabase>() {
1014            return None;
1015        }
1016
1017        None
1018    }
1019
1020    // FIXME: This algorithm operates on "objects", which may not be event targets.
1021    // All our current use-cases only work on event targets, but this might change in the future
1022    /// <https://dom.spec.whatwg.org/#retarget>
1023    pub(crate) fn retarget(&self, b: &Self) -> DomRoot<EventTarget> {
1024        // To retarget an object A against an object B, repeat these steps until they return an object:
1025        let mut a = DomRoot::from_ref(self);
1026        loop {
1027            // Step 1. If one of the following is true
1028            // * A is not a node
1029            // * A’s root is not a shadow root
1030            // * B is a node and A’s root is a shadow-including inclusive ancestor of B
1031            // then return A.
1032            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            // Step 2. Set A to A’s root’s host.
1046            a = DomRoot::from_ref(
1047                a_root
1048                    .downcast::<ShadowRoot>()
1049                    .unwrap()
1050                    .Host()
1051                    .upcast::<EventTarget>(),
1052            );
1053        }
1054    }
1055
1056    /// <https://html.spec.whatwg.org/multipage/#event-handler-content-attributes>
1057    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    /// <https://dom.spec.whatwg.org/#dom-eventtarget-eventtarget>
1080    fn Constructor(
1081        cx: &mut JSContext,
1082        global: &GlobalScope,
1083        proto: Option<HandleObject>,
1084    ) -> Fallible<DomRoot<EventTarget>> {
1085        Ok(EventTarget::new(cx, global, proto))
1086    }
1087
1088    /// <https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener>
1089    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    /// <https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener>
1099    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    /// <https://dom.spec.whatwg.org/#dom-eventtarget-dispatchevent>
1109    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(event.dispatch(cx, self, false))
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    /// <https://dom.spec.whatwg.org/#event-flatten-more>
1126    fn convert(self) -> AddEventListenerOptions {
1127        // Step 1. Let capture be the result of flattening options.
1128        // Step 5. Return capture, passive, once, and signal.
1129        match self {
1130            // Step 4. If options is a dictionary:
1131            AddEventListenerOptionsOrBoolean::AddEventListenerOptions(options) => options,
1132            AddEventListenerOptionsOrBoolean::Boolean(capture) => AddEventListenerOptions {
1133                parent: EventListenerOptions { capture },
1134                // Step 2. Let once be false.
1135                once: false,
1136                // Step 3. Let passive and signal be null.
1137                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}