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