feat: works

This commit is contained in:
2022-07-17 10:11:20 +08:00
parent 4ba63b4c2e
commit 74a202f1ed
458 changed files with 125067 additions and 8 deletions

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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(())
}

View File

@@ -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())
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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 {}

View File

@@ -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 {}

View File

@@ -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 {}

View File

@@ -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 {}

View File

@@ -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 {}

View File

@@ -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("{ ... }")
}
}
}

View File

@@ -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 }
}
}

View File

@@ -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 {}

View File

@@ -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 {}

View File

@@ -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);

File diff suppressed because it is too large Load Diff

View File

@@ -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);
}
}
}
}

View File

@@ -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<'_> {}

View File

@@ -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" ]"#,
),
]);
}