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 {
#[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)
}
#[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)
}
#[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)
}
#[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)
}
#[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)
}
#[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)
}
#[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)
}
#[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)
}
#[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)
}
#[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)
}
#[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)
}
#[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,
)
}
#[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,
)
}
}
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,
};
#[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>>,
}
#[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");
Ok(obj.prototype().as_ref().cloned())
}
#[inline]
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn ordinary_set_prototype_of(
obj: &JsObject,
val: JsPrototype,
_: &mut Context,
) -> JsResult<bool> {
{
let current = obj.prototype();
if val == *current {
return Ok(true);
}
}
if !obj.extensible() {
return Ok(false);
}
let mut p = val.clone();
while let Some(proto) = p {
if &proto == obj {
return Ok(false);
}
else if proto.borrow().data.internal_methods.__get_prototype_of__ as usize
!= ordinary_get_prototype_of as usize
{
break;
}
p = proto.prototype().clone();
}
obj.set_prototype(val);
Ok(true)
}
#[inline]
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn ordinary_is_extensible(obj: &JsObject, _context: &mut Context) -> JsResult<bool> {
Ok(obj.borrow().extensible)
}
#[inline]
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn ordinary_prevent_extensions(
obj: &JsObject,
_context: &mut Context,
) -> JsResult<bool> {
obj.borrow_mut().extensible = false;
Ok(true)
}
#[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");
Ok(obj.borrow().properties.get(key))
}
#[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");
let current = obj.__get_own_property__(&key, context)?;
let extensible = obj.__is_extensible__(context)?;
Ok(validate_and_apply_property_descriptor(
Some((obj, key)),
extensible,
desc,
current,
))
}
#[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");
if obj.__get_own_property__(key, context)?.is_some() {
Ok(true)
} else {
let parent = obj.__get_prototype_of__(context)?;
parent
.map_or(Ok(false), |obj| obj.__has_property__(key, context))
}
}
#[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");
match obj.__get_own_property__(key, context)? {
None => {
if let Some(parent) = obj.__get_prototype_of__(context)? {
parent.__get__(key, receiver, context)
}
else {
Ok(JsValue::undefined())
}
}
Some(ref desc) => match desc.kind() {
DescriptorKind::Data {
value: Some(value), ..
} => Ok(value.clone()),
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
context.call(get, &receiver, &[])
}
_ => Ok(JsValue::undefined()),
},
}
}
#[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");
let own_desc = if let Some(desc) = obj.__get_own_property__(&key, context)? {
desc
}
else if let Some(parent) = obj.__get_prototype_of__(context)? {
return parent.__set__(key, value, receiver, context);
}
else {
PropertyDescriptor::builder()
.value(JsValue::undefined())
.writable(true)
.enumerable(true)
.configurable(true)
.build()
};
if own_desc.is_data_descriptor() {
if !own_desc.expect_writable() {
return Ok(false);
}
let receiver = match receiver.as_object() {
Some(obj) => obj,
_ => return Ok(false),
};
if let Some(ref existing_desc) = receiver.__get_own_property__(&key, context)? {
if existing_desc.is_accessor_descriptor() {
return Ok(false);
}
if !existing_desc.expect_writable() {
return Ok(false);
}
return receiver.__define_own_property__(
key,
PropertyDescriptor::builder().value(value).build(),
context,
);
}
return receiver.create_data_property(key, value, context);
}
debug_assert!(own_desc.is_accessor_descriptor());
match own_desc.set() {
Some(set) if !set.is_undefined() => {
context.call(set, &receiver, &[value])?;
Ok(true)
}
_ => Ok(false),
}
}
#[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");
Ok(
match obj.__get_own_property__(key, context)? {
Some(desc) if desc.expect_configurable() => {
obj.borrow_mut().remove(key);
true
}
Some(_) => false,
None => true,
},
)
}
#[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");
let mut keys = Vec::new();
let ordered_indexes = {
let mut indexes: Vec<_> = obj.borrow().properties.index_property_keys().collect();
indexes.sort_unstable();
indexes
};
keys.extend(ordered_indexes.into_iter().map(Into::into));
keys.extend(
obj.borrow()
.properties
.string_property_keys()
.cloned()
.map(Into::into),
);
keys.extend(
obj.borrow()
.properties
.symbol_property_keys()
.cloned()
.map(Into::into),
);
Ok(keys)
}
#[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");
validate_and_apply_property_descriptor(None, extensible, desc, current)
}
#[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");
let mut current = if let Some(own) = current {
own
}
else {
if !extensible {
return false;
}
if let Some((obj, key)) = obj_and_key {
obj.borrow_mut().properties.insert(
&key,
if desc.is_generic_descriptor() || desc.is_data_descriptor() {
desc.into_data_defaulted()
}
else {
desc.into_accessor_defaulted()
},
);
}
return true;
};
if desc.is_empty() {
return true;
}
if !current.expect_configurable() {
if matches!(desc.configurable(), Some(true)) {
return false;
}
if matches!(desc.enumerable(), Some(desc_enum) if desc_enum != current.expect_enumerable())
{
return false;
}
}
if desc.is_generic_descriptor() {
}
else if current.is_data_descriptor() != desc.is_data_descriptor() {
if !current.expect_configurable() {
return false;
}
if obj_and_key.is_some() {
if current.is_data_descriptor() {
current = current.into_accessor_defaulted();
}
else {
current = current.into_data_defaulted();
}
}
}
else if current.is_data_descriptor() && desc.is_data_descriptor() {
if !current.expect_configurable() && !current.expect_writable() {
if matches!(desc.writable(), Some(true)) {
return false;
}
if matches!(desc.value(), Some(value) if !JsValue::same_value(value, current.expect_value()))
{
return false;
}
return true;
}
}
else if !current.expect_configurable() {
if matches!(desc.set(), Some(set) if !JsValue::same_value(set, current.expect_set())) {
return false;
}
if matches!(desc.get(), Some(get) if !JsValue::same_value(get, current.expect_get())) {
return false;
}
return true;
}
if let Some((obj, key)) = obj_and_key {
current.fill_with(&desc);
obj.borrow_mut().properties.insert(&key, current);
}
true
}
#[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");
if let Some(object) = constructor.as_object() {
if let Some(proto) = object.get(PROTOTYPE, context)?.as_object() {
return Ok(proto.clone());
}
}
Ok(default(context.intrinsics().constructors()).prototype())
}