Skip to content

Commit

Permalink
Implement Proxy object
Browse files Browse the repository at this point in the history
  • Loading branch information
raskad committed Oct 12, 2021
1 parent 4f9764c commit 5da94cd
Show file tree
Hide file tree
Showing 12 changed files with 1,354 additions and 61 deletions.
3 changes: 3 additions & 0 deletions boa/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub mod math;
pub mod nan;
pub mod number;
pub mod object;
pub mod proxy;
pub mod reflect;
pub mod regexp;
pub mod set;
Expand All @@ -44,6 +45,7 @@ pub(crate) use self::{
number::Number,
object::for_in_iterator::ForInIterator,
object::Object as BuiltInObjectObject,
proxy::Proxy,
reflect::Reflect,
regexp::RegExp,
set::set_iterator::SetIterator,
Expand Down Expand Up @@ -122,6 +124,7 @@ pub fn init(context: &mut Context) {
Math,
Json,
Array,
Proxy,
ArrayBuffer,
BigInt,
Boolean,
Expand Down
105 changes: 66 additions & 39 deletions boa/src/builtins/object/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ use crate::{
context::StandardObjects,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
IntegrityLevel, JsObject, ObjectData, ObjectInitializer, ObjectKind,
IntegrityLevel, JsObject, ObjectData, ObjectKind,
},
property::{Attribute, DescriptorKind, PropertyDescriptor, PropertyKey, PropertyNameKind},
property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind},
symbol::WellKnownSymbols,
value::JsValue,
BoaProfiler, Context, JsResult,
Expand Down Expand Up @@ -170,16 +170,17 @@ impl Object {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let object = args.get_or_undefined(0).to_object(context)?;
if let Some(key) = args.get(1) {
let key = key.to_property_key(context)?;
// 1. Let obj be ? ToObject(O).
let obj = args.get_or_undefined(0).to_object(context)?;

if let Some(desc) = object.__get_own_property__(&key, context)? {
return Ok(Self::from_property_descriptor(desc, context));
}
}
// 2. Let key be ? ToPropertyKey(P).
let key = args.get_or_undefined(1).to_property_key(context)?;

Ok(JsValue::undefined())
// 3. Let desc be ? obj.[[GetOwnProperty]](key).
let desc = obj.__get_own_property__(&key, context)?;

// 4. Return FromPropertyDescriptor(desc).
Ok(Self::from_property_descriptor(desc, context))
}

/// `Object.getOwnPropertyDescriptors( object )`
Expand All @@ -202,9 +203,7 @@ impl Object {

for key in object.borrow().properties().keys() {
let descriptor = {
let desc = object
.__get_own_property__(&key, context)?
.expect("Expected property to be on object.");
let desc = object.__get_own_property__(&key, context)?;
Self::from_property_descriptor(desc, context)
};

Expand All @@ -228,40 +227,64 @@ impl Object {
/// [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-frompropertydescriptor
fn from_property_descriptor(desc: PropertyDescriptor, context: &mut Context) -> JsValue {
let mut descriptor = ObjectInitializer::new(context);

// TODO: use CreateDataPropertyOrThrow
pub(crate) fn from_property_descriptor(
desc: Option<PropertyDescriptor>,
context: &mut Context,
) -> JsValue {
match desc {
// 1. If Desc is undefined, return undefined.
None => JsValue::undefined(),
Some(desc) => {
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
// 3. Assert: obj is an extensible ordinary object with no own properties.
let obj = context.construct_object();

// 4. If Desc has a [[Value]] field, then
if let Some(value) = desc.value() {
// a. Perform ! CreateDataPropertyOrThrow(obj, "value", Desc.[[Value]]).
obj.create_data_property_or_throw("value", value, context)
.expect("CreateDataPropertyOrThrow cannot fail here");
}

match desc.kind() {
DescriptorKind::Data { value, writable } => {
if let Some(value) = value {
descriptor.property("value", value.clone(), Attribute::all());
// 5. If Desc has a [[Writable]] field, then
if let Some(writable) = desc.writable() {
// a. Perform ! CreateDataPropertyOrThrow(obj, "writable", Desc.[[Writable]]).
obj.create_data_property_or_throw("writable", writable, context)
.expect("CreateDataPropertyOrThrow cannot fail here");
}
if let Some(writable) = writable {
descriptor.property("writable", *writable, Attribute::all());

// 6. If Desc has a [[Get]] field, then
if let Some(get) = desc.get() {
// a. Perform ! CreateDataPropertyOrThrow(obj, "get", Desc.[[Get]]).
obj.create_data_property_or_throw("get", get, context)
.expect("CreateDataPropertyOrThrow cannot fail here");
}
}
DescriptorKind::Accessor { get, set } => {
if let Some(get) = get {
descriptor.property("get", get.clone(), Attribute::all());

// 7. If Desc has a [[Set]] field, then
if let Some(set) = desc.set() {
// a. Perform ! CreateDataPropertyOrThrow(obj, "set", Desc.[[Set]]).
obj.create_data_property_or_throw("set", set, context)
.expect("CreateDataPropertyOrThrow cannot fail here");
}
if let Some(set) = set {
descriptor.property("set", set.clone(), Attribute::all());

// 8. If Desc has an [[Enumerable]] field, then
if let Some(enumerable) = desc.enumerable() {
// a. Perform ! CreateDataPropertyOrThrow(obj, "enumerable", Desc.[[Enumerable]]).
obj.create_data_property_or_throw("enumerable", enumerable, context)
.expect("CreateDataPropertyOrThrow cannot fail here");
}
}
_ => {}
}

if let Some(enumerable) = desc.enumerable() {
descriptor.property("enumerable", enumerable, Attribute::all());
}
// 9. If Desc has a [[Configurable]] field, then
if let Some(configurable) = desc.configurable() {
// a. Perform ! CreateDataPropertyOrThrow(obj, "configurable", Desc.[[Configurable]]).
obj.create_data_property_or_throw("configurable", configurable, context)
.expect("CreateDataPropertyOrThrow cannot fail here");
}

if let Some(configurable) = desc.configurable() {
descriptor.property("configurable", configurable, Attribute::all());
// 10. Return obj.
obj.into()
}
}

descriptor.build().into()
}

/// Uses the SameValue algorithm to check equality of objects
Expand All @@ -273,6 +296,10 @@ impl Object {
}

/// Get the `prototype` of an object.
///
/// [More information][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-object.setprototypeof
pub fn get_prototype_of(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult<JsValue> {
if args.is_empty() {
return ctx.throw_type_error(
Expand Down
184 changes: 184 additions & 0 deletions boa/src/builtins/proxy/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
//! 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 {
pub(crate) target: Option<JsObject>,
pub(crate) handler: Option<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;

/// `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::proxy_create(&JsValue::undefined(), args, context)
}

// `10.5.14 ProxyCreate ( target, handler )`
//
// More information:
// - [ECMAScript reference][spec]
//
// [spec]: https://tc39.es/ecma262/#sec-proxycreate
fn proxy_create(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. If Type(target) is not Object, throw a TypeError exception.
let target = if let Some(obj) = args.get_or_undefined(0).as_object() {
obj
} else {
return context.throw_type_error("Proxy constructor called with undefined target");
};

// 2. If Type(handler) is not Object, throw a TypeError exception.
let handler = if let Some(obj) = args.get_or_undefined(1).as_object() {
obj
} else {
return context.throw_type_error("Proxy constructor called with undefined 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 = context.construct_object();
p.borrow_mut().data = ObjectData::proxy(
Self {
target: target.clone().into(),
handler: handler.clone().into(),
},
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::proxy_create(&JsValue::undefined(), args, 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.
assert!(p.borrow().is_proxy());

// f. Set p.[[ProxyTarget]] to null.
// g. Set p.[[ProxyHandler]] to null.
p.borrow_mut().data = ObjectData::proxy(
Self {
target: None,
handler: None,
},
p.is_callable(),
p.is_constructor(),
);

// 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())
}
}
7 changes: 7 additions & 0 deletions boa/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ impl StandardConstructor {
#[derive(Debug, Clone)]
pub struct StandardObjects {
object: StandardConstructor,
proxy: StandardConstructor,
function: StandardConstructor,
array: StandardConstructor,
bigint: StandardConstructor,
Expand Down Expand Up @@ -115,6 +116,7 @@ impl Default for StandardObjects {
fn default() -> Self {
Self {
object: StandardConstructor::default(),
proxy: StandardConstructor::default(),
function: StandardConstructor::default(),
array: StandardConstructor::default(),
bigint: StandardConstructor::default(),
Expand Down Expand Up @@ -164,6 +166,11 @@ impl StandardObjects {
&self.object
}

#[inline]
pub fn proxy_object(&self) -> &StandardConstructor {
&self.proxy
}

#[inline]
pub fn function_object(&self) -> &StandardConstructor {
&self.function
Expand Down
Loading

0 comments on commit 5da94cd

Please sign in to comment.