use boa_gc::{Finalize, Trace};
use crate::{
builtins::Proxy,
native_function::{NativeFunction, NativeFunctionPointer},
object::{FunctionObjectBuilder, JsObject, JsObjectType, ObjectData},
string::utf16,
value::TryFromJs,
Context, JsNativeError, JsResult, JsValue,
};
use super::JsFunction;
#[derive(Debug, Clone, Trace, Finalize)]
pub struct JsProxy {
inner: JsObject,
}
impl JsProxy {
pub fn builder(target: JsObject) -> JsProxyBuilder {
JsProxyBuilder::new(target)
}
#[inline]
pub fn from_object(object: JsObject) -> JsResult<Self> {
if object.borrow().is_proxy() {
Ok(Self { inner: object })
} else {
Err(JsNativeError::typ()
.with_message("object is not a Proxy")
.into())
}
}
}
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 {}
impl TryFromJs for JsProxy {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::Object(o) => Self::from_object(o.clone()),
_ => Err(JsNativeError::typ()
.with_message("value is not a Proxy object")
.into()),
}
}
}
#[derive(Debug, Trace, Finalize)]
pub struct JsRevocableProxy {
proxy: JsProxy,
revoker: JsFunction,
}
impl JsRevocableProxy {
#[inline]
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
}
}
#[must_use]
#[derive(Clone)]
pub struct JsProxyBuilder {
target: JsObject,
apply: Option<NativeFunctionPointer>,
construct: Option<NativeFunctionPointer>,
define_property: Option<NativeFunctionPointer>,
delete_property: Option<NativeFunctionPointer>,
get: Option<NativeFunctionPointer>,
get_own_property_descriptor: Option<NativeFunctionPointer>,
get_prototype_of: Option<NativeFunctionPointer>,
has: Option<NativeFunctionPointer>,
is_extensible: Option<NativeFunctionPointer>,
own_keys: Option<NativeFunctionPointer>,
prevent_extensions: Option<NativeFunctionPointer>,
set: Option<NativeFunctionPointer>,
set_prototype_of: Option<NativeFunctionPointer>,
}
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 {
#[inline]
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,
}
}
#[inline]
pub fn apply(mut self, apply: NativeFunctionPointer) -> Self {
self.apply = Some(apply);
self
}
#[inline]
pub fn construct(mut self, construct: NativeFunctionPointer) -> Self {
self.construct = Some(construct);
self
}
#[inline]
pub fn define_property(mut self, define_property: NativeFunctionPointer) -> Self {
self.define_property = Some(define_property);
self
}
#[inline]
pub fn delete_property(mut self, delete_property: NativeFunctionPointer) -> Self {
self.delete_property = Some(delete_property);
self
}
#[inline]
pub fn get(mut self, get: NativeFunctionPointer) -> Self {
self.get = Some(get);
self
}
#[inline]
pub fn get_own_property_descriptor(
mut self,
get_own_property_descriptor: NativeFunctionPointer,
) -> Self {
self.get_own_property_descriptor = Some(get_own_property_descriptor);
self
}
#[inline]
pub fn get_prototype_of(mut self, get_prototype_of: NativeFunctionPointer) -> Self {
self.get_prototype_of = Some(get_prototype_of);
self
}
#[inline]
pub fn has(mut self, has: NativeFunctionPointer) -> Self {
self.has = Some(has);
self
}
#[inline]
pub fn is_extensible(mut self, is_extensible: NativeFunctionPointer) -> Self {
self.is_extensible = Some(is_extensible);
self
}
#[inline]
pub fn own_keys(mut self, own_keys: NativeFunctionPointer) -> Self {
self.own_keys = Some(own_keys);
self
}
#[inline]
pub fn prevent_extensions(mut self, prevent_extensions: NativeFunctionPointer) -> Self {
self.prevent_extensions = Some(prevent_extensions);
self
}
#[inline]
pub fn set(mut self, set: NativeFunctionPointer) -> Self {
self.set = Some(set);
self
}
#[inline]
pub fn set_prototype_of(mut self, set_prototype_of: NativeFunctionPointer) -> Self {
self.set_prototype_of = Some(set_prototype_of);
self
}
#[must_use]
pub fn build(self, context: &mut Context<'_>) -> JsProxy {
let handler = JsObject::with_object_proto(context.intrinsics());
if let Some(apply) = self.apply {
let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(apply))
.length(3)
.build();
handler
.create_data_property_or_throw(utf16!("apply"), f, context)
.expect("new object should be writable");
}
if let Some(construct) = self.construct {
let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(construct))
.length(3)
.build();
handler
.create_data_property_or_throw(utf16!("construct"), f, context)
.expect("new object should be writable");
}
if let Some(define_property) = self.define_property {
let f =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(define_property))
.length(3)
.build();
handler
.create_data_property_or_throw(utf16!("defineProperty"), f, context)
.expect("new object should be writable");
}
if let Some(delete_property) = self.delete_property {
let f =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(delete_property))
.length(2)
.build();
handler
.create_data_property_or_throw(utf16!("deleteProperty"), f, context)
.expect("new object should be writable");
}
if let Some(get) = self.get {
let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(get))
.length(3)
.build();
handler
.create_data_property_or_throw(utf16!("get"), f, context)
.expect("new object should be writable");
}
if let Some(get_own_property_descriptor) = self.get_own_property_descriptor {
let f = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(get_own_property_descriptor),
)
.length(2)
.build();
handler
.create_data_property_or_throw(utf16!("getOwnPropertyDescriptor"), f, context)
.expect("new object should be writable");
}
if let Some(get_prototype_of) = self.get_prototype_of {
let f =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(get_prototype_of))
.length(1)
.build();
handler
.create_data_property_or_throw(utf16!("getPrototypeOf"), f, context)
.expect("new object should be writable");
}
if let Some(has) = self.has {
let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(has))
.length(2)
.build();
handler
.create_data_property_or_throw(utf16!("has"), f, context)
.expect("new object should be writable");
}
if let Some(is_extensible) = self.is_extensible {
let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(is_extensible))
.length(1)
.build();
handler
.create_data_property_or_throw(utf16!("isExtensible"), f, context)
.expect("new object should be writable");
}
if let Some(own_keys) = self.own_keys {
let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(own_keys))
.length(1)
.build();
handler
.create_data_property_or_throw(utf16!("ownKeys"), f, context)
.expect("new object should be writable");
}
if let Some(prevent_extensions) = self.prevent_extensions {
let f = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(prevent_extensions),
)
.length(1)
.build();
handler
.create_data_property_or_throw(utf16!("preventExtensions"), f, context)
.expect("new object should be writable");
}
if let Some(set) = self.set {
let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(set))
.length(4)
.build();
handler
.create_data_property_or_throw(utf16!("set"), f, context)
.expect("new object should be writable");
}
if let Some(set_prototype_of) = self.set_prototype_of {
let f =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(set_prototype_of))
.length(2)
.build();
handler
.create_data_property_or_throw(utf16!("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_with_shared_shape(
context.root_shape(),
context.intrinsics().constructors().object().prototype(),
ObjectData::proxy(Proxy::new(self.target, handler), callable, constructor),
);
JsProxy { inner: proxy }
}
#[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 }
}
}