mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 20:27:45 +00:00
LibJS: Implement Object.defineProperties()
This commit is contained in:
parent
275da6fcc9
commit
da8a35a79e
6 changed files with 120 additions and 0 deletions
|
@ -95,6 +95,7 @@ namespace JS {
|
||||||
P(count) \
|
P(count) \
|
||||||
P(countReset) \
|
P(countReset) \
|
||||||
P(debug) \
|
P(debug) \
|
||||||
|
P(defineProperties) \
|
||||||
P(defineProperty) \
|
P(defineProperty) \
|
||||||
P(deleteProperty) \
|
P(deleteProperty) \
|
||||||
P(description) \
|
P(description) \
|
||||||
|
|
|
@ -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<NativeProperty>(move(getter), move(setter)), attribute);
|
return define_property(property_name, heap().allocate_without_global_object<NativeProperty>(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<NameAndDescriptor> 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<TypeError>(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)
|
void Object::visit_edges(Cell::Visitor& visitor)
|
||||||
{
|
{
|
||||||
Cell::visit_edges(visitor);
|
Cell::visit_edges(visitor);
|
||||||
|
|
|
@ -115,6 +115,8 @@ public:
|
||||||
bool define_native_function(const StringOrSymbol& property_name, AK::Function<Value(VM&, GlobalObject&)>, i32 length = 0, PropertyAttributes attributes = default_attributes);
|
bool define_native_function(const StringOrSymbol& property_name, AK::Function<Value(VM&, GlobalObject&)>, i32 length = 0, PropertyAttributes attributes = default_attributes);
|
||||||
bool define_native_property(const StringOrSymbol& property_name, AK::Function<Value(VM&, GlobalObject&)> getter, AK::Function<void(VM&, GlobalObject&, Value)> setter, PropertyAttributes attributes = default_attributes);
|
bool define_native_property(const StringOrSymbol& property_name, AK::Function<Value(VM&, GlobalObject&)> getter, AK::Function<void(VM&, GlobalObject&, Value)> setter, PropertyAttributes attributes = default_attributes);
|
||||||
|
|
||||||
|
void define_properties(Value properties);
|
||||||
|
|
||||||
virtual bool delete_property(const PropertyName&);
|
virtual bool delete_property(const PropertyName&);
|
||||||
|
|
||||||
virtual bool is_array() const { return false; }
|
virtual bool is_array() const { return false; }
|
||||||
|
|
|
@ -49,6 +49,7 @@ void ObjectConstructor::initialize(GlobalObject& global_object)
|
||||||
|
|
||||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||||
define_native_function(vm.names.defineProperty, define_property_, 3, attr);
|
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.is, is, 2, attr);
|
||||||
define_native_function(vm.names.getOwnPropertyDescriptor, get_own_property_descriptor, 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);
|
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;
|
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<TypeError>(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)
|
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::is)
|
||||||
{
|
{
|
||||||
return Value(same_value(vm.argument(0), vm.argument(1)));
|
return Value(same_value(vm.argument(0), vm.argument(1)));
|
||||||
|
|
|
@ -46,6 +46,7 @@ private:
|
||||||
virtual bool has_constructor() const override { return true; }
|
virtual bool has_constructor() const override { return true; }
|
||||||
|
|
||||||
JS_DECLARE_NATIVE_FUNCTION(define_property_);
|
JS_DECLARE_NATIVE_FUNCTION(define_property_);
|
||||||
|
JS_DECLARE_NATIVE_FUNCTION(define_properties);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(is);
|
JS_DECLARE_NATIVE_FUNCTION(is);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(get_own_property_descriptor);
|
JS_DECLARE_NATIVE_FUNCTION(get_own_property_descriptor);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(get_own_property_names);
|
JS_DECLARE_NATIVE_FUNCTION(get_own_property_names);
|
||||||
|
|
|
@ -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,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue