mirror of https://github.com/boa-dev/boa.git
Browse Source
* Implement `InternalObjectMethods` struct for `Object` * Implement remaining string internal methods * Split object internal methods and operations in modules * Store internal object methods as static struct * Document `ObjectData` and order indices on `[[OwnPropertyKeys]]` * Document and rearrange internal object methodspull/1518/head
jedel1043
3 years ago
committed by
GitHub
45 changed files with 2609 additions and 1381 deletions
@ -1,983 +0,0 @@ |
|||||||
//! This module defines the object internal methods.
|
|
||||||
//!
|
|
||||||
//! More information:
|
|
||||||
//! - [ECMAScript reference][spec]
|
|
||||||
//!
|
|
||||||
//! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
|
|
||||||
|
|
||||||
use crate::{ |
|
||||||
builtins::Array, |
|
||||||
object::{JsObject, Object, ObjectData}, |
|
||||||
property::{DescriptorKind, PropertyDescriptor, PropertyKey, PropertyNameKind}, |
|
||||||
value::{JsValue, Type}, |
|
||||||
BoaProfiler, Context, JsResult, |
|
||||||
}; |
|
||||||
|
|
||||||
impl JsObject { |
|
||||||
/// Check if object has property.
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-hasproperty
|
|
||||||
// NOTE: for now context is not used but it will in the future.
|
|
||||||
#[inline] |
|
||||||
pub fn has_property<K>(&self, key: K, _context: &mut Context) -> JsResult<bool> |
|
||||||
where |
|
||||||
K: Into<PropertyKey>, |
|
||||||
{ |
|
||||||
// 1. Assert: Type(O) is Object.
|
|
||||||
// 2. Assert: IsPropertyKey(P) is true.
|
|
||||||
// 3. Return ? O.[[HasProperty]](P).
|
|
||||||
Ok(self.__has_property__(&key.into())) |
|
||||||
} |
|
||||||
|
|
||||||
/// Check if it is extensible.
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-isextensible-o
|
|
||||||
// NOTE: for now context is not used but it will in the future.
|
|
||||||
#[inline] |
|
||||||
pub fn is_extensible(&self, _context: &mut Context) -> JsResult<bool> { |
|
||||||
// 1. Assert: Type(O) is Object.
|
|
||||||
// 2. Return ? O.[[IsExtensible]]().
|
|
||||||
Ok(self.__is_extensible__()) |
|
||||||
} |
|
||||||
|
|
||||||
/// Delete property, if deleted return `true`.
|
|
||||||
#[inline] |
|
||||||
pub fn delete<K>(&self, key: K) -> bool |
|
||||||
where |
|
||||||
K: Into<PropertyKey>, |
|
||||||
{ |
|
||||||
self.__delete__(&key.into()) |
|
||||||
} |
|
||||||
|
|
||||||
/// Defines the property or throws a `TypeError` if the operation fails.
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [EcmaScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow
|
|
||||||
#[inline] |
|
||||||
pub fn delete_property_or_throw<K>(&self, key: K, context: &mut Context) -> JsResult<bool> |
|
||||||
where |
|
||||||
K: Into<PropertyKey>, |
|
||||||
{ |
|
||||||
let key = key.into(); |
|
||||||
// 1. Assert: Type(O) is Object.
|
|
||||||
// 2. Assert: IsPropertyKey(P) is true.
|
|
||||||
// 3. Let success be ? O.[[Delete]](P).
|
|
||||||
let success = self.__delete__(&key); |
|
||||||
// 4. If success is false, throw a TypeError exception.
|
|
||||||
if !success { |
|
||||||
return Err(context.construct_type_error(format!("cannot delete property: {}", key))); |
|
||||||
} |
|
||||||
// 5. Return success.
|
|
||||||
Ok(success) |
|
||||||
} |
|
||||||
|
|
||||||
/// Check if object has an own property.
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-hasownproperty
|
|
||||||
#[inline] |
|
||||||
pub fn has_own_property<K>(&self, key: K, _context: &mut Context) -> JsResult<bool> |
|
||||||
where |
|
||||||
K: Into<PropertyKey>, |
|
||||||
{ |
|
||||||
let key = key.into(); |
|
||||||
// 1. Assert: Type(O) is Object.
|
|
||||||
// 2. Assert: IsPropertyKey(P) is true.
|
|
||||||
// 3. Let desc be ? O.[[GetOwnProperty]](P).
|
|
||||||
let desc = self.__get_own_property__(&key); |
|
||||||
// 4. If desc is undefined, return false.
|
|
||||||
// 5. Return true.
|
|
||||||
Ok(desc.is_some()) |
|
||||||
} |
|
||||||
|
|
||||||
/// Get property from object or throw.
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-get-o-p
|
|
||||||
#[inline] |
|
||||||
pub fn get<K>(&self, key: K, context: &mut Context) -> JsResult<JsValue> |
|
||||||
where |
|
||||||
K: Into<PropertyKey>, |
|
||||||
{ |
|
||||||
// 1. Assert: Type(O) is Object.
|
|
||||||
// 2. Assert: IsPropertyKey(P) is true.
|
|
||||||
// 3. Return ? O.[[Get]](P, O).
|
|
||||||
self.__get__(&key.into(), self.clone().into(), context) |
|
||||||
} |
|
||||||
|
|
||||||
/// set property of object or throw if bool flag is passed.
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-set-o-p-v-throw
|
|
||||||
#[inline] |
|
||||||
pub fn set<K, V>(&self, key: K, value: V, throw: bool, context: &mut Context) -> JsResult<bool> |
|
||||||
where |
|
||||||
K: Into<PropertyKey>, |
|
||||||
V: Into<JsValue>, |
|
||||||
{ |
|
||||||
let key = key.into(); |
|
||||||
// 1. Assert: Type(O) is Object.
|
|
||||||
// 2. Assert: IsPropertyKey(P) is true.
|
|
||||||
// 3. Assert: Type(Throw) is Boolean.
|
|
||||||
// 4. Let success be ? O.[[Set]](P, V, O).
|
|
||||||
let success = self.__set__(key.clone(), value.into(), self.clone().into(), context)?; |
|
||||||
// 5. If success is false and Throw is true, throw a TypeError exception.
|
|
||||||
if !success && throw { |
|
||||||
return Err( |
|
||||||
context.construct_type_error(format!("cannot set non-writable property: {}", key)) |
|
||||||
); |
|
||||||
} |
|
||||||
// 6. Return success.
|
|
||||||
Ok(success) |
|
||||||
} |
|
||||||
|
|
||||||
/// Define property or throw.
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow
|
|
||||||
#[inline] |
|
||||||
pub fn define_property_or_throw<K, P>( |
|
||||||
&self, |
|
||||||
key: K, |
|
||||||
desc: P, |
|
||||||
context: &mut Context, |
|
||||||
) -> JsResult<bool> |
|
||||||
where |
|
||||||
K: Into<PropertyKey>, |
|
||||||
P: Into<PropertyDescriptor>, |
|
||||||
{ |
|
||||||
let key = key.into(); |
|
||||||
// 1. Assert: Type(O) is Object.
|
|
||||||
// 2. Assert: IsPropertyKey(P) is true.
|
|
||||||
// 3. Let success be ? O.[[DefineOwnProperty]](P, desc).
|
|
||||||
let success = self.__define_own_property__(key.clone(), desc.into(), context)?; |
|
||||||
// 4. If success is false, throw a TypeError exception.
|
|
||||||
if !success { |
|
||||||
return Err(context.construct_type_error(format!("cannot redefine property: {}", key))); |
|
||||||
} |
|
||||||
// 5. Return success.
|
|
||||||
Ok(success) |
|
||||||
} |
|
||||||
|
|
||||||
/// Create data property
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow
|
|
||||||
pub fn create_data_property<K, V>( |
|
||||||
&self, |
|
||||||
key: K, |
|
||||||
value: V, |
|
||||||
context: &mut Context, |
|
||||||
) -> JsResult<bool> |
|
||||||
where |
|
||||||
K: Into<PropertyKey>, |
|
||||||
V: Into<JsValue>, |
|
||||||
{ |
|
||||||
// 1. Assert: Type(O) is Object.
|
|
||||||
// 2. Assert: IsPropertyKey(P) is true.
|
|
||||||
// 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
|
|
||||||
let new_desc = PropertyDescriptor::builder() |
|
||||||
.value(value) |
|
||||||
.writable(true) |
|
||||||
.enumerable(true) |
|
||||||
.configurable(true); |
|
||||||
// 4. Return ? O.[[DefineOwnProperty]](P, newDesc).
|
|
||||||
self.__define_own_property__(key.into(), new_desc.into(), context) |
|
||||||
} |
|
||||||
|
|
||||||
/// Create data property or throw
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow
|
|
||||||
pub fn create_data_property_or_throw<K, V>( |
|
||||||
&self, |
|
||||||
key: K, |
|
||||||
value: V, |
|
||||||
context: &mut Context, |
|
||||||
) -> JsResult<bool> |
|
||||||
where |
|
||||||
K: Into<PropertyKey>, |
|
||||||
V: Into<JsValue>, |
|
||||||
{ |
|
||||||
let key = key.into(); |
|
||||||
// 1. Assert: Type(O) is Object.
|
|
||||||
// 2. Assert: IsPropertyKey(P) is true.
|
|
||||||
// 3. Let success be ? CreateDataProperty(O, P, V).
|
|
||||||
let success = self.create_data_property(key.clone(), value, context)?; |
|
||||||
// 4. If success is false, throw a TypeError exception.
|
|
||||||
if !success { |
|
||||||
return Err(context.construct_type_error(format!("cannot redefine property: {}", key))); |
|
||||||
} |
|
||||||
// 5. Return success.
|
|
||||||
Ok(success) |
|
||||||
} |
|
||||||
|
|
||||||
/// `[[hasProperty]]`
|
|
||||||
#[inline] |
|
||||||
pub(crate) fn __has_property__(&self, key: &PropertyKey) -> bool { |
|
||||||
let prop = self.__get_own_property__(key); |
|
||||||
if prop.is_none() { |
|
||||||
let parent = self.__get_prototype_of__(); |
|
||||||
return if let JsValue::Object(ref object) = parent { |
|
||||||
object.__has_property__(key) |
|
||||||
} else { |
|
||||||
false |
|
||||||
}; |
|
||||||
} |
|
||||||
true |
|
||||||
} |
|
||||||
|
|
||||||
/// Check if it is extensible.
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible
|
|
||||||
#[inline] |
|
||||||
pub(crate) fn __is_extensible__(&self) -> bool { |
|
||||||
self.borrow().extensible |
|
||||||
} |
|
||||||
|
|
||||||
/// Disable extensibility.
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions
|
|
||||||
#[inline] |
|
||||||
pub fn __prevent_extensions__(&mut self) -> bool { |
|
||||||
self.borrow_mut().extensible = false; |
|
||||||
true |
|
||||||
} |
|
||||||
|
|
||||||
/// Delete property.
|
|
||||||
#[inline] |
|
||||||
pub(crate) fn __delete__(&self, key: &PropertyKey) -> bool { |
|
||||||
match self.__get_own_property__(key) { |
|
||||||
Some(desc) if desc.expect_configurable() => { |
|
||||||
self.remove(key); |
|
||||||
true |
|
||||||
} |
|
||||||
Some(_) => false, |
|
||||||
None => true, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// `[[Get]]`
|
|
||||||
pub fn __get__( |
|
||||||
&self, |
|
||||||
key: &PropertyKey, |
|
||||||
receiver: JsValue, |
|
||||||
context: &mut Context, |
|
||||||
) -> JsResult<JsValue> { |
|
||||||
match self.__get_own_property__(key) { |
|
||||||
None => { |
|
||||||
// parent will either be null or an Object
|
|
||||||
if let Some(parent) = self.__get_prototype_of__().as_object() { |
|
||||||
Ok(parent.__get__(key, receiver, context)?) |
|
||||||
} else { |
|
||||||
Ok(JsValue::undefined()) |
|
||||||
} |
|
||||||
} |
|
||||||
Some(ref desc) => match desc.kind() { |
|
||||||
DescriptorKind::Data { |
|
||||||
value: Some(value), .. |
|
||||||
} => Ok(value.clone()), |
|
||||||
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { |
|
||||||
context.call(get, &receiver, &[]) |
|
||||||
} |
|
||||||
_ => Ok(JsValue::undefined()), |
|
||||||
}, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// `[[Set]]`
|
|
||||||
pub fn __set__( |
|
||||||
&self, |
|
||||||
key: PropertyKey, |
|
||||||
value: JsValue, |
|
||||||
receiver: JsValue, |
|
||||||
context: &mut Context, |
|
||||||
) -> JsResult<bool> { |
|
||||||
let _timer = BoaProfiler::global().start_event("Object::set", "object"); |
|
||||||
|
|
||||||
// Fetch property key
|
|
||||||
let own_desc = if let Some(desc) = self.__get_own_property__(&key) { |
|
||||||
desc |
|
||||||
} else if let Some(ref mut parent) = self.__get_prototype_of__().as_object() { |
|
||||||
return parent.__set__(key, value, receiver, context); |
|
||||||
} else { |
|
||||||
PropertyDescriptor::builder() |
|
||||||
.value(JsValue::undefined()) |
|
||||||
.writable(true) |
|
||||||
.enumerable(true) |
|
||||||
.configurable(true) |
|
||||||
.build() |
|
||||||
}; |
|
||||||
|
|
||||||
if own_desc.is_data_descriptor() { |
|
||||||
if !own_desc.expect_writable() { |
|
||||||
return Ok(false); |
|
||||||
} |
|
||||||
|
|
||||||
let receiver = match receiver.as_object() { |
|
||||||
Some(obj) => obj, |
|
||||||
_ => return Ok(false), |
|
||||||
}; |
|
||||||
|
|
||||||
if let Some(ref existing_desc) = receiver.__get_own_property__(&key) { |
|
||||||
if existing_desc.is_accessor_descriptor() { |
|
||||||
return Ok(false); |
|
||||||
} |
|
||||||
if !existing_desc.expect_writable() { |
|
||||||
return Ok(false); |
|
||||||
} |
|
||||||
return receiver.__define_own_property__( |
|
||||||
key, |
|
||||||
PropertyDescriptor::builder().value(value).build(), |
|
||||||
context, |
|
||||||
); |
|
||||||
} else { |
|
||||||
return receiver.create_data_property(key, value, context); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
match own_desc.set() { |
|
||||||
Some(set) if !set.is_undefined() => { |
|
||||||
context.call(set, &receiver, &[value])?; |
|
||||||
Ok(true) |
|
||||||
} |
|
||||||
_ => Ok(false), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// `[[defineOwnProperty]]`
|
|
||||||
pub fn __define_own_property__( |
|
||||||
&self, |
|
||||||
key: PropertyKey, |
|
||||||
desc: PropertyDescriptor, |
|
||||||
context: &mut Context, |
|
||||||
) -> JsResult<bool> { |
|
||||||
if self.is_array() { |
|
||||||
self.array_define_own_property(key, desc, context) |
|
||||||
} else { |
|
||||||
Ok(self.ordinary_define_own_property(key, desc)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// Define an own property for an ordinary object.
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinarydefineownproperty
|
|
||||||
pub fn ordinary_define_own_property(&self, key: PropertyKey, desc: PropertyDescriptor) -> bool { |
|
||||||
let _timer = BoaProfiler::global().start_event("Object::define_own_property", "object"); |
|
||||||
|
|
||||||
let extensible = self.__is_extensible__(); |
|
||||||
|
|
||||||
let mut current = if let Some(own) = self.__get_own_property__(&key) { |
|
||||||
own |
|
||||||
} else { |
|
||||||
if !extensible { |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
self.insert( |
|
||||||
key, |
|
||||||
if desc.is_generic_descriptor() || desc.is_data_descriptor() { |
|
||||||
desc.into_data_defaulted() |
|
||||||
} else { |
|
||||||
desc.into_accessor_defaulted() |
|
||||||
}, |
|
||||||
); |
|
||||||
|
|
||||||
return true; |
|
||||||
}; |
|
||||||
|
|
||||||
// 3
|
|
||||||
if desc.is_empty() { |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
// 4
|
|
||||||
if !current.expect_configurable() { |
|
||||||
if matches!(desc.configurable(), Some(true)) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
if matches!(desc.enumerable(), Some(desc_enum) if desc_enum != current.expect_enumerable()) |
|
||||||
{ |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// 5
|
|
||||||
if desc.is_generic_descriptor() { |
|
||||||
// no further validation required
|
|
||||||
} else if current.is_data_descriptor() != desc.is_data_descriptor() { |
|
||||||
if !current.expect_configurable() { |
|
||||||
return false; |
|
||||||
} |
|
||||||
if current.is_data_descriptor() { |
|
||||||
current = current.into_accessor_defaulted(); |
|
||||||
} else { |
|
||||||
current = current.into_data_defaulted(); |
|
||||||
} |
|
||||||
} else if current.is_data_descriptor() && desc.is_data_descriptor() { |
|
||||||
if !current.expect_configurable() && !current.expect_writable() { |
|
||||||
if matches!(desc.writable(), Some(true)) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
if matches!(desc.value(), Some(value) if !JsValue::same_value(value, current.expect_value())) |
|
||||||
{ |
|
||||||
return false; |
|
||||||
} |
|
||||||
return true; |
|
||||||
} |
|
||||||
} else if !current.expect_configurable() { |
|
||||||
if matches!(desc.set(), Some(set) if !JsValue::same_value(set, current.expect_set())) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
if matches!(desc.get(), Some(get) if !JsValue::same_value(get, current.expect_get())) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
current.fill_with(desc); |
|
||||||
self.insert(key, current); |
|
||||||
|
|
||||||
true |
|
||||||
} |
|
||||||
|
|
||||||
/// Define an own property for an array.
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc
|
|
||||||
fn array_define_own_property( |
|
||||||
&self, |
|
||||||
key: PropertyKey, |
|
||||||
desc: PropertyDescriptor, |
|
||||||
context: &mut Context, |
|
||||||
) -> JsResult<bool> { |
|
||||||
match key { |
|
||||||
PropertyKey::String(ref s) if s == "length" => { |
|
||||||
let new_len_val = match desc.value() { |
|
||||||
Some(value) => value, |
|
||||||
_ => return Ok(self.ordinary_define_own_property("length".into(), desc)), |
|
||||||
}; |
|
||||||
|
|
||||||
let new_len = new_len_val.to_u32(context)?; |
|
||||||
let number_len = new_len_val.to_number(context)?; |
|
||||||
|
|
||||||
#[allow(clippy::float_cmp)] |
|
||||||
if new_len as f64 != number_len { |
|
||||||
return Err(context.construct_range_error("bad length for array")); |
|
||||||
} |
|
||||||
|
|
||||||
let mut new_len_desc = PropertyDescriptor::builder() |
|
||||||
.value(new_len) |
|
||||||
.maybe_writable(desc.writable()) |
|
||||||
.maybe_enumerable(desc.enumerable()) |
|
||||||
.maybe_configurable(desc.configurable()); |
|
||||||
let old_len_desc = self.__get_own_property__(&"length".into()).unwrap(); |
|
||||||
let old_len = old_len_desc.expect_value(); |
|
||||||
if new_len >= old_len.to_u32(context)? { |
|
||||||
return Ok( |
|
||||||
self.ordinary_define_own_property("length".into(), new_len_desc.build()) |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
if !old_len_desc.expect_writable() { |
|
||||||
return Ok(false); |
|
||||||
} |
|
||||||
|
|
||||||
let new_writable = if new_len_desc.inner().writable().unwrap_or(true) { |
|
||||||
true |
|
||||||
} else { |
|
||||||
new_len_desc = new_len_desc.writable(true); |
|
||||||
false |
|
||||||
}; |
|
||||||
|
|
||||||
if !self.ordinary_define_own_property("length".into(), new_len_desc.clone().build()) |
|
||||||
{ |
|
||||||
return Ok(false); |
|
||||||
} |
|
||||||
|
|
||||||
let max_value = self |
|
||||||
.borrow() |
|
||||||
.properties |
|
||||||
.index_property_keys() |
|
||||||
.max() |
|
||||||
.copied(); |
|
||||||
|
|
||||||
if let Some(mut index) = max_value { |
|
||||||
while index >= new_len { |
|
||||||
let contains_index = self.borrow().properties.contains_key(&index.into()); |
|
||||||
if contains_index && !self.__delete__(&index.into()) { |
|
||||||
new_len_desc = new_len_desc.value(index + 1); |
|
||||||
if !new_writable { |
|
||||||
new_len_desc = new_len_desc.writable(false); |
|
||||||
} |
|
||||||
self.ordinary_define_own_property( |
|
||||||
"length".into(), |
|
||||||
new_len_desc.build(), |
|
||||||
); |
|
||||||
return Ok(false); |
|
||||||
} |
|
||||||
|
|
||||||
index = if let Some(sub) = index.checked_sub(1) { |
|
||||||
sub |
|
||||||
} else { |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if !new_writable { |
|
||||||
self.ordinary_define_own_property( |
|
||||||
"length".into(), |
|
||||||
PropertyDescriptor::builder().writable(false).build(), |
|
||||||
); |
|
||||||
} |
|
||||||
Ok(true) |
|
||||||
} |
|
||||||
PropertyKey::Index(index) => { |
|
||||||
let old_len_desc = self.__get_own_property__(&"length".into()).unwrap(); |
|
||||||
let old_len = old_len_desc.expect_value().to_u32(context)?; |
|
||||||
if index >= old_len && !old_len_desc.expect_writable() { |
|
||||||
return Ok(false); |
|
||||||
} |
|
||||||
if self.ordinary_define_own_property(key, desc) { |
|
||||||
if index >= old_len && index < u32::MAX { |
|
||||||
let desc = PropertyDescriptor::builder() |
|
||||||
.value(index + 1) |
|
||||||
.maybe_writable(old_len_desc.writable()) |
|
||||||
.maybe_enumerable(old_len_desc.enumerable()) |
|
||||||
.maybe_configurable(old_len_desc.configurable()); |
|
||||||
self.ordinary_define_own_property("length".into(), desc.into()); |
|
||||||
} |
|
||||||
Ok(true) |
|
||||||
} else { |
|
||||||
Ok(false) |
|
||||||
} |
|
||||||
} |
|
||||||
_ => Ok(self.ordinary_define_own_property(key, desc)), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// Gets own property of 'Object'
|
|
||||||
///
|
|
||||||
#[inline] |
|
||||||
pub fn __get_own_property__(&self, key: &PropertyKey) -> Option<PropertyDescriptor> { |
|
||||||
let _timer = BoaProfiler::global().start_event("Object::get_own_property", "object"); |
|
||||||
|
|
||||||
let object = self.borrow(); |
|
||||||
match object.data { |
|
||||||
ObjectData::String(_) => self.string_exotic_get_own_property(key), |
|
||||||
_ => self.ordinary_get_own_property(key), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// StringGetOwnProperty abstract operation
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-stringgetownproperty
|
|
||||||
#[inline] |
|
||||||
pub fn string_get_own_property(&self, key: &PropertyKey) -> Option<PropertyDescriptor> { |
|
||||||
let object = self.borrow(); |
|
||||||
|
|
||||||
match key { |
|
||||||
PropertyKey::Index(index) => { |
|
||||||
let string = object.as_string().unwrap(); |
|
||||||
let pos = *index as usize; |
|
||||||
|
|
||||||
if pos >= string.len() { |
|
||||||
return None; |
|
||||||
} |
|
||||||
|
|
||||||
let result_str = string |
|
||||||
.encode_utf16() |
|
||||||
.nth(pos) |
|
||||||
.map(|utf16_val| JsValue::from(String::from_utf16_lossy(&[utf16_val])))?; |
|
||||||
|
|
||||||
let desc = PropertyDescriptor::builder() |
|
||||||
.value(result_str) |
|
||||||
.writable(false) |
|
||||||
.enumerable(true) |
|
||||||
.configurable(false) |
|
||||||
.build(); |
|
||||||
|
|
||||||
Some(desc) |
|
||||||
} |
|
||||||
_ => None, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// Gets own property of 'String' exotic object
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects-getownproperty-p
|
|
||||||
#[inline] |
|
||||||
pub fn string_exotic_get_own_property(&self, key: &PropertyKey) -> Option<PropertyDescriptor> { |
|
||||||
let desc = self.ordinary_get_own_property(key); |
|
||||||
|
|
||||||
if desc.is_some() { |
|
||||||
desc |
|
||||||
} else { |
|
||||||
self.string_get_own_property(key) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// The specification returns a Property Descriptor or Undefined.
|
|
||||||
///
|
|
||||||
/// These are 2 separate types and we can't do that here.
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p
|
|
||||||
#[inline] |
|
||||||
pub fn ordinary_get_own_property(&self, key: &PropertyKey) -> Option<PropertyDescriptor> { |
|
||||||
let object = self.borrow(); |
|
||||||
let property = object.properties.get(key); |
|
||||||
|
|
||||||
property.cloned() |
|
||||||
} |
|
||||||
|
|
||||||
/// Essential internal method OwnPropertyKeys
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#table-essential-internal-methods
|
|
||||||
#[inline] |
|
||||||
#[track_caller] |
|
||||||
pub fn own_property_keys(&self) -> Vec<PropertyKey> { |
|
||||||
self.borrow().properties.keys().collect() |
|
||||||
} |
|
||||||
|
|
||||||
/// The abstract operation ObjectDefineProperties
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-object.defineproperties
|
|
||||||
#[inline] |
|
||||||
pub fn define_properties(&mut self, props: JsValue, context: &mut Context) -> JsResult<()> { |
|
||||||
let props = &props.to_object(context)?; |
|
||||||
let keys = props.own_property_keys(); |
|
||||||
let mut descriptors: Vec<(PropertyKey, PropertyDescriptor)> = Vec::new(); |
|
||||||
|
|
||||||
for next_key in keys { |
|
||||||
if let Some(prop_desc) = props.__get_own_property__(&next_key) { |
|
||||||
if prop_desc.expect_enumerable() { |
|
||||||
let desc_obj = props.get(next_key.clone(), context)?; |
|
||||||
let desc = desc_obj.to_property_descriptor(context)?; |
|
||||||
descriptors.push((next_key, desc)); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for (p, d) in descriptors { |
|
||||||
self.define_property_or_throw(p, d, context)?; |
|
||||||
} |
|
||||||
|
|
||||||
Ok(()) |
|
||||||
} |
|
||||||
|
|
||||||
/// `Object.setPropertyOf(obj, prototype)`
|
|
||||||
///
|
|
||||||
/// This method sets the prototype (i.e., the internal `[[Prototype]]` property)
|
|
||||||
/// of a specified object to another object or `null`.
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
/// - [MDN documentation][mdn]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v
|
|
||||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf
|
|
||||||
#[inline] |
|
||||||
pub fn __set_prototype_of__(&mut self, val: JsValue) -> bool { |
|
||||||
debug_assert!(val.is_object() || val.is_null()); |
|
||||||
let current = self.__get_prototype_of__(); |
|
||||||
if JsValue::same_value(¤t, &val) { |
|
||||||
return true; |
|
||||||
} |
|
||||||
if !self.__is_extensible__() { |
|
||||||
return false; |
|
||||||
} |
|
||||||
let mut p = val.clone(); |
|
||||||
let mut done = false; |
|
||||||
while !done { |
|
||||||
if p.is_null() { |
|
||||||
done = true |
|
||||||
} else if JsValue::same_value(&JsValue::new(self.clone()), &p) { |
|
||||||
return false; |
|
||||||
} else { |
|
||||||
let prototype = p |
|
||||||
.as_object() |
|
||||||
.expect("prototype should be null or object") |
|
||||||
.__get_prototype_of__(); |
|
||||||
p = prototype; |
|
||||||
} |
|
||||||
} |
|
||||||
self.set_prototype_instance(val); |
|
||||||
true |
|
||||||
} |
|
||||||
|
|
||||||
/// Returns either the prototype or null
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [ECMAScript reference][spec]
|
|
||||||
/// - [MDN documentation][mdn]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof
|
|
||||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf
|
|
||||||
#[inline] |
|
||||||
#[track_caller] |
|
||||||
pub fn __get_prototype_of__(&self) -> JsValue { |
|
||||||
self.borrow().prototype.clone() |
|
||||||
} |
|
||||||
|
|
||||||
/// Helper function for property insertion.
|
|
||||||
#[inline] |
|
||||||
#[track_caller] |
|
||||||
pub(crate) fn insert<K, P>(&self, key: K, property: P) -> Option<PropertyDescriptor> |
|
||||||
where |
|
||||||
K: Into<PropertyKey>, |
|
||||||
P: Into<PropertyDescriptor>, |
|
||||||
{ |
|
||||||
self.borrow_mut().insert(key, property) |
|
||||||
} |
|
||||||
|
|
||||||
/// Helper function for property removal.
|
|
||||||
#[inline] |
|
||||||
#[track_caller] |
|
||||||
pub(crate) fn remove(&self, key: &PropertyKey) -> Option<PropertyDescriptor> { |
|
||||||
self.borrow_mut().remove(key) |
|
||||||
} |
|
||||||
|
|
||||||
/// 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 that a `Some` is returned
|
|
||||||
/// with that field, otherwise None is returned.
|
|
||||||
#[inline] |
|
||||||
pub fn insert_property<K, P>(&self, key: K, property: P) -> Option<PropertyDescriptor> |
|
||||||
where |
|
||||||
K: Into<PropertyKey>, |
|
||||||
P: Into<PropertyDescriptor>, |
|
||||||
{ |
|
||||||
self.insert(key.into(), property) |
|
||||||
} |
|
||||||
|
|
||||||
/// It determines if Object is a callable function with a `[[Call]]` internal method.
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [EcmaScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-iscallable
|
|
||||||
#[inline] |
|
||||||
#[track_caller] |
|
||||||
pub fn is_callable(&self) -> bool { |
|
||||||
self.borrow().is_callable() |
|
||||||
} |
|
||||||
|
|
||||||
/// It determines if Object is a function object with a `[[Construct]]` internal method.
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [EcmaScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-isconstructor
|
|
||||||
#[inline] |
|
||||||
#[track_caller] |
|
||||||
pub fn is_constructable(&self) -> bool { |
|
||||||
self.borrow().is_constructable() |
|
||||||
} |
|
||||||
|
|
||||||
/// Returns true if the JsObject is the global for a Realm
|
|
||||||
pub fn is_global(&self) -> bool { |
|
||||||
matches!(self.borrow().data, ObjectData::Global) |
|
||||||
} |
|
||||||
|
|
||||||
/// It is used to create List value whose elements are provided by the indexed properties of
|
|
||||||
/// self.
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [EcmaScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-createlistfromarraylike
|
|
||||||
pub(crate) fn create_list_from_array_like( |
|
||||||
&self, |
|
||||||
element_types: &[Type], |
|
||||||
context: &mut Context, |
|
||||||
) -> JsResult<Vec<JsValue>> { |
|
||||||
// 1. If elementTypes is not present, set elementTypes to « Undefined, Null, Boolean, String, Symbol, Number, BigInt, Object ».
|
|
||||||
let types = if element_types.is_empty() { |
|
||||||
&[ |
|
||||||
Type::Undefined, |
|
||||||
Type::Null, |
|
||||||
Type::Boolean, |
|
||||||
Type::String, |
|
||||||
Type::Symbol, |
|
||||||
Type::Number, |
|
||||||
Type::BigInt, |
|
||||||
Type::Object, |
|
||||||
] |
|
||||||
} else { |
|
||||||
element_types |
|
||||||
}; |
|
||||||
|
|
||||||
// TODO: 2. If Type(obj) is not Object, throw a TypeError exception.
|
|
||||||
|
|
||||||
// 3. Let len be ? LengthOfArrayLike(obj).
|
|
||||||
let len = self.length_of_array_like(context)?; |
|
||||||
|
|
||||||
// 4. Let list be a new empty List.
|
|
||||||
let mut list = Vec::with_capacity(len); |
|
||||||
|
|
||||||
// 5. Let index be 0.
|
|
||||||
// 6. Repeat, while index < len,
|
|
||||||
for index in 0..len { |
|
||||||
// a. Let indexName be ! ToString(𝔽(index)).
|
|
||||||
// b. Let next be ? Get(obj, indexName).
|
|
||||||
let next = self.get(index, context)?; |
|
||||||
// c. If Type(next) is not an element of elementTypes, throw a TypeError exception.
|
|
||||||
if !types.contains(&next.get_type()) { |
|
||||||
return Err(context.construct_type_error("bad type")); |
|
||||||
} |
|
||||||
// d. Append next as the last element of list.
|
|
||||||
list.push(next.clone()); |
|
||||||
// e. Set index to index + 1.
|
|
||||||
} |
|
||||||
|
|
||||||
// 7. Return list.
|
|
||||||
Ok(list) |
|
||||||
} |
|
||||||
|
|
||||||
/// It is used to iterate over names of object's keys.
|
|
||||||
///
|
|
||||||
/// More information:
|
|
||||||
/// - [EcmaScript reference][spec]
|
|
||||||
///
|
|
||||||
/// [spec]: https://tc39.es/ecma262/#sec-enumerableownpropertynames
|
|
||||||
pub(crate) fn enumerable_own_property_names( |
|
||||||
&self, |
|
||||||
kind: PropertyNameKind, |
|
||||||
context: &mut Context, |
|
||||||
) -> JsResult<Vec<JsValue>> { |
|
||||||
// 1. Assert: Type(O) is Object.
|
|
||||||
// 2. Let ownKeys be ? O.[[OwnPropertyKeys]]().
|
|
||||||
let own_keys = self.own_property_keys(); |
|
||||||
// 3. Let properties be a new empty List.
|
|
||||||
let mut properties = vec![]; |
|
||||||
|
|
||||||
// 4. For each element key of ownKeys, do
|
|
||||||
for key in own_keys { |
|
||||||
// a. If Type(key) is String, then
|
|
||||||
let key_str = match &key { |
|
||||||
PropertyKey::String(s) => Some(s.clone()), |
|
||||||
PropertyKey::Index(i) => Some(i.to_string().into()), |
|
||||||
_ => None, |
|
||||||
}; |
|
||||||
|
|
||||||
if let Some(key_str) = key_str { |
|
||||||
// i. Let desc be ? O.[[GetOwnProperty]](key).
|
|
||||||
let desc = self.__get_own_property__(&key); |
|
||||||
// ii. If desc is not undefined and desc.[[Enumerable]] is true, then
|
|
||||||
if let Some(desc) = desc { |
|
||||||
if desc.expect_enumerable() { |
|
||||||
match kind { |
|
||||||
// 1. If kind is key, append key to properties.
|
|
||||||
PropertyNameKind::Key => properties.push(key_str.into()), |
|
||||||
// 2. Else,
|
|
||||||
// a. Let value be ? Get(O, key).
|
|
||||||
// b. If kind is value, append value to properties.
|
|
||||||
PropertyNameKind::Value => { |
|
||||||
properties.push(self.get(key.clone(), context)?) |
|
||||||
} |
|
||||||
// c. Else,
|
|
||||||
// i. Assert: kind is key+value.
|
|
||||||
// ii. Let entry be ! CreateArrayFromList(« key, value »).
|
|
||||||
// iii. Append entry to properties.
|
|
||||||
PropertyNameKind::KeyAndValue => properties.push( |
|
||||||
Array::create_array_from_list( |
|
||||||
[key_str.into(), self.get(key.clone(), context)?], |
|
||||||
context, |
|
||||||
) |
|
||||||
.into(), |
|
||||||
), |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// 5. Return properties.
|
|
||||||
Ok(properties) |
|
||||||
} |
|
||||||
|
|
||||||
pub(crate) fn length_of_array_like(&self, context: &mut Context) -> JsResult<usize> { |
|
||||||
// 1. Assert: Type(obj) is Object.
|
|
||||||
// 2. Return ℝ(? ToLength(? Get(obj, "length"))).
|
|
||||||
self.get("length", context)?.to_length(context) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl Object { |
|
||||||
/// Helper function for property insertion.
|
|
||||||
#[inline] |
|
||||||
pub(crate) fn insert<K, P>(&mut self, key: K, property: P) -> Option<PropertyDescriptor> |
|
||||||
where |
|
||||||
K: Into<PropertyKey>, |
|
||||||
P: Into<PropertyDescriptor>, |
|
||||||
{ |
|
||||||
self.properties.insert(key.into(), property.into()) |
|
||||||
} |
|
||||||
|
|
||||||
/// Helper function for property removal.
|
|
||||||
#[inline] |
|
||||||
pub(crate) fn remove(&mut self, key: &PropertyKey) -> Option<PropertyDescriptor> { |
|
||||||
self.properties.remove(key) |
|
||||||
} |
|
||||||
|
|
||||||
/// 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 that a `Some` is returned
|
|
||||||
/// with that field, otherwise None is retuned.
|
|
||||||
#[inline] |
|
||||||
pub fn insert_property<K, P>(&mut self, key: K, property: P) -> Option<PropertyDescriptor> |
|
||||||
where |
|
||||||
K: Into<PropertyKey>, |
|
||||||
P: Into<PropertyDescriptor>, |
|
||||||
{ |
|
||||||
self.insert(key, property) |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,248 @@ |
|||||||
|
use crate::{ |
||||||
|
object::JsObject, |
||||||
|
property::{PropertyDescriptor, PropertyKey}, |
||||||
|
Context, JsResult, |
||||||
|
}; |
||||||
|
|
||||||
|
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; |
||||||
|
|
||||||
|
/// Definitions of the internal object methods for array exotic objects.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects
|
||||||
|
pub(crate) static ARRAY_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { |
||||||
|
__define_own_property__: array_exotic_define_own_property, |
||||||
|
..ORDINARY_INTERNAL_METHODS |
||||||
|
}; |
||||||
|
|
||||||
|
/// Define an own property for an array exotic object.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc
|
||||||
|
pub(crate) fn array_exotic_define_own_property( |
||||||
|
obj: &JsObject, |
||||||
|
key: PropertyKey, |
||||||
|
desc: PropertyDescriptor, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> { |
||||||
|
// 1. Assert: IsPropertyKey(P) is true.
|
||||||
|
match key { |
||||||
|
// 2. If P is "length", then
|
||||||
|
PropertyKey::String(ref s) if s == "length" => { |
||||||
|
// a. Return ? ArraySetLength(A, Desc).
|
||||||
|
|
||||||
|
// Abstract operation `ArraySetLength ( A, Desc )`
|
||||||
|
//
|
||||||
|
// https://tc39.es/ecma262/#sec-arraysetlength
|
||||||
|
|
||||||
|
// 1. If Desc.[[Value]] is absent, then
|
||||||
|
let new_len_val = match desc.value() { |
||||||
|
Some(value) => value, |
||||||
|
_ => { |
||||||
|
// a. Return OrdinaryDefineOwnProperty(A, "length", Desc).
|
||||||
|
return super::ordinary_define_own_property( |
||||||
|
obj, |
||||||
|
"length".into(), |
||||||
|
desc, |
||||||
|
context, |
||||||
|
); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// 3. Let newLen be ? ToUint32(Desc.[[Value]]).
|
||||||
|
let new_len = new_len_val.to_u32(context)?; |
||||||
|
|
||||||
|
// 4. Let numberLen be ? ToNumber(Desc.[[Value]]).
|
||||||
|
let number_len = new_len_val.to_number(context)?; |
||||||
|
|
||||||
|
// 5. If SameValueZero(newLen, numberLen) is false, throw a RangeError exception.
|
||||||
|
#[allow(clippy::float_cmp)] |
||||||
|
if new_len as f64 != number_len { |
||||||
|
return Err(context.construct_range_error("bad length for array")); |
||||||
|
} |
||||||
|
|
||||||
|
// 2. Let newLenDesc be a copy of Desc.
|
||||||
|
// 6. Set newLenDesc.[[Value]] to newLen.
|
||||||
|
let mut new_len_desc = PropertyDescriptor::builder() |
||||||
|
.value(new_len) |
||||||
|
.maybe_writable(desc.writable()) |
||||||
|
.maybe_enumerable(desc.enumerable()) |
||||||
|
.maybe_configurable(desc.configurable()); |
||||||
|
|
||||||
|
// 7. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length").
|
||||||
|
let old_len_desc = |
||||||
|
super::ordinary_get_own_property(obj, &"length".into(), context)?.unwrap(); |
||||||
|
|
||||||
|
// 8. Assert: ! IsDataDescriptor(oldLenDesc) is true.
|
||||||
|
debug_assert!(old_len_desc.is_data_descriptor()); |
||||||
|
|
||||||
|
// 9. Assert: oldLenDesc.[[Configurable]] is false.
|
||||||
|
debug_assert!(!old_len_desc.expect_configurable()); |
||||||
|
|
||||||
|
// 10. Let oldLen be oldLenDesc.[[Value]].
|
||||||
|
let old_len = old_len_desc.expect_value(); |
||||||
|
|
||||||
|
// 11. If newLen ≥ oldLen, then
|
||||||
|
if new_len >= old_len.to_u32(context)? { |
||||||
|
// a. Return OrdinaryDefineOwnProperty(A, "length", newLenDesc).
|
||||||
|
return super::ordinary_define_own_property( |
||||||
|
obj, |
||||||
|
"length".into(), |
||||||
|
new_len_desc.build(), |
||||||
|
context, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// 12. If oldLenDesc.[[Writable]] is false, return false.
|
||||||
|
if !old_len_desc.expect_writable() { |
||||||
|
return Ok(false); |
||||||
|
} |
||||||
|
|
||||||
|
// 13. If newLenDesc.[[Writable]] is absent or has the value true, let newWritable be true.
|
||||||
|
let new_writable = if new_len_desc.inner().writable().unwrap_or(true) { |
||||||
|
true |
||||||
|
} |
||||||
|
// 14. Else,
|
||||||
|
else { |
||||||
|
// a. NOTE: Setting the [[Writable]] attribute to false is deferred in case any
|
||||||
|
// elements cannot be deleted.
|
||||||
|
// c. Set newLenDesc.[[Writable]] to true.
|
||||||
|
new_len_desc = new_len_desc.writable(true); |
||||||
|
|
||||||
|
// b. Let newWritable be false.
|
||||||
|
false |
||||||
|
}; |
||||||
|
|
||||||
|
// 15. Let succeeded be ! OrdinaryDefineOwnProperty(A, "length", newLenDesc).
|
||||||
|
// 16. If succeeded is false, return false.
|
||||||
|
if !super::ordinary_define_own_property( |
||||||
|
obj, |
||||||
|
"length".into(), |
||||||
|
new_len_desc.clone().build(), |
||||||
|
context, |
||||||
|
) |
||||||
|
.unwrap() |
||||||
|
{ |
||||||
|
return Ok(false); |
||||||
|
} |
||||||
|
|
||||||
|
// 17. For each own property key P of A that is an array index, whose numeric value is
|
||||||
|
// greater than or equal to newLen, in descending numeric index order, do
|
||||||
|
let ordered_keys = { |
||||||
|
let mut keys: Vec<_> = obj |
||||||
|
.borrow() |
||||||
|
.properties |
||||||
|
.index_property_keys() |
||||||
|
.filter(|idx| new_len <= **idx && **idx < u32::MAX) |
||||||
|
.copied() |
||||||
|
.collect(); |
||||||
|
keys.sort_unstable_by(|x, y| y.cmp(x)); |
||||||
|
keys |
||||||
|
}; |
||||||
|
|
||||||
|
for index in ordered_keys { |
||||||
|
// a. Let deleteSucceeded be ! A.[[Delete]](P).
|
||||||
|
// b. If deleteSucceeded is false, then
|
||||||
|
if !obj.__delete__(&index.into(), context)? { |
||||||
|
// i. Set newLenDesc.[[Value]] to ! ToUint32(P) + 1𝔽.
|
||||||
|
new_len_desc = new_len_desc.value(index + 1); |
||||||
|
|
||||||
|
// ii. If newWritable is false, set newLenDesc.[[Writable]] to false.
|
||||||
|
if !new_writable { |
||||||
|
new_len_desc = new_len_desc.writable(false); |
||||||
|
} |
||||||
|
|
||||||
|
// iii. Perform ! OrdinaryDefineOwnProperty(A, "length", newLenDesc).
|
||||||
|
super::ordinary_define_own_property( |
||||||
|
obj, |
||||||
|
"length".into(), |
||||||
|
new_len_desc.build(), |
||||||
|
context, |
||||||
|
) |
||||||
|
.unwrap(); |
||||||
|
|
||||||
|
// iv. Return false.
|
||||||
|
return Ok(false); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 18. If newWritable is false, then
|
||||||
|
if !new_writable { |
||||||
|
// a. Set succeeded to ! OrdinaryDefineOwnProperty(A, "length",
|
||||||
|
// PropertyDescriptor { [[Writable]]: false }).
|
||||||
|
let succeeded = super::ordinary_define_own_property( |
||||||
|
obj, |
||||||
|
"length".into(), |
||||||
|
PropertyDescriptor::builder().writable(false).build(), |
||||||
|
context, |
||||||
|
)?; |
||||||
|
|
||||||
|
// b. Assert: succeeded is true.
|
||||||
|
debug_assert!(succeeded); |
||||||
|
} |
||||||
|
|
||||||
|
// 19. Return true.
|
||||||
|
Ok(true) |
||||||
|
} |
||||||
|
|
||||||
|
// 3. Else if P is an array index, then
|
||||||
|
PropertyKey::Index(index) if index < u32::MAX => { |
||||||
|
// a. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length").
|
||||||
|
let old_len_desc = |
||||||
|
super::ordinary_get_own_property(obj, &"length".into(), context)?.unwrap(); |
||||||
|
|
||||||
|
// b. Assert: ! IsDataDescriptor(oldLenDesc) is true.
|
||||||
|
debug_assert!(old_len_desc.is_data_descriptor()); |
||||||
|
|
||||||
|
// c. Assert: oldLenDesc.[[Configurable]] is false.
|
||||||
|
debug_assert!(!old_len_desc.expect_configurable()); |
||||||
|
|
||||||
|
// d. Let oldLen be oldLenDesc.[[Value]].
|
||||||
|
// e. Assert: oldLen is a non-negative integral Number.
|
||||||
|
// f. Let index be ! ToUint32(P).
|
||||||
|
let old_len = old_len_desc.expect_value().to_u32(context).unwrap(); |
||||||
|
|
||||||
|
// g. If index ≥ oldLen and oldLenDesc.[[Writable]] is false, return false.
|
||||||
|
if index >= old_len && !old_len_desc.expect_writable() { |
||||||
|
return Ok(false); |
||||||
|
} |
||||||
|
|
||||||
|
// h. Let succeeded be ! OrdinaryDefineOwnProperty(A, P, Desc).
|
||||||
|
if super::ordinary_define_own_property(obj, key, desc, context)? { |
||||||
|
// j. If index ≥ oldLen, then
|
||||||
|
if index >= old_len { |
||||||
|
// i. Set oldLenDesc.[[Value]] to index + 1𝔽.
|
||||||
|
let old_len_desc = PropertyDescriptor::builder() |
||||||
|
.value(index + 1) |
||||||
|
.maybe_writable(old_len_desc.writable()) |
||||||
|
.maybe_enumerable(old_len_desc.enumerable()) |
||||||
|
.maybe_configurable(old_len_desc.configurable()); |
||||||
|
|
||||||
|
// ii. Set succeeded to OrdinaryDefineOwnProperty(A, "length", oldLenDesc).
|
||||||
|
let succeeded = super::ordinary_define_own_property( |
||||||
|
obj, |
||||||
|
"length".into(), |
||||||
|
old_len_desc.into(), |
||||||
|
context, |
||||||
|
) |
||||||
|
.unwrap(); |
||||||
|
|
||||||
|
// iii. Assert: succeeded is true.
|
||||||
|
debug_assert!(succeeded); |
||||||
|
} |
||||||
|
|
||||||
|
// k. Return true.
|
||||||
|
Ok(true) |
||||||
|
} else { |
||||||
|
// i. If succeeded is false, return false.
|
||||||
|
Ok(false) |
||||||
|
} |
||||||
|
} |
||||||
|
// 4. Return OrdinaryDefineOwnProperty(A, P, Desc).
|
||||||
|
_ => super::ordinary_define_own_property(obj, key, desc, context), |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,861 @@ |
|||||||
|
//! This module defines the object internal methods.
|
||||||
|
//!
|
||||||
|
//! More information:
|
||||||
|
//! - [ECMAScript reference][spec]
|
||||||
|
//!
|
||||||
|
//! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
|
||||||
|
|
||||||
|
use crate::{ |
||||||
|
object::JsObject, |
||||||
|
property::{DescriptorKind, PropertyDescriptor, PropertyKey}, |
||||||
|
value::JsValue, |
||||||
|
BoaProfiler, Context, JsResult, |
||||||
|
}; |
||||||
|
|
||||||
|
pub(super) mod array; |
||||||
|
pub(super) mod string; |
||||||
|
|
||||||
|
impl JsObject { |
||||||
|
/// Internal method `[[GetPrototypeOf]]`
|
||||||
|
///
|
||||||
|
/// Return either the prototype of this object or null.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof
|
||||||
|
#[inline] |
||||||
|
#[track_caller] |
||||||
|
pub(crate) fn __get_prototype_of__(&self, context: &mut Context) -> JsResult<JsValue> { |
||||||
|
let func = self.borrow().data.internal_methods.__get_prototype_of__; |
||||||
|
func(self, context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Internal method `[[SetPrototypeOf]]`
|
||||||
|
///
|
||||||
|
/// Set the property of a specified object to another object or `null`.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn __set_prototype_of__( |
||||||
|
&mut self, |
||||||
|
val: JsValue, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> { |
||||||
|
let func = self.borrow().data.internal_methods.__set_prototype_of__; |
||||||
|
func(self, val, context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Internal method `[[IsExtensible]]`
|
||||||
|
///
|
||||||
|
/// Check if the object is extensible.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn __is_extensible__(&self, context: &mut Context) -> JsResult<bool> { |
||||||
|
let func = self.borrow().data.internal_methods.__is_extensible__; |
||||||
|
func(self, context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Internal method `[[PreventExtensions]]`
|
||||||
|
///
|
||||||
|
/// Disable extensibility for this object.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn __prevent_extensions__(&mut self, context: &mut Context) -> JsResult<bool> { |
||||||
|
let func = self.borrow().data.internal_methods.__prevent_extensions__; |
||||||
|
func(self, context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Internal method `[[GetOwnProperty]]`
|
||||||
|
///
|
||||||
|
/// Get the specified property of this object.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn __get_own_property__( |
||||||
|
&self, |
||||||
|
key: &PropertyKey, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<Option<PropertyDescriptor>> { |
||||||
|
let _timer = BoaProfiler::global().start_event("Object::get_own_property", "object"); |
||||||
|
let func = self.borrow().data.internal_methods.__get_own_property__; |
||||||
|
func(self, key, context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Internal method `[[DefineOwnProperty]]`
|
||||||
|
///
|
||||||
|
/// Define a new property of this object.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn __define_own_property__( |
||||||
|
&self, |
||||||
|
key: PropertyKey, |
||||||
|
desc: PropertyDescriptor, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> { |
||||||
|
let func = self.borrow().data.internal_methods.__define_own_property__; |
||||||
|
func(self, key, desc, context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Internal method `[[hasProperty]]`.
|
||||||
|
///
|
||||||
|
/// Check if the object or its prototype has the required property.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn __has_property__( |
||||||
|
&self, |
||||||
|
key: &PropertyKey, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> { |
||||||
|
let func = self.borrow().data.internal_methods.__has_property__; |
||||||
|
func(self, key, context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Internal method `[[Get]]`
|
||||||
|
///
|
||||||
|
/// Get the specified property of this object or its prototype.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-get-p-receiver
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn __get__( |
||||||
|
&self, |
||||||
|
key: &PropertyKey, |
||||||
|
receiver: JsValue, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<JsValue> { |
||||||
|
let func = self.borrow().data.internal_methods.__get__; |
||||||
|
func(self, key, receiver, context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Internal method `[[Set]]`
|
||||||
|
///
|
||||||
|
/// Set the specified property of this object or its prototype to the provided value.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn __set__( |
||||||
|
&self, |
||||||
|
key: PropertyKey, |
||||||
|
value: JsValue, |
||||||
|
receiver: JsValue, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> { |
||||||
|
let _timer = BoaProfiler::global().start_event("Object::set", "object"); |
||||||
|
let func = self.borrow().data.internal_methods.__set__; |
||||||
|
func(self, key, value, receiver, context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Internal method `[[Delete]]`
|
||||||
|
///
|
||||||
|
/// Delete the specified own property of this object.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-delete-p
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn __delete__(&self, key: &PropertyKey, context: &mut Context) -> JsResult<bool> { |
||||||
|
let func = self.borrow().data.internal_methods.__delete__; |
||||||
|
func(self, key, context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Internal method `[[OwnPropertyKeys]]`
|
||||||
|
///
|
||||||
|
/// Get all the keys of the properties of this object.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys
|
||||||
|
#[inline] |
||||||
|
#[track_caller] |
||||||
|
pub(crate) fn __own_property_keys__( |
||||||
|
&self, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<Vec<PropertyKey>> { |
||||||
|
let func = self.borrow().data.internal_methods.__own_property_keys__; |
||||||
|
func(self, context) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Definitions of the internal object methods for ordinary objects.
|
||||||
|
///
|
||||||
|
/// If you want to implement an exotic object, create a new `static InternalObjectMethods`
|
||||||
|
/// overriding the desired internal methods with the definitions of the spec
|
||||||
|
/// and set all other methods to the default ordinary values, if necessary.
|
||||||
|
///
|
||||||
|
/// E.g. `string::STRING_EXOTIC_INTERNAL_METHODS`
|
||||||
|
///
|
||||||
|
/// Then, reference this static in the creation phase of an `ObjectData`.
|
||||||
|
///
|
||||||
|
/// E.g. `ObjectData::string`
|
||||||
|
pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { |
||||||
|
__get_prototype_of__: ordinary_get_prototype_of, |
||||||
|
__set_prototype_of__: ordinary_set_prototype_of, |
||||||
|
__is_extensible__: ordinary_is_extensible, |
||||||
|
__prevent_extensions__: ordinary_prevent_extensions, |
||||||
|
__get_own_property__: ordinary_get_own_property, |
||||||
|
__define_own_property__: ordinary_define_own_property, |
||||||
|
__has_property__: ordinary_has_property, |
||||||
|
__get__: ordinary_get, |
||||||
|
__set__: ordinary_set, |
||||||
|
__delete__: ordinary_delete, |
||||||
|
__own_property_keys__: ordinary_own_property_keys, |
||||||
|
}; |
||||||
|
|
||||||
|
/// The internal representation of the internal methods of a `JsObject`.
|
||||||
|
///
|
||||||
|
/// This struct allows us to dynamically dispatch exotic objects with their
|
||||||
|
/// exclusive definitions of the internal methods, without having to
|
||||||
|
/// resort to `dyn Object`.
|
||||||
|
///
|
||||||
|
/// For a guide on how to implement exotic internal methods, see `ORDINARY_INTERNAL_METHODS`.
|
||||||
|
#[derive(Clone, Copy)] |
||||||
|
pub(crate) struct InternalObjectMethods { |
||||||
|
pub(crate) __get_prototype_of__: fn(&JsObject, &mut Context) -> JsResult<JsValue>, |
||||||
|
pub(crate) __set_prototype_of__: fn(&JsObject, JsValue, &mut Context) -> JsResult<bool>, |
||||||
|
pub(crate) __is_extensible__: fn(&JsObject, &mut Context) -> JsResult<bool>, |
||||||
|
pub(crate) __prevent_extensions__: fn(&JsObject, &mut Context) -> JsResult<bool>, |
||||||
|
pub(crate) __get_own_property__: |
||||||
|
fn(&JsObject, &PropertyKey, &mut Context) -> JsResult<Option<PropertyDescriptor>>, |
||||||
|
pub(crate) __define_own_property__: |
||||||
|
fn(&JsObject, PropertyKey, PropertyDescriptor, &mut Context) -> JsResult<bool>, |
||||||
|
pub(crate) __has_property__: fn(&JsObject, &PropertyKey, &mut Context) -> JsResult<bool>, |
||||||
|
pub(crate) __get__: fn(&JsObject, &PropertyKey, JsValue, &mut Context) -> JsResult<JsValue>, |
||||||
|
pub(crate) __set__: |
||||||
|
fn(&JsObject, PropertyKey, JsValue, JsValue, &mut Context) -> JsResult<bool>, |
||||||
|
pub(crate) __delete__: fn(&JsObject, &PropertyKey, &mut Context) -> JsResult<bool>, |
||||||
|
pub(crate) __own_property_keys__: fn(&JsObject, &mut Context) -> JsResult<Vec<PropertyKey>>, |
||||||
|
} |
||||||
|
|
||||||
|
/// Abstract operation `OrdinaryGetPrototypeOf`.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinarygetprototypeof
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn ordinary_get_prototype_of( |
||||||
|
obj: &JsObject, |
||||||
|
_context: &mut Context, |
||||||
|
) -> JsResult<JsValue> { |
||||||
|
// 1. Return O.[[Prototype]].
|
||||||
|
Ok(obj.borrow().prototype.clone()) |
||||||
|
} |
||||||
|
|
||||||
|
/// Abstract operation `OrdinarySetPrototypeOf`.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinarysetprototypeof
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn ordinary_set_prototype_of( |
||||||
|
obj: &JsObject, |
||||||
|
val: JsValue, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> { |
||||||
|
// 1. Assert: Either Type(V) is Object or Type(V) is Null.
|
||||||
|
debug_assert!(val.is_object() || val.is_null()); |
||||||
|
|
||||||
|
// 2. Let current be O.[[Prototype]].
|
||||||
|
let current = obj.__get_prototype_of__(context)?; |
||||||
|
|
||||||
|
// 3. If SameValue(V, current) is true, return true.
|
||||||
|
if JsValue::same_value(¤t, &val) { |
||||||
|
return Ok(true); |
||||||
|
} |
||||||
|
|
||||||
|
// 4. Let extensible be O.[[Extensible]].
|
||||||
|
// 5. If extensible is false, return false.
|
||||||
|
if !obj.__is_extensible__(context)? { |
||||||
|
return Ok(false); |
||||||
|
} |
||||||
|
|
||||||
|
// 6. Let p be V.
|
||||||
|
let mut p = val.clone(); |
||||||
|
|
||||||
|
// 7. Let done be false.
|
||||||
|
let mut done = false; |
||||||
|
|
||||||
|
// 8. Repeat, while done is false,
|
||||||
|
while !done { |
||||||
|
match p { |
||||||
|
// a. If p is null, set done to true.
|
||||||
|
JsValue::Null => done = true, |
||||||
|
JsValue::Object(ref proto) => { |
||||||
|
// b. Else if SameValue(p, O) is true, return false.
|
||||||
|
if JsObject::equals(proto, obj) { |
||||||
|
return Ok(false); |
||||||
|
} |
||||||
|
// c. Else,
|
||||||
|
// i. If p.[[GetPrototypeOf]] is not the ordinary object internal method defined
|
||||||
|
// in 10.1.1, set done to true.
|
||||||
|
else if proto.borrow().data.internal_methods.__get_prototype_of__ as usize |
||||||
|
!= ordinary_get_prototype_of as usize |
||||||
|
{ |
||||||
|
done = true; |
||||||
|
} |
||||||
|
// ii. Else, set p to p.[[Prototype]].
|
||||||
|
else { |
||||||
|
p = proto.__get_prototype_of__(context)?; |
||||||
|
} |
||||||
|
} |
||||||
|
_ => unreachable!(), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 9. Set O.[[Prototype]] to V.
|
||||||
|
obj.borrow_mut().prototype = val; |
||||||
|
|
||||||
|
// 10. Return true.
|
||||||
|
Ok(true) |
||||||
|
} |
||||||
|
|
||||||
|
/// Abstract operation `OrdinaryIsExtensible`.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryisextensible
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn ordinary_is_extensible(obj: &JsObject, _context: &mut Context) -> JsResult<bool> { |
||||||
|
// 1. Return O.[[Extensible]].
|
||||||
|
Ok(obj.borrow().extensible) |
||||||
|
} |
||||||
|
|
||||||
|
/// Abstract operation `OrdinaryPreventExtensions.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinarypreventextensions
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn ordinary_prevent_extensions( |
||||||
|
obj: &JsObject, |
||||||
|
_context: &mut Context, |
||||||
|
) -> JsResult<bool> { |
||||||
|
// 1. Set O.[[Extensible]] to false.
|
||||||
|
obj.borrow_mut().extensible = false; |
||||||
|
|
||||||
|
// 2. Return true.
|
||||||
|
Ok(true) |
||||||
|
} |
||||||
|
|
||||||
|
/// Abstract operation `OrdinaryGetOwnProperty`.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinarygetownproperty
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn ordinary_get_own_property( |
||||||
|
obj: &JsObject, |
||||||
|
key: &PropertyKey, |
||||||
|
_context: &mut Context, |
||||||
|
) -> JsResult<Option<PropertyDescriptor>> { |
||||||
|
// 1. Assert: IsPropertyKey(P) is true.
|
||||||
|
// 2. If O does not have an own property with key P, return undefined.
|
||||||
|
// 3. Let D be a newly created Property Descriptor with no fields.
|
||||||
|
// 4. Let X be O's own property whose key is P.
|
||||||
|
// 5. If X is a data property, then
|
||||||
|
// a. Set D.[[Value]] to the value of X's [[Value]] attribute.
|
||||||
|
// b. Set D.[[Writable]] to the value of X's [[Writable]] attribute.
|
||||||
|
// 6. Else,
|
||||||
|
// a. Assert: X is an accessor property.
|
||||||
|
// b. Set D.[[Get]] to the value of X's [[Get]] attribute.
|
||||||
|
// c. Set D.[[Set]] to the value of X's [[Set]] attribute.
|
||||||
|
// 7. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute.
|
||||||
|
// 8. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute.
|
||||||
|
// 9. Return D.
|
||||||
|
Ok(obj.borrow().properties.get(key).cloned()) |
||||||
|
} |
||||||
|
|
||||||
|
/// Abstract operation `OrdinaryDefineOwnProperty`.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinarydefineownproperty
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn ordinary_define_own_property( |
||||||
|
obj: &JsObject, |
||||||
|
key: PropertyKey, |
||||||
|
desc: PropertyDescriptor, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> { |
||||||
|
// 1. Let current be ? O.[[GetOwnProperty]](P).
|
||||||
|
let current = obj.__get_own_property__(&key, context)?; |
||||||
|
|
||||||
|
// 2. Let extensible be ? IsExtensible(O).
|
||||||
|
let extensible = obj.__is_extensible__(context)?; |
||||||
|
|
||||||
|
// 3. Return ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current).
|
||||||
|
Ok(validate_and_apply_property_descriptor( |
||||||
|
Some((obj, key)), |
||||||
|
extensible, |
||||||
|
desc, |
||||||
|
current, |
||||||
|
)) |
||||||
|
} |
||||||
|
|
||||||
|
/// Abstract operation `OrdinaryHasProperty`.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasproperty
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn ordinary_has_property( |
||||||
|
obj: &JsObject, |
||||||
|
key: &PropertyKey, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> { |
||||||
|
// 1. Assert: IsPropertyKey(P) is true.
|
||||||
|
// 2. Let hasOwn be ? O.[[GetOwnProperty]](P).
|
||||||
|
// 3. If hasOwn is not undefined, return true.
|
||||||
|
if obj.__get_own_property__(key, context)?.is_some() { |
||||||
|
Ok(true) |
||||||
|
} else { |
||||||
|
// 4. Let parent be ? O.[[GetPrototypeOf]]().
|
||||||
|
let parent = obj.__get_prototype_of__(context)?; |
||||||
|
|
||||||
|
// 5. If parent is not null, then
|
||||||
|
if let JsValue::Object(ref object) = parent { |
||||||
|
// a. Return ? parent.[[HasProperty]](P).
|
||||||
|
object.__has_property__(key, context) |
||||||
|
} else { |
||||||
|
// 6. Return false.
|
||||||
|
Ok(false) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Abstract operation `OrdinaryGet`.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryget
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn ordinary_get( |
||||||
|
obj: &JsObject, |
||||||
|
key: &PropertyKey, |
||||||
|
receiver: JsValue, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<JsValue> { |
||||||
|
// 1. Assert: IsPropertyKey(P) is true.
|
||||||
|
// 2. Let desc be ? O.[[GetOwnProperty]](P).
|
||||||
|
match obj.__get_own_property__(key, context)? { |
||||||
|
// If desc is undefined, then
|
||||||
|
None => { |
||||||
|
// a. Let parent be ? O.[[GetPrototypeOf]]().
|
||||||
|
if let Some(parent) = obj.__get_prototype_of__(context)?.as_object() { |
||||||
|
// c. Return ? parent.[[Get]](P, Receiver).
|
||||||
|
parent.__get__(key, receiver, context) |
||||||
|
} |
||||||
|
// b. If parent is null, return undefined.
|
||||||
|
else { |
||||||
|
Ok(JsValue::undefined()) |
||||||
|
} |
||||||
|
} |
||||||
|
Some(ref desc) => match desc.kind() { |
||||||
|
// 4. If IsDataDescriptor(desc) is true, return desc.[[Value]].
|
||||||
|
DescriptorKind::Data { |
||||||
|
value: Some(value), .. |
||||||
|
} => Ok(value.clone()), |
||||||
|
// 5. Assert: IsAccessorDescriptor(desc) is true.
|
||||||
|
// 6. Let getter be desc.[[Get]].
|
||||||
|
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { |
||||||
|
// 8. Return ? Call(getter, Receiver).
|
||||||
|
context.call(get, &receiver, &[]) |
||||||
|
} |
||||||
|
// 7. If getter is undefined, return undefined.
|
||||||
|
_ => Ok(JsValue::undefined()), |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Abstract operation `OrdinarySet`.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryset
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn ordinary_set( |
||||||
|
obj: &JsObject, |
||||||
|
key: PropertyKey, |
||||||
|
value: JsValue, |
||||||
|
receiver: JsValue, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> { |
||||||
|
// 1. Assert: IsPropertyKey(P) is true.
|
||||||
|
// 2. Let ownDesc be ? O.[[GetOwnProperty]](P).
|
||||||
|
// 3. Return OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc).
|
||||||
|
|
||||||
|
// OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc )
|
||||||
|
// https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-ordinarysetwithowndescriptor
|
||||||
|
|
||||||
|
// 1. Assert: IsPropertyKey(P) is true.
|
||||||
|
let own_desc = if let Some(desc) = obj.__get_own_property__(&key, context)? { |
||||||
|
desc |
||||||
|
} |
||||||
|
// 2. If ownDesc is undefined, then
|
||||||
|
// a. Let parent be ? O.[[GetPrototypeOf]]().
|
||||||
|
// b. If parent is not null, then
|
||||||
|
else if let Some(ref mut parent) = obj.__get_prototype_of__(context)?.as_object() { |
||||||
|
// i. Return ? parent.[[Set]](P, V, Receiver).
|
||||||
|
return parent.__set__(key, value, receiver, context); |
||||||
|
} |
||||||
|
// c. Else,
|
||||||
|
else { |
||||||
|
// i. Set ownDesc to the PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true,
|
||||||
|
// [[Enumerable]]: true, [[Configurable]]: true }.
|
||||||
|
PropertyDescriptor::builder() |
||||||
|
.value(JsValue::undefined()) |
||||||
|
.writable(true) |
||||||
|
.enumerable(true) |
||||||
|
.configurable(true) |
||||||
|
.build() |
||||||
|
}; |
||||||
|
|
||||||
|
// 3. If IsDataDescriptor(ownDesc) is true, then
|
||||||
|
if own_desc.is_data_descriptor() { |
||||||
|
// a. If ownDesc.[[Writable]] is false, return false.
|
||||||
|
if !own_desc.expect_writable() { |
||||||
|
return Ok(false); |
||||||
|
} |
||||||
|
|
||||||
|
let receiver = match receiver.as_object() { |
||||||
|
Some(obj) => obj, |
||||||
|
// b. If Type(Receiver) is not Object, return false.
|
||||||
|
_ => return Ok(false), |
||||||
|
}; |
||||||
|
|
||||||
|
// c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P).
|
||||||
|
// d. If existingDescriptor is not undefined, then
|
||||||
|
if let Some(ref existing_desc) = receiver.__get_own_property__(&key, context)? { |
||||||
|
// i. If IsAccessorDescriptor(existingDescriptor) is true, return false.
|
||||||
|
if existing_desc.is_accessor_descriptor() { |
||||||
|
return Ok(false); |
||||||
|
} |
||||||
|
|
||||||
|
// ii. If existingDescriptor.[[Writable]] is false, return false.
|
||||||
|
if !existing_desc.expect_writable() { |
||||||
|
return Ok(false); |
||||||
|
} |
||||||
|
|
||||||
|
// iii. Let valueDesc be the PropertyDescriptor { [[Value]]: V }.
|
||||||
|
// iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc).
|
||||||
|
return receiver.__define_own_property__( |
||||||
|
key, |
||||||
|
PropertyDescriptor::builder().value(value).build(), |
||||||
|
context, |
||||||
|
); |
||||||
|
} |
||||||
|
// e. Else
|
||||||
|
else { |
||||||
|
// i. Assert: Receiver does not currently have a property P.
|
||||||
|
// ii. Return ? CreateDataProperty(Receiver, P, V).
|
||||||
|
return receiver.create_data_property(key, value, context); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 4. Assert: IsAccessorDescriptor(ownDesc) is true.
|
||||||
|
debug_assert!(own_desc.is_accessor_descriptor()); |
||||||
|
|
||||||
|
// 5. Let setter be ownDesc.[[Set]].
|
||||||
|
match own_desc.set() { |
||||||
|
Some(set) if !set.is_undefined() => { |
||||||
|
// 7. Perform ? Call(setter, Receiver, « V »).
|
||||||
|
context.call(set, &receiver, &[value])?; |
||||||
|
|
||||||
|
// 8. Return true.
|
||||||
|
Ok(true) |
||||||
|
} |
||||||
|
// 6. If setter is undefined, return false.
|
||||||
|
_ => Ok(false), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Abstract operation `OrdinaryDelete`.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinarydelete
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn ordinary_delete( |
||||||
|
obj: &JsObject, |
||||||
|
key: &PropertyKey, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> { |
||||||
|
// 1. Assert: IsPropertyKey(P) is true.
|
||||||
|
Ok( |
||||||
|
// 2. Let desc be ? O.[[GetOwnProperty]](P).
|
||||||
|
match obj.__get_own_property__(key, context)? { |
||||||
|
// 4. If desc.[[Configurable]] is true, then
|
||||||
|
Some(desc) if desc.expect_configurable() => { |
||||||
|
// a. Remove the own property with name P from O.
|
||||||
|
obj.borrow_mut().remove(key); |
||||||
|
// b. Return true.
|
||||||
|
true |
||||||
|
} |
||||||
|
// 5. Return false.
|
||||||
|
Some(_) => false, |
||||||
|
// 3. If desc is undefined, return true.
|
||||||
|
None => true, |
||||||
|
}, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
/// Abstract operation `OrdinaryOwnPropertyKeys`.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryownpropertykeys
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn ordinary_own_property_keys( |
||||||
|
obj: &JsObject, |
||||||
|
_context: &mut Context, |
||||||
|
) -> JsResult<Vec<PropertyKey>> { |
||||||
|
// 1. Let keys be a new empty List.
|
||||||
|
let mut keys = Vec::new(); |
||||||
|
|
||||||
|
let ordered_indexes = { |
||||||
|
let mut indexes: Vec<_> = obj |
||||||
|
.borrow() |
||||||
|
.properties |
||||||
|
.index_property_keys() |
||||||
|
.copied() |
||||||
|
.collect(); |
||||||
|
indexes.sort_unstable(); |
||||||
|
indexes |
||||||
|
}; |
||||||
|
|
||||||
|
// 2. For each own property key P of O such that P is an array index, in ascending numeric index order, do
|
||||||
|
// a. Add P as the last element of keys.
|
||||||
|
keys.extend(ordered_indexes.into_iter().map(|idx| idx.into())); |
||||||
|
|
||||||
|
// 3. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do
|
||||||
|
// a. Add P as the last element of keys.
|
||||||
|
keys.extend( |
||||||
|
obj.borrow() |
||||||
|
.properties |
||||||
|
.string_property_keys() |
||||||
|
.cloned() |
||||||
|
.map(|s| s.into()), |
||||||
|
); |
||||||
|
|
||||||
|
// 4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do
|
||||||
|
// a. Add P as the last element of keys.
|
||||||
|
keys.extend( |
||||||
|
obj.borrow() |
||||||
|
.properties |
||||||
|
.symbol_property_keys() |
||||||
|
.cloned() |
||||||
|
.map(|sym| sym.into()), |
||||||
|
); |
||||||
|
|
||||||
|
// 5. Return keys.
|
||||||
|
Ok(keys) |
||||||
|
} |
||||||
|
|
||||||
|
/// Abstract operation `IsCompatiblePropertyDescriptor`
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-iscompatiblepropertydescriptor
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn is_compatible_property_descriptor( |
||||||
|
extensible: bool, |
||||||
|
desc: PropertyDescriptor, |
||||||
|
current: PropertyDescriptor, |
||||||
|
) -> bool { |
||||||
|
// 1. Return ValidateAndApplyPropertyDescriptor(undefined, undefined, Extensible, Desc, Current).
|
||||||
|
validate_and_apply_property_descriptor(None, extensible, desc, Some(current)) |
||||||
|
} |
||||||
|
|
||||||
|
/// Abstract operation `ValidateAndApplyPropertyDescriptor`
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn validate_and_apply_property_descriptor( |
||||||
|
obj_and_key: Option<(&JsObject, PropertyKey)>, |
||||||
|
extensible: bool, |
||||||
|
desc: PropertyDescriptor, |
||||||
|
current: Option<PropertyDescriptor>, |
||||||
|
) -> bool { |
||||||
|
// 1. Assert: If O is not undefined, then IsPropertyKey(P) is true.
|
||||||
|
|
||||||
|
let mut current = if let Some(own) = current { |
||||||
|
own |
||||||
|
} |
||||||
|
// 2. If current is undefined, then
|
||||||
|
else { |
||||||
|
// a. If extensible is false, return false.
|
||||||
|
if !extensible { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// b. Assert: extensible is true.
|
||||||
|
|
||||||
|
if let Some((obj, key)) = obj_and_key { |
||||||
|
obj.borrow_mut().properties.insert( |
||||||
|
key, |
||||||
|
// c. If IsGenericDescriptor(Desc) is true or IsDataDescriptor(Desc) is true, then
|
||||||
|
if desc.is_generic_descriptor() || desc.is_data_descriptor() { |
||||||
|
// i. If O is not undefined, create an own data property named P of
|
||||||
|
// object O whose [[Value]], [[Writable]], [[Enumerable]], and
|
||||||
|
// [[Configurable]] attribute values are described by Desc.
|
||||||
|
// If the value of an attribute field of Desc is absent, the attribute
|
||||||
|
// of the newly created property is set to its default value.
|
||||||
|
desc.into_data_defaulted() |
||||||
|
} |
||||||
|
// d. Else,
|
||||||
|
else { |
||||||
|
// i. Assert: ! IsAccessorDescriptor(Desc) is true.
|
||||||
|
|
||||||
|
// ii. If O is not undefined, create an own accessor property named P
|
||||||
|
// of object O whose [[Get]], [[Set]], [[Enumerable]], and [[Configurable]]
|
||||||
|
// attribute values are described by Desc. If the value of an attribute field
|
||||||
|
// of Desc is absent, the attribute of the newly created property is set to
|
||||||
|
// its default value.
|
||||||
|
desc.into_accessor_defaulted() |
||||||
|
}, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// e. Return true.
|
||||||
|
return true; |
||||||
|
}; |
||||||
|
|
||||||
|
// 3. If every field in Desc is absent, return true.
|
||||||
|
if desc.is_empty() { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// 4. If current.[[Configurable]] is false, then
|
||||||
|
if !current.expect_configurable() { |
||||||
|
// a. If Desc.[[Configurable]] is present and its value is true, return false.
|
||||||
|
if matches!(desc.configurable(), Some(true)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// b. If Desc.[[Enumerable]] is present and ! SameValue(Desc.[[Enumerable]], current.[[Enumerable]])
|
||||||
|
// is false, return false.
|
||||||
|
if matches!(desc.enumerable(), Some(desc_enum) if desc_enum != current.expect_enumerable()) |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 5. If ! IsGenericDescriptor(Desc) is true, then
|
||||||
|
if desc.is_generic_descriptor() { |
||||||
|
// a. NOTE: No further validation is required.
|
||||||
|
} |
||||||
|
// 6. Else if ! SameValue(! IsDataDescriptor(current), ! IsDataDescriptor(Desc)) is false, then
|
||||||
|
else if current.is_data_descriptor() != desc.is_data_descriptor() { |
||||||
|
// a. If current.[[Configurable]] is false, return false.
|
||||||
|
if !current.expect_configurable() { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
if obj_and_key.is_some() { |
||||||
|
// b. If IsDataDescriptor(current) is true, then
|
||||||
|
if current.is_data_descriptor() { |
||||||
|
// i. If O is not undefined, convert the property named P of object O from a data
|
||||||
|
// property to an accessor property. Preserve the existing values of the converted
|
||||||
|
// property's [[Configurable]] and [[Enumerable]] attributes and set the rest of
|
||||||
|
// the property's attributes to their default values.
|
||||||
|
current = current.into_accessor_defaulted(); |
||||||
|
} |
||||||
|
// c. Else,
|
||||||
|
else { |
||||||
|
// i. If O is not undefined, convert the property named P of object O from an
|
||||||
|
// accessor property to a data property. Preserve the existing values of the
|
||||||
|
// converted property's [[Configurable]] and [[Enumerable]] attributes and set
|
||||||
|
// the rest of the property's attributes to their default values.
|
||||||
|
current = current.into_data_defaulted(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
// 7. Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both true, then
|
||||||
|
else if current.is_data_descriptor() && desc.is_data_descriptor() { |
||||||
|
// a. If current.[[Configurable]] is false and current.[[Writable]] is false, then
|
||||||
|
if !current.expect_configurable() && !current.expect_writable() { |
||||||
|
// i. If Desc.[[Writable]] is present and Desc.[[Writable]] is true, return false.
|
||||||
|
if matches!(desc.writable(), Some(true)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
// ii. If Desc.[[Value]] is present and SameValue(Desc.[[Value]], current.[[Value]]) is false, return false.
|
||||||
|
if matches!(desc.value(), Some(value) if !JsValue::same_value(value, current.expect_value())) |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
// iii. Return true.
|
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
// 8. Else,
|
||||||
|
// a. Assert: ! IsAccessorDescriptor(current) and ! IsAccessorDescriptor(Desc) are both true.
|
||||||
|
// b. If current.[[Configurable]] is false, then
|
||||||
|
else if !current.expect_configurable() { |
||||||
|
// i. If Desc.[[Set]] is present and SameValue(Desc.[[Set]], current.[[Set]]) is false, return false.
|
||||||
|
if matches!(desc.set(), Some(set) if !JsValue::same_value(set, current.expect_set())) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// ii. If Desc.[[Get]] is present and SameValue(Desc.[[Get]], current.[[Get]]) is false, return false.
|
||||||
|
if matches!(desc.get(), Some(get) if !JsValue::same_value(get, current.expect_get())) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
// iii. Return true.
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// 9. If O is not undefined, then
|
||||||
|
if let Some((obj, key)) = obj_and_key { |
||||||
|
// a. For each field of Desc that is present, set the corresponding attribute of the
|
||||||
|
// property named P of object O to the value of the field.
|
||||||
|
current.fill_with(desc); |
||||||
|
obj.borrow_mut().properties.insert(key, current); |
||||||
|
} |
||||||
|
|
||||||
|
// 10. Return true.
|
||||||
|
true |
||||||
|
} |
@ -0,0 +1,189 @@ |
|||||||
|
use crate::{ |
||||||
|
object::JsObject, |
||||||
|
property::{PropertyDescriptor, PropertyKey}, |
||||||
|
Context, JsResult, JsValue, |
||||||
|
}; |
||||||
|
|
||||||
|
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; |
||||||
|
|
||||||
|
/// Definitions of the internal object methods for string exotic objects.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects
|
||||||
|
pub(crate) static STRING_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { |
||||||
|
__get_own_property__: string_exotic_get_own_property, |
||||||
|
__define_own_property__: string_exotic_define_own_property, |
||||||
|
__own_property_keys__: string_exotic_own_property_keys, |
||||||
|
..ORDINARY_INTERNAL_METHODS |
||||||
|
}; |
||||||
|
|
||||||
|
/// Gets own property of 'String' exotic object
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects-getownproperty-p
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn string_exotic_get_own_property( |
||||||
|
obj: &JsObject, |
||||||
|
key: &PropertyKey, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<Option<PropertyDescriptor>> { |
||||||
|
// 1. Assert: IsPropertyKey(P) is true.
|
||||||
|
// 2. Let desc be OrdinaryGetOwnProperty(S, P).
|
||||||
|
let desc = super::ordinary_get_own_property(obj, key, context)?; |
||||||
|
|
||||||
|
// 3. If desc is not undefined, return desc.
|
||||||
|
if desc.is_some() { |
||||||
|
Ok(desc) |
||||||
|
} else { |
||||||
|
// 4. Return ! StringGetOwnProperty(S, P).
|
||||||
|
Ok(string_get_own_property(obj, key)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Defines own property of 'String' exotic object
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects-defineownproperty-p-desc
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn string_exotic_define_own_property( |
||||||
|
obj: &JsObject, |
||||||
|
key: PropertyKey, |
||||||
|
desc: PropertyDescriptor, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> { |
||||||
|
// 1. Assert: IsPropertyKey(P) is true.
|
||||||
|
// 2. Let stringDesc be ! StringGetOwnProperty(S, P).
|
||||||
|
let string_desc = string_get_own_property(obj, &key); |
||||||
|
|
||||||
|
// 3. If stringDesc is not undefined, then
|
||||||
|
if let Some(string_desc) = string_desc { |
||||||
|
// a. Let extensible be S.[[Extensible]].
|
||||||
|
let extensible = obj.borrow().extensible; |
||||||
|
// b. Return ! IsCompatiblePropertyDescriptor(extensible, Desc, stringDesc).
|
||||||
|
Ok(super::is_compatible_property_descriptor( |
||||||
|
extensible, |
||||||
|
desc, |
||||||
|
string_desc, |
||||||
|
)) |
||||||
|
} else { |
||||||
|
// 4. Return ! OrdinaryDefineOwnProperty(S, P, Desc).
|
||||||
|
super::ordinary_define_own_property(obj, key, desc, context) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Gets own property keys of 'String' exotic object
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects-ownpropertykeys
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn string_exotic_own_property_keys( |
||||||
|
obj: &JsObject, |
||||||
|
_context: &mut Context, |
||||||
|
) -> JsResult<Vec<PropertyKey>> { |
||||||
|
let obj = obj.borrow(); |
||||||
|
|
||||||
|
// 2. Let str be O.[[StringData]].
|
||||||
|
// 3. Assert: Type(str) is String.
|
||||||
|
let string = obj |
||||||
|
.as_string() |
||||||
|
.expect("string exotic method should only be callable from string objects"); |
||||||
|
// 4. Let len be the length of str.
|
||||||
|
let len = string.encode_utf16().count(); |
||||||
|
|
||||||
|
// 1. Let keys be a new empty List.
|
||||||
|
let mut keys = Vec::with_capacity(len); |
||||||
|
|
||||||
|
// 5. For each integer i starting with 0 such that i < len, in ascending order, do
|
||||||
|
// a. Add ! ToString(𝔽(i)) as the last element of keys.
|
||||||
|
keys.extend((0..len).into_iter().map(|idx| idx.into())); |
||||||
|
|
||||||
|
// 6. For each own property key P of O such that P is an array index
|
||||||
|
// and ! ToIntegerOrInfinity(P) ≥ len, in ascending numeric index order, do
|
||||||
|
// a. Add P as the last element of keys.
|
||||||
|
let mut remaining_indices: Vec<_> = obj |
||||||
|
.properties |
||||||
|
.index_property_keys() |
||||||
|
.cloned() |
||||||
|
.filter(|idx| (*idx as usize) >= len) |
||||||
|
.collect(); |
||||||
|
remaining_indices.sort_unstable(); |
||||||
|
keys.extend(remaining_indices.into_iter().map(|idx| idx.into())); |
||||||
|
|
||||||
|
// 7. For each own property key P of O such that Type(P) is String and P is not
|
||||||
|
// an array index, in ascending chronological order of property creation, do
|
||||||
|
// a. Add P as the last element of keys.
|
||||||
|
keys.extend( |
||||||
|
obj.properties |
||||||
|
.string_property_keys() |
||||||
|
.cloned() |
||||||
|
.map(|s| s.into()), |
||||||
|
); |
||||||
|
|
||||||
|
// 8. For each own property key P of O such that Type(P) is Symbol, in ascending
|
||||||
|
// chronological order of property creation, do
|
||||||
|
// a. Add P as the last element of keys.
|
||||||
|
keys.extend( |
||||||
|
obj.properties |
||||||
|
.symbol_property_keys() |
||||||
|
.cloned() |
||||||
|
.map(|sym| sym.into()), |
||||||
|
); |
||||||
|
|
||||||
|
// 9. Return keys.
|
||||||
|
Ok(keys) |
||||||
|
} |
||||||
|
|
||||||
|
/// StringGetOwnProperty abstract operation
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-stringgetownproperty
|
||||||
|
#[allow(clippy::float_cmp)] |
||||||
|
#[inline] |
||||||
|
fn string_get_own_property(obj: &JsObject, key: &PropertyKey) -> Option<PropertyDescriptor> { |
||||||
|
// 1. Assert: S is an Object that has a [[StringData]] internal slot.
|
||||||
|
// 2. Assert: IsPropertyKey(P) is true.
|
||||||
|
// 3. If Type(P) is not String, return undefined.
|
||||||
|
// 4. Let index be ! CanonicalNumericIndexString(P).
|
||||||
|
// 5. If index is undefined, return undefined.
|
||||||
|
// 6. If IsIntegralNumber(index) is false, return undefined.
|
||||||
|
// 7. If index is -0𝔽, return undefined.
|
||||||
|
let pos = match key { |
||||||
|
PropertyKey::Index(index) => *index as usize, |
||||||
|
_ => return None, |
||||||
|
}; |
||||||
|
|
||||||
|
// 8. Let str be S.[[StringData]].
|
||||||
|
// 9. Assert: Type(str) is String.
|
||||||
|
let string = obj |
||||||
|
.borrow() |
||||||
|
.as_string() |
||||||
|
.expect("string exotic method should only be callable from string objects"); |
||||||
|
|
||||||
|
// 10. Let len be the length of str.
|
||||||
|
// 11. If ℝ(index) < 0 or len ≤ ℝ(index), return undefined.
|
||||||
|
// 12. Let resultStr be the String value of length 1, containing one code unit from str, specifically the code unit at index ℝ(index).
|
||||||
|
let result_str = string |
||||||
|
.encode_utf16() |
||||||
|
.nth(pos) |
||||||
|
.map(|c| JsValue::from(String::from_utf16_lossy(&[c])))?; |
||||||
|
|
||||||
|
// 13. Return the PropertyDescriptor { [[Value]]: resultStr, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false }.
|
||||||
|
let desc = PropertyDescriptor::builder() |
||||||
|
.value(result_str) |
||||||
|
.writable(false) |
||||||
|
.enumerable(true) |
||||||
|
.configurable(false) |
||||||
|
.build(); |
||||||
|
|
||||||
|
Some(desc) |
||||||
|
} |
@ -0,0 +1,487 @@ |
|||||||
|
use crate::{ |
||||||
|
builtins::Array, |
||||||
|
property::{PropertyDescriptor, PropertyKey, PropertyNameKind}, |
||||||
|
symbol::WellKnownSymbols, |
||||||
|
value::Type, |
||||||
|
Context, JsResult, JsValue, |
||||||
|
}; |
||||||
|
|
||||||
|
use super::JsObject; |
||||||
|
|
||||||
|
impl JsObject { |
||||||
|
/// Get property from object or throw.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-get-o-p
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn get<K>(&self, key: K, context: &mut Context) -> JsResult<JsValue> |
||||||
|
where |
||||||
|
K: Into<PropertyKey>, |
||||||
|
{ |
||||||
|
// 1. Assert: Type(O) is Object.
|
||||||
|
// 2. Assert: IsPropertyKey(P) is true.
|
||||||
|
// 3. Return ? O.[[Get]](P, O).
|
||||||
|
self.__get__(&key.into(), self.clone().into(), context) |
||||||
|
} |
||||||
|
|
||||||
|
/// set property of object or throw if bool flag is passed.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-set-o-p-v-throw
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn set<K, V>( |
||||||
|
&self, |
||||||
|
key: K, |
||||||
|
value: V, |
||||||
|
throw: bool, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> |
||||||
|
where |
||||||
|
K: Into<PropertyKey>, |
||||||
|
V: Into<JsValue>, |
||||||
|
{ |
||||||
|
let key = key.into(); |
||||||
|
// 1. Assert: Type(O) is Object.
|
||||||
|
// 2. Assert: IsPropertyKey(P) is true.
|
||||||
|
// 3. Assert: Type(Throw) is Boolean.
|
||||||
|
// 4. Let success be ? O.[[Set]](P, V, O).
|
||||||
|
let success = self.__set__(key.clone(), value.into(), self.clone().into(), context)?; |
||||||
|
// 5. If success is false and Throw is true, throw a TypeError exception.
|
||||||
|
if !success && throw { |
||||||
|
return Err( |
||||||
|
context.construct_type_error(format!("cannot set non-writable property: {}", key)) |
||||||
|
); |
||||||
|
} |
||||||
|
// 6. Return success.
|
||||||
|
Ok(success) |
||||||
|
} |
||||||
|
|
||||||
|
/// Create data property
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow
|
||||||
|
pub(crate) fn create_data_property<K, V>( |
||||||
|
&self, |
||||||
|
key: K, |
||||||
|
value: V, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> |
||||||
|
where |
||||||
|
K: Into<PropertyKey>, |
||||||
|
V: Into<JsValue>, |
||||||
|
{ |
||||||
|
// 1. Assert: Type(O) is Object.
|
||||||
|
// 2. Assert: IsPropertyKey(P) is true.
|
||||||
|
// 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
|
||||||
|
let new_desc = PropertyDescriptor::builder() |
||||||
|
.value(value) |
||||||
|
.writable(true) |
||||||
|
.enumerable(true) |
||||||
|
.configurable(true); |
||||||
|
// 4. Return ? O.[[DefineOwnProperty]](P, newDesc).
|
||||||
|
self.__define_own_property__(key.into(), new_desc.into(), context) |
||||||
|
} |
||||||
|
|
||||||
|
// todo: CreateMethodProperty
|
||||||
|
|
||||||
|
/// Create data property or throw
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow
|
||||||
|
pub(crate) fn create_data_property_or_throw<K, V>( |
||||||
|
&self, |
||||||
|
key: K, |
||||||
|
value: V, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> |
||||||
|
where |
||||||
|
K: Into<PropertyKey>, |
||||||
|
V: Into<JsValue>, |
||||||
|
{ |
||||||
|
let key = key.into(); |
||||||
|
// 1. Assert: Type(O) is Object.
|
||||||
|
// 2. Assert: IsPropertyKey(P) is true.
|
||||||
|
// 3. Let success be ? CreateDataProperty(O, P, V).
|
||||||
|
let success = self.create_data_property(key.clone(), value, context)?; |
||||||
|
// 4. If success is false, throw a TypeError exception.
|
||||||
|
if !success { |
||||||
|
return Err(context.construct_type_error(format!("cannot redefine property: {}", key))); |
||||||
|
} |
||||||
|
// 5. Return success.
|
||||||
|
Ok(success) |
||||||
|
} |
||||||
|
|
||||||
|
/// Define property or throw.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn define_property_or_throw<K, P>( |
||||||
|
&self, |
||||||
|
key: K, |
||||||
|
desc: P, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> |
||||||
|
where |
||||||
|
K: Into<PropertyKey>, |
||||||
|
P: Into<PropertyDescriptor>, |
||||||
|
{ |
||||||
|
let key = key.into(); |
||||||
|
// 1. Assert: Type(O) is Object.
|
||||||
|
// 2. Assert: IsPropertyKey(P) is true.
|
||||||
|
// 3. Let success be ? O.[[DefineOwnProperty]](P, desc).
|
||||||
|
let success = self.__define_own_property__(key.clone(), desc.into(), context)?; |
||||||
|
// 4. If success is false, throw a TypeError exception.
|
||||||
|
if !success { |
||||||
|
return Err(context.construct_type_error(format!("cannot redefine property: {}", key))); |
||||||
|
} |
||||||
|
// 5. Return success.
|
||||||
|
Ok(success) |
||||||
|
} |
||||||
|
|
||||||
|
/// Defines the property or throws a `TypeError` if the operation fails.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [EcmaScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn delete_property_or_throw<K>( |
||||||
|
&self, |
||||||
|
key: K, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> |
||||||
|
where |
||||||
|
K: Into<PropertyKey>, |
||||||
|
{ |
||||||
|
let key = key.into(); |
||||||
|
// 1. Assert: Type(O) is Object.
|
||||||
|
// 2. Assert: IsPropertyKey(P) is true.
|
||||||
|
// 3. Let success be ? O.[[Delete]](P).
|
||||||
|
let success = self.__delete__(&key, context)?; |
||||||
|
// 4. If success is false, throw a TypeError exception.
|
||||||
|
if !success { |
||||||
|
return Err(context.construct_type_error(format!("cannot delete property: {}", key))); |
||||||
|
} |
||||||
|
// 5. Return success.
|
||||||
|
Ok(success) |
||||||
|
} |
||||||
|
|
||||||
|
/// Retrieves value of specific property, when the value of the property is expected to be a function.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [EcmaScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-getmethod
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn get_method<K>(&self, context: &mut Context, key: K) -> JsResult<Option<JsObject>> |
||||||
|
where |
||||||
|
K: Into<PropertyKey>, |
||||||
|
{ |
||||||
|
// 1. Assert: IsPropertyKey(P) is true.
|
||||||
|
// 2. Let func be ? GetV(V, P).
|
||||||
|
let value = self.get(key, context)?; |
||||||
|
|
||||||
|
// 3. If func is either undefined or null, return undefined.
|
||||||
|
if value.is_null_or_undefined() { |
||||||
|
return Ok(None); |
||||||
|
} |
||||||
|
|
||||||
|
// 4. If IsCallable(func) is false, throw a TypeError exception.
|
||||||
|
// 5. Return func.
|
||||||
|
match value.as_object() { |
||||||
|
Some(object) if object.is_callable() => Ok(Some(object)), |
||||||
|
_ => Err(context |
||||||
|
.construct_type_error("value returned for property of object is not a function")), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Check if object has property.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-hasproperty
|
||||||
|
// NOTE: for now context is not used but it will in the future.
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn has_property<K>(&self, key: K, context: &mut Context) -> JsResult<bool> |
||||||
|
where |
||||||
|
K: Into<PropertyKey>, |
||||||
|
{ |
||||||
|
// 1. Assert: Type(O) is Object.
|
||||||
|
// 2. Assert: IsPropertyKey(P) is true.
|
||||||
|
// 3. Return ? O.[[HasProperty]](P).
|
||||||
|
self.__has_property__(&key.into(), context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Check if object has an own property.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-hasownproperty
|
||||||
|
#[inline] |
||||||
|
pub(crate) fn has_own_property<K>(&self, key: K, context: &mut Context) -> JsResult<bool> |
||||||
|
where |
||||||
|
K: Into<PropertyKey>, |
||||||
|
{ |
||||||
|
let key = key.into(); |
||||||
|
// 1. Assert: Type(O) is Object.
|
||||||
|
// 2. Assert: IsPropertyKey(P) is true.
|
||||||
|
// 3. Let desc be ? O.[[GetOwnProperty]](P).
|
||||||
|
let desc = self.__get_own_property__(&key, context)?; |
||||||
|
// 4. If desc is undefined, return false.
|
||||||
|
// 5. Return true.
|
||||||
|
Ok(desc.is_some()) |
||||||
|
} |
||||||
|
|
||||||
|
/// Call this object.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the object is currently mutably borrowed.
|
||||||
|
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
|
||||||
|
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
|
||||||
|
#[track_caller] |
||||||
|
#[inline] |
||||||
|
pub(crate) fn call( |
||||||
|
&self, |
||||||
|
this: &JsValue, |
||||||
|
args: &[JsValue], |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<JsValue> { |
||||||
|
self.call_construct(this, args, context, false) |
||||||
|
} |
||||||
|
|
||||||
|
/// Construct an instance of this object with the specified arguments.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the object is currently mutably borrowed.
|
||||||
|
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
|
||||||
|
#[track_caller] |
||||||
|
#[inline] |
||||||
|
pub(crate) fn construct( |
||||||
|
&self, |
||||||
|
args: &[JsValue], |
||||||
|
new_target: &JsValue, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<JsValue> { |
||||||
|
self.call_construct(new_target, args, context, true) |
||||||
|
} |
||||||
|
|
||||||
|
// todo: SetIntegrityLevel
|
||||||
|
|
||||||
|
// todo: TestIntegrityLevel
|
||||||
|
|
||||||
|
pub(crate) fn length_of_array_like(&self, context: &mut Context) -> JsResult<usize> { |
||||||
|
// 1. Assert: Type(obj) is Object.
|
||||||
|
// 2. Return ℝ(? ToLength(? Get(obj, "length"))).
|
||||||
|
self.get("length", context)?.to_length(context) |
||||||
|
} |
||||||
|
|
||||||
|
/// `7.3.22 SpeciesConstructor ( O, defaultConstructor )`
|
||||||
|
///
|
||||||
|
/// The abstract operation SpeciesConstructor takes arguments O (an Object) and defaultConstructor (a constructor).
|
||||||
|
/// It is used to retrieve the constructor that should be used to create new objects that are derived from O.
|
||||||
|
/// defaultConstructor is the constructor to use if a constructor @@species property cannot be found starting from O.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-speciesconstructor
|
||||||
|
pub(crate) fn species_constructor( |
||||||
|
&self, |
||||||
|
default_constructor: JsValue, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<JsValue> { |
||||||
|
// 1. Assert: Type(O) is Object.
|
||||||
|
|
||||||
|
// 2. Let C be ? Get(O, "constructor").
|
||||||
|
let c = self.clone().get("constructor", context)?; |
||||||
|
|
||||||
|
// 3. If C is undefined, return defaultConstructor.
|
||||||
|
if c.is_undefined() { |
||||||
|
return Ok(default_constructor); |
||||||
|
} |
||||||
|
|
||||||
|
// 4. If Type(C) is not Object, throw a TypeError exception.
|
||||||
|
if !c.is_object() { |
||||||
|
return context.throw_type_error("property 'constructor' is not an object"); |
||||||
|
} |
||||||
|
|
||||||
|
// 5. Let S be ? Get(C, @@species).
|
||||||
|
let s = c.get_field(WellKnownSymbols::species(), context)?; |
||||||
|
|
||||||
|
// 6. If S is either undefined or null, return defaultConstructor.
|
||||||
|
if s.is_null_or_undefined() { |
||||||
|
return Ok(default_constructor); |
||||||
|
} |
||||||
|
|
||||||
|
// 7. If IsConstructor(S) is true, return S.
|
||||||
|
// 8. Throw a TypeError exception.
|
||||||
|
if let Some(obj) = s.as_object() { |
||||||
|
if obj.is_constructable() { |
||||||
|
Ok(s) |
||||||
|
} else { |
||||||
|
context.throw_type_error("property 'constructor' is not a constructor") |
||||||
|
} |
||||||
|
} else { |
||||||
|
context.throw_type_error("property 'constructor' is not an object") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// It is used to iterate over names of object's keys.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [EcmaScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-enumerableownpropertynames
|
||||||
|
pub(crate) fn enumerable_own_property_names( |
||||||
|
&self, |
||||||
|
kind: PropertyNameKind, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<Vec<JsValue>> { |
||||||
|
// 1. Assert: Type(O) is Object.
|
||||||
|
// 2. Let ownKeys be ? O.[[OwnPropertyKeys]]().
|
||||||
|
let own_keys = self.__own_property_keys__(context)?; |
||||||
|
// 3. Let properties be a new empty List.
|
||||||
|
let mut properties = vec![]; |
||||||
|
|
||||||
|
// 4. For each element key of ownKeys, do
|
||||||
|
for key in own_keys { |
||||||
|
// a. If Type(key) is String, then
|
||||||
|
let key_str = match &key { |
||||||
|
PropertyKey::String(s) => Some(s.clone()), |
||||||
|
PropertyKey::Index(i) => Some(i.to_string().into()), |
||||||
|
_ => None, |
||||||
|
}; |
||||||
|
|
||||||
|
if let Some(key_str) = key_str { |
||||||
|
// i. Let desc be ? O.[[GetOwnProperty]](key).
|
||||||
|
let desc = self.__get_own_property__(&key, context)?; |
||||||
|
// ii. If desc is not undefined and desc.[[Enumerable]] is true, then
|
||||||
|
if let Some(desc) = desc { |
||||||
|
if desc.expect_enumerable() { |
||||||
|
match kind { |
||||||
|
// 1. If kind is key, append key to properties.
|
||||||
|
PropertyNameKind::Key => properties.push(key_str.into()), |
||||||
|
// 2. Else,
|
||||||
|
// a. Let value be ? Get(O, key).
|
||||||
|
// b. If kind is value, append value to properties.
|
||||||
|
PropertyNameKind::Value => { |
||||||
|
properties.push(self.get(key.clone(), context)?) |
||||||
|
} |
||||||
|
// c. Else,
|
||||||
|
// i. Assert: kind is key+value.
|
||||||
|
// ii. Let entry be ! CreateArrayFromList(« key, value »).
|
||||||
|
// iii. Append entry to properties.
|
||||||
|
PropertyNameKind::KeyAndValue => properties.push( |
||||||
|
Array::create_array_from_list( |
||||||
|
[key_str.into(), self.get(key.clone(), context)?], |
||||||
|
context, |
||||||
|
) |
||||||
|
.into(), |
||||||
|
), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 5. Return properties.
|
||||||
|
Ok(properties) |
||||||
|
} |
||||||
|
|
||||||
|
// todo: GetFunctionRealm
|
||||||
|
|
||||||
|
// todo: CopyDataProperties
|
||||||
|
|
||||||
|
// todo: PrivateElementFind
|
||||||
|
|
||||||
|
// todo: PrivateFieldAdd
|
||||||
|
|
||||||
|
// todo: PrivateMethodOrAccessorAdd
|
||||||
|
|
||||||
|
// todo: PrivateGet
|
||||||
|
|
||||||
|
// todo: PrivateSet
|
||||||
|
|
||||||
|
// todo: DefineField
|
||||||
|
|
||||||
|
// todo: InitializeInstanceElements
|
||||||
|
} |
||||||
|
|
||||||
|
impl JsValue { |
||||||
|
// todo: GetV
|
||||||
|
|
||||||
|
// todo: GetMethod
|
||||||
|
|
||||||
|
/// It is used to create List value whose elements are provided by the indexed properties of
|
||||||
|
/// self.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [EcmaScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-createlistfromarraylike
|
||||||
|
pub(crate) fn create_list_from_array_like( |
||||||
|
&self, |
||||||
|
element_types: &[Type], |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<Vec<JsValue>> { |
||||||
|
// 1. If elementTypes is not present, set elementTypes to « Undefined, Null, Boolean, String, Symbol, Number, BigInt, Object ».
|
||||||
|
let types = if element_types.is_empty() { |
||||||
|
&[ |
||||||
|
Type::Undefined, |
||||||
|
Type::Null, |
||||||
|
Type::Boolean, |
||||||
|
Type::String, |
||||||
|
Type::Symbol, |
||||||
|
Type::Number, |
||||||
|
Type::BigInt, |
||||||
|
Type::Object, |
||||||
|
] |
||||||
|
} else { |
||||||
|
element_types |
||||||
|
}; |
||||||
|
|
||||||
|
// 2. If Type(obj) is not Object, throw a TypeError exception.
|
||||||
|
let obj = self |
||||||
|
.as_object() |
||||||
|
.ok_or_else(|| context.construct_type_error("cannot create list from a primitive"))?; |
||||||
|
|
||||||
|
// 3. Let len be ? LengthOfArrayLike(obj).
|
||||||
|
let len = obj.length_of_array_like(context)?; |
||||||
|
|
||||||
|
// 4. Let list be a new empty List.
|
||||||
|
let mut list = Vec::with_capacity(len); |
||||||
|
|
||||||
|
// 5. Let index be 0.
|
||||||
|
// 6. Repeat, while index < len,
|
||||||
|
for index in 0..len { |
||||||
|
// a. Let indexName be ! ToString(𝔽(index)).
|
||||||
|
// b. Let next be ? Get(obj, indexName).
|
||||||
|
let next = obj.get(index, context)?; |
||||||
|
// c. If Type(next) is not an element of elementTypes, throw a TypeError exception.
|
||||||
|
if !types.contains(&next.get_type()) { |
||||||
|
return Err(context.construct_type_error("bad type")); |
||||||
|
} |
||||||
|
// d. Append next as the last element of list.
|
||||||
|
list.push(next.clone()); |
||||||
|
// e. Set index to index + 1.
|
||||||
|
} |
||||||
|
|
||||||
|
// 7. Return list.
|
||||||
|
Ok(list) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue