Skip to main content

boa_engine/object/builtins/
jsproxy.rs

1//! A Rust API wrapper for the `Proxy` Builtin ECMAScript Object
2use super::JsFunction;
3use crate::{
4    Context, JsExpect, JsNativeError, JsResult, JsValue,
5    builtins::Proxy,
6    js_string,
7    native_function::{NativeFunction, NativeFunctionPointer},
8    object::{FunctionObjectBuilder, JsObject},
9    value::TryFromJs,
10};
11use boa_gc::{Finalize, Trace};
12
13/// `JsProxy` provides a wrapper for Boa's implementation of the ECMAScript `Proxy` object
14///
15/// This is a wrapper type for the [`Proxy`][proxy] API that allows customizing
16/// essential behaviour for an object, like [property accesses][get] or the
17/// [`delete`][delete] operator.
18///
19/// The only way to construct this type is to use the [`JsProxyBuilder`] type; also
20/// accessible from [`JsProxy::builder`].
21///
22/// [get]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get
23/// [delete]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/deleteProperty
24/// [proxy]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
25#[derive(Debug, Clone, Trace, Finalize)]
26pub struct JsProxy {
27    inner: JsObject,
28}
29
30impl JsProxy {
31    /// Creates a new [`JsProxyBuilder`] to easily construct a [`JsProxy`].
32    pub fn builder(target: JsObject) -> JsProxyBuilder {
33        JsProxyBuilder::new(target)
34    }
35
36    /// Create a [`JsProxy`] from a [`JsObject`], if the object is not a `Proxy` throw a
37    /// `TypeError`.
38    #[inline]
39    pub fn from_object(object: JsObject) -> JsResult<Self> {
40        if object.is::<Proxy>() {
41            Ok(Self { inner: object })
42        } else {
43            Err(JsNativeError::typ()
44                .with_message("object is not a Proxy")
45                .into())
46        }
47    }
48}
49
50impl From<JsProxy> for JsObject {
51    #[inline]
52    fn from(o: JsProxy) -> Self {
53        o.inner.clone()
54    }
55}
56
57impl From<JsProxy> for JsValue {
58    #[inline]
59    fn from(o: JsProxy) -> Self {
60        o.inner.clone().into()
61    }
62}
63
64impl std::ops::Deref for JsProxy {
65    type Target = JsObject;
66
67    #[inline]
68    fn deref(&self) -> &Self::Target {
69        &self.inner
70    }
71}
72
73impl TryFromJs for JsProxy {
74    fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
75        if let Some(o) = value.as_object() {
76            Self::from_object(o.clone())
77        } else {
78            Err(JsNativeError::typ()
79                .with_message("value is not a Proxy object")
80                .into())
81        }
82    }
83}
84
85/// `JsRevocableProxy` provides a wrapper for `JsProxy` that can be disabled.
86///
87/// Safe interface for the [`Proxy.revocable`][revocable] method that creates a
88/// proxy that can be disabled using the [`JsRevocableProxy::revoke`] method.
89/// The internal proxy is accessible using the [`Deref`][`std::ops::Deref`] operator.
90///
91/// The only way to construct this type is to use the [`JsProxyBuilder`] type; also
92/// accessible from [`JsProxy::builder`]; with the [`JsProxyBuilder::build_revocable`]
93/// method.
94///
95/// [proxy]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
96/// [revocable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/revocable
97#[derive(Debug, Trace, Finalize)]
98pub struct JsRevocableProxy {
99    proxy: JsProxy,
100    revoker: JsFunction,
101}
102
103impl JsRevocableProxy {
104    /// Disables the traps of the internal `proxy` object, essentially
105    /// making it unusable and throwing `TypeError`s for all the traps.
106    #[inline]
107    pub fn revoke(self, context: &mut Context) -> JsResult<()> {
108        self.revoker.call(&JsValue::undefined(), &[], context)?;
109        Ok(())
110    }
111}
112
113impl std::ops::Deref for JsRevocableProxy {
114    type Target = JsProxy;
115
116    #[inline]
117    fn deref(&self) -> &Self::Target {
118        &self.proxy
119    }
120}
121
122/// Utility builder to create [`JsProxy`] objects from native functions.
123///
124/// This builder can be used when you need to create [`Proxy`] objects
125/// from Rust instead of Javascript, which should generate faster
126/// trap functions than its Javascript counterparts.
127#[must_use]
128#[derive(Clone)]
129pub struct JsProxyBuilder {
130    target: JsObject,
131    apply: Option<NativeFunctionPointer>,
132    construct: Option<NativeFunctionPointer>,
133    define_property: Option<NativeFunctionPointer>,
134    delete_property: Option<NativeFunctionPointer>,
135    get: Option<NativeFunctionPointer>,
136    get_own_property_descriptor: Option<NativeFunctionPointer>,
137    get_prototype_of: Option<NativeFunctionPointer>,
138    has: Option<NativeFunctionPointer>,
139    is_extensible: Option<NativeFunctionPointer>,
140    own_keys: Option<NativeFunctionPointer>,
141    prevent_extensions: Option<NativeFunctionPointer>,
142    set: Option<NativeFunctionPointer>,
143    set_prototype_of: Option<NativeFunctionPointer>,
144}
145
146impl std::fmt::Debug for JsProxyBuilder {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        #[derive(Debug)]
149        struct NativeFunction;
150        f.debug_struct("ProxyBuilder")
151            .field("target", &self.target)
152            .field("apply", &self.apply.map(|_| NativeFunction))
153            .field("construct", &self.construct.map(|_| NativeFunction))
154            .field(
155                "define_property",
156                &self.define_property.map(|_| NativeFunction),
157            )
158            .field(
159                "delete_property",
160                &self.delete_property.map(|_| NativeFunction),
161            )
162            .field("get", &self.get.map(|_| NativeFunction))
163            .field(
164                "get_own_property_descriptor",
165                &self.get_own_property_descriptor.map(|_| NativeFunction),
166            )
167            .field(
168                "get_prototype_of",
169                &self.get_prototype_of.map(|_| NativeFunction),
170            )
171            .field("has", &self.has.map(|_| NativeFunction))
172            .field("is_extensible", &self.is_extensible.map(|_| NativeFunction))
173            .field("own_keys", &self.own_keys.map(|_| NativeFunction))
174            .field(
175                "prevent_extensions",
176                &self.prevent_extensions.map(|_| NativeFunction),
177            )
178            .field("set", &self.set.map(|_| NativeFunction))
179            .field(
180                "set_prototype_of",
181                &self.set_prototype_of.map(|_| NativeFunction),
182            )
183            .finish()
184    }
185}
186
187impl JsProxyBuilder {
188    /// Create a new `ProxyBuilder` with every trap set to `undefined`.
189    #[inline]
190    pub fn new(target: JsObject) -> Self {
191        Self {
192            target,
193            apply: None,
194            construct: None,
195            define_property: None,
196            delete_property: None,
197            get: None,
198            get_own_property_descriptor: None,
199            get_prototype_of: None,
200            has: None,
201            is_extensible: None,
202            own_keys: None,
203            prevent_extensions: None,
204            set: None,
205            set_prototype_of: None,
206        }
207    }
208
209    /// Set the `apply` proxy trap to the specified native function.
210    ///
211    /// More information:
212    ///
213    /// - [MDN documentation][mdn]
214    ///
215    /// # Note
216    ///
217    /// If the `target` object is not a function, then `apply` will be ignored
218    /// when trying to call the proxy, which will throw a type error.
219    ///
220    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/apply
221    #[inline]
222    pub fn apply(mut self, apply: NativeFunctionPointer) -> Self {
223        self.apply = Some(apply);
224        self
225    }
226
227    /// Set the `construct` proxy trap to the specified native function.
228    ///
229    /// More information:
230    ///
231    /// - [MDN documentation][mdn]
232    ///
233    /// # Note
234    ///
235    /// If the `target` object is not a constructor, then `construct` will be ignored
236    /// when trying to construct an object using the proxy, which will throw a type error.
237    ///
238    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/construct
239    #[inline]
240    pub fn construct(mut self, construct: NativeFunctionPointer) -> Self {
241        self.construct = Some(construct);
242        self
243    }
244
245    /// Set the `defineProperty` proxy trap to the specified native function.
246    ///
247    /// More information:
248    ///
249    /// - [MDN documentation][mdn]
250    ///
251    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/defineProperty
252    #[inline]
253    pub fn define_property(mut self, define_property: NativeFunctionPointer) -> Self {
254        self.define_property = Some(define_property);
255        self
256    }
257
258    /// Set the `deleteProperty` proxy trap to the specified native function.
259    ///
260    /// More information:
261    ///
262    /// - [MDN documentation][mdn]
263    ///
264    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/deleteProperty
265    #[inline]
266    pub fn delete_property(mut self, delete_property: NativeFunctionPointer) -> Self {
267        self.delete_property = Some(delete_property);
268        self
269    }
270
271    /// Set the `get` proxy trap to the specified native function.
272    ///
273    /// More information:
274    ///
275    /// - [MDN documentation][mdn]
276    ///
277    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get
278    #[inline]
279    pub fn get(mut self, get: NativeFunctionPointer) -> Self {
280        self.get = Some(get);
281        self
282    }
283
284    /// Set the `getOwnPropertyDescriptor` proxy trap to the specified native function.
285    ///
286    /// More information:
287    ///
288    /// - [MDN documentation][mdn]
289    ///
290    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getOwnPropertyDescriptor
291    #[inline]
292    pub fn get_own_property_descriptor(
293        mut self,
294        get_own_property_descriptor: NativeFunctionPointer,
295    ) -> Self {
296        self.get_own_property_descriptor = Some(get_own_property_descriptor);
297        self
298    }
299
300    /// Set the `getPrototypeOf` proxy trap to the specified native function.
301    ///
302    /// More information:
303    ///
304    /// - [MDN documentation][mdn]
305    ///
306    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getPrototypeOf
307    #[inline]
308    pub fn get_prototype_of(mut self, get_prototype_of: NativeFunctionPointer) -> Self {
309        self.get_prototype_of = Some(get_prototype_of);
310        self
311    }
312
313    /// Set the `has` proxy trap to the specified native function.
314    ///
315    /// More information:
316    ///
317    /// - [MDN documentation][mdn]
318    ///
319    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/has
320    #[inline]
321    pub fn has(mut self, has: NativeFunctionPointer) -> Self {
322        self.has = Some(has);
323        self
324    }
325
326    /// Set the `isExtensible` proxy trap to the specified native function.
327    ///
328    /// More information:
329    ///
330    /// - [MDN documentation][mdn]
331    ///
332    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/isExtensible
333    #[inline]
334    pub fn is_extensible(mut self, is_extensible: NativeFunctionPointer) -> Self {
335        self.is_extensible = Some(is_extensible);
336        self
337    }
338
339    /// Set the `ownKeys` proxy trap to the specified native function.
340    ///
341    /// More information:
342    ///
343    /// - [MDN documentation][mdn]
344    ///
345    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys
346    #[inline]
347    pub fn own_keys(mut self, own_keys: NativeFunctionPointer) -> Self {
348        self.own_keys = Some(own_keys);
349        self
350    }
351
352    /// Set the `preventExtensions` proxy trap to the specified native function.
353    ///
354    /// More information:
355    ///
356    /// - [MDN documentation][mdn]
357    ///
358    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/preventExtensions
359    #[inline]
360    pub fn prevent_extensions(mut self, prevent_extensions: NativeFunctionPointer) -> Self {
361        self.prevent_extensions = Some(prevent_extensions);
362        self
363    }
364
365    /// Set the `set` proxy trap to the specified native function.
366    ///
367    /// More information:
368    ///
369    /// - [MDN documentation][mdn]
370    ///
371    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set
372    #[inline]
373    pub fn set(mut self, set: NativeFunctionPointer) -> Self {
374        self.set = Some(set);
375        self
376    }
377
378    /// Set the `setPrototypeOf` proxy trap to the specified native function.
379    ///
380    /// More information:
381    ///
382    /// - [MDN documentation][mdn]
383    ///
384    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/setPrototypeOf
385    #[inline]
386    pub fn set_prototype_of(mut self, set_prototype_of: NativeFunctionPointer) -> Self {
387        self.set_prototype_of = Some(set_prototype_of);
388        self
389    }
390
391    /// Build a [`JsObject`] of kind [`Proxy`].
392    ///
393    /// Equivalent to the `Proxy ( target, handler )` constructor, but returns a
394    /// [`JsObject`] in case there's a need to manipulate the returned object
395    /// inside Rust code.
396    pub fn build(self, context: &mut Context) -> JsResult<JsProxy> {
397        let handler = JsObject::with_object_proto(context.intrinsics());
398
399        if let Some(apply) = self.apply {
400            let f = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(apply))
401                .length(3)
402                .build();
403            handler
404                .create_data_property_or_throw(js_string!("apply"), f, context)
405                .js_expect("new object should be writable")?;
406        }
407        if let Some(construct) = self.construct {
408            let f =
409                FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(construct))
410                    .length(3)
411                    .build();
412            handler
413                .create_data_property_or_throw(js_string!("construct"), f, context)
414                .js_expect("new object should be writable")?;
415        }
416        if let Some(define_property) = self.define_property {
417            let f = FunctionObjectBuilder::new(
418                context.realm(),
419                NativeFunction::from_fn_ptr(define_property),
420            )
421            .length(3)
422            .build();
423            handler
424                .create_data_property_or_throw(js_string!("defineProperty"), f, context)
425                .js_expect("new object should be writable")?;
426        }
427        if let Some(delete_property) = self.delete_property {
428            let f = FunctionObjectBuilder::new(
429                context.realm(),
430                NativeFunction::from_fn_ptr(delete_property),
431            )
432            .length(2)
433            .build();
434            handler
435                .create_data_property_or_throw(js_string!("deleteProperty"), f, context)
436                .js_expect("new object should be writable")?;
437        }
438        if let Some(get) = self.get {
439            let f = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get))
440                .length(3)
441                .build();
442            handler
443                .create_data_property_or_throw(js_string!("get"), f, context)
444                .js_expect("new object should be writable")?;
445        }
446        if let Some(get_own_property_descriptor) = self.get_own_property_descriptor {
447            let f = FunctionObjectBuilder::new(
448                context.realm(),
449                NativeFunction::from_fn_ptr(get_own_property_descriptor),
450            )
451            .length(2)
452            .build();
453            handler
454                .create_data_property_or_throw(js_string!("getOwnPropertyDescriptor"), f, context)
455                .js_expect("new object should be writable")?;
456        }
457        if let Some(get_prototype_of) = self.get_prototype_of {
458            let f = FunctionObjectBuilder::new(
459                context.realm(),
460                NativeFunction::from_fn_ptr(get_prototype_of),
461            )
462            .length(1)
463            .build();
464            handler
465                .create_data_property_or_throw(js_string!("getPrototypeOf"), f, context)
466                .js_expect("new object should be writable")?;
467        }
468        if let Some(has) = self.has {
469            let f = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(has))
470                .length(2)
471                .build();
472            handler
473                .create_data_property_or_throw(js_string!("has"), f, context)
474                .js_expect("new object should be writable")?;
475        }
476        if let Some(is_extensible) = self.is_extensible {
477            let f = FunctionObjectBuilder::new(
478                context.realm(),
479                NativeFunction::from_fn_ptr(is_extensible),
480            )
481            .length(1)
482            .build();
483            handler
484                .create_data_property_or_throw(js_string!("isExtensible"), f, context)
485                .js_expect("new object should be writable")?;
486        }
487        if let Some(own_keys) = self.own_keys {
488            let f =
489                FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(own_keys))
490                    .length(1)
491                    .build();
492            handler
493                .create_data_property_or_throw(js_string!("ownKeys"), f, context)
494                .js_expect("new object should be writable")?;
495        }
496        if let Some(prevent_extensions) = self.prevent_extensions {
497            let f = FunctionObjectBuilder::new(
498                context.realm(),
499                NativeFunction::from_fn_ptr(prevent_extensions),
500            )
501            .length(1)
502            .build();
503            handler
504                .create_data_property_or_throw(js_string!("preventExtensions"), f, context)
505                .js_expect("new object should be writable")?;
506        }
507        if let Some(set) = self.set {
508            let f = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set))
509                .length(4)
510                .build();
511            handler
512                .create_data_property_or_throw(js_string!("set"), f, context)
513                .js_expect("new object should be writable")?;
514        }
515        if let Some(set_prototype_of) = self.set_prototype_of {
516            let f = FunctionObjectBuilder::new(
517                context.realm(),
518                NativeFunction::from_fn_ptr(set_prototype_of),
519            )
520            .length(2)
521            .build();
522            handler
523                .create_data_property_or_throw(js_string!("setPrototypeOf"), f, context)
524                .js_expect("new object should be writable")?;
525        }
526
527        let proxy = JsObject::from_proto_and_data_with_shared_shape(
528            context.root_shape(),
529            context.intrinsics().constructors().object().prototype(),
530            Proxy::new(self.target, handler),
531        )
532        .upcast();
533
534        Ok(JsProxy { inner: proxy })
535    }
536
537    /// Builds a [`JsObject`] of kind [`Proxy`] and a [`JsFunction`] that, when
538    /// called, disables the proxy of the object.
539    ///
540    /// Equivalent to the `Proxy.revocable ( target, handler )` static method,
541    /// but returns a [`JsObject`] for the proxy and a [`JsFunction`] for the
542    /// revoker in case there's a need to manipulate the returned objects
543    /// inside Rust code.
544    pub fn build_revocable(self, context: &mut Context) -> JsResult<JsRevocableProxy> {
545        let proxy = self.build(context)?;
546        let revoker = Proxy::revoker(proxy.inner.clone(), context);
547
548        Ok(JsRevocableProxy { proxy, revoker })
549    }
550}