feat: works
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
use crate::{
|
||||
object::JsObject,
|
||||
property::{DescriptorKind, PropertyDescriptor, PropertyKey},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
|
||||
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
|
||||
|
||||
pub(crate) static ARGUMENTS_EXOTIC_INTERNAL_METHODS: InternalObjectMethods =
|
||||
InternalObjectMethods {
|
||||
__get_own_property__: arguments_exotic_get_own_property,
|
||||
__define_own_property__: arguments_exotic_define_own_property,
|
||||
__get__: arguments_exotic_get,
|
||||
__set__: arguments_exotic_set,
|
||||
__delete__: arguments_exotic_delete,
|
||||
..ORDINARY_INTERNAL_METHODS
|
||||
};
|
||||
|
||||
/// `[[GetOwnProperty]]` for arguments exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-getownproperty-p
|
||||
#[inline]
|
||||
pub(crate) fn arguments_exotic_get_own_property(
|
||||
obj: &JsObject,
|
||||
key: &PropertyKey,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Option<PropertyDescriptor>> {
|
||||
// 1. Let desc be OrdinaryGetOwnProperty(args, P).
|
||||
// 2. If desc is undefined, return desc.
|
||||
let desc = if let Some(desc) = super::ordinary_get_own_property(obj, key, context)? {
|
||||
desc
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// 3. Let map be args.[[ParameterMap]].
|
||||
// 4. Let isMapped be ! HasOwnProperty(map, P).
|
||||
// 5. If isMapped is true, then
|
||||
if let PropertyKey::Index(index) = key {
|
||||
if let Some(value) = obj
|
||||
.borrow()
|
||||
.as_mapped_arguments()
|
||||
.expect("arguments exotic method must only be callable from arguments objects")
|
||||
.get(*index as usize)
|
||||
{
|
||||
// a. Set desc.[[Value]] to Get(map, P).
|
||||
return Ok(Some(
|
||||
PropertyDescriptor::builder()
|
||||
.value(value)
|
||||
.maybe_writable(desc.writable())
|
||||
.maybe_enumerable(desc.enumerable())
|
||||
.maybe_configurable(desc.configurable())
|
||||
.build(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Return desc.
|
||||
Ok(Some(desc))
|
||||
}
|
||||
|
||||
/// `[[DefineOwnProperty]]` for arguments exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-defineownproperty-p-desc
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub(crate) fn arguments_exotic_define_own_property(
|
||||
obj: &JsObject,
|
||||
key: PropertyKey,
|
||||
desc: PropertyDescriptor,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
// 2. Let isMapped be HasOwnProperty(map, P).
|
||||
let mapped = if let PropertyKey::Index(index) = key {
|
||||
// 1. Let map be args.[[ParameterMap]].
|
||||
obj.borrow()
|
||||
.as_mapped_arguments()
|
||||
.expect("arguments exotic method must only be callable from arguments objects")
|
||||
.get(index as usize)
|
||||
.map(|value| (index as usize, value))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let new_arg_desc = match desc.kind() {
|
||||
// 4. If isMapped is true and IsDataDescriptor(Desc) is true, then
|
||||
// a. If Desc.[[Value]] is not present and Desc.[[Writable]] is present and its
|
||||
// value is false, then
|
||||
DescriptorKind::Data {
|
||||
writable: Some(false),
|
||||
value: None,
|
||||
} =>
|
||||
// i. Set newArgDesc to a copy of Desc.
|
||||
// ii. Set newArgDesc.[[Value]] to Get(map, P).
|
||||
{
|
||||
if let Some((_, value)) = &mapped {
|
||||
PropertyDescriptor::builder()
|
||||
.value(value)
|
||||
.writable(false)
|
||||
.maybe_enumerable(desc.enumerable())
|
||||
.maybe_configurable(desc.configurable())
|
||||
.build()
|
||||
} else {
|
||||
desc.clone()
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Let newArgDesc be Desc.
|
||||
_ => desc.clone(),
|
||||
};
|
||||
|
||||
// 5. Let allowed be ? OrdinaryDefineOwnProperty(args, P, newArgDesc).
|
||||
// 6. If allowed is false, return false.
|
||||
if !super::ordinary_define_own_property(obj, key, new_arg_desc, context)? {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// 7. If isMapped is true, then
|
||||
if let Some((index, _)) = mapped {
|
||||
// 1. Let map be args.[[ParameterMap]].
|
||||
let mut obj_mut = obj.borrow_mut();
|
||||
let map = obj_mut
|
||||
.as_mapped_arguments_mut()
|
||||
.expect("arguments exotic method must only be callable from arguments objects");
|
||||
|
||||
// a. If IsAccessorDescriptor(Desc) is true, then
|
||||
if desc.is_accessor_descriptor() {
|
||||
// i. Call map.[[Delete]](P).
|
||||
map.delete(index);
|
||||
}
|
||||
// b. Else,
|
||||
else {
|
||||
// i. If Desc.[[Value]] is present, then
|
||||
if let Some(value) = desc.value() {
|
||||
// 1. Let setStatus be Set(map, P, Desc.[[Value]], false).
|
||||
// 2. Assert: setStatus is true because formal parameters mapped by argument objects are always writable.
|
||||
map.set(index, value);
|
||||
}
|
||||
|
||||
// ii. If Desc.[[Writable]] is present and its value is false, then
|
||||
if let Some(false) = desc.writable() {
|
||||
// 1. Call map.[[Delete]](P).
|
||||
map.delete(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Return true.
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// `[[Get]]` for arguments exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-get-p-receiver
|
||||
pub(crate) fn arguments_exotic_get(
|
||||
obj: &JsObject,
|
||||
key: &PropertyKey,
|
||||
receiver: JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
if let PropertyKey::Index(index) = key {
|
||||
// 1. Let map be args.[[ParameterMap]].
|
||||
// 2. Let isMapped be ! HasOwnProperty(map, P).
|
||||
if let Some(value) = obj
|
||||
.borrow()
|
||||
.as_mapped_arguments()
|
||||
.expect("arguments exotic method must only be callable from arguments objects")
|
||||
.get(*index as usize)
|
||||
{
|
||||
// a. Assert: map contains a formal parameter mapping for P.
|
||||
// b. Return Get(map, P).
|
||||
return Ok(value);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. If isMapped is false, then
|
||||
// a. Return ? OrdinaryGet(args, P, Receiver).
|
||||
super::ordinary_get(obj, key, receiver, context)
|
||||
}
|
||||
|
||||
/// `[[Set]]` for arguments exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-set-p-v-receiver
|
||||
pub(crate) fn arguments_exotic_set(
|
||||
obj: &JsObject,
|
||||
key: PropertyKey,
|
||||
value: JsValue,
|
||||
receiver: JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
// 1. If SameValue(args, Receiver) is false, then
|
||||
// a. Let isMapped be false.
|
||||
// 2. Else,
|
||||
if let PropertyKey::Index(index) = key {
|
||||
if JsValue::same_value(&obj.clone().into(), &receiver) {
|
||||
// a. Let map be args.[[ParameterMap]].
|
||||
// b. Let isMapped be ! HasOwnProperty(map, P).
|
||||
// 3. If isMapped is true, then
|
||||
// a. Let setStatus be Set(map, P, V, false).
|
||||
// b. Assert: setStatus is true because formal parameters mapped by argument objects are always writable.
|
||||
obj.borrow_mut()
|
||||
.as_mapped_arguments_mut()
|
||||
.expect("arguments exotic method must only be callable from arguments objects")
|
||||
.set(index as usize, &value);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Return ? OrdinarySet(args, P, V, Receiver).
|
||||
super::ordinary_set(obj, key, value, receiver, context)
|
||||
}
|
||||
|
||||
/// `[[Delete]]` for arguments exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-delete-p
|
||||
pub(crate) fn arguments_exotic_delete(
|
||||
obj: &JsObject,
|
||||
key: &PropertyKey,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
// 3. Let result be ? OrdinaryDelete(args, P).
|
||||
let result = super::ordinary_delete(obj, key, context)?;
|
||||
|
||||
if result {
|
||||
if let PropertyKey::Index(index) = key {
|
||||
// 1. Let map be args.[[ParameterMap]].
|
||||
// 2. Let isMapped be ! HasOwnProperty(map, P).
|
||||
// 4. If result is true and isMapped is true, then
|
||||
// a. Call map.[[Delete]](P).
|
||||
obj.borrow_mut()
|
||||
.as_mapped_arguments_mut()
|
||||
.expect("arguments exotic method must only be callable from arguments objects")
|
||||
.delete(*index as usize);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Return result.
|
||||
Ok(result)
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
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).
|
||||
|
||||
array_set_length(obj, desc, context)
|
||||
}
|
||||
// 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)?
|
||||
.expect("the property descriptor must exist");
|
||||
|
||||
// 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)
|
||||
.expect("this ToUint32 call must not fail");
|
||||
|
||||
// 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,
|
||||
)?;
|
||||
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstract operation `ArraySetLength ( A, Desc )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-arraysetlength
|
||||
fn array_set_length(
|
||||
obj: &JsObject,
|
||||
desc: PropertyDescriptor,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
// 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 f64::from(new_len) != number_len {
|
||||
return context.throw_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)?
|
||||
.expect("the property descriptor must exist");
|
||||
|
||||
// 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,
|
||||
)
|
||||
.expect("this OrdinaryDefineOwnProperty call must not fail")
|
||||
{
|
||||
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)
|
||||
.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,
|
||||
)
|
||||
.expect("this OrdinaryDefineOwnProperty call must not fail");
|
||||
|
||||
// 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,
|
||||
)
|
||||
.expect("this OrdinaryDefineOwnProperty call must not fail");
|
||||
|
||||
// b. Assert: succeeded is true.
|
||||
debug_assert!(succeeded);
|
||||
}
|
||||
|
||||
// 19. Return true.
|
||||
Ok(true)
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
use crate::{object::JsObject, Context, JsResult, JsValue};
|
||||
|
||||
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
|
||||
|
||||
/// Definitions of the internal object methods for function objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects
|
||||
pub(crate) static BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS: InternalObjectMethods =
|
||||
InternalObjectMethods {
|
||||
__call__: Some(bound_function_exotic_call),
|
||||
..ORDINARY_INTERNAL_METHODS
|
||||
};
|
||||
|
||||
pub(crate) static BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS: InternalObjectMethods =
|
||||
InternalObjectMethods {
|
||||
__call__: Some(bound_function_exotic_call),
|
||||
__construct__: Some(bound_function_exotic_construct),
|
||||
..ORDINARY_INTERNAL_METHODS
|
||||
};
|
||||
|
||||
/// Internal method `[[Call]]` for Bound Function Exotic Objects
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-bound-function-exotic-objects-call-thisargument-argumentslist
|
||||
#[track_caller]
|
||||
#[inline]
|
||||
fn bound_function_exotic_call(
|
||||
obj: &JsObject,
|
||||
_: &JsValue,
|
||||
arguments_list: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let obj = obj.borrow();
|
||||
let bound_function = obj
|
||||
.as_bound_function()
|
||||
.expect("bound function exotic method should only be callable from bound function objects");
|
||||
|
||||
// 1. Let target be F.[[BoundTargetFunction]].
|
||||
let target = bound_function.target_function();
|
||||
|
||||
// 2. Let boundThis be F.[[BoundThis]].
|
||||
let bound_this = bound_function.this();
|
||||
|
||||
// 3. Let boundArgs be F.[[BoundArguments]].
|
||||
let bound_args = bound_function.args();
|
||||
|
||||
// 4. Let args be the list-concatenation of boundArgs and argumentsList.
|
||||
let mut args = bound_args.to_vec();
|
||||
args.extend_from_slice(arguments_list);
|
||||
|
||||
// 5. Return ? Call(target, boundThis, args).
|
||||
target.call(bound_this, &args, context)
|
||||
}
|
||||
|
||||
/// Internal method `[[Construct]]` for Bound Function Exotic Objects
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-bound-function-exotic-objects-construct-argumentslist-newtarget
|
||||
#[track_caller]
|
||||
#[inline]
|
||||
fn bound_function_exotic_construct(
|
||||
obj: &JsObject,
|
||||
arguments_list: &[JsValue],
|
||||
new_target: &JsObject,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsObject> {
|
||||
let object = obj.borrow();
|
||||
let bound_function = object
|
||||
.as_bound_function()
|
||||
.expect("bound function exotic method should only be callable from bound function objects");
|
||||
|
||||
// 1. Let target be F.[[BoundTargetFunction]].
|
||||
let target = bound_function.target_function();
|
||||
|
||||
// 2. Assert: IsConstructor(target) is true.
|
||||
|
||||
// 3. Let boundArgs be F.[[BoundArguments]].
|
||||
let bound_args = bound_function.args();
|
||||
|
||||
// 4. Let args be the list-concatenation of boundArgs and argumentsList.
|
||||
let mut args = bound_args.to_vec();
|
||||
args.extend_from_slice(arguments_list);
|
||||
|
||||
// 5. If SameValue(F, newTarget) is true, set newTarget to target.
|
||||
let new_target = if JsObject::equals(obj, new_target) {
|
||||
target
|
||||
} else {
|
||||
new_target
|
||||
};
|
||||
|
||||
// 6. Return ? Construct(target, args, newTarget).
|
||||
target.construct(&args, Some(new_target), context)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
use crate::{
|
||||
object::{
|
||||
internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS},
|
||||
JsObject,
|
||||
},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
|
||||
/// Definitions of the internal object methods for function objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects
|
||||
pub(crate) static FUNCTION_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
|
||||
__call__: Some(function_call),
|
||||
__construct__: None,
|
||||
..ORDINARY_INTERNAL_METHODS
|
||||
};
|
||||
|
||||
pub(crate) static CONSTRUCTOR_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
|
||||
__call__: Some(function_call),
|
||||
__construct__: Some(function_construct),
|
||||
..ORDINARY_INTERNAL_METHODS
|
||||
};
|
||||
|
||||
/// 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]
|
||||
fn function_call(
|
||||
obj: &JsObject,
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
obj.call_internal(this, args, context)
|
||||
}
|
||||
|
||||
/// 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]
|
||||
fn function_construct(
|
||||
obj: &JsObject,
|
||||
args: &[JsValue],
|
||||
new_target: &JsObject,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsObject> {
|
||||
obj.construct_internal(args, &new_target.clone().into(), context)
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
use crate::{
|
||||
object::{InternalObjectMethods, JsObject, ORDINARY_INTERNAL_METHODS},
|
||||
property::{DescriptorKind, PropertyDescriptor, PropertyKey},
|
||||
value::JsValue,
|
||||
Context, JsResult,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
/// Definitions of the internal object methods for global object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-global-object
|
||||
pub(crate) static GLOBAL_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
|
||||
__get_own_property__: global_get_own_property,
|
||||
__is_extensible__: global_is_extensible,
|
||||
__prevent_extensions__: global_prevent_extensions,
|
||||
__define_own_property__: global_define_own_property,
|
||||
__has_property__: global_has_property,
|
||||
__get__: global_get,
|
||||
__set__: global_set,
|
||||
__delete__: global_delete,
|
||||
..ORDINARY_INTERNAL_METHODS
|
||||
};
|
||||
|
||||
/// Abstract operation `OrdinaryGetOwnProperty`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinarygetownproperty
|
||||
#[inline]
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn global_get_own_property(
|
||||
_obj: &JsObject,
|
||||
key: &PropertyKey,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Option<PropertyDescriptor>> {
|
||||
let _timer = Profiler::global().start_event("Object::global_get_own_property", "object");
|
||||
// 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(context.realm.global_property_map.get(key))
|
||||
}
|
||||
|
||||
/// Abstract operation `OrdinaryIsExtensible`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryisextensible
|
||||
#[inline]
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn global_is_extensible(_obj: &JsObject, context: &mut Context) -> JsResult<bool> {
|
||||
// 1. Return O.[[Extensible]].
|
||||
Ok(context.realm.global_extensible)
|
||||
}
|
||||
|
||||
/// Abstract operation `OrdinaryPreventExtensions`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinarypreventextensions
|
||||
#[inline]
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn global_prevent_extensions(_obj: &JsObject, context: &mut Context) -> JsResult<bool> {
|
||||
// 1. Set O.[[Extensible]] to false.
|
||||
context.realm.global_extensible = false;
|
||||
|
||||
// 2. Return true.
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Abstract operation `OrdinaryDefineOwnProperty`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinarydefineownproperty
|
||||
#[inline]
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub(crate) fn global_define_own_property(
|
||||
obj: &JsObject,
|
||||
key: PropertyKey,
|
||||
desc: PropertyDescriptor,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
let _timer = Profiler::global().start_event("Object::global_define_own_property", "object");
|
||||
// 1. Let current be ? O.[[GetOwnProperty]](P).
|
||||
let current = global_get_own_property(obj, &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(
|
||||
&key, extensible, desc, current, context,
|
||||
))
|
||||
}
|
||||
|
||||
/// Abstract operation `OrdinaryHasProperty`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasproperty
|
||||
#[inline]
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn global_has_property(
|
||||
_obj: &JsObject,
|
||||
key: &PropertyKey,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
let _timer = Profiler::global().start_event("Object::global_has_property", "object");
|
||||
Ok(context.realm.global_property_map.contains_key(key))
|
||||
}
|
||||
|
||||
/// Abstract operation `OrdinaryGet`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryget
|
||||
#[inline]
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub(crate) fn global_get(
|
||||
obj: &JsObject,
|
||||
key: &PropertyKey,
|
||||
receiver: JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let _timer = Profiler::global().start_event("Object::global_get", "object");
|
||||
// 1. Assert: IsPropertyKey(P) is true.
|
||||
// 2. Let desc be ? O.[[GetOwnProperty]](P).
|
||||
match global_get_own_property(obj, key, context)? {
|
||||
// If desc is undefined, then
|
||||
None => {
|
||||
// b. If parent is null, return undefined.
|
||||
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]
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub(crate) fn global_set(
|
||||
_obj: &JsObject,
|
||||
key: PropertyKey,
|
||||
value: JsValue,
|
||||
_receiver: JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
global_set_no_receiver(&key, value, context)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn global_set_no_receiver(
|
||||
key: &PropertyKey,
|
||||
value: JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
let _timer = Profiler::global().start_event("Object::global_set", "object");
|
||||
|
||||
// 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) = context.realm.global_property_map.get(key) {
|
||||
desc
|
||||
}
|
||||
// c. Else,
|
||||
else {
|
||||
PropertyDescriptor::builder()
|
||||
.value(value.clone())
|
||||
.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);
|
||||
}
|
||||
|
||||
// c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P).
|
||||
// d. If existingDescriptor is not undefined, then
|
||||
let desc = if let Some(existing_desc) = context.realm.global_property_map.get(key) {
|
||||
// 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).
|
||||
PropertyDescriptor::builder().value(value).build()
|
||||
} else {
|
||||
// i. Assert: Receiver does not currently have a property P.
|
||||
// ii. Return ? CreateDataProperty(Receiver, P, V).
|
||||
PropertyDescriptor::builder()
|
||||
.value(value)
|
||||
.writable(true)
|
||||
.enumerable(true)
|
||||
.configurable(true)
|
||||
.build()
|
||||
};
|
||||
|
||||
// 1. Let current be ? O.[[GetOwnProperty]](P).
|
||||
let current = context.realm.global_property_map.get(key);
|
||||
|
||||
// 2. Let extensible be ? IsExtensible(O).
|
||||
let extensible = context.realm.global_extensible;
|
||||
|
||||
// 3. Return ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current).
|
||||
return Ok(validate_and_apply_property_descriptor(
|
||||
key, extensible, desc, current, 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, &context.global_object().clone().into(), &[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]
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn global_delete(
|
||||
_obj: &JsObject,
|
||||
key: &PropertyKey,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
let _timer = Profiler::global().start_event("Object::global_delete", "object");
|
||||
// 1. Assert: IsPropertyKey(P) is true.
|
||||
// 2. Let desc be ? O.[[GetOwnProperty]](P).
|
||||
match context.realm.global_property_map.get(key) {
|
||||
// 4. If desc.[[Configurable]] is true, then
|
||||
Some(desc) if desc.expect_configurable() => {
|
||||
// a. Remove the own property with name P from O.
|
||||
context.realm.global_property_map.remove(key);
|
||||
// b. Return true.
|
||||
Ok(true)
|
||||
}
|
||||
// 5. Return false.
|
||||
Some(_) => Ok(false),
|
||||
// 3. If desc is undefined, return true.
|
||||
None => Ok(true),
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstract operation `ValidateAndApplyPropertyDescriptor`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor
|
||||
#[inline]
|
||||
pub(crate) fn validate_and_apply_property_descriptor(
|
||||
key: &PropertyKey,
|
||||
extensible: bool,
|
||||
desc: PropertyDescriptor,
|
||||
current: Option<PropertyDescriptor>,
|
||||
context: &mut Context,
|
||||
) -> bool {
|
||||
let _timer = Profiler::global().start_event(
|
||||
"Object::global_validate_and_apply_property_descriptor",
|
||||
"object",
|
||||
);
|
||||
// 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.
|
||||
context.realm.global_property_map.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;
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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);
|
||||
context.realm.global_property_map.insert(key, current);
|
||||
|
||||
// 10. Return true.
|
||||
true
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
use crate::{
|
||||
builtins::{array_buffer::SharedMemoryOrder, typed_array::integer_indexed_object::ContentType},
|
||||
object::JsObject,
|
||||
property::{PropertyDescriptor, PropertyKey},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
|
||||
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
|
||||
|
||||
/// Definitions of the internal object methods for integer-indexed exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects
|
||||
pub(crate) static INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS: InternalObjectMethods =
|
||||
InternalObjectMethods {
|
||||
__get_own_property__: integer_indexed_exotic_get_own_property,
|
||||
__has_property__: integer_indexed_exotic_has_property,
|
||||
__define_own_property__: integer_indexed_exotic_define_own_property,
|
||||
__get__: integer_indexed_exotic_get,
|
||||
__set__: integer_indexed_exotic_set,
|
||||
__delete__: integer_indexed_exotic_delete,
|
||||
__own_property_keys__: integer_indexed_exotic_own_property_keys,
|
||||
..ORDINARY_INTERNAL_METHODS
|
||||
};
|
||||
|
||||
/// `[[GetOwnProperty]]` internal method for Integer-Indexed exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-getownproperty-p
|
||||
#[inline]
|
||||
pub(crate) fn integer_indexed_exotic_get_own_property(
|
||||
obj: &JsObject,
|
||||
key: &PropertyKey,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Option<PropertyDescriptor>> {
|
||||
// 1. If Type(P) is String, then
|
||||
// a. Let numericIndex be ! CanonicalNumericIndexString(P).
|
||||
// b. If numericIndex is not undefined, then
|
||||
if let PropertyKey::Index(index) = key {
|
||||
// i. Let value be ! IntegerIndexedElementGet(O, numericIndex).
|
||||
// ii. If value is undefined, return undefined.
|
||||
// iii. Return the PropertyDescriptor { [[Value]]: value, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
|
||||
Ok(integer_indexed_element_get(obj, *index as usize).map(|v| {
|
||||
PropertyDescriptor::builder()
|
||||
.value(v)
|
||||
.writable(true)
|
||||
.enumerable(true)
|
||||
.configurable(true)
|
||||
.build()
|
||||
}))
|
||||
} else {
|
||||
// 2. Return OrdinaryGetOwnProperty(O, P).
|
||||
super::ordinary_get_own_property(obj, key, context)
|
||||
}
|
||||
}
|
||||
|
||||
/// `[[HasProperty]]` internal method for Integer-Indexed exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-hasproperty-p
|
||||
#[inline]
|
||||
pub(crate) fn integer_indexed_exotic_has_property(
|
||||
obj: &JsObject,
|
||||
key: &PropertyKey,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
// 1. If Type(P) is String, then
|
||||
// a. Let numericIndex be ! CanonicalNumericIndexString(P).
|
||||
if let PropertyKey::Index(index) = key {
|
||||
// b. If numericIndex is not undefined, return ! IsValidIntegerIndex(O, numericIndex).
|
||||
Ok(is_valid_integer_index(obj, *index as usize))
|
||||
} else {
|
||||
// 2. Return ? OrdinaryHasProperty(O, P).
|
||||
super::ordinary_has_property(obj, key, context)
|
||||
}
|
||||
}
|
||||
|
||||
/// `[[DefineOwnProperty]]` internal method for Integer-Indexed exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-defineownproperty-p-desc
|
||||
#[inline]
|
||||
pub(crate) fn integer_indexed_exotic_define_own_property(
|
||||
obj: &JsObject,
|
||||
key: PropertyKey,
|
||||
desc: PropertyDescriptor,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
// 1. If Type(P) is String, then
|
||||
// a. Let numericIndex be ! CanonicalNumericIndexString(P).
|
||||
// b. If numericIndex is not undefined, then
|
||||
if let PropertyKey::Index(index) = key {
|
||||
// i. If ! IsValidIntegerIndex(O, numericIndex) is false, return false.
|
||||
// ii. If Desc has a [[Configurable]] field and if Desc.[[Configurable]] is false, return false.
|
||||
// iii. If Desc has an [[Enumerable]] field and if Desc.[[Enumerable]] is false, return false.
|
||||
// v. If Desc has a [[Writable]] field and if Desc.[[Writable]] is false, return false.
|
||||
// iv. If ! IsAccessorDescriptor(Desc) is true, return false.
|
||||
if !is_valid_integer_index(obj, index as usize)
|
||||
|| !desc
|
||||
.configurable()
|
||||
.or_else(|| desc.enumerable())
|
||||
.or_else(|| desc.writable())
|
||||
.unwrap_or(true)
|
||||
|| desc.is_accessor_descriptor()
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// vi. If Desc has a [[Value]] field, perform ? IntegerIndexedElementSet(O, numericIndex, Desc.[[Value]]).
|
||||
if let Some(value) = desc.value() {
|
||||
integer_indexed_element_set(obj, index as usize, value, context)?;
|
||||
}
|
||||
|
||||
// vii. Return true.
|
||||
Ok(true)
|
||||
} else {
|
||||
// 2. Return ! OrdinaryDefineOwnProperty(O, P, Desc).
|
||||
super::ordinary_define_own_property(obj, key, desc, context)
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal method `[[Get]]` for Integer-Indexed exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-get-p-receiver
|
||||
#[inline]
|
||||
pub(crate) fn integer_indexed_exotic_get(
|
||||
obj: &JsObject,
|
||||
key: &PropertyKey,
|
||||
receiver: JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If Type(P) is String, then
|
||||
// a. Let numericIndex be ! CanonicalNumericIndexString(P).
|
||||
// b. If numericIndex is not undefined, then
|
||||
if let PropertyKey::Index(index) = key {
|
||||
// i. Return ! IntegerIndexedElementGet(O, numericIndex).
|
||||
Ok(integer_indexed_element_get(obj, *index as usize).unwrap_or_default())
|
||||
} else {
|
||||
// 2. Return ? OrdinaryGet(O, P, Receiver).
|
||||
super::ordinary_get(obj, key, receiver, context)
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal method `[[Set]]` for Integer-Indexed exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-set-p-v-receiver
|
||||
#[inline]
|
||||
pub(crate) fn integer_indexed_exotic_set(
|
||||
obj: &JsObject,
|
||||
key: PropertyKey,
|
||||
value: JsValue,
|
||||
receiver: JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
// 1. If Type(P) is String, then
|
||||
// a. Let numericIndex be ! CanonicalNumericIndexString(P).
|
||||
// b. If numericIndex is not undefined, then
|
||||
if let PropertyKey::Index(index) = key {
|
||||
// i. Perform ? IntegerIndexedElementSet(O, numericIndex, V).
|
||||
integer_indexed_element_set(obj, index as usize, &value, context)?;
|
||||
|
||||
// ii. Return true.
|
||||
Ok(true)
|
||||
} else {
|
||||
// 2. Return ? OrdinarySet(O, P, V, Receiver).
|
||||
super::ordinary_set(obj, key, value, receiver, context)
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal method `[[Delete]]` for Integer-Indexed exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-delete-p
|
||||
#[inline]
|
||||
pub(crate) fn integer_indexed_exotic_delete(
|
||||
obj: &JsObject,
|
||||
key: &PropertyKey,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
// 1. If Type(P) is String, then
|
||||
// a. Let numericIndex be ! CanonicalNumericIndexString(P).
|
||||
// b. If numericIndex is not undefined, then
|
||||
if let PropertyKey::Index(index) = key {
|
||||
// i. If ! IsValidIntegerIndex(O, numericIndex) is false, return true; else return false.
|
||||
Ok(!is_valid_integer_index(obj, *index as usize))
|
||||
} else {
|
||||
// 2. Return ? OrdinaryDelete(O, P).
|
||||
super::ordinary_delete(obj, key, context)
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal method `[[OwnPropertyKeys]]` for Integer-Indexed exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-ownpropertykeys
|
||||
#[inline]
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn integer_indexed_exotic_own_property_keys(
|
||||
obj: &JsObject,
|
||||
_context: &mut Context,
|
||||
) -> JsResult<Vec<PropertyKey>> {
|
||||
let obj = obj.borrow();
|
||||
let inner = obj.as_typed_array().expect(
|
||||
"integer indexed exotic method should only be callable from integer indexed objects",
|
||||
);
|
||||
|
||||
// 1. Let keys be a new empty List.
|
||||
let mut keys = if inner.is_detached() {
|
||||
vec![]
|
||||
} else {
|
||||
// 2. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is false, then
|
||||
// a. For each integer i starting with 0 such that i < O.[[ArrayLength]], in ascending order, do
|
||||
// i. Add ! ToString(𝔽(i)) as the last element of keys.
|
||||
(0..inner.array_length())
|
||||
.into_iter()
|
||||
.map(|index| PropertyKey::Index(index as u32))
|
||||
.collect()
|
||||
};
|
||||
|
||||
// 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.properties
|
||||
.string_property_keys()
|
||||
.cloned()
|
||||
.map(Into::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.properties
|
||||
.symbol_property_keys()
|
||||
.cloned()
|
||||
.map(Into::into),
|
||||
);
|
||||
|
||||
// 5. Return keys.
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
/// Abstract operation `IsValidIntegerIndex ( O, index )`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isvalidintegerindex
|
||||
pub(crate) fn is_valid_integer_index(obj: &JsObject, index: usize) -> bool {
|
||||
let obj = obj.borrow();
|
||||
let inner = obj.as_typed_array().expect(
|
||||
"integer indexed exotic method should only be callable from integer indexed objects",
|
||||
);
|
||||
// 1. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, return false.
|
||||
// 2. If ! IsIntegralNumber(index) is false, return false.
|
||||
// 3. If index is -0𝔽, return false.
|
||||
// 4. If ℝ(index) < 0 or ℝ(index) ≥ O.[[ArrayLength]], return false.
|
||||
// 5. Return true.
|
||||
!inner.is_detached() && index < inner.array_length()
|
||||
}
|
||||
|
||||
/// Abstract operation `IntegerIndexedElementGet ( O, index )`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integerindexedelementget
|
||||
fn integer_indexed_element_get(obj: &JsObject, index: usize) -> Option<JsValue> {
|
||||
// 1. If ! IsValidIntegerIndex(O, index) is false, return undefined.
|
||||
if !is_valid_integer_index(obj, index) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let obj = obj.borrow();
|
||||
let inner = obj
|
||||
.as_typed_array()
|
||||
.expect("Already checked for detached buffer");
|
||||
let buffer_obj = inner
|
||||
.viewed_array_buffer()
|
||||
.expect("Already checked for detached buffer");
|
||||
let buffer_obj_borrow = buffer_obj.borrow();
|
||||
let buffer = buffer_obj_borrow
|
||||
.as_array_buffer()
|
||||
.expect("Already checked for detached buffer");
|
||||
|
||||
// 2. Let offset be O.[[ByteOffset]].
|
||||
let offset = inner.byte_offset();
|
||||
|
||||
// 3. Let arrayTypeName be the String value of O.[[TypedArrayName]].
|
||||
// 6. Let elementType be the Element Type value in Table 73 for arrayTypeName.
|
||||
let elem_type = inner.typed_array_name();
|
||||
|
||||
// 4. Let elementSize be the Element Size value specified in Table 73 for arrayTypeName.
|
||||
let size = elem_type.element_size();
|
||||
|
||||
// 5. Let indexedPosition be (ℝ(index) × elementSize) + offset.
|
||||
let indexed_position = (index * size) + offset;
|
||||
|
||||
// 7. Return GetValueFromBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, true, Unordered).
|
||||
Some(buffer.get_value_from_buffer(
|
||||
indexed_position,
|
||||
elem_type,
|
||||
true,
|
||||
SharedMemoryOrder::Unordered,
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
/// Abstract operation `IntegerIndexedElementSet ( O, index, value )`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integerindexedelementset
|
||||
fn integer_indexed_element_set(
|
||||
obj: &JsObject,
|
||||
index: usize,
|
||||
value: &JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<()> {
|
||||
let obj_borrow = obj.borrow();
|
||||
let inner = obj_borrow.as_typed_array().expect(
|
||||
"integer indexed exotic method should only be callable from integer indexed objects",
|
||||
);
|
||||
|
||||
let num_value = if inner.typed_array_name().content_type() == ContentType::BigInt {
|
||||
// 1. If O.[[ContentType]] is BigInt, let numValue be ? ToBigInt(value).
|
||||
value.to_bigint(context)?.into()
|
||||
} else {
|
||||
// 2. Otherwise, let numValue be ? ToNumber(value).
|
||||
value.to_number(context)?.into()
|
||||
};
|
||||
|
||||
// 3. If ! IsValidIntegerIndex(O, index) is true, then
|
||||
if is_valid_integer_index(obj, index) {
|
||||
// a. Let offset be O.[[ByteOffset]].
|
||||
let offset = inner.byte_offset();
|
||||
|
||||
// b. Let arrayTypeName be the String value of O.[[TypedArrayName]].
|
||||
// e. Let elementType be the Element Type value in Table 73 for arrayTypeName.
|
||||
let elem_type = inner.typed_array_name();
|
||||
|
||||
// c. Let elementSize be the Element Size value specified in Table 73 for arrayTypeName.
|
||||
let size = elem_type.element_size();
|
||||
|
||||
// d. Let indexedPosition be (ℝ(index) × elementSize) + offset.
|
||||
let indexed_position = (index * size) + offset;
|
||||
|
||||
let buffer_obj = inner
|
||||
.viewed_array_buffer()
|
||||
.expect("Already checked for detached buffer");
|
||||
let mut buffer_obj_borrow = buffer_obj.borrow_mut();
|
||||
let buffer = buffer_obj_borrow
|
||||
.as_array_buffer_mut()
|
||||
.expect("Already checked for detached buffer");
|
||||
|
||||
// f. Perform SetValueInBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, numValue, true, Unordered).
|
||||
buffer
|
||||
.set_value_in_buffer(
|
||||
indexed_position,
|
||||
elem_type,
|
||||
&num_value,
|
||||
SharedMemoryOrder::Unordered,
|
||||
None,
|
||||
context,
|
||||
)
|
||||
.expect("SetValueInBuffer cannot fail here");
|
||||
}
|
||||
|
||||
// 4. Return NormalCompletion(undefined).
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,967 @@
|
||||
//! 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 super::{JsPrototype, PROTOTYPE};
|
||||
use crate::{
|
||||
context::intrinsics::{StandardConstructor, StandardConstructors},
|
||||
object::JsObject,
|
||||
property::{DescriptorKind, PropertyDescriptor, PropertyKey},
|
||||
value::JsValue,
|
||||
Context, JsResult,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
pub(super) mod arguments;
|
||||
pub(super) mod array;
|
||||
pub(super) mod bound_function;
|
||||
pub(super) mod function;
|
||||
pub(crate) mod global;
|
||||
pub(super) mod integer_indexed;
|
||||
pub(super) mod proxy;
|
||||
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<JsPrototype> {
|
||||
let _timer = Profiler::global().start_event("Object::__get_prototype_of__", "object");
|
||||
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__(
|
||||
&self,
|
||||
val: JsPrototype,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
let _timer = Profiler::global().start_event("Object::__set_prototype_of__", "object");
|
||||
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 _timer = Profiler::global().start_event("Object::__is_extensible__", "object");
|
||||
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__(&self, context: &mut Context) -> JsResult<bool> {
|
||||
let _timer = Profiler::global().start_event("Object::__prevent_extensions__", "object");
|
||||
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 = Profiler::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 _timer = Profiler::global().start_event("Object::__define_own_property__", "object");
|
||||
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 _timer = Profiler::global().start_event("Object::__has_property__", "object");
|
||||
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 _timer = Profiler::global().start_event("Object::__get__", "object");
|
||||
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 = Profiler::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 _timer = Profiler::global().start_event("Object::__delete__", "object");
|
||||
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 _timer = Profiler::global().start_event("Object::__own_property_keys__", "object");
|
||||
let func = self.borrow().data.internal_methods.__own_property_keys__;
|
||||
func(self, context)
|
||||
}
|
||||
|
||||
/// Internal method `[[Call]]`
|
||||
///
|
||||
/// Call this object if it has a `[[Call]]` internal method.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub(crate) fn __call__(
|
||||
&self,
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let _timer = Profiler::global().start_event("Object::__call__", "object");
|
||||
let func = self.borrow().data.internal_methods.__call__;
|
||||
func.expect("called `[[Call]]` for object without a `[[Call]]` internal method")(
|
||||
self, this, args, context,
|
||||
)
|
||||
}
|
||||
|
||||
/// Internal method `[[Construct]]`
|
||||
///
|
||||
/// Construct a new instance of this object if this object has a `[[Construct]]` internal method.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub(crate) fn __construct__(
|
||||
&self,
|
||||
args: &[JsValue],
|
||||
new_target: &JsObject,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsObject> {
|
||||
let _timer = Profiler::global().start_event("Object::__construct__", "object");
|
||||
let func = self.borrow().data.internal_methods.__construct__;
|
||||
func.expect("called `[[Construct]]` for object without a `[[Construct]]` internal method")(
|
||||
self, args, new_target, 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,
|
||||
__call__: None,
|
||||
__construct__: None,
|
||||
};
|
||||
|
||||
/// 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)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) struct InternalObjectMethods {
|
||||
pub(crate) __get_prototype_of__: fn(&JsObject, &mut Context) -> JsResult<JsPrototype>,
|
||||
pub(crate) __set_prototype_of__: fn(&JsObject, JsPrototype, &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>>,
|
||||
pub(crate) __call__:
|
||||
Option<fn(&JsObject, &JsValue, &[JsValue], &mut Context) -> JsResult<JsValue>>,
|
||||
pub(crate) __construct__:
|
||||
Option<fn(&JsObject, &[JsValue], &JsObject, &mut Context) -> JsResult<JsObject>>,
|
||||
}
|
||||
|
||||
/// Abstract operation `OrdinaryGetPrototypeOf`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinarygetprototypeof
|
||||
#[inline]
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn ordinary_get_prototype_of(
|
||||
obj: &JsObject,
|
||||
_context: &mut Context,
|
||||
) -> JsResult<JsPrototype> {
|
||||
let _timer = Profiler::global().start_event("Object::ordinary_get_prototype_of", "object");
|
||||
|
||||
// 1. Return O.[[Prototype]].
|
||||
Ok(obj.prototype().as_ref().cloned())
|
||||
}
|
||||
|
||||
/// Abstract operation `OrdinarySetPrototypeOf`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinarysetprototypeof
|
||||
#[inline]
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn ordinary_set_prototype_of(
|
||||
obj: &JsObject,
|
||||
val: JsPrototype,
|
||||
_: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
// 1. Assert: Either Type(V) is Object or Type(V) is Null.
|
||||
{
|
||||
// 2. Let current be O.[[Prototype]].
|
||||
let current = obj.prototype();
|
||||
|
||||
// 3. If SameValue(V, current) is true, return true.
|
||||
if val == *current {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Let extensible be O.[[Extensible]].
|
||||
// 5. If extensible is false, return false.
|
||||
if !obj.extensible() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// 6. Let p be V.
|
||||
let mut p = val.clone();
|
||||
|
||||
// 7. Let done be false.
|
||||
// 8. Repeat, while done is false,
|
||||
// a. If p is null, set done to true.
|
||||
while let Some(proto) = p {
|
||||
// b. Else if SameValue(p, O) is true, return false.
|
||||
if &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
|
||||
{
|
||||
break;
|
||||
}
|
||||
// ii. Else, set p to p.[[Prototype]].
|
||||
p = proto.prototype().clone();
|
||||
}
|
||||
|
||||
// 9. Set O.[[Prototype]] to V.
|
||||
obj.set_prototype(val);
|
||||
|
||||
// 10. Return true.
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Abstract operation `OrdinaryIsExtensible`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryisextensible
|
||||
#[inline]
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
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]
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
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]
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn ordinary_get_own_property(
|
||||
obj: &JsObject,
|
||||
key: &PropertyKey,
|
||||
_context: &mut Context,
|
||||
) -> JsResult<Option<PropertyDescriptor>> {
|
||||
let _timer = Profiler::global().start_event("Object::ordinary_get_own_property", "object");
|
||||
// 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))
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
let _timer = Profiler::global().start_event("Object::ordinary_define_own_property", "object");
|
||||
// 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> {
|
||||
let _timer = Profiler::global().start_event("Object::ordinary_has_property", "object");
|
||||
// 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)?;
|
||||
|
||||
parent
|
||||
// 5. If parent is not null, then
|
||||
// a. Return ? parent.[[HasProperty]](P).
|
||||
// 6. Return false.
|
||||
.map_or(Ok(false), |obj| obj.__has_property__(key, context))
|
||||
}
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
let _timer = Profiler::global().start_event("Object::ordinary_get", "object");
|
||||
// 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)? {
|
||||
// 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> {
|
||||
let _timer = Profiler::global().start_event("Object::ordinary_set", "object");
|
||||
|
||||
// 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(parent) = obj.__get_prototype_of__(context)? {
|
||||
// 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
|
||||
// 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> {
|
||||
let _timer = Profiler::global().start_event("Object::ordinary_delete", "object");
|
||||
// 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]
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn ordinary_own_property_keys(
|
||||
obj: &JsObject,
|
||||
_context: &mut Context,
|
||||
) -> JsResult<Vec<PropertyKey>> {
|
||||
let _timer = Profiler::global().start_event("Object::ordinary_own_property_keys", "object");
|
||||
// 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().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(Into::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(Into::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(Into::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: Option<PropertyDescriptor>,
|
||||
) -> bool {
|
||||
let _timer =
|
||||
Profiler::global().start_event("Object::is_compatible_property_descriptor", "object");
|
||||
|
||||
// 1. Return ValidateAndApplyPropertyDescriptor(undefined, undefined, Extensible, Desc, Current).
|
||||
validate_and_apply_property_descriptor(None, extensible, desc, 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 {
|
||||
let _timer =
|
||||
Profiler::global().start_event("Object::validate_and_apply_property_descriptor", "object");
|
||||
// 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
|
||||
}
|
||||
|
||||
/// Abstract operation `GetPrototypeFromConstructor`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-getprototypefromconstructor
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub(crate) fn get_prototype_from_constructor<F>(
|
||||
constructor: &JsValue,
|
||||
default: F,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsObject>
|
||||
where
|
||||
F: FnOnce(&StandardConstructors) -> &StandardConstructor,
|
||||
{
|
||||
let _timer = Profiler::global().start_event("Object::get_prototype_from_constructor", "object");
|
||||
// 1. Assert: intrinsicDefaultProto is this specification's name of an intrinsic
|
||||
// object.
|
||||
// The corresponding object must be an intrinsic that is intended to be used
|
||||
// as the [[Prototype]] value of an object.
|
||||
// 2. Let proto be ? Get(constructor, "prototype").
|
||||
if let Some(object) = constructor.as_object() {
|
||||
if let Some(proto) = object.get(PROTOTYPE, context)?.as_object() {
|
||||
return Ok(proto.clone());
|
||||
}
|
||||
}
|
||||
// 3. If Type(proto) is not Object, then
|
||||
// TODO: handle realms
|
||||
// a. Let realm be ? GetFunctionRealm(constructor).
|
||||
// b. Set proto to realm's intrinsic object named intrinsicDefaultProto.
|
||||
Ok(default(context.intrinsics().constructors()).prototype())
|
||||
}
|
||||
@@ -0,0 +1,993 @@
|
||||
use crate::{
|
||||
builtins::{array, object::Object},
|
||||
object::{InternalObjectMethods, JsObject, JsPrototype},
|
||||
property::{PropertyDescriptor, PropertyKey},
|
||||
value::Type,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
/// 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 PROXY_EXOTIC_INTERNAL_METHODS_BASIC: InternalObjectMethods =
|
||||
InternalObjectMethods {
|
||||
__get_prototype_of__: proxy_exotic_get_prototype_of,
|
||||
__set_prototype_of__: proxy_exotic_set_prototype_of,
|
||||
__is_extensible__: proxy_exotic_is_extensible,
|
||||
__prevent_extensions__: proxy_exotic_prevent_extensions,
|
||||
__get_own_property__: proxy_exotic_get_own_property,
|
||||
__define_own_property__: proxy_exotic_define_own_property,
|
||||
__has_property__: proxy_exotic_has_property,
|
||||
__get__: proxy_exotic_get,
|
||||
__set__: proxy_exotic_set,
|
||||
__delete__: proxy_exotic_delete,
|
||||
__own_property_keys__: proxy_exotic_own_property_keys,
|
||||
__call__: None,
|
||||
__construct__: None,
|
||||
};
|
||||
|
||||
pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_WITH_CALL: InternalObjectMethods =
|
||||
InternalObjectMethods {
|
||||
__call__: Some(proxy_exotic_call),
|
||||
..PROXY_EXOTIC_INTERNAL_METHODS_BASIC
|
||||
};
|
||||
|
||||
pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_ALL: InternalObjectMethods =
|
||||
InternalObjectMethods {
|
||||
__call__: Some(proxy_exotic_call),
|
||||
__construct__: Some(proxy_exotic_construct),
|
||||
..PROXY_EXOTIC_INTERNAL_METHODS_BASIC
|
||||
};
|
||||
|
||||
/// `10.5.1 [[GetPrototypeOf]] ( )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof
|
||||
pub(crate) fn proxy_exotic_get_prototype_of(
|
||||
obj: &JsObject,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsPrototype> {
|
||||
// 1. Let handler be O.[[ProxyHandler]].
|
||||
// 2. If handler is null, throw a TypeError exception.
|
||||
// 3. Assert: Type(handler) is Object.
|
||||
// 4. Let target be O.[[ProxyTarget]].
|
||||
let (target, handler) = obj
|
||||
.borrow()
|
||||
.as_proxy()
|
||||
.expect("Proxy object internal internal method called on non-proxy object")
|
||||
.try_data(context)?;
|
||||
|
||||
// 5. Let trap be ? GetMethod(handler, "getPrototypeOf").
|
||||
let trap = if let Some(trap) = handler.get_method("getPrototypeOf", context)? {
|
||||
trap
|
||||
// 6. If trap is undefined, then
|
||||
} else {
|
||||
// a. Return ? target.[[GetPrototypeOf]]().
|
||||
return target.__get_prototype_of__(context);
|
||||
};
|
||||
|
||||
// 7. Let handlerProto be ? Call(trap, handler, « target »).
|
||||
let handler_proto = trap.call(&handler.into(), &[target.clone().into()], context)?;
|
||||
|
||||
// 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError exception.
|
||||
let handler_proto = match &handler_proto {
|
||||
JsValue::Object(obj) => Some(obj.clone()),
|
||||
JsValue::Null => None,
|
||||
_ => return context.throw_type_error("Proxy trap result is neither object nor null"),
|
||||
};
|
||||
|
||||
// 9. Let extensibleTarget be ? IsExtensible(target).
|
||||
// 10. If extensibleTarget is true, return handlerProto.
|
||||
if target.is_extensible(context)? {
|
||||
return Ok(handler_proto);
|
||||
}
|
||||
|
||||
// 11. Let targetProto be ? target.[[GetPrototypeOf]]().
|
||||
let target_proto = target.__get_prototype_of__(context)?;
|
||||
|
||||
// 12. If SameValue(handlerProto, targetProto) is false, throw a TypeError exception.
|
||||
if handler_proto != target_proto {
|
||||
return context.throw_type_error("Proxy trap returned unexpected prototype");
|
||||
}
|
||||
|
||||
// 13. Return handlerProto.
|
||||
Ok(handler_proto)
|
||||
}
|
||||
|
||||
/// `10.5.2 [[SetPrototypeOf]] ( V )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-setprototypeof-v
|
||||
#[inline]
|
||||
pub(crate) fn proxy_exotic_set_prototype_of(
|
||||
obj: &JsObject,
|
||||
val: JsPrototype,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
// 1. Let handler be O.[[ProxyHandler]].
|
||||
// 2. If handler is null, throw a TypeError exception.
|
||||
// 3. Assert: Type(handler) is Object.
|
||||
// 4. Let target be O.[[ProxyTarget]].
|
||||
let (target, handler) = obj
|
||||
.borrow()
|
||||
.as_proxy()
|
||||
.expect("Proxy object internal internal method called on non-proxy object")
|
||||
.try_data(context)?;
|
||||
|
||||
// 5. Let trap be ? GetMethod(handler, "setPrototypeOf").
|
||||
let trap = if let Some(trap) = handler.get_method("setPrototypeOf", context)? {
|
||||
trap
|
||||
// 6. If trap is undefined, then
|
||||
} else {
|
||||
// a. Return ? target.[[SetPrototypeOf]](V).
|
||||
return target.__set_prototype_of__(val, context);
|
||||
};
|
||||
|
||||
// 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, V »)).
|
||||
// 8. If booleanTrapResult is false, return false.
|
||||
if !trap
|
||||
.call(
|
||||
&handler.into(),
|
||||
&[
|
||||
target.clone().into(),
|
||||
val.clone().map_or(JsValue::Null, Into::into),
|
||||
],
|
||||
context,
|
||||
)?
|
||||
.to_boolean()
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// 9. Let extensibleTarget be ? IsExtensible(target).
|
||||
// 10. If extensibleTarget is true, return true.
|
||||
if target.is_extensible(context)? {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// 11. Let targetProto be ? target.[[GetPrototypeOf]]().
|
||||
let target_proto = target.__get_prototype_of__(context)?;
|
||||
|
||||
// 12. If SameValue(V, targetProto) is false, throw a TypeError exception.
|
||||
if val != target_proto {
|
||||
return context.throw_type_error("Proxy trap failed to set prototype");
|
||||
}
|
||||
|
||||
// 13. Return true.
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// `10.5.3 [[IsExtensible]] ( )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-isextensible
|
||||
#[inline]
|
||||
pub(crate) fn proxy_exotic_is_extensible(obj: &JsObject, context: &mut Context) -> JsResult<bool> {
|
||||
// 1. Let handler be O.[[ProxyHandler]].
|
||||
// 2. If handler is null, throw a TypeError exception.
|
||||
// 3. Assert: Type(handler) is Object.
|
||||
// 4. Let target be O.[[ProxyTarget]].
|
||||
let (target, handler) = obj
|
||||
.borrow()
|
||||
.as_proxy()
|
||||
.expect("Proxy object internal internal method called on non-proxy object")
|
||||
.try_data(context)?;
|
||||
|
||||
// 5. Let trap be ? GetMethod(handler, "isExtensible").
|
||||
let trap = if let Some(trap) = handler.get_method("isExtensible", context)? {
|
||||
trap
|
||||
// 6. If trap is undefined, then
|
||||
} else {
|
||||
// a. Return ? IsExtensible(target).
|
||||
return target.is_extensible(context);
|
||||
};
|
||||
|
||||
// 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target »)).
|
||||
let boolean_trap_result = trap
|
||||
.call(&handler.into(), &[target.clone().into()], context)?
|
||||
.to_boolean();
|
||||
|
||||
// 8. Let targetResult be ? IsExtensible(target).
|
||||
let target_result = target.is_extensible(context)?;
|
||||
|
||||
// 9. If SameValue(booleanTrapResult, targetResult) is false, throw a TypeError exception.
|
||||
if boolean_trap_result != target_result {
|
||||
return context.throw_type_error("Proxy trap returned unexpected extensible value");
|
||||
}
|
||||
|
||||
// 10. Return booleanTrapResult.
|
||||
Ok(boolean_trap_result)
|
||||
}
|
||||
|
||||
/// `10.5.4 [[PreventExtensions]] ( )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-preventextensions
|
||||
#[inline]
|
||||
pub(crate) fn proxy_exotic_prevent_extensions(
|
||||
obj: &JsObject,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
// 1. Let handler be O.[[ProxyHandler]].
|
||||
// 2. If handler is null, throw a TypeError exception.
|
||||
// 3. Assert: Type(handler) is Object.
|
||||
// 4. Let target be O.[[ProxyTarget]].
|
||||
let (target, handler) = obj
|
||||
.borrow()
|
||||
.as_proxy()
|
||||
.expect("Proxy object internal internal method called on non-proxy object")
|
||||
.try_data(context)?;
|
||||
|
||||
// 5. Let trap be ? GetMethod(handler, "preventExtensions").
|
||||
let trap = if let Some(trap) = handler.get_method("preventExtensions", context)? {
|
||||
trap
|
||||
// 6. If trap is undefined, then
|
||||
} else {
|
||||
// a. Return ? target.[[PreventExtensions]]().
|
||||
return target.__prevent_extensions__(context);
|
||||
};
|
||||
|
||||
// 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target »)).
|
||||
let boolean_trap_result = trap
|
||||
.call(&handler.into(), &[target.clone().into()], context)?
|
||||
.to_boolean();
|
||||
|
||||
// 8. If booleanTrapResult is true, then
|
||||
if boolean_trap_result && target.is_extensible(context)? {
|
||||
// a. Let extensibleTarget be ? IsExtensible(target).
|
||||
// b. If extensibleTarget is true, throw a TypeError exception.
|
||||
return context.throw_type_error("Proxy trap failed to set extensible");
|
||||
}
|
||||
|
||||
// 9. Return booleanTrapResult.
|
||||
Ok(boolean_trap_result)
|
||||
}
|
||||
|
||||
/// `10.5.5 [[GetOwnProperty]] ( P )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getownproperty-p
|
||||
#[inline]
|
||||
pub(crate) fn proxy_exotic_get_own_property(
|
||||
obj: &JsObject,
|
||||
key: &PropertyKey,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Option<PropertyDescriptor>> {
|
||||
// 1. Let handler be O.[[ProxyHandler]].
|
||||
// 2. If handler is null, throw a TypeError exception.
|
||||
// 3. Assert: Type(handler) is Object.
|
||||
// 4. Let target be O.[[ProxyTarget]].
|
||||
let (target, handler) = obj
|
||||
.borrow()
|
||||
.as_proxy()
|
||||
.expect("Proxy object internal internal method called on non-proxy object")
|
||||
.try_data(context)?;
|
||||
|
||||
// 5. Let trap be ? GetMethod(handler, "getOwnPropertyDescriptor").
|
||||
let trap = if let Some(trap) = handler.get_method("getOwnPropertyDescriptor", context)? {
|
||||
trap
|
||||
// 6. If trap is undefined, then
|
||||
} else {
|
||||
// a. Return ? target.[[GetOwnProperty]](P).
|
||||
return target.__get_own_property__(key, context);
|
||||
};
|
||||
|
||||
// 7. Let trapResultObj be ? Call(trap, handler, « target, P »).
|
||||
let trap_result_obj = trap.call(
|
||||
&handler.into(),
|
||||
&[target.clone().into(), key.clone().into()],
|
||||
context,
|
||||
)?;
|
||||
|
||||
// 8. If Type(trapResultObj) is neither Object nor Undefined, throw a TypeError exception.
|
||||
if !trap_result_obj.is_object() && !trap_result_obj.is_undefined() {
|
||||
return context.throw_type_error("Proxy trap result is neither object nor undefined");
|
||||
}
|
||||
|
||||
// 9. Let targetDesc be ? target.[[GetOwnProperty]](P).
|
||||
let target_desc = target.__get_own_property__(key, context)?;
|
||||
|
||||
// 10. If trapResultObj is undefined, then
|
||||
if trap_result_obj.is_undefined() {
|
||||
if let Some(desc) = target_desc {
|
||||
// b. If targetDesc.[[Configurable]] is false, throw a TypeError exception.
|
||||
if !desc.expect_configurable() {
|
||||
return context.throw_type_error(
|
||||
"Proxy trap result is undefined adn target result is not configurable",
|
||||
);
|
||||
}
|
||||
|
||||
// c. Let extensibleTarget be ? IsExtensible(target).
|
||||
// d. If extensibleTarget is false, throw a TypeError exception.
|
||||
if !target.is_extensible(context)? {
|
||||
return context.throw_type_error(
|
||||
"Proxy trap result is undefined and target is not extensible",
|
||||
);
|
||||
}
|
||||
// e. Return undefined.
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// a. If targetDesc is undefined, return undefined.
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// 11. Let extensibleTarget be ? IsExtensible(target).
|
||||
let extensible_target = target.is_extensible(context)?;
|
||||
|
||||
// 12. Let resultDesc be ? ToPropertyDescriptor(trapResultObj).
|
||||
let result_desc = trap_result_obj.to_property_descriptor(context)?;
|
||||
|
||||
// 13. Call CompletePropertyDescriptor(resultDesc).
|
||||
let result_desc = result_desc.complete_property_descriptor();
|
||||
|
||||
// 14. Let valid be IsCompatiblePropertyDescriptor(extensibleTarget, resultDesc, targetDesc).
|
||||
// 15. If valid is false, throw a TypeError exception.
|
||||
if !super::is_compatible_property_descriptor(
|
||||
extensible_target,
|
||||
result_desc.clone(),
|
||||
target_desc.clone(),
|
||||
) {
|
||||
return context.throw_type_error("Proxy trap returned unexpected property");
|
||||
}
|
||||
|
||||
// 16. If resultDesc.[[Configurable]] is false, then
|
||||
if !result_desc.expect_configurable() {
|
||||
// a. If targetDesc is undefined or targetDesc.[[Configurable]] is true, then
|
||||
match &target_desc {
|
||||
Some(desc) if !desc.expect_configurable() => {
|
||||
// b. If resultDesc has a [[Writable]] field and resultDesc.[[Writable]] is false, then
|
||||
if let Some(false) = result_desc.writable() {
|
||||
// i. If targetDesc.[[Writable]] is true, throw a TypeError exception.
|
||||
if desc.expect_writable() {
|
||||
return
|
||||
context.throw_type_error("Proxy trap result is writable and not configurable while target result is not configurable")
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
// i. Throw a TypeError exception.
|
||||
_ => {
|
||||
return context.throw_type_error(
|
||||
"Proxy trap result is not configurable and target result is undefined",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 17. Return resultDesc.
|
||||
Ok(Some(result_desc))
|
||||
}
|
||||
|
||||
/// `10.5.6 [[DefineOwnProperty]] ( P, Desc )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc
|
||||
#[inline]
|
||||
pub(crate) fn proxy_exotic_define_own_property(
|
||||
obj: &JsObject,
|
||||
key: PropertyKey,
|
||||
desc: PropertyDescriptor,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
// 1. Let handler be O.[[ProxyHandler]].
|
||||
// 2. If handler is null, throw a TypeError exception.
|
||||
// 3. Assert: Type(handler) is Object.
|
||||
// 4. Let target be O.[[ProxyTarget]].
|
||||
let (target, handler) = obj
|
||||
.borrow()
|
||||
.as_proxy()
|
||||
.expect("Proxy object internal internal method called on non-proxy object")
|
||||
.try_data(context)?;
|
||||
|
||||
// 5. Let trap be ? GetMethod(handler, "defineProperty").
|
||||
let trap = if let Some(trap) = handler.get_method("defineProperty", context)? {
|
||||
trap
|
||||
// 6. If trap is undefined, then
|
||||
} else {
|
||||
// a. Return ? target.[[DefineOwnProperty]](P, Desc).
|
||||
return target.__define_own_property__(key, desc, context);
|
||||
};
|
||||
|
||||
// 7. Let descObj be FromPropertyDescriptor(Desc).
|
||||
let desc_obj = Object::from_property_descriptor(Some(desc.clone()), context);
|
||||
|
||||
// 8. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P, descObj »)).
|
||||
// 9. If booleanTrapResult is false, return false.
|
||||
if !trap
|
||||
.call(
|
||||
&handler.into(),
|
||||
&[target.clone().into(), key.clone().into(), desc_obj],
|
||||
context,
|
||||
)?
|
||||
.to_boolean()
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// 10. Let targetDesc be ? target.[[GetOwnProperty]](P).
|
||||
let target_desc = target.__get_own_property__(&key, context)?;
|
||||
|
||||
// 11. Let extensibleTarget be ? IsExtensible(target).
|
||||
let extensible_target = target.is_extensible(context)?;
|
||||
|
||||
// 12. If Desc has a [[Configurable]] field and if Desc.[[Configurable]] is false, then
|
||||
let setting_config_false = matches!(desc.configurable(), Some(false));
|
||||
|
||||
match target_desc {
|
||||
// 14. If targetDesc is undefined, then
|
||||
None => {
|
||||
// a. If extensibleTarget is false, throw a TypeError exception.
|
||||
if !extensible_target {
|
||||
return context.throw_type_error("Proxy trap failed to set property");
|
||||
}
|
||||
|
||||
// b. If settingConfigFalse is true, throw a TypeError exception.
|
||||
if setting_config_false {
|
||||
return context.throw_type_error("Proxy trap failed to set property");
|
||||
}
|
||||
}
|
||||
// 15. Else,
|
||||
Some(target_desc) => {
|
||||
// a. If IsCompatiblePropertyDescriptor(extensibleTarget, Desc, targetDesc) is false, throw a TypeError exception.
|
||||
if !super::is_compatible_property_descriptor(
|
||||
extensible_target,
|
||||
desc.clone(),
|
||||
Some(target_desc.clone()),
|
||||
) {
|
||||
return context.throw_type_error("Proxy trap set property to unexpected value");
|
||||
}
|
||||
|
||||
// b. If settingConfigFalse is true and targetDesc.[[Configurable]] is true, throw a TypeError exception.
|
||||
if setting_config_false && target_desc.expect_configurable() {
|
||||
return context.throw_type_error(
|
||||
"Proxy trap set property with unexpected configurable field",
|
||||
);
|
||||
}
|
||||
|
||||
// c. If IsDataDescriptor(targetDesc) is true, targetDesc.[[Configurable]] is false, and targetDesc.[[Writable]] is true, then
|
||||
if target_desc.is_data_descriptor()
|
||||
&& !target_desc.expect_configurable()
|
||||
&& target_desc.expect_writable()
|
||||
{
|
||||
// i. If Desc has a [[Writable]] field and Desc.[[Writable]] is false, throw a TypeError exception.
|
||||
if let Some(writable) = desc.writable() {
|
||||
if !writable {
|
||||
return context.throw_type_error(
|
||||
"Proxy trap set property with unexpected writable field",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 16. Return true.
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// `10.5.7 [[HasProperty]] ( P )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-hasproperty-p
|
||||
#[inline]
|
||||
pub(crate) fn proxy_exotic_has_property(
|
||||
obj: &JsObject,
|
||||
key: &PropertyKey,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
// 1. Let handler be O.[[ProxyHandler]].
|
||||
// 2. If handler is null, throw a TypeError exception.
|
||||
// 3. Assert: Type(handler) is Object.
|
||||
// 4. Let target be O.[[ProxyTarget]].
|
||||
let (target, handler) = obj
|
||||
.borrow()
|
||||
.as_proxy()
|
||||
.expect("Proxy object internal internal method called on non-proxy object")
|
||||
.try_data(context)?;
|
||||
|
||||
// 5. Let trap be ? GetMethod(handler, "has").
|
||||
let trap = if let Some(trap) = handler.get_method("has", context)? {
|
||||
trap
|
||||
// 6. If trap is undefined, then
|
||||
} else {
|
||||
// a. Return ? target.[[HasProperty]](P).
|
||||
return target.has_property(key.clone(), context);
|
||||
};
|
||||
|
||||
// 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P »)).
|
||||
let boolean_trap_result = trap
|
||||
.call(
|
||||
&handler.into(),
|
||||
&[target.clone().into(), key.clone().into()],
|
||||
context,
|
||||
)?
|
||||
.to_boolean();
|
||||
|
||||
// 8. If booleanTrapResult is false, then
|
||||
if !boolean_trap_result {
|
||||
// a. Let targetDesc be ? target.[[GetOwnProperty]](P).
|
||||
let target_desc = target.__get_own_property__(key, context)?;
|
||||
|
||||
// b. If targetDesc is not undefined, then
|
||||
if let Some(target_desc) = target_desc {
|
||||
// i. If targetDesc.[[Configurable]] is false, throw a TypeError exception.
|
||||
if !target_desc.expect_configurable() {
|
||||
return context.throw_type_error("Proxy trap returned unexpected property");
|
||||
}
|
||||
|
||||
// ii. Let extensibleTarget be ? IsExtensible(target).
|
||||
// iii. If extensibleTarget is false, throw a TypeError exception.
|
||||
if !target.is_extensible(context)? {
|
||||
return context.throw_type_error("Proxy trap returned unexpected property");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 9. Return booleanTrapResult.
|
||||
Ok(boolean_trap_result)
|
||||
}
|
||||
|
||||
/// `10.5.8 [[Get]] ( P, Receiver )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver
|
||||
#[inline]
|
||||
pub(crate) fn proxy_exotic_get(
|
||||
obj: &JsObject,
|
||||
key: &PropertyKey,
|
||||
receiver: JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let handler be O.[[ProxyHandler]].
|
||||
// 2. If handler is null, throw a TypeError exception.
|
||||
// 3. Assert: Type(handler) is Object.
|
||||
// 4. Let target be O.[[ProxyTarget]].
|
||||
let (target, handler) = obj
|
||||
.borrow()
|
||||
.as_proxy()
|
||||
.expect("Proxy object internal internal method called on non-proxy object")
|
||||
.try_data(context)?;
|
||||
|
||||
// 5. Let trap be ? GetMethod(handler, "get").
|
||||
let trap = if let Some(trap) = handler.get_method("get", context)? {
|
||||
trap
|
||||
// 6. If trap is undefined, then
|
||||
} else {
|
||||
// a. Return ? target.[[Get]](P, Receiver).
|
||||
return target.__get__(key, receiver, context);
|
||||
};
|
||||
|
||||
// 7. Let trapResult be ? Call(trap, handler, « target, P, Receiver »).
|
||||
let trap_result = trap.call(
|
||||
&handler.into(),
|
||||
&[target.clone().into(), key.clone().into(), receiver],
|
||||
context,
|
||||
)?;
|
||||
|
||||
// 8. Let targetDesc be ? target.[[GetOwnProperty]](P).
|
||||
let target_desc = target.__get_own_property__(key, context)?;
|
||||
|
||||
// 9. If targetDesc is not undefined and targetDesc.[[Configurable]] is false, then
|
||||
if let Some(target_desc) = target_desc {
|
||||
if !target_desc.expect_configurable() {
|
||||
// a. If IsDataDescriptor(targetDesc) is true and targetDesc.[[Writable]] is false, then
|
||||
if target_desc.is_data_descriptor() && !target_desc.expect_writable() {
|
||||
// i. If SameValue(trapResult, targetDesc.[[Value]]) is false, throw a TypeError exception.
|
||||
if !JsValue::same_value(&trap_result, target_desc.expect_value()) {
|
||||
return context
|
||||
.throw_type_error("Proxy trap returned unexpected data descriptor");
|
||||
}
|
||||
}
|
||||
|
||||
// b. If IsAccessorDescriptor(targetDesc) is true and targetDesc.[[Get]] is undefined, then
|
||||
if target_desc.is_accessor_descriptor() && target_desc.expect_get().is_undefined() {
|
||||
// i. If trapResult is not undefined, throw a TypeError exception.
|
||||
if !trap_result.is_undefined() {
|
||||
return context
|
||||
.throw_type_error("Proxy trap returned unexpected accessor descriptor");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 10. Return trapResult.
|
||||
Ok(trap_result)
|
||||
}
|
||||
|
||||
/// `10.5.9 [[Set]] ( P, V, Receiver )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver
|
||||
#[inline]
|
||||
pub(crate) fn proxy_exotic_set(
|
||||
obj: &JsObject,
|
||||
key: PropertyKey,
|
||||
value: JsValue,
|
||||
receiver: JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
// 1. Let handler be O.[[ProxyHandler]].
|
||||
// 2. If handler is null, throw a TypeError exception.
|
||||
// 3. Assert: Type(handler) is Object.
|
||||
// 4. Let target be O.[[ProxyTarget]].
|
||||
let (target, handler) = obj
|
||||
.borrow()
|
||||
.as_proxy()
|
||||
.expect("Proxy object internal internal method called on non-proxy object")
|
||||
.try_data(context)?;
|
||||
|
||||
// 5. Let trap be ? GetMethod(handler, "set").
|
||||
let trap = if let Some(trap) = handler.get_method("set", context)? {
|
||||
trap
|
||||
// 6. If trap is undefined, then
|
||||
} else {
|
||||
// a. Return ? target.[[Set]](P, V, Receiver).
|
||||
return target.__set__(key, value, receiver, context);
|
||||
};
|
||||
|
||||
// 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P, V, Receiver »)).
|
||||
// 8. If booleanTrapResult is false, return false.
|
||||
if !trap
|
||||
.call(
|
||||
&handler.into(),
|
||||
&[target.clone().into(), value.clone(), receiver],
|
||||
context,
|
||||
)?
|
||||
.to_boolean()
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// 9. Let targetDesc be ? target.[[GetOwnProperty]](P).
|
||||
let target_desc = target.__get_own_property__(&key, context)?;
|
||||
|
||||
// 10. If targetDesc is not undefined and targetDesc.[[Configurable]] is false, then
|
||||
if let Some(target_desc) = target_desc {
|
||||
if !target_desc.expect_configurable() {
|
||||
// a. If IsDataDescriptor(targetDesc) is true and targetDesc.[[Writable]] is false, then
|
||||
if target_desc.is_data_descriptor() && !target_desc.expect_writable() {
|
||||
// i. If SameValue(V, targetDesc.[[Value]]) is false, throw a TypeError exception.
|
||||
if !JsValue::same_value(&value, target_desc.expect_value()) {
|
||||
return context.throw_type_error("Proxy trap set unexpected data descriptor");
|
||||
}
|
||||
}
|
||||
|
||||
// b. If IsAccessorDescriptor(targetDesc) is true, then
|
||||
if target_desc.is_accessor_descriptor() {
|
||||
// i. If targetDesc.[[Set]] is undefined, throw a TypeError exception.
|
||||
match target_desc.set() {
|
||||
None | Some(&JsValue::Undefined) => {
|
||||
return context
|
||||
.throw_type_error("Proxy trap set unexpected accessor descriptor");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 11. Return true.
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// `10.5.10 [[Delete]] ( P )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-delete-p
|
||||
#[inline]
|
||||
pub(crate) fn proxy_exotic_delete(
|
||||
obj: &JsObject,
|
||||
key: &PropertyKey,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
// 1. Let handler be O.[[ProxyHandler]].
|
||||
// 2. If handler is null, throw a TypeError exception.
|
||||
// 3. Assert: Type(handler) is Object.
|
||||
// 4. Let target be O.[[ProxyTarget]].
|
||||
let (target, handler) = obj
|
||||
.borrow()
|
||||
.as_proxy()
|
||||
.expect("Proxy object internal internal method called on non-proxy object")
|
||||
.try_data(context)?;
|
||||
|
||||
// 5. Let trap be ? GetMethod(handler, "deleteProperty").
|
||||
let trap = if let Some(trap) = handler.get_method("deleteProperty", context)? {
|
||||
trap
|
||||
// 6. If trap is undefined, then
|
||||
} else {
|
||||
// a. Return ? target.[[Delete]](P).
|
||||
return target.__delete__(key, context);
|
||||
};
|
||||
|
||||
// 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P »)).
|
||||
// 8. If booleanTrapResult is false, return false.
|
||||
if !trap
|
||||
.call(
|
||||
&handler.into(),
|
||||
&[target.clone().into(), key.clone().into()],
|
||||
context,
|
||||
)?
|
||||
.to_boolean()
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// 9. Let targetDesc be ? target.[[GetOwnProperty]](P).
|
||||
match target.__get_own_property__(key, context)? {
|
||||
// 10. If targetDesc is undefined, return true.
|
||||
None => return Ok(true),
|
||||
// 11. If targetDesc.[[Configurable]] is false, throw a TypeError exception.
|
||||
Some(target_desc) => {
|
||||
if !target_desc.expect_configurable() {
|
||||
return context.throw_type_error("Proxy trap failed to delete property");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 12. Let extensibleTarget be ? IsExtensible(target).
|
||||
// 13. If extensibleTarget is false, throw a TypeError exception.
|
||||
if !target.is_extensible(context)? {
|
||||
return context.throw_type_error("Proxy trap failed to delete property");
|
||||
}
|
||||
|
||||
// 14. Return true.
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// `10.5.11 [[OwnPropertyKeys]] ( )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys
|
||||
#[inline]
|
||||
pub(crate) fn proxy_exotic_own_property_keys(
|
||||
obj: &JsObject,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Vec<PropertyKey>> {
|
||||
// 1. Let handler be O.[[ProxyHandler]].
|
||||
// 2. If handler is null, throw a TypeError exception.
|
||||
// 3. Assert: Type(handler) is Object.
|
||||
// 4. Let target be O.[[ProxyTarget]].
|
||||
let (target, handler) = obj
|
||||
.borrow()
|
||||
.as_proxy()
|
||||
.expect("Proxy object internal internal method called on non-proxy object")
|
||||
.try_data(context)?;
|
||||
|
||||
// 5. Let trap be ? GetMethod(handler, "ownKeys").
|
||||
let trap = if let Some(trap) = handler.get_method("ownKeys", context)? {
|
||||
trap
|
||||
// 6. If trap is undefined, then
|
||||
} else {
|
||||
// a. Return ? target.[[OwnPropertyKeys]]().
|
||||
return target.__own_property_keys__(context);
|
||||
};
|
||||
|
||||
// 7. Let trapResultArray be ? Call(trap, handler, « target »).
|
||||
let trap_result_array = trap.call(&handler.into(), &[target.clone().into()], context)?;
|
||||
|
||||
// 8. Let trapResult be ? CreateListFromArrayLike(trapResultArray, « String, Symbol »).
|
||||
let trap_result_raw =
|
||||
trap_result_array.create_list_from_array_like(&[Type::String, Type::Symbol], context)?;
|
||||
|
||||
// 9. If trapResult contains any duplicate entries, throw a TypeError exception.
|
||||
let mut unchecked_result_keys: FxHashSet<PropertyKey> = FxHashSet::default();
|
||||
let mut trap_result = Vec::new();
|
||||
for value in &trap_result_raw {
|
||||
match value {
|
||||
JsValue::String(s) => {
|
||||
if !unchecked_result_keys.insert(s.clone().into()) {
|
||||
return context.throw_type_error(
|
||||
"Proxy trap result contains duplicate string property keys",
|
||||
);
|
||||
}
|
||||
trap_result.push(s.clone().into());
|
||||
}
|
||||
JsValue::Symbol(s) => {
|
||||
if !unchecked_result_keys.insert(s.clone().into()) {
|
||||
return context.throw_type_error(
|
||||
"Proxy trap result contains duplicate symbol property keys",
|
||||
);
|
||||
}
|
||||
trap_result.push(s.clone().into());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// 10. Let extensibleTarget be ? IsExtensible(target).
|
||||
let extensible_target = target.is_extensible(context)?;
|
||||
|
||||
// 11. Let targetKeys be ? target.[[OwnPropertyKeys]]().
|
||||
// 12. Assert: targetKeys is a List of property keys.
|
||||
// 13. Assert: targetKeys contains no duplicate entries.
|
||||
let target_keys = target.__own_property_keys__(context)?;
|
||||
|
||||
// 14. Let targetConfigurableKeys be a new empty List.
|
||||
// 15. Let targetNonconfigurableKeys be a new empty List.
|
||||
let mut target_configurable_keys = Vec::new();
|
||||
let mut target_nonconfigurable_keys = Vec::new();
|
||||
|
||||
// 16. For each element key of targetKeys, do
|
||||
for key in target_keys {
|
||||
// a. Let desc be ? target.[[GetOwnProperty]](key).
|
||||
match target.__get_own_property__(&key, context)? {
|
||||
// b. If desc is not undefined and desc.[[Configurable]] is false, then
|
||||
Some(desc) if !desc.expect_configurable() => {
|
||||
// i. Append key as an element of targetNonconfigurableKeys.
|
||||
target_nonconfigurable_keys.push(key);
|
||||
}
|
||||
// c. Else,
|
||||
_ => {
|
||||
// i. Append key as an element of targetConfigurableKeys.
|
||||
target_configurable_keys.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 17. If extensibleTarget is true and targetNonconfigurableKeys is empty, then
|
||||
if extensible_target && target_nonconfigurable_keys.is_empty() {
|
||||
// a. Return trapResult.
|
||||
return Ok(trap_result);
|
||||
}
|
||||
|
||||
// 18. Let uncheckedResultKeys be a List whose elements are the elements of trapResult.
|
||||
// 19. For each element key of targetNonconfigurableKeys, do
|
||||
for key in target_nonconfigurable_keys {
|
||||
// a. If key is not an element of uncheckedResultKeys, throw a TypeError exception.
|
||||
// b. Remove key from uncheckedResultKeys.
|
||||
if !unchecked_result_keys.remove(&key) {
|
||||
return context.throw_type_error(
|
||||
"Proxy trap failed to return all non-configurable property keys",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 20. If extensibleTarget is true, return trapResult.
|
||||
if extensible_target {
|
||||
return Ok(trap_result);
|
||||
}
|
||||
|
||||
// 21. For each element key of targetConfigurableKeys, do
|
||||
for key in target_configurable_keys {
|
||||
// a. If key is not an element of uncheckedResultKeys, throw a TypeError exception.
|
||||
// b. Remove key from uncheckedResultKeys.
|
||||
if !unchecked_result_keys.remove(&key) {
|
||||
return context
|
||||
.throw_type_error("Proxy trap failed to return all configurable property keys");
|
||||
}
|
||||
}
|
||||
|
||||
// 22. If uncheckedResultKeys is not empty, throw a TypeError exception.
|
||||
if !unchecked_result_keys.is_empty() {
|
||||
return context.throw_type_error("Proxy trap failed to return all property keys");
|
||||
}
|
||||
|
||||
// 23. Return trapResult.
|
||||
Ok(trap_result)
|
||||
}
|
||||
|
||||
/// `10.5.12 [[Call]] ( thisArgument, argumentsList )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist
|
||||
fn proxy_exotic_call(
|
||||
obj: &JsObject,
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let handler be O.[[ProxyHandler]].
|
||||
// 2. If handler is null, throw a TypeError exception.
|
||||
// 3. Assert: Type(handler) is Object.
|
||||
// 4. Let target be O.[[ProxyTarget]].
|
||||
let (target, handler) = obj
|
||||
.borrow()
|
||||
.as_proxy()
|
||||
.expect("Proxy object internal internal method called on non-proxy object")
|
||||
.try_data(context)?;
|
||||
|
||||
// 5. Let trap be ? GetMethod(handler, "apply").
|
||||
let trap = if let Some(trap) = handler.get_method("apply", context)? {
|
||||
trap
|
||||
// 6. If trap is undefined, then
|
||||
} else {
|
||||
// a. Return ? Call(target, thisArgument, argumentsList).
|
||||
return target.call(this, args, context);
|
||||
};
|
||||
|
||||
// 7. Let argArray be ! CreateArrayFromList(argumentsList).
|
||||
let arg_array = array::Array::create_array_from_list(args.to_vec(), context);
|
||||
|
||||
// 8. Return ? Call(trap, handler, « target, thisArgument, argArray »).
|
||||
trap.call(
|
||||
&handler.into(),
|
||||
&[target.clone().into(), this.clone(), arg_array.into()],
|
||||
context,
|
||||
)
|
||||
}
|
||||
|
||||
/// `[[Construct]] ( argumentsList, newTarget )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-construct-argumentslist-newtarget
|
||||
fn proxy_exotic_construct(
|
||||
obj: &JsObject,
|
||||
args: &[JsValue],
|
||||
new_target: &JsObject,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsObject> {
|
||||
// 1. Let handler be O.[[ProxyHandler]].
|
||||
// 2. If handler is null, throw a TypeError exception.
|
||||
// 3. Assert: Type(handler) is Object.
|
||||
// 4. Let target be O.[[ProxyTarget]].
|
||||
let (target, handler) = obj
|
||||
.borrow()
|
||||
.as_proxy()
|
||||
.expect("Proxy object internal internal method called on non-proxy object")
|
||||
.try_data(context)?;
|
||||
|
||||
// 5. Assert: IsConstructor(target) is true.
|
||||
assert!(target.is_constructor());
|
||||
|
||||
// 6. Let trap be ? GetMethod(handler, "construct").
|
||||
let trap = if let Some(trap) = handler.get_method("construct", context)? {
|
||||
trap
|
||||
// 7. If trap is undefined, then
|
||||
} else {
|
||||
// a. Return ? Construct(target, argumentsList, newTarget).
|
||||
return target.construct(args, Some(new_target), context);
|
||||
};
|
||||
|
||||
// 8. Let argArray be ! CreateArrayFromList(argumentsList).
|
||||
let arg_array = array::Array::create_array_from_list(args.to_vec(), context);
|
||||
|
||||
// 9. Let newObj be ? Call(trap, handler, « target, argArray, newTarget »).
|
||||
let new_obj = trap.call(
|
||||
&handler.into(),
|
||||
&[
|
||||
target.clone().into(),
|
||||
arg_array.into(),
|
||||
new_target.clone().into(),
|
||||
],
|
||||
context,
|
||||
)?;
|
||||
|
||||
// 10. If Type(newObj) is not Object, throw a TypeError exception.
|
||||
let new_obj = new_obj.as_object().cloned().ok_or_else(|| {
|
||||
context.construct_type_error("Proxy trap constructor returned non-object value")
|
||||
})?;
|
||||
|
||||
// 11. Return newObj.
|
||||
Ok(new_obj)
|
||||
}
|
||||
@@ -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,
|
||||
Some(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]
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
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(Into::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()
|
||||
.filter(|idx| (*idx as usize) >= len)
|
||||
.collect();
|
||||
remaining_indices.sort_unstable();
|
||||
keys.extend(remaining_indices.into_iter().map(Into::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(Into::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(Into::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,377 @@
|
||||
use crate::{
|
||||
builtins::Array,
|
||||
object::{JsFunction, JsObject, JsObjectType},
|
||||
value::IntoOrUndefined,
|
||||
Context, JsResult, JsString, JsValue,
|
||||
};
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// JavaScript `Array` rust object.
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
pub struct JsArray {
|
||||
inner: JsObject,
|
||||
}
|
||||
|
||||
impl JsArray {
|
||||
/// Create a new empty array.
|
||||
#[inline]
|
||||
pub fn new(context: &mut Context) -> Self {
|
||||
let inner = Array::array_create(0, None, context)
|
||||
.expect("creating an empty array with the default prototype must not fail");
|
||||
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
/// Create an array from a `IntoIterator<Item = JsValue>` convertible object.
|
||||
#[inline]
|
||||
pub fn from_iter<I>(elements: I, context: &mut Context) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = JsValue>,
|
||||
{
|
||||
Self {
|
||||
inner: Array::create_array_from_list(elements, context),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [`JsArray`] from a [`JsObject`], if the object is not an array throw a `TypeError`.
|
||||
///
|
||||
/// This does not clone the fields of the array, it only does a shallow clone of the object.
|
||||
#[inline]
|
||||
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> {
|
||||
if object.borrow().is_array() {
|
||||
Ok(Self { inner: object })
|
||||
} else {
|
||||
context.throw_type_error("object is not an Array")
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the length of the array.
|
||||
///
|
||||
/// Same a `array.length` in JavaScript.
|
||||
#[inline]
|
||||
pub fn length(&self, context: &mut Context) -> JsResult<usize> {
|
||||
self.inner.length_of_array_like(context)
|
||||
}
|
||||
|
||||
/// Check if the array is empty, i.e. the `length` is zero.
|
||||
#[inline]
|
||||
pub fn is_empty(&self, context: &mut Context) -> JsResult<bool> {
|
||||
self.inner.length_of_array_like(context).map(|len| len == 0)
|
||||
}
|
||||
|
||||
/// Push an element to the array.
|
||||
#[inline]
|
||||
pub fn push<T>(&self, value: T, context: &mut Context) -> JsResult<JsValue>
|
||||
where
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
self.push_items(&[value.into()], context)
|
||||
}
|
||||
|
||||
/// Pushes a slice of elements to the array.
|
||||
#[inline]
|
||||
pub fn push_items(&self, items: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Array::push(&self.inner.clone().into(), items, context)
|
||||
}
|
||||
|
||||
/// Pops an element from the array.
|
||||
#[inline]
|
||||
pub fn pop(&self, context: &mut Context) -> JsResult<JsValue> {
|
||||
Array::pop(&self.inner.clone().into(), &[], context)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn at<T>(&self, index: T, context: &mut Context) -> JsResult<JsValue>
|
||||
where
|
||||
T: Into<i64>,
|
||||
{
|
||||
Array::at(&self.inner.clone().into(), &[index.into().into()], context)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shift(&self, context: &mut Context) -> JsResult<JsValue> {
|
||||
Array::shift(&self.inner.clone().into(), &[], context)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn unshift(&self, items: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Array::shift(&self.inner.clone().into(), items, context)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reverse(&self, context: &mut Context) -> JsResult<Self> {
|
||||
Array::reverse(&self.inner.clone().into(), &[], context)?;
|
||||
Ok(self.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn concat(&self, items: &[JsValue], context: &mut Context) -> JsResult<Self> {
|
||||
let object = Array::concat(&self.inner.clone().into(), items, context)?
|
||||
.as_object()
|
||||
.cloned()
|
||||
.expect("Array.prototype.filter should always return object");
|
||||
|
||||
Self::from_object(object, context)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn join(&self, separator: Option<JsString>, context: &mut Context) -> JsResult<JsString> {
|
||||
Array::join(
|
||||
&self.inner.clone().into(),
|
||||
&[separator.into_or_undefined()],
|
||||
context,
|
||||
)
|
||||
.map(|x| {
|
||||
x.as_string()
|
||||
.cloned()
|
||||
.expect("Array.prototype.join always returns string")
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fill<T>(
|
||||
&self,
|
||||
value: T,
|
||||
start: Option<u32>,
|
||||
end: Option<u32>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Self>
|
||||
where
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
Array::fill(
|
||||
&self.inner.clone().into(),
|
||||
&[
|
||||
value.into(),
|
||||
start.into_or_undefined(),
|
||||
end.into_or_undefined(),
|
||||
],
|
||||
context,
|
||||
)?;
|
||||
Ok(self.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn index_of<T>(
|
||||
&self,
|
||||
search_element: T,
|
||||
from_index: Option<u32>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Option<u32>>
|
||||
where
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
let index = Array::index_of(
|
||||
&self.inner.clone().into(),
|
||||
&[search_element.into(), from_index.into_or_undefined()],
|
||||
context,
|
||||
)?
|
||||
.as_number()
|
||||
.expect("Array.prototype.indexOf should always return number");
|
||||
|
||||
#[allow(clippy::float_cmp)]
|
||||
if index == -1.0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(index as u32))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn last_index_of<T>(
|
||||
&self,
|
||||
search_element: T,
|
||||
from_index: Option<u32>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Option<u32>>
|
||||
where
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
let index = Array::last_index_of(
|
||||
&self.inner.clone().into(),
|
||||
&[search_element.into(), from_index.into_or_undefined()],
|
||||
context,
|
||||
)?
|
||||
.as_number()
|
||||
.expect("Array.prototype.lastIndexOf should always return number");
|
||||
|
||||
#[allow(clippy::float_cmp)]
|
||||
if index == -1.0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(index as u32))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn find(
|
||||
&self,
|
||||
predicate: JsFunction,
|
||||
this_arg: Option<JsValue>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
Array::find(
|
||||
&self.inner.clone().into(),
|
||||
&[predicate.into(), this_arg.into_or_undefined()],
|
||||
context,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn filter(
|
||||
&self,
|
||||
callback: JsFunction,
|
||||
this_arg: Option<JsValue>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Self> {
|
||||
let object = Array::filter(
|
||||
&self.inner.clone().into(),
|
||||
&[callback.into(), this_arg.into_or_undefined()],
|
||||
context,
|
||||
)?
|
||||
.as_object()
|
||||
.cloned()
|
||||
.expect("Array.prototype.filter should always return object");
|
||||
|
||||
Self::from_object(object, context)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn map(
|
||||
&self,
|
||||
callback: JsFunction,
|
||||
this_arg: Option<JsValue>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Self> {
|
||||
let object = Array::map(
|
||||
&self.inner.clone().into(),
|
||||
&[callback.into(), this_arg.into_or_undefined()],
|
||||
context,
|
||||
)?
|
||||
.as_object()
|
||||
.cloned()
|
||||
.expect("Array.prototype.map should always return object");
|
||||
|
||||
Self::from_object(object, context)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn every(
|
||||
&self,
|
||||
callback: JsFunction,
|
||||
this_arg: Option<JsValue>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
let result = Array::every(
|
||||
&self.inner.clone().into(),
|
||||
&[callback.into(), this_arg.into_or_undefined()],
|
||||
context,
|
||||
)?
|
||||
.as_boolean()
|
||||
.expect("Array.prototype.every should always return boolean");
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn some(
|
||||
&self,
|
||||
callback: JsFunction,
|
||||
this_arg: Option<JsValue>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
let result = Array::some(
|
||||
&self.inner.clone().into(),
|
||||
&[callback.into(), this_arg.into_or_undefined()],
|
||||
context,
|
||||
)?
|
||||
.as_boolean()
|
||||
.expect("Array.prototype.some should always return boolean");
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sort(&self, compare_fn: Option<JsFunction>, context: &mut Context) -> JsResult<Self> {
|
||||
Array::sort(
|
||||
&self.inner.clone().into(),
|
||||
&[compare_fn.into_or_undefined()],
|
||||
context,
|
||||
)?;
|
||||
|
||||
Ok(self.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn slice(
|
||||
&self,
|
||||
start: Option<u32>,
|
||||
end: Option<u32>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Self> {
|
||||
let object = Array::slice(
|
||||
&self.inner.clone().into(),
|
||||
&[start.into_or_undefined(), end.into_or_undefined()],
|
||||
context,
|
||||
)?
|
||||
.as_object()
|
||||
.cloned()
|
||||
.expect("Array.prototype.slice should always return object");
|
||||
|
||||
Self::from_object(object, context)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reduce(
|
||||
&self,
|
||||
callback: JsFunction,
|
||||
initial_value: Option<JsValue>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
Array::reduce(
|
||||
&self.inner.clone().into(),
|
||||
&[callback.into(), initial_value.into_or_undefined()],
|
||||
context,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reduce_right(
|
||||
&self,
|
||||
callback: JsFunction,
|
||||
initial_value: Option<JsValue>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
Array::reduce_right(
|
||||
&self.inner.clone().into(),
|
||||
&[callback.into(), initial_value.into_or_undefined()],
|
||||
context,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsArray> for JsObject {
|
||||
#[inline]
|
||||
fn from(o: JsArray) -> Self {
|
||||
o.inner.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsArray> for JsValue {
|
||||
#[inline]
|
||||
fn from(o: JsArray) -> Self {
|
||||
o.inner.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for JsArray {
|
||||
type Target = JsObject;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl JsObjectType for JsArray {}
|
||||
@@ -0,0 +1,123 @@
|
||||
use crate::{
|
||||
builtins::array_buffer::ArrayBuffer,
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, JsObject, JsObjectType, ObjectData,
|
||||
},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// JavaScript `ArrayBuffer` rust object.
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
pub struct JsArrayBuffer {
|
||||
inner: JsObject,
|
||||
}
|
||||
|
||||
impl JsArrayBuffer {
|
||||
/// Create a new array buffer with byte length.
|
||||
#[inline]
|
||||
pub fn new(byte_length: usize, context: &mut Context) -> JsResult<Self> {
|
||||
let inner = ArrayBuffer::allocate(
|
||||
&context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.array_buffer()
|
||||
.constructor()
|
||||
.into(),
|
||||
byte_length,
|
||||
context,
|
||||
)?;
|
||||
|
||||
Ok(Self { inner })
|
||||
}
|
||||
|
||||
/// Create a new array buffer from byte block.
|
||||
///
|
||||
/// This uses the passed byte block as the internal storage, it does not clone it!
|
||||
///
|
||||
/// The `byte_length` will be set to `byte_block.len()`.
|
||||
#[inline]
|
||||
pub fn from_byte_block(byte_block: Vec<u8>, context: &mut Context) -> JsResult<Self> {
|
||||
let byte_length = byte_block.len();
|
||||
|
||||
let constructor = context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.array_buffer()
|
||||
.constructor()
|
||||
.into();
|
||||
|
||||
// 1. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] »).
|
||||
let prototype = get_prototype_from_constructor(
|
||||
&constructor,
|
||||
StandardConstructors::array_buffer,
|
||||
context,
|
||||
)?;
|
||||
let obj = context.construct_object();
|
||||
obj.set_prototype(prototype.into());
|
||||
|
||||
// 2. Let block be ? CreateByteDataBlock(byteLength).
|
||||
//
|
||||
// NOTE: We skip step 2. because we already have the block
|
||||
// that is passed to us as a function argument.
|
||||
let block = byte_block;
|
||||
|
||||
// 3. Set obj.[[ArrayBufferData]] to block.
|
||||
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
|
||||
obj.borrow_mut().data = ObjectData::array_buffer(ArrayBuffer {
|
||||
array_buffer_data: Some(block),
|
||||
array_buffer_byte_length: byte_length,
|
||||
array_buffer_detach_key: JsValue::Undefined,
|
||||
});
|
||||
|
||||
Ok(Self { inner: obj })
|
||||
}
|
||||
|
||||
/// Create a [`JsArrayBuffer`] from a [`JsObject`], if the object is not an array buffer throw a `TypeError`.
|
||||
///
|
||||
/// This does not clone the fields of the array buffer, it only does a shallow clone of the object.
|
||||
#[inline]
|
||||
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> {
|
||||
if object.borrow().is_array_buffer() {
|
||||
Ok(Self { inner: object })
|
||||
} else {
|
||||
context.throw_type_error("object is not an ArrayBuffer")
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the byte length of the array buffer.
|
||||
#[inline]
|
||||
pub fn byte_length(&self, context: &mut Context) -> usize {
|
||||
ArrayBuffer::get_byte_length(&self.inner.clone().into(), &[], context)
|
||||
.expect("it should not throw")
|
||||
.as_number()
|
||||
.expect("expected a number") as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsArrayBuffer> for JsObject {
|
||||
#[inline]
|
||||
fn from(o: JsArrayBuffer) -> Self {
|
||||
o.inner.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsArrayBuffer> for JsValue {
|
||||
#[inline]
|
||||
fn from(o: JsArrayBuffer) -> Self {
|
||||
o.inner.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for JsArrayBuffer {
|
||||
type Target = JsObject;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl JsObjectType for JsArrayBuffer {}
|
||||
@@ -0,0 +1,54 @@
|
||||
use crate::{
|
||||
object::{JsObject, JsObjectType},
|
||||
JsValue,
|
||||
};
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// JavaScript `Function` rust object.
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
pub struct JsFunction {
|
||||
inner: JsObject,
|
||||
}
|
||||
|
||||
impl JsFunction {
|
||||
#[inline]
|
||||
pub(crate) fn from_object_unchecked(object: JsObject) -> Self {
|
||||
Self { inner: object }
|
||||
}
|
||||
|
||||
/// Create a [`JsFunction`] from a [`JsObject`], or return `None` if the object is not a function.
|
||||
///
|
||||
/// This does not clone the fields of the function, it only does a shallow clone of the object.
|
||||
#[inline]
|
||||
pub fn from_object(object: JsObject) -> Option<Self> {
|
||||
object
|
||||
.is_callable()
|
||||
.then(|| Self::from_object_unchecked(object))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsFunction> for JsObject {
|
||||
#[inline]
|
||||
fn from(o: JsFunction) -> Self {
|
||||
o.inner.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsFunction> for JsValue {
|
||||
#[inline]
|
||||
fn from(o: JsFunction) -> Self {
|
||||
o.inner.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for JsFunction {
|
||||
type Target = JsObject;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl JsObjectType for JsFunction {}
|
||||
@@ -0,0 +1,426 @@
|
||||
//! This module implements a wrapper for the Map Builtin Javascript Object
|
||||
use crate::{
|
||||
builtins::map::{add_entries_from_iterable, ordered_map::OrderedMap},
|
||||
builtins::Map,
|
||||
object::{JsFunction, JsMapIterator, JsObject, JsObjectType, ObjectData},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// `JsMap` provides a wrapper for Boa's implementation of the Javascript `Map` object.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Create a `JsMap` and set a new entry
|
||||
/// ```
|
||||
/// # use boa_engine::{
|
||||
/// # object::JsMap,
|
||||
/// # Context, JsValue,
|
||||
/// # };
|
||||
///
|
||||
/// // Create default `Context`
|
||||
/// let context = &mut Context::default();
|
||||
///
|
||||
/// // Create a new empty `JsMap`.
|
||||
/// let map = JsMap::new(context);
|
||||
///
|
||||
/// // Set key-value pairs for the `JsMap`.
|
||||
/// map.set("Key-1", "Value-1", context).unwrap();
|
||||
/// map.set("Key-2", 10, context).unwrap();
|
||||
///
|
||||
/// assert_eq!(map.get_size(context).unwrap(), 2.into());
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
/// Create a `JsMap` from a `JsArray`
|
||||
/// ```
|
||||
/// # use boa_engine::{
|
||||
/// # object::{JsArray, JsMap},
|
||||
/// # Context, JsValue,
|
||||
/// # };
|
||||
///
|
||||
/// // Create a default `Context`
|
||||
/// let context = &mut Context::default();
|
||||
///
|
||||
/// // Create an array of two `[key, value]` pairs
|
||||
/// let js_array = JsArray::new(context);
|
||||
///
|
||||
/// // Create a `[key, value]` pair of JsValues
|
||||
/// let vec_one: Vec<JsValue> = vec![JsValue::new("first-key"), JsValue::new("first-value")];
|
||||
///
|
||||
/// // We create an push our `[key, value]` pair onto our array as a `JsArray`
|
||||
/// js_array.push(JsArray::from_iter(vec_one, context), context).unwrap();
|
||||
///
|
||||
/// // Create a `JsMap` from the `JsArray` using it's iterable property.
|
||||
/// let js_iterable_map = JsMap::from_js_iterable(&js_array.into(), context).unwrap();
|
||||
///
|
||||
/// assert_eq!(js_iterable_map.get("first-key", context).unwrap(), "first-value".into());
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
pub struct JsMap {
|
||||
inner: JsObject,
|
||||
}
|
||||
|
||||
impl JsMap {
|
||||
/// Creates a new empty [`JsMap`] object.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use boa_engine::{
|
||||
/// # object::JsMap,
|
||||
/// # Context, JsValue,
|
||||
/// # };
|
||||
///
|
||||
/// // Create a new context.
|
||||
/// let context = &mut Context::default();
|
||||
///
|
||||
/// // Create a new empty `JsMap`.
|
||||
/// let map = JsMap::new(context);
|
||||
///
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn new(context: &mut Context) -> Self {
|
||||
let map = Self::create_map(context);
|
||||
Self { inner: map }
|
||||
}
|
||||
|
||||
/// Create a new [`JsMap`] object from a [`JsObject`] that has an `@@Iterator` field.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use boa_engine::{
|
||||
/// # object::{JsArray, JsMap},
|
||||
/// # Context, JsResult, JsValue,
|
||||
/// # };
|
||||
///
|
||||
/// // Create a default `Context`
|
||||
/// let context = &mut Context::default();
|
||||
///
|
||||
/// // Create an array of two `[key, value]` pairs
|
||||
/// let js_array = JsArray::new(context);
|
||||
///
|
||||
/// // Create a `[key, value]` pair of JsValues and add it to the `JsArray` as a `JsArray`
|
||||
/// let vec_one: Vec<JsValue> = vec![JsValue::new("first-key"), JsValue::new("first-value")];
|
||||
/// js_array.push(JsArray::from_iter(vec_one, context), context).unwrap();
|
||||
///
|
||||
/// // Create a `JsMap` from the `JsArray` using it's iterable property.
|
||||
/// let js_iterable_map = JsMap::from_js_iterable(&js_array.into(), context).unwrap();
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
#[inline]
|
||||
pub fn from_js_iterable(iterable: &JsValue, context: &mut Context) -> JsResult<Self> {
|
||||
// Create a new map object.
|
||||
let map = Self::create_map(context);
|
||||
|
||||
// Let adder be Get(map, "set") per spec. This action should not fail with default map.
|
||||
let adder = map
|
||||
.get("set", context)
|
||||
.expect("creating a map with the default prototype must not fail");
|
||||
|
||||
let _completion_record = add_entries_from_iterable(&map, iterable, &adder, context)?;
|
||||
|
||||
Ok(Self { inner: map })
|
||||
}
|
||||
|
||||
/// Creates a [`JsMap`] from a valid [`JsObject`], or returns a `TypeError` if the provided object is not a [`JsMap`]
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Valid Example - returns a `JsMap` object
|
||||
/// ```
|
||||
/// # use boa_engine::{
|
||||
/// # builtins::map::ordered_map::OrderedMap,
|
||||
/// # object::{JsObject, ObjectData, JsMap},
|
||||
/// # Context, JsValue,
|
||||
/// # };
|
||||
///
|
||||
/// let context = &mut Context::default();
|
||||
///
|
||||
/// // `some_object` can be any JavaScript `Map` object.
|
||||
/// let some_object = JsObject::from_proto_and_data(
|
||||
/// context.intrinsics().constructors().map().prototype(),
|
||||
/// ObjectData::map(OrderedMap::new())
|
||||
/// );
|
||||
///
|
||||
/// // Create `JsMap` object with incoming object.
|
||||
/// let js_map = JsMap::from_object(some_object, context).unwrap();
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
/// Invalid Example - returns a `TypeError` with the message "object is not a Map"
|
||||
/// ```
|
||||
/// # use boa_engine::{
|
||||
/// # object::{JsObject, JsArray, JsMap},
|
||||
/// # Context, JsResult, JsValue,
|
||||
/// # };
|
||||
///
|
||||
/// let context = &mut Context::default();
|
||||
///
|
||||
/// let some_object = JsArray::new(context);
|
||||
///
|
||||
/// // Some object is an Array object, not a map object
|
||||
/// assert!(JsMap::from_object(some_object.into(), context).is_err());
|
||||
///
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> {
|
||||
if object.borrow().is_map() {
|
||||
Ok(Self { inner: object })
|
||||
} else {
|
||||
context.throw_type_error("object is not a Map")
|
||||
}
|
||||
}
|
||||
|
||||
// Utility function to generate the default `Map` object.
|
||||
fn create_map(context: &mut Context) -> JsObject {
|
||||
// Get default Map prototype
|
||||
let prototype = context.intrinsics().constructors().map().prototype();
|
||||
|
||||
// Create a default map object with [[MapData]] as a new empty list
|
||||
JsObject::from_proto_and_data(prototype, ObjectData::map(OrderedMap::new()))
|
||||
}
|
||||
|
||||
/// Returns a new [`JsMapIterator`] object that yields the `[key, value]` pairs within the [`JsMap`] in insertion order.
|
||||
#[inline]
|
||||
pub fn entries(&self, context: &mut Context) -> JsResult<JsMapIterator> {
|
||||
let iterator_record = Map::entries(&self.inner.clone().into(), &[], context)?
|
||||
.get_iterator(context, None, None)?;
|
||||
let map_iterator_object = iterator_record.iterator();
|
||||
JsMapIterator::from_object(map_iterator_object.clone(), context)
|
||||
}
|
||||
|
||||
/// Returns a new [`JsMapIterator`] object that yields the `key` for each element within the [`JsMap`] in insertion order.
|
||||
#[inline]
|
||||
pub fn keys(&self, context: &mut Context) -> JsResult<JsMapIterator> {
|
||||
let iterator_record = Map::keys(&self.inner.clone().into(), &[], context)?
|
||||
.get_iterator(context, None, None)?;
|
||||
let map_iterator_object = iterator_record.iterator();
|
||||
JsMapIterator::from_object(map_iterator_object.clone(), context)
|
||||
}
|
||||
|
||||
/// Inserts a new entry into the [`JsMap`] object
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use boa_engine::{
|
||||
/// # object::JsMap,
|
||||
/// # Context, JsValue,
|
||||
/// # };
|
||||
///
|
||||
/// let context = &mut Context::default();
|
||||
///
|
||||
/// let js_map = JsMap::new(context);
|
||||
///
|
||||
/// js_map.set("foo", "bar", context).unwrap();
|
||||
/// js_map.set(2, 4, context).unwrap();
|
||||
///
|
||||
/// assert_eq!(js_map.get("foo", context).unwrap(), "bar".into());
|
||||
/// assert_eq!(js_map.get(2, context).unwrap(), 4.into())
|
||||
///
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn set<K, V>(&self, key: K, value: V, context: &mut Context) -> JsResult<JsValue>
|
||||
where
|
||||
K: Into<JsValue>,
|
||||
V: Into<JsValue>,
|
||||
{
|
||||
Map::set(
|
||||
&self.inner.clone().into(),
|
||||
&[key.into(), value.into()],
|
||||
context,
|
||||
)
|
||||
}
|
||||
|
||||
/// Gets the size of the [`JsMap`] object.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use boa_engine::{
|
||||
/// # object::JsMap,
|
||||
/// # Context, JsValue,
|
||||
/// # };
|
||||
///
|
||||
/// let context = &mut Context::default();
|
||||
///
|
||||
/// let js_map = JsMap::new(context);
|
||||
///
|
||||
/// js_map.set("foo", "bar", context).unwrap();
|
||||
///
|
||||
/// let map_size = js_map.get_size(context).unwrap();
|
||||
///
|
||||
/// assert_eq!(map_size, 1.into());
|
||||
///
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn get_size(&self, context: &mut Context) -> JsResult<JsValue> {
|
||||
Map::get_size(&self.inner.clone().into(), &[], context)
|
||||
}
|
||||
|
||||
/// Removes element from [`JsMap`] with a matching `key` value.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use boa_engine::{
|
||||
/// # object::JsMap,
|
||||
/// # Context, JsValue,
|
||||
/// # };
|
||||
///
|
||||
/// let context = &mut Context::default();
|
||||
///
|
||||
/// let js_map = JsMap::new(context);
|
||||
/// js_map.set("foo", "bar", context).unwrap();
|
||||
/// js_map.set("hello", "world", context).unwrap();
|
||||
///
|
||||
/// js_map.delete("foo", context).unwrap();
|
||||
///
|
||||
/// assert_eq!(js_map.get_size(context).unwrap(), 1.into());
|
||||
/// assert_eq!(js_map.get("foo", context).unwrap(), JsValue::undefined());
|
||||
///
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn delete<T>(&self, key: T, context: &mut Context) -> JsResult<JsValue>
|
||||
where
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
Map::delete(&self.inner.clone().into(), &[key.into()], context)
|
||||
}
|
||||
|
||||
/// Gets the value associated with the specified key within the [`JsMap`], or `undefined` if the key does not exist.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use boa_engine::{
|
||||
/// # object::JsMap,
|
||||
/// # Context, JsValue,
|
||||
/// # };
|
||||
///
|
||||
/// let context = &mut Context::default();
|
||||
/// let js_map = JsMap::new(context);
|
||||
/// js_map.set("foo", "bar", context).unwrap();
|
||||
///
|
||||
/// let retrieved_value = js_map.get("foo", context).unwrap();
|
||||
///
|
||||
/// assert_eq!(retrieved_value, "bar".into());
|
||||
///
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn get<T>(&self, key: T, context: &mut Context) -> JsResult<JsValue>
|
||||
where
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
Map::get(&self.inner.clone().into(), &[key.into()], context)
|
||||
}
|
||||
|
||||
/// Removes all entries from the [`JsMap`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use boa_engine::{
|
||||
/// # object::JsMap,
|
||||
/// # Context, JsValue,
|
||||
/// # };
|
||||
///
|
||||
/// let context = &mut Context::default();
|
||||
///
|
||||
/// let js_map = JsMap::new(context);
|
||||
/// js_map.set("foo", "bar", context).unwrap();
|
||||
/// js_map.set("hello", "world", context).unwrap();
|
||||
///
|
||||
/// js_map.clear(context).unwrap();
|
||||
///
|
||||
/// assert_eq!(js_map.get_size(context).unwrap(), 0.into());
|
||||
///
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn clear(&self, context: &mut Context) -> JsResult<JsValue> {
|
||||
Map::clear(&self.inner.clone().into(), &[], context)
|
||||
}
|
||||
|
||||
/// Checks if [`JsMap`] has an entry with the provided `key` value.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use boa_engine::{
|
||||
/// # object::JsMap,
|
||||
/// # Context, JsValue,
|
||||
/// # };
|
||||
///
|
||||
/// let context = &mut Context::default();
|
||||
///
|
||||
/// let js_map = JsMap::new(context);
|
||||
/// js_map.set("foo", "bar", context).unwrap();
|
||||
///
|
||||
/// let has_key = js_map.has("foo", context).unwrap();
|
||||
///
|
||||
/// assert_eq!(has_key, true.into());
|
||||
///
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn has<T>(&self, key: T, context: &mut Context) -> JsResult<JsValue>
|
||||
where
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
Map::has(&self.inner.clone().into(), &[key.into()], context)
|
||||
}
|
||||
|
||||
/// Executes the provided callback function for each key-value pair within the [`JsMap`].
|
||||
#[inline]
|
||||
pub fn for_each(
|
||||
&self,
|
||||
callback: JsFunction,
|
||||
this_arg: JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
Map::for_each(
|
||||
&self.inner.clone().into(),
|
||||
&[callback.into(), this_arg],
|
||||
context,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a new [`JsMapIterator`] object that yields the `value` for each element within the [`JsMap`] in insertion order.
|
||||
#[inline]
|
||||
pub fn values(&self, context: &mut Context) -> JsResult<JsMapIterator> {
|
||||
let iterator_record = Map::values(&self.inner.clone().into(), &[], context)?
|
||||
.get_iterator(context, None, None)?;
|
||||
let map_iterator_object = iterator_record.iterator();
|
||||
JsMapIterator::from_object(map_iterator_object.clone(), context)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsMap> for JsObject {
|
||||
#[inline]
|
||||
fn from(o: JsMap) -> Self {
|
||||
o.inner.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsMap> for JsValue {
|
||||
#[inline]
|
||||
fn from(o: JsMap) -> Self {
|
||||
o.inner.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for JsMap {
|
||||
type Target = JsObject;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl JsObjectType for JsMap {}
|
||||
@@ -0,0 +1,57 @@
|
||||
//! This module implements a wrapper for the `MapIterator` object
|
||||
use crate::{
|
||||
builtins::map::map_iterator::MapIterator,
|
||||
object::{JsObject, JsObjectType},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// JavaScript `MapIterator` rust object
|
||||
#[derive(Debug, Clone, Finalize, Trace)]
|
||||
pub struct JsMapIterator {
|
||||
inner: JsObject,
|
||||
}
|
||||
|
||||
impl JsMapIterator {
|
||||
/// Create a [`JsMapIterator`] from a [`JsObject`]. If object is not a `MapIterator`, throw `TypeError`
|
||||
#[inline]
|
||||
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> {
|
||||
if object.borrow().is_map_iterator() {
|
||||
Ok(Self { inner: object })
|
||||
} else {
|
||||
context.throw_type_error("object is not a MapIterator")
|
||||
}
|
||||
}
|
||||
|
||||
/// Advances the `JsMapIterator` and gets the next result in the `JsMap`
|
||||
pub fn next(&self, context: &mut Context) -> JsResult<JsValue> {
|
||||
MapIterator::next(&self.inner.clone().into(), &[], context)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsMapIterator> for JsObject {
|
||||
#[inline]
|
||||
fn from(o: JsMapIterator) -> Self {
|
||||
o.inner.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsMapIterator> for JsValue {
|
||||
#[inline]
|
||||
fn from(o: JsMapIterator) -> Self {
|
||||
o.inner.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for JsMapIterator {
|
||||
type Target = JsObject;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl JsObjectType for JsMapIterator {}
|
||||
@@ -0,0 +1,856 @@
|
||||
//! This module implements the `JsObject` structure.
|
||||
//!
|
||||
//! The `JsObject` is a garbage collected Object.
|
||||
|
||||
use super::{JsPrototype, NativeObject, Object, PropertyMap};
|
||||
use crate::{
|
||||
object::{ObjectData, ObjectKind},
|
||||
property::{PropertyDescriptor, PropertyKey},
|
||||
value::PreferredType,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_gc::{self, Finalize, Gc, Trace};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
error::Error,
|
||||
fmt::{self, Debug, Display},
|
||||
result::Result as StdResult,
|
||||
};
|
||||
|
||||
/// A wrapper type for an immutably borrowed type T.
|
||||
pub type Ref<'a, T> = boa_gc::Ref<'a, T>;
|
||||
|
||||
/// A wrapper type for a mutably borrowed type T.
|
||||
pub type RefMut<'a, T, U> = boa_gc::RefMut<'a, T, U>;
|
||||
|
||||
/// Garbage collected `Object`.
|
||||
#[derive(Trace, Finalize, Clone, Default)]
|
||||
pub struct JsObject {
|
||||
inner: Gc<boa_gc::Cell<Object>>,
|
||||
}
|
||||
|
||||
impl JsObject {
|
||||
/// Create a new `JsObject` from an internal `Object`.
|
||||
#[inline]
|
||||
fn from_object(object: Object) -> Self {
|
||||
Self {
|
||||
inner: Gc::new(boa_gc::Cell::new(object)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new empty `JsObject`, with `prototype` set to `JsValue::Null`
|
||||
/// and `data` set to `ObjectData::ordinary`
|
||||
pub fn empty() -> Self {
|
||||
Self::from_object(Object::default())
|
||||
}
|
||||
|
||||
/// The more general form of `OrdinaryObjectCreate` and `MakeBasicObject`.
|
||||
///
|
||||
/// Create a `JsObject` and automatically set its internal methods and
|
||||
/// internal slots from the `data` provided.
|
||||
#[inline]
|
||||
pub fn from_proto_and_data<O: Into<Option<Self>>>(prototype: O, data: ObjectData) -> Self {
|
||||
Self::from_object(Object {
|
||||
data,
|
||||
prototype: prototype.into(),
|
||||
extensible: true,
|
||||
properties: PropertyMap::default(),
|
||||
private_elements: FxHashMap::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Immutably borrows the `Object`.
|
||||
///
|
||||
/// The borrow lasts until the returned `Ref` exits scope.
|
||||
/// Multiple immutable borrows can be taken out at the same time.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn borrow(&self) -> Ref<'_, Object> {
|
||||
self.try_borrow().expect("Object already mutably borrowed")
|
||||
}
|
||||
|
||||
/// Mutably borrows the Object.
|
||||
///
|
||||
/// The borrow lasts until the returned `RefMut` exits scope.
|
||||
/// The object cannot be borrowed while this borrow is active.
|
||||
///
|
||||
///# Panics
|
||||
/// Panics if the object is currently borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn borrow_mut(&self) -> RefMut<'_, Object, Object> {
|
||||
self.try_borrow_mut().expect("Object already borrowed")
|
||||
}
|
||||
|
||||
/// Immutably borrows the `Object`, returning an error if the value is currently mutably borrowed.
|
||||
///
|
||||
/// The borrow lasts until the returned `GcCellRef` exits scope.
|
||||
/// Multiple immutable borrows can be taken out at the same time.
|
||||
///
|
||||
/// This is the non-panicking variant of [`borrow`](#method.borrow).
|
||||
#[inline]
|
||||
pub fn try_borrow(&self) -> StdResult<Ref<'_, Object>, BorrowError> {
|
||||
self.inner.try_borrow().map_err(|_| BorrowError)
|
||||
}
|
||||
|
||||
/// Mutably borrows the object, returning an error if the value is currently borrowed.
|
||||
///
|
||||
/// The borrow lasts until the returned `GcCellRefMut` exits scope.
|
||||
/// The object be borrowed while this borrow is active.
|
||||
///
|
||||
/// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut).
|
||||
#[inline]
|
||||
pub fn try_borrow_mut(&self) -> StdResult<RefMut<'_, Object, Object>, BorrowMutError> {
|
||||
self.inner.try_borrow_mut().map_err(|_| BorrowMutError)
|
||||
}
|
||||
|
||||
/// Checks if the garbage collected memory is the same.
|
||||
#[inline]
|
||||
pub fn equals(lhs: &Self, rhs: &Self) -> bool {
|
||||
std::ptr::eq(lhs.as_ref(), rhs.as_ref())
|
||||
}
|
||||
|
||||
/// Converts an object to a primitive.
|
||||
///
|
||||
/// Diverges from the spec to prevent a stack overflow when the object is recursive.
|
||||
/// For example,
|
||||
/// ```javascript
|
||||
/// let a = [1];
|
||||
/// a[1] = a;
|
||||
/// console.log(a.toString()); // We print "1,"
|
||||
/// ```
|
||||
/// The spec doesn't mention what to do in this situation, but a naive implementation
|
||||
/// would overflow the stack recursively calling `toString()`. We follow v8 and SpiderMonkey
|
||||
/// instead by returning a default value for the given `hint` -- either `0.` or `""`.
|
||||
/// Example in v8: <https://repl.it/repls/IvoryCircularCertification#index.js>
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinarytoprimitive
|
||||
pub(crate) fn ordinary_to_primitive(
|
||||
&self,
|
||||
context: &mut Context,
|
||||
hint: PreferredType,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Assert: Type(O) is Object.
|
||||
// Already is JsObject by type.
|
||||
// 2. Assert: Type(hint) is String and its value is either "string" or "number".
|
||||
debug_assert!(hint == PreferredType::String || hint == PreferredType::Number);
|
||||
|
||||
// Diverge from the spec here to make sure we aren't going to overflow the stack by converting
|
||||
// a recursive structure
|
||||
// We can follow v8 & SpiderMonkey's lead and return a default value for the hint in this situation
|
||||
// (see https://repl.it/repls/IvoryCircularCertification#index.js)
|
||||
let recursion_limiter = RecursionLimiter::new(self);
|
||||
if recursion_limiter.live {
|
||||
// we're in a recursive object, bail
|
||||
return Ok(match hint {
|
||||
PreferredType::Number => JsValue::new(0),
|
||||
PreferredType::String => JsValue::new(""),
|
||||
PreferredType::Default => unreachable!("checked type hint in step 2"),
|
||||
});
|
||||
}
|
||||
|
||||
// 3. If hint is "string", then
|
||||
// a. Let methodNames be « "toString", "valueOf" ».
|
||||
// 4. Else,
|
||||
// a. Let methodNames be « "valueOf", "toString" ».
|
||||
let method_names = if hint == PreferredType::String {
|
||||
["toString", "valueOf"]
|
||||
} else {
|
||||
["valueOf", "toString"]
|
||||
};
|
||||
|
||||
// 5. For each name in methodNames in List order, do
|
||||
for name in &method_names {
|
||||
// a. Let method be ? Get(O, name).
|
||||
let method = self.get(*name, context)?;
|
||||
|
||||
// b. If IsCallable(method) is true, then
|
||||
if let Some(method) = method.as_callable() {
|
||||
// i. Let result be ? Call(method, O).
|
||||
let result = method.call(&self.clone().into(), &[], context)?;
|
||||
|
||||
// ii. If Type(result) is not Object, return result.
|
||||
if !result.is_object() {
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Throw a TypeError exception.
|
||||
context.throw_type_error("cannot convert object to primitive value")
|
||||
}
|
||||
|
||||
/// Return `true` if it is a native object and the native type is `T`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is<T>(&self) -> bool
|
||||
where
|
||||
T: NativeObject,
|
||||
{
|
||||
self.borrow().is::<T>()
|
||||
}
|
||||
|
||||
/// Downcast a reference to the object,
|
||||
/// if the object is type native object type `T`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn downcast_ref<T>(&self) -> Option<Ref<'_, T>>
|
||||
where
|
||||
T: NativeObject,
|
||||
{
|
||||
let object = self.borrow();
|
||||
if object.is::<T>() {
|
||||
Some(Ref::map(object, |x| {
|
||||
x.downcast_ref::<T>().expect("downcasting reference failed")
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Downcast a mutable reference to the object,
|
||||
/// if the object is type native object type `T`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn downcast_mut<T>(&mut self) -> Option<RefMut<'_, Object, T>>
|
||||
where
|
||||
T: NativeObject,
|
||||
{
|
||||
let object = self.borrow_mut();
|
||||
if object.is::<T>() {
|
||||
Some(RefMut::map(object, |x| {
|
||||
x.downcast_mut::<T>()
|
||||
.expect("downcasting mutable reference failed")
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the prototype of the object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn prototype(&self) -> Ref<'_, JsPrototype> {
|
||||
Ref::map(self.borrow(), Object::prototype)
|
||||
}
|
||||
|
||||
/// Get the extensibility of the object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
pub(crate) fn extensible(&self) -> bool {
|
||||
self.borrow().extensible
|
||||
}
|
||||
|
||||
/// Set the prototype of the object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn set_prototype(&self, prototype: JsPrototype) -> bool {
|
||||
self.borrow_mut().set_prototype(prototype)
|
||||
}
|
||||
|
||||
/// Checks if it's an `Array` object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_array(&self) -> bool {
|
||||
self.borrow().is_array()
|
||||
}
|
||||
|
||||
/// Checks if it is an `ArrayIterator` object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_array_iterator(&self) -> bool {
|
||||
self.borrow().is_array_iterator()
|
||||
}
|
||||
|
||||
/// Checks if it's an `ArrayBuffer` object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_array_buffer(&self) -> bool {
|
||||
self.borrow().is_array_buffer()
|
||||
}
|
||||
|
||||
/// Checks if it is a `Map` object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_map(&self) -> bool {
|
||||
self.borrow().is_map()
|
||||
}
|
||||
|
||||
/// Checks if it's a `MapIterator` object
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_map_iterator(&self) -> bool {
|
||||
self.borrow().is_map_iterator()
|
||||
}
|
||||
|
||||
/// Checks if it is a `Set` object
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_set(&self) -> bool {
|
||||
self.borrow().is_set()
|
||||
}
|
||||
|
||||
/// Checks if it is a `SetIterator` object
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_set_iterator(&self) -> bool {
|
||||
self.borrow().is_set_iterator()
|
||||
}
|
||||
|
||||
/// Checks if it's a `String` object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_string(&self) -> bool {
|
||||
self.borrow().is_string()
|
||||
}
|
||||
|
||||
/// Checks if it's a `Function` object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_function(&self) -> bool {
|
||||
self.borrow().is_function()
|
||||
}
|
||||
|
||||
/// Checks if it's a `Generator` object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_generator(&self) -> bool {
|
||||
self.borrow().is_generator()
|
||||
}
|
||||
|
||||
/// Checks if it's a `Symbol` object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_symbol(&self) -> bool {
|
||||
self.borrow().is_symbol()
|
||||
}
|
||||
|
||||
/// Checks if it's an `Error` object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_error(&self) -> bool {
|
||||
self.borrow().is_error()
|
||||
}
|
||||
|
||||
/// Checks if it's a `Boolean` object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_boolean(&self) -> bool {
|
||||
self.borrow().is_boolean()
|
||||
}
|
||||
|
||||
/// Checks if it's a `Number` object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_number(&self) -> bool {
|
||||
self.borrow().is_number()
|
||||
}
|
||||
|
||||
/// Checks if it's a `BigInt` object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_bigint(&self) -> bool {
|
||||
self.borrow().is_bigint()
|
||||
}
|
||||
|
||||
/// Checks if it's a `RegExp` object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_regexp(&self) -> bool {
|
||||
self.borrow().is_regexp()
|
||||
}
|
||||
|
||||
/// Checks if it's a `TypedArray` object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_typed_array(&self) -> bool {
|
||||
self.borrow().is_typed_array()
|
||||
}
|
||||
|
||||
/// Checks if it's a `Promise` object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_promise(&self) -> bool {
|
||||
self.borrow().is_promise()
|
||||
}
|
||||
|
||||
/// Checks if it's an ordinary object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_ordinary(&self) -> bool {
|
||||
self.borrow().is_ordinary()
|
||||
}
|
||||
|
||||
/// Returns `true` if it holds an Rust type that implements `NativeObject`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn is_native_object(&self) -> bool {
|
||||
self.borrow().is_native_object()
|
||||
}
|
||||
|
||||
pub fn to_property_descriptor(&self, context: &mut Context) -> JsResult<PropertyDescriptor> {
|
||||
// 1 is implemented on the method `to_property_descriptor` of value
|
||||
|
||||
// 2. Let desc be a new Property Descriptor that initially has no fields.
|
||||
let mut desc = PropertyDescriptor::builder();
|
||||
|
||||
// 3. Let hasEnumerable be ? HasProperty(Obj, "enumerable").
|
||||
// 4. If hasEnumerable is true, then ...
|
||||
if self.has_property("enumerable", context)? {
|
||||
// a. Let enumerable be ! ToBoolean(? Get(Obj, "enumerable")).
|
||||
// b. Set desc.[[Enumerable]] to enumerable.
|
||||
desc = desc.enumerable(self.get("enumerable", context)?.to_boolean());
|
||||
}
|
||||
|
||||
// 5. Let hasConfigurable be ? HasProperty(Obj, "configurable").
|
||||
// 6. If hasConfigurable is true, then ...
|
||||
if self.has_property("configurable", context)? {
|
||||
// a. Let configurable be ! ToBoolean(? Get(Obj, "configurable")).
|
||||
// b. Set desc.[[Configurable]] to configurable.
|
||||
desc = desc.configurable(self.get("configurable", context)?.to_boolean());
|
||||
}
|
||||
|
||||
// 7. Let hasValue be ? HasProperty(Obj, "value").
|
||||
// 8. If hasValue is true, then ...
|
||||
if self.has_property("value", context)? {
|
||||
// a. Let value be ? Get(Obj, "value").
|
||||
// b. Set desc.[[Value]] to value.
|
||||
desc = desc.value(self.get("value", context)?);
|
||||
}
|
||||
|
||||
// 9. Let hasWritable be ? HasProperty(Obj, ).
|
||||
// 10. If hasWritable is true, then ...
|
||||
if self.has_property("writable", context)? {
|
||||
// a. Let writable be ! ToBoolean(? Get(Obj, "writable")).
|
||||
// b. Set desc.[[Writable]] to writable.
|
||||
desc = desc.writable(self.get("writable", context)?.to_boolean());
|
||||
}
|
||||
|
||||
// 11. Let hasGet be ? HasProperty(Obj, "get").
|
||||
// 12. If hasGet is true, then
|
||||
let get = if self.has_property("get", context)? {
|
||||
// a. Let getter be ? Get(Obj, "get").
|
||||
let getter = self.get("get", context)?;
|
||||
// b. If IsCallable(getter) is false and getter is not undefined, throw a TypeError exception.
|
||||
// todo: extract IsCallable to be callable from Value
|
||||
if !getter.is_undefined() && getter.as_object().map_or(true, |o| !o.is_callable()) {
|
||||
return context.throw_type_error("Property descriptor getter must be callable");
|
||||
}
|
||||
// c. Set desc.[[Get]] to getter.
|
||||
Some(getter)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// 13. Let hasSet be ? HasProperty(Obj, "set").
|
||||
// 14. If hasSet is true, then
|
||||
let set = if self.has_property("set", context)? {
|
||||
// 14.a. Let setter be ? Get(Obj, "set").
|
||||
let setter = self.get("set", context)?;
|
||||
// 14.b. If IsCallable(setter) is false and setter is not undefined, throw a TypeError exception.
|
||||
// todo: extract IsCallable to be callable from Value
|
||||
if !setter.is_undefined() && setter.as_object().map_or(true, |o| !o.is_callable()) {
|
||||
return context.throw_type_error("Property descriptor setter must be callable");
|
||||
}
|
||||
// 14.c. Set desc.[[Set]] to setter.
|
||||
Some(setter)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// 15. If desc.[[Get]] is present or desc.[[Set]] is present, then ...
|
||||
// a. If desc.[[Value]] is present or desc.[[Writable]] is present, throw a TypeError exception.
|
||||
if get.as_ref().or(set.as_ref()).is_some() && desc.inner().is_data_descriptor() {
|
||||
return context.throw_type_error(
|
||||
"Invalid property descriptor.\
|
||||
Cannot both specify accessors and a value or writable attribute",
|
||||
);
|
||||
}
|
||||
|
||||
desc = desc.maybe_get(get).maybe_set(set);
|
||||
|
||||
// 16. Return desc.
|
||||
Ok(desc.build())
|
||||
}
|
||||
|
||||
/// `7.3.25 CopyDataProperties ( target, source, excludedItems )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-copydataproperties
|
||||
#[inline]
|
||||
pub fn copy_data_properties<K>(
|
||||
&self,
|
||||
source: &JsValue,
|
||||
excluded_keys: Vec<K>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<()>
|
||||
where
|
||||
K: Into<PropertyKey>,
|
||||
{
|
||||
// 1. Assert: Type(target) is Object.
|
||||
// 2. Assert: excludedItems is a List of property keys.
|
||||
// 3. If source is undefined or null, return target.
|
||||
if source.is_null_or_undefined() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 4. Let from be ! ToObject(source).
|
||||
let from = source
|
||||
.to_object(context)
|
||||
.expect("function ToObject should never complete abruptly here");
|
||||
|
||||
// 5. Let keys be ? from.[[OwnPropertyKeys]]().
|
||||
// 6. For each element nextKey of keys, do
|
||||
let excluded_keys: Vec<PropertyKey> = excluded_keys.into_iter().map(Into::into).collect();
|
||||
for key in from.__own_property_keys__(context)? {
|
||||
// a. Let excluded be false.
|
||||
let mut excluded = false;
|
||||
|
||||
// b. For each element e of excludedItems, do
|
||||
for e in &excluded_keys {
|
||||
// i. If SameValue(e, nextKey) is true, then
|
||||
if *e == key {
|
||||
// 1. Set excluded to true.
|
||||
excluded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// c. If excluded is false, then
|
||||
if !excluded {
|
||||
// i. Let desc be ? from.[[GetOwnProperty]](nextKey).
|
||||
let desc = from.__get_own_property__(&key, context)?;
|
||||
|
||||
// ii. If desc is not undefined and desc.[[Enumerable]] is true, then
|
||||
if let Some(desc) = desc {
|
||||
if let Some(enumerable) = desc.enumerable() {
|
||||
if enumerable {
|
||||
// 1. Let propValue be ? Get(from, nextKey).
|
||||
let prop_value = from.__get__(&key, from.clone().into(), context)?;
|
||||
|
||||
// 2. Perform ! CreateDataPropertyOrThrow(target, nextKey, propValue).
|
||||
self.create_data_property_or_throw(key, prop_value, context)
|
||||
.expect(
|
||||
"CreateDataPropertyOrThrow should never complete abruptly here",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Return target.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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().data.internal_methods.__call__.is_some()
|
||||
}
|
||||
|
||||
/// 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_constructor(&self) -> bool {
|
||||
self.borrow().data.internal_methods.__construct__.is_some()
|
||||
}
|
||||
|
||||
/// Returns true if the `JsObject` is the global for a Realm
|
||||
pub fn is_global(&self) -> bool {
|
||||
matches!(
|
||||
self.borrow().data,
|
||||
ObjectData {
|
||||
kind: ObjectKind::Global,
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<boa_gc::Cell<Object>> for JsObject {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &boa_gc::Cell<Object> {
|
||||
&*self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for JsObject {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Self::equals(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
/// An error returned by [`JsObject::try_borrow`](struct.JsObject.html#method.try_borrow).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct BorrowError;
|
||||
|
||||
impl Display for BorrowError {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt("Object already mutably borrowed", f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BorrowError {}
|
||||
|
||||
/// An error returned by [`JsObject::try_borrow_mut`](struct.JsObject.html#method.try_borrow_mut).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct BorrowMutError;
|
||||
|
||||
impl Display for BorrowMutError {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt("Object already borrowed", f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BorrowMutError {}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
enum RecursionValueState {
|
||||
/// This value is "live": there's an active RecursionLimiter that hasn't been dropped.
|
||||
Live,
|
||||
/// This value has been seen before, but the recursion limiter has been dropped.
|
||||
/// For example:
|
||||
/// ```javascript
|
||||
/// let b = [];
|
||||
/// JSON.stringify([ // Create a recursion limiter for the root here
|
||||
/// b, // state for b's &JsObject here is None
|
||||
/// b, // state for b's &JsObject here is Visited
|
||||
/// ]);
|
||||
/// ```
|
||||
Visited,
|
||||
}
|
||||
|
||||
/// Prevents infinite recursion during `Debug::fmt`, `JSON.stringify`, and other conversions.
|
||||
/// This uses a thread local, so is not safe to use where the object graph will be traversed by
|
||||
/// multiple threads!
|
||||
#[derive(Debug)]
|
||||
pub struct RecursionLimiter {
|
||||
/// If this was the first `JsObject` in the tree.
|
||||
top_level: bool,
|
||||
/// The ptr being kept in the HashSet, so we can delete it when we drop.
|
||||
ptr: usize,
|
||||
/// If this JsObject has been visited before in the graph, but not in the current branch.
|
||||
pub visited: bool,
|
||||
/// If this JsObject has been visited in the current branch of the graph.
|
||||
pub live: bool,
|
||||
}
|
||||
|
||||
impl Drop for RecursionLimiter {
|
||||
fn drop(&mut self) {
|
||||
if self.top_level {
|
||||
// When the top level of the graph is dropped, we can free the entire map for the next traversal.
|
||||
Self::SEEN.with(|hm| hm.borrow_mut().clear());
|
||||
} else if !self.live {
|
||||
// This was the first RL for this object to become live, so it's no longer live now that it's dropped.
|
||||
Self::SEEN.with(|hm| {
|
||||
hm.borrow_mut()
|
||||
.insert(self.ptr, RecursionValueState::Visited)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RecursionLimiter {
|
||||
thread_local! {
|
||||
/// The map of pointers to `JsObject` that have been visited during the current `Debug::fmt` graph,
|
||||
/// and the current state of their RecursionLimiter (dropped or live -- see `RecursionValueState`)
|
||||
static SEEN: RefCell<HashMap<usize, RecursionValueState>> = RefCell::new(HashMap::new());
|
||||
}
|
||||
|
||||
/// Determines if the specified `JsObject` has been visited, and returns a struct that will free it when dropped.
|
||||
///
|
||||
/// This is done by maintaining a thread-local hashset containing the pointers of `JsObject` values that have been
|
||||
/// visited. The first `JsObject` visited will clear the hashset, while any others will check if they are contained
|
||||
/// by the hashset.
|
||||
pub fn new(o: &JsObject) -> Self {
|
||||
// We shouldn't have to worry too much about this being moved during Debug::fmt.
|
||||
let ptr = (o.as_ref() as *const _) as usize;
|
||||
let (top_level, visited, live) = Self::SEEN.with(|hm| {
|
||||
let mut hm = hm.borrow_mut();
|
||||
let top_level = hm.is_empty();
|
||||
let old_state = hm.insert(ptr, RecursionValueState::Live);
|
||||
|
||||
(
|
||||
top_level,
|
||||
old_state == Some(RecursionValueState::Visited),
|
||||
old_state == Some(RecursionValueState::Live),
|
||||
)
|
||||
});
|
||||
|
||||
Self {
|
||||
top_level,
|
||||
ptr,
|
||||
visited,
|
||||
live,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for JsObject {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let limiter = RecursionLimiter::new(self);
|
||||
|
||||
// Typically, using `!limiter.live` would be good enough here.
|
||||
// However, the JS object hierarchy involves quite a bit of repitition, and the sheer amount of data makes
|
||||
// understanding the Debug output impossible; limiting the usefulness of it.
|
||||
//
|
||||
// Instead, we check if the object has appeared before in the entire graph. This means that objects will appear
|
||||
// at most once, hopefully making things a bit clearer.
|
||||
if !limiter.visited && !limiter.live {
|
||||
f.debug_tuple("JsObject").field(&self.inner).finish()
|
||||
} else {
|
||||
f.write_str("{ ... }")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,478 @@
|
||||
use boa_gc::{Finalize, Trace};
|
||||
|
||||
use crate::{
|
||||
builtins::{function::NativeFunctionSignature, Proxy},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
|
||||
use super::{FunctionBuilder, JsFunction, JsObject, JsObjectType, ObjectData};
|
||||
|
||||
/// JavaScript [`Proxy`][proxy] rust object.
|
||||
///
|
||||
/// This is a wrapper type for the [`Proxy`][proxy] API that allows customizing
|
||||
/// essential behaviour for an object, like [property accesses][get] or the
|
||||
/// [`delete`][delete] operator.
|
||||
///
|
||||
/// The only way to construct this type is to use the [`JsProxyBuilder`] type; also
|
||||
/// accessible from [`JsProxy::builder`].
|
||||
///
|
||||
/// [get]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get
|
||||
/// [delete]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/deleteProperty
|
||||
/// [proxy]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
pub struct JsProxy {
|
||||
inner: JsObject,
|
||||
}
|
||||
|
||||
impl JsProxy {
|
||||
pub fn builder(target: JsObject) -> JsProxyBuilder {
|
||||
JsProxyBuilder::new(target)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsProxy> for JsObject {
|
||||
#[inline]
|
||||
fn from(o: JsProxy) -> Self {
|
||||
o.inner.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsProxy> for JsValue {
|
||||
#[inline]
|
||||
fn from(o: JsProxy) -> Self {
|
||||
o.inner.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for JsProxy {
|
||||
type Target = JsObject;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl JsObjectType for JsProxy {}
|
||||
|
||||
/// JavaScript [`Proxy`][proxy] rust object that can be disabled.
|
||||
///
|
||||
/// Safe interface for the [`Proxy.revocable`][revocable] method that creates a
|
||||
/// proxy that can be disabled using the [`JsRevocableProxy::revoke`] method.
|
||||
/// The internal proxy is accessible using the [`Deref`][`std::ops::Deref`] operator.
|
||||
///
|
||||
/// The only way to construct this type is to use the [`JsProxyBuilder`] type; also
|
||||
/// accessible from [`JsProxy::builder`]; with the [`JsProxyBuilder::build_revocable`]
|
||||
/// method.
|
||||
///
|
||||
/// [proxy]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
|
||||
/// [revocable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/revocable
|
||||
#[derive(Debug, Trace, Finalize)]
|
||||
pub struct JsRevocableProxy {
|
||||
proxy: JsProxy,
|
||||
revoker: JsFunction,
|
||||
}
|
||||
|
||||
impl JsRevocableProxy {
|
||||
/// Disables the traps of the internal `proxy` object, essentially
|
||||
/// making it unusable and throwing `TypeError`s for all the traps.
|
||||
pub fn revoke(self, context: &mut Context) -> JsResult<()> {
|
||||
self.revoker.call(&JsValue::undefined(), &[], context)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for JsRevocableProxy {
|
||||
type Target = JsProxy;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.proxy
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility builder to create [`JsProxy`] objects from native functions.
|
||||
///
|
||||
/// This builder can be used when you need to create [`Proxy`] objects
|
||||
/// from Rust instead of Javascript, which should generate faster
|
||||
/// trap functions than its Javascript counterparts.
|
||||
#[must_use]
|
||||
#[derive(Clone)]
|
||||
pub struct JsProxyBuilder {
|
||||
target: JsObject,
|
||||
apply: Option<NativeFunctionSignature>,
|
||||
construct: Option<NativeFunctionSignature>,
|
||||
define_property: Option<NativeFunctionSignature>,
|
||||
delete_property: Option<NativeFunctionSignature>,
|
||||
get: Option<NativeFunctionSignature>,
|
||||
get_own_property_descriptor: Option<NativeFunctionSignature>,
|
||||
get_prototype_of: Option<NativeFunctionSignature>,
|
||||
has: Option<NativeFunctionSignature>,
|
||||
is_extensible: Option<NativeFunctionSignature>,
|
||||
own_keys: Option<NativeFunctionSignature>,
|
||||
prevent_extensions: Option<NativeFunctionSignature>,
|
||||
set: Option<NativeFunctionSignature>,
|
||||
set_prototype_of: Option<NativeFunctionSignature>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for JsProxyBuilder {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct NativeFunction;
|
||||
f.debug_struct("ProxyBuilder")
|
||||
.field("target", &self.target)
|
||||
.field("apply", &self.apply.map(|_| NativeFunction))
|
||||
.field("construct", &self.construct.map(|_| NativeFunction))
|
||||
.field(
|
||||
"define_property",
|
||||
&self.define_property.map(|_| NativeFunction),
|
||||
)
|
||||
.field(
|
||||
"delete_property",
|
||||
&self.delete_property.map(|_| NativeFunction),
|
||||
)
|
||||
.field("get", &self.get.map(|_| NativeFunction))
|
||||
.field(
|
||||
"get_own_property_descriptor",
|
||||
&self.get_own_property_descriptor.map(|_| NativeFunction),
|
||||
)
|
||||
.field(
|
||||
"get_prototype_of",
|
||||
&self.get_prototype_of.map(|_| NativeFunction),
|
||||
)
|
||||
.field("has", &self.has.map(|_| NativeFunction))
|
||||
.field("is_extensible", &self.is_extensible.map(|_| NativeFunction))
|
||||
.field("own_keys", &self.own_keys.map(|_| NativeFunction))
|
||||
.field(
|
||||
"prevent_extensions",
|
||||
&self.prevent_extensions.map(|_| NativeFunction),
|
||||
)
|
||||
.field("set", &self.set.map(|_| NativeFunction))
|
||||
.field(
|
||||
"set_prototype_of",
|
||||
&self.set_prototype_of.map(|_| NativeFunction),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl JsProxyBuilder {
|
||||
/// Create a new `ProxyBuilder` with every trap set to `undefined`.
|
||||
pub fn new(target: JsObject) -> Self {
|
||||
Self {
|
||||
target,
|
||||
apply: None,
|
||||
construct: None,
|
||||
define_property: None,
|
||||
delete_property: None,
|
||||
get: None,
|
||||
get_own_property_descriptor: None,
|
||||
get_prototype_of: None,
|
||||
has: None,
|
||||
is_extensible: None,
|
||||
own_keys: None,
|
||||
prevent_extensions: None,
|
||||
set: None,
|
||||
set_prototype_of: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the `apply` proxy trap to the specified native function.
|
||||
///
|
||||
/// More information:
|
||||
///
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// If the `target` object is not a function, then `apply` will be ignored
|
||||
/// when trying to call the proxy, which will throw a type error.
|
||||
///
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/apply
|
||||
pub fn apply(mut self, apply: NativeFunctionSignature) -> Self {
|
||||
self.apply = Some(apply);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `construct` proxy trap to the specified native function.
|
||||
///
|
||||
/// More information:
|
||||
///
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// If the `target` object is not a constructor, then `construct` will be ignored
|
||||
/// when trying to construct an object using the proxy, which will throw a type error.
|
||||
///
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/construct
|
||||
pub fn construct(mut self, construct: NativeFunctionSignature) -> Self {
|
||||
self.construct = Some(construct);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `defineProperty` proxy trap to the specified native function.
|
||||
///
|
||||
/// More information:
|
||||
///
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/defineProperty
|
||||
pub fn define_property(mut self, define_property: NativeFunctionSignature) -> Self {
|
||||
self.define_property = Some(define_property);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `deleteProperty` proxy trap to the specified native function.
|
||||
///
|
||||
/// More information:
|
||||
///
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/deleteProperty
|
||||
pub fn delete_property(mut self, delete_property: NativeFunctionSignature) -> Self {
|
||||
self.delete_property = Some(delete_property);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `get` proxy trap to the specified native function.
|
||||
///
|
||||
/// More information:
|
||||
///
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get
|
||||
pub fn get(mut self, get: NativeFunctionSignature) -> Self {
|
||||
self.get = Some(get);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `getOwnPropertyDescriptor` proxy trap to the specified native function.
|
||||
///
|
||||
/// More information:
|
||||
///
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getOwnPropertyDescriptor
|
||||
pub fn get_own_property_descriptor(
|
||||
mut self,
|
||||
get_own_property_descriptor: NativeFunctionSignature,
|
||||
) -> Self {
|
||||
self.get_own_property_descriptor = Some(get_own_property_descriptor);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `getPrototypeOf` proxy trap to the specified native function.
|
||||
///
|
||||
/// More information:
|
||||
///
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getPrototypeOf
|
||||
pub fn get_prototype_of(mut self, get_prototype_of: NativeFunctionSignature) -> Self {
|
||||
self.get_prototype_of = Some(get_prototype_of);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `has` proxy trap to the specified native function.
|
||||
///
|
||||
/// More information:
|
||||
///
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/has
|
||||
pub fn has(mut self, has: NativeFunctionSignature) -> Self {
|
||||
self.has = Some(has);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `isExtensible` proxy trap to the specified native function.
|
||||
///
|
||||
/// More information:
|
||||
///
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/isExtensible
|
||||
pub fn is_extensible(mut self, is_extensible: NativeFunctionSignature) -> Self {
|
||||
self.is_extensible = Some(is_extensible);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `ownKeys` proxy trap to the specified native function.
|
||||
///
|
||||
/// More information:
|
||||
///
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys
|
||||
pub fn own_keys(mut self, own_keys: NativeFunctionSignature) -> Self {
|
||||
self.own_keys = Some(own_keys);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `preventExtensions` proxy trap to the specified native function.
|
||||
///
|
||||
/// More information:
|
||||
///
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/preventExtensions
|
||||
pub fn prevent_extensions(mut self, prevent_extensions: NativeFunctionSignature) -> Self {
|
||||
self.prevent_extensions = Some(prevent_extensions);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `set` proxy trap to the specified native function.
|
||||
///
|
||||
/// More information:
|
||||
///
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set
|
||||
pub fn set(mut self, set: NativeFunctionSignature) -> Self {
|
||||
self.set = Some(set);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `setPrototypeOf` proxy trap to the specified native function.
|
||||
///
|
||||
/// More information:
|
||||
///
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/setPrototypeOf
|
||||
pub fn set_prototype_of(mut self, set_prototype_of: NativeFunctionSignature) -> Self {
|
||||
self.set_prototype_of = Some(set_prototype_of);
|
||||
self
|
||||
}
|
||||
|
||||
/// Build a [`JsObject`] of kind [`Proxy`].
|
||||
///
|
||||
/// Equivalent to the `Proxy ( target, handler )` constructor, but returns a
|
||||
/// [`JsObject`] in case there's a need to manipulate the returned object
|
||||
/// inside Rust code.
|
||||
#[must_use]
|
||||
pub fn build(self, context: &mut Context) -> JsProxy {
|
||||
let handler = context.construct_object();
|
||||
|
||||
if let Some(apply) = self.apply {
|
||||
let f = FunctionBuilder::native(context, apply).length(3).build();
|
||||
handler
|
||||
.create_data_property_or_throw("apply", f, context)
|
||||
.expect("new object should be writable");
|
||||
}
|
||||
if let Some(construct) = self.construct {
|
||||
let f = FunctionBuilder::native(context, construct)
|
||||
.length(3)
|
||||
.build();
|
||||
handler
|
||||
.create_data_property_or_throw("construct", f, context)
|
||||
.expect("new object should be writable");
|
||||
}
|
||||
if let Some(define_property) = self.define_property {
|
||||
let f = FunctionBuilder::native(context, define_property)
|
||||
.length(3)
|
||||
.build();
|
||||
handler
|
||||
.create_data_property_or_throw("defineProperty", f, context)
|
||||
.expect("new object should be writable");
|
||||
}
|
||||
if let Some(delete_property) = self.delete_property {
|
||||
let f = FunctionBuilder::native(context, delete_property)
|
||||
.length(2)
|
||||
.build();
|
||||
handler
|
||||
.create_data_property_or_throw("deleteProperty", f, context)
|
||||
.expect("new object should be writable");
|
||||
}
|
||||
if let Some(get) = self.get {
|
||||
let f = FunctionBuilder::native(context, get).length(3).build();
|
||||
handler
|
||||
.create_data_property_or_throw("get", f, context)
|
||||
.expect("new object should be writable");
|
||||
}
|
||||
if let Some(get_own_property_descriptor) = self.get_own_property_descriptor {
|
||||
let f = FunctionBuilder::native(context, get_own_property_descriptor)
|
||||
.length(2)
|
||||
.build();
|
||||
handler
|
||||
.create_data_property_or_throw("getOwnPropertyDescriptor", f, context)
|
||||
.expect("new object should be writable");
|
||||
}
|
||||
if let Some(get_prototype_of) = self.get_prototype_of {
|
||||
let f = FunctionBuilder::native(context, get_prototype_of)
|
||||
.length(1)
|
||||
.build();
|
||||
handler
|
||||
.create_data_property_or_throw("getPrototypeOf", f, context)
|
||||
.expect("new object should be writable");
|
||||
}
|
||||
if let Some(has) = self.has {
|
||||
let f = FunctionBuilder::native(context, has).length(2).build();
|
||||
handler
|
||||
.create_data_property_or_throw("has", f, context)
|
||||
.expect("new object should be writable");
|
||||
}
|
||||
if let Some(is_extensible) = self.is_extensible {
|
||||
let f = FunctionBuilder::native(context, is_extensible)
|
||||
.length(1)
|
||||
.build();
|
||||
handler
|
||||
.create_data_property_or_throw("isExtensible", f, context)
|
||||
.expect("new object should be writable");
|
||||
}
|
||||
if let Some(own_keys) = self.own_keys {
|
||||
let f = FunctionBuilder::native(context, own_keys).length(1).build();
|
||||
handler
|
||||
.create_data_property_or_throw("ownKeys", f, context)
|
||||
.expect("new object should be writable");
|
||||
}
|
||||
if let Some(prevent_extensions) = self.prevent_extensions {
|
||||
let f = FunctionBuilder::native(context, prevent_extensions)
|
||||
.length(1)
|
||||
.build();
|
||||
handler
|
||||
.create_data_property_or_throw("preventExtensions", f, context)
|
||||
.expect("new object should be writable");
|
||||
}
|
||||
if let Some(set) = self.set {
|
||||
let f = FunctionBuilder::native(context, set).length(4).build();
|
||||
handler
|
||||
.create_data_property_or_throw("set", f, context)
|
||||
.expect("new object should be writable");
|
||||
}
|
||||
if let Some(set_prototype_of) = self.set_prototype_of {
|
||||
let f = FunctionBuilder::native(context, set_prototype_of)
|
||||
.length(2)
|
||||
.build();
|
||||
handler
|
||||
.create_data_property_or_throw("setPrototypeOf", f, context)
|
||||
.expect("new object should be writable");
|
||||
}
|
||||
|
||||
let callable = self.target.is_callable();
|
||||
let constructor = self.target.is_constructor();
|
||||
|
||||
let proxy = JsObject::from_proto_and_data(
|
||||
context.intrinsics().constructors().object().prototype(),
|
||||
ObjectData::proxy(Proxy::new(self.target, handler), callable, constructor),
|
||||
);
|
||||
|
||||
JsProxy { inner: proxy }
|
||||
}
|
||||
|
||||
/// Builds a [`JsObject`] of kind [`Proxy`] and a [`JsFunction`] that, when
|
||||
/// called, disables the proxy of the object.
|
||||
///
|
||||
/// Equivalent to the `Proxy.revocable ( target, handler )` static method,
|
||||
/// but returns a [`JsObject`] for the proxy and a [`JsFunction`] for the
|
||||
/// revoker in case there's a need to manipulate the returned objects
|
||||
/// inside Rust code.
|
||||
#[must_use]
|
||||
pub fn build_revocable(self, context: &mut Context) -> JsRevocableProxy {
|
||||
let proxy = self.build(context);
|
||||
let revoker = Proxy::revoker(proxy.inner.clone(), context);
|
||||
|
||||
JsRevocableProxy { proxy, revoker }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use boa_gc::{Finalize, Trace};
|
||||
|
||||
use crate::{
|
||||
builtins::Set,
|
||||
object::{JsFunction, JsObject, JsObjectType, JsSetIterator},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
|
||||
// This is an wrapper for `JsSet`
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
pub struct JsSet {
|
||||
inner: JsObject,
|
||||
}
|
||||
|
||||
impl JsSet {
|
||||
/// Create a new empty set.
|
||||
///
|
||||
/// Doesn't matches JavaScript `new Set()` as it doesn't takes an iterator
|
||||
/// similar to Rust initialization.
|
||||
#[inline]
|
||||
pub fn new(context: &mut Context) -> Self {
|
||||
let inner = Set::set_create(None, context);
|
||||
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
/// Returns the size of the `Set` as an integer.
|
||||
///
|
||||
/// Same as JavaScript's `set.size`.
|
||||
#[inline]
|
||||
pub fn size(&self, context: &mut Context) -> JsResult<usize> {
|
||||
Set::get_size(&self.inner.clone().into(), context)
|
||||
}
|
||||
|
||||
/// Appends value to the Set object.
|
||||
/// Returns the Set object with added value.
|
||||
///
|
||||
/// Same as JavaScript's `set.add(value)`.
|
||||
#[inline]
|
||||
pub fn add<T>(&self, value: T, context: &mut Context) -> JsResult<JsValue>
|
||||
where
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
self.add_items(&[value.into()], context)
|
||||
}
|
||||
|
||||
/// Adds slice as a single element.
|
||||
/// Returns the Set object with added slice.
|
||||
///
|
||||
/// Same as JavaScript's `set.add(["one", "two", "three"])`
|
||||
#[inline]
|
||||
pub fn add_items(&self, items: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Set::add(&self.inner.clone().into(), items, context)
|
||||
}
|
||||
|
||||
/// Removes all elements from the Set object.
|
||||
/// Returns `Undefined`.
|
||||
///
|
||||
/// Same as JavaScript's `set.clear()`.
|
||||
#[inline]
|
||||
pub fn clear(&self, context: &mut Context) -> JsResult<JsValue> {
|
||||
Set::clear(&self.inner.clone().into(), &[JsValue::Null], context)
|
||||
}
|
||||
|
||||
/// Removes the element associated to the value.
|
||||
/// Returns a boolean asserting whether an element was
|
||||
/// successfully removed or not.
|
||||
///
|
||||
/// Same as JavaScript's `set.delete(value)`.
|
||||
#[inline]
|
||||
pub fn delete<T>(&self, value: T, context: &mut Context) -> JsResult<bool>
|
||||
where
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
match Set::delete(&self.inner.clone().into(), &[value.into()], context)? {
|
||||
JsValue::Boolean(bool) => Ok(bool),
|
||||
_ => Err(JsValue::Undefined),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a boolean asserting whether an element is present
|
||||
/// with the given value in the Set object or not.
|
||||
///
|
||||
/// Same as JavaScript's `set.has(value)`.
|
||||
#[inline]
|
||||
pub fn has<T>(&self, value: T, context: &mut Context) -> JsResult<bool>
|
||||
where
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
match Set::has(&self.inner.clone().into(), &[value.into()], context)? {
|
||||
JsValue::Boolean(bool) => Ok(bool),
|
||||
_ => Err(JsValue::Undefined),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new iterator object that yields the values
|
||||
/// for each element in the Set object in insertion order.
|
||||
///
|
||||
/// Same as JavaScript's `set.values()`.
|
||||
#[inline]
|
||||
pub fn values(&self, context: &mut Context) -> JsResult<JsSetIterator> {
|
||||
let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::Null], context)?
|
||||
.get_iterator(context, None, None)?;
|
||||
|
||||
JsSetIterator::from_object(iterator_object.iterator().clone(), context)
|
||||
}
|
||||
|
||||
/// Alias for `Set.prototype.values()`
|
||||
/// Returns a new iterator object that yields the values
|
||||
/// for each element in the Set object in insertion order.
|
||||
///
|
||||
/// Same as JavaScript's `set.keys()`.
|
||||
#[inline]
|
||||
pub fn keys(&self, context: &mut Context) -> JsResult<JsSetIterator> {
|
||||
let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::Null], context)?
|
||||
.get_iterator(context, None, None)?;
|
||||
|
||||
JsSetIterator::from_object(iterator_object.iterator().clone(), context)
|
||||
}
|
||||
|
||||
/// Calls callbackFn once for each value present in the Set object,
|
||||
/// in insertion order.
|
||||
/// Returns `Undefined`.
|
||||
///
|
||||
/// Same as JavaScript's `set.forEach(values)`.
|
||||
#[inline]
|
||||
pub fn for_each(
|
||||
&self,
|
||||
callback: JsFunction,
|
||||
this_arg: JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
Set::for_each(
|
||||
&self.inner.clone().into(),
|
||||
&[callback.into(), this_arg],
|
||||
context,
|
||||
)
|
||||
}
|
||||
|
||||
/// Utility: Creates `JsSet` from `JsObject`, if not a Set throw `TypeError`.
|
||||
#[inline]
|
||||
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> {
|
||||
if object.borrow().is_set() {
|
||||
Ok(Self { inner: object })
|
||||
} else {
|
||||
context.throw_error("Object is not a Set")
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility: Creates a `JsSet` from a `<IntoIterator<Item = JsValue>` convertible object.
|
||||
#[inline]
|
||||
pub fn from_iter<I>(elements: I, context: &mut Context) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = JsValue>,
|
||||
{
|
||||
let inner = Set::create_set_from_list(elements, context);
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsSet> for JsObject {
|
||||
#[inline]
|
||||
fn from(o: JsSet) -> Self {
|
||||
o.inner.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsSet> for JsValue {
|
||||
#[inline]
|
||||
fn from(o: JsSet) -> Self {
|
||||
o.inner.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for JsSet {
|
||||
type Target = JsObject;
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl JsObjectType for JsSet {}
|
||||
@@ -0,0 +1,56 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use boa_gc::{Finalize, Trace};
|
||||
|
||||
use crate::{
|
||||
builtins::SetIterator,
|
||||
object::{JsObject, JsObjectType},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
|
||||
/// JavaScript `SetIterator` rust object
|
||||
#[derive(Debug, Clone, Finalize, Trace)]
|
||||
pub struct JsSetIterator {
|
||||
inner: JsObject,
|
||||
}
|
||||
|
||||
impl JsSetIterator {
|
||||
/// Create a `JsSetIterator` from a `JsObject`.
|
||||
/// If object is not a `SetIterator`, throw `TypeError`.
|
||||
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> {
|
||||
if object.borrow().is_set_iterator() {
|
||||
Ok(Self { inner: object })
|
||||
} else {
|
||||
context.throw_type_error("object is not a SetIterator")
|
||||
}
|
||||
}
|
||||
/// Advances the `JsSetIterator` and gets the next result in the `JsSet`.
|
||||
pub fn next(&self, context: &mut Context) -> JsResult<JsValue> {
|
||||
SetIterator::next(&self.inner.clone().into(), &[JsValue::Null], context)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsSetIterator> for JsObject {
|
||||
#[inline]
|
||||
fn from(o: JsSetIterator) -> Self {
|
||||
o.inner.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsSetIterator> for JsValue {
|
||||
#[inline]
|
||||
fn from(o: JsSetIterator) -> Self {
|
||||
o.inner.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for JsSetIterator {
|
||||
type Target = JsObject;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl JsObjectType for JsSetIterator {}
|
||||
@@ -0,0 +1,428 @@
|
||||
use crate::{
|
||||
builtins::typed_array::TypedArray,
|
||||
object::{JsArrayBuffer, JsFunction, JsObject, JsObjectType},
|
||||
value::IntoOrUndefined,
|
||||
Context, JsResult, JsString, JsValue,
|
||||
};
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// JavaScript `TypedArray` rust object.
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
pub struct JsTypedArray {
|
||||
inner: JsValue,
|
||||
}
|
||||
|
||||
impl JsTypedArray {
|
||||
/// Create a [`JsTypedArray`] from a [`JsObject`], if the object is not a typed array throw a `TypeError`.
|
||||
///
|
||||
/// This does not clone the fields of the typed array, it only does a shallow clone of the object.
|
||||
#[inline]
|
||||
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> {
|
||||
if object.borrow().is_typed_array() {
|
||||
Ok(Self {
|
||||
inner: object.into(),
|
||||
})
|
||||
} else {
|
||||
context.throw_type_error("object is not a TypedArray")
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the length of the array.
|
||||
///
|
||||
/// Same a `array.length` in JavaScript.
|
||||
#[inline]
|
||||
pub fn length(&self, context: &mut Context) -> JsResult<usize> {
|
||||
Ok(TypedArray::length(&self.inner, &[], context)?
|
||||
.as_number()
|
||||
.map(|x| x as usize)
|
||||
.expect("length should return a number"))
|
||||
}
|
||||
|
||||
/// Check if the array is empty, i.e. the `length` is zero.
|
||||
#[inline]
|
||||
pub fn is_empty(&self, context: &mut Context) -> JsResult<bool> {
|
||||
Ok(self.length(context)? == 0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn at<T>(&self, index: T, context: &mut Context) -> JsResult<JsValue>
|
||||
where
|
||||
T: Into<i64>,
|
||||
{
|
||||
TypedArray::at(&self.inner, &[index.into().into()], context)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn byte_length(&self, context: &mut Context) -> JsResult<usize> {
|
||||
Ok(TypedArray::byte_length(&self.inner, &[], context)?
|
||||
.as_number()
|
||||
.map(|x| x as usize)
|
||||
.expect("byteLength should return a number"))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn byte_offset(&self, context: &mut Context) -> JsResult<usize> {
|
||||
Ok(TypedArray::byte_offset(&self.inner, &[], context)?
|
||||
.as_number()
|
||||
.map(|x| x as usize)
|
||||
.expect("byteLength should return a number"))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fill<T>(
|
||||
&self,
|
||||
value: T,
|
||||
start: Option<usize>,
|
||||
end: Option<usize>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Self>
|
||||
where
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
TypedArray::fill(
|
||||
&self.inner,
|
||||
&[
|
||||
value.into(),
|
||||
start.into_or_undefined(),
|
||||
end.into_or_undefined(),
|
||||
],
|
||||
context,
|
||||
)?;
|
||||
Ok(self.clone())
|
||||
}
|
||||
|
||||
pub fn every(
|
||||
&self,
|
||||
predicate: JsFunction,
|
||||
this_arg: Option<JsValue>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
let result = TypedArray::every(
|
||||
&self.inner,
|
||||
&[predicate.into(), this_arg.into_or_undefined()],
|
||||
context,
|
||||
)?
|
||||
.as_boolean()
|
||||
.expect("TypedArray.prototype.every should always return boolean");
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn some(
|
||||
&self,
|
||||
callback: JsFunction,
|
||||
this_arg: Option<JsValue>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
let result = TypedArray::some(
|
||||
&self.inner,
|
||||
&[callback.into(), this_arg.into_or_undefined()],
|
||||
context,
|
||||
)?
|
||||
.as_boolean()
|
||||
.expect("TypedArray.prototype.some should always return boolean");
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sort(&self, compare_fn: Option<JsFunction>, context: &mut Context) -> JsResult<Self> {
|
||||
TypedArray::sort(&self.inner, &[compare_fn.into_or_undefined()], context)?;
|
||||
|
||||
Ok(self.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn filter(
|
||||
&self,
|
||||
callback: JsFunction,
|
||||
this_arg: Option<JsValue>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Self> {
|
||||
let object = TypedArray::filter(
|
||||
&self.inner,
|
||||
&[callback.into(), this_arg.into_or_undefined()],
|
||||
context,
|
||||
)?;
|
||||
|
||||
Ok(Self { inner: object })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn map(
|
||||
&self,
|
||||
callback: JsFunction,
|
||||
this_arg: Option<JsValue>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Self> {
|
||||
let object = TypedArray::map(
|
||||
&self.inner,
|
||||
&[callback.into(), this_arg.into_or_undefined()],
|
||||
context,
|
||||
)?;
|
||||
|
||||
Ok(Self { inner: object })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reduce(
|
||||
&self,
|
||||
callback: JsFunction,
|
||||
initial_value: Option<JsValue>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
TypedArray::reduce(
|
||||
&self.inner,
|
||||
&[callback.into(), initial_value.into_or_undefined()],
|
||||
context,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reduce_right(
|
||||
&self,
|
||||
callback: JsFunction,
|
||||
initial_value: Option<JsValue>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
TypedArray::reduceright(
|
||||
&self.inner,
|
||||
&[callback.into(), initial_value.into_or_undefined()],
|
||||
context,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reverse(&self, context: &mut Context) -> JsResult<Self> {
|
||||
TypedArray::reverse(&self.inner, &[], context)?;
|
||||
Ok(self.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn slice(
|
||||
&self,
|
||||
start: Option<usize>,
|
||||
end: Option<usize>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Self> {
|
||||
let object = TypedArray::slice(
|
||||
&self.inner,
|
||||
&[start.into_or_undefined(), end.into_or_undefined()],
|
||||
context,
|
||||
)?;
|
||||
|
||||
Ok(Self { inner: object })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn find(
|
||||
&self,
|
||||
predicate: JsFunction,
|
||||
this_arg: Option<JsValue>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
TypedArray::find(
|
||||
&self.inner,
|
||||
&[predicate.into(), this_arg.into_or_undefined()],
|
||||
context,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn index_of<T>(
|
||||
&self,
|
||||
search_element: T,
|
||||
from_index: Option<usize>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Option<usize>>
|
||||
where
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
let index = TypedArray::index_of(
|
||||
&self.inner,
|
||||
&[search_element.into(), from_index.into_or_undefined()],
|
||||
context,
|
||||
)?
|
||||
.as_number()
|
||||
.expect("TypedArray.prototype.indexOf should always return number");
|
||||
|
||||
#[allow(clippy::float_cmp)]
|
||||
if index == -1.0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(index as usize))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn last_index_of<T>(
|
||||
&self,
|
||||
search_element: T,
|
||||
from_index: Option<usize>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Option<usize>>
|
||||
where
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
let index = TypedArray::last_index_of(
|
||||
&self.inner,
|
||||
&[search_element.into(), from_index.into_or_undefined()],
|
||||
context,
|
||||
)?
|
||||
.as_number()
|
||||
.expect("TypedArray.prototype.lastIndexOf should always return number");
|
||||
|
||||
#[allow(clippy::float_cmp)]
|
||||
if index == -1.0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(index as usize))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn join(&self, separator: Option<JsString>, context: &mut Context) -> JsResult<JsString> {
|
||||
TypedArray::join(&self.inner, &[separator.into_or_undefined()], context).map(|x| {
|
||||
x.as_string()
|
||||
.cloned()
|
||||
.expect("TypedArray.prototype.join always returns string")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsTypedArray> for JsObject {
|
||||
#[inline]
|
||||
fn from(o: JsTypedArray) -> Self {
|
||||
o.inner
|
||||
.as_object()
|
||||
.expect("should always be an object")
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsTypedArray> for JsValue {
|
||||
#[inline]
|
||||
fn from(o: JsTypedArray) -> Self {
|
||||
o.inner.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for JsTypedArray {
|
||||
type Target = JsObject;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.inner.as_object().expect("should always be an object")
|
||||
}
|
||||
}
|
||||
|
||||
impl JsObjectType for JsTypedArray {}
|
||||
|
||||
macro_rules! JsTypedArrayType {
|
||||
($name:ident, $constructor_function:ident, $constructor_object:ident, $element:ty) => {
|
||||
#[doc = concat!("JavaScript `", stringify!($constructor_function), "` rust object.")]
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
pub struct $name {
|
||||
inner: JsTypedArray,
|
||||
}
|
||||
|
||||
impl $name {
|
||||
#[inline]
|
||||
pub fn from_array_buffer(
|
||||
array_buffer: JsArrayBuffer,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Self> {
|
||||
let new_target = context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.$constructor_object()
|
||||
.constructor()
|
||||
.into();
|
||||
let object = crate::builtins::typed_array::$constructor_function::constructor(
|
||||
&new_target,
|
||||
&[array_buffer.into()],
|
||||
context,
|
||||
)?
|
||||
.as_object()
|
||||
.expect("object")
|
||||
.clone();
|
||||
|
||||
Ok(Self {
|
||||
inner: JsTypedArray {
|
||||
inner: object.into(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_iter<I>(elements: I, context: &mut Context) -> JsResult<Self>
|
||||
where
|
||||
I: IntoIterator<Item = $element>,
|
||||
{
|
||||
let bytes: Vec<_> = elements
|
||||
.into_iter()
|
||||
.flat_map(<$element>::to_ne_bytes)
|
||||
.collect();
|
||||
let array_buffer = JsArrayBuffer::from_byte_block(bytes, context)?;
|
||||
let new_target = context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.$constructor_object()
|
||||
.constructor()
|
||||
.into();
|
||||
let object = crate::builtins::typed_array::$constructor_function::constructor(
|
||||
&new_target,
|
||||
&[array_buffer.into()],
|
||||
context,
|
||||
)?
|
||||
.as_object()
|
||||
.expect("object")
|
||||
.clone();
|
||||
|
||||
Ok(Self {
|
||||
inner: JsTypedArray {
|
||||
inner: object.into(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$name> for JsObject {
|
||||
#[inline]
|
||||
fn from(o: $name) -> Self {
|
||||
o.inner
|
||||
.inner
|
||||
.as_object()
|
||||
.expect("should always be an object")
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$name> for JsValue {
|
||||
#[inline]
|
||||
fn from(o: $name) -> Self {
|
||||
o.inner.inner.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for $name {
|
||||
type Target = JsTypedArray;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
JsTypedArrayType!(JsUint8Array, Uint8Array, typed_uint8_array, u8);
|
||||
JsTypedArrayType!(JsInt8Array, Int8Array, typed_int8_array, i8);
|
||||
JsTypedArrayType!(JsUint16Array, Uint16Array, typed_uint16_array, u16);
|
||||
JsTypedArrayType!(JsInt16Array, Int16Array, typed_int16_array, i16);
|
||||
JsTypedArrayType!(JsUint32Array, Uint32Array, typed_uint32_array, u32);
|
||||
JsTypedArrayType!(JsInt32Array, Int32Array, typed_int32_array, i32);
|
||||
JsTypedArrayType!(JsFloat32Array, Float32Array, typed_float32_array, f32);
|
||||
JsTypedArrayType!(JsFloat64Array, Float64Array, typed_float64_array, f64);
|
||||
2123
__wasm/wit-bindgen-sample/engine/boa/boa_engine/src/object/mod.rs
Normal file
2123
__wasm/wit-bindgen-sample/engine/boa/boa_engine/src/object/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,841 @@
|
||||
use crate::{
|
||||
builtins::Array,
|
||||
context::intrinsics::{StandardConstructor, StandardConstructors},
|
||||
object::JsObject,
|
||||
property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind},
|
||||
symbol::WellKnownSymbols,
|
||||
value::Type,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
|
||||
/// Object integrity level.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum IntegrityLevel {
|
||||
/// Sealed object integrity level.
|
||||
///
|
||||
/// Preventing new properties from being added to it and marking all existing
|
||||
/// properties as non-configurable. Values of present properties can still be
|
||||
/// changed as long as they are writable.
|
||||
Sealed,
|
||||
|
||||
/// Frozen object integrity level
|
||||
///
|
||||
/// A frozen object can no longer be changed; freezing an object prevents new
|
||||
/// properties from being added to it, existing properties from being removed,
|
||||
/// prevents changing the enumerability, configurability, or writability of
|
||||
/// existing properties, and prevents the values of existing properties from
|
||||
/// being changed. In addition, freezing an object also prevents its prototype
|
||||
/// from being changed.
|
||||
Frozen,
|
||||
}
|
||||
|
||||
impl IntegrityLevel {
|
||||
/// Returns `true` if the integrity level is sealed.
|
||||
pub fn is_sealed(&self) -> bool {
|
||||
matches!(self, Self::Sealed)
|
||||
}
|
||||
|
||||
/// Returns `true` if the integrity level is frozen.
|
||||
pub fn is_frozen(&self) -> bool {
|
||||
matches!(self, Self::Frozen)
|
||||
}
|
||||
}
|
||||
|
||||
impl JsObject {
|
||||
/// Check if object is extensible.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isextensible-o
|
||||
#[inline]
|
||||
pub fn is_extensible(&self, context: &mut Context) -> JsResult<bool> {
|
||||
// 1. Return ? O.[[IsExtensible]]().
|
||||
self.__is_extensible__(context)
|
||||
}
|
||||
|
||||
/// 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 context.throw_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-createdataproperty
|
||||
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)
|
||||
}
|
||||
|
||||
// todo: CreateMethodProperty
|
||||
|
||||
/// Create data property or throw
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createdatapropertyorthrow
|
||||
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 context.throw_type_error(format!("cannot redefine property: {key}"));
|
||||
}
|
||||
// 5. Return success.
|
||||
Ok(success)
|
||||
}
|
||||
|
||||
/// Create non-enumerable data property or throw
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createnonenumerabledatapropertyinfallibly
|
||||
pub(crate) fn create_non_enumerable_data_property_or_throw<K, V>(
|
||||
&self,
|
||||
key: K,
|
||||
value: V,
|
||||
context: &mut Context,
|
||||
) where
|
||||
K: Into<PropertyKey>,
|
||||
V: Into<JsValue>,
|
||||
{
|
||||
// 1. Assert: O is an ordinary, extensible object with no non-configurable properties.
|
||||
|
||||
// 2. Let newDesc be the PropertyDescriptor {
|
||||
// [[Value]]: V,
|
||||
// [[Writable]]: true,
|
||||
// [[Enumerable]]: false,
|
||||
// [[Configurable]]: true
|
||||
// }.
|
||||
let new_desc = PropertyDescriptorBuilder::new()
|
||||
.value(value)
|
||||
.writable(true)
|
||||
.enumerable(false)
|
||||
.configurable(true)
|
||||
.build();
|
||||
|
||||
// 3. Perform ! DefinePropertyOrThrow(O, P, newDesc).
|
||||
self.define_property_or_throw(key, new_desc, context)
|
||||
.expect("should not fail according to spec");
|
||||
|
||||
// 4. Return unused.
|
||||
}
|
||||
|
||||
/// 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 context.throw_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-deletepropertyorthrow
|
||||
#[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, context)?;
|
||||
// 4. If success is false, throw a TypeError exception.
|
||||
if !success {
|
||||
return context.throw_type_error(format!("cannot delete property: {key}"));
|
||||
}
|
||||
// 5. Return success.
|
||||
Ok(success)
|
||||
}
|
||||
|
||||
/// Check if object has property.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-hasproperty
|
||||
#[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).
|
||||
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 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 fn call(
|
||||
&self,
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If argumentsList is not present, set argumentsList to a new empty List.
|
||||
// 2. If IsCallable(F) is false, throw a TypeError exception.
|
||||
if !self.is_callable() {
|
||||
return context.throw_type_error("not a function");
|
||||
}
|
||||
// 3. Return ? F.[[Call]](V, argumentsList).
|
||||
self.__call__(this, args, context)
|
||||
}
|
||||
|
||||
/// `Construct ( F [ , argumentsList [ , newTarget ] ] )`
|
||||
///
|
||||
/// Construct an instance of this object with the specified arguments.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the object is currently mutably borrowed.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-construct
|
||||
#[track_caller]
|
||||
#[inline]
|
||||
pub fn construct(
|
||||
&self,
|
||||
args: &[JsValue],
|
||||
new_target: Option<&JsObject>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsObject> {
|
||||
// 1. If newTarget is not present, set newTarget to F.
|
||||
let new_target = new_target.unwrap_or(self);
|
||||
// 2. If argumentsList is not present, set argumentsList to a new empty List.
|
||||
// 3. Return ? F.[[Construct]](argumentsList, newTarget).
|
||||
self.__construct__(args, new_target, context)
|
||||
}
|
||||
|
||||
/// Make the object [`sealed`][IntegrityLevel::Sealed] or [`frozen`][IntegrityLevel::Frozen].
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-setintegritylevel
|
||||
#[inline]
|
||||
pub fn set_integrity_level(
|
||||
&self,
|
||||
level: IntegrityLevel,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Assert: level is either sealed or frozen.
|
||||
|
||||
// 3. Let status be ? O.[[PreventExtensions]]().
|
||||
let status = self.__prevent_extensions__(context)?;
|
||||
// 4. If status is false, return false.
|
||||
if !status {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// 5. Let keys be ? O.[[OwnPropertyKeys]]().
|
||||
let keys = self.__own_property_keys__(context)?;
|
||||
|
||||
match level {
|
||||
// 6. If level is sealed, then
|
||||
IntegrityLevel::Sealed => {
|
||||
// a. For each element k of keys, do
|
||||
for k in keys {
|
||||
// i. Perform ? DefinePropertyOrThrow(O, k, PropertyDescriptor { [[Configurable]]: false }).
|
||||
self.define_property_or_throw(
|
||||
k,
|
||||
PropertyDescriptor::builder().configurable(false).build(),
|
||||
context,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
// 7. Else,
|
||||
// a. Assert: level is frozen.
|
||||
IntegrityLevel::Frozen => {
|
||||
// b. For each element k of keys, do
|
||||
for k in keys {
|
||||
// i. Let currentDesc be ? O.[[GetOwnProperty]](k).
|
||||
let current_desc = self.__get_own_property__(&k, context)?;
|
||||
// ii. If currentDesc is not undefined, then
|
||||
if let Some(current_desc) = current_desc {
|
||||
// 1. If IsAccessorDescriptor(currentDesc) is true, then
|
||||
let desc = if current_desc.is_accessor_descriptor() {
|
||||
// a. Let desc be the PropertyDescriptor { [[Configurable]]: false }.
|
||||
PropertyDescriptor::builder().configurable(false).build()
|
||||
// 2. Else,
|
||||
} else {
|
||||
// a. Let desc be the PropertyDescriptor { [[Configurable]]: false, [[Writable]]: false }.
|
||||
PropertyDescriptor::builder()
|
||||
.configurable(false)
|
||||
.writable(false)
|
||||
.build()
|
||||
};
|
||||
// 3. Perform ? DefinePropertyOrThrow(O, k, desc).
|
||||
self.define_property_or_throw(k, desc, context)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Return true.
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Check if the object is [`sealed`][IntegrityLevel::Sealed] or [`frozen`][IntegrityLevel::Frozen].
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-testintegritylevel
|
||||
#[inline]
|
||||
pub fn test_integrity_level(
|
||||
&self,
|
||||
level: IntegrityLevel,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
// 1. Assert: Type(O) is Object.
|
||||
// 2. Assert: level is either sealed or frozen.
|
||||
|
||||
// 3. Let extensible be ? IsExtensible(O).
|
||||
let extensible = self.is_extensible(context)?;
|
||||
|
||||
// 4. If extensible is true, return false.
|
||||
if extensible {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// 5. NOTE: If the object is extensible, none of its properties are examined.
|
||||
// 6. Let keys be ? O.[[OwnPropertyKeys]]().
|
||||
let keys = self.__own_property_keys__(context)?;
|
||||
|
||||
// 7. For each element k of keys, do
|
||||
for k in keys {
|
||||
// a. Let currentDesc be ? O.[[GetOwnProperty]](k).
|
||||
let current_desc = self.__get_own_property__(&k, context)?;
|
||||
// b. If currentDesc is not undefined, then
|
||||
if let Some(current_desc) = current_desc {
|
||||
// i. If currentDesc.[[Configurable]] is true, return false.
|
||||
if current_desc.expect_configurable() {
|
||||
return Ok(false);
|
||||
}
|
||||
// ii. If level is frozen and IsDataDescriptor(currentDesc) is true, then
|
||||
if level.is_frozen() && current_desc.is_data_descriptor() {
|
||||
// 1. If currentDesc.[[Writable]] is true, return false.
|
||||
if current_desc.expect_writable() {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 8. Return true.
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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<F>(
|
||||
&self,
|
||||
default_constructor: F,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Self>
|
||||
where
|
||||
F: FnOnce(&StandardConstructors) -> &StandardConstructor,
|
||||
{
|
||||
// 1. Assert: Type(O) is Object.
|
||||
|
||||
// 2. Let C be ? Get(O, "constructor").
|
||||
let c = self.get("constructor", context)?;
|
||||
|
||||
// 3. If C is undefined, return defaultConstructor.
|
||||
if c.is_undefined() {
|
||||
return Ok(default_constructor(context.intrinsics().constructors()).constructor());
|
||||
}
|
||||
|
||||
// 4. If Type(C) is not Object, throw a TypeError exception.
|
||||
let c = if let Some(c) = c.as_object() {
|
||||
c
|
||||
} else {
|
||||
return context.throw_type_error("property 'constructor' is not an object");
|
||||
};
|
||||
|
||||
// 5. Let S be ? Get(C, @@species).
|
||||
let s = c.get(WellKnownSymbols::species(), context)?;
|
||||
|
||||
// 6. If S is either undefined or null, return defaultConstructor.
|
||||
if s.is_null_or_undefined() {
|
||||
return Ok(default_constructor(context.intrinsics().constructors()).constructor());
|
||||
}
|
||||
|
||||
// 7. If IsConstructor(S) is true, return S.
|
||||
// 8. Throw a TypeError exception.
|
||||
match s.as_object() {
|
||||
Some(obj) if obj.is_constructor() => Ok(obj.clone()),
|
||||
_ => context.throw_type_error("property 'constructor' is not a constructor"),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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()),
|
||||
PropertyKey::Symbol(_) => 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)
|
||||
}
|
||||
|
||||
/// Abstract operation `GetMethod ( V, P )`
|
||||
///
|
||||
/// Retrieves the value of a 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
|
||||
pub(crate) fn get_method<K>(&self, key: K, context: &mut Context) -> JsResult<Option<Self>>
|
||||
where
|
||||
K: Into<PropertyKey>,
|
||||
{
|
||||
// Note: The spec specifies this function for JsValue.
|
||||
// It is implemented for JsObject for convenience.
|
||||
|
||||
// 1. Assert: IsPropertyKey(P) is true.
|
||||
// 2. Let func be ? GetV(V, P).
|
||||
match &self.__get__(&key.into(), self.clone().into(), context)? {
|
||||
// 3. If func is either undefined or null, return undefined.
|
||||
JsValue::Undefined | JsValue::Null => Ok(None),
|
||||
// 5. Return func.
|
||||
JsValue::Object(obj) if obj.is_callable() => Ok(Some(obj.clone())),
|
||||
// 4. If IsCallable(func) is false, throw a TypeError exception.
|
||||
_ => {
|
||||
context.throw_type_error("value returned for property of object is not a function")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstract operation `IsArray ( argument )`
|
||||
///
|
||||
/// Check if a value is an array.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isarray
|
||||
pub(crate) fn is_array_abstract(&self, context: &mut Context) -> JsResult<bool> {
|
||||
// Note: The spec specifies this function for JsValue.
|
||||
// It is implemented for JsObject for convenience.
|
||||
|
||||
// 2. If argument is an Array exotic object, return true.
|
||||
if self.is_array() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// 3. If argument is a Proxy exotic object, then
|
||||
let object = self.borrow();
|
||||
if let Some(proxy) = object.as_proxy() {
|
||||
// a. If argument.[[ProxyHandler]] is null, throw a TypeError exception.
|
||||
// b. Let target be argument.[[ProxyTarget]].
|
||||
let (target, _) = proxy.try_data(context)?;
|
||||
|
||||
// c. Return ? IsArray(target).
|
||||
return target.is_array_abstract(context);
|
||||
}
|
||||
|
||||
// 4. Return false.
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
// todo: GetFunctionRealm
|
||||
|
||||
// todo: CopyDataProperties
|
||||
|
||||
// todo: PrivateElementFind
|
||||
|
||||
// todo: PrivateFieldAdd
|
||||
|
||||
// todo: PrivateMethodOrAccessorAdd
|
||||
|
||||
// todo: PrivateGet
|
||||
|
||||
// todo: PrivateSet
|
||||
|
||||
// todo: DefineField
|
||||
|
||||
// todo: InitializeInstanceElements
|
||||
}
|
||||
|
||||
impl JsValue {
|
||||
/// Abstract operation `GetV ( V, P )`.
|
||||
///
|
||||
/// Retrieves the value of a specific property of an ECMAScript language value. If the value is
|
||||
/// not an object, the property lookup is performed using a wrapper object appropriate for the
|
||||
/// type of the value.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-getv
|
||||
#[inline]
|
||||
pub(crate) fn get_v<K>(&self, key: K, context: &mut Context) -> JsResult<Self>
|
||||
where
|
||||
K: Into<PropertyKey>,
|
||||
{
|
||||
// 1. Let O be ? ToObject(V).
|
||||
let o = self.to_object(context)?;
|
||||
|
||||
// 2. Return ? O.[[Get]](P, V).
|
||||
o.__get__(&key.into(), self.clone(), context)
|
||||
}
|
||||
|
||||
/// Abstract operation `GetMethod ( V, P )`
|
||||
///
|
||||
/// Retrieves the value of a 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, key: K, context: &mut Context) -> JsResult<Option<JsObject>>
|
||||
where
|
||||
K: Into<PropertyKey>,
|
||||
{
|
||||
// Note: The spec specifies this function for JsValue.
|
||||
// The main part of the function is implemented for JsObject.
|
||||
self.to_object(context)?.get_method(key, context)
|
||||
}
|
||||
|
||||
/// 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<Self>> {
|
||||
// 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 context.throw_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)
|
||||
}
|
||||
|
||||
/// Abstract operation `( V, P [ , argumentsList ] )`
|
||||
///
|
||||
/// Calls a method property of an ECMAScript language value.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-invoke
|
||||
pub(crate) fn invoke<K>(&self, key: K, args: &[Self], context: &mut Context) -> JsResult<Self>
|
||||
where
|
||||
K: Into<PropertyKey>,
|
||||
{
|
||||
// 1. If argumentsList is not present, set argumentsList to a new empty List.
|
||||
// 2. Let func be ? GetV(V, P).
|
||||
let func = self.get_v(key, context)?;
|
||||
|
||||
// 3. Return ? Call(func, V, argumentsList)
|
||||
context.call(&func, self, args)
|
||||
}
|
||||
|
||||
/// Abstract operation `OrdinaryHasInstance ( C, O )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasinstance
|
||||
pub fn ordinary_has_instance(
|
||||
function: &Self,
|
||||
object: &Self,
|
||||
context: &mut Context,
|
||||
) -> JsResult<bool> {
|
||||
// 1. If IsCallable(C) is false, return false.
|
||||
let function = if let Some(function) = function.as_callable() {
|
||||
function
|
||||
} else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
// 2. If C has a [[BoundTargetFunction]] internal slot, then
|
||||
if let Some(bound_function) = function.borrow().as_bound_function() {
|
||||
// a. Let BC be C.[[BoundTargetFunction]].
|
||||
// b. Return ? InstanceofOperator(O, BC).
|
||||
return Self::instance_of(
|
||||
object,
|
||||
&bound_function.target_function().clone().into(),
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
let mut object = if let Some(obj) = object.as_object() {
|
||||
obj.clone()
|
||||
} else {
|
||||
// 3. If Type(O) is not Object, return false.
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
// 4. Let P be ? Get(C, "prototype").
|
||||
let prototype = function.get("prototype", context)?;
|
||||
|
||||
let prototype = if let Some(obj) = prototype.as_object() {
|
||||
obj
|
||||
} else {
|
||||
// 5. If Type(P) is not Object, throw a TypeError exception.
|
||||
return context
|
||||
.throw_type_error("function has non-object prototype in instanceof check");
|
||||
};
|
||||
|
||||
// 6. Repeat,
|
||||
loop {
|
||||
// a. Set O to ? O.[[GetPrototypeOf]]().
|
||||
object = match object.__get_prototype_of__(context)? {
|
||||
Some(obj) => obj,
|
||||
// b. If O is null, return false.
|
||||
None => return Ok(false),
|
||||
};
|
||||
|
||||
// c. If SameValue(P, O) is true, return true.
|
||||
if JsObject::equals(&object, prototype) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,763 @@
|
||||
use super::{PropertyDescriptor, PropertyKey};
|
||||
use crate::{property::PropertyDescriptorBuilder, JsString, JsSymbol, JsValue};
|
||||
use boa_gc::{custom_trace, Finalize, Trace};
|
||||
use indexmap::IndexMap;
|
||||
use rustc_hash::{FxHashMap, FxHasher};
|
||||
use std::{collections::hash_map, hash::BuildHasherDefault, iter::FusedIterator};
|
||||
|
||||
/// Type alias to make it easier to work with the string properties on the global object.
|
||||
pub(crate) type GlobalPropertyMap =
|
||||
IndexMap<JsString, PropertyDescriptor, BuildHasherDefault<FxHasher>>;
|
||||
|
||||
/// Wrapper around `indexmap::IndexMap` for usage in `PropertyMap`.
|
||||
#[derive(Debug, Finalize)]
|
||||
struct OrderedHashMap<K: Trace>(IndexMap<K, PropertyDescriptor, BuildHasherDefault<FxHasher>>);
|
||||
|
||||
impl<K: Trace> Default for OrderedHashMap<K> {
|
||||
fn default() -> Self {
|
||||
Self(IndexMap::with_hasher(BuildHasherDefault::default()))
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<K: Trace> Trace for OrderedHashMap<K> {
|
||||
custom_trace!(this, {
|
||||
for (k, v) in this.0.iter() {
|
||||
mark(k);
|
||||
mark(v);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// This represents all the indexed properties.
|
||||
///
|
||||
/// The index properties can be stored in two storage methods:
|
||||
/// - `Dense` Storage
|
||||
/// - `Sparse` Storage
|
||||
///
|
||||
/// By default it is dense storage.
|
||||
#[derive(Debug, Trace, Finalize)]
|
||||
enum IndexedProperties {
|
||||
/// Dense storage holds a contiguous array of properties where the index in the array is the key of the property.
|
||||
/// These are known to be data descriptors with a value field, writable field set to `true`, configurable field set to `true`, enumerable field set to `true`.
|
||||
///
|
||||
/// Since we know the properties of the property descriptors (and they are all the same) we can omit it and just store only
|
||||
/// the value field and construct the data property descriptor on demand.
|
||||
///
|
||||
/// This storage method is used by default.
|
||||
Dense(Vec<JsValue>),
|
||||
|
||||
/// Sparse storage this storage is used as a backup if the element keys are not continuous or the property descriptors
|
||||
/// are not data descriptors with with a value field, writable field set to `true`, configurable field set to `true`, enumerable field set to `true`.
|
||||
///
|
||||
/// This method uses more space, since we also have to store the property descriptors, not just the value.
|
||||
/// It is also slower because we need to to a hash lookup.
|
||||
Sparse(FxHashMap<u32, PropertyDescriptor>),
|
||||
}
|
||||
|
||||
impl Default for IndexedProperties {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::Dense(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexedProperties {
|
||||
/// Get a property descriptor if it exists.
|
||||
#[inline]
|
||||
fn get(&self, key: u32) -> Option<PropertyDescriptor> {
|
||||
match self {
|
||||
Self::Sparse(ref map) => map.get(&key).cloned(),
|
||||
Self::Dense(ref vec) => vec.get(key as usize).map(|value| {
|
||||
PropertyDescriptorBuilder::new()
|
||||
.writable(true)
|
||||
.enumerable(true)
|
||||
.configurable(true)
|
||||
.value(value.clone())
|
||||
.build()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function for converting from a dense storage type to sparse storage type.
|
||||
#[inline]
|
||||
fn convert_dense_to_sparse(vec: &mut Vec<JsValue>) -> FxHashMap<u32, PropertyDescriptor> {
|
||||
let data = std::mem::take(vec);
|
||||
|
||||
data.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, value)| {
|
||||
(
|
||||
index as u32,
|
||||
PropertyDescriptorBuilder::new()
|
||||
.writable(true)
|
||||
.enumerable(true)
|
||||
.configurable(true)
|
||||
.value(value)
|
||||
.build(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Inserts a property descriptor with the specified key.
|
||||
#[inline]
|
||||
fn insert(&mut self, key: u32, property: PropertyDescriptor) -> Option<PropertyDescriptor> {
|
||||
let vec = match self {
|
||||
Self::Sparse(map) => return map.insert(key, property),
|
||||
Self::Dense(vec) => {
|
||||
let len = vec.len() as u32;
|
||||
if key <= len
|
||||
&& property.value().is_some()
|
||||
&& property.writable().unwrap_or(false)
|
||||
&& property.enumerable().unwrap_or(false)
|
||||
&& property.configurable().unwrap_or(false)
|
||||
{
|
||||
// Fast Path: continues array access.
|
||||
|
||||
let mut value = property
|
||||
.value()
|
||||
.cloned()
|
||||
.expect("already checked that the property descriptor has a value field");
|
||||
|
||||
// If the key is pointing one past the last element, we push it!
|
||||
//
|
||||
// Since the previous key is the current key - 1. Meaning that the elements are continuos.
|
||||
if key == len {
|
||||
vec.push(value);
|
||||
return None;
|
||||
}
|
||||
|
||||
// If it the key points in at a already taken index, swap and return it.
|
||||
std::mem::swap(&mut vec[key as usize], &mut value);
|
||||
return Some(
|
||||
PropertyDescriptorBuilder::new()
|
||||
.writable(true)
|
||||
.enumerable(true)
|
||||
.configurable(true)
|
||||
.value(value)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
vec
|
||||
}
|
||||
};
|
||||
|
||||
// Slow path: converting to sparse storage.
|
||||
let mut map = Self::convert_dense_to_sparse(vec);
|
||||
let old_property = map.insert(key, property);
|
||||
*self = Self::Sparse(map);
|
||||
|
||||
old_property
|
||||
}
|
||||
|
||||
/// Inserts a property descriptor with the specified key.
|
||||
#[inline]
|
||||
fn remove(&mut self, key: u32) -> Option<PropertyDescriptor> {
|
||||
let vec = match self {
|
||||
Self::Sparse(map) => return map.remove(&key),
|
||||
Self::Dense(vec) => {
|
||||
// Fast Path: contiguous storage.
|
||||
|
||||
// Has no elements or out of range, nothing to delete!
|
||||
if vec.is_empty() || key as usize >= vec.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// If the key is pointing at the last element, then we pop it and return it.
|
||||
//
|
||||
// It does not make the storage sparse
|
||||
if key as usize == vec.len().wrapping_sub(1) {
|
||||
let value = vec.pop().expect("Already checked if it is out of bounds");
|
||||
return Some(
|
||||
PropertyDescriptorBuilder::new()
|
||||
.writable(true)
|
||||
.enumerable(true)
|
||||
.configurable(true)
|
||||
.value(value)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
vec
|
||||
}
|
||||
};
|
||||
|
||||
// Slow Path: conversion to sparse storage.
|
||||
let mut map = Self::convert_dense_to_sparse(vec);
|
||||
let old_property = map.remove(&key);
|
||||
*self = Self::Sparse(map);
|
||||
|
||||
old_property
|
||||
}
|
||||
|
||||
/// Check if we contain the key to a property descriptor.
|
||||
fn contains_key(&self, key: u32) -> bool {
|
||||
match self {
|
||||
Self::Sparse(map) => map.contains_key(&key),
|
||||
Self::Dense(vec) => (0..vec.len() as u32).contains(&key),
|
||||
}
|
||||
}
|
||||
|
||||
fn iter(&self) -> IndexProperties<'_> {
|
||||
match self {
|
||||
Self::Dense(vec) => IndexProperties::Dense(vec.iter().enumerate()),
|
||||
Self::Sparse(map) => IndexProperties::Sparse(map.iter()),
|
||||
}
|
||||
}
|
||||
|
||||
fn keys(&self) -> IndexPropertyKeys<'_> {
|
||||
match self {
|
||||
Self::Dense(vec) => IndexPropertyKeys::Dense(0..vec.len() as u32),
|
||||
Self::Sparse(map) => IndexPropertyKeys::Sparse(map.keys()),
|
||||
}
|
||||
}
|
||||
|
||||
fn values(&self) -> IndexPropertyValues<'_> {
|
||||
match self {
|
||||
Self::Dense(vec) => IndexPropertyValues::Dense(vec.iter()),
|
||||
Self::Sparse(map) => IndexPropertyValues::Sparse(map.values()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Trace, Finalize)]
|
||||
pub struct PropertyMap {
|
||||
indexed_properties: IndexedProperties,
|
||||
/// Properties
|
||||
string_properties: OrderedHashMap<JsString>,
|
||||
/// Symbol Properties
|
||||
symbol_properties: OrderedHashMap<JsSymbol>,
|
||||
}
|
||||
|
||||
impl PropertyMap {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &PropertyKey) -> Option<PropertyDescriptor> {
|
||||
match key {
|
||||
PropertyKey::Index(index) => self.indexed_properties.get(*index),
|
||||
PropertyKey::String(string) => self.string_properties.0.get(string).cloned(),
|
||||
PropertyKey::Symbol(symbol) => self.symbol_properties.0.get(symbol).cloned(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
key: &PropertyKey,
|
||||
property: PropertyDescriptor,
|
||||
) -> Option<PropertyDescriptor> {
|
||||
match &key {
|
||||
PropertyKey::Index(index) => self.indexed_properties.insert(*index, property),
|
||||
PropertyKey::String(string) => {
|
||||
self.string_properties.0.insert(string.clone(), property)
|
||||
}
|
||||
PropertyKey::Symbol(symbol) => {
|
||||
self.symbol_properties.0.insert(symbol.clone(), property)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: &PropertyKey) -> Option<PropertyDescriptor> {
|
||||
match key {
|
||||
PropertyKey::Index(index) => self.indexed_properties.remove(*index),
|
||||
PropertyKey::String(string) => self.string_properties.0.shift_remove(string),
|
||||
PropertyKey::Symbol(symbol) => self.symbol_properties.0.shift_remove(symbol),
|
||||
}
|
||||
}
|
||||
|
||||
/// Overrides all the indexed properties, setting it to dense storage.
|
||||
pub(crate) fn override_indexed_properties(&mut self, properties: Vec<JsValue>) {
|
||||
self.indexed_properties = IndexedProperties::Dense(properties);
|
||||
}
|
||||
|
||||
/// An iterator visiting all key-value pairs in arbitrary order. The iterator element type is `(PropertyKey, &'a Property)`.
|
||||
///
|
||||
/// This iterator does not recurse down the prototype chain.
|
||||
#[inline]
|
||||
pub fn iter(&self) -> Iter<'_> {
|
||||
Iter {
|
||||
indexed_properties: self.indexed_properties.iter(),
|
||||
string_properties: self.string_properties.0.iter(),
|
||||
symbol_properties: self.symbol_properties.0.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator visiting all keys in arbitrary order. The iterator element type is `PropertyKey`.
|
||||
///
|
||||
/// This iterator does not recurse down the prototype chain.
|
||||
#[inline]
|
||||
pub fn keys(&self) -> Keys<'_> {
|
||||
Keys(self.iter())
|
||||
}
|
||||
|
||||
/// An iterator visiting all values in arbitrary order. The iterator element type is `&'a Property`.
|
||||
///
|
||||
/// This iterator does not recurse down the prototype chain.
|
||||
#[inline]
|
||||
pub fn values(&self) -> Values<'_> {
|
||||
Values(self.iter())
|
||||
}
|
||||
|
||||
/// An iterator visiting all symbol key-value pairs in arbitrary order. The iterator element type is `(&'a RcSymbol, &'a Property)`.
|
||||
///
|
||||
///
|
||||
/// This iterator does not recurse down the prototype chain.
|
||||
#[inline]
|
||||
pub fn symbol_properties(&self) -> SymbolProperties<'_> {
|
||||
SymbolProperties(self.symbol_properties.0.iter())
|
||||
}
|
||||
|
||||
/// An iterator visiting all symbol keys in arbitrary order. The iterator element type is `&'a RcSymbol`.
|
||||
///
|
||||
/// This iterator does not recurse down the prototype chain.
|
||||
#[inline]
|
||||
pub fn symbol_property_keys(&self) -> SymbolPropertyKeys<'_> {
|
||||
SymbolPropertyKeys(self.symbol_properties.0.keys())
|
||||
}
|
||||
|
||||
/// An iterator visiting all symbol values in arbitrary order. The iterator element type is `&'a Property`.
|
||||
///
|
||||
/// This iterator does not recurse down the prototype chain.
|
||||
#[inline]
|
||||
pub fn symbol_property_values(&self) -> SymbolPropertyValues<'_> {
|
||||
SymbolPropertyValues(self.symbol_properties.0.values())
|
||||
}
|
||||
|
||||
/// An iterator visiting all indexed key-value pairs in arbitrary order. The iterator element type is `(&'a u32, &'a Property)`.
|
||||
///
|
||||
/// This iterator does not recurse down the prototype chain.
|
||||
#[inline]
|
||||
pub fn index_properties(&self) -> IndexProperties<'_> {
|
||||
self.indexed_properties.iter()
|
||||
}
|
||||
|
||||
/// An iterator visiting all index keys in arbitrary order. The iterator element type is `&'a u32`.
|
||||
///
|
||||
/// This iterator does not recurse down the prototype chain.
|
||||
#[inline]
|
||||
pub fn index_property_keys(&self) -> IndexPropertyKeys<'_> {
|
||||
self.indexed_properties.keys()
|
||||
}
|
||||
|
||||
/// An iterator visiting all index values in arbitrary order. The iterator element type is `&'a Property`.
|
||||
///
|
||||
/// This iterator does not recurse down the prototype chain.
|
||||
#[inline]
|
||||
pub fn index_property_values(&self) -> IndexPropertyValues<'_> {
|
||||
self.indexed_properties.values()
|
||||
}
|
||||
|
||||
/// An iterator visiting all string key-value pairs in arbitrary order. The iterator element type is `(&'a RcString, &'a Property)`.
|
||||
///
|
||||
/// This iterator does not recurse down the prototype chain.
|
||||
#[inline]
|
||||
pub fn string_properties(&self) -> StringProperties<'_> {
|
||||
StringProperties(self.string_properties.0.iter())
|
||||
}
|
||||
|
||||
/// An iterator visiting all string keys in arbitrary order. The iterator element type is `&'a RcString`.
|
||||
///
|
||||
/// This iterator does not recurse down the prototype chain.
|
||||
#[inline]
|
||||
pub fn string_property_keys(&self) -> StringPropertyKeys<'_> {
|
||||
StringPropertyKeys(self.string_properties.0.keys())
|
||||
}
|
||||
|
||||
/// An iterator visiting all string values in arbitrary order. The iterator element type is `&'a Property`.
|
||||
///
|
||||
/// This iterator does not recurse down the prototype chain.
|
||||
#[inline]
|
||||
pub fn string_property_values(&self) -> StringPropertyValues<'_> {
|
||||
StringPropertyValues(self.string_properties.0.values())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains_key(&self, key: &PropertyKey) -> bool {
|
||||
match key {
|
||||
PropertyKey::Index(index) => self.indexed_properties.contains_key(*index),
|
||||
PropertyKey::String(string) => self.string_properties.0.contains_key(string),
|
||||
PropertyKey::Symbol(symbol) => self.symbol_properties.0.contains_key(symbol),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn string_property_map(&self) -> &GlobalPropertyMap {
|
||||
&self.string_properties.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn string_property_map_mut(&mut self) -> &mut GlobalPropertyMap {
|
||||
&mut self.string_properties.0
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the property entries of an `Object`
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Iter<'a> {
|
||||
indexed_properties: IndexProperties<'a>,
|
||||
string_properties: indexmap::map::Iter<'a, JsString, PropertyDescriptor>,
|
||||
symbol_properties: indexmap::map::Iter<'a, JsSymbol, PropertyDescriptor>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Iter<'a> {
|
||||
type Item = (PropertyKey, PropertyDescriptor);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some((key, value)) = self.indexed_properties.next() {
|
||||
Some((key.into(), value))
|
||||
} else if let Some((key, value)) = self.string_properties.next() {
|
||||
Some((key.clone().into(), value.clone()))
|
||||
} else {
|
||||
let (key, value) = self.symbol_properties.next()?;
|
||||
Some((key.clone().into(), value.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for Iter<'_> {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.indexed_properties.len() + self.string_properties.len() + self.symbol_properties.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for Iter<'_> {}
|
||||
|
||||
/// An iterator over the keys (`PropertyKey`) of an `Object`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Keys<'a>(Iter<'a>);
|
||||
|
||||
impl<'a> Iterator for Keys<'a> {
|
||||
type Item = PropertyKey;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let (key, _) = self.0.next()?;
|
||||
Some(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for Keys<'_> {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for Keys<'_> {}
|
||||
|
||||
/// An iterator over the values (`Property`) of an `Object`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Values<'a>(Iter<'a>);
|
||||
|
||||
impl<'a> Iterator for Values<'a> {
|
||||
type Item = PropertyDescriptor;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let (_, value) = self.0.next()?;
|
||||
Some(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for Values<'_> {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for Values<'_> {}
|
||||
|
||||
/// An iterator over the `Symbol` property entries of an `Object`
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SymbolProperties<'a>(indexmap::map::Iter<'a, JsSymbol, PropertyDescriptor>);
|
||||
|
||||
impl<'a> Iterator for SymbolProperties<'a> {
|
||||
type Item = (&'a JsSymbol, &'a PropertyDescriptor);
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.0.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for SymbolProperties<'_> {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for SymbolProperties<'_> {}
|
||||
|
||||
/// An iterator over the keys (`RcSymbol`) of an `Object`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SymbolPropertyKeys<'a>(indexmap::map::Keys<'a, JsSymbol, PropertyDescriptor>);
|
||||
|
||||
impl<'a> Iterator for SymbolPropertyKeys<'a> {
|
||||
type Item = &'a JsSymbol;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.0.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for SymbolPropertyKeys<'_> {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for SymbolPropertyKeys<'_> {}
|
||||
|
||||
/// An iterator over the `Symbol` values (`Property`) of an `Object`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SymbolPropertyValues<'a>(indexmap::map::Values<'a, JsSymbol, PropertyDescriptor>);
|
||||
|
||||
impl<'a> Iterator for SymbolPropertyValues<'a> {
|
||||
type Item = &'a PropertyDescriptor;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.0.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for SymbolPropertyValues<'_> {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for SymbolPropertyValues<'_> {}
|
||||
|
||||
/// An iterator over the indexed property entries of an `Object`
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IndexProperties<'a> {
|
||||
Dense(std::iter::Enumerate<std::slice::Iter<'a, JsValue>>),
|
||||
Sparse(hash_map::Iter<'a, u32, PropertyDescriptor>),
|
||||
}
|
||||
|
||||
impl<'a> Iterator for IndexProperties<'a> {
|
||||
type Item = (u32, PropertyDescriptor);
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
Self::Dense(vec) => vec.next().map(|(index, value)| {
|
||||
(
|
||||
index as u32,
|
||||
PropertyDescriptorBuilder::new()
|
||||
.writable(true)
|
||||
.configurable(true)
|
||||
.enumerable(true)
|
||||
.value(value.clone())
|
||||
.build(),
|
||||
)
|
||||
}),
|
||||
Self::Sparse(map) => map.next().map(|(index, value)| (*index, value.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match self {
|
||||
Self::Dense(vec) => vec.size_hint(),
|
||||
Self::Sparse(map) => map.size_hint(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for IndexProperties<'_> {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
match self {
|
||||
Self::Dense(vec) => vec.len(),
|
||||
Self::Sparse(map) => map.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for IndexProperties<'_> {}
|
||||
|
||||
/// An iterator over the index keys (`u32`) of an `Object`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IndexPropertyKeys<'a> {
|
||||
Dense(std::ops::Range<u32>),
|
||||
Sparse(hash_map::Keys<'a, u32, PropertyDescriptor>),
|
||||
}
|
||||
|
||||
impl<'a> Iterator for IndexPropertyKeys<'a> {
|
||||
type Item = u32;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
Self::Dense(vec) => vec.next(),
|
||||
Self::Sparse(map) => map.next().copied(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match self {
|
||||
Self::Dense(vec) => vec.size_hint(),
|
||||
Self::Sparse(map) => map.size_hint(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for IndexPropertyKeys<'_> {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
match self {
|
||||
Self::Dense(vec) => vec.len(),
|
||||
Self::Sparse(map) => map.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for IndexPropertyKeys<'_> {}
|
||||
|
||||
/// An iterator over the index values (`Property`) of an `Object`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IndexPropertyValues<'a> {
|
||||
Dense(std::slice::Iter<'a, JsValue>),
|
||||
Sparse(hash_map::Values<'a, u32, PropertyDescriptor>),
|
||||
}
|
||||
|
||||
impl<'a> Iterator for IndexPropertyValues<'a> {
|
||||
type Item = PropertyDescriptor;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
Self::Dense(vec) => vec.next().map(|value| {
|
||||
PropertyDescriptorBuilder::new()
|
||||
.writable(true)
|
||||
.configurable(true)
|
||||
.enumerable(true)
|
||||
.value(value.clone())
|
||||
.build()
|
||||
}),
|
||||
Self::Sparse(map) => map.next().cloned(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match self {
|
||||
Self::Dense(vec) => vec.size_hint(),
|
||||
Self::Sparse(map) => map.size_hint(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for IndexPropertyValues<'_> {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
match self {
|
||||
Self::Dense(vec) => vec.len(),
|
||||
Self::Sparse(map) => map.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for IndexPropertyValues<'_> {}
|
||||
|
||||
/// An iterator over the `String` property entries of an `Object`
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StringProperties<'a>(indexmap::map::Iter<'a, JsString, PropertyDescriptor>);
|
||||
|
||||
impl<'a> Iterator for StringProperties<'a> {
|
||||
type Item = (&'a JsString, &'a PropertyDescriptor);
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.0.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for StringProperties<'_> {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for StringProperties<'_> {}
|
||||
|
||||
/// An iterator over the string keys (`RcString`) of an `Object`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StringPropertyKeys<'a>(indexmap::map::Keys<'a, JsString, PropertyDescriptor>);
|
||||
|
||||
impl<'a> Iterator for StringPropertyKeys<'a> {
|
||||
type Item = &'a JsString;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.0.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for StringPropertyKeys<'_> {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for StringPropertyKeys<'_> {}
|
||||
|
||||
/// An iterator over the string values (`Property`) of an `Object`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StringPropertyValues<'a>(indexmap::map::Values<'a, JsString, PropertyDescriptor>);
|
||||
|
||||
impl<'a> Iterator for StringPropertyValues<'a> {
|
||||
type Item = &'a PropertyDescriptor;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.0.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for StringPropertyValues<'_> {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for StringPropertyValues<'_> {}
|
||||
@@ -0,0 +1,46 @@
|
||||
use crate::{check_output, exec, TestAction};
|
||||
|
||||
#[test]
|
||||
fn ordinary_has_instance_nonobject_prototype() {
|
||||
let scenario = r#"
|
||||
try {
|
||||
function C() {}
|
||||
C.prototype = 1
|
||||
String instanceof C
|
||||
} catch (err) {
|
||||
err.toString()
|
||||
}
|
||||
"#;
|
||||
|
||||
assert_eq!(
|
||||
&exec(scenario),
|
||||
"\"TypeError: function has non-object prototype in instanceof check\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_properties_return_order() {
|
||||
let scenario = r#"
|
||||
var o = {
|
||||
p1: 'v1',
|
||||
p2: 'v2',
|
||||
p3: 'v3',
|
||||
};
|
||||
o.p4 = 'v4';
|
||||
o[2] = 'iv2';
|
||||
o[0] = 'iv0';
|
||||
o[1] = 'iv1';
|
||||
delete o.p1;
|
||||
delete o.p3;
|
||||
o.p1 = 'v1';
|
||||
"#;
|
||||
|
||||
check_output(&[
|
||||
TestAction::Execute(scenario),
|
||||
TestAction::TestEq("Object.keys(o)", r#"[ "0", "1", "2", "p2", "p4", "p1" ]"#),
|
||||
TestAction::TestEq(
|
||||
"Object.values(o)",
|
||||
r#"[ "iv0", "iv1", "iv2", "v2", "v4", "v1" ]"#,
|
||||
),
|
||||
]);
|
||||
}
|
||||
Reference in New Issue
Block a user