diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index bf0b031fa9..9324dc94a8 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -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, }; @@ -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()) @@ -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 { + 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 { + 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 { + 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 { let x = args.get(0).cloned().unwrap_or_else(Value::undefined); diff --git a/boa/src/builtins/object/tests.rs b/boa/src/builtins/object/tests.rs index 0c8eae2999..287ca54032 100644 --- a/boa/src/builtins/object/tests.rs +++ b/boa/src/builtins/object/tests.rs @@ -1,4 +1,4 @@ -use crate::{forward, Context}; +use crate::{forward, Context, Value}; #[test] fn object_create_with_regular_object() { @@ -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();