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