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