mirror of https://github.com/boa-dev/boa.git
Browse Source
This Pull Request fixes/closes #2075. It changes the following: - Implements a `ProxyBuilder` struct to be able to create `Proxy` objects from native function traps. - Documents `ProxyBuilder` and adjusts some documentation. - Fixes a clippy lint.pull/2084/head
jedel1043
3 years ago
7 changed files with 527 additions and 62 deletions
@ -0,0 +1,477 @@ |
|||||||
|
use boa_gc::{Finalize, Trace}; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
builtins::{function::NativeFunctionSignature, Proxy}, |
||||||
|
Context, JsResult, JsValue, |
||||||
|
}; |
||||||
|
|
||||||
|
use super::{FunctionBuilder, JsFunction, JsObject, JsObjectType, ObjectData}; |
||||||
|
|
||||||
|
/// JavaScript [`Proxy`][proxy] rust object.
|
||||||
|
///
|
||||||
|
/// This is a wrapper type for the [`Proxy`][proxy] API that allows customizing
|
||||||
|
/// essential behaviour for an object, like [property accesses][get] or the
|
||||||
|
/// [`delete`][delete] operator.
|
||||||
|
///
|
||||||
|
/// The only way to construct this type is to use the [`JsProxyBuilder`] type; also
|
||||||
|
/// accessible from [`JsProxy::builder`].
|
||||||
|
///
|
||||||
|
/// [get]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get
|
||||||
|
/// [delete]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/deleteProperty
|
||||||
|
/// [proxy]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
|
||||||
|
#[derive(Debug, Clone, Trace, Finalize)] |
||||||
|
pub struct JsProxy { |
||||||
|
inner: JsObject, |
||||||
|
} |
||||||
|
|
||||||
|
impl JsProxy { |
||||||
|
pub fn builder(target: JsObject) -> JsProxyBuilder { |
||||||
|
JsProxyBuilder::new(target) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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 {} |
||||||
|
|
||||||
|
/// JavaScript [`Proxy`][proxy] rust object that can be disabled.
|
||||||
|
///
|
||||||
|
/// Safe interface for the [`Proxy.revocable`][revocable] method that creates a
|
||||||
|
/// proxy that can be disabled using the [`JsRevocableProxy::revoke`] method.
|
||||||
|
/// The internal proxy is accessible using the [`Deref`][`std::ops::Deref`] operator.
|
||||||
|
///
|
||||||
|
/// The only way to construct this type is to use the [`JsProxyBuilder`] type; also
|
||||||
|
/// accessible from [`JsProxy::builder`]; with the [`JsProxyBuilder::build_revocable`]
|
||||||
|
/// method.
|
||||||
|
///
|
||||||
|
/// [revocable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/revocable
|
||||||
|
#[derive(Debug, Trace, Finalize)] |
||||||
|
pub struct JsRevocableProxy { |
||||||
|
proxy: JsProxy, |
||||||
|
revoker: JsFunction, |
||||||
|
} |
||||||
|
|
||||||
|
impl JsRevocableProxy { |
||||||
|
/// Disables the traps of the internal `proxy` object, essentially
|
||||||
|
/// making it unusable and throwing `TypeError`s for all the traps.
|
||||||
|
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 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Utility builder to create [`JsProxy`] objects from native functions.
|
||||||
|
///
|
||||||
|
/// This builder can be used when you need to create [`Proxy`] objects
|
||||||
|
/// from Rust instead of Javascript, which should generate faster
|
||||||
|
/// trap functions than its Javascript counterparts.
|
||||||
|
#[must_use] |
||||||
|
#[derive(Clone)] |
||||||
|
pub struct JsProxyBuilder { |
||||||
|
target: JsObject, |
||||||
|
apply: Option<NativeFunctionSignature>, |
||||||
|
construct: Option<NativeFunctionSignature>, |
||||||
|
define_property: Option<NativeFunctionSignature>, |
||||||
|
delete_property: Option<NativeFunctionSignature>, |
||||||
|
get: Option<NativeFunctionSignature>, |
||||||
|
get_own_property_descriptor: Option<NativeFunctionSignature>, |
||||||
|
get_prototype_of: Option<NativeFunctionSignature>, |
||||||
|
has: Option<NativeFunctionSignature>, |
||||||
|
is_extensible: Option<NativeFunctionSignature>, |
||||||
|
own_keys: Option<NativeFunctionSignature>, |
||||||
|
prevent_extensions: Option<NativeFunctionSignature>, |
||||||
|
set: Option<NativeFunctionSignature>, |
||||||
|
set_prototype_of: Option<NativeFunctionSignature>, |
||||||
|
} |
||||||
|
|
||||||
|
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 { |
||||||
|
/// Create a new `ProxyBuilder` with every trap set to `undefined`.
|
||||||
|
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, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Set the `apply` proxy trap to the specified native function.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
///
|
||||||
|
/// - [MDN documentation][mdn]
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// If the `target` object is not a function, then `apply` will be ignored
|
||||||
|
/// when trying to call the proxy, which will throw a type error.
|
||||||
|
///
|
||||||
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/apply
|
||||||
|
pub fn apply(mut self, apply: NativeFunctionSignature) -> Self { |
||||||
|
self.apply = Some(apply); |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Set the `construct` proxy trap to the specified native function.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
///
|
||||||
|
/// - [MDN documentation][mdn]
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// If the `target` object is not a constructor, then `construct` will be ignored
|
||||||
|
/// when trying to construct an object using the proxy, which will throw a type error.
|
||||||
|
///
|
||||||
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/construct
|
||||||
|
pub fn construct(mut self, construct: NativeFunctionSignature) -> Self { |
||||||
|
self.construct = Some(construct); |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Set the `defineProperty` proxy trap to the specified native function.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
///
|
||||||
|
/// - [MDN documentation][mdn]
|
||||||
|
///
|
||||||
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/defineProperty
|
||||||
|
pub fn define_property(mut self, define_property: NativeFunctionSignature) -> Self { |
||||||
|
self.define_property = Some(define_property); |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Set the `deleteProperty` proxy trap to the specified native function.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
///
|
||||||
|
/// - [MDN documentation][mdn]
|
||||||
|
///
|
||||||
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/deleteProperty
|
||||||
|
pub fn delete_property(mut self, delete_property: NativeFunctionSignature) -> Self { |
||||||
|
self.delete_property = Some(delete_property); |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Set the `get` proxy trap to the specified native function.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
///
|
||||||
|
/// - [MDN documentation][mdn]
|
||||||
|
///
|
||||||
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get
|
||||||
|
pub fn get(mut self, get: NativeFunctionSignature) -> Self { |
||||||
|
self.get = Some(get); |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Set the `getOwnPropertyDescriptor` proxy trap to the specified native function.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
///
|
||||||
|
/// - [MDN documentation][mdn]
|
||||||
|
///
|
||||||
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getOwnPropertyDescriptor
|
||||||
|
pub fn get_own_property_descriptor( |
||||||
|
mut self, |
||||||
|
get_own_property_descriptor: NativeFunctionSignature, |
||||||
|
) -> Self { |
||||||
|
self.get_own_property_descriptor = Some(get_own_property_descriptor); |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Set the `getPrototypeOf` proxy trap to the specified native function.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
///
|
||||||
|
/// - [MDN documentation][mdn]
|
||||||
|
///
|
||||||
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getPrototypeOf
|
||||||
|
pub fn get_prototype_of(mut self, get_prototype_of: NativeFunctionSignature) -> Self { |
||||||
|
self.get_prototype_of = Some(get_prototype_of); |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Set the `has` proxy trap to the specified native function.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
///
|
||||||
|
/// - [MDN documentation][mdn]
|
||||||
|
///
|
||||||
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/has
|
||||||
|
pub fn has(mut self, has: NativeFunctionSignature) -> Self { |
||||||
|
self.has = Some(has); |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Set the `isExtensible` proxy trap to the specified native function.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
///
|
||||||
|
/// - [MDN documentation][mdn]
|
||||||
|
///
|
||||||
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/isExtensible
|
||||||
|
pub fn is_extensible(mut self, is_extensible: NativeFunctionSignature) -> Self { |
||||||
|
self.is_extensible = Some(is_extensible); |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Set the `ownKeys` proxy trap to the specified native function.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
///
|
||||||
|
/// - [MDN documentation][mdn]
|
||||||
|
///
|
||||||
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys
|
||||||
|
pub fn own_keys(mut self, own_keys: NativeFunctionSignature) -> Self { |
||||||
|
self.own_keys = Some(own_keys); |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Set the `preventExtensions` proxy trap to the specified native function.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
///
|
||||||
|
/// - [MDN documentation][mdn]
|
||||||
|
///
|
||||||
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/preventExtensions
|
||||||
|
pub fn prevent_extensions(mut self, prevent_extensions: NativeFunctionSignature) -> Self { |
||||||
|
self.prevent_extensions = Some(prevent_extensions); |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Set the `set` proxy trap to the specified native function.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
///
|
||||||
|
/// - [MDN documentation][mdn]
|
||||||
|
///
|
||||||
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set
|
||||||
|
pub fn set(mut self, set: NativeFunctionSignature) -> Self { |
||||||
|
self.set = Some(set); |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Set the `setPrototypeOf` proxy trap to the specified native function.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
///
|
||||||
|
/// - [MDN documentation][mdn]
|
||||||
|
///
|
||||||
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/setPrototypeOf
|
||||||
|
pub fn set_prototype_of(mut self, set_prototype_of: NativeFunctionSignature) -> Self { |
||||||
|
self.set_prototype_of = Some(set_prototype_of); |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
/// Build a [`JsObject`] of kind [`Proxy`].
|
||||||
|
///
|
||||||
|
/// Equivalent to the `Proxy ( target, handler )` constructor, but returns a
|
||||||
|
/// [`JsObject`] in case there's a need to manipulate the returned object
|
||||||
|
/// inside Rust code.
|
||||||
|
#[must_use] |
||||||
|
pub fn build(self, context: &mut Context) -> JsProxy { |
||||||
|
let handler = context.construct_object(); |
||||||
|
|
||||||
|
if let Some(apply) = self.apply { |
||||||
|
let f = FunctionBuilder::native(context, apply).length(3).build(); |
||||||
|
handler |
||||||
|
.create_data_property_or_throw("apply", f, context) |
||||||
|
.expect("new object should be writable"); |
||||||
|
} |
||||||
|
if let Some(construct) = self.construct { |
||||||
|
let f = FunctionBuilder::native(context, construct) |
||||||
|
.length(3) |
||||||
|
.build(); |
||||||
|
handler |
||||||
|
.create_data_property_or_throw("construct", f, context) |
||||||
|
.expect("new object should be writable"); |
||||||
|
} |
||||||
|
if let Some(define_property) = self.define_property { |
||||||
|
let f = FunctionBuilder::native(context, define_property) |
||||||
|
.length(3) |
||||||
|
.build(); |
||||||
|
handler |
||||||
|
.create_data_property_or_throw("defineProperty", f, context) |
||||||
|
.expect("new object should be writable"); |
||||||
|
} |
||||||
|
if let Some(delete_property) = self.delete_property { |
||||||
|
let f = FunctionBuilder::native(context, delete_property) |
||||||
|
.length(2) |
||||||
|
.build(); |
||||||
|
handler |
||||||
|
.create_data_property_or_throw("deleteProperty", f, context) |
||||||
|
.expect("new object should be writable"); |
||||||
|
} |
||||||
|
if let Some(get) = self.get { |
||||||
|
let f = FunctionBuilder::native(context, get).length(3).build(); |
||||||
|
handler |
||||||
|
.create_data_property_or_throw("get", f, context) |
||||||
|
.expect("new object should be writable"); |
||||||
|
} |
||||||
|
if let Some(get_own_property_descriptor) = self.get_own_property_descriptor { |
||||||
|
let f = FunctionBuilder::native(context, get_own_property_descriptor) |
||||||
|
.length(2) |
||||||
|
.build(); |
||||||
|
handler |
||||||
|
.create_data_property_or_throw("getOwnPropertyDescriptor", f, context) |
||||||
|
.expect("new object should be writable"); |
||||||
|
} |
||||||
|
if let Some(get_prototype_of) = self.get_prototype_of { |
||||||
|
let f = FunctionBuilder::native(context, get_prototype_of) |
||||||
|
.length(1) |
||||||
|
.build(); |
||||||
|
handler |
||||||
|
.create_data_property_or_throw("getPrototypeOf", f, context) |
||||||
|
.expect("new object should be writable"); |
||||||
|
} |
||||||
|
if let Some(has) = self.has { |
||||||
|
let f = FunctionBuilder::native(context, has).length(2).build(); |
||||||
|
handler |
||||||
|
.create_data_property_or_throw("has", f, context) |
||||||
|
.expect("new object should be writable"); |
||||||
|
} |
||||||
|
if let Some(is_extensible) = self.is_extensible { |
||||||
|
let f = FunctionBuilder::native(context, is_extensible) |
||||||
|
.length(1) |
||||||
|
.build(); |
||||||
|
handler |
||||||
|
.create_data_property_or_throw("isExtensible", f, context) |
||||||
|
.expect("new object should be writable"); |
||||||
|
} |
||||||
|
if let Some(own_keys) = self.own_keys { |
||||||
|
let f = FunctionBuilder::native(context, own_keys).length(1).build(); |
||||||
|
handler |
||||||
|
.create_data_property_or_throw("ownKeys", f, context) |
||||||
|
.expect("new object should be writable"); |
||||||
|
} |
||||||
|
if let Some(prevent_extensions) = self.prevent_extensions { |
||||||
|
let f = FunctionBuilder::native(context, prevent_extensions) |
||||||
|
.length(1) |
||||||
|
.build(); |
||||||
|
handler |
||||||
|
.create_data_property_or_throw("preventExtensions", f, context) |
||||||
|
.expect("new object should be writable"); |
||||||
|
} |
||||||
|
if let Some(set) = self.set { |
||||||
|
let f = FunctionBuilder::native(context, set).length(4).build(); |
||||||
|
handler |
||||||
|
.create_data_property_or_throw("set", f, context) |
||||||
|
.expect("new object should be writable"); |
||||||
|
} |
||||||
|
if let Some(set_prototype_of) = self.set_prototype_of { |
||||||
|
let f = FunctionBuilder::native(context, set_prototype_of) |
||||||
|
.length(2) |
||||||
|
.build(); |
||||||
|
handler |
||||||
|
.create_data_property_or_throw("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( |
||||||
|
context.intrinsics().constructors().object().prototype(), |
||||||
|
ObjectData::proxy(Proxy::new(self.target, handler), callable, constructor), |
||||||
|
); |
||||||
|
|
||||||
|
JsProxy { inner: proxy } |
||||||
|
} |
||||||
|
|
||||||
|
/// Builds a [`JsObject`] of kind [`Proxy`] and a [`JsFunction`] that, when
|
||||||
|
/// called, disables the proxy of the object.
|
||||||
|
///
|
||||||
|
/// Equivalent to the `Proxy.revocable ( target, handler )` static method,
|
||||||
|
/// but returns a [`JsObject`] for the proxy and a [`JsFunction`] for the
|
||||||
|
/// revoker in case there's a need to manipulate the returned objects
|
||||||
|
/// inside Rust code.
|
||||||
|
#[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 } |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue