diff --git a/core/engine/src/builtins/builder.rs b/core/engine/src/builtins/builder.rs index 459a5104d9a..e135f36ec00 100644 --- a/core/engine/src/builtins/builder.rs +++ b/core/engine/src/builtins/builder.rs @@ -5,7 +5,7 @@ use crate::{ native_function::{NativeFunctionObject, NativeFunctionPointer}, object::{ shape::{property_table::PropertyTableInner, slot::SlotAttributes}, - FunctionBinding, JsFunction, JsPrototype, CONSTRUCTOR, PROTOTYPE, + FunctionBinding, JsFunction, JsPrototype, LazyBuiltIn, CONSTRUCTOR, PROTOTYPE, }, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, @@ -391,12 +391,25 @@ impl BuiltInConstructorWithPrototype<'_> { } let mut object = self.object.borrow_mut(); - let function = object - .downcast_mut::() - .expect("Builtin must be a function object"); - function.f = NativeFunction::from_fn_ptr(self.function); - function.constructor = Some(ConstructorKind::Base); - function.realm = Some(self.realm.clone()); + if object.is::() { + let function = object + .downcast_mut::() + .expect("Builtin must be a function object"); + function.f = NativeFunction::from_fn_ptr(self.function); + function.constructor = Some(ConstructorKind::Base); + function.realm = Some(self.realm.clone()); + } else if object.is::() { + let lazy = object + .downcast_mut::() + .expect("Builtin must be a lazy object"); + lazy.set_constructor( + NativeFunction::from_fn_ptr(self.function), + self.realm.clone(), + ); + } else { + unreachable!("The object must be a function or a lazy object"); + } + object .properties_mut() .shape diff --git a/core/engine/src/builtins/function/mod.rs b/core/engine/src/builtins/function/mod.rs index 8c4967e5644..49787df0a02 100644 --- a/core/engine/src/builtins/function/mod.rs +++ b/core/engine/src/builtins/function/mod.rs @@ -26,7 +26,7 @@ use crate::{ get_prototype_from_constructor, CallValue, InternalObjectMethods, ORDINARY_INTERNAL_METHODS, }, - JsData, JsFunction, JsObject, PrivateElement, PrivateName, + JsData, JsFunction, JsObject, LazyBuiltIn, PrivateElement, PrivateName, }, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, @@ -843,7 +843,7 @@ impl BuiltInFunctionObject { }; let object_borrow = object.borrow(); - if object_borrow.is::() { + if object_borrow.is::() || object_borrow.is::() { let name = { // Is there a case here where if there is no name field on a value // name should default to None? Do all functions have names set? diff --git a/core/engine/src/builtins/mod.rs b/core/engine/src/builtins/mod.rs index b9a738f059e..844b4aa58bc 100644 --- a/core/engine/src/builtins/mod.rs +++ b/core/engine/src/builtins/mod.rs @@ -201,16 +201,15 @@ impl Realm { AsyncIterator::init(self); AsyncFromSyncIterator::init(self); ForInIterator::init(self); - Math::init(self); + // Math::init(self); Json::init(self); - Array::init(self); ArrayIterator::init(self); Proxy::init(self); ArrayBuffer::init(self); SharedArrayBuffer::init(self); BigInt::init(self); Boolean::init(self); - Date::init(self); + // Date::init(self); DataView::init(self); Map::init(self); MapIterator::init(self); diff --git a/core/engine/src/context/intrinsics.rs b/core/engine/src/context/intrinsics.rs index a0d8519a7f7..4854adfe34c 100644 --- a/core/engine/src/context/intrinsics.rs +++ b/core/engine/src/context/intrinsics.rs @@ -1,10 +1,13 @@ //! Data structures that contain intrinsic objects and constructors. -use boa_gc::{Finalize, Trace}; +use boa_gc::{Finalize, Trace, WeakGc}; use boa_macros::js_str; use crate::{ - builtins::{iterable::IteratorPrototypes, uri::UriFunctions, Array, OrdinaryObject}, + builtins::{ + iterable::IteratorPrototypes, uri::UriFunctions, Array, Date, IntrinsicObject, Math, + OrdinaryObject, + }, js_string, object::{ internal_methods::immutable_prototype::IMMUTABLE_PROTOTYPE_EXOTIC_INTERNAL_METHODS, @@ -12,6 +15,7 @@ use crate::{ JsFunction, JsObject, Object, CONSTRUCTOR, PROTOTYPE, }, property::{Attribute, PropertyKey}, + realm::{Realm, RealmInner}, JsSymbol, }; @@ -40,13 +44,13 @@ impl Intrinsics { /// To initialize all the intrinsics with their spec properties, see [`Realm::initialize`]. /// /// [`Realm::initialize`]: crate::realm::Realm::initialize - pub(crate) fn uninit(root_shape: &RootShape) -> Option { - let constructors = StandardConstructors::default(); + pub(crate) fn uninit(root_shape: &RootShape, realm_inner: &WeakGc) -> Option { + let constructors = StandardConstructors::new(realm_inner); let templates = ObjectTemplates::new(root_shape, &constructors); Some(Self { constructors, - objects: IntrinsicObjects::uninit()?, + objects: IntrinsicObjects::uninit(realm_inner)?, templates, }) } @@ -95,6 +99,14 @@ impl StandardConstructor { } } + /// Similar to `with_prototype`, but the prototype is lazily initialized. + fn lazy(init: fn(&Realm) -> (), realm_inner: WeakGc) -> Self { + Self { + constructor: JsFunction::lazy_intrinsic_function(true, init, realm_inner), + prototype: JsObject::default(), + } + } + /// Build a constructor with a defined prototype. fn with_prototype(prototype: JsObject) -> Self { Self { @@ -203,8 +215,8 @@ pub struct StandardConstructors { calendar: StandardConstructor, } -impl Default for StandardConstructors { - fn default() -> Self { +impl StandardConstructors { + fn new(realm_inner: &WeakGc) -> Self { Self { object: StandardConstructor::with_prototype(JsObject::from_object_and_vtable( Object::::default(), @@ -212,14 +224,14 @@ impl Default for StandardConstructors { )), async_generator_function: StandardConstructor::default(), proxy: StandardConstructor::default(), - date: StandardConstructor::default(), + date: StandardConstructor::lazy(Date::init, realm_inner.clone()), function: StandardConstructor { constructor: JsFunction::empty_intrinsic_function(true), prototype: JsFunction::empty_intrinsic_function(false).into(), }, async_function: StandardConstructor::default(), generator_function: StandardConstructor::default(), - array: StandardConstructor::with_prototype(JsObject::from_proto_and_data(None, Array)), + array: StandardConstructor::lazy(Array::init, realm_inner.clone()), bigint: StandardConstructor::default(), number: StandardConstructor::with_prototype(JsObject::from_proto_and_data(None, 0.0)), boolean: StandardConstructor::with_prototype(JsObject::from_proto_and_data( @@ -1120,10 +1132,10 @@ impl IntrinsicObjects { /// /// [`Realm::initialize`]: crate::realm::Realm::initialize #[allow(clippy::unnecessary_wraps)] - pub(crate) fn uninit() -> Option { + pub(crate) fn uninit(realm_inner: &WeakGc) -> Option { Some(Self { reflect: JsObject::default(), - math: JsObject::default(), + math: JsObject::lazy(Math::init, realm_inner), json: JsObject::default(), throw_type_error: JsFunction::empty_intrinsic_function(false), array_prototype_values: JsFunction::empty_intrinsic_function(false), diff --git a/core/engine/src/object/builtins/jsfunction.rs b/core/engine/src/object/builtins/jsfunction.rs index 16ff8fdd898..9c0b3f8e470 100644 --- a/core/engine/src/object/builtins/jsfunction.rs +++ b/core/engine/src/object/builtins/jsfunction.rs @@ -1,12 +1,15 @@ //! A Rust API wrapper for Boa's `Function` Builtin ECMAScript Object +use crate::realm::{Realm, RealmInner}; use crate::{ builtins::function::ConstructorKind, native_function::NativeFunctionObject, object::JsObject, value::TryFromJs, Context, JsNativeError, JsResult, JsValue, NativeFunction, TryIntoJsResult, }; -use boa_gc::{Finalize, Trace}; +use boa_gc::{Finalize, Trace, WeakGc}; use std::marker::PhantomData; use std::ops::Deref; +use super::lazy_builtin::{BuiltinKind, LazyBuiltIn}; + /// A trait for converting a tuple of Rust values into a vector of `JsValue`, /// to be used as arguments for a JavaScript function. pub trait TryIntoJsArguments { @@ -135,6 +138,28 @@ impl JsFunction { } } + /// Creates a new, lazy intrinsic functionobject with only its function internal methods set. + /// When the function is accessed it will call init from the procided init function + pub(crate) fn lazy_intrinsic_function( + constructor: bool, + init: fn(&Realm), + realm_inner: WeakGc, + ) -> Self { + Self { + inner: JsObject::from_proto_and_data( + None, + LazyBuiltIn { + init_and_realm: Some((init, realm_inner)), + kind: BuiltinKind::Function(NativeFunctionObject { + f: NativeFunction::from_fn_ptr(|_, _, _| Ok(JsValue::undefined())), + constructor: constructor.then_some(ConstructorKind::Base), + realm: None, + }), + }, + ), + } + } + /// Creates a [`JsFunction`] from a [`JsObject`], or returns `None` if the object is not a function. /// /// This does not clone the fields of the function, it only does a shallow clone of the object. diff --git a/core/engine/src/object/builtins/lazy_builtin.rs b/core/engine/src/object/builtins/lazy_builtin.rs new file mode 100644 index 00000000000..3c1de85dfac --- /dev/null +++ b/core/engine/src/object/builtins/lazy_builtin.rs @@ -0,0 +1,453 @@ +use crate::{ + builtins::function::ConstructorKind, + gc::custom_trace, + native_function::NativeFunctionObject, + object::{ + internal_methods::{ + non_existant_call, non_existant_construct, ordinary_define_own_property, + ordinary_delete, ordinary_get, ordinary_get_own_property, ordinary_get_prototype_of, + ordinary_has_property, ordinary_is_extensible, ordinary_own_property_keys, + ordinary_prevent_extensions, ordinary_set, ordinary_set_prototype_of, ordinary_try_get, + CallValue, InternalMethodContext, InternalObjectMethods, + }, + JsPrototype, + }, + property::{PropertyDescriptor, PropertyKey}, + realm::{Realm, RealmInner}, + Context, JsData, JsNativeError, JsObject, JsResult, JsValue, NativeFunction, +}; +use boa_gc::{Finalize, Trace, WeakGc}; + +#[derive(Debug, Clone, Trace, Finalize)] +pub(crate) enum BuiltinKind { + Function(NativeFunctionObject), + Ordinary, +} + +/// A builtin function. Used for lazy initialization of builtins. + +#[derive(Clone, Finalize, Debug)] +#[allow(clippy::type_complexity)] +pub struct LazyBuiltIn { + pub(crate) init_and_realm: Option<(fn(&Realm), WeakGc)>, + pub(crate) kind: BuiltinKind, +} + +impl LazyBuiltIn { + pub(crate) fn set_constructor(&mut self, function: NativeFunction, realm: Realm) { + if let BuiltinKind::Function(ref mut native_function) = self.kind { + native_function.f = function; + native_function.constructor = Some(ConstructorKind::Base); + native_function.realm = Some(realm); + } else { + panic!("Expected BuiltinKind::Function"); + } + } +} + +// SAFETY: Temporary, TODO move back to derived Trace when possible +unsafe impl Trace for LazyBuiltIn { + custom_trace!(this, mark, { + mark(&this.kind); + }); +} + +// Implement the trait for JsData by overriding all internal_methods by calling init before calling into the underlying internel_method +impl JsData for LazyBuiltIn { + fn internal_methods(&self) -> &'static InternalObjectMethods { + static FUNCTION: InternalObjectMethods = InternalObjectMethods { + __construct__: lazy_construct, + __call__: lazy_call, + ..LAZY_INTERNAL_METHODS + }; + + if let BuiltinKind::Function(_) = self.kind { + return &FUNCTION; + } + + &LAZY_INTERNAL_METHODS + } +} + +pub(crate) static LAZY_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { + __get_prototype_of__: lazy_get_prototype_of, + __set_prototype_of__: lazy_set_prototype_of, + __is_extensible__: lazy_is_extensible, + __prevent_extensions__: lazy_prevent_extensions, + __get_own_property__: lazy_get_own_property, + __define_own_property__: lazy_define_own_property, + __has_property__: lazy_has_property, + __try_get__: lazy_try_get, + __get__: lazy_get, + __set__: lazy_set, + __delete__: lazy_delete, + __own_property_keys__: lazy_own_property_keys, + __call__: non_existant_call, + __construct__: non_existant_construct, +}; + +pub(crate) fn lazy_get_prototype_of( + obj: &JsObject, + context: &mut Context, +) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + // If there is a value in here, we need to initialize the builtin + if builtin.borrow().data.init_and_realm.is_some() { + let mut builtin_borrow = builtin.borrow_mut(); + let (init, realm_inner) = builtin_borrow + .data + .init_and_realm + .take() + .expect("init_and_realm not set"); + + let realm = &Realm { + inner: realm_inner.upgrade().expect("realm_inner not set"), + }; + drop(builtin_borrow); + init(realm); + } + + ordinary_get_prototype_of(obj, context) +} + +pub(crate) fn lazy_set_prototype_of( + obj: &JsObject, + prototype: JsPrototype, + context: &mut Context, +) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + // If there is a value in here, we need to initialize the builtin + if builtin.borrow().data.init_and_realm.is_some() { + let mut builtin_borrow = builtin.borrow_mut(); + let (init, realm_inner) = builtin_borrow + .data + .init_and_realm + .take() + .expect("init_and_realm not set"); + + let realm = &Realm { + inner: realm_inner.upgrade().expect("realm_inner not set"), + }; + drop(builtin_borrow); + init(realm); + } + + ordinary_set_prototype_of(obj, prototype, context) +} +pub(crate) fn lazy_is_extensible(obj: &JsObject, context: &mut Context) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + // If there is a value in here, we need to initialize the builtin + if builtin.borrow().data.init_and_realm.is_some() { + let mut builtin_borrow = builtin.borrow_mut(); + let (init, realm_inner) = builtin_borrow + .data + .init_and_realm + .take() + .expect("init_and_realm not set"); + + let realm = &Realm { + inner: realm_inner.upgrade().expect("realm_inner not set"), + }; + drop(builtin_borrow); + init(realm); + } + + ordinary_is_extensible(obj, context) +} + +pub(crate) fn lazy_prevent_extensions(obj: &JsObject, context: &mut Context) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + // If there is a value in here, we need to initialize the builtin + if builtin.borrow().data.init_and_realm.is_some() { + let mut builtin_borrow = builtin.borrow_mut(); + let (init, realm_inner) = builtin_borrow + .data + .init_and_realm + .take() + .expect("init_and_realm not set"); + + let realm = &Realm { + inner: realm_inner.upgrade().expect("realm_inner not set"), + }; + drop(builtin_borrow); + init(realm); + } + + ordinary_prevent_extensions(obj, context) +} + +pub(crate) fn lazy_get_own_property( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult> { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + // If there is a value in here, we need to initialize the builtin + if builtin.borrow().data.init_and_realm.is_some() { + let mut builtin_borrow = builtin.borrow_mut(); + let (init, realm_inner) = builtin_borrow + .data + .init_and_realm + .take() + .expect("init_and_realm not set"); + + let realm = &Realm { + inner: realm_inner.upgrade().expect("realm_inner not set"), + }; + drop(builtin_borrow); + init(realm); + } + + ordinary_get_own_property(obj, key, context) +} + +pub(crate) fn lazy_define_own_property( + obj: &JsObject, + key: &PropertyKey, + desc: PropertyDescriptor, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + // If there is a value in here, we need to initialize the builtin + if builtin.borrow().data.init_and_realm.is_some() { + let mut builtin_borrow = builtin.borrow_mut(); + let (init, realm_inner) = builtin_borrow + .data + .init_and_realm + .take() + .expect("init_and_realm not set"); + + let realm = &Realm { + inner: realm_inner.upgrade().expect("realm_inner not set"), + }; + drop(builtin_borrow); + init(realm); + } + + ordinary_define_own_property(obj, key, desc, context) +} + +pub(crate) fn lazy_has_property( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + // If there is a value in here, we need to initialize the builtin + if builtin.borrow().data.init_and_realm.is_some() { + let mut builtin_borrow = builtin.borrow_mut(); + let (init, realm_inner) = builtin_borrow + .data + .init_and_realm + .take() + .expect("init_and_realm not set"); + + let realm = &Realm { + inner: realm_inner.upgrade().expect("realm_inner not set"), + }; + drop(builtin_borrow); + init(realm); + } + + ordinary_has_property(obj, key, context) +} + +pub(crate) fn lazy_try_get( + obj: &JsObject, + key: &PropertyKey, + receiver: JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult> { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + // If there is a value in here, we need to initialize the builtin + if builtin.borrow().data.init_and_realm.is_some() { + let mut builtin_borrow = builtin.borrow_mut(); + let (init, realm_inner) = builtin_borrow + .data + .init_and_realm + .take() + .expect("init_and_realm not set"); + + let realm = &Realm { + inner: realm_inner.upgrade().expect("realm_inner not set"), + }; + drop(builtin_borrow); + init(realm); + } + + ordinary_try_get(obj, key, receiver, context) +} + +pub(crate) fn lazy_get( + obj: &JsObject, + key: &PropertyKey, + receiver: JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + // If there is a value in here, we need to initialize the builtin + if builtin.borrow().data.init_and_realm.is_some() { + let mut builtin_borrow = builtin.borrow_mut(); + let (init, realm_inner) = builtin_borrow + .data + .init_and_realm + .take() + .expect("init_and_realm not set"); + + let realm = &Realm { + inner: realm_inner.upgrade().expect("realm_inner not set"), + }; + drop(builtin_borrow); + init(realm); + } + + ordinary_get(obj, key, receiver, context) +} + +pub(crate) fn lazy_set( + obj: &JsObject, + key: PropertyKey, + value: JsValue, + receiver: JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + // If there is a value in here, we need to initialize the builtin + if builtin.borrow().data.init_and_realm.is_some() { + let mut builtin_borrow = builtin.borrow_mut(); + let (init, realm_inner) = builtin_borrow + .data + .init_and_realm + .take() + .expect("init_and_realm not set"); + + let realm = &Realm { + inner: realm_inner.upgrade().expect("realm_inner not set"), + }; + drop(builtin_borrow); + init(realm); + } + + ordinary_set(obj, key, value, receiver, context) +} + +pub(crate) fn lazy_delete( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + // If there is a value in here, we need to initialize the builtin + if builtin.borrow().data.init_and_realm.is_some() { + let mut builtin_borrow = builtin.borrow_mut(); + let (init, realm_inner) = builtin_borrow + .data + .init_and_realm + .take() + .expect("init_and_realm not set"); + + let realm = &Realm { + inner: realm_inner.upgrade().expect("realm_inner not set"), + }; + drop(builtin_borrow); + init(realm); + } + + ordinary_delete(obj, key, context) +} + +pub(crate) fn lazy_own_property_keys( + obj: &JsObject, + context: &mut Context, +) -> JsResult> { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + // If there is a value in here, we need to initialize the builtin + if builtin.borrow().data.init_and_realm.is_some() { + let mut builtin_borrow = builtin.borrow_mut(); + let (init, realm_inner) = builtin_borrow + .data + .init_and_realm + .take() + .expect("init_and_realm not set"); + + let realm = &Realm { + inner: realm_inner.upgrade().expect("realm_inner not set"), + }; + drop(builtin_borrow); + init(realm); + } + + ordinary_own_property_keys(obj, context) +} + +pub(crate) fn lazy_construct( + obj: &JsObject, + argument_count: usize, + context: &mut Context, +) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + let kind = &builtin.borrow().data.kind; + // If there is a value in here, we need to initialize the builtin + if builtin.borrow().data.init_and_realm.is_some() { + let mut builtin_borrow = builtin.borrow_mut(); + let (init, realm_inner) = builtin_borrow + .data + .init_and_realm + .take() + .expect("init_and_realm not set"); + + let realm = &Realm { + inner: realm_inner.upgrade().expect("realm_inner not set"), + }; + drop(builtin_borrow); + init(realm); + } + + match kind { + BuiltinKind::Ordinary => Err(JsNativeError::typ() + .with_message("not a constructor") + .with_realm(context.realm().clone()) + .into()), + BuiltinKind::Function(constructor) => Ok((constructor.internal_methods().__construct__)( + obj, + argument_count, + context, + )?), + } +} + +pub(crate) fn lazy_call( + obj: &JsObject, + argument_count: usize, + context: &mut Context, +) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + let kind = &builtin.borrow().data.kind; + // If there is a value in here, we need to initialize the builtin + if builtin.borrow().data.init_and_realm.is_some() { + let mut builtin_borrow = builtin.borrow_mut(); + let (init, realm_inner) = builtin_borrow + .data + .init_and_realm + .take() + .expect("init_and_realm not set"); + + let realm = &Realm { + inner: realm_inner.upgrade().expect("realm_inner not set"), + }; + drop(builtin_borrow); + init(realm); + } + + match kind { + BuiltinKind::Ordinary => Err(JsNativeError::typ() + .with_message("not a constructor") + .with_realm(context.realm().clone()) + .into()), + BuiltinKind::Function(function) => Ok((function.internal_methods().__call__)( + obj, + argument_count, + context, + )?), + } +} diff --git a/core/engine/src/object/builtins/mod.rs b/core/engine/src/object/builtins/mod.rs index 923f5ad9fb1..3691ef279db 100644 --- a/core/engine/src/object/builtins/mod.rs +++ b/core/engine/src/object/builtins/mod.rs @@ -17,6 +17,7 @@ mod jsset; mod jsset_iterator; mod jssharedarraybuffer; mod jstypedarray; +mod lazy_builtin; pub use jsarray::*; pub use jsarraybuffer::*; @@ -33,3 +34,4 @@ pub use jsset::*; pub use jsset_iterator::*; pub use jssharedarraybuffer::*; pub use jstypedarray::*; +pub use lazy_builtin::*; diff --git a/core/engine/src/object/internal_methods/mod.rs b/core/engine/src/object/internal_methods/mod.rs index ac671752f3c..3cdff643c19 100644 --- a/core/engine/src/object/internal_methods/mod.rs +++ b/core/engine/src/object/internal_methods/mod.rs @@ -1117,7 +1117,7 @@ where Ok(default(realm.intrinsics().constructors()).prototype()) } -fn non_existant_call( +pub(crate) fn non_existant_call( _obj: &JsObject, _argument_count: usize, context: &mut Context, @@ -1128,7 +1128,7 @@ fn non_existant_call( .into()) } -fn non_existant_construct( +pub(crate) fn non_existant_construct( _obj: &JsObject, _argument_count: usize, context: &mut Context, diff --git a/core/engine/src/object/jsobject.rs b/core/engine/src/object/jsobject.rs index eccb1f81383..9cc466d0a19 100644 --- a/core/engine/src/object/jsobject.rs +++ b/core/engine/src/object/jsobject.rs @@ -5,7 +5,7 @@ use super::{ internal_methods::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS}, shape::RootShape, - JsPrototype, NativeObject, Object, PrivateName, PropertyMap, + BuiltinKind, JsPrototype, LazyBuiltIn, NativeObject, Object, PrivateName, PropertyMap, }; use crate::{ builtins::{ @@ -17,10 +17,11 @@ use crate::{ error::JsNativeError, js_string, property::{PropertyDescriptor, PropertyKey}, + realm::{Realm, RealmInner}, value::PreferredType, Context, JsResult, JsString, JsValue, }; -use boa_gc::{self, Finalize, Gc, GcBox, GcRefCell, Trace}; +use boa_gc::{self, Finalize, Gc, GcBox, GcRefCell, Trace, WeakGc}; use boa_macros::js_str; use std::{ cell::RefCell, @@ -92,6 +93,17 @@ impl JsObject { inner: coerce_gc(gc), } } + /// Creates a new lazy `JsObject` from its inner object and its vtable. + /// This is used for built-in objects that are lazily initialized. + pub(crate) fn lazy(init: fn(&Realm) -> (), realm_inner: &WeakGc) -> Self { + Self::from_proto_and_data( + None, + LazyBuiltIn { + init_and_realm: Some((init, realm_inner.clone())), + kind: BuiltinKind::Ordinary, + }, + ) + } /// Creates a new ordinary object with its prototype set to the `Object` prototype. /// diff --git a/core/engine/src/realm.rs b/core/engine/src/realm.rs index b034420c2a9..50f65104b18 100644 --- a/core/engine/src/realm.rs +++ b/core/engine/src/realm.rs @@ -20,7 +20,7 @@ use crate::{ environments::DeclarativeEnvironment, module::Module, object::shape::RootShape, - HostDefined, JsNativeError, JsObject, JsResult, JsString, + HostDefined, JsObject, JsResult, JsString, }; use boa_gc::{Finalize, Gc, GcRef, GcRefCell, GcRefMut, Trace}; use boa_profiler::Profiler; @@ -30,7 +30,9 @@ use boa_profiler::Profiler; /// In the specification these are called Realm Records. #[derive(Clone, Trace, Finalize)] pub struct Realm { - inner: Gc, + /// The inner data of the realm, which includes the intrinsics, environment, + /// global object, and other realm-specific information. + pub inner: Gc, } impl Eq for Realm {} @@ -53,7 +55,12 @@ impl std::fmt::Debug for Realm { } #[derive(Trace, Finalize)] -struct Inner { + +/// The inner data of a Realm. +/// +/// This struct contains all the realm-specific information, including the intrinsics, +/// environment, global object, and other necessary data for the execution context. +pub struct RealmInner { intrinsics: Intrinsics, /// The global declarative environment of this realm. @@ -70,29 +77,43 @@ struct Inner { template_map: GcRefCell>, loaded_modules: GcRefCell>, host_classes: GcRefCell>, - host_defined: GcRefCell, } +#[allow(clippy::missing_fields_in_debug)] +impl std::fmt::Debug for RealmInner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RealmInner") + .field("intrinsics", &self.intrinsics) + .field("environment", &self.environment) + .field("global_object", &self.global_object) + .field("global_this", &self.global_this) + .field("template_map", &self.template_map) + .field("loaded_modules", &self.loaded_modules) + .field("host_classes", &self.host_classes) + .finish() + } +} impl Realm { /// Create a new [`Realm`]. #[inline] pub fn create(hooks: &dyn HostHooks, root_shape: &RootShape) -> JsResult { let _timer = Profiler::global().start_event("Realm::create", "realm"); - let intrinsics = Intrinsics::uninit(root_shape).ok_or_else(|| { - JsNativeError::typ().with_message("failed to create the realm intrinsics") - })?; + // Use Gc::new_cyclic to create the Realm with a cyclic reference + let inner = Gc::new_cyclic(|weak_realm| { + // Initialize intrinsics with a reference to the weak_realm + let intrinsics = Intrinsics::uninit(root_shape, weak_realm) + .expect("failed to create the realm intrinsics"); - let global_object = hooks.create_global_object(&intrinsics); - let global_this = hooks - .create_global_this(&intrinsics) - .unwrap_or_else(|| global_object.clone()); - let environment = Gc::new(DeclarativeEnvironment::global()); - let scope = Scope::new_global(); + let global_object = hooks.create_global_object(&intrinsics); + let global_this = hooks + .create_global_this(&intrinsics) + .unwrap_or_else(|| global_object.clone()); + let environment = Gc::new(DeclarativeEnvironment::global()); + let scope = Scope::new_global(); - let realm = Self { - inner: Gc::new(Inner { + RealmInner { intrinsics, environment, scope, @@ -102,8 +123,10 @@ impl Realm { loaded_modules: GcRefCell::default(), host_classes: GcRefCell::default(), host_defined: GcRefCell::default(), - }), - }; + } + }); + + let realm = Self { inner }; realm.initialize();