diff --git a/boa_cli/src/debug/limits.rs b/boa_cli/src/debug/limits.rs index 5a3b59a700e..c2dee1bb073 100644 --- a/boa_cli/src/debug/limits.rs +++ b/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(); diff --git a/boa_cli/src/debug/optimizer.rs b/boa_cli/src/debug/optimizer.rs index 2580b776cd1..45c76993e65 100644 --- a/boa_cli/src/debug/optimizer.rs +++ b/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(); diff --git a/boa_engine/src/builtins/async_generator/mod.rs b/boa_engine/src/builtins/async_generator/mod.rs index 872767db957..0511e1bb6b6 100644 --- a/boa_engine/src/builtins/async_generator/mod.rs +++ b/boa_engine/src/builtins/async_generator/mod.rs @@ -570,7 +570,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 = { @@ -608,7 +608,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(); diff --git a/boa_engine/src/builtins/function/tests.rs b/boa_engine/src/builtins/function/tests.rs index d5d87131fbd..565e4af2970 100644 --- a/boa_engine/src/builtins/function/tests.rs +++ b/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; diff --git a/boa_engine/src/builtins/intl/collator/mod.rs b/boa_engine/src/builtins/intl/collator/mod.rs index af88d1cb30c..95dfc94370c 100644 --- a/boa_engine/src/builtins/intl/collator/mod.rs +++ b/boa_engine/src/builtins/intl/collator/mod.rs @@ -443,7 +443,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( diff --git a/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs b/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs index 5557b7427d3..c72d18dddcf 100644 --- a/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs +++ b/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs @@ -353,7 +353,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( diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index 4c4fc5202a1..1e0f2d51867 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -1283,7 +1283,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); diff --git a/boa_engine/src/builtins/promise/mod.rs b/boa_engine/src/builtins/promise/mod.rs index 000c8bc9b8c..8a80b3cd741 100644 --- a/boa_engine/src/builtins/promise/mod.rs +++ b/boa_engine/src/builtins/promise/mod.rs @@ -227,7 +227,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(); @@ -612,7 +612,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 @@ -856,7 +856,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 @@ -942,7 +942,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 @@ -1203,7 +1203,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 @@ -1631,7 +1631,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. @@ -1652,7 +1652,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. @@ -1682,7 +1682,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. @@ -1703,7 +1703,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). @@ -2096,7 +2096,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 @@ -2202,7 +2202,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 diff --git a/boa_engine/src/builtins/proxy/mod.rs b/boa_engine/src/builtins/proxy/mod.rs index 8e754c589ad..9324ea6713f 100644 --- a/boa_engine/src/builtins/proxy/mod.rs +++ b/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. diff --git a/boa_engine/src/context/hooks.rs b/boa_engine/src/context/hooks.rs index 34024854dee..58ae6d21992 100644 --- a/boa_engine/src/context/hooks.rs +++ b/boa_engine/src/context/hooks.rs @@ -1,14 +1,23 @@ use crate::{ builtins::promise::OperationType, job::JobCallback, - object::{JsFunction, JsObject}, + object::{JsFunction, JsObject, NativeObject}, realm::Realm, Context, JsResult, JsValue, }; +use boa_macros::{Finalize, Trace}; use chrono::{DateTime, FixedOffset, Local, LocalResult, NaiveDateTime, TimeZone, Utc}; use super::intrinsics::Intrinsics; +/// Default `[[HostDefined]]` type. +/// +/// This is also used to indicate that the `host-defined` has **not** been set. +/// +/// It's a zero-size type so `Box` doesn't allocate! +#[derive(Default, Debug, Trace, Finalize)] +pub struct DefaultHostDefined {} + /// [`Host Hooks`] customizable by the host code or engine. /// /// Every hook contains on its `Requirements` section the spec requirements @@ -172,6 +181,13 @@ pub trait HostHooks { None } + /// Creates the new [`Realm`]'s `[[HostDefined]]` field. + /// + /// By default it returns a [`DefaultHostDefined`] type. + fn create_host_defined_realm_field(&self, _intrinsics: &Intrinsics) -> Box { + Box::::default() + } + /// Gets the current UTC time of the host. /// /// Defaults to using [`Utc::now`] on all targets, which can cause panics if your platform diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 5d8551a8202..255fe0a0354 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -315,7 +315,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) @@ -348,7 +348,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) diff --git a/boa_engine/src/job.rs b/boa_engine/src/job.rs index 45d7c58a94d..4c15741d0f0 100644 --- a/boa_engine/src/job.rs +++ b/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}, @@ -142,7 +142,8 @@ impl Debug for JobCallback { impl JobCallback { /// Creates a new `JobCallback`. - pub fn new(callback: JsFunction, host_defined: T) -> Self { + #[inline] + pub fn new(callback: JsFunction, host_defined: T) -> Self { Self { callback, host_defined: Box::new(host_defined), @@ -150,18 +151,21 @@ impl JobCallback { } /// Gets the inner callback of the job. + #[inline] pub const fn callback(&self) -> &JsFunction { &self.callback } - /// Gets a reference to the host defined additional field as an `Any` trait object. - pub fn host_defined(&self) -> &dyn Any { - self.host_defined.as_any() + /// Gets a reference to the host defined additional field as an [`NativeObject`] trait object. + #[inline] + 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 } } diff --git a/boa_engine/src/module/mod.rs b/boa_engine/src/module/mod.rs index a1b25e246e3..fbe4c34d7d9 100644 --- a/boa_engine/src/module/mod.rs +++ b/boa_engine/src/module/mod.rs @@ -584,7 +584,7 @@ impl Module { .then( Some( FunctionObjectBuilder::new( - context, + context.realm(), NativeFunction::from_copy_closure_with_captures( |_, _, module, context| { module.link(context)?; @@ -601,7 +601,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(), diff --git a/boa_engine/src/module/source.rs b/boa_engine/src/module/source.rs index bc4afc52a79..49669016046 100644 --- a/boa_engine/src/module/source.rs +++ b/boa_engine/src/module/source.rs @@ -1256,7 +1256,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). @@ -1272,7 +1272,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()); diff --git a/boa_engine/src/object/builtins/jsproxy.rs b/boa_engine/src/object/builtins/jsproxy.rs index 575263562c4..b3d005c2544 100644 --- a/boa_engine/src/object/builtins/jsproxy.rs +++ b/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"); diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 74f123105ac..71c20473dfd 100644 --- a/boa_engine/src/object/mod.rs +++ b/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 NativeObject for T { } } +impl dyn NativeObject { + /// Returns `true` if the inner type is the same as `T`. + #[inline] + pub fn is(&self) -> bool { + // Get `TypeId` of the type this function is instantiated with. + let t = TypeId::of::(); + + // 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(&self) -> Option<&T> { + if self.is::() { + // 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(&mut self) -> Option<&mut T> { + if self.is::() { + // 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(&self) -> &T { + debug_assert!(self.is::()); + let ptr: *const dyn NativeObject = self; + // SAFETY: caller guarantees that T is the correct type + unsafe { &*ptr.cast::() } + } + + /// 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(&mut self) -> &mut T { + debug_assert!(self.is::()); + // SAFETY: caller guarantees that T is the correct type + let ptr: *mut dyn NativeObject = self; + unsafe { &mut *ptr.cast::() } + } +} + /// The internal representation of a JavaScript object. #[derive(Debug, Finalize)] pub struct Object { @@ -1857,20 +1927,20 @@ 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, 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 { + pub fn new(realm: &'realm Realm, function: NativeFunction) -> Self { Self { - context, + realm, function, constructor: None, name: js_string!(), @@ -1918,9 +1988,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()], ); @@ -1987,7 +2057,7 @@ impl<'ctx, 'host> ObjectInitializer<'ctx, 'host> { B: Into, { 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) @@ -2107,7 +2177,7 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> { B: Into, { 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) @@ -2135,7 +2205,7 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> { B: Into, { 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) diff --git a/boa_engine/src/realm.rs b/boa_engine/src/realm.rs index 95ff5941f38..9ce82c7322a 100644 --- a/boa_engine/src/realm.rs +++ b/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, NativeObject}, + 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>, loaded_modules: GcRefCell>, + + host_defined: Box, } 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"); @@ -67,6 +69,8 @@ impl Realm { .unwrap_or_else(|| global_object.clone()); let environment = Gc::new(DeclarativeEnvironment::global(global_this.clone())); + let host_defined = hooks.create_host_defined_realm_field(&intrinsics); + let realm = Self { inner: Gc::new(Inner { intrinsics, @@ -75,6 +79,7 @@ impl Realm { global_this, template_map: GcRefCell::default(), loaded_modules: GcRefCell::default(), + host_defined, }), }; @@ -84,10 +89,19 @@ impl Realm { } /// Gets the intrinsics of this `Realm`. + #[inline] pub fn intrinsics(&self) -> &Intrinsics { &self.inner.intrinsics } + /// Returns the `\[\[\HostDefined]\]` field of the [`Realm`]. + /// + /// See [`HostHooks::create_host_defined_realm_field()`]. + #[inline] + pub fn host_defined(&self) -> &dyn NativeObject { + &*self.inner.host_defined + } + pub(crate) fn environment(&self) -> &Gc { &self.inner.environment } diff --git a/boa_engine/src/value/conversions/try_from_js.rs b/boa_engine/src/value/conversions/try_from_js.rs index bfd44936cbc..975881d4a74 100644 --- a/boa_engine/src/value/conversions/try_from_js.rs +++ b/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 { + 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 { match value { diff --git a/boa_engine/src/vm/opcode/await_stm/mod.rs b/boa_engine/src/vm/opcode/await_stm/mod.rs index 6296da9294a..17b11dda2ce 100644 --- a/boa_engine/src/vm/opcode/await_stm/mod.rs +++ b/boa_engine/src/vm/opcode/await_stm/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. diff --git a/boa_examples/src/bin/closures.rs b/boa_examples/src/bin/closures.rs index eb4b89705f8..bd158e0bc19 100644 --- a/boa_examples/src/bin/closures.rs +++ b/boa_examples/src/bin/closures.rs @@ -74,7 +74,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(); diff --git a/boa_examples/src/bin/host_defined.rs b/boa_examples/src/bin/host_defined.rs new file mode 100644 index 00000000000..eb94964977b --- /dev/null +++ b/boa_examples/src/bin/host_defined.rs @@ -0,0 +1,91 @@ +// This example goes into the details on how to pass closures as functions inside Rust and call them +// from Javascript. + +use std::cell::Cell; + +use boa_engine::{ + context::{intrinsics::Intrinsics, HostHooks}, + native_function::NativeFunction, + object::{FunctionObjectBuilder, NativeObject}, + property::Attribute, + 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: Cell, +} + +/// Create a custom [`HostHooks`]. +#[derive(Default)] +struct CustomHostHooks {} + +impl HostHooks for CustomHostHooks { + // Override default implementation for creating the `[[HostDefined]]` field on realms. + fn create_host_defined_realm_field(&self, _intrinsics: &Intrinsics) -> Box { + Box::::default() + } +} + +fn main() -> Result<(), JsError> { + let host_hooks: &dyn HostHooks = &CustomHostHooks::default(); + + // We create a new `Context` to create a new Javascript executor with the custom HostHooks. + let mut context = Context::builder().host_hooks(host_hooks).build()?; + + // Get the realm from the context. + let realm = context.realm().clone(); + + // Get the host define field from the realm and downcast it to our concrete type. + let Some(host_defined) = realm.host_defined().and_then(::downcast_ref::) else { + return Err(JsNativeError::typ().with_message("Realm does not HostDefined field").into()); + }; + + // Assert that the initial state. + assert_eq!(host_defined.counter.get(), 0); + + // Create and register function for setting 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 realm = context.realm(); + let Some(host_defined) = realm.host_defined().downcast_ref::() else { + return Err(JsNativeError::typ().with_message("Realm does not HostDefined field").into()); + }; + + host_defined.counter.set(value); + + Ok(value.into()) + }) + )?; + + context.register_global_builtin_callable( + "getRealmValue", 0, NativeFunction::from_fn_ptr(|_, _, context| { + let realm = context.realm(); + let Some(host_defined) = realm.host_defined().downcast_ref::() else { + return Err(JsNativeError::typ().with_message("Realm does not HostDefined field").into()); + }; + + Ok(host_defined.counter.get().into()) + }) + )?; + + // Run code in JavaScript that mutates the host-defined field on the Realm. + context.eval_script(Source::from_bytes( + r" + setRealmValue(50); + setRealmValue(getRealmValue() * 2); + ", + ))?; + + // Assert that the host-defined field changed. + assert_eq!(host_defined.counter.get(), 100); + + Ok(()) +} diff --git a/boa_examples/src/bin/jsarray.rs b/boa_examples/src/bin/jsarray.rs index 7088d18d83a..8d8578d4172 100644 --- a/boa_examples/src/bin/jsarray.rs +++ b/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(); diff --git a/boa_examples/src/bin/jstypedarray.rs b/boa_examples/src/bin/jstypedarray.rs index e5c31611c23..1da60569207 100644 --- a/boa_examples/src/bin/jstypedarray.rs +++ b/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(); diff --git a/boa_examples/src/bin/modules.rs b/boa_examples/src/bin/modules.rs index f1ea129e80c..b7fda87d45b 100644 --- a/boa_examples/src/bin/modules.rs +++ b/boa_examples/src/bin/modules.rs @@ -57,7 +57,7 @@ fn main() -> Result<(), Box> { .then( Some( FunctionObjectBuilder::new( - context, + context.realm(), NativeFunction::from_copy_closure_with_captures( |_, _, module, context| { // After loading, link all modules by resolving the imports @@ -78,7 +78,7 @@ fn main() -> Result<(), Box> { .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 diff --git a/boa_tester/src/exec/mod.rs b/boa_tester/src/exec/mod.rs index 1b065267168..77efd1626ee 100644 --- a/boa_tester/src/exec/mod.rs +++ b/boa_tester/src/exec/mod.rs @@ -608,7 +608,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| {