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