diff --git a/core/engine/src/context/mod.rs b/core/engine/src/context/mod.rs index c69ebd5ec8..32df528b35 100644 --- a/core/engine/src/context/mod.rs +++ b/core/engine/src/context/mod.rs @@ -25,7 +25,7 @@ use crate::{ realm::Realm, script::Script, vm::{ActiveRunnable, CallFrame, Vm}, - JsNativeError, JsResult, JsString, JsValue, Source, + HostDefined, JsNativeError, JsResult, JsString, JsValue, NativeObject, Source, }; use self::intrinsics::StandardConstructor; @@ -115,6 +115,8 @@ pub struct Context { /// Unique identifier for each parser instance used during the context lifetime. parser_identifier: u32, + + data: HostDefined, } impl std::fmt::Debug for Context { @@ -585,6 +587,32 @@ impl Context { pub fn can_block(&self) -> bool { self.can_block } + + /// Insert a type into the context-specific [`HostDefined`] field. + #[inline] + pub fn insert_data(&mut self, value: T) -> Option> { + self.data.insert(value) + } + + /// Check if the context-specific [`HostDefined`] has type T. + #[inline] + #[must_use] + pub fn has_data(&self) -> bool { + self.data.has::() + } + + /// Remove type T from the context-specific [`HostDefined`], if it exists. + #[inline] + pub fn remove_data(&mut self) -> Option> { + self.data.remove::() + } + + /// Get type T from the context-specific [`HostDefined`], if it exists. + #[inline] + #[must_use] + pub fn get_data(&self) -> Option<&T> { + self.data.get::() + } } // ==== Private API ==== @@ -1070,6 +1098,7 @@ impl ContextBuilder { root_shape, parser_identifier: 0, can_block: self.can_block, + data: HostDefined::default(), }; builtins::set_default_global_bindings(&mut context)?; diff --git a/core/interop/src/into_js_function_impls.rs b/core/interop/src/into_js_function_impls.rs index d0d3093195..772a9914f8 100644 --- a/core/interop/src/into_js_function_impls.rs +++ b/core/interop/src/into_js_function_impls.rs @@ -16,35 +16,35 @@ macro_rules! impl_into_js_function { ($($id: ident: $t: ident),*) => { impl<$($t,)* R, T> IntoJsFunctionSealed<($($t,)*), R> for T where - $($t: TryFromJsArgument + 'static,)* + $($t: for<'a> TryFromJsArgument<'a> + 'static,)* R: TryIntoJsResult, T: FnMut($($t,)*) -> R + 'static {} impl<$($t,)* R, T> IntoJsFunctionSealed<($($t,)* ContextArgToken,), R> for T where - $($t: TryFromJsArgument + 'static,)* + $($t: for<'a> TryFromJsArgument<'a> + 'static,)* R: TryIntoJsResult, T: FnMut($($t,)* &mut Context) -> R + 'static {} - impl<$($t,)* R, T> IntoJsFunctionSealed<($($t,)* JsRest,), R> for T + impl<$($t,)* R, T> IntoJsFunctionSealed<($($t,)* JsRest<'_>,), R> for T where - $($t: TryFromJsArgument + 'static,)* + $($t: for<'a> TryFromJsArgument<'a> + 'static,)* R: TryIntoJsResult, - T: FnMut($($t,)* JsRest) -> R + 'static + T: FnMut($($t,)* JsRest<'_>) -> R + 'static {} - impl<$($t,)* R, T> IntoJsFunctionSealed<($($t,)* JsRest, ContextArgToken), R> for T + impl<$($t,)* R, T> IntoJsFunctionSealed<($($t,)* JsRest<'_>, ContextArgToken), R> for T where - $($t: TryFromJsArgument + 'static,)* + $($t: for<'a> TryFromJsArgument<'a> + 'static,)* R: TryIntoJsResult, - T: FnMut($($t,)* JsRest, &mut Context) -> R + 'static + T: FnMut($($t,)* JsRest<'_>, &mut Context) -> R + 'static {} impl<$($t,)* R, T> UnsafeIntoJsFunction<($($t,)*), R> for T where - $($t: TryFromJsArgument + 'static,)* + $($t: for<'a> TryFromJsArgument<'a> + 'static,)* R: TryIntoJsResult, T: FnMut($($t,)*) -> R + 'static, { @@ -68,11 +68,11 @@ macro_rules! impl_into_js_function { } } - impl<$($t,)* R, T> UnsafeIntoJsFunction<($($t,)* JsRest,), R> for T + impl<$($t,)* R, T> UnsafeIntoJsFunction<($($t,)* JsRest<'_>,), R> for T where - $($t: TryFromJsArgument + 'static,)* + $($t: for<'a> TryFromJsArgument<'a> + 'static,)* R: TryIntoJsResult, - T: FnMut($($t,)* JsRest) -> R + 'static, + T: FnMut($($t,)* JsRest<'_>) -> R + 'static, { #[allow(unused_variables)] unsafe fn into_js_function_unsafe(self, _context: &mut Context) -> NativeFunction { @@ -96,7 +96,7 @@ macro_rules! impl_into_js_function { impl<$($t,)* R, T> UnsafeIntoJsFunction<($($t,)* ContextArgToken,), R> for T where - $($t: TryFromJsArgument + 'static,)* + $($t: for<'a> TryFromJsArgument<'a> + 'static,)* R: TryIntoJsResult, T: FnMut($($t,)* &mut Context) -> R + 'static, { @@ -116,11 +116,11 @@ macro_rules! impl_into_js_function { } } - impl<$($t,)* R, T> UnsafeIntoJsFunction<($($t,)* JsRest, ContextArgToken), R> for T + impl<$($t,)* R, T> UnsafeIntoJsFunction<($($t,)* JsRest<'_>, ContextArgToken), R> for T where - $($t: TryFromJsArgument + 'static,)* + $($t: for<'a> TryFromJsArgument<'a> + 'static,)* R: TryIntoJsResult, - T: FnMut($($t,)* JsRest, &mut Context) -> R + 'static, + T: FnMut($($t,)* JsRest<'_>, &mut Context) -> R + 'static, { #[allow(unused_variables)] unsafe fn into_js_function_unsafe(self, _context: &mut Context) -> NativeFunction { @@ -141,7 +141,7 @@ macro_rules! impl_into_js_function { // Safe versions for `Fn(..) -> ...`. impl<$($t,)* R, T> IntoJsFunctionCopied<($($t,)*), R> for T where - $($t: TryFromJsArgument + 'static,)* + $($t: for<'a> TryFromJsArgument<'a> + 'static,)* R: TryIntoJsResult, T: Fn($($t,)*) -> R + 'static + Copy, { @@ -159,11 +159,11 @@ macro_rules! impl_into_js_function { } } - impl<$($t,)* R, T> IntoJsFunctionCopied<($($t,)* JsRest,), R> for T + impl<$($t,)* R, T> IntoJsFunctionCopied<($($t,)* JsRest<'_>,), R> for T where - $($t: TryFromJsArgument + 'static,)* + $($t: for<'a> TryFromJsArgument<'a> + 'static,)* R: TryIntoJsResult, - T: Fn($($t,)* JsRest) -> R + 'static + Copy, + T: Fn($($t,)* JsRest<'_>) -> R + 'static + Copy, { #[allow(unused_variables)] fn into_js_function_copied(self, _context: &mut Context) -> NativeFunction { @@ -181,7 +181,7 @@ macro_rules! impl_into_js_function { impl<$($t,)* R, T> IntoJsFunctionCopied<($($t,)* ContextArgToken,), R> for T where - $($t: TryFromJsArgument + 'static,)* + $($t: for<'a> TryFromJsArgument<'a> + 'static,)* R: TryIntoJsResult, T: Fn($($t,)* &mut Context) -> R + 'static + Copy, { @@ -199,11 +199,11 @@ macro_rules! impl_into_js_function { } } - impl<$($t,)* R, T> IntoJsFunctionCopied<($($t,)* JsRest, ContextArgToken), R> for T + impl<$($t,)* R, T> IntoJsFunctionCopied<($($t,)* JsRest<'_>, ContextArgToken), R> for T where - $($t: TryFromJsArgument + 'static,)* + $($t: for<'a> TryFromJsArgument<'a> + 'static,)* R: TryIntoJsResult, - T: Fn($($t,)* JsRest, &mut Context) -> R + 'static + Copy, + T: Fn($($t,)* JsRest<'_>, &mut Context) -> R + 'static + Copy, { #[allow(unused_variables)] fn into_js_function_copied(self, _context: &mut Context) -> NativeFunction { diff --git a/core/interop/src/lib.rs b/core/interop/src/lib.rs index 16c3beb8e7..54cfc27d54 100644 --- a/core/interop/src/lib.rs +++ b/core/interop/src/lib.rs @@ -2,7 +2,9 @@ use boa_engine::module::SyntheticModuleInitializer; use boa_engine::value::TryFromJs; -use boa_engine::{Context, JsResult, JsString, JsValue, Module, NativeFunction}; +use boa_engine::{ + Context, JsNativeError, JsResult, JsString, JsValue, Module, NativeFunction, NativeObject, +}; pub use boa_macros; pub mod loaders; @@ -116,21 +118,21 @@ pub trait IntoJsFunctionCopied: private::IntoJsFunctionSealed: Sized { /// Try to convert a JS argument into a Rust value, returning the /// value and the rest of the arguments to be parsed. /// /// # Errors /// Any parsing errors that may occur during the conversion. - fn try_from_js_argument<'a>( + fn try_from_js_argument( this: &'a JsValue, rest: &'a [JsValue], context: &mut Context, ) -> JsResult<(Self, &'a [JsValue])>; } -impl TryFromJsArgument for T { - fn try_from_js_argument<'a>( +impl<'a, T: TryFromJs> TryFromJsArgument<'a> for T { + fn try_from_js_argument( _: &'a JsValue, rest: &'a [JsValue], context: &mut Context, @@ -164,26 +166,27 @@ impl TryFromJsArgument for T { /// assert_eq!(result, JsValue::new(6)); /// ``` #[derive(Debug, Clone)] -pub struct JsRest(pub Vec); +pub struct JsRest<'a>(pub &'a [JsValue]); #[allow(unused)] -impl JsRest { +impl<'a> JsRest<'a> { /// Consumes the `JsRest` and returns the inner list of `JsValue`. #[must_use] - pub fn into_inner(self) -> Vec { + pub fn into_inner(self) -> &'a [JsValue] { self.0 } + /// Transforms the `JsRest` into a `Vec`. + #[must_use] + pub fn to_vec(self) -> Vec { + self.0.to_vec() + } + /// Returns an iterator over the inner list of `JsValue`. pub fn iter(&self) -> impl Iterator { self.0.iter() } - /// Returns a mutable iterator over the inner list of `JsValue`. - pub fn iter_mut(&mut self) -> impl Iterator { - self.0.iter_mut() - } - /// Returns the length of the inner list of `JsValue`. #[must_use] pub fn len(&self) -> usize { @@ -197,18 +200,18 @@ impl JsRest { } } -impl From<&[JsValue]> for JsRest { - fn from(values: &[JsValue]) -> Self { - Self(values.to_vec()) +impl<'a> From<&'a [JsValue]> for JsRest<'a> { + fn from(values: &'a [JsValue]) -> Self { + Self(values) } } -impl IntoIterator for JsRest { - type Item = JsValue; - type IntoIter = std::vec::IntoIter; +impl<'a> IntoIterator for JsRest<'a> { + type Item = &'a JsValue; + type IntoIter = std::slice::Iter<'a, JsValue>; fn into_iter(self) -> Self::IntoIter { - self.into_inner().into_iter() + self.into_inner().iter() } } @@ -265,8 +268,8 @@ impl JsAll { } } -impl TryFromJsArgument for JsAll { - fn try_from_js_argument<'a>( +impl<'a, T: TryFromJs> TryFromJsArgument<'a> for JsAll { + fn try_from_js_argument( _this: &'a JsValue, mut rest: &'a [JsValue], context: &mut Context, @@ -292,8 +295,8 @@ impl TryFromJsArgument for JsAll { #[derive(Debug, Clone)] pub struct JsThis(pub T); -impl TryFromJsArgument for JsThis { - fn try_from_js_argument<'a>( +impl<'a, T: TryFromJs> TryFromJsArgument<'a> for JsThis { + fn try_from_js_argument( this: &'a JsValue, rest: &'a [JsValue], context: &mut Context, @@ -302,6 +305,50 @@ impl TryFromJsArgument for JsThis { } } +/// Captures a [`ContextData`] data from the [`Context`] as a JS function argument, +/// based on its type. +/// +/// The host defined type must implement [`Clone`], otherwise the borrow +/// checker would not be able to ensure the safety of the context while +/// making the function call. Because of this, it is recommended to use +/// types that are cheap to clone. +/// +/// For example, +/// ``` +/// # use boa_engine::{Context, Finalize, JsData, JsValue, Trace}; +/// use boa_interop::{IntoJsFunctionCopied, ContextData}; +/// +/// #[derive(Clone, Debug, Finalize, JsData, Trace)] +/// struct CustomHostDefinedStruct { +/// #[unsafe_ignore_trace] +/// pub counter: usize, +/// } +/// let mut context = Context::default(); +/// context.insert_data(CustomHostDefinedStruct { counter: 123 }); +/// let f = (|ContextData(host): ContextData| { +/// host.counter + 1 +/// }).into_js_function_copied(&mut context); +/// +/// assert_eq!(f.call(&JsValue::undefined(), &[], &mut context), Ok(JsValue::new(124))); +/// ``` +#[derive(Debug, Clone)] +pub struct ContextData(pub T); + +impl<'a, T: NativeObject + Clone> TryFromJsArgument<'a> for ContextData { + fn try_from_js_argument( + _this: &'a JsValue, + rest: &'a [JsValue], + context: &mut Context, + ) -> JsResult<(Self, &'a [JsValue])> { + match context.get_data::() { + Some(value) => Ok((ContextData(value.clone()), rest)), + None => Err(JsNativeError::typ() + .with_message("Context data not found") + .into()), + } + } +} + // Implement `IntoJsFunction` for functions with a various list of // arguments. mod into_js_function_impls; @@ -310,9 +357,12 @@ mod into_js_function_impls; #[allow(clippy::missing_panics_doc)] pub fn into_js_module() { use boa_engine::{js_string, JsValue, Source}; + use boa_gc::{Gc, GcRefCell}; use std::cell::RefCell; use std::rc::Rc; + type ResultType = Gc>; + let loader = Rc::new(loaders::HashMapModuleLoader::new()); let mut context = Context::builder() .module_loader(loader.clone()) @@ -322,7 +372,9 @@ pub fn into_js_module() { let foo_count = Rc::new(RefCell::new(0)); let bar_count = Rc::new(RefCell::new(0)); let dad_count = Rc::new(RefCell::new(0)); - let result = Rc::new(RefCell::new(JsValue::undefined())); + + context.insert_data(Gc::new(GcRefCell::new(JsValue::undefined()))); + let module = unsafe { vec![ ( @@ -354,7 +406,7 @@ pub fn into_js_module() { UnsafeIntoJsFunction::into_js_function_unsafe( { let counter = dad_count.clone(); - move |args: JsRest, context: &mut Context| { + move |args: JsRest<'_>, context: &mut Context| { *counter.borrow_mut() += args .into_iter() .map(|i| i.try_js_into::(context).unwrap()) @@ -366,15 +418,10 @@ pub fn into_js_module() { ), ( js_string!("send"), - UnsafeIntoJsFunction::into_js_function_unsafe( - { - let result = result.clone(); - move |value: JsValue| { - *result.borrow_mut() = value; - } - }, - &mut context, - ), + (move |value: JsValue, ContextData(result): ContextData| { + *result.borrow_mut() = value; + }) + .into_js_function_copied(&mut context), ), ] } @@ -409,10 +456,12 @@ pub fn into_js_module() { promise_result.state() ); + let result = context.get_data::().unwrap().borrow().clone(); + assert_eq!(*foo_count.borrow(), 2); assert_eq!(*bar_count.borrow(), 15); assert_eq!(*dad_count.borrow(), 24); - assert_eq!(result.borrow().clone().try_js_into(&mut context), Ok(1u32)); + assert_eq!(result.try_js_into(&mut context), Ok(1u32)); } #[test]