mirror of https://github.com/boa-dev/boa.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
861 lines
30 KiB
861 lines
30 KiB
//! 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 |
|
}
|
|
|