mirror of https://github.com/boa-dev/boa.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1063 lines
31 KiB
1063 lines
31 KiB
//! Boa's representation of a JavaScript object and builtin object wrappers |
|
//! |
|
//! For the builtin object wrappers, please see [`object::builtins`][builtins] for implementors. |
|
|
|
pub use jsobject::{RecursionLimiter, Ref, RefMut}; |
|
pub use operations::IntegrityLevel; |
|
pub use property_map::*; |
|
use thin_vec::ThinVec; |
|
|
|
use self::{internal_methods::ORDINARY_INTERNAL_METHODS, shape::Shape}; |
|
use crate::{ |
|
builtins::{ |
|
function::{ |
|
arguments::{MappedArguments, UnmappedArguments}, |
|
ConstructorKind, |
|
}, |
|
typed_array::{TypedArray, TypedArrayKind}, |
|
OrdinaryObject, |
|
}, |
|
context::intrinsics::StandardConstructor, |
|
js_string, |
|
native_function::{NativeFunction, NativeFunctionObject}, |
|
property::{Attribute, PropertyDescriptor, PropertyKey}, |
|
realm::Realm, |
|
string::{common::StaticJsStrings, utf16}, |
|
Context, JsString, JsSymbol, JsValue, |
|
}; |
|
|
|
use boa_gc::{Finalize, Trace}; |
|
use std::{ |
|
any::{Any, TypeId}, |
|
fmt::Debug, |
|
}; |
|
|
|
#[cfg(test)] |
|
mod tests; |
|
|
|
pub(crate) mod internal_methods; |
|
|
|
pub mod builtins; |
|
mod datatypes; |
|
mod jsobject; |
|
mod operations; |
|
mod property_map; |
|
pub mod shape; |
|
|
|
pub(crate) use builtins::*; |
|
|
|
pub use datatypes::JsData; |
|
pub use jsobject::*; |
|
|
|
pub(crate) trait JsObjectType: Into<JsValue> + Into<JsObject> {} |
|
|
|
/// Const `constructor`, usually set on prototypes as a key to point to their respective constructor object. |
|
pub const CONSTRUCTOR: &[u16] = utf16!("constructor"); |
|
|
|
/// Const `prototype`, usually set on constructors as a key to point to their respective prototype object. |
|
pub const PROTOTYPE: &[u16] = utf16!("prototype"); |
|
|
|
/// Common field names. |
|
|
|
/// A type alias for an object prototype. |
|
/// |
|
/// A `None` values means that the prototype is the `null` value. |
|
pub type JsPrototype = Option<JsObject>; |
|
|
|
/// The internal storage of an object's property values. |
|
/// |
|
/// The [`shape::Shape`] contains the property names and attributes. |
|
pub(crate) type ObjectStorage = Vec<JsValue>; |
|
|
|
/// This trait allows Rust types to be passed around as objects. |
|
/// |
|
/// This is automatically implemented when a type implements `Any`, `Trace`, and `JsData`. |
|
pub trait NativeObject: Any + Trace + JsData { |
|
/// Convert the Rust type which implements `NativeObject` to a `&dyn Any`. |
|
fn as_any(&self) -> &dyn Any; |
|
|
|
/// Convert the Rust type which implements `NativeObject` to a `&mut dyn Any`. |
|
fn as_mut_any(&mut self) -> &mut dyn Any; |
|
|
|
/// Gets the type name of the value. |
|
fn type_name_of_value(&self) -> &'static str; |
|
} |
|
|
|
// TODO: Use super trait casting in Rust 1.75 |
|
impl<T: Any + Trace + JsData> NativeObject for T { |
|
fn as_any(&self) -> &dyn Any { |
|
self |
|
} |
|
|
|
fn as_mut_any(&mut self) -> &mut dyn Any { |
|
self |
|
} |
|
|
|
fn type_name_of_value(&self) -> &'static str { |
|
fn name_of_val<T: ?Sized>(_val: &T) -> &'static str { |
|
std::any::type_name::<T>() |
|
} |
|
|
|
name_of_val(self) |
|
} |
|
} |
|
|
|
// TODO: Use super trait casting in Rust 1.75 |
|
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, Trace)] |
|
// SAFETY: This does not implement drop, so this is safe. |
|
#[boa_gc(unsafe_no_drop)] |
|
pub struct Object<T: ?Sized> { |
|
/// The collection of properties contained in the object |
|
pub(crate) properties: PropertyMap, |
|
/// Whether it can have new properties added to it. |
|
pub(crate) extensible: bool, |
|
/// The `[[PrivateElements]]` internal slot. |
|
private_elements: ThinVec<(PrivateName, PrivateElement)>, |
|
/// The inner object data |
|
pub(crate) data: T, |
|
} |
|
|
|
impl<T: Default> Default for Object<T> { |
|
fn default() -> Self { |
|
Self { |
|
properties: PropertyMap::default(), |
|
extensible: true, |
|
private_elements: ThinVec::new(), |
|
data: T::default(), |
|
} |
|
} |
|
} |
|
|
|
/// A Private Name. |
|
#[derive(Clone, Debug, PartialEq, Eq, Trace, Finalize)] |
|
pub struct PrivateName { |
|
/// The `[[Description]]` internal slot of the private name. |
|
description: JsString, |
|
|
|
/// The unique identifier of the private name. |
|
id: usize, |
|
} |
|
|
|
impl PrivateName { |
|
/// Create a new private name. |
|
pub(crate) const fn new(description: JsString, id: usize) -> Self { |
|
Self { description, id } |
|
} |
|
} |
|
|
|
/// The representation of private object elements. |
|
#[derive(Clone, Debug, Trace, Finalize)] |
|
pub enum PrivateElement { |
|
/// A private field. |
|
Field(JsValue), |
|
|
|
/// A private method. |
|
Method(JsObject), |
|
|
|
/// A private element accessor. |
|
Accessor { |
|
/// A getter function. |
|
getter: Option<JsObject>, |
|
|
|
/// A setter function. |
|
setter: Option<JsObject>, |
|
}, |
|
} |
|
|
|
impl<T: ?Sized> Object<T> { |
|
/// Returns the shape of the object. |
|
#[must_use] |
|
pub const fn shape(&self) -> &Shape { |
|
&self.properties.shape |
|
} |
|
|
|
/// Returns the data of the object. |
|
#[inline] |
|
#[must_use] |
|
pub const fn data(&self) -> &T { |
|
&self.data |
|
} |
|
|
|
/// Gets the prototype instance of this object. |
|
#[inline] |
|
#[must_use] |
|
pub fn prototype(&self) -> JsPrototype { |
|
self.properties.shape.prototype() |
|
} |
|
|
|
/// Sets the prototype instance of the object. |
|
/// |
|
/// [More information][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-invariants-of-the-essential-internal-methods |
|
#[track_caller] |
|
pub fn set_prototype<O: Into<JsPrototype>>(&mut self, prototype: O) -> bool { |
|
let prototype = prototype.into(); |
|
if self.extensible { |
|
self.properties.shape = self.properties.shape.change_prototype_transition(prototype); |
|
true |
|
} else { |
|
// If target is non-extensible, [[SetPrototypeOf]] must return false |
|
// unless V is the SameValue as the target's observed [[GetPrototypeOf]] value. |
|
self.prototype() == prototype |
|
} |
|
} |
|
|
|
/// Returns the properties of the object. |
|
#[inline] |
|
#[must_use] |
|
pub const fn properties(&self) -> &PropertyMap { |
|
&self.properties |
|
} |
|
|
|
#[inline] |
|
pub(crate) fn properties_mut(&mut self) -> &mut PropertyMap { |
|
&mut self.properties |
|
} |
|
|
|
/// Inserts a field in the object `properties` without checking if it's writable. |
|
/// |
|
/// If a field was already in the object with the same name, then `true` is returned |
|
/// otherwise, `false` is returned. |
|
pub(crate) fn insert<K, P>(&mut self, key: K, property: P) -> bool |
|
where |
|
K: Into<PropertyKey>, |
|
P: Into<PropertyDescriptor>, |
|
{ |
|
self.properties.insert(&key.into(), property.into()) |
|
} |
|
|
|
/// Helper function for property removal without checking if it's configurable. |
|
/// |
|
/// Returns `true` if the property was removed, `false` otherwise. |
|
#[inline] |
|
pub(crate) fn remove(&mut self, key: &PropertyKey) -> bool { |
|
self.properties.remove(key) |
|
} |
|
|
|
/// Append a private element to an object. |
|
pub(crate) fn append_private_element(&mut self, name: PrivateName, element: PrivateElement) { |
|
if let PrivateElement::Accessor { getter, setter } = &element { |
|
for (key, value) in &mut self.private_elements { |
|
if name == *key { |
|
if let PrivateElement::Accessor { |
|
getter: existing_getter, |
|
setter: existing_setter, |
|
} = value |
|
{ |
|
if existing_getter.is_none() { |
|
*existing_getter = getter.clone(); |
|
} |
|
if existing_setter.is_none() { |
|
*existing_setter = setter.clone(); |
|
} |
|
return; |
|
} |
|
} |
|
} |
|
} |
|
|
|
self.private_elements.push((name, element)); |
|
} |
|
} |
|
|
|
impl Object<dyn NativeObject> { |
|
/// Return `true` if it is a native object and the native type is `T`. |
|
#[must_use] |
|
pub fn is<T: NativeObject>(&self) -> bool { |
|
self.data.is::<T>() |
|
} |
|
|
|
/// Downcast a reference to the object, |
|
/// if the object is type native object type `T`. |
|
#[must_use] |
|
pub fn downcast_ref<T: NativeObject>(&self) -> Option<&T> { |
|
self.data.downcast_ref::<T>() |
|
} |
|
|
|
/// Downcast a mutable reference to the object, |
|
/// if the object is type native object type `T`. |
|
pub fn downcast_mut<T: NativeObject>(&mut self) -> Option<&mut T> { |
|
self.data.downcast_mut::<T>() |
|
} |
|
|
|
/// Checks if this object is an `Arguments` object. |
|
pub(crate) fn is_arguments(&self) -> bool { |
|
self.is::<UnmappedArguments>() || self.is::<MappedArguments>() |
|
} |
|
|
|
/// Checks if it a `Uint8Array` object. |
|
#[inline] |
|
#[must_use] |
|
pub fn is_typed_uint8_array(&self) -> bool { |
|
if let Some(int) = self.downcast_ref::<TypedArray>() { |
|
matches!(int.kind(), TypedArrayKind::Uint8) |
|
} else { |
|
false |
|
} |
|
} |
|
|
|
/// Checks if it a `Int8Array` object. |
|
#[inline] |
|
#[must_use] |
|
pub fn is_typed_int8_array(&self) -> bool { |
|
if let Some(int) = self.downcast_ref::<TypedArray>() { |
|
matches!(int.kind(), TypedArrayKind::Int8) |
|
} else { |
|
false |
|
} |
|
} |
|
|
|
/// Checks if it a `Uint16Array` object. |
|
#[inline] |
|
#[must_use] |
|
pub fn is_typed_uint16_array(&self) -> bool { |
|
if let Some(int) = self.downcast_ref::<TypedArray>() { |
|
matches!(int.kind(), TypedArrayKind::Uint16) |
|
} else { |
|
false |
|
} |
|
} |
|
|
|
/// Checks if it a `Int16Array` object. |
|
#[inline] |
|
#[must_use] |
|
pub fn is_typed_int16_array(&self) -> bool { |
|
if let Some(int) = self.downcast_ref::<TypedArray>() { |
|
matches!(int.kind(), TypedArrayKind::Int16) |
|
} else { |
|
false |
|
} |
|
} |
|
|
|
/// Checks if it a `Uint32Array` object. |
|
#[inline] |
|
#[must_use] |
|
pub fn is_typed_uint32_array(&self) -> bool { |
|
if let Some(int) = self.downcast_ref::<TypedArray>() { |
|
matches!(int.kind(), TypedArrayKind::Uint32) |
|
} else { |
|
false |
|
} |
|
} |
|
|
|
/// Checks if it a `Int32Array` object. |
|
#[inline] |
|
#[must_use] |
|
pub fn is_typed_int32_array(&self) -> bool { |
|
if let Some(int) = self.downcast_ref::<TypedArray>() { |
|
matches!(int.kind(), TypedArrayKind::Int32) |
|
} else { |
|
false |
|
} |
|
} |
|
|
|
/// Checks if it a `Float32Array` object. |
|
#[inline] |
|
#[must_use] |
|
pub fn is_typed_float32_array(&self) -> bool { |
|
if let Some(int) = self.downcast_ref::<TypedArray>() { |
|
matches!(int.kind(), TypedArrayKind::Float32) |
|
} else { |
|
false |
|
} |
|
} |
|
|
|
/// Checks if it a `Float64Array` object. |
|
#[inline] |
|
#[must_use] |
|
pub fn is_typed_float64_array(&self) -> bool { |
|
if let Some(int) = self.downcast_ref::<TypedArray>() { |
|
matches!(int.kind(), TypedArrayKind::Float64) |
|
} else { |
|
false |
|
} |
|
} |
|
} |
|
|
|
/// The functions binding. |
|
/// |
|
/// Specifies what is the name of the function object (`name` property), |
|
/// and the binding name of the function object which can be different |
|
/// from the function name. |
|
/// |
|
/// The only way to construct this is with the `From` trait. |
|
/// |
|
/// There are two implementations: |
|
/// - From a single type `T` which implements `Into<FunctionBinding>` which sets the binding |
|
/// name and the function name to the same value. |
|
/// - From a tuple `(B: Into<PropertyKey>, N: Into<JsString>)`, where the `B` is the binding name |
|
/// and the `N` is the function name. |
|
#[derive(Debug, Clone)] |
|
pub struct FunctionBinding { |
|
pub(crate) binding: PropertyKey, |
|
pub(crate) name: JsString, |
|
} |
|
|
|
impl From<JsString> for FunctionBinding { |
|
#[inline] |
|
fn from(name: JsString) -> Self { |
|
Self { |
|
binding: name.clone().into(), |
|
name, |
|
} |
|
} |
|
} |
|
|
|
impl From<JsSymbol> for FunctionBinding { |
|
#[inline] |
|
fn from(binding: JsSymbol) -> Self { |
|
Self { |
|
name: binding.fn_name(), |
|
binding: binding.into(), |
|
} |
|
} |
|
} |
|
|
|
impl<B, N> From<(B, N)> for FunctionBinding |
|
where |
|
B: Into<PropertyKey>, |
|
N: Into<JsString>, |
|
{ |
|
fn from((binding, name): (B, N)) -> Self { |
|
Self { |
|
binding: binding.into(), |
|
name: name.into(), |
|
} |
|
} |
|
} |
|
|
|
/// Builder for creating native function objects |
|
#[derive(Debug)] |
|
pub struct FunctionObjectBuilder<'realm> { |
|
realm: &'realm Realm, |
|
function: NativeFunction, |
|
constructor: Option<ConstructorKind>, |
|
name: JsString, |
|
length: usize, |
|
} |
|
|
|
impl<'realm> FunctionObjectBuilder<'realm> { |
|
/// Create a new `FunctionBuilder` for creating a native function. |
|
#[inline] |
|
#[must_use] |
|
pub fn new(realm: &'realm Realm, function: NativeFunction) -> Self { |
|
Self { |
|
realm, |
|
function, |
|
constructor: None, |
|
name: js_string!(), |
|
length: 0, |
|
} |
|
} |
|
|
|
/// Specify the name property of object function object. |
|
/// |
|
/// The default is `""` (empty string). |
|
#[must_use] |
|
pub fn name<N>(mut self, name: N) -> Self |
|
where |
|
N: Into<JsString>, |
|
{ |
|
self.name = name.into(); |
|
self |
|
} |
|
|
|
/// Specify the length property of object function object. |
|
/// |
|
/// How many arguments this function takes. |
|
/// |
|
/// The default is `0`. |
|
#[inline] |
|
#[must_use] |
|
pub const fn length(mut self, length: usize) -> Self { |
|
self.length = length; |
|
self |
|
} |
|
|
|
/// Specify whether the object function object can be called with `new` keyword. |
|
/// |
|
/// The default is `false`. |
|
#[must_use] |
|
pub fn constructor(mut self, yes: bool) -> Self { |
|
self.constructor = yes.then_some(ConstructorKind::Base); |
|
self |
|
} |
|
|
|
/// Build the function object. |
|
#[must_use] |
|
pub fn build(self) -> JsFunction { |
|
let object = self.realm.intrinsics().templates().function().create( |
|
NativeFunctionObject { |
|
f: self.function, |
|
constructor: self.constructor, |
|
realm: Some(self.realm.clone()), |
|
}, |
|
vec![self.length.into(), self.name.into()], |
|
); |
|
|
|
JsFunction::from_object_unchecked(object) |
|
} |
|
} |
|
|
|
/// Builder for creating objects with properties. |
|
/// |
|
/// # Examples |
|
/// |
|
/// ``` |
|
/// # use boa_engine::{ |
|
/// # Context, |
|
/// # JsValue, |
|
/// # NativeFunction, |
|
/// # object::ObjectInitializer, |
|
/// # property::Attribute, |
|
/// # js_string, |
|
/// # }; |
|
/// let mut context = Context::default(); |
|
/// let object = ObjectInitializer::new(&mut context) |
|
/// .property(js_string!("hello"), js_string!("world"), Attribute::all()) |
|
/// .property(1, 1, Attribute::all()) |
|
/// .function( |
|
/// NativeFunction::from_fn_ptr(|_, _, _| Ok(JsValue::undefined())), |
|
/// js_string!("func"), |
|
/// 0, |
|
/// ) |
|
/// .build(); |
|
/// ``` |
|
/// |
|
/// The equivalent in JavaScript would be: |
|
/// ```text |
|
/// let object = { |
|
/// hello: "world", |
|
/// "1": 1, |
|
/// func: function() {} |
|
/// } |
|
/// ``` |
|
#[derive(Debug)] |
|
pub struct ObjectInitializer<'ctx> { |
|
context: &'ctx mut Context, |
|
object: JsObject, |
|
} |
|
|
|
impl<'ctx> ObjectInitializer<'ctx> { |
|
/// Create a new `ObjectBuilder`. |
|
#[inline] |
|
pub fn new(context: &'ctx mut Context) -> Self { |
|
let object = JsObject::with_object_proto(context.intrinsics()); |
|
Self { context, object } |
|
} |
|
|
|
/// Create a new `ObjectBuilder` with custom [`NativeObject`] data. |
|
pub fn with_native_data<T: NativeObject>(data: T, context: &'ctx mut Context) -> Self { |
|
let object = JsObject::from_proto_and_data_with_shared_shape( |
|
context.root_shape(), |
|
context.intrinsics().constructors().object().prototype(), |
|
data, |
|
); |
|
Self { context, object } |
|
} |
|
|
|
/// Create a new `ObjectBuilder` with custom [`NativeObject`] data and custom prototype. |
|
pub fn with_native_data_and_proto<T: NativeObject>( |
|
data: T, |
|
proto: JsObject, |
|
context: &'ctx mut Context, |
|
) -> Self { |
|
let object = |
|
JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, data); |
|
Self { context, object } |
|
} |
|
|
|
/// Add a function to the object. |
|
pub fn function<B>(&mut self, function: NativeFunction, binding: B, length: usize) -> &mut Self |
|
where |
|
B: Into<FunctionBinding>, |
|
{ |
|
let binding = binding.into(); |
|
let function = FunctionObjectBuilder::new(self.context.realm(), function) |
|
.name(binding.name) |
|
.length(length) |
|
.constructor(false) |
|
.build(); |
|
|
|
self.object.borrow_mut().insert( |
|
binding.binding, |
|
PropertyDescriptor::builder() |
|
.value(function) |
|
.writable(true) |
|
.enumerable(false) |
|
.configurable(true), |
|
); |
|
self |
|
} |
|
|
|
/// Add a property to the object. |
|
pub fn property<K, V>(&mut self, key: K, value: V, attribute: Attribute) -> &mut Self |
|
where |
|
K: Into<PropertyKey>, |
|
V: Into<JsValue>, |
|
{ |
|
let property = PropertyDescriptor::builder() |
|
.value(value) |
|
.writable(attribute.writable()) |
|
.enumerable(attribute.enumerable()) |
|
.configurable(attribute.configurable()); |
|
self.object.borrow_mut().insert(key, property); |
|
self |
|
} |
|
|
|
/// Add new accessor property to the object. |
|
/// |
|
/// # Panics |
|
/// |
|
/// If both getter or setter are [`None`]. |
|
pub fn accessor<K>( |
|
&mut self, |
|
key: K, |
|
get: Option<JsFunction>, |
|
set: Option<JsFunction>, |
|
attribute: Attribute, |
|
) -> &mut Self |
|
where |
|
K: Into<PropertyKey>, |
|
{ |
|
// Accessors should have at least one function. |
|
assert!(set.is_some() || get.is_some()); |
|
|
|
let property = PropertyDescriptor::builder() |
|
.maybe_get(get) |
|
.maybe_set(set) |
|
.enumerable(attribute.enumerable()) |
|
.configurable(attribute.configurable()); |
|
self.object.borrow_mut().insert(key, property); |
|
self |
|
} |
|
|
|
/// Build the object. |
|
#[inline] |
|
pub fn build(&mut self) -> JsObject { |
|
self.object.clone() |
|
} |
|
|
|
/// Gets the context used to create the object. |
|
#[inline] |
|
pub fn context(&mut self) -> &mut Context { |
|
self.context |
|
} |
|
} |
|
|
|
/// Builder for creating constructors objects, like `Array`. |
|
#[derive(Debug)] |
|
pub struct ConstructorBuilder<'ctx> { |
|
context: &'ctx mut Context, |
|
function: NativeFunction, |
|
constructor_object: Object<OrdinaryObject>, |
|
has_prototype_property: bool, |
|
prototype: Object<OrdinaryObject>, |
|
name: JsString, |
|
length: usize, |
|
callable: bool, |
|
kind: Option<ConstructorKind>, |
|
inherit: Option<JsPrototype>, |
|
custom_prototype: Option<JsPrototype>, |
|
} |
|
|
|
impl<'ctx> ConstructorBuilder<'ctx> { |
|
/// Create a new `ConstructorBuilder`. |
|
#[inline] |
|
pub fn new(context: &'ctx mut Context, function: NativeFunction) -> ConstructorBuilder<'ctx> { |
|
Self { |
|
context, |
|
function, |
|
constructor_object: Object { |
|
data: OrdinaryObject, |
|
properties: PropertyMap::default(), |
|
extensible: true, |
|
private_elements: ThinVec::new(), |
|
}, |
|
prototype: Object { |
|
data: OrdinaryObject, |
|
properties: PropertyMap::default(), |
|
extensible: true, |
|
private_elements: ThinVec::new(), |
|
}, |
|
length: 0, |
|
name: js_string!(), |
|
callable: true, |
|
kind: Some(ConstructorKind::Base), |
|
inherit: None, |
|
custom_prototype: None, |
|
has_prototype_property: true, |
|
} |
|
} |
|
|
|
/// Add new method to the constructors prototype. |
|
pub fn method<B>(&mut self, function: NativeFunction, binding: B, length: usize) -> &mut Self |
|
where |
|
B: Into<FunctionBinding>, |
|
{ |
|
let binding = binding.into(); |
|
let function = FunctionObjectBuilder::new(self.context.realm(), function) |
|
.name(binding.name) |
|
.length(length) |
|
.constructor(false) |
|
.build(); |
|
|
|
self.prototype.insert( |
|
binding.binding, |
|
PropertyDescriptor::builder() |
|
.value(function) |
|
.writable(true) |
|
.enumerable(false) |
|
.configurable(true), |
|
); |
|
self |
|
} |
|
|
|
/// Add new static method to the constructors object itself. |
|
pub fn static_method<B>( |
|
&mut self, |
|
function: NativeFunction, |
|
binding: B, |
|
length: usize, |
|
) -> &mut Self |
|
where |
|
B: Into<FunctionBinding>, |
|
{ |
|
let binding = binding.into(); |
|
let function = FunctionObjectBuilder::new(self.context.realm(), function) |
|
.name(binding.name) |
|
.length(length) |
|
.constructor(false) |
|
.build(); |
|
|
|
self.constructor_object.insert( |
|
binding.binding, |
|
PropertyDescriptor::builder() |
|
.value(function) |
|
.writable(true) |
|
.enumerable(false) |
|
.configurable(true), |
|
); |
|
self |
|
} |
|
|
|
/// Add new data property to the constructor's prototype. |
|
pub fn property<K, V>(&mut self, key: K, value: V, attribute: Attribute) -> &mut Self |
|
where |
|
K: Into<PropertyKey>, |
|
V: Into<JsValue>, |
|
{ |
|
let property = PropertyDescriptor::builder() |
|
.value(value) |
|
.writable(attribute.writable()) |
|
.enumerable(attribute.enumerable()) |
|
.configurable(attribute.configurable()); |
|
self.prototype.insert(key, property); |
|
self |
|
} |
|
|
|
/// Add new static data property to the constructor object itself. |
|
pub fn static_property<K, V>(&mut self, key: K, value: V, attribute: Attribute) -> &mut Self |
|
where |
|
K: Into<PropertyKey>, |
|
V: Into<JsValue>, |
|
{ |
|
let property = PropertyDescriptor::builder() |
|
.value(value) |
|
.writable(attribute.writable()) |
|
.enumerable(attribute.enumerable()) |
|
.configurable(attribute.configurable()); |
|
self.constructor_object.insert(key, property); |
|
self |
|
} |
|
|
|
/// Add new accessor property to the constructor's prototype. |
|
pub fn accessor<K>( |
|
&mut self, |
|
key: K, |
|
get: Option<JsFunction>, |
|
set: Option<JsFunction>, |
|
attribute: Attribute, |
|
) -> &mut Self |
|
where |
|
K: Into<PropertyKey>, |
|
{ |
|
let property = PropertyDescriptor::builder() |
|
.maybe_get(get) |
|
.maybe_set(set) |
|
.enumerable(attribute.enumerable()) |
|
.configurable(attribute.configurable()); |
|
self.prototype.insert(key, property); |
|
self |
|
} |
|
|
|
/// Add new static accessor property to the constructor object itself. |
|
pub fn static_accessor<K>( |
|
&mut self, |
|
key: K, |
|
get: Option<JsFunction>, |
|
set: Option<JsFunction>, |
|
attribute: Attribute, |
|
) -> &mut Self |
|
where |
|
K: Into<PropertyKey>, |
|
{ |
|
let property = PropertyDescriptor::builder() |
|
.maybe_get(get) |
|
.maybe_set(set) |
|
.enumerable(attribute.enumerable()) |
|
.configurable(attribute.configurable()); |
|
self.constructor_object.insert(key, property); |
|
self |
|
} |
|
|
|
/// Add new property to the constructor's prototype. |
|
pub fn property_descriptor<K, P>(&mut self, key: K, property: P) -> &mut Self |
|
where |
|
K: Into<PropertyKey>, |
|
P: Into<PropertyDescriptor>, |
|
{ |
|
let property = property.into(); |
|
self.prototype.insert(key, property); |
|
self |
|
} |
|
|
|
/// Add new static property to the constructor object itself. |
|
pub fn static_property_descriptor<K, P>(&mut self, key: K, property: P) -> &mut Self |
|
where |
|
K: Into<PropertyKey>, |
|
P: Into<PropertyDescriptor>, |
|
{ |
|
let property = property.into(); |
|
self.constructor_object.insert(key, property); |
|
self |
|
} |
|
|
|
/// Specify how many arguments the constructor function takes. |
|
/// |
|
/// Default is `0`. |
|
#[inline] |
|
pub fn length(&mut self, length: usize) -> &mut Self { |
|
self.length = length; |
|
self |
|
} |
|
|
|
/// Specify the name of the constructor function. |
|
/// |
|
/// Default is `"[object]"` |
|
pub fn name<N>(&mut self, name: N) -> &mut Self |
|
where |
|
N: AsRef<str>, |
|
{ |
|
self.name = name.as_ref().into(); |
|
self |
|
} |
|
|
|
/// Specify whether the constructor function can be called. |
|
/// |
|
/// Default is `true` |
|
#[inline] |
|
pub fn callable(&mut self, callable: bool) -> &mut Self { |
|
self.callable = callable; |
|
self |
|
} |
|
|
|
/// Specify whether the constructor function can be called with `new` keyword. |
|
/// |
|
/// Default is `true` |
|
#[inline] |
|
pub fn constructor(&mut self, constructor: bool) -> &mut Self { |
|
self.kind = constructor.then_some(ConstructorKind::Base); |
|
self |
|
} |
|
|
|
/// Specify the parent prototype which objects created by this constructor |
|
/// inherit from. |
|
/// |
|
/// Default is `Object.prototype` |
|
pub fn inherit<O: Into<JsPrototype>>(&mut self, prototype: O) -> &mut Self { |
|
self.inherit = Some(prototype.into()); |
|
self |
|
} |
|
|
|
/// Specify the `[[Prototype]]` internal field of this constructor. |
|
/// |
|
/// Default is `Function.prototype` |
|
pub fn custom_prototype<O: Into<JsPrototype>>(&mut self, prototype: O) -> &mut Self { |
|
self.custom_prototype = Some(prototype.into()); |
|
self |
|
} |
|
|
|
/// Specify whether the constructor function has a 'prototype' property. |
|
/// |
|
/// Default is `true` |
|
#[inline] |
|
pub fn has_prototype_property(&mut self, has_prototype_property: bool) -> &mut Self { |
|
self.has_prototype_property = has_prototype_property; |
|
self |
|
} |
|
|
|
/// Return the current context. |
|
#[inline] |
|
pub fn context(&mut self) -> &mut Context { |
|
self.context |
|
} |
|
|
|
/// Build the constructor function object. |
|
#[must_use] |
|
pub fn build(mut self) -> StandardConstructor { |
|
let length = PropertyDescriptor::builder() |
|
.value(self.length) |
|
.writable(false) |
|
.enumerable(false) |
|
.configurable(true); |
|
let name = PropertyDescriptor::builder() |
|
.value(self.name.clone()) |
|
.writable(false) |
|
.enumerable(false) |
|
.configurable(true); |
|
|
|
let prototype = { |
|
if let Some(proto) = self.inherit.take() { |
|
self.prototype.set_prototype(proto); |
|
} else { |
|
self.prototype.set_prototype( |
|
self.context |
|
.intrinsics() |
|
.constructors() |
|
.object() |
|
.prototype(), |
|
); |
|
} |
|
|
|
JsObject::from_object_and_vtable(self.prototype, &ORDINARY_INTERNAL_METHODS) |
|
}; |
|
|
|
let constructor = { |
|
let mut constructor = Object { |
|
properties: self.constructor_object.properties, |
|
extensible: self.constructor_object.extensible, |
|
private_elements: self.constructor_object.private_elements, |
|
data: NativeFunctionObject { |
|
f: self.function, |
|
constructor: self.kind, |
|
realm: Some(self.context.realm().clone()), |
|
}, |
|
}; |
|
|
|
constructor.insert(StaticJsStrings::LENGTH, length); |
|
constructor.insert(utf16!("name"), name); |
|
|
|
if let Some(proto) = self.custom_prototype.take() { |
|
constructor.set_prototype(proto); |
|
} else { |
|
constructor.set_prototype( |
|
self.context |
|
.intrinsics() |
|
.constructors() |
|
.function() |
|
.prototype(), |
|
); |
|
} |
|
|
|
if self.has_prototype_property { |
|
constructor.insert( |
|
PROTOTYPE, |
|
PropertyDescriptor::builder() |
|
.value(prototype.clone()) |
|
.writable(false) |
|
.enumerable(false) |
|
.configurable(false), |
|
); |
|
} |
|
|
|
let internal_methods = constructor.data.internal_methods(); |
|
JsObject::from_object_and_vtable(constructor, internal_methods) |
|
}; |
|
|
|
{ |
|
let mut prototype = prototype.borrow_mut(); |
|
prototype.insert( |
|
CONSTRUCTOR, |
|
PropertyDescriptor::builder() |
|
.value(constructor.clone()) |
|
.writable(true) |
|
.enumerable(false) |
|
.configurable(true), |
|
); |
|
} |
|
|
|
StandardConstructor::new(JsFunction::from_object_unchecked(constructor), prototype) |
|
} |
|
}
|
|
|