mirror of https://github.com/boa-dev/boa.git
Haled Odat
1 year ago
committed by
GitHub
29 changed files with 512 additions and 123 deletions
@ -0,0 +1,127 @@ |
|||||||
|
use std::any::TypeId; |
||||||
|
|
||||||
|
use boa_gc::{GcRef, GcRefCell, GcRefMut}; |
||||||
|
use boa_macros::{Finalize, Trace}; |
||||||
|
use rustc_hash::FxHashMap; |
||||||
|
|
||||||
|
use crate::object::NativeObject; |
||||||
|
|
||||||
|
/// Map used to store the host defined objects.
|
||||||
|
#[doc(hidden)] |
||||||
|
type HostDefinedMap = FxHashMap<TypeId, Box<dyn NativeObject>>; |
||||||
|
|
||||||
|
/// This represents a `ECMASCript` specification \[`HostDefined`\] field.
|
||||||
|
///
|
||||||
|
/// This allows storing types which are mapped by their [`TypeId`].
|
||||||
|
#[derive(Default, Trace, Finalize)] |
||||||
|
#[allow(missing_debug_implementations)] |
||||||
|
pub struct HostDefined { |
||||||
|
state: GcRefCell<HostDefinedMap>, |
||||||
|
} |
||||||
|
|
||||||
|
impl HostDefined { |
||||||
|
/// Insert a type into the [`HostDefined`].
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if [`HostDefined`] field is borrowed.
|
||||||
|
#[track_caller] |
||||||
|
pub fn insert_default<T: NativeObject + Default>(&self) -> Option<Box<dyn NativeObject>> { |
||||||
|
self.state |
||||||
|
.borrow_mut() |
||||||
|
.insert(TypeId::of::<T>(), Box::<T>::default()) |
||||||
|
} |
||||||
|
|
||||||
|
/// Insert a type into the [`HostDefined`].
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if [`HostDefined`] field is borrowed.
|
||||||
|
#[track_caller] |
||||||
|
pub fn insert<T: NativeObject>(&self, value: T) -> Option<Box<dyn NativeObject>> { |
||||||
|
self.state |
||||||
|
.borrow_mut() |
||||||
|
.insert(TypeId::of::<T>(), Box::new(value)) |
||||||
|
} |
||||||
|
|
||||||
|
/// Check if the [`HostDefined`] has type T.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if [`HostDefined`] field is borrowed mutably.
|
||||||
|
#[track_caller] |
||||||
|
pub fn has<T: NativeObject>(&self) -> bool { |
||||||
|
self.state.borrow().contains_key(&TypeId::of::<T>()) |
||||||
|
} |
||||||
|
|
||||||
|
/// Remove type T from [`HostDefined`], if it exists.
|
||||||
|
///
|
||||||
|
/// Returns [`Some`] with the object if it exits, [`None`] otherwise.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if [`HostDefined`] field is borrowed.
|
||||||
|
#[track_caller] |
||||||
|
pub fn remove<T: NativeObject>(&self) -> Option<Box<dyn NativeObject>> { |
||||||
|
self.state.borrow_mut().remove(&TypeId::of::<T>()) |
||||||
|
} |
||||||
|
|
||||||
|
/// Get type T from [`HostDefined`], if it exits.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if [`HostDefined`] field is borrowed.
|
||||||
|
#[track_caller] |
||||||
|
pub fn get<T: NativeObject>(&self) -> Option<GcRef<'_, T>> { |
||||||
|
let state = self.state.borrow(); |
||||||
|
|
||||||
|
state |
||||||
|
.get(&TypeId::of::<T>()) |
||||||
|
.map(Box::as_ref) |
||||||
|
.and_then(<dyn NativeObject>::downcast_ref::<T>)?; |
||||||
|
|
||||||
|
Some(GcRef::map(state, |state| { |
||||||
|
state |
||||||
|
.get(&TypeId::of::<T>()) |
||||||
|
.map(Box::as_ref) |
||||||
|
.and_then(<dyn NativeObject>::downcast_ref::<T>) |
||||||
|
.expect("Should not fail") |
||||||
|
})) |
||||||
|
} |
||||||
|
|
||||||
|
/// Get type T from [`HostDefined`], if it exits.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if [`HostDefined`] field is borrowed.
|
||||||
|
#[track_caller] |
||||||
|
pub fn get_mut<T: NativeObject>(&self) -> Option<GcRefMut<'_, HostDefinedMap, T>> { |
||||||
|
let mut state = self.state.borrow_mut(); |
||||||
|
|
||||||
|
state |
||||||
|
.get_mut(&TypeId::of::<T>()) |
||||||
|
.map(Box::as_mut) |
||||||
|
.and_then(<dyn NativeObject>::downcast_mut::<T>)?; |
||||||
|
|
||||||
|
Some(GcRefMut::map( |
||||||
|
state, |
||||||
|
|state: &mut FxHashMap<TypeId, Box<dyn NativeObject>>| { |
||||||
|
state |
||||||
|
.get_mut(&TypeId::of::<T>()) |
||||||
|
.map(Box::as_mut) |
||||||
|
.and_then(<dyn NativeObject>::downcast_mut::<T>) |
||||||
|
.expect("Should not fail") |
||||||
|
}, |
||||||
|
)) |
||||||
|
} |
||||||
|
|
||||||
|
/// Clears all the objects.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if [`HostDefined`] field is borrowed.
|
||||||
|
#[track_caller] |
||||||
|
pub fn clear(&self) { |
||||||
|
self.state.borrow_mut().clear(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,135 @@ |
|||||||
|
// This example goes into the details on how to store user defined structs/state that is shared.
|
||||||
|
|
||||||
|
use boa_engine::{ |
||||||
|
native_function::NativeFunction, Context, JsArgs, JsError, JsNativeError, Source, |
||||||
|
}; |
||||||
|
use boa_gc::{Finalize, Trace}; |
||||||
|
|
||||||
|
/// Custom host-defined struct that has some state, and can be shared between JavaScript and rust.
|
||||||
|
#[derive(Default, Trace, Finalize)] |
||||||
|
struct CustomHostDefinedStruct { |
||||||
|
#[unsafe_ignore_trace] |
||||||
|
counter: usize, |
||||||
|
} |
||||||
|
|
||||||
|
/// Custom host-defined struct that has some state, and can be shared between JavaScript and rust.
|
||||||
|
#[derive(Trace, Finalize)] |
||||||
|
struct AnotherCustomHostDefinedStruct { |
||||||
|
#[unsafe_ignore_trace] |
||||||
|
counter: usize, |
||||||
|
} |
||||||
|
|
||||||
|
impl AnotherCustomHostDefinedStruct { |
||||||
|
fn new(value: usize) -> Self { |
||||||
|
Self { counter: value } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn main() -> Result<(), JsError> { |
||||||
|
// We create a new `Context` to create a new Javascript executor..
|
||||||
|
let mut context = Context::default(); |
||||||
|
|
||||||
|
// Get the realm from the context.
|
||||||
|
let realm = context.realm().clone(); |
||||||
|
|
||||||
|
// Insert a default CustomHostDefinedStruct.
|
||||||
|
realm |
||||||
|
.host_defined() |
||||||
|
.insert_default::<CustomHostDefinedStruct>(); |
||||||
|
|
||||||
|
{ |
||||||
|
assert!(realm.host_defined().has::<CustomHostDefinedStruct>()); |
||||||
|
|
||||||
|
// Get the [[HostDefined]] field from the realm and downcast it to our concrete type.
|
||||||
|
let Some(host_defined) = realm.host_defined().get::<CustomHostDefinedStruct>() else { |
||||||
|
return Err(JsNativeError::typ() |
||||||
|
.with_message("Realm does not have HostDefined field") |
||||||
|
.into()); |
||||||
|
}; |
||||||
|
|
||||||
|
// Assert that the [[HostDefined]] field is in it's initial state.
|
||||||
|
assert_eq!(host_defined.counter, 0); |
||||||
|
} |
||||||
|
|
||||||
|
// Insert another struct with state into [[HostDefined]] field.
|
||||||
|
realm |
||||||
|
.host_defined() |
||||||
|
.insert(AnotherCustomHostDefinedStruct::new(10)); |
||||||
|
|
||||||
|
{ |
||||||
|
assert!(realm.host_defined().has::<AnotherCustomHostDefinedStruct>()); |
||||||
|
|
||||||
|
// Get the [[HostDefined]] field from the realm and downcast it to our concrete type.
|
||||||
|
let Some(host_defined) = realm.host_defined().get::<AnotherCustomHostDefinedStruct>() |
||||||
|
else { |
||||||
|
return Err(JsNativeError::typ() |
||||||
|
.with_message("Realm does not have HostDefined field") |
||||||
|
.into()); |
||||||
|
}; |
||||||
|
|
||||||
|
// Assert that the [[HostDefined]] field is in it's initial state.
|
||||||
|
assert_eq!(host_defined.counter, 10); |
||||||
|
} |
||||||
|
|
||||||
|
// Remove a type from the [[HostDefined]] field.
|
||||||
|
assert!(realm |
||||||
|
.host_defined() |
||||||
|
.remove::<AnotherCustomHostDefinedStruct>() |
||||||
|
.is_some()); |
||||||
|
|
||||||
|
// Create and register function for getting and setting the realm value.
|
||||||
|
//
|
||||||
|
// The funtion lives in the context's realm and has access to the host-defined field.
|
||||||
|
context.register_global_builtin_callable( |
||||||
|
"setRealmValue", |
||||||
|
1, |
||||||
|
NativeFunction::from_fn_ptr(|_, args, context| { |
||||||
|
let value: usize = args.get_or_undefined(0).try_js_into(context)?; |
||||||
|
|
||||||
|
let host_defined = context.realm().host_defined(); |
||||||
|
let Some(mut host_defined) = host_defined.get_mut::<CustomHostDefinedStruct>() else { |
||||||
|
return Err(JsNativeError::typ() |
||||||
|
.with_message("Realm does not have HostDefined field") |
||||||
|
.into()); |
||||||
|
}; |
||||||
|
|
||||||
|
host_defined.counter = value; |
||||||
|
|
||||||
|
Ok(value.into()) |
||||||
|
}), |
||||||
|
)?; |
||||||
|
|
||||||
|
context.register_global_builtin_callable( |
||||||
|
"getRealmValue", |
||||||
|
0, |
||||||
|
NativeFunction::from_fn_ptr(|_, _, context| { |
||||||
|
let host_defined = context.realm().host_defined(); |
||||||
|
let Some(host_defined) = host_defined.get::<CustomHostDefinedStruct>() else { |
||||||
|
return Err(JsNativeError::typ() |
||||||
|
.with_message("Realm does not have HostDefined field") |
||||||
|
.into()); |
||||||
|
}; |
||||||
|
|
||||||
|
Ok(host_defined.counter.into()) |
||||||
|
}), |
||||||
|
)?; |
||||||
|
|
||||||
|
// Run code in JavaScript that mutates the host-defined field on the Realm.
|
||||||
|
context.eval(Source::from_bytes( |
||||||
|
r" |
||||||
|
setRealmValue(50); |
||||||
|
setRealmValue(getRealmValue() * 2); |
||||||
|
", |
||||||
|
))?; |
||||||
|
|
||||||
|
let Some(host_defined) = realm.host_defined().get::<CustomHostDefinedStruct>() else { |
||||||
|
return Err(JsNativeError::typ() |
||||||
|
.with_message("Realm does not have HostDefined field") |
||||||
|
.into()); |
||||||
|
}; |
||||||
|
|
||||||
|
// Assert that the host-defined field changed.
|
||||||
|
assert_eq!(host_defined.counter, 100); |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
Loading…
Reference in new issue