mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-20 09:49:15 +03:00
LibJS: Distinguish between omitted descriptor attributes and false ones
When calling Object.defineProperty, there is now a difference between omitting a descriptor attribute and specifying that it is false. For example, "{}" and "{ configurable: false }" will have different attribute values.
This commit is contained in:
parent
5c485d4a1e
commit
5ad5322f6a
Notes:
sideshowbarker
2024-07-19 05:47:47 +09:00
Author: https://github.com/mattco98 Commit: https://github.com/SerenityOS/serenity/commit/5ad5322f6a1 Pull-request: https://github.com/SerenityOS/serenity/pull/2489 Reviewed-by: https://github.com/awesomekling
@ -1304,7 +1304,7 @@ Value ObjectExpression::execute(Interpreter& interpreter) const
|
|||||||
auto& obj_to_spread = key_result.as_object();
|
auto& obj_to_spread = key_result.as_object();
|
||||||
|
|
||||||
for (auto& it : obj_to_spread.shape().property_table_ordered()) {
|
for (auto& it : obj_to_spread.shape().property_table_ordered()) {
|
||||||
if (it.value.attributes & Attribute::Enumerable)
|
if (it.value.attributes.is_enumerable())
|
||||||
object->define_property(it.key, obj_to_spread.get(it.key));
|
object->define_property(it.key, obj_to_spread.get(it.key));
|
||||||
}
|
}
|
||||||
} else if (key_result.is_string()) {
|
} else if (key_result.is_string()) {
|
||||||
|
@ -47,7 +47,7 @@ Optional<ValueAndAttributes> SimpleIndexedPropertyStorage::get(u32 index) const
|
|||||||
return ValueAndAttributes { m_packed_elements[index], default_attributes };
|
return ValueAndAttributes { m_packed_elements[index], default_attributes };
|
||||||
}
|
}
|
||||||
|
|
||||||
void SimpleIndexedPropertyStorage::put(u32 index, Value value, u8 attributes)
|
void SimpleIndexedPropertyStorage::put(u32 index, Value value, PropertyAttributes attributes)
|
||||||
{
|
{
|
||||||
ASSERT(attributes == default_attributes);
|
ASSERT(attributes == default_attributes);
|
||||||
ASSERT(index < SPARSE_ARRAY_THRESHOLD);
|
ASSERT(index < SPARSE_ARRAY_THRESHOLD);
|
||||||
@ -66,7 +66,7 @@ void SimpleIndexedPropertyStorage::remove(u32 index)
|
|||||||
m_packed_elements[index] = {};
|
m_packed_elements[index] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void SimpleIndexedPropertyStorage::insert(u32 index, Value value, u8 attributes)
|
void SimpleIndexedPropertyStorage::insert(u32 index, Value value, PropertyAttributes attributes)
|
||||||
{
|
{
|
||||||
ASSERT(attributes == default_attributes);
|
ASSERT(attributes == default_attributes);
|
||||||
ASSERT(index < SPARSE_ARRAY_THRESHOLD);
|
ASSERT(index < SPARSE_ARRAY_THRESHOLD);
|
||||||
@ -122,7 +122,7 @@ Optional<ValueAndAttributes> GenericIndexedPropertyStorage::get(u32 index) const
|
|||||||
return m_sparse_elements.get(index);
|
return m_sparse_elements.get(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericIndexedPropertyStorage::put(u32 index, Value value, u8 attributes)
|
void GenericIndexedPropertyStorage::put(u32 index, Value value, PropertyAttributes attributes)
|
||||||
{
|
{
|
||||||
if (index >= m_array_size)
|
if (index >= m_array_size)
|
||||||
m_array_size = index + 1;
|
m_array_size = index + 1;
|
||||||
@ -151,7 +151,7 @@ void GenericIndexedPropertyStorage::remove(u32 index)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericIndexedPropertyStorage::insert(u32 index, Value value, u8 attributes)
|
void GenericIndexedPropertyStorage::insert(u32 index, Value value, PropertyAttributes attributes)
|
||||||
{
|
{
|
||||||
if (index >= m_array_size) {
|
if (index >= m_array_size) {
|
||||||
put(index, value, attributes);
|
put(index, value, attributes);
|
||||||
@ -281,7 +281,7 @@ Optional<ValueAndAttributes> IndexedProperties::get(Object* this_object, u32 ind
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IndexedProperties::put(Object* this_object, u32 index, Value value, u8 attributes, bool evaluate_accessors)
|
void IndexedProperties::put(Object* this_object, u32 index, Value value, PropertyAttributes attributes, bool evaluate_accessors)
|
||||||
{
|
{
|
||||||
if (m_storage->is_simple_storage() && (index >= SPARSE_ARRAY_THRESHOLD || attributes != default_attributes))
|
if (m_storage->is_simple_storage() && (index >= SPARSE_ARRAY_THRESHOLD || attributes != default_attributes))
|
||||||
switch_to_generic_storage();
|
switch_to_generic_storage();
|
||||||
@ -304,13 +304,13 @@ bool IndexedProperties::remove(u32 index)
|
|||||||
auto result = m_storage->get(index);
|
auto result = m_storage->get(index);
|
||||||
if (!result.has_value())
|
if (!result.has_value())
|
||||||
return true;
|
return true;
|
||||||
if (!(result.value().attributes & Attribute::Configurable))
|
if (!result.value().attributes.is_configurable())
|
||||||
return false;
|
return false;
|
||||||
m_storage->remove(index);
|
m_storage->remove(index);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IndexedProperties::insert(u32 index, Value value, u8 attributes)
|
void IndexedProperties::insert(u32 index, Value value, PropertyAttributes attributes)
|
||||||
{
|
{
|
||||||
if (m_storage->is_simple_storage() && (index >= SPARSE_ARRAY_THRESHOLD || attributes != default_attributes || array_like_size() == SPARSE_ARRAY_THRESHOLD))
|
if (m_storage->is_simple_storage() && (index >= SPARSE_ARRAY_THRESHOLD || attributes != default_attributes || array_like_size() == SPARSE_ARRAY_THRESHOLD))
|
||||||
switch_to_generic_storage();
|
switch_to_generic_storage();
|
||||||
|
@ -37,7 +37,7 @@ const u32 MIN_PACKED_RESIZE_AMOUNT = 20;
|
|||||||
|
|
||||||
struct ValueAndAttributes {
|
struct ValueAndAttributes {
|
||||||
Value value;
|
Value value;
|
||||||
u8 attributes { default_attributes };
|
PropertyAttributes attributes { default_attributes };
|
||||||
};
|
};
|
||||||
|
|
||||||
class IndexedProperties;
|
class IndexedProperties;
|
||||||
@ -50,10 +50,10 @@ public:
|
|||||||
|
|
||||||
virtual bool has_index(u32 index) const = 0;
|
virtual bool has_index(u32 index) const = 0;
|
||||||
virtual Optional<ValueAndAttributes> get(u32 index) const = 0;
|
virtual Optional<ValueAndAttributes> get(u32 index) const = 0;
|
||||||
virtual void put(u32 index, Value value, u8 attributes = default_attributes) = 0;
|
virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) = 0;
|
||||||
virtual void remove(u32 index) = 0;
|
virtual void remove(u32 index) = 0;
|
||||||
|
|
||||||
virtual void insert(u32 index, Value value, u8 attributes = default_attributes) = 0;
|
virtual void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes) = 0;
|
||||||
virtual ValueAndAttributes take_first() = 0;
|
virtual ValueAndAttributes take_first() = 0;
|
||||||
virtual ValueAndAttributes take_last() = 0;
|
virtual ValueAndAttributes take_last() = 0;
|
||||||
|
|
||||||
@ -71,10 +71,10 @@ public:
|
|||||||
|
|
||||||
virtual bool has_index(u32 index) const override;
|
virtual bool has_index(u32 index) const override;
|
||||||
virtual Optional<ValueAndAttributes> get(u32 index) const override;
|
virtual Optional<ValueAndAttributes> get(u32 index) const override;
|
||||||
virtual void put(u32 index, Value value, u8 attributes = default_attributes) override;
|
virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) override;
|
||||||
virtual void remove(u32 index) override;
|
virtual void remove(u32 index) override;
|
||||||
|
|
||||||
virtual void insert(u32 index, Value value, u8 attributes = default_attributes) override;
|
virtual void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes) override;
|
||||||
virtual ValueAndAttributes take_first() override;
|
virtual ValueAndAttributes take_first() override;
|
||||||
virtual ValueAndAttributes take_last() override;
|
virtual ValueAndAttributes take_last() override;
|
||||||
|
|
||||||
@ -98,10 +98,10 @@ public:
|
|||||||
|
|
||||||
virtual bool has_index(u32 index) const override;
|
virtual bool has_index(u32 index) const override;
|
||||||
virtual Optional<ValueAndAttributes> get(u32 index) const override;
|
virtual Optional<ValueAndAttributes> get(u32 index) const override;
|
||||||
virtual void put(u32 index, Value value, u8 attributes = default_attributes) override;
|
virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) override;
|
||||||
virtual void remove(u32 index) override;
|
virtual void remove(u32 index) override;
|
||||||
|
|
||||||
virtual void insert(u32 index, Value value, u8 attributes = default_attributes) override;
|
virtual void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes) override;
|
||||||
virtual ValueAndAttributes take_first() override;
|
virtual ValueAndAttributes take_first() override;
|
||||||
virtual ValueAndAttributes take_last() override;
|
virtual ValueAndAttributes take_last() override;
|
||||||
|
|
||||||
@ -146,14 +146,14 @@ public:
|
|||||||
|
|
||||||
bool has_index(u32 index) const { return m_storage->has_index(index); }
|
bool has_index(u32 index) const { return m_storage->has_index(index); }
|
||||||
Optional<ValueAndAttributes> get(Object* this_object, u32 index, bool evaluate_accessors = true) const;
|
Optional<ValueAndAttributes> get(Object* this_object, u32 index, bool evaluate_accessors = true) const;
|
||||||
void put(Object* this_object, u32 index, Value value, u8 attributes = default_attributes, bool evaluate_accessors = true);
|
void put(Object* this_object, u32 index, Value value, PropertyAttributes attributes = default_attributes, bool evaluate_accessors = true);
|
||||||
bool remove(u32 index);
|
bool remove(u32 index);
|
||||||
|
|
||||||
void insert(u32 index, Value value, u8 attributes = default_attributes);
|
void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes);
|
||||||
ValueAndAttributes take_first(Object* this_object);
|
ValueAndAttributes take_first(Object* this_object);
|
||||||
ValueAndAttributes take_last(Object* this_object);
|
ValueAndAttributes take_last(Object* this_object);
|
||||||
|
|
||||||
void append(Value value, u8 attributes = default_attributes) { put(nullptr, array_like_size(), value, attributes, false); }
|
void append(Value value, PropertyAttributes attributes = default_attributes) { put(nullptr, array_like_size(), value, attributes, false); }
|
||||||
void append_all(Object* this_object, const IndexedProperties& properties, bool evaluate_accessors = true);
|
void append_all(Object* this_object, const IndexedProperties& properties, bool evaluate_accessors = true);
|
||||||
|
|
||||||
IndexedPropertyIterator begin(bool skip_empty = true) const { return IndexedPropertyIterator(*this, 0, skip_empty); };
|
IndexedPropertyIterator begin(bool skip_empty = true) const { return IndexedPropertyIterator(*this, 0, skip_empty); };
|
||||||
|
@ -123,7 +123,7 @@ Value Object::get_own_property(const Object& this_object, PropertyName property_
|
|||||||
return value_here;
|
return value_here;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value Object::get_own_properties(const Object& this_object, GetOwnPropertyMode kind, u8 attributes) const
|
Value Object::get_own_properties(const Object& this_object, GetOwnPropertyMode kind, PropertyAttributes attributes) const
|
||||||
{
|
{
|
||||||
auto* properties_array = Array::create(interpreter().global_object());
|
auto* properties_array = Array::create(interpreter().global_object());
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ Value Object::get_own_properties(const Object& this_object, GetOwnPropertyMode k
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (auto& it : this_object.shape().property_table_ordered()) {
|
for (auto& it : this_object.shape().property_table_ordered()) {
|
||||||
if (it.value.attributes & attributes) {
|
if (it.value.attributes.bits() & attributes.bits()) {
|
||||||
size_t offset = it.value.offset + property_index;
|
size_t offset = it.value.offset + property_index;
|
||||||
|
|
||||||
if (kind == GetOwnPropertyMode::Key) {
|
if (kind == GetOwnPropertyMode::Key) {
|
||||||
@ -194,7 +194,7 @@ Value Object::get_own_properties(const Object& this_object, GetOwnPropertyMode k
|
|||||||
Value Object::get_own_property_descriptor(PropertyName property_name) const
|
Value Object::get_own_property_descriptor(PropertyName property_name) const
|
||||||
{
|
{
|
||||||
Value value;
|
Value value;
|
||||||
u8 attributes;
|
PropertyAttributes attributes;
|
||||||
|
|
||||||
if (property_name.is_number()) {
|
if (property_name.is_number()) {
|
||||||
auto existing_value = m_indexed_properties.get(nullptr, property_name.as_number(), false);
|
auto existing_value = m_indexed_properties.get(nullptr, property_name.as_number(), false);
|
||||||
@ -214,12 +214,12 @@ Value Object::get_own_property_descriptor(PropertyName property_name) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto* descriptor = Object::create_empty(interpreter(), interpreter().global_object());
|
auto* descriptor = Object::create_empty(interpreter(), interpreter().global_object());
|
||||||
descriptor->define_property("enumerable", Value((attributes & Attribute::Enumerable) != 0));
|
descriptor->define_property("enumerable", Value(attributes.is_enumerable()));
|
||||||
descriptor->define_property("configurable", Value((attributes & Attribute::Configurable) != 0));
|
descriptor->define_property("configurable", Value(attributes.is_configurable()));
|
||||||
if (value.is_object() && value.as_object().is_native_property()) {
|
if (value.is_object() && value.as_object().is_native_property()) {
|
||||||
auto result = call_native_property_getter(const_cast<Object*>(this), value);
|
auto result = call_native_property_getter(const_cast<Object*>(this), value);
|
||||||
descriptor->define_property("value", result);
|
descriptor->define_property("value", result);
|
||||||
descriptor->define_property("writable", Value((attributes & Attribute::Writable) != 0));
|
descriptor->define_property("writable", Value(attributes.is_writable()));
|
||||||
} else if (value.is_accessor()) {
|
} else if (value.is_accessor()) {
|
||||||
auto& pair = value.as_accessor();
|
auto& pair = value.as_accessor();
|
||||||
if (pair.getter())
|
if (pair.getter())
|
||||||
@ -228,7 +228,7 @@ Value Object::get_own_property_descriptor(PropertyName property_name) const
|
|||||||
descriptor->define_property("set", pair.setter());
|
descriptor->define_property("set", pair.setter());
|
||||||
} else {
|
} else {
|
||||||
descriptor->define_property("value", value.value_or(js_undefined()));
|
descriptor->define_property("value", value.value_or(js_undefined()));
|
||||||
descriptor->define_property("writable", Value((attributes & Attribute::Writable) != 0));
|
descriptor->define_property("writable", Value(attributes.is_writable()));
|
||||||
}
|
}
|
||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
@ -242,13 +242,25 @@ void Object::set_shape(Shape& new_shape)
|
|||||||
bool Object::define_property(const FlyString& property_name, const Object& descriptor, bool throw_exceptions)
|
bool Object::define_property(const FlyString& property_name, const Object& descriptor, bool throw_exceptions)
|
||||||
{
|
{
|
||||||
bool is_accessor_property = descriptor.has_property("get") || descriptor.has_property("set");
|
bool is_accessor_property = descriptor.has_property("get") || descriptor.has_property("set");
|
||||||
u8 configurable = descriptor.get("configurable").value_or(Value(false)).to_boolean() * Attribute::Configurable;
|
PropertyAttributes attributes;
|
||||||
if (interpreter().exception())
|
if (descriptor.has_property("configurable")) {
|
||||||
return {};
|
if (interpreter().exception())
|
||||||
u8 enumerable = descriptor.get("enumerable").value_or(Value(false)).to_boolean() * Attribute::Enumerable;
|
return false;
|
||||||
if (interpreter().exception())
|
attributes.set_has_configurable();
|
||||||
return {};
|
if (descriptor.get("configurable").value_or(Value(false)).to_boolean())
|
||||||
u8 attributes = configurable | enumerable;
|
attributes.set_configurable();
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (descriptor.has_property("enumerable")) {
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
attributes.set_has_enumerable();
|
||||||
|
if (descriptor.get("enumerable").value_or(Value(false)).to_boolean())
|
||||||
|
attributes.set_enumerable();
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (is_accessor_property) {
|
if (is_accessor_property) {
|
||||||
if (descriptor.has_property("value") || descriptor.has_property("writable")) {
|
if (descriptor.has_property("value") || descriptor.has_property("writable")) {
|
||||||
@ -291,10 +303,17 @@ bool Object::define_property(const FlyString& property_name, const Object& descr
|
|||||||
auto value = descriptor.get("value");
|
auto value = descriptor.get("value");
|
||||||
if (interpreter().exception())
|
if (interpreter().exception())
|
||||||
return {};
|
return {};
|
||||||
u8 writable = descriptor.get("writable").value_or(Value(false)).to_boolean() * Attribute::Writable;
|
if (descriptor.has_property("writable")) {
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
attributes.set_has_writable();
|
||||||
|
if (descriptor.get("writable").value_or(Value(false)).to_boolean())
|
||||||
|
attributes.set_writable();
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (interpreter().exception())
|
if (interpreter().exception())
|
||||||
return {};
|
return {};
|
||||||
attributes |= writable;
|
|
||||||
|
|
||||||
dbg() << "Defining new property " << property_name << " with data descriptor { attributes=" << attributes
|
dbg() << "Defining new property " << property_name << " with data descriptor { attributes=" << attributes
|
||||||
<< ", value=" << (value.is_empty() ? "<empty>" : value.to_string_without_side_effects()) << " }";
|
<< ", value=" << (value.is_empty() ? "<empty>" : value.to_string_without_side_effects()) << " }";
|
||||||
@ -302,7 +321,7 @@ bool Object::define_property(const FlyString& property_name, const Object& descr
|
|||||||
return define_property(property_name, value, attributes, throw_exceptions);
|
return define_property(property_name, value, attributes, throw_exceptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Object::define_property(PropertyName property_name, Value value, u8 attributes, bool throw_exceptions)
|
bool Object::define_property(PropertyName property_name, Value value, PropertyAttributes attributes, bool throw_exceptions)
|
||||||
{
|
{
|
||||||
if (property_name.is_number())
|
if (property_name.is_number())
|
||||||
return put_own_property_by_index(*this, property_name.as_number(), value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions);
|
return put_own_property_by_index(*this, property_name.as_number(), value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions);
|
||||||
@ -313,7 +332,7 @@ bool Object::define_property(PropertyName property_name, Value value, u8 attribu
|
|||||||
return put_own_property(*this, property_name.as_string(), value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions);
|
return put_own_property(*this, property_name.as_string(), value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Object::put_own_property(Object& this_object, const FlyString& property_name, Value value, u8 attributes, PutOwnPropertyMode mode, bool throw_exceptions)
|
bool Object::put_own_property(Object& this_object, const FlyString& property_name, Value value, PropertyAttributes attributes, PutOwnPropertyMode mode, bool throw_exceptions)
|
||||||
{
|
{
|
||||||
ASSERT(!(mode == PutOwnPropertyMode::Put && value.is_accessor()));
|
ASSERT(!(mode == PutOwnPropertyMode::Put && value.is_accessor()));
|
||||||
|
|
||||||
@ -327,9 +346,9 @@ bool Object::put_own_property(Object& this_object, const FlyString& property_nam
|
|||||||
if (value.is_accessor()) {
|
if (value.is_accessor()) {
|
||||||
auto& accessor = value.as_accessor();
|
auto& accessor = value.as_accessor();
|
||||||
if (accessor.getter())
|
if (accessor.getter())
|
||||||
attributes |= Attribute::HasGet;
|
attributes.set_has_getter();
|
||||||
if (accessor.setter())
|
if (accessor.setter())
|
||||||
attributes |= Attribute::HasSet;
|
attributes.set_has_setter();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto metadata = shape().lookup(property_name);
|
auto metadata = shape().lookup(property_name);
|
||||||
@ -352,7 +371,7 @@ bool Object::put_own_property(Object& this_object, const FlyString& property_nam
|
|||||||
ASSERT(metadata.has_value());
|
ASSERT(metadata.has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!new_property && mode == PutOwnPropertyMode::DefineProperty && !(metadata.value().attributes & Attribute::Configurable) && attributes != metadata.value().attributes) {
|
if (!new_property && mode == PutOwnPropertyMode::DefineProperty && !metadata.value().attributes.is_configurable() && attributes != metadata.value().attributes) {
|
||||||
dbg() << "Disallow reconfig of non-configurable property";
|
dbg() << "Disallow reconfig of non-configurable property";
|
||||||
if (throw_exceptions)
|
if (throw_exceptions)
|
||||||
interpreter().throw_exception<TypeError>(String::format("Cannot change attributes of non-configurable property '%s'", property_name.characters()));
|
interpreter().throw_exception<TypeError>(String::format("Cannot change attributes of non-configurable property '%s'", property_name.characters()));
|
||||||
@ -371,7 +390,7 @@ bool Object::put_own_property(Object& this_object, const FlyString& property_nam
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto value_here = m_storage[metadata.value().offset];
|
auto value_here = m_storage[metadata.value().offset];
|
||||||
if (!new_property && mode == PutOwnPropertyMode::Put && !value_here.is_accessor() && !(metadata.value().attributes & Attribute::Writable)) {
|
if (!new_property && mode == PutOwnPropertyMode::Put && !value_here.is_accessor() && !metadata.value().attributes.is_writable()) {
|
||||||
dbg() << "Disallow write to non-writable property";
|
dbg() << "Disallow write to non-writable property";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -387,7 +406,7 @@ bool Object::put_own_property(Object& this_object, const FlyString& property_nam
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Object::put_own_property_by_index(Object& this_object, u32 property_index, Value value, u8 attributes, PutOwnPropertyMode mode, bool throw_exceptions)
|
bool Object::put_own_property_by_index(Object& this_object, u32 property_index, Value value, PropertyAttributes attributes, PutOwnPropertyMode mode, bool throw_exceptions)
|
||||||
{
|
{
|
||||||
ASSERT(!(mode == PutOwnPropertyMode::Put && value.is_accessor()));
|
ASSERT(!(mode == PutOwnPropertyMode::Put && value.is_accessor()));
|
||||||
|
|
||||||
@ -401,16 +420,16 @@ bool Object::put_own_property_by_index(Object& this_object, u32 property_index,
|
|||||||
if (value.is_accessor()) {
|
if (value.is_accessor()) {
|
||||||
auto& accessor = value.as_accessor();
|
auto& accessor = value.as_accessor();
|
||||||
if (accessor.getter())
|
if (accessor.getter())
|
||||||
attributes |= Attribute::HasGet;
|
attributes.set_has_getter();
|
||||||
if (accessor.setter())
|
if (accessor.setter())
|
||||||
attributes |= Attribute::HasSet;
|
attributes.set_has_setter();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto existing_property = m_indexed_properties.get(nullptr, property_index, false);
|
auto existing_property = m_indexed_properties.get(nullptr, property_index, false);
|
||||||
auto new_property = !existing_property.has_value();
|
auto new_property = !existing_property.has_value();
|
||||||
auto existing_attributes = new_property ? 0 : existing_property.value().attributes;
|
PropertyAttributes existing_attributes = new_property ? 0 : existing_property.value().attributes;
|
||||||
|
|
||||||
if (!new_property && mode == PutOwnPropertyMode::DefineProperty && !(existing_attributes & Attribute::Configurable) && attributes != existing_attributes) {
|
if (!new_property && mode == PutOwnPropertyMode::DefineProperty && !existing_attributes.is_configurable() && attributes != existing_attributes) {
|
||||||
dbg() << "Disallow reconfig of non-configurable property";
|
dbg() << "Disallow reconfig of non-configurable property";
|
||||||
if (throw_exceptions)
|
if (throw_exceptions)
|
||||||
interpreter().throw_exception<TypeError>(String::format("Cannot change attributes of non-configurable property %d", property_index));
|
interpreter().throw_exception<TypeError>(String::format("Cannot change attributes of non-configurable property %d", property_index));
|
||||||
@ -418,7 +437,7 @@ bool Object::put_own_property_by_index(Object& this_object, u32 property_index,
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto value_here = new_property ? Value() : existing_property.value().value;
|
auto value_here = new_property ? Value() : existing_property.value().value;
|
||||||
if (!new_property && mode == PutOwnPropertyMode::Put && !value_here.is_accessor() && !(existing_attributes & Attribute::Writable)) {
|
if (!new_property && mode == PutOwnPropertyMode::Put && !value_here.is_accessor() && !existing_attributes.is_writable()) {
|
||||||
dbg() << "Disallow write to non-writable property";
|
dbg() << "Disallow write to non-writable property";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -442,7 +461,7 @@ Value Object::delete_property(PropertyName property_name)
|
|||||||
auto metadata = shape().lookup(property_name.as_string());
|
auto metadata = shape().lookup(property_name.as_string());
|
||||||
if (!metadata.has_value())
|
if (!metadata.has_value())
|
||||||
return Value(true);
|
return Value(true);
|
||||||
if (!(metadata.value().attributes & Attribute::Configurable))
|
if (!metadata.value().attributes.is_configurable())
|
||||||
return Value(false);
|
return Value(false);
|
||||||
|
|
||||||
size_t deleted_offset = metadata.value().offset;
|
size_t deleted_offset = metadata.value().offset;
|
||||||
@ -565,7 +584,7 @@ bool Object::put(PropertyName property_name, Value value)
|
|||||||
return put_own_property(*this, property_string, value, default_attributes, PutOwnPropertyMode::Put);
|
return put_own_property(*this, property_string, value, default_attributes, PutOwnPropertyMode::Put);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Object::define_native_function(const FlyString& property_name, AK::Function<Value(Interpreter&)> native_function, i32 length, u8 attribute)
|
bool Object::define_native_function(const FlyString& property_name, AK::Function<Value(Interpreter&)> native_function, i32 length, PropertyAttributes attribute)
|
||||||
{
|
{
|
||||||
auto* function = NativeFunction::create(interpreter(), interpreter().global_object(), property_name, move(native_function));
|
auto* function = NativeFunction::create(interpreter(), interpreter().global_object(), property_name, move(native_function));
|
||||||
function->define_property("length", Value(length), Attribute::Configurable);
|
function->define_property("length", Value(length), Attribute::Configurable);
|
||||||
@ -573,7 +592,7 @@ bool Object::define_native_function(const FlyString& property_name, AK::Function
|
|||||||
return define_property(property_name, function, attribute);
|
return define_property(property_name, function, attribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Object::define_native_property(const FlyString& property_name, AK::Function<Value(Interpreter&)> getter, AK::Function<void(Interpreter&, Value)> setter, u8 attribute)
|
bool Object::define_native_property(const FlyString& property_name, AK::Function<Value(Interpreter&)> getter, AK::Function<void(Interpreter&, Value)> setter, PropertyAttributes attribute)
|
||||||
{
|
{
|
||||||
return define_property(property_name, heap().allocate<NativeProperty>(move(getter), move(setter)), attribute);
|
return define_property(property_name, heap().allocate<NativeProperty>(move(getter), move(setter)), attribute);
|
||||||
}
|
}
|
||||||
|
@ -68,14 +68,14 @@ public:
|
|||||||
bool put(PropertyName, Value);
|
bool put(PropertyName, Value);
|
||||||
|
|
||||||
Value get_own_property(const Object& this_object, PropertyName) const;
|
Value get_own_property(const Object& this_object, PropertyName) const;
|
||||||
Value get_own_properties(const Object& this_object, GetOwnPropertyMode, u8 attributes = default_attributes) const;
|
Value get_own_properties(const Object& this_object, GetOwnPropertyMode, PropertyAttributes attributes = default_attributes) const;
|
||||||
Value get_own_property_descriptor(PropertyName) const;
|
Value get_own_property_descriptor(PropertyName) const;
|
||||||
|
|
||||||
bool define_property(const FlyString& property_name, const Object& descriptor, bool throw_exceptions = true);
|
bool define_property(const FlyString& property_name, const Object& descriptor, bool throw_exceptions = true);
|
||||||
bool define_property(PropertyName, Value value, u8 attributes = default_attributes, bool throw_exceptions = true);
|
bool define_property(PropertyName, Value value, PropertyAttributes attributes = default_attributes, bool throw_exceptions = true);
|
||||||
|
|
||||||
bool define_native_function(const FlyString& property_name, AK::Function<Value(Interpreter&)>, i32 length = 0, u8 attribute = default_attributes);
|
bool define_native_function(const FlyString& property_name, AK::Function<Value(Interpreter&)>, i32 length = 0, PropertyAttributes attributes = default_attributes);
|
||||||
bool define_native_property(const FlyString& property_name, AK::Function<Value(Interpreter&)> getter, AK::Function<void(Interpreter&, Value)> setter, u8 attribute = default_attributes);
|
bool define_native_property(const FlyString& property_name, AK::Function<Value(Interpreter&)> getter, AK::Function<void(Interpreter&, Value)> setter, PropertyAttributes attributes = default_attributes);
|
||||||
|
|
||||||
Value delete_property(PropertyName);
|
Value delete_property(PropertyName);
|
||||||
|
|
||||||
@ -116,8 +116,8 @@ public:
|
|||||||
private:
|
private:
|
||||||
virtual Value get_by_index(u32 property_index) const;
|
virtual Value get_by_index(u32 property_index) const;
|
||||||
virtual bool put_by_index(u32 property_index, Value);
|
virtual bool put_by_index(u32 property_index, Value);
|
||||||
bool put_own_property(Object& this_object, const FlyString& property_name, Value, u8 attributes, PutOwnPropertyMode = PutOwnPropertyMode::Put, bool throw_exceptions = true);
|
bool put_own_property(Object& this_object, const FlyString& property_name, Value, PropertyAttributes attributes, PutOwnPropertyMode = PutOwnPropertyMode::Put, bool throw_exceptions = true);
|
||||||
bool put_own_property_by_index(Object& this_object, u32 property_index, Value, u8 attributes, PutOwnPropertyMode = PutOwnPropertyMode::Put, bool throw_exceptions = true);
|
bool put_own_property_by_index(Object& this_object, u32 property_index, Value, PropertyAttributes attributes, PutOwnPropertyMode = PutOwnPropertyMode::Put, bool throw_exceptions = true);
|
||||||
|
|
||||||
Value call_native_property_getter(Object* this_object, Value property) const;
|
Value call_native_property_getter(Object* this_object, Value property) const;
|
||||||
void call_native_property_setter(Object* this_object, Value property, Value) const;
|
void call_native_property_setter(Object* this_object, Value property, Value) const;
|
||||||
|
91
Libraries/LibJS/Runtime/PropertyAttributes.h
Normal file
91
Libraries/LibJS/Runtime/PropertyAttributes.h
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace JS {
|
||||||
|
|
||||||
|
struct Attribute {
|
||||||
|
enum {
|
||||||
|
Configurable = 1 << 0,
|
||||||
|
Enumerable = 1 << 1,
|
||||||
|
Writable = 1 << 2,
|
||||||
|
HasGetter = 1 << 3,
|
||||||
|
HasSetter = 1 << 4,
|
||||||
|
HasConfigurable = 1 << 5,
|
||||||
|
HasEnumerable = 1 << 6,
|
||||||
|
HasWritable = 1 << 7,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class PropertyAttributes {
|
||||||
|
public:
|
||||||
|
PropertyAttributes(u8 bits = 0)
|
||||||
|
{
|
||||||
|
m_bits = bits;
|
||||||
|
if (bits & Attribute::Configurable)
|
||||||
|
m_bits |= Attribute::HasConfigurable;
|
||||||
|
if (bits & Attribute::Enumerable)
|
||||||
|
m_bits |= Attribute::HasEnumerable;
|
||||||
|
if (bits & Attribute::Writable)
|
||||||
|
m_bits |= Attribute::HasWritable;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_empty() const { return !m_bits; }
|
||||||
|
|
||||||
|
bool has_configurable() const { return m_bits & Attribute::HasConfigurable; }
|
||||||
|
bool has_enumerable() const { return m_bits & Attribute::HasEnumerable; }
|
||||||
|
bool has_writable() const { return m_bits & Attribute::HasWritable; }
|
||||||
|
bool has_getter() const { return m_bits & Attribute::HasGetter; }
|
||||||
|
bool has_setter() const { return m_bits & Attribute::HasSetter; }
|
||||||
|
|
||||||
|
bool is_configurable() const { return m_bits & Attribute::Configurable; }
|
||||||
|
bool is_enumerable() const { return m_bits & Attribute::Enumerable; }
|
||||||
|
bool is_writable() const { return m_bits & Attribute::Writable; }
|
||||||
|
|
||||||
|
void set_has_configurable() { m_bits |= Attribute::HasConfigurable; }
|
||||||
|
void set_has_enumerable() { m_bits |= Attribute::HasEnumerable; }
|
||||||
|
void set_has_writable() { m_bits |= Attribute::HasWritable; }
|
||||||
|
void set_configurable() { m_bits |= Attribute::Configurable; }
|
||||||
|
void set_enumerable() { m_bits |= Attribute::Enumerable; }
|
||||||
|
void set_writable() { m_bits |= Attribute::Writable; }
|
||||||
|
void set_has_getter() { m_bits |= Attribute::HasGetter; }
|
||||||
|
void set_has_setter() { m_bits |= Attribute::HasSetter; }
|
||||||
|
|
||||||
|
bool operator==(const PropertyAttributes& other) const { return m_bits == other.m_bits; }
|
||||||
|
bool operator!=(const PropertyAttributes& other) const { return m_bits != other.m_bits; }
|
||||||
|
|
||||||
|
u8 bits() const { return m_bits; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
u8 m_bits;
|
||||||
|
};
|
||||||
|
|
||||||
|
const LogStream& operator<<(const LogStream& stream, const PropertyAttributes& attributes);
|
||||||
|
|
||||||
|
const PropertyAttributes default_attributes = Attribute::Configurable | Attribute::Writable | Attribute::Enumerable;
|
||||||
|
|
||||||
|
}
|
@ -29,6 +29,11 @@
|
|||||||
|
|
||||||
namespace JS {
|
namespace JS {
|
||||||
|
|
||||||
|
const LogStream& operator<<(const LogStream& stream, const PropertyAttributes& attributes)
|
||||||
|
{
|
||||||
|
return stream << attributes.bits();
|
||||||
|
}
|
||||||
|
|
||||||
Shape* Shape::create_unique_clone() const
|
Shape* Shape::create_unique_clone() const
|
||||||
{
|
{
|
||||||
auto* new_shape = heap().allocate<Shape>();
|
auto* new_shape = heap().allocate<Shape>();
|
||||||
@ -40,7 +45,7 @@ Shape* Shape::create_unique_clone() const
|
|||||||
return new_shape;
|
return new_shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
Shape* Shape::create_put_transition(const FlyString& property_name, u8 attributes)
|
Shape* Shape::create_put_transition(const FlyString& property_name, PropertyAttributes attributes)
|
||||||
{
|
{
|
||||||
TransitionKey key { property_name, attributes };
|
TransitionKey key { property_name, attributes };
|
||||||
if (auto* existing_shape = m_forward_transitions.get(key).value_or(nullptr))
|
if (auto* existing_shape = m_forward_transitions.get(key).value_or(nullptr))
|
||||||
@ -50,7 +55,7 @@ Shape* Shape::create_put_transition(const FlyString& property_name, u8 attribute
|
|||||||
return new_shape;
|
return new_shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
Shape* Shape::create_configure_transition(const FlyString& property_name, u8 attributes)
|
Shape* Shape::create_configure_transition(const FlyString& property_name, PropertyAttributes attributes)
|
||||||
{
|
{
|
||||||
TransitionKey key { property_name, attributes };
|
TransitionKey key { property_name, attributes };
|
||||||
if (auto* existing_shape = m_forward_transitions.get(key).value_or(nullptr))
|
if (auto* existing_shape = m_forward_transitions.get(key).value_or(nullptr))
|
||||||
@ -69,7 +74,7 @@ Shape::Shape()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Shape::Shape(Shape* previous_shape, const FlyString& property_name, u8 attributes, TransitionType transition_type)
|
Shape::Shape(Shape* previous_shape, const FlyString& property_name, PropertyAttributes attributes, TransitionType transition_type)
|
||||||
: m_previous(previous_shape)
|
: m_previous(previous_shape)
|
||||||
, m_property_name(property_name)
|
, m_property_name(property_name)
|
||||||
, m_attributes(attributes)
|
, m_attributes(attributes)
|
||||||
@ -160,7 +165,7 @@ void Shape::ensure_property_table() const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shape::add_property_to_unique_shape(const FlyString& property_name, u8 attributes)
|
void Shape::add_property_to_unique_shape(const FlyString& property_name, PropertyAttributes attributes)
|
||||||
{
|
{
|
||||||
ASSERT(is_unique());
|
ASSERT(is_unique());
|
||||||
ASSERT(m_property_table);
|
ASSERT(m_property_table);
|
||||||
@ -168,7 +173,7 @@ void Shape::add_property_to_unique_shape(const FlyString& property_name, u8 attr
|
|||||||
m_property_table->set(property_name, { m_property_table->size(), attributes });
|
m_property_table->set(property_name, { m_property_table->size(), attributes });
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shape::reconfigure_property_in_unique_shape(const FlyString& property_name, u8 attributes)
|
void Shape::reconfigure_property_in_unique_shape(const FlyString& property_name, PropertyAttributes attributes)
|
||||||
{
|
{
|
||||||
ASSERT(is_unique());
|
ASSERT(is_unique());
|
||||||
ASSERT(m_property_table);
|
ASSERT(m_property_table);
|
||||||
|
@ -31,35 +31,23 @@
|
|||||||
#include <AK/OwnPtr.h>
|
#include <AK/OwnPtr.h>
|
||||||
#include <LibJS/Forward.h>
|
#include <LibJS/Forward.h>
|
||||||
#include <LibJS/Runtime/Cell.h>
|
#include <LibJS/Runtime/Cell.h>
|
||||||
|
#include <LibJS/Runtime/PropertyAttributes.h>
|
||||||
#include <LibJS/Runtime/Value.h>
|
#include <LibJS/Runtime/Value.h>
|
||||||
|
|
||||||
namespace JS {
|
namespace JS {
|
||||||
|
|
||||||
struct Attribute {
|
|
||||||
enum {
|
|
||||||
Configurable = 1 << 0,
|
|
||||||
Enumerable = 1 << 1,
|
|
||||||
Writable = 1 << 2,
|
|
||||||
HasGet = 1 << 3,
|
|
||||||
HasSet = 1 << 4,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const u8 default_attributes = Attribute::Configurable | Attribute::Writable | Attribute::Enumerable;
|
|
||||||
|
|
||||||
struct PropertyMetadata {
|
struct PropertyMetadata {
|
||||||
size_t offset { 0 };
|
size_t offset { 0 };
|
||||||
u8 attributes { 0 };
|
PropertyAttributes attributes { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TransitionKey {
|
struct TransitionKey {
|
||||||
FlyString property_name;
|
FlyString property_name;
|
||||||
u8 attributes { 0 };
|
PropertyAttributes attributes { 0 };
|
||||||
|
|
||||||
bool operator==(const TransitionKey& other) const
|
bool operator==(const TransitionKey& other) const
|
||||||
{
|
{
|
||||||
return property_name == other.property_name
|
return property_name == other.property_name && attributes == other.attributes;
|
||||||
&& attributes == other.attributes;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,11 +63,11 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
Shape();
|
Shape();
|
||||||
Shape(Shape* previous_shape, const FlyString& property_name, u8 attributes, TransitionType);
|
Shape(Shape* previous_shape, const FlyString& property_name, PropertyAttributes attributes, TransitionType);
|
||||||
Shape(Shape* previous_shape, Object* new_prototype);
|
Shape(Shape* previous_shape, Object* new_prototype);
|
||||||
|
|
||||||
Shape* create_put_transition(const FlyString& name, u8 attributes);
|
Shape* create_put_transition(const FlyString& name, PropertyAttributes attributes);
|
||||||
Shape* create_configure_transition(const FlyString& name, u8 attributes);
|
Shape* create_configure_transition(const FlyString& name, PropertyAttributes attributes);
|
||||||
Shape* create_prototype_transition(Object* new_prototype);
|
Shape* create_prototype_transition(Object* new_prototype);
|
||||||
|
|
||||||
bool is_unique() const { return m_unique; }
|
bool is_unique() const { return m_unique; }
|
||||||
@ -102,8 +90,8 @@ public:
|
|||||||
void set_prototype_without_transition(Object* new_prototype) { m_prototype = new_prototype; }
|
void set_prototype_without_transition(Object* new_prototype) { m_prototype = new_prototype; }
|
||||||
|
|
||||||
void remove_property_from_unique_shape(const FlyString&, size_t offset);
|
void remove_property_from_unique_shape(const FlyString&, size_t offset);
|
||||||
void add_property_to_unique_shape(const FlyString&, u8 attributes);
|
void add_property_to_unique_shape(const FlyString&, PropertyAttributes attributes);
|
||||||
void reconfigure_property_in_unique_shape(const FlyString& property_name, u8 attributes);
|
void reconfigure_property_in_unique_shape(const FlyString& property_name, PropertyAttributes attributes);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual const char* class_name() const override { return "Shape"; }
|
virtual const char* class_name() const override { return "Shape"; }
|
||||||
@ -116,7 +104,7 @@ private:
|
|||||||
HashMap<TransitionKey, Shape*> m_forward_transitions;
|
HashMap<TransitionKey, Shape*> m_forward_transitions;
|
||||||
Shape* m_previous { nullptr };
|
Shape* m_previous { nullptr };
|
||||||
FlyString m_property_name;
|
FlyString m_property_name;
|
||||||
u8 m_attributes { 0 };
|
PropertyAttributes m_attributes { 0 };
|
||||||
bool m_unique { false };
|
bool m_unique { false };
|
||||||
Object* m_prototype { nullptr };
|
Object* m_prototype { nullptr };
|
||||||
TransitionType m_transition_type { TransitionType::Invalid };
|
TransitionType m_transition_type { TransitionType::Invalid };
|
||||||
@ -128,6 +116,6 @@ template<>
|
|||||||
struct AK::Traits<JS::TransitionKey> : public GenericTraits<JS::TransitionKey> {
|
struct AK::Traits<JS::TransitionKey> : public GenericTraits<JS::TransitionKey> {
|
||||||
static unsigned hash(const JS::TransitionKey& key)
|
static unsigned hash(const JS::TransitionKey& key)
|
||||||
{
|
{
|
||||||
return pair_int_hash(int_hash(key.attributes), key.property_name.hash());
|
return pair_int_hash(key.attributes.bits(), key.property_name.hash());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user