diff options
Diffstat (limited to 'components/script_bindings/callback.rs')
-rw-r--r-- | components/script_bindings/callback.rs | 297 |
1 files changed, 295 insertions, 2 deletions
diff --git a/components/script_bindings/callback.rs b/components/script_bindings/callback.rs index d891535e671..a31b57f677f 100644 --- a/components/script_bindings/callback.rs +++ b/components/script_bindings/callback.rs @@ -2,10 +2,31 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use js::jsapi::JSObject; -use js::rust::HandleObject; +//! Base classes to work with IDL callbacks. +use std::default::Default; +use std::ffi::CString; +use std::mem::drop; +use std::rc::Rc; + +use js::jsapi::{ + AddRawValueRoot, EnterRealm, Heap, IsCallable, JSObject, LeaveRealm, Realm, RemoveRawValueRoot, +}; +use js::jsval::{JSVal, ObjectValue, UndefinedValue}; +use js::rust::wrappers::{JS_GetProperty, JS_WrapObject}; +use js::rust::{HandleObject, MutableHandleValue, Runtime}; + +use crate::DomTypes; +use crate::codegen::GenericBindings::WindowBinding::Window_Binding::WindowMethods; +use crate::error::{Error, Fallible}; +use crate::inheritance::Castable; +use crate::interfaces::{DocumentHelpers, DomHelpers, GlobalScopeHelpers}; +use crate::realms::{InRealm, enter_realm}; use crate::reflector::DomObject; +use crate::root::{Dom, DomRoot}; +use crate::script_runtime::{CanGc, JSContext}; +use crate::settings_stack::{GenericAutoEntryScript, GenericAutoIncumbentScript}; +use crate::utils::AsCCharPtrPtr; pub trait ThisReflector { fn jsobject(&self) -> *mut JSObject; @@ -22,3 +43,275 @@ impl ThisReflector for HandleObject<'_> { self.get() } } + +/// The exception handling used for a call. +#[derive(Clone, Copy, PartialEq)] +pub enum ExceptionHandling { + /// Report any exception and don't throw it to the caller code. + Report, + /// Throw any exception to the caller code. + Rethrow, +} + +/// A common base class for representing IDL callback function and +/// callback interface types. +#[derive(JSTraceable)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +pub struct CallbackObject<D: DomTypes> { + /// The underlying `JSObject`. + callback: Heap<*mut JSObject>, + permanent_js_root: Heap<JSVal>, + + /// The ["callback context"], that is, the global to use as incumbent + /// global when calling the callback. + /// + /// Looking at the WebIDL standard, it appears as though there would always + /// be a value here, but [sometimes] callback functions are created by + /// hand-waving without defining the value of the callback context, and + /// without any JavaScript code on the stack to grab an incumbent global + /// from. + /// + /// ["callback context"]: https://heycam.github.io/webidl/#dfn-callback-context + /// [sometimes]: https://github.com/whatwg/html/issues/2248 + incumbent: Option<Dom<D::GlobalScope>>, +} + +impl<D: DomTypes> CallbackObject<D> { + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + // These are used by the bindings and do not need `default()` functions. + #[allow(clippy::new_without_default)] + fn new() -> Self { + Self { + callback: Heap::default(), + permanent_js_root: Heap::default(), + incumbent: D::GlobalScope::incumbent().map(|i| Dom::from_ref(&*i)), + } + } + + pub fn get(&self) -> *mut JSObject { + self.callback.get() + } + + #[allow(unsafe_code)] + unsafe fn init(&mut self, cx: JSContext, callback: *mut JSObject) { + self.callback.set(callback); + self.permanent_js_root.set(ObjectValue(callback)); + assert!(AddRawValueRoot( + *cx, + self.permanent_js_root.get_unsafe(), + b"CallbackObject::root\n".as_c_char_ptr() + )); + } +} + +impl<D: DomTypes> Drop for CallbackObject<D> { + #[allow(unsafe_code)] + fn drop(&mut self) { + unsafe { + if let Some(cx) = Runtime::get() { + RemoveRawValueRoot(cx.as_ptr(), self.permanent_js_root.get_unsafe()); + } + } + } +} + +impl<D: DomTypes> PartialEq for CallbackObject<D> { + fn eq(&self, other: &CallbackObject<D>) -> bool { + self.callback.get() == other.callback.get() + } +} + +/// A trait to be implemented by concrete IDL callback function and +/// callback interface types. +pub trait CallbackContainer<D: DomTypes> { + /// Create a new CallbackContainer object for the given `JSObject`. + /// + /// # Safety + /// `callback` must point to a valid, non-null JSObject. + unsafe fn new(cx: JSContext, callback: *mut JSObject) -> Rc<Self>; + /// Returns the underlying `CallbackObject`. + fn callback_holder(&self) -> &CallbackObject<D>; + /// Returns the underlying `JSObject`. + fn callback(&self) -> *mut JSObject { + self.callback_holder().get() + } + /// Returns the ["callback context"], that is, the global to use as + /// incumbent global when calling the callback. + /// + /// ["callback context"]: https://heycam.github.io/webidl/#dfn-callback-context + fn incumbent(&self) -> Option<&D::GlobalScope> { + self.callback_holder().incumbent.as_deref() + } +} + +/// A common base class for representing IDL callback function types. +#[derive(JSTraceable, PartialEq)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +pub struct CallbackFunction<D: DomTypes> { + object: CallbackObject<D>, +} + +impl<D: DomTypes> CallbackFunction<D> { + /// Create a new `CallbackFunction` for this object. + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + // These are used by the bindings and do not need `default()` functions. + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { + object: CallbackObject::new(), + } + } + + /// Returns the underlying `CallbackObject`. + pub fn callback_holder(&self) -> &CallbackObject<D> { + &self.object + } + + /// Initialize the callback function with a value. + /// Should be called once this object is done moving. + /// + /// # Safety + /// `callback` must point to a valid, non-null JSObject. + pub unsafe fn init(&mut self, cx: JSContext, callback: *mut JSObject) { + self.object.init(cx, callback); + } +} + +/// A common base class for representing IDL callback interface types. +#[derive(JSTraceable, PartialEq)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +pub struct CallbackInterface<D: DomTypes> { + object: CallbackObject<D>, +} + +impl<D: DomTypes> CallbackInterface<D> { + /// Create a new CallbackInterface object for the given `JSObject`. + // These are used by the bindings and do not need `default()` functions. + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { + object: CallbackObject::new(), + } + } + + /// Returns the underlying `CallbackObject`. + pub fn callback_holder(&self) -> &CallbackObject<D> { + &self.object + } + + /// Initialize the callback function with a value. + /// Should be called once this object is done moving. + /// + /// # Safety + /// `callback` must point to a valid, non-null JSObject. + pub unsafe fn init(&mut self, cx: JSContext, callback: *mut JSObject) { + self.object.init(cx, callback); + } + + /// Returns the property with the given `name`, if it is a callable object, + /// or an error otherwise. + pub fn get_callable_property(&self, cx: JSContext, name: &str) -> Fallible<JSVal> { + rooted!(in(*cx) let mut callable = UndefinedValue()); + rooted!(in(*cx) let obj = self.callback_holder().get()); + unsafe { + let c_name = CString::new(name).unwrap(); + if !JS_GetProperty(*cx, obj.handle(), c_name.as_ptr(), callable.handle_mut()) { + return Err(Error::JSFailed); + } + + if !callable.is_object() || !IsCallable(callable.to_object()) { + return Err(Error::Type(format!( + "The value of the {} property is not callable", + name + ))); + } + } + Ok(callable.get()) + } +} + +/// Wraps the reflector for `p` into the realm of `cx`. +pub(crate) fn wrap_call_this_value<T: ThisReflector>( + cx: JSContext, + p: &T, + mut rval: MutableHandleValue, +) -> bool { + rooted!(in(*cx) let mut obj = p.jsobject()); + assert!(!obj.is_null()); + + unsafe { + if !JS_WrapObject(*cx, obj.handle_mut()) { + return false; + } + } + + rval.set(ObjectValue(*obj)); + true +} + +/// A class that performs whatever setup we need to safely make a call while +/// this class is on the stack. After `new` returns, the call is safe to make. +pub struct CallSetup<D: DomTypes> { + /// The global for reporting exceptions. This is the global object of the + /// (possibly wrapped) callback object. + exception_global: DomRoot<D::GlobalScope>, + /// The `JSContext` used for the call. + cx: JSContext, + /// The realm we were in before the call. + old_realm: *mut Realm, + /// The exception handling used for the call. + handling: ExceptionHandling, + /// <https://heycam.github.io/webidl/#es-invoking-callback-functions> + /// steps 8 and 18.2. + entry_script: Option<GenericAutoEntryScript<D>>, + /// <https://heycam.github.io/webidl/#es-invoking-callback-functions> + /// steps 9 and 18.1. + incumbent_script: Option<GenericAutoIncumbentScript<D>>, +} + +impl<D: DomTypes> CallSetup<D> { + /// Performs the setup needed to make a call. + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + pub fn new<T: CallbackContainer<D>>(callback: &T, handling: ExceptionHandling) -> Self { + let global = unsafe { D::GlobalScope::from_object(callback.callback()) }; + if let Some(window) = global.downcast::<D::Window>() { + window.Document().ensure_safe_to_run_script_or_layout(); + } + let cx = D::GlobalScope::get_cx(); + + let aes = GenericAutoEntryScript::<D>::new(&global); + let ais = callback.incumbent().map(GenericAutoIncumbentScript::new); + CallSetup { + exception_global: global, + cx, + old_realm: unsafe { EnterRealm(*cx, callback.callback()) }, + handling, + entry_script: Some(aes), + incumbent_script: ais, + } + } + + /// Returns the `JSContext` used for the call. + pub fn get_context(&self) -> JSContext { + self.cx + } +} + +impl<D: DomTypes> Drop for CallSetup<D> { + fn drop(&mut self) { + unsafe { + LeaveRealm(*self.cx, self.old_realm); + } + if self.handling == ExceptionHandling::Report { + let ar = enter_realm::<D>(&*self.exception_global); + <D as DomHelpers<D>>::report_pending_exception( + self.cx, + true, + InRealm::Entered(&ar), + CanGc::note(), + ); + } + drop(self.incumbent_script.take()); + drop(self.entry_script.take().unwrap()); + } +} |