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