use crate::{
builtins::{array, object::Object},
object::{InternalObjectMethods, JsObject, JsPrototype},
property::{PropertyDescriptor, PropertyKey},
value::Type,
Context, JsResult, JsValue,
};
use rustc_hash::FxHashSet;
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
};
pub(crate) fn proxy_exotic_get_prototype_of(
obj: &JsObject,
context: &mut Context,
) -> JsResult<JsPrototype> {
let (target, handler) = obj
.borrow()
.as_proxy()
.expect("Proxy object internal internal method called on non-proxy object")
.try_data(context)?;
let trap = if let Some(trap) = handler.get_method("getPrototypeOf", context)? {
trap
} else {
return target.__get_prototype_of__(context);
};
let handler_proto = trap.call(&handler.into(), &[target.clone().into()], context)?;
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"),
};
if target.is_extensible(context)? {
return Ok(handler_proto);
}
let target_proto = target.__get_prototype_of__(context)?;
if handler_proto != target_proto {
return context.throw_type_error("Proxy trap returned unexpected prototype");
}
Ok(handler_proto)
}
#[inline]
pub(crate) fn proxy_exotic_set_prototype_of(
obj: &JsObject,
val: JsPrototype,
context: &mut Context,
) -> JsResult<bool> {
let (target, handler) = obj
.borrow()
.as_proxy()
.expect("Proxy object internal internal method called on non-proxy object")
.try_data(context)?;
let trap = if let Some(trap) = handler.get_method("setPrototypeOf", context)? {
trap
} else {
return target.__set_prototype_of__(val, context);
};
if !trap
.call(
&handler.into(),
&[
target.clone().into(),
val.clone().map_or(JsValue::Null, Into::into),
],
context,
)?
.to_boolean()
{
return Ok(false);
}
if target.is_extensible(context)? {
return Ok(true);
}
let target_proto = target.__get_prototype_of__(context)?;
if val != target_proto {
return context.throw_type_error("Proxy trap failed to set prototype");
}
Ok(true)
}
#[inline]
pub(crate) fn proxy_exotic_is_extensible(obj: &JsObject, context: &mut Context) -> JsResult<bool> {
let (target, handler) = obj
.borrow()
.as_proxy()
.expect("Proxy object internal internal method called on non-proxy object")
.try_data(context)?;
let trap = if let Some(trap) = handler.get_method("isExtensible", context)? {
trap
} else {
return target.is_extensible(context);
};
let boolean_trap_result = trap
.call(&handler.into(), &[target.clone().into()], context)?
.to_boolean();
let target_result = target.is_extensible(context)?;
if boolean_trap_result != target_result {
return context.throw_type_error("Proxy trap returned unexpected extensible value");
}
Ok(boolean_trap_result)
}
#[inline]
pub(crate) fn proxy_exotic_prevent_extensions(
obj: &JsObject,
context: &mut Context,
) -> JsResult<bool> {
let (target, handler) = obj
.borrow()
.as_proxy()
.expect("Proxy object internal internal method called on non-proxy object")
.try_data(context)?;
let trap = if let Some(trap) = handler.get_method("preventExtensions", context)? {
trap
} else {
return target.__prevent_extensions__(context);
};
let boolean_trap_result = trap
.call(&handler.into(), &[target.clone().into()], context)?
.to_boolean();
if boolean_trap_result && target.is_extensible(context)? {
return context.throw_type_error("Proxy trap failed to set extensible");
}
Ok(boolean_trap_result)
}
#[inline]
pub(crate) fn proxy_exotic_get_own_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
) -> JsResult<Option<PropertyDescriptor>> {
let (target, handler) = obj
.borrow()
.as_proxy()
.expect("Proxy object internal internal method called on non-proxy object")
.try_data(context)?;
let trap = if let Some(trap) = handler.get_method("getOwnPropertyDescriptor", context)? {
trap
} else {
return target.__get_own_property__(key, context);
};
let trap_result_obj = trap.call(
&handler.into(),
&[target.clone().into(), key.clone().into()],
context,
)?;
if !trap_result_obj.is_object() && !trap_result_obj.is_undefined() {
return context.throw_type_error("Proxy trap result is neither object nor undefined");
}
let target_desc = target.__get_own_property__(key, context)?;
if trap_result_obj.is_undefined() {
if let Some(desc) = target_desc {
if !desc.expect_configurable() {
return context.throw_type_error(
"Proxy trap result is undefined adn target result is not configurable",
);
}
if !target.is_extensible(context)? {
return context.throw_type_error(
"Proxy trap result is undefined and target is not extensible",
);
}
return Ok(None);
}
return Ok(None);
}
let extensible_target = target.is_extensible(context)?;
let result_desc = trap_result_obj.to_property_descriptor(context)?;
let result_desc = result_desc.complete_property_descriptor();
if !super::is_compatible_property_descriptor(
extensible_target,
result_desc.clone(),
target_desc.clone(),
) {
return context.throw_type_error("Proxy trap returned unexpected property");
}
if !result_desc.expect_configurable() {
match &target_desc {
Some(desc) if !desc.expect_configurable() => {
if let Some(false) = result_desc.writable() {
if desc.expect_writable() {
return
context.throw_type_error("Proxy trap result is writable and not configurable while target result is not configurable")
;
}
}
}
_ => {
return context.throw_type_error(
"Proxy trap result is not configurable and target result is undefined",
)
}
}
}
Ok(Some(result_desc))
}
#[inline]
pub(crate) fn proxy_exotic_define_own_property(
obj: &JsObject,
key: PropertyKey,
desc: PropertyDescriptor,
context: &mut Context,
) -> JsResult<bool> {
let (target, handler) = obj
.borrow()
.as_proxy()
.expect("Proxy object internal internal method called on non-proxy object")
.try_data(context)?;
let trap = if let Some(trap) = handler.get_method("defineProperty", context)? {
trap
} else {
return target.__define_own_property__(key, desc, context);
};
let desc_obj = Object::from_property_descriptor(Some(desc.clone()), context);
if !trap
.call(
&handler.into(),
&[target.clone().into(), key.clone().into(), desc_obj],
context,
)?
.to_boolean()
{
return Ok(false);
}
let target_desc = target.__get_own_property__(&key, context)?;
let extensible_target = target.is_extensible(context)?;
let setting_config_false = matches!(desc.configurable(), Some(false));
match target_desc {
None => {
if !extensible_target {
return context.throw_type_error("Proxy trap failed to set property");
}
if setting_config_false {
return context.throw_type_error("Proxy trap failed to set property");
}
}
Some(target_desc) => {
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");
}
if setting_config_false && target_desc.expect_configurable() {
return context.throw_type_error(
"Proxy trap set property with unexpected configurable field",
);
}
if target_desc.is_data_descriptor()
&& !target_desc.expect_configurable()
&& target_desc.expect_writable()
{
if let Some(writable) = desc.writable() {
if !writable {
return context.throw_type_error(
"Proxy trap set property with unexpected writable field",
);
}
}
}
}
}
Ok(true)
}
#[inline]
pub(crate) fn proxy_exotic_has_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
) -> JsResult<bool> {
let (target, handler) = obj
.borrow()
.as_proxy()
.expect("Proxy object internal internal method called on non-proxy object")
.try_data(context)?;
let trap = if let Some(trap) = handler.get_method("has", context)? {
trap
} else {
return target.has_property(key.clone(), context);
};
let boolean_trap_result = trap
.call(
&handler.into(),
&[target.clone().into(), key.clone().into()],
context,
)?
.to_boolean();
if !boolean_trap_result {
let target_desc = target.__get_own_property__(key, context)?;
if let Some(target_desc) = target_desc {
if !target_desc.expect_configurable() {
return context.throw_type_error("Proxy trap returned unexpected property");
}
if !target.is_extensible(context)? {
return context.throw_type_error("Proxy trap returned unexpected property");
}
}
}
Ok(boolean_trap_result)
}
#[inline]
pub(crate) fn proxy_exotic_get(
obj: &JsObject,
key: &PropertyKey,
receiver: JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
let (target, handler) = obj
.borrow()
.as_proxy()
.expect("Proxy object internal internal method called on non-proxy object")
.try_data(context)?;
let trap = if let Some(trap) = handler.get_method("get", context)? {
trap
} else {
return target.__get__(key, receiver, context);
};
let trap_result = trap.call(
&handler.into(),
&[target.clone().into(), key.clone().into(), receiver],
context,
)?;
let target_desc = target.__get_own_property__(key, context)?;
if let Some(target_desc) = target_desc {
if !target_desc.expect_configurable() {
if target_desc.is_data_descriptor() && !target_desc.expect_writable() {
if !JsValue::same_value(&trap_result, target_desc.expect_value()) {
return context
.throw_type_error("Proxy trap returned unexpected data descriptor");
}
}
if target_desc.is_accessor_descriptor() && target_desc.expect_get().is_undefined() {
if !trap_result.is_undefined() {
return context
.throw_type_error("Proxy trap returned unexpected accessor descriptor");
}
}
}
}
Ok(trap_result)
}
#[inline]
pub(crate) fn proxy_exotic_set(
obj: &JsObject,
key: PropertyKey,
value: JsValue,
receiver: JsValue,
context: &mut Context,
) -> JsResult<bool> {
let (target, handler) = obj
.borrow()
.as_proxy()
.expect("Proxy object internal internal method called on non-proxy object")
.try_data(context)?;
let trap = if let Some(trap) = handler.get_method("set", context)? {
trap
} else {
return target.__set__(key, value, receiver, context);
};
if !trap
.call(
&handler.into(),
&[target.clone().into(), value.clone(), receiver],
context,
)?
.to_boolean()
{
return Ok(false);
}
let target_desc = target.__get_own_property__(&key, context)?;
if let Some(target_desc) = target_desc {
if !target_desc.expect_configurable() {
if target_desc.is_data_descriptor() && !target_desc.expect_writable() {
if !JsValue::same_value(&value, target_desc.expect_value()) {
return context.throw_type_error("Proxy trap set unexpected data descriptor");
}
}
if target_desc.is_accessor_descriptor() {
match target_desc.set() {
None | Some(&JsValue::Undefined) => {
return context
.throw_type_error("Proxy trap set unexpected accessor descriptor");
}
_ => {}
}
}
}
}
Ok(true)
}
#[inline]
pub(crate) fn proxy_exotic_delete(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
) -> JsResult<bool> {
let (target, handler) = obj
.borrow()
.as_proxy()
.expect("Proxy object internal internal method called on non-proxy object")
.try_data(context)?;
let trap = if let Some(trap) = handler.get_method("deleteProperty", context)? {
trap
} else {
return target.__delete__(key, context);
};
if !trap
.call(
&handler.into(),
&[target.clone().into(), key.clone().into()],
context,
)?
.to_boolean()
{
return Ok(false);
}
match target.__get_own_property__(key, context)? {
None => return Ok(true),
Some(target_desc) => {
if !target_desc.expect_configurable() {
return context.throw_type_error("Proxy trap failed to delete property");
}
}
}
if !target.is_extensible(context)? {
return context.throw_type_error("Proxy trap failed to delete property");
}
Ok(true)
}
#[inline]
pub(crate) fn proxy_exotic_own_property_keys(
obj: &JsObject,
context: &mut Context,
) -> JsResult<Vec<PropertyKey>> {
let (target, handler) = obj
.borrow()
.as_proxy()
.expect("Proxy object internal internal method called on non-proxy object")
.try_data(context)?;
let trap = if let Some(trap) = handler.get_method("ownKeys", context)? {
trap
} else {
return target.__own_property_keys__(context);
};
let trap_result_array = trap.call(&handler.into(), &[target.clone().into()], context)?;
let trap_result_raw =
trap_result_array.create_list_from_array_like(&[Type::String, Type::Symbol], context)?;
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());
}
_ => {}
}
}
let extensible_target = target.is_extensible(context)?;
let target_keys = target.__own_property_keys__(context)?;
let mut target_configurable_keys = Vec::new();
let mut target_nonconfigurable_keys = Vec::new();
for key in target_keys {
match target.__get_own_property__(&key, context)? {
Some(desc) if !desc.expect_configurable() => {
target_nonconfigurable_keys.push(key);
}
_ => {
target_configurable_keys.push(key);
}
}
}
if extensible_target && target_nonconfigurable_keys.is_empty() {
return Ok(trap_result);
}
for key in target_nonconfigurable_keys {
if !unchecked_result_keys.remove(&key) {
return context.throw_type_error(
"Proxy trap failed to return all non-configurable property keys",
);
}
}
if extensible_target {
return Ok(trap_result);
}
for key in target_configurable_keys {
if !unchecked_result_keys.remove(&key) {
return context
.throw_type_error("Proxy trap failed to return all configurable property keys");
}
}
if !unchecked_result_keys.is_empty() {
return context.throw_type_error("Proxy trap failed to return all property keys");
}
Ok(trap_result)
}
fn proxy_exotic_call(
obj: &JsObject,
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let (target, handler) = obj
.borrow()
.as_proxy()
.expect("Proxy object internal internal method called on non-proxy object")
.try_data(context)?;
let trap = if let Some(trap) = handler.get_method("apply", context)? {
trap
} else {
return target.call(this, args, context);
};
let arg_array = array::Array::create_array_from_list(args.to_vec(), context);
trap.call(
&handler.into(),
&[target.clone().into(), this.clone(), arg_array.into()],
context,
)
}
fn proxy_exotic_construct(
obj: &JsObject,
args: &[JsValue],
new_target: &JsObject,
context: &mut Context,
) -> JsResult<JsObject> {
let (target, handler) = obj
.borrow()
.as_proxy()
.expect("Proxy object internal internal method called on non-proxy object")
.try_data(context)?;
assert!(target.is_constructor());
let trap = if let Some(trap) = handler.get_method("construct", context)? {
trap
} else {
return target.construct(args, Some(new_target), context);
};
let arg_array = array::Array::create_array_from_list(args.to_vec(), context);
let new_obj = trap.call(
&handler.into(),
&[
target.clone().into(),
arg_array.into(),
new_target.clone().into(),
],
context,
)?;
let new_obj = new_obj.as_object().cloned().ok_or_else(|| {
context.construct_type_error("Proxy trap constructor returned non-object value")
})?;
Ok(new_obj)
}