use crate::{
ecmascript::{
Agent, ArgumentsList, BUILTIN_STRING_MEMORY, Behaviour, Builtin, ExceptionType,
InternalMethods, JsResult, Object, PropertyDescriptor, Realm, String, Value,
builders::OrdinaryObjectBuilder, call_function, construct, create_array_from_list,
create_list_from_array_like, is_callable, is_constructor, to_property_key_complex,
to_property_key_simple, try_result_into_js,
},
engine::{Bindable, GcScope, Scopable},
heap::WellKnownSymbols,
};
pub(crate) struct ReflectObject;
struct ReflectObjectApply;
impl Builtin for ReflectObjectApply {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.apply;
const LENGTH: u8 = 3;
const BEHAVIOUR: Behaviour = Behaviour::Regular(ReflectObject::apply);
}
struct ReflectObjectConstruct;
impl Builtin for ReflectObjectConstruct {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.construct;
const LENGTH: u8 = 2;
const BEHAVIOUR: Behaviour = Behaviour::Regular(ReflectObject::construct);
}
struct ReflectObjectDefineProperty;
impl Builtin for ReflectObjectDefineProperty {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.defineProperty;
const LENGTH: u8 = 2;
const BEHAVIOUR: Behaviour = Behaviour::Regular(ReflectObject::define_property);
}
struct ReflectObjectDeleteProperty;
impl Builtin for ReflectObjectDeleteProperty {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.deleteProperty;
const LENGTH: u8 = 2;
const BEHAVIOUR: Behaviour = Behaviour::Regular(ReflectObject::delete_property);
}
struct ReflectObjectGet;
impl Builtin for ReflectObjectGet {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.get;
const LENGTH: u8 = 2;
const BEHAVIOUR: Behaviour = Behaviour::Regular(ReflectObject::get);
}
struct ReflectObjectGetOwnPropertyDescriptor;
impl Builtin for ReflectObjectGetOwnPropertyDescriptor {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.getOwnPropertyDescriptor;
const LENGTH: u8 = 2;
const BEHAVIOUR: Behaviour = Behaviour::Regular(ReflectObject::get_own_property_descriptor);
}
struct ReflectObjectGetPrototypeOf;
impl Builtin for ReflectObjectGetPrototypeOf {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.getPrototypeOf;
const LENGTH: u8 = 1;
const BEHAVIOUR: Behaviour = Behaviour::Regular(ReflectObject::get_prototype_of);
}
struct ReflectObjectHas;
impl Builtin for ReflectObjectHas {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.has;
const LENGTH: u8 = 2;
const BEHAVIOUR: Behaviour = Behaviour::Regular(ReflectObject::has);
}
struct ReflectObjectIsExtensible;
impl Builtin for ReflectObjectIsExtensible {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.isExtensible;
const LENGTH: u8 = 1;
const BEHAVIOUR: Behaviour = Behaviour::Regular(ReflectObject::is_extensible);
}
struct ReflectObjectOwnKeys;
impl Builtin for ReflectObjectOwnKeys {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.ownKeys;
const LENGTH: u8 = 1;
const BEHAVIOUR: Behaviour = Behaviour::Regular(ReflectObject::own_keys);
}
struct ReflectObjectPreventExtensions;
impl Builtin for ReflectObjectPreventExtensions {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.preventExtensions;
const LENGTH: u8 = 1;
const BEHAVIOUR: Behaviour = Behaviour::Regular(ReflectObject::prevent_extensions);
}
struct ReflectObjectSet;
impl Builtin for ReflectObjectSet {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.set;
const LENGTH: u8 = 3;
const BEHAVIOUR: Behaviour = Behaviour::Regular(ReflectObject::set);
}
struct ReflectObjectSetPrototypeOf;
impl Builtin for ReflectObjectSetPrototypeOf {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.setPrototypeOf;
const LENGTH: u8 = 2;
const BEHAVIOUR: Behaviour = Behaviour::Regular(ReflectObject::set_prototype_of);
}
impl ReflectObject {
fn apply<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let nogc = gc.nogc();
let target = arguments.get(0).bind(nogc);
let this_argument = arguments.get(1).bind(nogc);
let arguments_list = arguments.get(2).bind(nogc);
let Some(target) = is_callable(target, nogc) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Value is not callable",
gc.into_nogc(),
));
};
let target = target.scope(agent, nogc);
let this_argument = this_argument.scope(agent, nogc);
let args = create_list_from_array_like(agent, arguments_list.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
call_function(
agent,
target.get(agent),
this_argument.get(agent),
Some(ArgumentsList::from_mut_slice(&mut args.unbind())),
gc,
)
}
fn construct<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let nogc = gc.nogc();
let target = arguments.get(0).bind(nogc);
let arguments_list = arguments.get(1).bind(nogc);
let Some(target) = is_constructor(agent, target) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Value is not a constructor",
gc.into_nogc(),
));
};
let new_target = if arguments.len() > 2 {
let new_target = arguments.get(2).bind(nogc);
let Some(new_target) = is_constructor(agent, new_target) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Value is not a constructor",
gc.into_nogc(),
));
};
new_target
} else {
target
};
let target = target.scope(agent, nogc);
let new_target = new_target.scope(agent, nogc);
let args = create_list_from_array_like(agent, arguments_list.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
construct(
agent,
target.get(agent),
Some(ArgumentsList::from_mut_slice(&mut args.unbind())),
Some(new_target.get(agent)),
gc,
)
.map(|o| o.into())
}
fn define_property<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let nogc = gc.nogc();
let target = arguments.get(0).bind(nogc);
let property_key = arguments.get(1).bind(nogc);
let mut attributes = arguments.get(2).bind(nogc);
let Ok(target) = Object::try_from(target) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Value is not an object",
gc.into_nogc(),
));
};
let mut target = target.bind(nogc);
let mut scoped_target = None;
let mut key = if let Some(key) = to_property_key_simple(agent, property_key, nogc) {
key
} else {
scoped_target = Some(target.scope(agent, nogc));
let scoped_attributes = attributes.scope(agent, nogc);
let key = to_property_key_complex(agent, property_key.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
target = scoped_target.as_ref().unwrap().get(agent);
attributes = scoped_attributes.get(agent);
key
};
let desc = if let Some(desc) = try_result_into_js(
PropertyDescriptor::try_to_property_descriptor(agent, attributes, gc.nogc()),
)
.unbind()?
.bind(gc.nogc())
{
desc
} else {
if scoped_target.is_none() {
scoped_target = Some(target.scope(agent, gc.nogc()));
}
let scoped_key = key.scope(agent, gc.nogc());
let desc = PropertyDescriptor::to_property_descriptor(
agent,
attributes.unbind(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
key = scoped_key.get(agent).bind(gc.nogc());
target = scoped_target.unwrap().get(agent).bind(gc.nogc());
desc
};
let ret =
target
.unbind()
.internal_define_own_property(agent, key.unbind(), desc.unbind(), gc)?;
Ok(ret.into())
}
fn delete_property<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let nogc = gc.nogc();
let target = arguments.get(0).bind(nogc);
let property_key = arguments.get(1).bind(nogc);
let Ok(mut target) = Object::try_from(target) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Value is not an object",
gc.into_nogc(),
));
};
let key = if let Some(key) = to_property_key_simple(agent, property_key, nogc) {
key
} else {
let scoped_target = target.scope(agent, nogc);
let key = to_property_key_complex(agent, property_key.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
target = scoped_target.get(agent);
key
};
let ret = target
.unbind()
.internal_delete(agent, key.unbind(), gc.reborrow())
.unbind()?;
Ok(ret.into())
}
fn get<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let nogc = gc.nogc();
let target = arguments.get(0).bind(nogc);
let property_key = arguments.get(1).bind(nogc);
let mut receiver = if arguments.len() > 2 {
Some(arguments.get(2).bind(nogc))
} else {
None
};
let Ok(target) = Object::try_from(target) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Value is not an object",
gc.into_nogc(),
));
};
let mut target = target.bind(nogc);
let key = if let Some(key) = to_property_key_simple(agent, property_key, nogc) {
key
} else {
let scoped_target = target.scope(agent, nogc);
let scoped_receiver = receiver.map(|receiver| receiver.scope(agent, nogc));
let key = to_property_key_complex(agent, property_key.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
target = scoped_target.get(agent).bind(gc.nogc());
receiver = scoped_receiver.map(|scoped_receiver| scoped_receiver.get(agent));
key
};
let receiver = receiver.unwrap_or(target.into());
target
.unbind()
.internal_get(agent, key.unbind(), receiver.unbind(), gc)
}
fn get_own_property_descriptor<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let nogc = gc.nogc();
let target = arguments.get(0).bind(nogc);
let property_key = arguments.get(1).bind(nogc);
let Ok(target) = Object::try_from(target) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Value is not an object",
gc.into_nogc(),
));
};
let mut target = target.bind(nogc);
let key = if let Some(key) = to_property_key_simple(agent, property_key, nogc) {
key
} else {
let scoped_target = target.scope(agent, nogc);
let key = to_property_key_complex(agent, property_key.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
target = scoped_target.get(agent).bind(gc.nogc());
key
};
let desc = target
.unbind()
.internal_get_own_property(agent, key.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
match PropertyDescriptor::from_property_descriptor(desc.unbind(), agent, gc.into_nogc()) {
Some(ret) => Ok(ret.into()),
None => Ok(Value::Undefined),
}
}
fn get_prototype_of<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let nogc = gc.nogc();
let target = arguments.get(0).bind(nogc);
let Ok(target) = Object::try_from(target) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Value is not an object",
gc.into_nogc(),
));
};
match target.unbind().internal_get_prototype_of(agent, gc)? {
Some(ret) => Ok(ret.into()),
None => Ok(Value::Null),
}
}
fn has<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let nogc = gc.nogc();
let target = arguments.get(0).bind(nogc);
let property_key = arguments.get(1).bind(nogc);
let Ok(target) = Object::try_from(target) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Value is not an object",
gc.into_nogc(),
));
};
let mut target = target.bind(nogc);
let key = if let Some(key) = to_property_key_simple(agent, property_key, nogc) {
key
} else {
let scoped_target = target.scope(agent, nogc);
let key = to_property_key_complex(agent, property_key.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
target = scoped_target.get(agent).bind(gc.nogc());
key
};
let ret = target
.unbind()
.internal_has_property(agent, key.unbind(), gc)?;
Ok(ret.into())
}
fn is_extensible<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let nogc = gc.nogc();
let target = arguments.get(0).bind(nogc);
let Ok(target) = Object::try_from(target) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Value is not an object",
gc.into_nogc(),
));
};
let ret = target.unbind().internal_is_extensible(agent, gc)?;
Ok(ret.into())
}
fn own_keys<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let nogc = gc.nogc();
let target = arguments.get(0).bind(nogc);
let Ok(target) = Object::try_from(target) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Value is not an object",
gc.into_nogc(),
));
};
let keys: Vec<Value> = target
.unbind()
.internal_own_property_keys(agent, gc.reborrow())
.unbind()?
.into_iter()
.map(|key| key.convert_to_value(agent, gc.nogc()).into())
.collect();
Ok(create_array_from_list(agent, &keys.unbind(), gc.into_nogc()).into())
}
fn prevent_extensions<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let nogc = gc.nogc();
let target = arguments.get(0).bind(nogc);
let Ok(target) = Object::try_from(target) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Value is not an object",
gc.into_nogc(),
));
};
let ret = target.unbind().internal_prevent_extensions(agent, gc)?;
Ok(ret.into())
}
fn set<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let nogc = gc.nogc();
let target = arguments.get(0).bind(nogc);
let property_key = arguments.get(1).bind(nogc);
let mut v = arguments.get(2).bind(nogc);
let mut receiver = if arguments.len() > 3 {
Some(arguments.get(3).bind(nogc))
} else {
None
};
let Ok(mut target) = Object::try_from(target) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Value is not an object",
gc.into_nogc(),
));
};
let key = if let Some(key) = to_property_key_simple(agent, property_key, nogc) {
key
} else {
let scoped_target = target.scope(agent, nogc);
let scoped_v = v.scope(agent, nogc);
let scoped_receiver = receiver.map(|receiver| receiver.scope(agent, nogc));
let key = to_property_key_complex(agent, property_key.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
target = scoped_target.get(agent);
v = scoped_v.get(agent);
receiver = scoped_receiver.map(|scoped_receiver| scoped_receiver.get(agent));
key
};
let receiver = receiver.unwrap_or(target.into());
let ret =
target
.unbind()
.internal_set(agent, key.unbind(), v.unbind(), receiver.unbind(), gc)?;
Ok(ret.into())
}
fn set_prototype_of<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let nogc = gc.nogc();
let target = arguments.get(0).bind(nogc);
let proto = arguments.get(1).bind(nogc);
let Ok(target) = Object::try_from(target) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Value is not an object",
gc.into_nogc(),
));
};
let proto = if let Ok(proto) = Object::try_from(proto) {
Some(proto)
} else if proto.is_null() {
None
} else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Prototype must be an object or null",
gc.into_nogc(),
));
};
let ret = target
.unbind()
.internal_set_prototype_of(agent, proto.unbind(), gc)?;
Ok(ret.into())
}
pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>) {
let intrinsics = agent.get_realm_record_by_id(realm).intrinsics();
let object_prototype = intrinsics.object_prototype();
let this = intrinsics.reflect();
OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this)
.with_property_capacity(14)
.with_prototype(object_prototype)
.with_builtin_function_property::<ReflectObjectApply>()
.with_builtin_function_property::<ReflectObjectConstruct>()
.with_builtin_function_property::<ReflectObjectDefineProperty>()
.with_builtin_function_property::<ReflectObjectDeleteProperty>()
.with_builtin_function_property::<ReflectObjectGet>()
.with_builtin_function_property::<ReflectObjectGetOwnPropertyDescriptor>()
.with_builtin_function_property::<ReflectObjectGetPrototypeOf>()
.with_builtin_function_property::<ReflectObjectHas>()
.with_builtin_function_property::<ReflectObjectIsExtensible>()
.with_builtin_function_property::<ReflectObjectOwnKeys>()
.with_builtin_function_property::<ReflectObjectPreventExtensions>()
.with_builtin_function_property::<ReflectObjectSet>()
.with_builtin_function_property::<ReflectObjectSetPrototypeOf>()
.with_property(|builder| {
builder
.with_key(WellKnownSymbols::ToStringTag.into())
.with_value_readonly(BUILTIN_STRING_MEMORY.Reflect.into())
.with_enumerable(false)
.with_configurable(true)
.build()
})
.build();
}
}