| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * 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/. */ |
| |
| //! 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, NullValue, 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; |
| } |
| |
| impl<T: DomObject> ThisReflector for T { |
| fn jsobject(&self) -> *mut JSObject { |
| self.reflector().get_jsobject().get() |
| } |
| } |
| |
| impl ThisReflector for HandleObject<'_> { |
| fn jsobject(&self) -> *mut JSObject { |
| 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, MallocSizeOf)] |
| #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] |
| pub struct CallbackObject<D: DomTypes> { |
| /// The underlying `JSObject`. |
| #[ignore_malloc_size_of = "measured by mozjs"] |
| callback: Heap<*mut JSObject>, |
| #[ignore_malloc_size_of = "measured by mozjs"] |
| 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)); |
| unsafe { |
| 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, MallocSizeOf, 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) { |
| unsafe { self.object.init(cx, callback) }; |
| } |
| } |
| |
| /// A common base class for representing IDL callback interface types. |
| #[derive(JSTraceable, MallocSizeOf, 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) { |
| unsafe { 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()); |
| |
| if obj.is_null() { |
| rval.set(NullValue()); |
| return true; |
| } |
| |
| 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()); |
| } |
| } |