use crate::{
error::JsNativeError,
object::JsObject,
property::{PropertyDescriptor, PropertyKey},
string::utf16,
Context, JsResult,
};
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
pub(crate) static ARRAY_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
__define_own_property__: array_exotic_define_own_property,
..ORDINARY_INTERNAL_METHODS
};
pub(crate) fn array_exotic_define_own_property(
obj: &JsObject,
key: &PropertyKey,
desc: PropertyDescriptor,
context: &mut Context<'_>,
) -> JsResult<bool> {
match *key {
PropertyKey::String(ref s) if s == utf16!("length") => {
array_set_length(obj, desc, context)
}
PropertyKey::Index(index) if index < u32::MAX => {
let old_len_desc =
super::ordinary_get_own_property(obj, &utf16!("length").into(), context)?
.expect("the property descriptor must exist");
debug_assert!(old_len_desc.is_data_descriptor());
debug_assert!(!old_len_desc.expect_configurable());
let old_len = old_len_desc
.expect_value()
.to_u32(context)
.expect("this ToUint32 call must not fail");
if index >= old_len && !old_len_desc.expect_writable() {
return Ok(false);
}
if super::ordinary_define_own_property(obj, key, desc, context)? {
if index >= old_len {
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());
let succeeded = super::ordinary_define_own_property(
obj,
&utf16!("length").into(),
old_len_desc.into(),
context,
)?;
debug_assert!(succeeded);
}
Ok(true)
} else {
Ok(false)
}
}
_ => super::ordinary_define_own_property(obj, key, desc, context),
}
}
fn array_set_length(
obj: &JsObject,
desc: PropertyDescriptor,
context: &mut Context<'_>,
) -> JsResult<bool> {
let Some(new_len_val) = desc.value() else {
return super::ordinary_define_own_property(obj, &utf16!("length").into(), desc, context);
};
let new_len = new_len_val.to_u32(context)?;
let number_len = new_len_val.to_number(context)?;
#[allow(clippy::float_cmp)]
if f64::from(new_len) != number_len {
return Err(JsNativeError::range()
.with_message("bad length for array")
.into());
}
let mut new_len_desc = PropertyDescriptor::builder()
.value(new_len)
.maybe_writable(desc.writable())
.maybe_enumerable(desc.enumerable())
.maybe_configurable(desc.configurable());
let old_len_desc = super::ordinary_get_own_property(obj, &utf16!("length").into(), context)?
.expect("the property descriptor must exist");
debug_assert!(old_len_desc.is_data_descriptor());
debug_assert!(!old_len_desc.expect_configurable());
let old_len = old_len_desc.expect_value();
if new_len >= old_len.to_u32(context)? {
return super::ordinary_define_own_property(
obj,
&utf16!("length").into(),
new_len_desc.build(),
context,
);
}
if !old_len_desc.expect_writable() {
return Ok(false);
}
let new_writable = if new_len_desc.inner().writable().unwrap_or(true) {
true
}
else {
new_len_desc = new_len_desc.writable(true);
false
};
if !super::ordinary_define_own_property(
obj,
&utf16!("length").into(),
new_len_desc.clone().build(),
context,
)
.expect("this OrdinaryDefineOwnProperty call must not fail")
{
return Ok(false);
}
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 {
if !obj.__delete__(&index.into(), context)? {
new_len_desc = new_len_desc.value(index + 1);
if !new_writable {
new_len_desc = new_len_desc.writable(false);
}
super::ordinary_define_own_property(
obj,
&utf16!("length").into(),
new_len_desc.build(),
context,
)
.expect("this OrdinaryDefineOwnProperty call must not fail");
return Ok(false);
}
}
if !new_writable {
let succeeded = super::ordinary_define_own_property(
obj,
&utf16!("length").into(),
PropertyDescriptor::builder().writable(false).build(),
context,
)
.expect("this OrdinaryDefineOwnProperty call must not fail");
debug_assert!(succeeded);
}
Ok(true)
}