Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Object.getOwnPropertyDescriptor() and Object.getOwnPropertyDescriptors() #798

Merged
merged 16 commits into from
Oct 12, 2020
121 changes: 120 additions & 1 deletion boa/src/builtins/object/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@

use crate::{
builtins::BuiltIn,
object::{ConstructorBuilder, Object as BuiltinObject, ObjectData},
object::{ConstructorBuilder, Object as BuiltinObject, ObjectData, ObjectInitializer},
property::Attribute,
property::DataDescriptor,
property::PropertyDescriptor,
value::{same_value, Value},
BoaProfiler, Context, Result,
};
Expand Down Expand Up @@ -55,6 +57,16 @@ impl BuiltIn for Object {
.static_method(Self::define_property, "defineProperty", 3)
.static_method(Self::define_properties, "defineProperties", 2)
.static_method(Self::is, "is", 2)
.static_method(
Self::get_own_property_descriptor,
"getOwnPropertyDescriptor",
2,
)
.static_method(
Self::get_own_property_descriptors,
"getOwnPropertyDescriptors",
1,
)
.build();

(Self::NAME, object.into(), Self::attribute())
Expand Down Expand Up @@ -109,6 +121,113 @@ impl Object {
Ok(obj)
}

/// `Object.getOwnPropertyDescriptor( object, property )`
///
/// Returns an object describing the configuration of a specific property on a given object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-object.getownpropertydescriptor
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor
pub fn get_own_property_descriptor(
_: &Value,
args: &[Value],
ctx: &mut Context,
) -> Result<Value> {
let object = args.get(0).unwrap_or(&Value::undefined()).to_object(ctx)?;
if let Some(key) = args.get(1) {
let key = key.to_property_key(ctx)?;

if let Some(desc) = object.borrow().get_own_property(&key) {
return Ok(Self::from_property_descriptor(desc, ctx)?);
}
}

Ok(Value::undefined())
}

/// `Object.getOwnPropertyDescriptors( object )`
///
/// Returns all own property descriptors of a given object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-object.getownpropertydescriptors
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors
pub fn get_own_property_descriptors(
_: &Value,
args: &[Value],
ctx: &mut Context,
) -> Result<Value> {
let object = args.get(0).unwrap_or(&Value::undefined()).to_object(ctx)?;
let descriptors = ctx.construct_object();

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

if !descriptor.is_undefined() {
descriptors.borrow_mut().insert(
key,
PropertyDescriptor::from(DataDescriptor::new(descriptor, Attribute::all())),
);
}
}

Ok(Value::Object(descriptors))
}

/// The abstract operation `FromPropertyDescriptor`.
///
/// [ECMAScript reference][spec]
/// [spec]: https://tc39.es/ecma262/#sec-frompropertydescriptor
fn from_property_descriptor(desc: PropertyDescriptor, ctx: &mut Context) -> Result<Value> {
let mut descriptor = ObjectInitializer::new(ctx);

if let PropertyDescriptor::Data(data_desc) = &desc {
descriptor.property("value", data_desc.value(), Attribute::all());
}

if let PropertyDescriptor::Accessor(accessor_desc) = &desc {
if let Some(setter) = accessor_desc.setter() {
descriptor.property("set", Value::Object(setter.to_owned()), Attribute::all());
}
if let Some(getter) = accessor_desc.getter() {
descriptor.property("get", Value::Object(getter.to_owned()), Attribute::all());
}
}

let writable = if let PropertyDescriptor::Data(data_desc) = &desc {
data_desc.writable()
} else {
false
};

descriptor
.property("writable", Value::from(writable), Attribute::all())
.property(
"enumerable",
Value::from(desc.enumerable()),
Attribute::all(),
)
.property(
"configurable",
Value::from(desc.configurable()),
Attribute::all(),
);

Ok(descriptor.build().into())
}

/// Uses the SameValue algorithm to check equality of objects
pub fn is(_: &Value, args: &[Value], _: &mut Context) -> Result<Value> {
let x = args.get(0).cloned().unwrap_or_else(Value::undefined);
Expand Down
51 changes: 50 additions & 1 deletion boa/src/builtins/object/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{forward, Context};
use crate::{forward, Context, Value};

#[test]
fn object_create_with_regular_object() {
Expand Down Expand Up @@ -194,6 +194,55 @@ fn define_symbol_property() {
assert_eq!(forward(&mut ctx, "obj[sym]"), "\"val\"");
}

#[test]
fn get_own_property_descriptor_1_arg_returns_undefined() {
let mut ctx = Context::new();
let code = r#"
let obj = {a: 2};
Object.getOwnPropertyDescriptor(obj)
"#;
assert_eq!(ctx.eval(code).unwrap(), Value::undefined());
}

#[test]
fn get_own_property_descriptor() {
let mut ctx = Context::new();
forward(
&mut ctx,
r#"
let obj = {a: 2};
let result = Object.getOwnPropertyDescriptor(obj, "a");
"#,
);

assert_eq!(forward(&mut ctx, "result.enumerable"), "true");
assert_eq!(forward(&mut ctx, "result.writable"), "true");
assert_eq!(forward(&mut ctx, "result.configurable"), "true");
assert_eq!(forward(&mut ctx, "result.value"), "2");
}

#[test]
fn get_own_property_descriptors() {
let mut ctx = Context::new();
forward(
&mut ctx,
r#"
let obj = {a: 1, b: 2};
let result = Object.getOwnPropertyDescriptors(obj);
"#,
);

assert_eq!(forward(&mut ctx, "result.a.enumerable"), "true");
assert_eq!(forward(&mut ctx, "result.a.writable"), "true");
assert_eq!(forward(&mut ctx, "result.a.configurable"), "true");
assert_eq!(forward(&mut ctx, "result.a.value"), "1");

assert_eq!(forward(&mut ctx, "result.b.enumerable"), "true");
assert_eq!(forward(&mut ctx, "result.b.writable"), "true");
assert_eq!(forward(&mut ctx, "result.b.configurable"), "true");
assert_eq!(forward(&mut ctx, "result.b.value"), "2");
}

#[test]
fn object_define_properties() {
let mut ctx = Context::new();
Expand Down