From fb1b8d559556f5d5bc00704d9224166b26f33456 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 6 Oct 2020 02:36:26 +0300 Subject: [PATCH] Add Object.defineProperties and handle props argument in Object.create (#746) --- boa/src/builtins/object/mod.rs | 48 +++++++++++++++++++++++------- boa/src/builtins/object/tests.rs | 19 ++++++++++++ boa/src/object/internal_methods.rs | 39 +++++++++++++++++++++++- 3 files changed, 94 insertions(+), 12 deletions(-) diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 7c53dd4c1d..2152ed2495 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -53,6 +53,7 @@ impl BuiltIn for Object { .static_method(Self::set_prototype_of, "setPrototypeOf", 2) .static_method(Self::get_prototype_of, "getPrototypeOf", 1) .static_method(Self::define_property, "defineProperty", 3) + .static_method(Self::define_properties, "defineProperties", 2) .static_method(Self::is, "is", 2) .build(); @@ -84,24 +85,28 @@ impl Object { /// /// [spec]: https://tc39.es/ecma262/#sec-object.create /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create - pub fn create(_: &Value, args: &[Value], interpreter: &mut Context) -> Result { + pub fn create(_: &Value, args: &[Value], ctx: &mut Context) -> Result { let prototype = args.get(0).cloned().unwrap_or_else(Value::undefined); let properties = args.get(1).cloned().unwrap_or_else(Value::undefined); - if properties != Value::Undefined { - unimplemented!("propertiesObject argument of Object.create") - } - - match prototype { - Value::Object(_) | Value::Null => Ok(Value::object(BuiltinObject::with_prototype( + let obj = match prototype { + Value::Object(_) | Value::Null => Value::object(BuiltinObject::with_prototype( prototype, ObjectData::Ordinary, - ))), - _ => interpreter.throw_type_error(format!( - "Object prototype may only be an Object or null: {}", - prototype.display() )), + _ => { + return ctx.throw_type_error(format!( + "Object prototype may only be an Object or null: {}", + prototype.display() + )) + } + }; + + if !properties.is_undefined() { + return Object::define_properties(&Value::Undefined, &[obj, properties], ctx); } + + Ok(obj) } /// Uses the SameValue algorithm to check equality of objects @@ -140,6 +145,27 @@ impl Object { Ok(Value::undefined()) } + /// `Object.defineProperties( proto, [propertiesObject] )` + /// + /// Creates or update own properties to the object + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object.defineproperties + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties + pub fn define_properties(_: &Value, args: &[Value], ctx: &mut Context) -> Result { + let arg = args.get(0).cloned().unwrap_or(Value::undefined()); + let arg_obj = arg.as_object_mut(); + if let Some(mut obj) = arg_obj { + let props = args.get(1).cloned().unwrap_or_else(Value::undefined); + obj.define_properties(props, ctx)?; + Ok(arg.clone()) + } else { + ctx.throw_type_error("Expected an object") + } + } /// `Object.prototype.toString()` /// /// This method returns a string representing the object. diff --git a/boa/src/builtins/object/tests.rs b/boa/src/builtins/object/tests.rs index 9491c08db7..6ff8ea4fbb 100644 --- a/boa/src/builtins/object/tests.rs +++ b/boa/src/builtins/object/tests.rs @@ -194,3 +194,22 @@ fn define_symbol_property() { assert_eq!(forward(&mut ctx, "obj[sym]"), "\"val\""); } + +#[test] +fn object_define_properties() { + let mut ctx = Context::new(); + + let init = r#" + const obj = {}; + + Object.defineProperties(obj, { + p: { + value: 42, + writable: true + } + }); + "#; + eprintln!("{}", forward(&mut ctx, init)); + + assert_eq!(forward(&mut ctx, "obj.p"), "42"); +} diff --git a/boa/src/object/internal_methods.rs b/boa/src/object/internal_methods.rs index 4a9f44e4ad..dbb086ac9c 100644 --- a/boa/src/object/internal_methods.rs +++ b/boa/src/object/internal_methods.rs @@ -9,7 +9,7 @@ use crate::{ object::Object, property::{Attribute, Property, PropertyKey}, value::{same_value, Value}, - BoaProfiler, + BoaProfiler, Context, Result, }; impl Object { @@ -282,6 +282,43 @@ impl Object { }) } + /// Essential internal method OwnPropertyKeys + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec](https://tc39.es/ecma262/#table-essential-internal-methods) + pub fn own_property_keys(&self) -> Vec { + self.keys().collect() + } + + /// The abstract operation ObjectDefineProperties + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object.defineproperties + pub fn define_properties(&mut self, props: Value, ctx: &mut Context) -> Result<()> { + let props = props.to_object(ctx)?; + let keys = props.borrow().own_property_keys(); + let mut descriptors: Vec<(PropertyKey, Property)> = Vec::new(); + + for next_key in keys { + let prop_desc = props.borrow().get_own_property(&next_key); + if prop_desc.enumerable() { + let desc_obj = props.borrow().get(&next_key); + let desc = Property::from(&desc_obj); + descriptors.push((next_key, desc)); + } + } + + descriptors.into_iter().for_each(|(p, d)| { + self.define_own_property(p, d); + }); + + Ok(()) + } + // /// `Object.setPropertyOf(obj, prototype)` // /// // /// This method sets the prototype (i.e., the internal `[[Prototype]]` property)