script_bindings/
callback.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
5//! Base classes to work with IDL callbacks.
6
7use std::default::Default;
8use std::ffi::CString;
9use std::mem::drop;
10use std::rc::Rc;
11
12use js::jsapi::{
13    AddRawValueRoot, EnterRealm, Heap, IsCallable, JSObject, LeaveRealm, Realm, RemoveRawValueRoot,
14};
15use js::jsval::{JSVal, NullValue, ObjectValue, UndefinedValue};
16use js::rust::wrappers::{JS_GetProperty, JS_WrapObject};
17use js::rust::{HandleObject, MutableHandleValue, Runtime};
18
19use crate::DomTypes;
20use crate::codegen::GenericBindings::WindowBinding::Window_Binding::WindowMethods;
21use crate::error::{Error, Fallible};
22use crate::inheritance::Castable;
23use crate::interfaces::{DocumentHelpers, DomHelpers, GlobalScopeHelpers};
24use crate::realms::{InRealm, enter_realm};
25use crate::reflector::DomObject;
26use crate::root::{Dom, DomRoot};
27use crate::script_runtime::{CanGc, JSContext};
28use crate::settings_stack::{GenericAutoEntryScript, GenericAutoIncumbentScript};
29use crate::utils::AsCCharPtrPtr;
30
31pub trait ThisReflector {
32    fn jsobject(&self) -> *mut JSObject;
33}
34
35impl<T: DomObject> ThisReflector for T {
36    fn jsobject(&self) -> *mut JSObject {
37        self.reflector().get_jsobject().get()
38    }
39}
40
41impl ThisReflector for HandleObject<'_> {
42    fn jsobject(&self) -> *mut JSObject {
43        self.get()
44    }
45}
46
47/// The exception handling used for a call.
48#[derive(Clone, Copy, PartialEq)]
49pub enum ExceptionHandling {
50    /// Report any exception and don't throw it to the caller code.
51    Report,
52    /// Throw any exception to the caller code.
53    Rethrow,
54}
55
56/// A common base class for representing IDL callback function and
57/// callback interface types.
58#[derive(JSTraceable, MallocSizeOf)]
59#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
60pub struct CallbackObject<D: DomTypes> {
61    /// The underlying `JSObject`.
62    #[ignore_malloc_size_of = "measured by mozjs"]
63    callback: Heap<*mut JSObject>,
64    #[ignore_malloc_size_of = "measured by mozjs"]
65    permanent_js_root: Heap<JSVal>,
66
67    /// The ["callback context"], that is, the global to use as incumbent
68    /// global when calling the callback.
69    ///
70    /// Looking at the WebIDL standard, it appears as though there would always
71    /// be a value here, but [sometimes] callback functions are created by
72    /// hand-waving without defining the value of the callback context, and
73    /// without any JavaScript code on the stack to grab an incumbent global
74    /// from.
75    ///
76    /// ["callback context"]: https://heycam.github.io/webidl/#dfn-callback-context
77    /// [sometimes]: https://github.com/whatwg/html/issues/2248
78    incumbent: Option<Dom<D::GlobalScope>>,
79}
80
81impl<D: DomTypes> CallbackObject<D> {
82    // These are used by the bindings and do not need `default()` functions.
83    #[allow(clippy::new_without_default)]
84    fn new() -> Self {
85        Self {
86            callback: Heap::default(),
87            permanent_js_root: Heap::default(),
88            incumbent: D::GlobalScope::incumbent().map(|i| Dom::from_ref(&*i)),
89        }
90    }
91
92    pub fn get(&self) -> *mut JSObject {
93        self.callback.get()
94    }
95
96    #[expect(unsafe_code)]
97    unsafe fn init(&mut self, cx: JSContext, callback: *mut JSObject) {
98        self.callback.set(callback);
99        self.permanent_js_root.set(ObjectValue(callback));
100        unsafe {
101            assert!(AddRawValueRoot(
102                *cx,
103                self.permanent_js_root.get_unsafe(),
104                b"CallbackObject::root\n".as_c_char_ptr()
105            ));
106        }
107    }
108}
109
110impl<D: DomTypes> Drop for CallbackObject<D> {
111    #[expect(unsafe_code)]
112    fn drop(&mut self) {
113        unsafe {
114            if let Some(cx) = Runtime::get() {
115                RemoveRawValueRoot(cx.as_ptr(), self.permanent_js_root.get_unsafe());
116            }
117        }
118    }
119}
120
121impl<D: DomTypes> PartialEq for CallbackObject<D> {
122    fn eq(&self, other: &CallbackObject<D>) -> bool {
123        self.callback.get() == other.callback.get()
124    }
125}
126
127/// A trait to be implemented by concrete IDL callback function and
128/// callback interface types.
129pub trait CallbackContainer<D: DomTypes> {
130    /// Create a new CallbackContainer object for the given `JSObject`.
131    ///
132    /// # Safety
133    /// `callback` must point to a valid, non-null JSObject.
134    unsafe fn new(cx: JSContext, callback: *mut JSObject) -> Rc<Self>;
135    /// Returns the underlying `CallbackObject`.
136    fn callback_holder(&self) -> &CallbackObject<D>;
137    /// Returns the underlying `JSObject`.
138    fn callback(&self) -> *mut JSObject {
139        self.callback_holder().get()
140    }
141    /// Returns the ["callback context"], that is, the global to use as
142    /// incumbent global when calling the callback.
143    ///
144    /// ["callback context"]: https://heycam.github.io/webidl/#dfn-callback-context
145    fn incumbent(&self) -> Option<&D::GlobalScope> {
146        self.callback_holder().incumbent.as_deref()
147    }
148}
149
150/// A common base class for representing IDL callback function types.
151#[derive(JSTraceable, MallocSizeOf, PartialEq)]
152#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
153pub struct CallbackFunction<D: DomTypes> {
154    object: CallbackObject<D>,
155}
156
157impl<D: DomTypes> CallbackFunction<D> {
158    /// Create a new `CallbackFunction` for this object.
159    // These are used by the bindings and do not need `default()` functions.
160    #[expect(clippy::new_without_default)]
161    pub fn new() -> Self {
162        Self {
163            object: CallbackObject::new(),
164        }
165    }
166
167    /// Returns the underlying `CallbackObject`.
168    pub fn callback_holder(&self) -> &CallbackObject<D> {
169        &self.object
170    }
171
172    /// Initialize the callback function with a value.
173    /// Should be called once this object is done moving.
174    ///
175    /// # Safety
176    /// `callback` must point to a valid, non-null JSObject.
177    pub unsafe fn init(&mut self, cx: JSContext, callback: *mut JSObject) {
178        unsafe { self.object.init(cx, callback) };
179    }
180}
181
182/// A common base class for representing IDL callback interface types.
183#[derive(JSTraceable, MallocSizeOf, PartialEq)]
184#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
185pub struct CallbackInterface<D: DomTypes> {
186    object: CallbackObject<D>,
187}
188
189impl<D: DomTypes> CallbackInterface<D> {
190    /// Create a new CallbackInterface object for the given `JSObject`.
191    // These are used by the bindings and do not need `default()` functions.
192    #[expect(clippy::new_without_default)]
193    pub fn new() -> Self {
194        Self {
195            object: CallbackObject::new(),
196        }
197    }
198
199    /// Returns the underlying `CallbackObject`.
200    pub fn callback_holder(&self) -> &CallbackObject<D> {
201        &self.object
202    }
203
204    /// Initialize the callback function with a value.
205    /// Should be called once this object is done moving.
206    ///
207    /// # Safety
208    /// `callback` must point to a valid, non-null JSObject.
209    pub unsafe fn init(&mut self, cx: JSContext, callback: *mut JSObject) {
210        unsafe { self.object.init(cx, callback) };
211    }
212
213    /// Returns the property with the given `name`, if it is a callable object,
214    /// or an error otherwise.
215    pub fn get_callable_property(&self, cx: JSContext, name: &str) -> Fallible<JSVal> {
216        rooted!(in(*cx) let mut callable = UndefinedValue());
217        rooted!(in(*cx) let obj = self.callback_holder().get());
218        unsafe {
219            let c_name = CString::new(name).unwrap();
220            if !JS_GetProperty(*cx, obj.handle(), c_name.as_ptr(), callable.handle_mut()) {
221                return Err(Error::JSFailed);
222            }
223
224            if !callable.is_object() || !IsCallable(callable.to_object()) {
225                return Err(Error::Type(format!(
226                    "The value of the {} property is not callable",
227                    name
228                )));
229            }
230        }
231        Ok(callable.get())
232    }
233}
234
235/// Wraps the reflector for `p` into the realm of `cx`.
236pub(crate) fn wrap_call_this_value<T: ThisReflector>(
237    cx: JSContext,
238    p: &T,
239    mut rval: MutableHandleValue,
240) -> bool {
241    rooted!(in(*cx) let mut obj = p.jsobject());
242
243    if obj.is_null() {
244        rval.set(NullValue());
245        return true;
246    }
247
248    unsafe {
249        if !JS_WrapObject(*cx, obj.handle_mut()) {
250            return false;
251        }
252    }
253
254    rval.set(ObjectValue(*obj));
255    true
256}
257
258/// A class that performs whatever setup we need to safely make a call while
259/// this class is on the stack. After `new` returns, the call is safe to make.
260pub struct CallSetup<D: DomTypes> {
261    /// The global for reporting exceptions. This is the global object of the
262    /// (possibly wrapped) callback object.
263    exception_global: DomRoot<D::GlobalScope>,
264    /// The `JSContext` used for the call.
265    cx: JSContext,
266    /// The realm we were in before the call.
267    old_realm: *mut Realm,
268    /// The exception handling used for the call.
269    handling: ExceptionHandling,
270    /// <https://heycam.github.io/webidl/#es-invoking-callback-functions>
271    /// steps 8 and 18.2.
272    entry_script: Option<GenericAutoEntryScript<D>>,
273    /// <https://heycam.github.io/webidl/#es-invoking-callback-functions>
274    /// steps 9 and 18.1.
275    incumbent_script: Option<GenericAutoIncumbentScript<D>>,
276}
277
278impl<D: DomTypes> CallSetup<D> {
279    /// Performs the setup needed to make a call.
280    pub fn new<T: CallbackContainer<D>>(callback: &T, handling: ExceptionHandling) -> Self {
281        let global = unsafe { D::GlobalScope::from_object(callback.callback()) };
282        if let Some(window) = global.downcast::<D::Window>() {
283            window.Document().ensure_safe_to_run_script_or_layout();
284        }
285        let cx = D::GlobalScope::get_cx();
286
287        let aes = GenericAutoEntryScript::<D>::new(&global);
288        let ais = callback.incumbent().map(GenericAutoIncumbentScript::new);
289        CallSetup {
290            exception_global: global,
291            cx,
292            old_realm: unsafe { EnterRealm(*cx, callback.callback()) },
293            handling,
294            entry_script: Some(aes),
295            incumbent_script: ais,
296        }
297    }
298
299    /// Returns the `JSContext` used for the call.
300    pub fn get_context(&self) -> JSContext {
301        self.cx
302    }
303}
304
305impl<D: DomTypes> Drop for CallSetup<D> {
306    fn drop(&mut self) {
307        unsafe {
308            LeaveRealm(*self.cx, self.old_realm);
309        }
310        if self.handling == ExceptionHandling::Report {
311            let ar = enter_realm::<D>(&*self.exception_global);
312            <D as DomHelpers<D>>::report_pending_exception(
313                self.cx,
314                true,
315                InRealm::Entered(&ar),
316                CanGc::note(),
317            );
318        }
319        drop(self.incumbent_script.take());
320        drop(self.entry_script.take().unwrap());
321    }
322}