Browse Source

Implement `[[HostDefined]]` field on `Realm`s (#2952)

- Improve HostDefined field to allow multiple stored types
pull/3316/head
Haled Odat 1 year ago committed by GitHub
parent
commit
822634cf41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      boa_cli/src/debug/limits.rs
  2. 28
      boa_cli/src/debug/optimizer.rs
  3. 4
      boa_engine/src/builtins/async_generator/mod.rs
  4. 2
      boa_engine/src/builtins/function/tests.rs
  5. 2
      boa_engine/src/builtins/intl/collator/mod.rs
  6. 2
      boa_engine/src/builtins/iterable/async_from_sync_iterator.rs
  7. 2
      boa_engine/src/builtins/object/mod.rs
  8. 22
      boa_engine/src/builtins/promise/mod.rs
  9. 2
      boa_engine/src/builtins/proxy/mod.rs
  10. 4
      boa_engine/src/context/mod.rs
  11. 127
      boa_engine/src/host_defined.rs
  12. 20
      boa_engine/src/job.rs
  13. 9
      boa_engine/src/lib.rs
  14. 4
      boa_engine/src/module/mod.rs
  15. 4
      boa_engine/src/module/source.rs
  16. 16
      boa_engine/src/object/builtins/jspromise.rs
  17. 75
      boa_engine/src/object/builtins/jsproxy.rs
  18. 93
      boa_engine/src/object/mod.rs
  19. 19
      boa_engine/src/realm.rs
  20. 15
      boa_engine/src/value/conversions/try_from_js.rs
  21. 4
      boa_engine/src/vm/opcode/await/mod.rs
  22. 6
      boa_engine/src/vm/opcode/call/mod.rs
  23. 2
      boa_examples/src/bin/closures.rs
  24. 135
      boa_examples/src/bin/host_defined.rs
  25. 6
      boa_examples/src/bin/jsarray.rs
  26. 2
      boa_examples/src/bin/jstypedarray.rs
  27. 4
      boa_examples/src/bin/modules.rs
  28. 2
      boa_gc/src/trace.rs
  29. 2
      boa_tester/src/exec/mod.rs

22
boa_cli/src/debug/limits.rs

@ -32,22 +32,24 @@ fn set_recursion(_: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> Js
}
pub(super) fn create_object(context: &mut Context<'_>) -> JsObject {
let get_loop = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(get_loop))
.name("get loop")
.length(0)
.build();
let set_loop = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(set_loop))
.name("set loop")
.length(1)
.build();
let get_loop =
FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_loop))
.name("get loop")
.length(0)
.build();
let set_loop =
FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_loop))
.name("set loop")
.length(1)
.build();
let get_recursion =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(get_recursion))
FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_recursion))
.name("get recursion")
.length(0)
.build();
let set_recursion =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(set_recursion))
FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_recursion))
.name("set recursion")
.length(1)
.build();

28
boa_cli/src/debug/optimizer.rs

@ -44,24 +44,28 @@ fn set_statistics(_: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> J
}
pub(super) fn create_object(context: &mut Context<'_>) -> JsObject {
let get_constant_folding =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(get_constant_folding))
.name("get constantFolding")
.length(0)
.build();
let set_constant_folding =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(set_constant_folding))
.name("set constantFolding")
.length(1)
.build();
let get_constant_folding = FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_fn_ptr(get_constant_folding),
)
.name("get constantFolding")
.length(0)
.build();
let set_constant_folding = FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_fn_ptr(set_constant_folding),
)
.name("set constantFolding")
.length(1)
.build();
let get_statistics =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(get_statistics))
FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_statistics))
.name("get statistics")
.length(0)
.build();
let set_statistics =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(set_statistics))
FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_statistics))
.name("set statistics")
.length(1)
.build();

4
boa_engine/src/builtins/async_generator/mod.rs

