use crate::{
builtins::BuiltInObject,
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError,
native_function::NativeFunction,
object::{FunctionObjectBuilder, JsFunction, JsObject, ObjectData},
realm::Realm,
string::utf16,
Context, JsArgs, JsResult, JsValue,
};
use boa_gc::{Finalize, GcRefCell, Trace};
use boa_profiler::Profiler;
use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject};
#[derive(Debug, Clone, Trace, Finalize)]
pub struct Proxy {
data: Option<(JsObject, JsObject)>,
}
impl IntrinsicObject for Proxy {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(Self::NAME, "init");
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.static_method(Self::revocable, "revocable", 2)
.build_without_prototype();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
}
}
impl BuiltInObject for Proxy {
const NAME: &'static str = "Proxy";
}
impl BuiltInConstructor for Proxy {
const LENGTH: usize = 2;
const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
StandardConstructors::proxy;
fn constructor(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
if new_target.is_undefined() {
return Err(JsNativeError::typ()
.with_message("Proxy constructor called on undefined new target")
.into());
}
Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context).map(JsValue::from)
}
}
impl Proxy {
pub(crate) fn new(target: JsObject, handler: JsObject) -> Self {
Self {
data: Some((target, handler)),
}
}
pub(crate) fn try_data(&self) -> JsResult<(JsObject, JsObject)> {
self.data.clone().ok_or_else(|| {
JsNativeError::typ()
.with_message("Proxy object has empty handler and target")
.into()
})
}
pub(crate) fn create(
target: &JsValue,
handler: &JsValue,
context: &mut Context<'_>,
) -> JsResult<JsObject> {
let target = target.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("Proxy constructor called with non-object target")
})?;
let handler = handler.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("Proxy constructor called with non-object handler")
})?;
let p = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
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 {
FunctionObjectBuilder::new(
context,
NativeFunction::from_copy_closure_with_captures(
|_, _, revocable_proxy, _| {
if let Some(p) = std::mem::take(&mut *revocable_proxy.borrow_mut()) {
p.borrow_mut()
.as_proxy_mut()
.expect("[[RevocableProxy]] must be a proxy object")
.data = None;
}
Ok(JsValue::undefined())
},
GcRefCell::new(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 = JsObject::with_object_proto(context.intrinsics());
result
.create_data_property_or_throw(utf16!("proxy"), p, context)
.expect("CreateDataPropertyOrThrow cannot fail here");
result
.create_data_property_or_throw(utf16!("revoke"), revoker, context)
.expect("CreateDataPropertyOrThrow cannot fail here");
Ok(result.into())
}
}