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