Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[[HostDefined]] Refactor #3370

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 35 additions & 54 deletions boa_engine/src/host_defined.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,97 +6,78 @@ 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>,
env: FxHashMap<TypeId, GcRefCell<Box<dyn NativeObject>>>,
}

#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn downcast_boxed_native_object_unchecked<T: NativeObject>(
obj: Box<dyn NativeObject>,
) -> Box<T> {
let raw: *mut dyn NativeObject = Box::into_raw(obj);
Box::from_raw(raw as *mut T)
}

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())
pub fn insert_default<T: NativeObject + Default>(&mut self) -> Option<Box<T>> {
self.env
.insert(TypeId::of::<T>(), GcRefCell::new(Box::new(T::default())))
.map(|obj| unsafe { downcast_boxed_native_object_unchecked(obj.into_inner()) })
}

/// 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))
pub fn insert<T: NativeObject>(&mut self, value: T) -> Option<Box<T>> {
self.env
.insert(TypeId::of::<T>(), GcRefCell::new(Box::new(value)))
.map(|obj| unsafe { downcast_boxed_native_object_unchecked(obj.into_inner()) })
}

/// 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>())
self.env.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>())
pub fn remove<T: NativeObject>(&mut self) -> Option<Box<T>> {
self.env
.remove(&TypeId::of::<T>())
.map(|obj| unsafe { downcast_boxed_native_object_unchecked(obj.into_inner()) })
}

/// Get type T from [`HostDefined`], if it exits.
/// Get type T from [`HostDefined`], if it exists.
///
/// # Panics
///
/// Panics if [`HostDefined`] field is borrowed.
/// Panics if T's entry in [`HostDefined`] is mutably borrowed.
#[track_caller]
pub fn get<T: NativeObject>(&self) -> Option<GcRef<'_, T>> {
GcRef::try_map(self.state.borrow(), |state| {
state
.get(&TypeId::of::<T>())
.map(Box::as_ref)
.and_then(<dyn NativeObject>::downcast_ref::<T>)
})
let entry = self.env.get(&TypeId::of::<T>())?;

GcRef::try_map(entry.borrow(), |obj| obj.as_ref().downcast_ref::<T>())
}

/// Get type T from [`HostDefined`], if it exits.
/// Get type T from [`HostDefined`], if it exists.
///
/// # Panics
///
/// Panics if [`HostDefined`] field is borrowed.
/// Panics if T's entry in [`HostDefined`] is borrowed.
#[track_caller]
pub fn get_mut<T: NativeObject>(&self) -> Option<GcRefMut<'_, HostDefinedMap, T>> {
GcRefMut::try_map(
self.state.borrow_mut(),
|state: &mut FxHashMap<TypeId, Box<dyn NativeObject>>| {
state
.get_mut(&TypeId::of::<T>())
.map(Box::as_mut)
.and_then(<dyn NativeObject>::downcast_mut::<T>)
},
)
pub fn get_mut<T: NativeObject>(&self) -> Option<GcRefMut<'_, Box<dyn NativeObject>, T>> {
let entry = self.env.get(&TypeId::of::<T>())?;

GcRefMut::try_map(entry.borrow_mut(), |obj| obj.as_mut().downcast_mut::<T>())
}

/// Clears all the objects.
Expand All @@ -105,7 +86,7 @@ impl HostDefined {
///
/// Panics if [`HostDefined`] field is borrowed.
#[track_caller]
pub fn clear(&self) {
self.state.borrow_mut().clear();
pub fn clear(&mut self) {
self.env.clear();
}
}
6 changes: 3 additions & 3 deletions boa_engine/src/realm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ struct Inner {
loaded_modules: GcRefCell<FxHashMap<JsString, Module>>,
host_classes: GcRefCell<FxHashMap<TypeId, StandardConstructor>>,

host_defined: HostDefined,
host_defined: GcRefCell<HostDefined>,
}

impl Realm {
Expand All @@ -86,7 +86,7 @@ impl Realm {
template_map: GcRefCell::default(),
loaded_modules: GcRefCell::default(),
host_classes: GcRefCell::default(),
host_defined: HostDefined::default(),
host_defined: GcRefCell::default(),
}),
};

Expand All @@ -107,7 +107,7 @@ impl Realm {
/// [spec]: https://tc39.es/ecma262/#table-realm-record-fields
#[inline]
#[must_use]
pub fn host_defined(&self) -> &HostDefined {
pub fn host_defined(&self) -> &GcRefCell<HostDefined> {
&self.inner.host_defined
}

Expand Down
40 changes: 23 additions & 17 deletions boa_examples/src/bin/host_defined.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,51 +32,54 @@ fn main() -> Result<(), JsError> {
// Get the realm from the context.
let realm = context.realm().clone();

// Get the [[HostDefined]] field from the realm.
let mut host_defined = realm.host_defined().borrow_mut();

// Insert a default CustomHostDefinedStruct.
realm
.host_defined()
.insert_default::<CustomHostDefinedStruct>();
host_defined.insert_default::<CustomHostDefinedStruct>();

{
assert!(realm.host_defined().has::<CustomHostDefinedStruct>());
assert!(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 {
let Some(custom_host_defined_struct) = 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);
assert_eq!(custom_host_defined_struct.counter, 0);
}

// Insert another struct with state into [[HostDefined]] field.
realm
.host_defined()
.insert(AnotherCustomHostDefinedStruct::new(10));
host_defined.insert(AnotherCustomHostDefinedStruct::new(10));

{
assert!(realm.host_defined().has::<AnotherCustomHostDefinedStruct>());
assert!(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>()
let Some(another_custom_host_defined_struct) =
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);
assert_eq!(another_custom_host_defined_struct.counter, 10);
}

// Remove a type from the [[HostDefined]] field.
assert!(realm
.host_defined()
assert!(host_defined
.remove::<AnotherCustomHostDefinedStruct>()
.is_some());

// We need to explicitly drop the borrow of the [[HostDefined]] field
// to avoid a double borrow when running the JavaScript code.
drop(host_defined);

// 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.
Expand All @@ -86,7 +89,7 @@ fn main() -> Result<(), JsError> {
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 host_defined = context.realm().host_defined().borrow_mut();
let Some(mut host_defined) = host_defined.get_mut::<CustomHostDefinedStruct>() else {
return Err(JsNativeError::typ()
.with_message("Realm does not have HostDefined field")
Expand All @@ -103,7 +106,7 @@ fn main() -> Result<(), JsError> {
js_string!("getRealmValue"),
0,
NativeFunction::from_fn_ptr(|_, _, context| {
let host_defined = context.realm().host_defined();
let host_defined = context.realm().host_defined().borrow_mut();
let Some(host_defined) = host_defined.get::<CustomHostDefinedStruct>() else {
return Err(JsNativeError::typ()
.with_message("Realm does not have HostDefined field")
Expand All @@ -122,7 +125,10 @@ fn main() -> Result<(), JsError> {
",
))?;

let Some(host_defined) = realm.host_defined().get::<CustomHostDefinedStruct>() else {
// Get the [[HostDefined]] field from the realm
let host_defined = realm.host_defined().borrow();

let Some(host_defined) = host_defined.get::<CustomHostDefinedStruct>() else {
return Err(JsNativeError::typ()
.with_message("Realm does not have HostDefined field")
.into());
Expand Down
Loading