Skip to main content

script/dom/event/
eventtarget.rs

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