use crate::{
builtins::{BuiltIn, JsArgs},
object::{ConstructorBuilder, FunctionBuilder, JsFunction, JsObject, ObjectData},
Context, JsResult, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
use tap::{Conv, Pipe};
#[derive(Debug, Clone, Trace, Finalize)]
pub struct Proxy {
data: Option<(JsObject, JsObject)>,
}
impl BuiltIn for Proxy {
const NAME: &'static str = "Proxy";
fn init(context: &mut Context) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init");
ConstructorBuilder::with_standard_constructor(
context,
Self::constructor,
context.intrinsics().constructors().proxy().clone(),
)
.name(Self::NAME)
.length(Self::LENGTH)
.has_prototype_property(false)
.static_method(Self::revocable, "revocable", 2)
.build()
.conv::<JsValue>()
.pipe(Some)
}
}
impl Proxy {
const LENGTH: usize = 2;
pub(crate) fn new(target: JsObject, handler: JsObject) -> Self {
Self {
data: Some((target, handler)),
}
}
pub(crate) fn try_data(&self, context: &mut Context) -> JsResult<(JsObject, JsObject)> {
self.data.clone().ok_or_else(|| {
context.construct_type_error("Proxy object has empty handler and target")
})
}
pub(crate) fn constructor(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
if new_target.is_undefined() {
return context.throw_type_error("Proxy constructor called on undefined new target");
}
Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context).map(JsValue::from)
}
pub(crate) fn create(
target: &JsValue,
handler: &JsValue,
context: &mut Context,
) -> JsResult<JsObject> {
let target = target.as_object().ok_or_else(|| {
context.construct_type_error("Proxy constructor called with non-object target")
})?;
let handler = handler.as_object().ok_or_else(|| {
context.construct_type_error("Proxy constructor called with non-object handler")
})?;
let p = JsObject::from_proto_and_data(
context.intrinsics().constructors().object().prototype(),
ObjectData::proxy(
Self::new(target.clone(), handler.clone()),
target.is_callable(),
target.is_constructor(),
),
);
Ok(p)
}
pub(crate) fn revoker(proxy: JsObject, context: &mut Context) -> JsFunction {
FunctionBuilder::closure_with_captures(
context,
|_, _, revocable_proxy, _| {
if let Some(p) = revocable_proxy.take() {
p.borrow_mut()
.as_proxy_mut()
.expect("[[RevocableProxy]] must be a proxy object")
.data = None;
}
Ok(JsValue::undefined())
},
Some(proxy),
)
.build()
}
fn revocable(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let p = Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context)?;
let revoker = Self::revoker(p.clone(), context);
let result = context.construct_object();
result
.create_data_property_or_throw("proxy", p, context)
.expect("CreateDataPropertyOrThrow cannot fail here");
result
.create_data_property_or_throw("revoke", revoker, context)
.expect("CreateDataPropertyOrThrow cannot fail here");
Ok(result.into())
}
}