diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 1001e535b2..f26254a349 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -95,6 +95,7 @@ namespace JS { P(count) \ P(countReset) \ P(debug) \ + P(defineProperties) \ P(defineProperty) \ P(deleteProperty) \ P(description) \ diff --git a/Userland/Libraries/LibJS/Runtime/Object.cpp b/Userland/Libraries/LibJS/Runtime/Object.cpp index 41a1b29070..3c989a7c7e 100644 --- a/Userland/Libraries/LibJS/Runtime/Object.cpp +++ b/Userland/Libraries/LibJS/Runtime/Object.cpp @@ -900,6 +900,49 @@ bool Object::define_native_property(const StringOrSymbol& property_name, AK::Fun return define_property(property_name, heap().allocate_without_global_object(move(getter), move(setter)), attribute); } +// 20.1.2.3.1 ObjectDefineProperties, https://tc39.es/ecma262/#sec-objectdefineproperties +void Object::define_properties(Value properties) +{ + auto& vm = this->vm(); + auto* props = properties.to_object(global_object()); + if (!props) + return; + auto keys = props->get_own_properties(PropertyKind::Key); + if (vm.exception()) + return; + struct NameAndDescriptor { + PropertyName name; + PropertyDescriptor descriptor; + }; + Vector descriptors; + for (auto& key : keys) { + auto property_name = PropertyName::from_value(global_object(), key); + auto property_descriptor = props->get_own_property_descriptor(property_name); + if (property_descriptor.has_value() && property_descriptor->attributes.is_enumerable()) { + auto descriptor_object = props->get(property_name); + if (vm.exception()) + return; + if (!descriptor_object.is_object()) { + vm.throw_exception(global_object(), ErrorType::NotAnObject, descriptor_object.to_string_without_side_effects()); + return; + } + auto descriptor = PropertyDescriptor::from_dictionary(vm, descriptor_object.as_object()); + if (vm.exception()) + return; + descriptors.append({ property_name, descriptor }); + } + } + for (auto& [name, descriptor] : descriptors) { + // FIXME: The spec has both of this handled by DefinePropertyOrThrow(O, P, desc). + // We should invest some time in improving object property handling, it not being + // super close to the spec makes this and other things unnecessarily complicated. + if (descriptor.is_accessor_descriptor()) + define_accessor(name, descriptor.getter, descriptor.setter, descriptor.attributes); + else + define_property(name, descriptor.value, descriptor.attributes); + } +} + void Object::visit_edges(Cell::Visitor& visitor) { Cell::visit_edges(visitor); diff --git a/Userland/Libraries/LibJS/Runtime/Object.h b/Userland/Libraries/LibJS/Runtime/Object.h index 5210670df2..23f5f53e5f 100644 --- a/Userland/Libraries/LibJS/Runtime/Object.h +++ b/Userland/Libraries/LibJS/Runtime/Object.h @@ -115,6 +115,8 @@ public: bool define_native_function(const StringOrSymbol& property_name, AK::Function, i32 length = 0, PropertyAttributes attributes = default_attributes); bool define_native_property(const StringOrSymbol& property_name, AK::Function getter, AK::Function setter, PropertyAttributes attributes = default_attributes); + void define_properties(Value properties); + virtual bool delete_property(const PropertyName&); virtual bool is_array() const { return false; } diff --git a/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp index bbbb3c4a33..6138f438d7 100644 --- a/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp @@ -49,6 +49,7 @@ void ObjectConstructor::initialize(GlobalObject& global_object) u8 attr = Attribute::Writable | Attribute::Configurable; define_native_function(vm.names.defineProperty, define_property_, 3, attr); + define_native_function(vm.names.defineProperties, define_properties, 2, attr); define_native_function(vm.names.is, is, 2, attr); define_native_function(vm.names.getOwnPropertyDescriptor, get_own_property_descriptor, 2, attr); define_native_function(vm.names.getOwnPropertyNames, get_own_property_names, 1, attr); @@ -250,6 +251,21 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::define_property_) return &object; } +// 20.1.2.3 Object.defineProperties, https://tc39.es/ecma262/#sec-object.defineproperties +JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::define_properties) +{ + if (!vm.argument(0).is_object()) { + vm.throw_exception(global_object, ErrorType::NotAnObject, "Object argument"); + return {}; + } + auto& object = vm.argument(0).as_object(); + auto properties = vm.argument(1); + object.define_properties(properties); + if (vm.exception()) + return {}; + return &object; +} + JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::is) { return Value(same_value(vm.argument(0), vm.argument(1))); diff --git a/Userland/Libraries/LibJS/Runtime/ObjectConstructor.h b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.h index 364e72f6a9..a31dcc05de 100644 --- a/Userland/Libraries/LibJS/Runtime/ObjectConstructor.h +++ b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.h @@ -46,6 +46,7 @@ private: virtual bool has_constructor() const override { return true; } JS_DECLARE_NATIVE_FUNCTION(define_property_); + JS_DECLARE_NATIVE_FUNCTION(define_properties); JS_DECLARE_NATIVE_FUNCTION(is); JS_DECLARE_NATIVE_FUNCTION(get_own_property_descriptor); JS_DECLARE_NATIVE_FUNCTION(get_own_property_names); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.defineProperties.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.defineProperties.js new file mode 100644 index 0000000000..67e4198bba --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.defineProperties.js @@ -0,0 +1,57 @@ +test("length is 2", () => { + expect(Object.defineProperties).toHaveLength(2); +}); + +describe("errors", () => { + test("non-object argument", () => { + expect(() => Object.defineProperties(42, {})).toThrowWithMessage( + TypeError, + "Object argument is not an object" + ); + }); +}); + +describe("normal behavior", () => { + test("returns given object", () => { + const o = {}; + expect(Object.defineProperties(o, {})).toBe(o); + }); + + test("defines given properties on object", () => { + const properties = { + foo: { + writable: true, + configurable: true, + value: "foo", + }, + bar: { + enumerable: true, + value: "bar", + }, + baz: { + get() {}, + set() {}, + }, + }; + const o = Object.defineProperties({}, properties); + expect(Object.getOwnPropertyNames(o)).toEqual(["foo", "bar", "baz"]); + expect(Object.getOwnPropertyDescriptor(o, "foo")).toEqual({ + value: "foo", + writable: true, + enumerable: false, + configurable: true, + }); + expect(Object.getOwnPropertyDescriptor(o, "bar")).toEqual({ + value: "bar", + writable: false, + enumerable: true, + configurable: false, + }); + expect(Object.getOwnPropertyDescriptor(o, "baz")).toEqual({ + get: properties.baz.get, + set: properties.baz.set, + enumerable: false, + configurable: false, + }); + }); +});