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    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
83    // These are used by the bindings and do not need `default()` functions.
84    #[allow(clippy::new_without_default)]
85    fn new() -> Self {
86        Self {
87            callback: Heap::default(),
88            permanent_js_root: Heap::default(),
89            incumbent: D::GlobalScope::incumbent().map(|i| Dom::from_ref(&*i)),
90        }
91    }
92
93    pub fn get(&self) -> *mut JSObject {
94        self.callback.get()
95    }
96
97    #[allow(unsafe_code)]
98    unsafe fn init(&mut self, cx: JSContext, callback: *mut JSObject) {
99        self.callback.set(callback);
100        self.permanent_js_root.set(ObjectValue(callback));
101        unsafe {
102            assert!(AddRawValueRoot(
103                *cx,
104                self.permanent_js_root.get_unsafe(),
105                b"CallbackObject::root\n".as_c_char_ptr()
106            ));
107        }
108    }
109}
110
111impl<D: DomTypes> Drop for CallbackObject<D> {
112    #[allow(unsafe_code)]
113    fn drop(&mut self) {
114        unsafe {
115            if let Some(cx) = Runtime::get() {
116                RemoveRawValueRoot(cx.as_ptr(), self.permanent_js_root.get_unsafe());
117            }
118        }
119    }
120}
121
122impl<D: DomTypes> PartialEq for CallbackObject<D> {
123    fn eq(&self, other: &CallbackObject<D>) -> bool {
124        self.callback.get() == other.callback.get()
125    }
126}
127
128/// A trait to be implemented by concrete IDL callback function and
129/// callback interface types.
130pub trait CallbackContainer<D: DomTypes> {
131    /// Create a new CallbackContainer object for the given `JSObject`.
132    ///
133    /// # Safety
134    /// `callback` must point to a valid, non-null JSObject.
135    unsafe fn new(cx: JSContext, callback: *mut JSObject) -> Rc<Self>;
136    /// Returns the underlying `CallbackObject`.
137    fn callback_holder(&self) -> &CallbackObject<D>;
138    /// Returns the underlying `JSObject`.
139    fn callback(&self) -> *mut JSObject {
140        self.callback_holder().get()
141    }
142    /// Returns the ["callback context"], that is, the global to use as
143    /// incumbent global when calling the callback.
144    ///
145    /// ["callback context"]: https://heycam.github.io/webidl/#dfn-callback-context
146    fn incumbent(&self) -> Option<&D::GlobalScope> {
147        self.callback_holder().incumbent.as_deref()
148    }
149}
150
151/// A common base class for representing IDL callback function types.
152#[derive(JSTraceable, MallocSizeOf, PartialEq)]
153#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
154pub struct CallbackFunction<D: DomTypes> {
155    object: CallbackObject<D>,
156}
157
158impl<D: DomTypes> CallbackFunction<D> {
159    /// Create a new `CallbackFunction` for this object.
160    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
161    // These are used by the bindings and do not need `default()` functions.
162    #[allow(clippy::new_without_default)]
163    pub fn new() -> Self {
164        Self {
165            object: CallbackObject::new(),
166        }
167    }
168
169    /// Returns the underlying `CallbackObject`.
170    pub fn callback_holder(&self) -> &CallbackObject<D> {
171        &self.object
172    }
173
174    /// Initialize the callback function with a value.
175    /// Should be called once this object is done moving.
176    ///
177    /// # Safety
178    /// `callback` must point to a valid, non-null JSObject.
179    pub unsafe fn init(&mut self, cx: JSContext, callback: *mut JSObject) {
180        unsafe { self.object.init(cx, callback) };
181    }
182}
183
184/// A common base class for representing IDL callback interface types.
185#[derive(JSTraceable, MallocSizeOf, PartialEq)]
186#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
187pub struct CallbackInterface<D: DomTypes> {
188    object: CallbackObject<D>,
189}
190
191impl<D: DomTypes> CallbackInterface<D> {
192    /// Create a new CallbackInterface object for the given `JSObject`.
193    // These are used by the bindings and do not need `default()` functions.
194    #[allow(clippy::new_without_default)]
195    pub fn new() -> Self {
196        Self {
197            object: CallbackObject::new(),
198        }
199    }
200
201    /// Returns the underlying `CallbackObject`.
202    pub fn callback_holder(&self) -> &CallbackObject<D> {
203        &self.object
204    }
205
206    /// Initialize the callback function with a value.
207    /// Should be called once this object is done moving.
208    ///
209    /// # Safety
210    /// `callback` must point to a valid, non-null JSObject.
211    pub unsafe fn init(&mut self, cx: JSContext, callback: *mut JSObject) {
212        unsafe { self.object.init(cx, callback) };
213    }
214
215    /// Returns the property with the given `name`, if it is a callable object,
216    /// or an error otherwise.
217    pub fn get_callable_property(&self, cx: JSContext, name: &str) -> Fallible<JSVal> {
218        rooted!(in(*cx) let mut callable = UndefinedValue());
219        rooted!(in(*cx) let obj = self.callback_holder().get());
220        unsafe {
221            let c_name = CString::new(name).unwrap();
222            if !JS_GetProperty(*cx, obj.handle(), c_name.as_ptr(), callable.handle_mut()) {
223                return Err(Error::JSFailed);
224            }
225
226            if !callable.is_object() || !IsCallable(callable.to_object()) {
227                return Err(Error::Type(format!(
228                    "The value of the {} property is not callable",
229                    name
230                )));
231            }
232        }
233        Ok(callable.get())
234    }
235}
236
237/// Wraps the reflector for `p` into the realm of `cx`.
238pub(crate) fn wrap_call_this_value<T: ThisReflector>(
239    cx: JSContext,
240    p: &T,
241    mut rval: MutableHandleValue,
242) -> bool {
243    rooted!(in(*cx) let mut obj = p.jsobject());
244
245    if obj.is_null() {
246        rval.set(NullValue());
247        return true;
248    }
249
250    unsafe {
251        if !JS_WrapObject(*cx, obj.handle_mut()) {
252            return false;
253        }
254    }
255
256    rval.set(ObjectValue(*obj));
257    true
258}
259
260/// A class that performs whatever setup we need to safely make a call while
261/// this class is on the stack. After `new` returns, the call is safe to make.
262pub struct CallSetup<D: DomTypes> {
263    /// The global for reporting exceptions. This is the global object of the
264    /// (possibly wrapped) callback object.
265    exception_global: DomRoot<D::GlobalScope>,
266    /// The `JSContext` used for the call.
267    cx: JSContext,
268    /// The realm we were in before the call.
269    old_realm: *mut Realm,
270    /// The exception handling used for the call.
271    handling: ExceptionHandling,
272    /// <https://heycam.github.io/webidl/#es-invoking-callback-functions>
273    /// steps 8 and 18.2.
274    entry_script: Option<GenericAutoEntryScript<D>>,
275    /// <https://heycam.github.io/webidl/#es-invoking-callback-functions>
276    /// steps 9 and 18.1.
277    incumbent_script: Option<GenericAutoIncumbentScript<D>>,
278}
279
280impl<D: DomTypes> CallSetup<D> {
281    /// Performs the setup needed to make a call.
282    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
283    pub fn new<T: CallbackContainer<D>>(callback: &T, handling: ExceptionHandling) -> Self {
284        let global = unsafe { D::GlobalScope::from_object(callback.callback()) };
285        if let Some(window) = global.downcast::<D::Window>() {
286            window.Document().ensure_safe_to_run_script_or_layout();
287        }
288        let cx = D::GlobalScope::get_cx();
289
290        let aes = GenericAutoEntryScript::<D>::new(&global);
291        let ais = callback.incumbent().map(GenericAutoIncumbentScript::new);
292        CallSetup {
293            exception_global: global,
294            cx,
295            old_realm: unsafe { EnterRealm(*cx, callback.callback()) },
296            handling,
297            entry_script: Some(aes),
298            incumbent_script: ais,
299        }
300    }
301
302    /// Returns the `JSContext` used for the call.
303    pub fn get_context(&self) -> JSContext {
304        self.cx
305    }
306}
307
308impl<D: DomTypes> Drop for CallSetup<D> {
309    fn drop(&mut self) {
310        unsafe {
311            LeaveRealm(*self.cx, self.old_realm);
312        }
313        if self.handling == ExceptionHandling::Report {
314            let ar = enter_realm::<D>(&*self.exception_global);
315            <D as DomHelpers<D>>::report_pending_exception(
316                self.cx,
317                true,
318                InRealm::Entered(&ar),
319                CanGc::note(),
320            );
321        }
322        drop(self.incumbent_script.take());
323        drop(self.entry_script.take().unwrap());
324    }
325}