@ -550,7 +550,7 @@ impl AsyncGenerator {
// 7. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures generator and performs the following steps when called:
// 8. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
let on_fulfilled = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_this, args, generator, context| {
let next = {
@ -588,7 +588,7 @@ impl AsyncGenerator {
// 9. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures generator and performs the following steps when called:
// 10. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
let on_rejected = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_this, args, generator, context| {
let mut generator_borrow_mut = generator.borrow_mut();

2
boa_engine/src/builtins/function/tests.rs

@ -145,7 +145,7 @@ fn closure_capture_clone() {
.unwrap();
let func = FunctionObjectBuilder::new(
ctx,
ctx.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, _, captures, context| {
let (string, object) = &captures;

2
boa_engine/src/builtins/intl/collator/mod.rs

@ -427,7 +427,7 @@ impl Collator {
f
} else {
let bound_compare = FunctionObjectBuilder::new(
context,
context.realm(),
// 10.3.3.1. Collator Compare Functions
// https://tc39.es/ecma402/#sec-collator-compare-functions
NativeFunction::from_copy_closure_with_captures(

2
boa_engine/src/builtins/iterable/async_from_sync_iterator.rs

@ -319,7 +319,7 @@ impl AsyncFromSyncIterator {
// that captures done and performs the following steps when called:
// 9. Let onFulfilled be CreateBuiltinFunction(unwrap, 1, "", « »).
let on_fulfilled = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure(move |_this, args, context| {
// a. Return CreateIterResultObject(value, done).
Ok(create_iter_result_object(

2
boa_engine/src/builtins/object/mod.rs

@ -1281,7 +1281,7 @@ impl Object {
// 4. Let closure be a new Abstract Closure with parameters (key, value) that captures
// obj and performs the following steps when called:
let closure = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, args, obj, context| {
let key = args.get_or_undefined(0);

22
boa_engine/src/builtins/promise/mod.rs

@ -229,7 +229,7 @@ impl PromiseCapability {
// 4. Let executorClosure be a new Abstract Closure with parameters (resolve, reject) that captures promiseCapability and performs the following steps when called:
// 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »).
let executor = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_this, args: &[JsValue], captures, _| {
let mut promise_capability = captures.borrow_mut();
@ -595,7 +595,7 @@ impl Promise {
// p. Set onFulfilled.[[Capability]] to resultCapability.
// q. Set onFulfilled.[[RemainingElements]] to remainingElementsCount.
let on_fulfilled = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, args, captures, context| {
// https://tc39.es/ecma262/#sec-promise.all-resolve-element-functions
@ -820,7 +820,7 @@ impl Promise {
// q. Set onFulfilled.[[Capability]] to resultCapability.
// r. Set onFulfilled.[[RemainingElements]] to remainingElementsCount.
let on_fulfilled = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, args, captures, context| {
// https://tc39.es/ecma262/#sec-promise.allsettled-resolve-element-functions
@ -906,7 +906,7 @@ impl Promise {
// y. Set onRejected.[[Capability]] to resultCapability.
// z. Set onRejected.[[RemainingElements]] to remainingElementsCount.
let on_rejected = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, args, captures, context| {
// https://tc39.es/ecma262/#sec-promise.allsettled-reject-element-functions
@ -1149,7 +1149,7 @@ impl Promise {
// p. Set onRejected.[[Capability]] to resultCapability.
// q. Set onRejected.[[RemainingElements]] to remainingElementsCount.
let on_rejected = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, args, captures, context| {
// https://tc39.es/ecma262/#sec-promise.any-reject-element-functions
@ -1567,7 +1567,7 @@ impl Promise {
// a. Let thenFinallyClosure be a new Abstract Closure with parameters (value) that captures onFinally and C and performs the following steps when called:
let then_finally_closure = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_this, args, captures, context| {
/// Capture object for the abstract `returnValue` closure.
@ -1588,7 +1588,7 @@ impl Promise {
// iii. Let returnValue be a new Abstract Closure with no parameters that captures value and performs the following steps when called:
let return_value = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_this, _args, captures, _context| {
// 1. Return value.
@ -1618,7 +1618,7 @@ impl Promise {
// c. Let catchFinallyClosure be a new Abstract Closure with parameters (reason) that captures onFinally and C and performs the following steps when called:
let catch_finally_closure = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_this, args, captures, context| {
/// Capture object for the abstract `throwReason` closure.
@ -1639,7 +1639,7 @@ impl Promise {
// iii. Let throwReason be a new Abstract Closure with no parameters that captures reason and performs the following steps when called:
let throw_reason = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_this, _args, captures, _context| {
// 1. Return ThrowCompletion(reason).
@ -2020,7 +2020,7 @@ impl Promise {
// 3. Let lengthResolve be the number of non-optional parameters of the function definition in Promise Resolve Functions.
// 4. Let resolve be CreateBuiltinFunction(stepsResolve, lengthResolve, "", « [[Promise]], [[AlreadyResolved]] »).
let resolve = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_this, args, captures, context| {
// https://tc39.es/ecma262/#sec-promise-resolve-functions
@ -2119,7 +2119,7 @@ impl Promise {
// 8. Let lengthReject be the number of non-optional parameters of the function definition in Promise Reject Functions.
// 9. Let reject be CreateBuiltinFunction(stepsReject, lengthReject, "", « [[Promise]], [[AlreadyResolved]] »).
let reject = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_this, args, captures, context| {
// https://tc39.es/ecma262/#sec-promise-reject-functions

2
boa_engine/src/builtins/proxy/mod.rs

@ -143,7 +143,7 @@ impl Proxy {
// 3. Let revoker be ! CreateBuiltinFunction(revokerClosure, 0, "", « [[RevocableProxy]] »).
// 4. Set revoker.[[RevocableProxy]] to p.
FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, _, revocable_proxy, _| {
// a. Let F be the active function object.

4
boa_engine/src/context/mod.rs

@ -256,7 +256,7 @@ impl<'host> Context<'host> {
length: usize,
body: NativeFunction,
) -> JsResult<()> {
let function = FunctionObjectBuilder::new(self, body)
let function = FunctionObjectBuilder::new(&self.realm, body)
.name(name)
.length(length)
.constructor(true)
@ -289,7 +289,7 @@ impl<'host> Context<'host> {
length: usize,
body: NativeFunction,
) -> JsResult<()> {
let function = FunctionObjectBuilder::new(self, body)
let function = FunctionObjectBuilder::new(&self.realm, body)
.name(name)
.length(length)
.constructor(false)

127
boa_engine/src/host_defined.rs

@ -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();
}
}

20
boa_engine/src/job.rs

@ -17,7 +17,7 @@
//! [Job]: https://tc39.es/ecma262/#sec-jobs
//! [JobCallback]: https://tc39.es/ecma262/#sec-jobcallback-records
use std::{any::Any, cell::RefCell, collections::VecDeque, fmt::Debug, future::Future, pin::Pin};
use std::{cell::RefCell, collections::VecDeque, fmt::Debug, future::Future, pin::Pin};
use crate::{
object::{JsFunction, NativeObject},
@ -153,7 +153,8 @@ impl Debug for JobCallback {
impl JobCallback {
/// Creates a new `JobCallback`.
pub fn new<T: Any + Trace>(callback: JsFunction, host_defined: T) -> Self {
#[inline]
pub fn new<T: NativeObject>(callback: JsFunction, host_defined: T) -> Self {
Self {
callback,
host_defined: Box::new(host_defined),
@ -161,20 +162,23 @@ impl JobCallback {
}
/// Gets the inner callback of the job.
#[inline]
#[must_use]
pub const fn callback(&self) -> &JsFunction {
&self.callback
}
/// Gets a reference to the host defined additional field as an `Any` trait object.
/// Gets a reference to the host defined additional field as an [`NativeObject`] trait object.
#[inline]
#[must_use]
pub fn host_defined(&self) -> &dyn Any {
self.host_defined.as_any()
pub fn host_defined(&self) -> &dyn NativeObject {
&*self.host_defined
}
/// Gets a mutable reference to the host defined additional field as an `Any` trait object.
pub fn host_defined_mut(&mut self) -> &mut dyn Any {
self.host_defined.as_mut_any()
/// Gets a mutable reference to the host defined additional field as an [`NativeObject`] trait object.
#[inline]
pub fn host_defined_mut(&mut self) -> &mut dyn NativeObject {
&mut *self.host_defined
}
}

9
boa_engine/src/lib.rs

@ -147,12 +147,14 @@ pub mod realm;
pub mod script;
pub mod string;
pub mod symbol;
// pub(crate) mod tagged;
pub mod value;
pub mod vm;
mod host_defined;
mod tagged;
#[cfg(test)]
mod tests;
pub mod value;
pub mod vm;
/// A convenience module that re-exports the most commonly-used Boa APIs
pub mod prelude {
@ -175,6 +177,7 @@ pub use crate::{
bigint::JsBigInt,
context::Context,
error::{JsError, JsNativeError, JsNativeErrorKind},
host_defined::HostDefined,
module::Module,
native_function::NativeFunction,
object::JsObject,

4
boa_engine/src/module/mod.rs

@ -652,7 +652,7 @@ impl Module {
.then(
Some(
FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, _, module, context| {
module.link(context)?;
@ -669,7 +669,7 @@ impl Module {
.then(
Some(
FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, _, module, context| Ok(module.evaluate(context).into()),
self.clone(),

4
boa_engine/src/module/source.rs

@ -1267,7 +1267,7 @@ impl SourceTextModule {
// 4. Let fulfilledClosure be a new Abstract Closure with no parameters that captures module and performs the following steps when called:
// 5. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 0, "", « »).
let on_fulfilled = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, _, module, context| {
// a. Perform AsyncModuleExecutionFulfilled(module).
@ -1283,7 +1283,7 @@ impl SourceTextModule {
// 6. Let rejectedClosure be a new Abstract Closure with parameters (error) that captures module and performs the following steps when called:
// 7. Let onRejected be CreateBuiltinFunction(rejectedClosure, 0, "", « »).
let on_rejected = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, args, module, context| {
let error = JsError::from_opaque(args.get_or_undefined(0).clone());

16
boa_engine/src/object/builtins/jspromise.rs

@ -52,7 +52,7 @@ use boa_gc::{Finalize, Gc, GcRefCell, Trace};
/// .then(
/// Some(
/// FunctionObjectBuilder::new(
/// context,
/// context.realm(),
/// NativeFunction::from_fn_ptr(|_, args, _| {
/// Err(JsError::from_opaque(args.get_or_undefined(0).clone()).into())
/// }),
@ -64,7 +64,7 @@ use boa_gc::{Finalize, Gc, GcRefCell, Trace};
/// )?
/// .catch(
/// FunctionObjectBuilder::new(
/// context,
/// context.realm(),
/// NativeFunction::from_fn_ptr(|_, args, _| {
/// Ok(args.get_or_undefined(0).clone())
/// }),
@ -74,7 +74,7 @@ use boa_gc::{Finalize, Gc, GcRefCell, Trace};
/// )?
/// .finally(
/// FunctionObjectBuilder::new(
/// context,
/// context.realm(),
/// NativeFunction::from_fn_ptr(|_, _, context| {
/// context
/// .global_object()
@ -463,7 +463,7 @@ impl JsPromise {
/// )?.then(
/// Some(
/// FunctionObjectBuilder::new(
/// context,
/// context.realm(),
/// NativeFunction::from_fn_ptr(|_, args, context| {
/// args.get_or_undefined(0).to_string(context).map(JsValue::from)
/// }),
@ -529,7 +529,7 @@ impl JsPromise {
/// context,
/// )?.catch(
/// FunctionObjectBuilder::new(
/// context,
/// context.realm(),
/// NativeFunction::from_fn_ptr(|_, args, context| {
/// args.get_or_undefined(0).to_string(context).map(JsValue::from)
/// }),
@ -593,7 +593,7 @@ impl JsPromise {
/// context,
/// )?.finally(
/// FunctionObjectBuilder::new(
/// context,
/// context.realm(),
/// NativeFunction::from_fn_ptr(|_, _, context| {
/// context
/// .global_object()
@ -966,7 +966,7 @@ impl JsPromise {
let state = state.clone();
FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
move |_, args, state, _| {
finish(state, Ok(args.get_or_undefined(0).clone()));
@ -982,7 +982,7 @@ impl JsPromise {
let state = state.clone();
FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
move |_, args, state, _| {
let err = JsError::from_opaque(args.get_or_undefined(0).clone());

75
boa_engine/src/object/builtins/jsproxy.rs

@ -401,7 +401,7 @@ impl JsProxyBuilder {
let handler = JsObject::with_object_proto(context.intrinsics());
if let Some(apply) = self.apply {
let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(apply))
let f = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(apply))
.length(3)
.build();
handler
@ -409,33 +409,38 @@ impl JsProxyBuilder {
.expect("new object should be writable");
}
if let Some(construct) = self.construct {
let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(construct))
.length(3)
.build();
let f =
FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(construct))
.length(3)
.build();
handler
.create_data_property_or_throw(utf16!("construct"), f, context)
.expect("new object should be writable");
}
if let Some(define_property) = self.define_property {
let f =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(define_property))
.length(3)
.build();
let f = FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_fn_ptr(define_property),
)
.length(3)
.build();
handler
.create_data_property_or_throw(utf16!("defineProperty"), f, context)
.expect("new object should be writable");
}
if let Some(delete_property) = self.delete_property {
let f =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(delete_property))
.length(2)
.build();
let f = FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_fn_ptr(delete_property),
)
.length(2)
.build();
handler
.create_data_property_or_throw(utf16!("deleteProperty"), f, context)
.expect("new object should be writable");
}
if let Some(get) = self.get {
let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(get))
let f = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get))
.length(3)
.build();
handler
@ -444,7 +449,7 @@ impl JsProxyBuilder {
}
if let Some(get_own_property_descriptor) = self.get_own_property_descriptor {
let f = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_fn_ptr(get_own_property_descriptor),
)
.length(2)
@ -454,16 +459,18 @@ impl JsProxyBuilder {
.expect("new object should be writable");
}
if let Some(get_prototype_of) = self.get_prototype_of {
let f =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(get_prototype_of))
.length(1)
.build();
let f = FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_fn_ptr(get_prototype_of),
)
.length(1)
.build();
handler
.create_data_property_or_throw(utf16!("getPrototypeOf"), f, context)
.expect("new object should be writable");
}
if let Some(has) = self.has {
let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(has))
let f = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(has))
.length(2)
.build();
handler
@ -471,24 +478,28 @@ impl JsProxyBuilder {
.expect("new object should be writable");
}
if let Some(is_extensible) = self.is_extensible {
let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(is_extensible))
.length(1)
.build();
let f = FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_fn_ptr(is_extensible),
)
.length(1)
.build();
handler
.create_data_property_or_throw(utf16!("isExtensible"), f, context)
.expect("new object should be writable");
}
if let Some(own_keys) = self.own_keys {
let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(own_keys))
.length(1)
.build();
let f =
FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(own_keys))
.length(1)
.build();
handler
.create_data_property_or_throw(utf16!("ownKeys"), f, context)
.expect("new object should be writable");
}
if let Some(prevent_extensions) = self.prevent_extensions {
let f = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_fn_ptr(prevent_extensions),
)
.length(1)
@ -498,7 +509,7 @@ impl JsProxyBuilder {
.expect("new object should be writable");
}
if let Some(set) = self.set {
let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(set))
let f = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set))
.length(4)
.build();
handler
@ -506,10 +517,12 @@ impl JsProxyBuilder {
.expect("new object should be writable");
}
if let Some(set_prototype_of) = self.set_prototype_of {
let f =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(set_prototype_of))
.length(2)
.build();
let f = FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_fn_ptr(set_prototype_of),
)
.length(2)
.build();
handler
.create_data_property_or_throw(utf16!("setPrototypeOf"), f, context)
.expect("new object should be writable");

93
boa_engine/src/object/mod.rs

@ -60,13 +60,14 @@ use crate::{
module::ModuleNamespace,
native_function::NativeFunction,
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
string::utf16,
Context, JsBigInt, JsString, JsSymbol, JsValue,
};
use boa_gc::{custom_trace, Finalize, Trace, WeakGc};
use std::{
any::Any,
any::{Any, TypeId},
fmt::{self, Debug},
ops::{Deref, DerefMut},
};
@ -130,6 +131,75 @@ impl<T: Any + Trace> NativeObject for T {
}
}
impl dyn NativeObject {
/// Returns `true` if the inner type is the same as `T`.
#[inline]
pub fn is<T: NativeObject>(&self) -> bool {
// Get `TypeId` of the type this function is instantiated with.
let t = TypeId::of::<T>();
// Get `TypeId` of the type in the trait object (`self`).
let concrete = self.type_id();
// Compare both `TypeId`s on equality.
t == concrete
}
/// Returns some reference to the inner value if it is of type `T`, or
/// `None` if it isn't.
#[inline]
pub fn downcast_ref<T: NativeObject>(&self) -> Option<&T> {
if self.is::<T>() {
// SAFETY: just checked whether we are pointing to the correct type, and we can rely on
// that check for memory safety because we have implemented NativeObject for all types; no other
// impls can exist as they would conflict with our impl.
unsafe { Some(self.downcast_ref_unchecked()) }
} else {
None
}
}
/// Returns some mutable reference to the inner value if it is of type `T`, or
/// `None` if it isn't.
#[inline]
pub fn downcast_mut<T: NativeObject>(&mut self) -> Option<&mut T> {
if self.is::<T>() {
// SAFETY: Already checked if inner type is T, so this is safe.
unsafe { Some(self.downcast_mut_unchecked()) }
} else {
None
}
}
/// Returns a reference to the inner value as type `dyn T`.
///
/// # Safety
///
/// The contained value must be of type `T`. Calling this method
/// with the incorrect type is *undefined behavior*.
#[inline]
pub unsafe fn downcast_ref_unchecked<T: NativeObject>(&self) -> &T {
debug_assert!(self.is::<T>());
let ptr: *const dyn NativeObject = self;
// SAFETY: caller guarantees that T is the correct type
unsafe { &*ptr.cast::<T>() }
}
/// Returns a mutable reference to the inner value as type `dyn T`.
///
/// # Safety
///
/// The contained value must be of type `T`. Calling this method
/// with the incorrect type is *undefined behavior*.
#[inline]
pub unsafe fn downcast_mut_unchecked<T: NativeObject>(&mut self) -> &mut T {
debug_assert!(self.is::<T>());
// SAFETY: caller guarantees that T is the correct type
let ptr: *mut dyn NativeObject = self;
unsafe { &mut *ptr.cast::<T>() }
}
}
/// The internal representation of a JavaScript object.
#[derive(Debug, Finalize)]
pub struct Object {
@ -1992,20 +2062,21 @@ where
/// Builder for creating native function objects
#[derive(Debug)]
pub struct FunctionObjectBuilder<'ctx, 'host> {
context: &'ctx mut Context<'host>,
pub struct FunctionObjectBuilder<'realm> {
realm: &'realm Realm,
function: NativeFunction,
constructor: Option<ConstructorKind>,
name: JsString,
length: usize,
}
impl<'ctx, 'host> FunctionObjectBuilder<'ctx, 'host> {
impl<'realm> FunctionObjectBuilder<'realm> {
/// Create a new `FunctionBuilder` for creating a native function.
#[inline]
pub fn new(context: &'ctx mut Context<'host>, function: NativeFunction) -> Self {
#[must_use]
pub fn new(realm: &'realm Realm, function: NativeFunction) -> Self {
Self {
context,
realm,
function,
constructor: None,
name: js_string!(),
@ -2054,9 +2125,9 @@ impl<'ctx, 'host> FunctionObjectBuilder<'ctx, 'host> {
function: self.function,
constructor: self.constructor,
},
self.context.realm().clone(),
self.realm.clone(),
);
let object = self.context.intrinsics().templates().function().create(
let object = self.realm.intrinsics().templates().function().create(
ObjectData::function(function, self.constructor.is_some()),
vec![self.length.into(), self.name.into()],
);
@ -2123,7 +2194,7 @@ impl<'ctx, 'host> ObjectInitializer<'ctx, 'host> {
B: Into<FunctionBinding>,
{
let binding = binding.into();
let function = FunctionObjectBuilder::new(self.context, function)
let function = FunctionObjectBuilder::new(self.context.realm(), function)
.name(binding.name)
.length(length)
.constructor(false)
@ -2249,7 +2320,7 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> {
B: Into<FunctionBinding>,
{
let binding = binding.into();
let function = FunctionObjectBuilder::new(self.context, function)
let function = FunctionObjectBuilder::new(self.context.realm(), function)
.name(binding.name)
.length(length)
.constructor(false)
@ -2277,7 +2348,7 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> {
B: Into<FunctionBinding>,
{
let binding = binding.into();
let function = FunctionObjectBuilder::new(self.context, function)
let function = FunctionObjectBuilder::new(self.context.realm(), function)
.name(binding.name)
.length(length)
.constructor(false)

19
boa_engine/src/realm.rs

@ -10,8 +10,8 @@ use crate::{
context::{intrinsics::Intrinsics, HostHooks},
environments::DeclarativeEnvironment,
module::Module,
object::{shape::RootShape, JsObject},
JsString,
object::shape::RootShape,
HostDefined, JsObject, JsString,
};
use boa_gc::{Finalize, Gc, GcRefCell, Trace};
use boa_profiler::Profiler;
@ -52,10 +52,12 @@ struct Inner {
global_this: JsObject,
template_map: GcRefCell<FxHashMap<u64, JsObject>>,
loaded_modules: GcRefCell<FxHashMap<JsString, Module>>,
host_defined: HostDefined,
}
impl Realm {
/// Create a new Realm.
/// Create a new [`Realm`].
#[inline]
pub fn create(hooks: &dyn HostHooks, root_shape: &RootShape) -> Self {
let _timer = Profiler::global().start_event("Realm::create", "realm");
@ -75,6 +77,7 @@ impl Realm {
global_this,
template_map: GcRefCell::default(),
loaded_modules: GcRefCell::default(),
host_defined: HostDefined::default(),
}),
};
@ -84,11 +87,21 @@ impl Realm {
}
/// Gets the intrinsics of this `Realm`.
#[inline]
#[must_use]
pub fn intrinsics(&self) -> &Intrinsics {
&self.inner.intrinsics
}
/// Returns the [`ECMAScript specification`][spec] defined [`\[\[\HostDefined]\]`][`HostDefined`] field of the [`Realm`].
///
/// [spec]: https://tc39.es/ecma262/#table-realm-record-fields
#[inline]
#[must_use]
pub fn host_defined(&self) -> &HostDefined {
&self.inner.host_defined
}
pub(crate) fn environment(&self) -> &Gc<DeclarativeEnvironment> {
&self.inner.environment
}

15
boa_engine/src/value/conversions/try_from_js.rs

@ -210,6 +210,21 @@ impl TryFromJs for u64 {
}
}
impl TryFromJs for usize {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::Integer(i) => (*i).try_into().map_err(|e| {
JsNativeError::typ()
.with_message(format!("cannot convert value to a usize: {e}"))
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a usize")
.into()),
}
}
}
impl TryFromJs for i128 {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {

4
boa_engine/src/vm/opcode/await/mod.rs

@ -36,7 +36,7 @@ impl Operation for Await {
// 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called:
// 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
let on_fulfilled = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_this, args, captures, context| {
// a. Let prevContext be the running execution context.
@ -77,7 +77,7 @@ impl Operation for Await {
// 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the following steps when called:
// 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
let on_rejected = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_this, args, captures, context| {
// a. Let prevContext be the running execution context.

6
boa_engine/src/vm/opcode/call/mod.rs

@ -366,7 +366,7 @@ impl Operation for ImportCall {
// 4. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures promiseCapability and performs the following steps when called:
// 5. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
let on_rejected = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, args, cap, context| {
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « reason »).
@ -385,7 +385,7 @@ impl Operation for ImportCall {
// 6. Let linkAndEvaluateClosure be a new Abstract Closure with no parameters that captures module, promiseCapability, and onRejected and performs the following steps when called:
// 7. Let linkAndEvaluate be CreateBuiltinFunction(linkAndEvaluateClosure, 0, "", « »).
let link_evaluate = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, _, (module, cap, on_rejected), context| {
// a. Let link be Completion(module.Link()).
@ -406,7 +406,7 @@ impl Operation for ImportCall {
// d. Let fulfilledClosure be a new Abstract Closure with no parameters that captures module and promiseCapability and performs the following steps when called:
// e. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 0, "", « »).
let fulfill = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, _, (module, cap), context| {
// i. Let namespace be GetModuleNamespace(module).

2
boa_examples/src/bin/closures.rs

@ -71,7 +71,7 @@ fn main() -> Result<(), JsError> {
// We can use `FunctionBuilder` to define a closure with additional captures and custom property
// attributes.
let js_function = FunctionObjectBuilder::new(
&mut context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, _, captures, context| {
let mut captures = captures.borrow_mut();

135
boa_examples/src/bin/host_defined.rs

@ -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(())
}

6
boa_examples/src/bin/jsarray.rs

@ -62,7 +62,7 @@ fn main() -> JsResult<()> {
assert_eq!(&joined_array, utf16!("14::false::false::false::10"));
let filter_callback = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_fn_ptr(|_this, args, _context| {
Ok(args.get(0).cloned().unwrap_or_default().is_number().into())
}),
@ -70,7 +70,7 @@ fn main() -> JsResult<()> {
.build();
let map_callback = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_fn_ptr(|_this, args, context| {
args.get(0)
.cloned()
@ -96,7 +96,7 @@ fn main() -> JsResult<()> {
assert_eq!(&chained_array.join(None, context)?, utf16!("196,1,2,3"));
let reduce_callback = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_fn_ptr(|_this, args, context| {
let accumulator = args.get(0).cloned().unwrap_or_default();
let value = args.get(1).cloned().unwrap_or_default();

2
boa_examples/src/bin/jstypedarray.rs

@ -25,7 +25,7 @@ fn main() -> JsResult<()> {
}
let callback = FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_fn_ptr(|_this, args, context| {
let accumulator = args.get(0).cloned().unwrap_or_default();
let value = args.get(1).cloned().unwrap_or_default();

4
boa_examples/src/bin/modules.rs

@ -58,7 +58,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.then(
Some(
FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, _, module, context| {
// After loading, link all modules by resolving the imports
@ -79,7 +79,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.then(
Some(
FunctionObjectBuilder::new(
context,
context.realm(),
NativeFunction::from_copy_closure_with_captures(
// Finally, evaluate the root module.
// This returns a `JsPromise` since a module could have

2
boa_gc/src/trace.rs

@ -1,4 +1,5 @@
use std::{
any::TypeId,
borrow::{Cow, ToOwned},
cell::Cell,
collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque},
@ -143,6 +144,7 @@ simple_empty_finalize_trace![
f32,
f64,
char,
TypeId,
String,
Box<str>,
Rc<str>,

2
boa_tester/src/exec/mod.rs

@ -622,7 +622,7 @@ fn is_error_type(error: &JsError, target_type: ErrorType, context: &mut Context<
fn register_print_fn(context: &mut Context<'_>, async_result: AsyncResult) {
// We use `FunctionBuilder` to define a closure with additional captures.
let js_function = FunctionObjectBuilder::new(
context,
context.realm(),
// SAFETY: `AsyncResult` has only non-traceable captures, making this safe.
unsafe {
NativeFunction::from_closure(move |_, args, context| {

Loading…
Cancel
Save