mirror of https://github.com/boa-dev/boa.git
Browse Source
* Implement `Proxy` object * Restucture `Proxy` struct fields * Apply some suggestionspull/1672/head
raskad
3 years ago
committed by
GitHub
13 changed files with 1432 additions and 72 deletions
@ -0,0 +1,188 @@ |
|||||||
|
//! This module implements the global `Proxy` object.
|
||||||
|
//!
|
||||||
|
//! The `Proxy` object enables you to create a proxy for another object,
|
||||||
|
//! which can intercept and redefine fundamental operations for that object.
|
||||||
|
//!
|
||||||
|
//! More information:
|
||||||
|
//! - [ECMAScript reference][spec]
|
||||||
|
//! - [MDN documentation][mdn]
|
||||||
|
//!
|
||||||
|
//! [spec]: https://tc39.es/ecma262/#sec-proxy-objects
|
||||||
|
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
|
||||||
|
|
||||||
|
use crate::{ |
||||||
|
builtins::{BuiltIn, JsArgs}, |
||||||
|
gc::{Finalize, Trace}, |
||||||
|
object::{ConstructorBuilder, FunctionBuilder, JsObject, ObjectData}, |
||||||
|
property::Attribute, |
||||||
|
BoaProfiler, Context, JsResult, JsValue, |
||||||
|
}; |
||||||
|
|
||||||
|
/// Javascript `Proxy` object.
|
||||||
|
#[derive(Debug, Clone, Trace, Finalize)] |
||||||
|
pub struct Proxy { |
||||||
|
// (target, handler)
|
||||||
|
data: Option<(JsObject, JsObject)>, |
||||||
|
} |
||||||
|
|
||||||
|
impl BuiltIn for Proxy { |
||||||
|
const NAME: &'static str = "Proxy"; |
||||||
|
|
||||||
|
const ATTRIBUTE: Attribute = Attribute::WRITABLE |
||||||
|
.union(Attribute::NON_ENUMERABLE) |
||||||
|
.union(Attribute::CONFIGURABLE); |
||||||
|
|
||||||
|
fn init(context: &mut Context) -> JsValue { |
||||||
|
let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); |
||||||
|
|
||||||
|
ConstructorBuilder::with_standard_object( |
||||||
|
context, |
||||||
|
Self::constructor, |
||||||
|
context.standard_objects().proxy_object().clone(), |
||||||
|
) |
||||||
|
.name(Self::NAME) |
||||||
|
.length(Self::LENGTH) |
||||||
|
.constructor_has_prototype(false) |
||||||
|
.static_method(Self::revocable, "revocable", 2) |
||||||
|
.build() |
||||||
|
.into() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Proxy { |
||||||
|
const LENGTH: usize = 2; |
||||||
|
|
||||||
|
fn new(target: JsObject, handler: JsObject) -> Self { |
||||||
|
Self { |
||||||
|
data: Some((target, handler)), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// This is an internal method only built for usage in the proxy internal methods.
|
||||||
|
///
|
||||||
|
/// It returns the (target, handler) of the proxy.
|
||||||
|
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") |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
/// `28.2.1.1 Proxy ( target, handler )`
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-proxy-target-handler
|
||||||
|
pub(crate) fn constructor( |
||||||
|
new_target: &JsValue, |
||||||
|
args: &[JsValue], |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<JsValue> { |
||||||
|
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||||
|
if new_target.is_undefined() { |
||||||
|
return context.throw_type_error("Proxy constructor called on undefined new target"); |
||||||
|
} |
||||||
|
|
||||||
|
// 2. Return ? ProxyCreate(target, handler).
|
||||||
|
Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context) |
||||||
|
} |
||||||
|
|
||||||
|
// `10.5.14 ProxyCreate ( target, handler )`
|
||||||
|
//
|
||||||
|
// More information:
|
||||||
|
// - [ECMAScript reference][spec]
|
||||||
|
//
|
||||||
|
// [spec]: https://tc39.es/ecma262/#sec-proxycreate
|
||||||
|
fn create(target: &JsValue, handler: &JsValue, context: &mut Context) -> JsResult<JsValue> { |
||||||
|
// 1. If Type(target) is not Object, throw a TypeError exception.
|
||||||
|
let target = target.as_object().ok_or_else(|| { |
||||||
|
context.construct_type_error("Proxy constructor called with non-object handler") |
||||||
|
})?; |
||||||
|
|
||||||
|
// 2. If Type(handler) is not Object, throw a TypeError exception.
|
||||||
|
let handler = handler.as_object().ok_or_else(|| { |
||||||
|
context.construct_type_error("Proxy constructor called with non-object handler") |
||||||
|
})?; |
||||||
|
|
||||||
|
// 3. Let P be ! MakeBasicObject(« [[ProxyHandler]], [[ProxyTarget]] »).
|
||||||
|
// 4. Set P's essential internal methods, except for [[Call]] and [[Construct]], to the definitions specified in 10.5.
|
||||||
|
// 5. If IsCallable(target) is true, then
|
||||||
|
// a. Set P.[[Call]] as specified in 10.5.12.
|
||||||
|
// b. If IsConstructor(target) is true, then
|
||||||
|
// i. Set P.[[Construct]] as specified in 10.5.13.
|
||||||
|
// 6. Set P.[[ProxyTarget]] to target.
|
||||||
|
// 7. Set P.[[ProxyHandler]] to handler.
|
||||||
|
let p = JsObject::from_proto_and_data( |
||||||
|
context.standard_objects().object_object().prototype(), |
||||||
|
ObjectData::proxy( |
||||||
|
Self::new(target.clone(), handler.clone()), |
||||||
|
target.is_callable(), |
||||||
|
target.is_constructor(), |
||||||
|
), |
||||||
|
); |
||||||
|
|
||||||
|
// 8. Return P.
|
||||||
|
Ok(p.into()) |
||||||
|
} |
||||||
|
|
||||||
|
/// `28.2.2.1 Proxy.revocable ( target, handler )`
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-proxy.revocable
|
||||||
|
fn revocable(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> { |
||||||
|
// 1. Let p be ? ProxyCreate(target, handler).
|
||||||
|
let p = Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context)?; |
||||||
|
|
||||||
|
// 3. Let revoker be ! CreateBuiltinFunction(revokerClosure, 0, "", « [[RevocableProxy]] »).
|
||||||
|
// 4. Set revoker.[[RevocableProxy]] to p.
|
||||||
|
let revoker = FunctionBuilder::closure_with_captures( |
||||||
|
context, |
||||||
|
|_, _, revocable_proxy, _| { |
||||||
|
// a. Let F be the active function object.
|
||||||
|
// b. Let p be F.[[RevocableProxy]].
|
||||||
|
// c. If p is null, return undefined.
|
||||||
|
if revocable_proxy.is_null() { |
||||||
|
return Ok(JsValue::undefined()); |
||||||
|
} |
||||||
|
|
||||||
|
let p = revocable_proxy |
||||||
|
.as_object() |
||||||
|
.expect("[[RevocableProxy]] must be an object or null"); |
||||||
|
|
||||||
|
// e. Assert: p is a Proxy object.
|
||||||
|
// f. Set p.[[ProxyTarget]] to null.
|
||||||
|
// g. Set p.[[ProxyHandler]] to null.
|
||||||
|
p.borrow_mut() |
||||||
|
.as_proxy_mut() |
||||||
|
.expect("[[RevocableProxy]] must be a proxy object") |
||||||
|
.data = None; |
||||||
|
|
||||||
|
// d. Set F.[[RevocableProxy]] to null.
|
||||||
|
*revocable_proxy = JsValue::Null; |
||||||
|
|
||||||
|
// h. Return undefined.
|
||||||
|
Ok(JsValue::undefined()) |
||||||
|
}, |
||||||
|
p.clone(), |
||||||
|
) |
||||||
|
.build(); |
||||||
|
|
||||||
|
// 5. Let result be ! OrdinaryObjectCreate(%Object.prototype%).
|
||||||
|
let result = context.construct_object(); |
||||||
|
|
||||||
|
// 6. Perform ! CreateDataPropertyOrThrow(result, "proxy", p).
|
||||||
|
result |
||||||
|
.create_data_property_or_throw("proxy", p, context) |
||||||
|
.expect("CreateDataPropertyOrThrow cannot fail here"); |
||||||
|
|
||||||
|
// 7. Perform ! CreateDataPropertyOrThrow(result, "revoke", revoker).
|
||||||
|
result |
||||||
|
.create_data_property_or_throw("revoke", revoker, context) |
||||||
|
.expect("CreateDataPropertyOrThrow cannot fail here"); |
||||||
|
|
||||||
|
// 8. Return result.
|
||||||
|
Ok(result.into()) |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue