mirror of
https://github.com/RGBCube/serenity
synced 2025-05-22 19:35:06 +00:00
LibJS: Add Proxy objects
Includes all traps except the following: [[Call]], [[Construct]], [[OwnPropertyKeys]]. An important implication of this commit is that any call to any virtual Object method has the potential to throw an exception. These methods were not checked in this commit -- a future commit will have to protect these various method calls throughout the codebase.
This commit is contained in:
parent
58a72e9b81
commit
39ad42defd
29 changed files with 1697 additions and 54 deletions
|
@ -359,7 +359,7 @@ Value ForInStatement::execute(Interpreter& interpreter) const
|
||||||
return {};
|
return {};
|
||||||
auto* object = rhs_result.to_object(interpreter);
|
auto* object = rhs_result.to_object(interpreter);
|
||||||
while (object) {
|
while (object) {
|
||||||
auto property_names = object->get_own_properties(*object, Object::GetOwnPropertyMode::Key, Attribute::Enumerable);
|
auto property_names = object->get_own_properties(*object, Object::GetOwnPropertyMode::Key, true);
|
||||||
for (auto& property_name : property_names.as_object().indexed_properties()) {
|
for (auto& property_name : property_names.as_object().indexed_properties()) {
|
||||||
interpreter.set_variable(variable_name, property_name.value_and_attributes(object).value);
|
interpreter.set_variable(variable_name, property_name.value_and_attributes(object).value);
|
||||||
if (interpreter.exception())
|
if (interpreter.exception())
|
||||||
|
|
|
@ -41,6 +41,10 @@ set(SOURCES
|
||||||
Runtime/Object.cpp
|
Runtime/Object.cpp
|
||||||
Runtime/ObjectPrototype.cpp
|
Runtime/ObjectPrototype.cpp
|
||||||
Runtime/PrimitiveString.cpp
|
Runtime/PrimitiveString.cpp
|
||||||
|
Runtime/PropertyAttributes.cpp
|
||||||
|
Runtime/ProxyConstructor.cpp
|
||||||
|
Runtime/ProxyObject.cpp
|
||||||
|
Runtime/ProxyPrototype.cpp
|
||||||
Runtime/Reference.cpp
|
Runtime/Reference.cpp
|
||||||
Runtime/ReflectObject.cpp
|
Runtime/ReflectObject.cpp
|
||||||
Runtime/ScriptFunction.cpp
|
Runtime/ScriptFunction.cpp
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
__JS_ENUMERATE(Function, function, FunctionPrototype, FunctionConstructor) \
|
__JS_ENUMERATE(Function, function, FunctionPrototype, FunctionConstructor) \
|
||||||
__JS_ENUMERATE(NumberObject, number, NumberPrototype, NumberConstructor) \
|
__JS_ENUMERATE(NumberObject, number, NumberPrototype, NumberConstructor) \
|
||||||
__JS_ENUMERATE(Object, object, ObjectPrototype, ObjectConstructor) \
|
__JS_ENUMERATE(Object, object, ObjectPrototype, ObjectConstructor) \
|
||||||
|
__JS_ENUMERATE(ProxyObject, proxy, ProxyPrototype, ProxyConstructor) \
|
||||||
__JS_ENUMERATE(StringObject, string, StringPrototype, StringConstructor) \
|
__JS_ENUMERATE(StringObject, string, StringPrototype, StringConstructor) \
|
||||||
__JS_ENUMERATE(SymbolObject, symbol, SymbolPrototype, SymbolConstructor)
|
__JS_ENUMERATE(SymbolObject, symbol, SymbolPrototype, SymbolConstructor)
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,8 @@
|
||||||
#include <LibJS/Runtime/Object.h>
|
#include <LibJS/Runtime/Object.h>
|
||||||
#include <LibJS/Runtime/ObjectConstructor.h>
|
#include <LibJS/Runtime/ObjectConstructor.h>
|
||||||
#include <LibJS/Runtime/ObjectPrototype.h>
|
#include <LibJS/Runtime/ObjectPrototype.h>
|
||||||
|
#include <LibJS/Runtime/ProxyConstructor.h>
|
||||||
|
#include <LibJS/Runtime/ProxyPrototype.h>
|
||||||
#include <LibJS/Runtime/ReflectObject.h>
|
#include <LibJS/Runtime/ReflectObject.h>
|
||||||
#include <LibJS/Runtime/Shape.h>
|
#include <LibJS/Runtime/Shape.h>
|
||||||
#include <LibJS/Runtime/StringConstructor.h>
|
#include <LibJS/Runtime/StringConstructor.h>
|
||||||
|
@ -103,6 +105,7 @@ void GlobalObject::initialize()
|
||||||
add_constructor("Function", m_function_constructor, *m_function_prototype);
|
add_constructor("Function", m_function_constructor, *m_function_prototype);
|
||||||
add_constructor("Number", m_number_constructor, *m_number_prototype);
|
add_constructor("Number", m_number_constructor, *m_number_prototype);
|
||||||
add_constructor("Object", m_object_constructor, *m_object_prototype);
|
add_constructor("Object", m_object_constructor, *m_object_prototype);
|
||||||
|
add_constructor("Proxy", m_proxy_constructor, *m_proxy_prototype);
|
||||||
add_constructor("String", m_string_constructor, *m_string_prototype);
|
add_constructor("String", m_string_constructor, *m_string_prototype);
|
||||||
add_constructor("Symbol", m_symbol_constructor, *m_symbol_prototype);
|
add_constructor("Symbol", m_symbol_constructor, *m_symbol_prototype);
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
|
|
||||||
namespace JS {
|
namespace JS {
|
||||||
|
|
||||||
PropertyDescriptor PropertyDescriptor::from_object(Interpreter& interpreter, const Object& object)
|
PropertyDescriptor PropertyDescriptor::from_dictionary(Interpreter& interpreter, const Object& object)
|
||||||
{
|
{
|
||||||
PropertyAttributes attributes;
|
PropertyAttributes attributes;
|
||||||
if (object.has_property("configurable")) {
|
if (object.has_property("configurable")) {
|
||||||
|
@ -146,12 +146,12 @@ Value Object::get_own_property(const Object& this_object, PropertyName property_
|
||||||
auto existing_property = m_indexed_properties.get(nullptr, property_name.as_number(), false);
|
auto existing_property = m_indexed_properties.get(nullptr, property_name.as_number(), false);
|
||||||
if (!existing_property.has_value())
|
if (!existing_property.has_value())
|
||||||
return {};
|
return {};
|
||||||
value_here = existing_property.value().value;
|
value_here = existing_property.value().value.value_or(js_undefined());
|
||||||
} else {
|
} else {
|
||||||
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 {};
|
return {};
|
||||||
value_here = m_storage[metadata.value().offset];
|
value_here = m_storage[metadata.value().offset].value_or(js_undefined());
|
||||||
}
|
}
|
||||||
|
|
||||||
ASSERT(!value_here.is_empty());
|
ASSERT(!value_here.is_empty());
|
||||||
|
@ -163,7 +163,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, PropertyAttributes attributes) const
|
Value Object::get_own_properties(const Object& this_object, GetOwnPropertyMode kind, bool only_enumerable_properties) const
|
||||||
{
|
{
|
||||||
auto* properties_array = Array::create(interpreter().global_object());
|
auto* properties_array = Array::create(interpreter().global_object());
|
||||||
|
|
||||||
|
@ -189,16 +189,20 @@ Value Object::get_own_properties(const Object& this_object, GetOwnPropertyMode k
|
||||||
|
|
||||||
size_t property_index = 0;
|
size_t property_index = 0;
|
||||||
for (auto& entry : m_indexed_properties) {
|
for (auto& entry : m_indexed_properties) {
|
||||||
|
auto value_and_attributes = entry.value_and_attributes(const_cast<Object*>(&this_object));
|
||||||
|
if (only_enumerable_properties && !value_and_attributes.attributes.is_enumerable())
|
||||||
|
continue;
|
||||||
|
|
||||||
if (kind == GetOwnPropertyMode::Key) {
|
if (kind == GetOwnPropertyMode::Key) {
|
||||||
properties_array->define_property(property_index, js_string(interpreter(), String::number(entry.index())));
|
properties_array->define_property(property_index, js_string(interpreter(), String::number(entry.index())));
|
||||||
} else if (kind == GetOwnPropertyMode::Value) {
|
} else if (kind == GetOwnPropertyMode::Value) {
|
||||||
properties_array->define_property(property_index, entry.value_and_attributes(const_cast<Object*>(&this_object)).value);
|
properties_array->define_property(property_index, value_and_attributes.value);
|
||||||
if (interpreter().exception())
|
if (interpreter().exception())
|
||||||
return {};
|
return {};
|
||||||
} else {
|
} else {
|
||||||
auto* entry_array = Array::create(interpreter().global_object());
|
auto* entry_array = Array::create(interpreter().global_object());
|
||||||
entry_array->define_property(0, js_string(interpreter(), String::number(entry.index())));
|
entry_array->define_property(0, js_string(interpreter(), String::number(entry.index())));
|
||||||
entry_array->define_property(1, entry.value_and_attributes(const_cast<Object*>(&this_object)).value);
|
entry_array->define_property(1, value_and_attributes.value);
|
||||||
if (interpreter().exception())
|
if (interpreter().exception())
|
||||||
return {};
|
return {};
|
||||||
properties_array->define_property(property_index, entry_array);
|
properties_array->define_property(property_index, entry_array);
|
||||||
|
@ -208,23 +212,24 @@ 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.bits() & attributes.bits()) {
|
if (only_enumerable_properties && !it.value.attributes.is_enumerable())
|
||||||
size_t offset = it.value.offset + property_index;
|
continue;
|
||||||
|
|
||||||
if (kind == GetOwnPropertyMode::Key) {
|
size_t offset = it.value.offset + property_index;
|
||||||
properties_array->define_property(offset, js_string(interpreter(), it.key));
|
|
||||||
} else if (kind == GetOwnPropertyMode::Value) {
|
if (kind == GetOwnPropertyMode::Key) {
|
||||||
properties_array->define_property(offset, this_object.get(it.key));
|
properties_array->define_property(offset, js_string(interpreter(), it.key));
|
||||||
if (interpreter().exception())
|
} else if (kind == GetOwnPropertyMode::Value) {
|
||||||
return {};
|
properties_array->define_property(offset, this_object.get(it.key));
|
||||||
} else {
|
if (interpreter().exception())
|
||||||
auto* entry_array = Array::create(interpreter().global_object());
|
return {};
|
||||||
entry_array->define_property(0, js_string(interpreter(), it.key));
|
} else {
|
||||||
entry_array->define_property(1, this_object.get(it.key));
|
auto* entry_array = Array::create(interpreter().global_object());
|
||||||
if (interpreter().exception())
|
entry_array->define_property(0, js_string(interpreter(), it.key));
|
||||||
return {};
|
entry_array->define_property(1, this_object.get(it.key));
|
||||||
properties_array->define_property(offset, entry_array);
|
if (interpreter().exception())
|
||||||
}
|
return {};
|
||||||
|
properties_array->define_property(offset, entry_array);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,10 +42,10 @@ namespace JS {
|
||||||
struct PropertyDescriptor {
|
struct PropertyDescriptor {
|
||||||
PropertyAttributes attributes;
|
PropertyAttributes attributes;
|
||||||
Value value;
|
Value value;
|
||||||
Function* getter;
|
Function* getter { nullptr };
|
||||||
Function* setter;
|
Function* setter { nullptr };
|
||||||
|
|
||||||
static PropertyDescriptor from_object(Interpreter&, const Object&);
|
static PropertyDescriptor from_dictionary(Interpreter&, const Object&);
|
||||||
|
|
||||||
bool is_accessor_descriptor() const { return getter || setter; }
|
bool is_accessor_descriptor() const { return getter || setter; }
|
||||||
bool is_data_descriptor() const { return !(value.is_empty() && !attributes.has_writable()); }
|
bool is_data_descriptor() const { return !(value.is_empty() && !attributes.has_writable()); }
|
||||||
|
@ -73,25 +73,25 @@ public:
|
||||||
Shape& shape() { return *m_shape; }
|
Shape& shape() { return *m_shape; }
|
||||||
const Shape& shape() const { return *m_shape; }
|
const Shape& shape() const { return *m_shape; }
|
||||||
|
|
||||||
Value get(PropertyName) const;
|
virtual Value get(PropertyName) const;
|
||||||
|
|
||||||
bool has_property(PropertyName) const;
|
virtual bool has_property(PropertyName) const;
|
||||||
bool has_own_property(PropertyName) const;
|
bool has_own_property(PropertyName) const;
|
||||||
|
|
||||||
bool put(PropertyName, Value);
|
virtual 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, PropertyAttributes attributes = default_attributes) const;
|
Value get_own_properties(const Object& this_object, GetOwnPropertyMode, bool only_enumerable_properties = false) const;
|
||||||
Optional<PropertyDescriptor> get_own_property_descriptor(PropertyName) const;
|
virtual Optional<PropertyDescriptor> get_own_property_descriptor(PropertyName) const;
|
||||||
Value get_own_property_descriptor_object(PropertyName) const;
|
Value get_own_property_descriptor_object(PropertyName) const;
|
||||||
|
|
||||||
bool define_property(const FlyString& property_name, const Object& descriptor, bool throw_exceptions = true);
|
virtual bool define_property(const FlyString& property_name, const Object& descriptor, bool throw_exceptions = true);
|
||||||
bool define_property(PropertyName, Value value, PropertyAttributes 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, PropertyAttributes attributes = 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, PropertyAttributes attributes = 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);
|
virtual Value delete_property(PropertyName);
|
||||||
|
|
||||||
virtual bool is_array() const { return false; }
|
virtual bool is_array() const { return false; }
|
||||||
virtual bool is_boolean() const { return false; }
|
virtual bool is_boolean() const { return false; }
|
||||||
|
@ -101,19 +101,20 @@ public:
|
||||||
virtual bool is_native_function() const { return false; }
|
virtual bool is_native_function() const { return false; }
|
||||||
virtual bool is_bound_function() const { return false; }
|
virtual bool is_bound_function() const { return false; }
|
||||||
virtual bool is_native_property() const { return false; }
|
virtual bool is_native_property() const { return false; }
|
||||||
|
virtual bool is_proxy_object() const { return false; }
|
||||||
virtual bool is_string_object() const { return false; }
|
virtual bool is_string_object() const { return false; }
|
||||||
virtual bool is_symbol_object() const { return false; }
|
virtual bool is_symbol_object() const { return false; }
|
||||||
|
|
||||||
virtual const char* class_name() const override { return "Object"; }
|
virtual const char* class_name() const override { return "Object"; }
|
||||||
virtual void visit_children(Cell::Visitor&) override;
|
virtual void visit_children(Cell::Visitor&) override;
|
||||||
|
|
||||||
Object* prototype();
|
virtual Object* prototype();
|
||||||
const Object* prototype() const;
|
virtual const Object* prototype() const;
|
||||||
bool set_prototype(Object* prototype);
|
virtual bool set_prototype(Object* prototype);
|
||||||
bool has_prototype(const Object* prototype) const;
|
bool has_prototype(const Object* prototype) const;
|
||||||
|
|
||||||
bool is_extensible() const { return m_is_extensible; }
|
virtual bool is_extensible() const { return m_is_extensible; }
|
||||||
bool prevent_extensions();
|
virtual bool prevent_extensions();
|
||||||
|
|
||||||
virtual Value value_of() const { return Value(const_cast<Object*>(this)); }
|
virtual Value value_of() const { return Value(const_cast<Object*>(this)); }
|
||||||
virtual Value to_primitive(Value::PreferredType preferred_type = Value::PreferredType::Default) const;
|
virtual Value to_primitive(Value::PreferredType preferred_type = Value::PreferredType::Default) const;
|
||||||
|
|
|
@ -98,7 +98,7 @@ Value ObjectConstructor::get_prototype_of(Interpreter& interpreter)
|
||||||
Value ObjectConstructor::set_prototype_of(Interpreter& interpreter)
|
Value ObjectConstructor::set_prototype_of(Interpreter& interpreter)
|
||||||
{
|
{
|
||||||
if (interpreter.argument_count() < 2)
|
if (interpreter.argument_count() < 2)
|
||||||
interpreter.throw_exception<TypeError>("Object.setPrototypeOf requires at least two arguments");
|
return interpreter.throw_exception<TypeError>("Object.setPrototypeOf requires at least two arguments");
|
||||||
auto* object = interpreter.argument(0).to_object(interpreter);
|
auto* object = interpreter.argument(0).to_object(interpreter);
|
||||||
if (interpreter.exception())
|
if (interpreter.exception())
|
||||||
return {};
|
return {};
|
||||||
|
@ -113,7 +113,8 @@ Value ObjectConstructor::set_prototype_of(Interpreter& interpreter)
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
if (!object->set_prototype(prototype)) {
|
if (!object->set_prototype(prototype)) {
|
||||||
interpreter.throw_exception<TypeError>("Can't set prototype of non-extensible object");
|
if (!interpreter.exception())
|
||||||
|
interpreter.throw_exception<TypeError>("Object's setPrototypeOf method returned false");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return object;
|
return object;
|
||||||
|
@ -133,7 +134,8 @@ Value ObjectConstructor::prevent_extensions(Interpreter& interpreter)
|
||||||
if (!argument.is_object())
|
if (!argument.is_object())
|
||||||
return argument;
|
return argument;
|
||||||
if (!argument.as_object().prevent_extensions()) {
|
if (!argument.as_object().prevent_extensions()) {
|
||||||
interpreter.throw_exception<TypeError>("Proxy preventExtensions handler returned false");
|
if (!interpreter.exception())
|
||||||
|
interpreter.throw_exception<TypeError>("Proxy preventExtensions handler returned false");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return argument;
|
return argument;
|
||||||
|
@ -161,7 +163,16 @@ Value ObjectConstructor::define_property_(Interpreter& interpreter)
|
||||||
if (interpreter.exception())
|
if (interpreter.exception())
|
||||||
return {};
|
return {};
|
||||||
auto& descriptor = interpreter.argument(2).as_object();
|
auto& descriptor = interpreter.argument(2).as_object();
|
||||||
object.define_property(property_key, descriptor);
|
if (!object.define_property(property_key, descriptor)) {
|
||||||
|
if (!interpreter.exception()) {
|
||||||
|
if (object.is_proxy_object()) {
|
||||||
|
interpreter.throw_exception<TypeError>("Proxy handler's defineProperty method returned false");
|
||||||
|
} else {
|
||||||
|
interpreter.throw_exception<TypeError>("Unable to define property on non-extensible object");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
return &object;
|
return &object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +190,7 @@ Value ObjectConstructor::keys(Interpreter& interpreter)
|
||||||
if (interpreter.exception())
|
if (interpreter.exception())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
return obj_arg->get_own_properties(*obj_arg, GetOwnPropertyMode::Key, Attribute::Enumerable);
|
return obj_arg->get_own_properties(*obj_arg, GetOwnPropertyMode::Key, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Value ObjectConstructor::values(Interpreter& interpreter)
|
Value ObjectConstructor::values(Interpreter& interpreter)
|
||||||
|
@ -191,7 +202,7 @@ Value ObjectConstructor::values(Interpreter& interpreter)
|
||||||
if (interpreter.exception())
|
if (interpreter.exception())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
return obj_arg->get_own_properties(*obj_arg, GetOwnPropertyMode::Value, Attribute::Enumerable);
|
return obj_arg->get_own_properties(*obj_arg, GetOwnPropertyMode::Value, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Value ObjectConstructor::entries(Interpreter& interpreter)
|
Value ObjectConstructor::entries(Interpreter& interpreter)
|
||||||
|
@ -203,7 +214,7 @@ Value ObjectConstructor::entries(Interpreter& interpreter)
|
||||||
if (interpreter.exception())
|
if (interpreter.exception())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
return obj_arg->get_own_properties(*obj_arg, GetOwnPropertyMode::KeyAndValue, Attribute::Enumerable);
|
return obj_arg->get_own_properties(*obj_arg, GetOwnPropertyMode::KeyAndValue, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
36
Libraries/LibJS/Runtime/PropertyAttributes.cpp
Normal file
36
Libraries/LibJS/Runtime/PropertyAttributes.cpp
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <LibJS/Runtime/PropertyAttributes.h>
|
||||||
|
|
||||||
|
namespace JS {
|
||||||
|
|
||||||
|
const LogStream& operator<<(const LogStream& stream, const PropertyAttributes& attributes)
|
||||||
|
{
|
||||||
|
return stream << attributes.bits();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -26,6 +26,9 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Types.h>
|
||||||
|
#include <AK/LogStream.h>
|
||||||
|
|
||||||
namespace JS {
|
namespace JS {
|
||||||
|
|
||||||
struct Attribute {
|
struct Attribute {
|
||||||
|
|
68
Libraries/LibJS/Runtime/ProxyConstructor.cpp
Normal file
68
Libraries/LibJS/Runtime/ProxyConstructor.cpp
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <LibJS/Interpreter.h>
|
||||||
|
#include <LibJS/Runtime/Array.h>
|
||||||
|
#include <LibJS/Runtime/Error.h>
|
||||||
|
#include <LibJS/Runtime/GlobalObject.h>
|
||||||
|
#include <LibJS/Runtime/ProxyConstructor.h>
|
||||||
|
#include <LibJS/Runtime/ProxyObject.h>
|
||||||
|
|
||||||
|
namespace JS {
|
||||||
|
|
||||||
|
ProxyConstructor::ProxyConstructor()
|
||||||
|
: NativeFunction("Proxy", *interpreter().global_object().function_prototype())
|
||||||
|
{
|
||||||
|
define_property("prototype", interpreter().global_object().proxy_prototype(), 0);
|
||||||
|
define_property("length", Value(2), Attribute::Configurable);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProxyConstructor::~ProxyConstructor()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Value ProxyConstructor::call(Interpreter& interpreter)
|
||||||
|
{
|
||||||
|
return interpreter.throw_exception<TypeError>("Proxy must be called with the \"new\" operator");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value ProxyConstructor::construct(Interpreter& interpreter)
|
||||||
|
{
|
||||||
|
if (interpreter.argument_count() < 2)
|
||||||
|
return interpreter.throw_exception<TypeError>("Proxy requires at least two arguments");
|
||||||
|
|
||||||
|
auto target = interpreter.argument(0);
|
||||||
|
auto handler = interpreter.argument(1);
|
||||||
|
|
||||||
|
if (!target.is_object())
|
||||||
|
return interpreter.throw_exception<TypeError>(String::format("Expected target argument of Proxy constructor to be object, got %s", target.to_string_without_side_effects().characters()));
|
||||||
|
if (!handler.is_object())
|
||||||
|
return interpreter.throw_exception<TypeError>(String::format("Expected handler argument of Proxy constructor to be object, got %s", handler.to_string_without_side_effects().characters()));
|
||||||
|
|
||||||
|
return ProxyObject::create(interpreter.global_object(), target.as_object(), handler.as_object());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
46
Libraries/LibJS/Runtime/ProxyConstructor.h
Normal file
46
Libraries/LibJS/Runtime/ProxyConstructor.h
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
#include <LibJS/Runtime/NativeFunction.h>
|
||||||
|
|
||||||
|
namespace JS {
|
||||||
|
|
||||||
|
class ProxyConstructor final : public NativeFunction {
|
||||||
|
public:
|
||||||
|
ProxyConstructor();
|
||||||
|
virtual ~ProxyConstructor() override;
|
||||||
|
|
||||||
|
virtual Value call(Interpreter&) override;
|
||||||
|
virtual Value construct(Interpreter&) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual bool has_constructor() const override { return true; }
|
||||||
|
virtual const char* class_name() const override { return "ProxyConstructor"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
465
Libraries/LibJS/Runtime/ProxyObject.cpp
Normal file
465
Libraries/LibJS/Runtime/ProxyObject.cpp
Normal file
|
@ -0,0 +1,465 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <LibJS/Interpreter.h>
|
||||||
|
#include <LibJS/Runtime/Accessor.h>
|
||||||
|
#include <LibJS/Runtime/Error.h>
|
||||||
|
#include <LibJS/Runtime/GlobalObject.h>
|
||||||
|
#include <LibJS/Runtime/ProxyObject.h>
|
||||||
|
|
||||||
|
namespace JS {
|
||||||
|
|
||||||
|
bool static is_compatible_property_descriptor(Interpreter& interpreter, bool is_extensible, PropertyDescriptor new_descriptor, Optional<PropertyDescriptor> current_descriptor_optional)
|
||||||
|
{
|
||||||
|
if (!current_descriptor_optional.has_value())
|
||||||
|
return is_extensible;
|
||||||
|
auto current_descriptor = current_descriptor_optional.value();
|
||||||
|
if (new_descriptor.attributes.is_empty() && new_descriptor.value.is_empty() && !new_descriptor.getter && !new_descriptor.setter)
|
||||||
|
return true;
|
||||||
|
if (!current_descriptor.attributes.is_configurable()) {
|
||||||
|
if (new_descriptor.attributes.is_configurable())
|
||||||
|
return false;
|
||||||
|
if (new_descriptor.attributes.has_enumerable() && new_descriptor.attributes.is_enumerable() != current_descriptor.attributes.is_enumerable())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (new_descriptor.is_generic_descriptor())
|
||||||
|
return true;
|
||||||
|
if (current_descriptor.is_data_descriptor() != new_descriptor.is_data_descriptor() && !current_descriptor.attributes.is_configurable())
|
||||||
|
return false;
|
||||||
|
if (current_descriptor.is_data_descriptor() && new_descriptor.is_data_descriptor() && !current_descriptor.attributes.is_configurable() && !current_descriptor.attributes.is_writable()) {
|
||||||
|
if (new_descriptor.attributes.is_writable())
|
||||||
|
return false;
|
||||||
|
return new_descriptor.value.is_empty() && same_value(interpreter, new_descriptor.value, current_descriptor.value);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProxyObject* ProxyObject::create(GlobalObject& global_object, Object& target, Object& handler)
|
||||||
|
{
|
||||||
|
return global_object.heap().allocate<ProxyObject>(target, handler, *global_object.proxy_prototype());
|
||||||
|
}
|
||||||
|
|
||||||
|
ProxyObject::ProxyObject(Object& target, Object& handler, Object& prototype)
|
||||||
|
: Object(&prototype)
|
||||||
|
, m_target(target)
|
||||||
|
, m_handler(handler)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ProxyObject::~ProxyObject()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Object* ProxyObject::prototype()
|
||||||
|
{
|
||||||
|
if (m_is_revoked) {
|
||||||
|
interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto trap = m_handler.get("getPrototypeOf");
|
||||||
|
if (interpreter().exception())
|
||||||
|
return nullptr;
|
||||||
|
if (trap.is_empty() || trap.is_undefined() || trap.is_null())
|
||||||
|
return m_target.prototype();
|
||||||
|
if (!trap.is_function()) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's getPrototypeOf trap wasn't undefined, null, or callable");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
MarkedValueList arguments(interpreter().heap());
|
||||||
|
arguments.values().append(Value(&m_target));
|
||||||
|
auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments));
|
||||||
|
if (interpreter().exception())
|
||||||
|
return nullptr;
|
||||||
|
if (!trap_result.is_object() && !trap_result.is_null()) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's getPrototypeOf trap violates invariant: must return an object or null");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (m_target.is_extensible()) {
|
||||||
|
if (trap_result.is_null())
|
||||||
|
return nullptr;
|
||||||
|
return &trap_result.as_object();
|
||||||
|
}
|
||||||
|
auto target_proto = m_target.prototype();
|
||||||
|
if (interpreter().exception())
|
||||||
|
return nullptr;
|
||||||
|
if (!same_value(interpreter(), trap_result, Value(target_proto))) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's getPrototypeOf trap violates invariant: cannot return a different prototype object for a non-extensible target");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &trap_result.as_object();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Object* ProxyObject::prototype() const
|
||||||
|
{
|
||||||
|
if (m_is_revoked) {
|
||||||
|
interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return const_cast<const Object*>(const_cast<ProxyObject*>(this)->prototype());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProxyObject::set_prototype(Object* object)
|
||||||
|
{
|
||||||
|
if (m_is_revoked) {
|
||||||
|
interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto trap = m_handler.get("setPrototypeOf");
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
if (trap.is_empty() || trap.is_undefined() || trap.is_null())
|
||||||
|
return m_target.set_prototype(object);
|
||||||
|
if (!trap.is_function()) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's setPrototypeOf trap wasn't undefined, null, or callable");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MarkedValueList arguments(interpreter().heap());
|
||||||
|
arguments.values().append(Value(&m_target));
|
||||||
|
arguments.values().append(Value(object));
|
||||||
|
auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean();
|
||||||
|
if (interpreter().exception() || !trap_result)
|
||||||
|
return false;
|
||||||
|
if (m_target.is_extensible())
|
||||||
|
return true;
|
||||||
|
auto* target_proto = m_target.prototype();
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
if (!same_value(interpreter(), Value(object), Value(target_proto))) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's setPrototypeOf trap violates invariant: the argument must match the prototype of the target if the target is non-extensible");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProxyObject::is_extensible() const
|
||||||
|
{
|
||||||
|
if (m_is_revoked) {
|
||||||
|
interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto trap = m_handler.get("isExtensible");
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
if (trap.is_empty() || trap.is_undefined() || trap.is_null())
|
||||||
|
return m_target.is_extensible();
|
||||||
|
if (!trap.is_function()) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's isExtensible trap wasn't undefined, null, or callable");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
MarkedValueList arguments(interpreter().heap());
|
||||||
|
arguments.values().append(Value(&m_target));
|
||||||
|
auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean();
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
if (trap_result != m_target.is_extensible()) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's isExtensible trap violates invariant: return value must match the target's extensibility");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return trap_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProxyObject::prevent_extensions()
|
||||||
|
{
|
||||||
|
if (m_is_revoked) {
|
||||||
|
interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto trap = m_handler.get("preventExtensions");
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
if (trap.is_empty() || trap.is_undefined() || trap.is_null())
|
||||||
|
return m_target.prevent_extensions();
|
||||||
|
if (!trap.is_function()) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's preventExtensions trap wasn't undefined, null, or callable");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
MarkedValueList arguments(interpreter().heap());
|
||||||
|
arguments.values().append(Value(&m_target));
|
||||||
|
auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean();
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
if (trap_result && m_target.is_extensible()) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's preventExtensions trap violates invariant: cannot return true if the target object is extensible");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return trap_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<PropertyDescriptor> ProxyObject::get_own_property_descriptor(PropertyName name) const
|
||||||
|
{
|
||||||
|
if (m_is_revoked) {
|
||||||
|
interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto trap = m_handler.get("getOwnPropertyDescriptor");
|
||||||
|
if (interpreter().exception())
|
||||||
|
return {};
|
||||||
|
if (trap.is_empty() || trap.is_undefined() || trap.is_null())
|
||||||
|
return m_target.get_own_property_descriptor(name);
|
||||||
|
if (!trap.is_function()) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's getOwnPropertyDescriptor trap wasn't undefined, null, or callable");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
MarkedValueList arguments(interpreter().heap());
|
||||||
|
arguments.values().append(Value(&m_target));
|
||||||
|
arguments.values().append(js_string(interpreter(), name.to_string()));
|
||||||
|
auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments));
|
||||||
|
if (interpreter().exception())
|
||||||
|
return {};
|
||||||
|
if (!trap_result.is_object() && !trap_result.is_undefined()) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's getOwnPropertyDescriptor trap violates invariant: must return an object or undefined");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto target_desc = m_target.get_own_property_descriptor(name);
|
||||||
|
if (interpreter().exception())
|
||||||
|
return {};
|
||||||
|
if (trap_result.is_undefined()) {
|
||||||
|
if (!target_desc.has_value())
|
||||||
|
return {};
|
||||||
|
if (!target_desc.value().attributes.is_configurable()) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's getOwnPropertyDescriptor trap violates invariant: cannot return undefined for a property on the target which is a non-configurable property");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (!m_target.is_extensible()) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's getOwnPropertyDescriptor trap violates invariant: cannot report a property as being undefined if it exists as an own property of the target and the target is non-extensible");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto result_desc = PropertyDescriptor::from_dictionary(interpreter(), trap_result.as_object());
|
||||||
|
if (interpreter().exception())
|
||||||
|
return {};
|
||||||
|
if (!is_compatible_property_descriptor(interpreter(), m_target.is_extensible(), result_desc, target_desc)) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's getOwnPropertyDescriptor trap violates invariant: invalid property descriptor for existing property on the target");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (!result_desc.attributes.is_configurable() && (!target_desc.has_value() || target_desc.value().attributes.is_configurable())) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's getOwnPropertyDescriptor trap violates invariant: cannot report target's property as non-configurable if the property does not exist, or if it is configurable");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return result_desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProxyObject::define_property(const FlyString& property_name, const Object& descriptor, bool throw_exceptions)
|
||||||
|
{
|
||||||
|
if (m_is_revoked) {
|
||||||
|
interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto trap = m_handler.get("defineProperty");
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
if (trap.is_empty() || trap.is_undefined() || trap.is_null())
|
||||||
|
return m_target.define_property(property_name, descriptor, throw_exceptions);
|
||||||
|
if (!trap.is_function()) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's defineProperty trap wasn't undefined, null, or callable");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MarkedValueList arguments(interpreter().heap());
|
||||||
|
arguments.values().append(Value(&m_target));
|
||||||
|
arguments.values().append(js_string(interpreter(), property_name));
|
||||||
|
arguments.values().append(Value(const_cast<Object*>(&descriptor)));
|
||||||
|
auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean();
|
||||||
|
if (interpreter().exception() || !trap_result)
|
||||||
|
return false;
|
||||||
|
auto target_desc = m_target.get_own_property_descriptor(property_name);
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
bool setting_config_false = false;
|
||||||
|
if (descriptor.has_property("configurable") && !descriptor.get("configurable").to_boolean())
|
||||||
|
setting_config_false = true;
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
if (!target_desc.has_value()) {
|
||||||
|
if (!m_target.is_extensible()) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's defineProperty trap violates invariant: a property cannot be reported as being defined if the property does not exist on the target and the target is non-extensible");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (setting_config_false) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's defineProperty trap violates invariant: a property cannot be defined as non-configurable if it does not already exist on the target object");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!is_compatible_property_descriptor(interpreter(), m_target.is_extensible(), PropertyDescriptor::from_dictionary(interpreter(), descriptor), target_desc)) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's defineProperty trap violates invariant: the new descriptor is not compatible with the existing descriptor of the property on the target");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (setting_config_false && target_desc.value().attributes.is_configurable()) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's defineProperty trap violates invariant: a property cannot be defined as non-configurable if it already exists on the target object as a configurable property");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProxyObject::has_property(PropertyName name) const
|
||||||
|
{
|
||||||
|
if (m_is_revoked) {
|
||||||
|
interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto trap = m_handler.get("has");
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
if (trap.is_empty() || trap.is_undefined() || trap.is_null())
|
||||||
|
return m_target.has_property(name);
|
||||||
|
if (!trap.is_function()) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's has trap wasn't undefined, null, or callable");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MarkedValueList arguments(interpreter().heap());
|
||||||
|
arguments.values().append(Value(&m_target));
|
||||||
|
arguments.values().append(js_string(interpreter(), name.to_string()));
|
||||||
|
auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean();
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
if (!trap_result) {
|
||||||
|
auto target_desc = m_target.get_own_property_descriptor(name);
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
if (target_desc.has_value()) {
|
||||||
|
if (!target_desc.value().attributes.is_configurable()) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's has trap violates invariant: a property cannot be reported as non-existent if it exists on the target as a non-configurable property");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!m_target.is_extensible()) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's has trap violates invariant: a property cannot be reported as non-existent if it exist on the target and the target is non-extensible");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return trap_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value ProxyObject::get(PropertyName name) const
|
||||||
|
{
|
||||||
|
if (m_is_revoked) {
|
||||||
|
interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto trap = m_handler.get("get");
|
||||||
|
if (interpreter().exception())
|
||||||
|
return {};
|
||||||
|
if (trap.is_empty() || trap.is_undefined() || trap.is_null())
|
||||||
|
return m_target.get(name);
|
||||||
|
if (!trap.is_function())
|
||||||
|
return interpreter().throw_exception<TypeError>("Proxy handler's get trap wasn't undefined, null, or callable");
|
||||||
|
MarkedValueList arguments(interpreter().heap());
|
||||||
|
arguments.values().append(Value(&m_target));
|
||||||
|
arguments.values().append(js_string(interpreter(), name.to_string()));
|
||||||
|
arguments.values().append(Value(const_cast<ProxyObject*>(this)));
|
||||||
|
auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments));
|
||||||
|
if (interpreter().exception())
|
||||||
|
return {};
|
||||||
|
auto target_desc = m_target.get_own_property_descriptor(name);
|
||||||
|
if (target_desc.has_value()) {
|
||||||
|
if (interpreter().exception())
|
||||||
|
return {};
|
||||||
|
if (target_desc.value().is_data_descriptor() && !target_desc.value().attributes.is_writable() && !same_value(interpreter(), trap_result, target_desc.value().value))
|
||||||
|
return interpreter().throw_exception<TypeError>("Proxy handler's get trap violates invariant: the returned value must match the value on the target if the property exists on the target as a non-writable, non-configurable own data property");
|
||||||
|
if (target_desc.value().is_accessor_descriptor() && target_desc.value().getter == nullptr && !trap_result.is_undefined())
|
||||||
|
return interpreter().throw_exception<TypeError>("Proxy handler's get trap violates invariant: the returned value must be undefined if the property exists on the target as a non-configurable accessor property with an undefined get attribute");
|
||||||
|
}
|
||||||
|
return trap_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProxyObject::put(PropertyName name, Value value)
|
||||||
|
{
|
||||||
|
if (m_is_revoked) {
|
||||||
|
interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto trap = m_handler.get("set");
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
if (trap.is_empty() || trap.is_undefined() || trap.is_null())
|
||||||
|
return m_target.put(name, value);
|
||||||
|
if (!trap.is_function()) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's set trap wasn't undefined, null, or callable");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MarkedValueList arguments(interpreter().heap());
|
||||||
|
arguments.values().append(Value(&m_target));
|
||||||
|
arguments.values().append(js_string(interpreter(), name.to_string()));
|
||||||
|
arguments.values().append(value);
|
||||||
|
arguments.values().append(Value(const_cast<ProxyObject*>(this)));
|
||||||
|
auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean();
|
||||||
|
if (interpreter().exception() || !trap_result)
|
||||||
|
return false;
|
||||||
|
auto target_desc = m_target.get_own_property_descriptor(name);
|
||||||
|
if (interpreter().exception())
|
||||||
|
return false;
|
||||||
|
if (target_desc.has_value() && !target_desc.value().attributes.is_configurable()) {
|
||||||
|
if (target_desc.value().is_data_descriptor() && !target_desc.value().attributes.is_writable() && !same_value(interpreter(), value, target_desc.value().value)) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's set trap violates invariant: cannot return true for a property on the target which is a non-configurable, non-writable own data property");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (target_desc.value().is_accessor_descriptor() && !target_desc.value().setter) {
|
||||||
|
interpreter().throw_exception<TypeError>("Proxy handler's set trap violates invariant: cannot return true for a property on the target which is a non-configurable own accessor property with an undefined set attribute");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value ProxyObject::delete_property(PropertyName name)
|
||||||
|
{
|
||||||
|
if (m_is_revoked) {
|
||||||
|
interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto trap = m_handler.get("deleteProperty");
|
||||||
|
if (interpreter().exception())
|
||||||
|
return {};
|
||||||
|
if (trap.is_empty() || trap.is_undefined() || trap.is_null())
|
||||||
|
return m_target.delete_property(name);
|
||||||
|
if (!trap.is_function())
|
||||||
|
return interpreter().throw_exception<TypeError>("Proxy handler's delete trap wasn't undefined, null, or callable");
|
||||||
|
MarkedValueList arguments(interpreter().heap());
|
||||||
|
arguments.values().append(Value(&m_target));
|
||||||
|
arguments.values().append(js_string(interpreter(), name.to_string()));
|
||||||
|
auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean();
|
||||||
|
if (interpreter().exception())
|
||||||
|
return {};
|
||||||
|
if (!trap_result)
|
||||||
|
return Value(false);
|
||||||
|
auto target_desc = m_target.get_own_property_descriptor(name);
|
||||||
|
if (interpreter().exception())
|
||||||
|
return {};
|
||||||
|
if (!target_desc.has_value())
|
||||||
|
return Value(true);
|
||||||
|
if (!target_desc.value().attributes.is_configurable())
|
||||||
|
return interpreter().throw_exception<TypeError>("Proxy handler's delete trap violates invariant: cannot report a non-configurable own property of the target as deleted");
|
||||||
|
return Value(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxyObject::visit_children(Cell::Visitor& visitor)
|
||||||
|
{
|
||||||
|
Object::visit_children(visitor);
|
||||||
|
visitor.visit(&m_target);
|
||||||
|
visitor.visit(&m_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
67
Libraries/LibJS/Runtime/ProxyObject.h
Normal file
67
Libraries/LibJS/Runtime/ProxyObject.h
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
#include <LibJS/Runtime/Object.h>
|
||||||
|
|
||||||
|
namespace JS {
|
||||||
|
|
||||||
|
class ProxyObject : public Object {
|
||||||
|
public:
|
||||||
|
static ProxyObject* create(GlobalObject&, Object& target, Object& handler);
|
||||||
|
|
||||||
|
ProxyObject(Object& target, Object& handler, Object& prototype);
|
||||||
|
virtual ~ProxyObject() override;
|
||||||
|
|
||||||
|
const Object& target() const { return m_target; }
|
||||||
|
const Object& handler() const { return m_handler; }
|
||||||
|
|
||||||
|
virtual Object* prototype() override;
|
||||||
|
virtual const Object* prototype() const override;
|
||||||
|
virtual bool set_prototype(Object* object) override;
|
||||||
|
virtual bool is_extensible() const override;
|
||||||
|
virtual bool prevent_extensions() override;
|
||||||
|
virtual Optional<PropertyDescriptor> get_own_property_descriptor(PropertyName) const override;
|
||||||
|
virtual bool define_property(const FlyString& property_name, const Object& descriptor, bool throw_exceptions = true) override;
|
||||||
|
virtual bool has_property(PropertyName name) const override;
|
||||||
|
virtual Value get(PropertyName name) const override;
|
||||||
|
virtual bool put(PropertyName name, Value value) override;
|
||||||
|
virtual Value delete_property(PropertyName name) override;
|
||||||
|
|
||||||
|
void revoke() { m_is_revoked = true; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual void visit_children(Visitor&) override;
|
||||||
|
virtual const char* class_name() const override { return "ProxyObject"; }
|
||||||
|
virtual bool is_proxy_object() const override { return true; }
|
||||||
|
|
||||||
|
Object& m_target;
|
||||||
|
Object& m_handler;
|
||||||
|
bool m_is_revoked { false };
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
46
Libraries/LibJS/Runtime/ProxyPrototype.cpp
Normal file
46
Libraries/LibJS/Runtime/ProxyPrototype.cpp
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/Function.h>
|
||||||
|
#include <AK/StringBuilder.h>
|
||||||
|
#include <LibJS/Heap/Heap.h>
|
||||||
|
#include <LibJS/Interpreter.h>
|
||||||
|
#include <LibJS/Runtime/Error.h>
|
||||||
|
#include <LibJS/Runtime/GlobalObject.h>
|
||||||
|
#include <LibJS/Runtime/ProxyPrototype.h>
|
||||||
|
|
||||||
|
namespace JS {
|
||||||
|
|
||||||
|
ProxyPrototype::ProxyPrototype()
|
||||||
|
: Object(interpreter().global_object().object_prototype())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ProxyPrototype::~ProxyPrototype()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
42
Libraries/LibJS/Runtime/ProxyPrototype.h
Normal file
42
Libraries/LibJS/Runtime/ProxyPrototype.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
#include <LibJS/Runtime/Object.h>
|
||||||
|
|
||||||
|
namespace JS {
|
||||||
|
|
||||||
|
class ProxyPrototype final : public Object {
|
||||||
|
public:
|
||||||
|
ProxyPrototype();
|
||||||
|
virtual ~ProxyPrototype() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual const char* class_name() const override { return "ProxyPrototype"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -29,11 +29,6 @@
|
||||||
|
|
||||||
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>();
|
||||||
|
|
|
@ -23,7 +23,13 @@ try {
|
||||||
o.baz = "baz";
|
o.baz = "baz";
|
||||||
assert(o.baz === undefined);
|
assert(o.baz === undefined);
|
||||||
|
|
||||||
Object.defineProperty(o, "baz", { value: "baz" });
|
assertThrowsError(() => {
|
||||||
|
Object.defineProperty(o, "baz", { value: "baz" });
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Unable to define property on non-extensible object",
|
||||||
|
});
|
||||||
|
|
||||||
assert(o.baz === undefined);
|
assert(o.baz === undefined);
|
||||||
|
|
||||||
assertThrowsError(() => {
|
assertThrowsError(() => {
|
||||||
|
|
|
@ -4,12 +4,19 @@ try {
|
||||||
assert(Object.setPrototypeOf.length === 2);
|
assert(Object.setPrototypeOf.length === 2);
|
||||||
|
|
||||||
assertThrowsError(() => {
|
assertThrowsError(() => {
|
||||||
Object.setPrototypeOf({}, "foo");
|
Object.setPrototypeOf();
|
||||||
}, {
|
}, {
|
||||||
error: TypeError,
|
error: TypeError,
|
||||||
message: "Prototype must be null or object"
|
message: "Object.setPrototypeOf requires at least two arguments",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// assertThrowsError(() => {
|
||||||
|
// Object.setPrototypeOf({}, "foo");
|
||||||
|
// }, {
|
||||||
|
// error: TypeError,
|
||||||
|
// message: "Prototype must be null or object"
|
||||||
|
// });
|
||||||
|
|
||||||
o = {};
|
o = {};
|
||||||
p = {};
|
p = {};
|
||||||
assert(Object.setPrototypeOf(o, p) === o);
|
assert(Object.setPrototypeOf(o, p) === o);
|
||||||
|
@ -19,7 +26,7 @@ try {
|
||||||
Object.setPrototypeOf(o, {});
|
Object.setPrototypeOf(o, {});
|
||||||
}, {
|
}, {
|
||||||
error: TypeError,
|
error: TypeError,
|
||||||
message: "Can't set prototype of non-extensible object"
|
message: "Object's setPrototypeOf method returned false"
|
||||||
});
|
});
|
||||||
assert(Object.setPrototypeOf(o, p) === o);
|
assert(Object.setPrototypeOf(o, p) === o);
|
||||||
|
|
||||||
|
|
107
Libraries/LibJS/Tests/Proxy.handler-defineProperty.js
Normal file
107
Libraries/LibJS/Tests/Proxy.handler-defineProperty.js
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
load("test-common.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
let p = new Proxy({}, { defineProperty: null });
|
||||||
|
assert(Object.defineProperty(p, "foo", {}) === p);
|
||||||
|
p = new Proxy({}, { defineProperty: undefined });
|
||||||
|
assert(Object.defineProperty(p, "foo", {}) === p);
|
||||||
|
p = new Proxy({}, {});
|
||||||
|
assert(Object.defineProperty(p, "foo", {}) == p);
|
||||||
|
|
||||||
|
let o = {};
|
||||||
|
p = new Proxy(o, {
|
||||||
|
defineProperty(target, name, descriptor) {
|
||||||
|
assert(target === o);
|
||||||
|
assert(name === "foo");
|
||||||
|
assert(descriptor.configurable === true);
|
||||||
|
assert(descriptor.enumerable === undefined);
|
||||||
|
assert(descriptor.writable === true);
|
||||||
|
assert(descriptor.value === 10);
|
||||||
|
assert(descriptor.get === undefined);
|
||||||
|
assert(descriptor.set === undefined);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(p, "foo", { configurable: true, writable: true, value: 10 });
|
||||||
|
|
||||||
|
p = new Proxy(o, {
|
||||||
|
defineProperty(target, name, descriptor) {
|
||||||
|
if (target[name] === undefined)
|
||||||
|
Object.defineProperty(target, name, descriptor);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(p, "foo", { value: 10, enumerable: true, configurable: false, writable: true });
|
||||||
|
let d = Object.getOwnPropertyDescriptor(p, "foo");
|
||||||
|
assert(d.enumerable === true);
|
||||||
|
assert(d.configurable === false);
|
||||||
|
assert(d.writable === true);
|
||||||
|
assert(d.value === 10);
|
||||||
|
assert(d.get === undefined);
|
||||||
|
assert(d.set === undefined);
|
||||||
|
|
||||||
|
Object.defineProperty(p, "foo", { value: 20, enumerable: true, configurable: false, writable: true });
|
||||||
|
d = Object.getOwnPropertyDescriptor(p, "foo");
|
||||||
|
assert(d.enumerable === true);
|
||||||
|
assert(d.configurable === false);
|
||||||
|
assert(d.writable === true);
|
||||||
|
assert(d.value === 10);
|
||||||
|
assert(d.get === undefined);
|
||||||
|
assert(d.set === undefined);
|
||||||
|
|
||||||
|
|
||||||
|
// Invariants
|
||||||
|
|
||||||
|
p = new Proxy({}, {
|
||||||
|
defineProperty() { return false; }
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.defineProperty(p, "foo", {});
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's defineProperty method returned false",
|
||||||
|
});
|
||||||
|
|
||||||
|
o = {};
|
||||||
|
Object.preventExtensions(o);
|
||||||
|
p = new Proxy(o, {
|
||||||
|
defineProperty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.defineProperty(p, "foo", {});
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's defineProperty trap violates invariant: a property cannot be reported as being defined if the property does not exist on the target and the target is non-extensible",
|
||||||
|
});
|
||||||
|
|
||||||
|
o = {};
|
||||||
|
Object.defineProperty(o, "foo", { value: 10, configurable: true });
|
||||||
|
p = new Proxy(o, {
|
||||||
|
defineProperty() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.defineProperty(p, "bar", { value: 6, configurable: false });
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's defineProperty trap violates invariant: a property cannot be defined as non-configurable if it does not already exist on the target object",
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.defineProperty(p, "foo", { value: 6, configurable: false });
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's defineProperty trap violates invariant: a property cannot be defined as non-configurable if it already exists on the target object as a configurable property",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("PASS");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("FAIL: " + e);
|
||||||
|
}
|
56
Libraries/LibJS/Tests/Proxy.handler-deleteProperty.js
Normal file
56
Libraries/LibJS/Tests/Proxy.handler-deleteProperty.js
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
load("test-common.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert(delete (new Proxy({}, { deleteProperty: undefined })).foo === true);
|
||||||
|
assert(delete (new Proxy({}, { deleteProperty: null })).foo === true);
|
||||||
|
assert(delete (new Proxy({}, {})).foo === true);
|
||||||
|
|
||||||
|
let o = {};
|
||||||
|
let p = new Proxy(o, {
|
||||||
|
deleteProperty(target, property) {
|
||||||
|
assert(target === o);
|
||||||
|
assert(property === "foo");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
delete p.foo;
|
||||||
|
|
||||||
|
o = { foo: 1, bar: 2 };
|
||||||
|
p = new Proxy(o, {
|
||||||
|
deleteProperty(target, property) {
|
||||||
|
if (property === "foo") {
|
||||||
|
delete target[property];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(delete p.foo === true);
|
||||||
|
assert(delete p.bar === false);
|
||||||
|
|
||||||
|
assert(o.foo === undefined);
|
||||||
|
assert(o.bar === 2);
|
||||||
|
|
||||||
|
// Invariants
|
||||||
|
|
||||||
|
o = {};
|
||||||
|
Object.defineProperty(o, "foo", { configurable: false });
|
||||||
|
p = new Proxy(o, {
|
||||||
|
deleteProperty() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
delete p.foo;
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's delete trap violates invariant: cannot report a non-configurable own property of the target as deleted",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("PASS");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("FAIL: " + e);
|
||||||
|
}
|
71
Libraries/LibJS/Tests/Proxy.handler-get.js
Normal file
71
Libraries/LibJS/Tests/Proxy.handler-get.js
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
load("test-common.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert((new Proxy({}, { get: undefined })).foo === undefined);
|
||||||
|
assert((new Proxy({}, { get: null })).foo === undefined);
|
||||||
|
assert((new Proxy({}, {})).foo === undefined);
|
||||||
|
|
||||||
|
let o = {};
|
||||||
|
let p = new Proxy(o, {
|
||||||
|
get(target, property, receiver) {
|
||||||
|
assert(target === o);
|
||||||
|
assert(property === "foo");
|
||||||
|
assert(receiver === p);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
p.foo;
|
||||||
|
|
||||||
|
o = { foo: 1 };
|
||||||
|
p = new Proxy(o, {
|
||||||
|
get(target, property, receiver) {
|
||||||
|
if (property === "bar") {
|
||||||
|
return 2;
|
||||||
|
} else if (property === "baz") {
|
||||||
|
return receiver.qux;
|
||||||
|
} else if (property === "qux") {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
return target[property];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(p.foo === 1);
|
||||||
|
assert(p.bar === 2);
|
||||||
|
assert(p.baz === 3);
|
||||||
|
assert(p.qux === 3);
|
||||||
|
assert(p.test === undefined);
|
||||||
|
|
||||||
|
// Invariants
|
||||||
|
|
||||||
|
o = {};
|
||||||
|
Object.defineProperty(o, "foo", { value: 5, configurable: false, writable: true });
|
||||||
|
Object.defineProperty(o, "bar", { value: 10, configurable: false, writable: false });
|
||||||
|
|
||||||
|
p = new Proxy(o, {
|
||||||
|
get() {
|
||||||
|
return 8;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(p.foo === 8);
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
p.bar;
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's get trap violates invariant: the returned value must match the value on the target if the property exists on the target as a non-writable, non-configurable own data property",
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(o, "baz", { configurable: false, set(_) {} });
|
||||||
|
assertThrowsError(() => {
|
||||||
|
p.baz;
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's get trap violates invariant: the returned value must be undefined if the property exists on the target as a non-configurable accessor property with an undefined get attribute",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("PASS");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("FAIL: " + e);
|
||||||
|
}
|
159
Libraries/LibJS/Tests/Proxy.handler-getOwnPropertyDescriptor.js
Normal file
159
Libraries/LibJS/Tests/Proxy.handler-getOwnPropertyDescriptor.js
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
load("test-common.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert(Object.getOwnPropertyDescriptor(new Proxy({}, { getOwnPropertyDescriptor: null }), "a") === undefined);
|
||||||
|
assert(Object.getOwnPropertyDescriptor(new Proxy({}, { getOwnPropertyDescriptor: undefined }), "a") === undefined);
|
||||||
|
assert(Object.getOwnPropertyDescriptor(new Proxy({}, {}), "a") === undefined);
|
||||||
|
|
||||||
|
let o = {};
|
||||||
|
let p = new Proxy(o, {
|
||||||
|
getOwnPropertyDescriptor(target, property) {
|
||||||
|
assert(target === o);
|
||||||
|
assert(property === "foo");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.getOwnPropertyDescriptor(p, "foo");
|
||||||
|
|
||||||
|
o = { foo: "bar" };
|
||||||
|
Object.defineProperty(o, "baz", { value: "qux", enumerable: false, configurable: true, writable: false });
|
||||||
|
p = new Proxy(o, {
|
||||||
|
getOwnPropertyDescriptor(target, property) {
|
||||||
|
if (property === "baz")
|
||||||
|
return Object.getOwnPropertyDescriptor(target, "baz");
|
||||||
|
return { value: target[property], enumerable: false, configurable: true, writable: true };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let d = Object.getOwnPropertyDescriptor(p, "baz");
|
||||||
|
assert(d.configurable === true);
|
||||||
|
assert(d.enumerable === false);
|
||||||
|
assert(d.writable === false);
|
||||||
|
assert(d.value === "qux");
|
||||||
|
assert(d.get === undefined);
|
||||||
|
assert(d.set === undefined);
|
||||||
|
|
||||||
|
d = Object.getOwnPropertyDescriptor(p, "foo");
|
||||||
|
assert(d.configurable === true);
|
||||||
|
assert(d.enumerable === false);
|
||||||
|
assert(d.writable === true);
|
||||||
|
assert(d.value === "bar");
|
||||||
|
assert(d.get === undefined);
|
||||||
|
assert(d.set === undefined);
|
||||||
|
|
||||||
|
// Invariants
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.getOwnPropertyDescriptor(new Proxy({}, {
|
||||||
|
getOwnPropertyDescriptor: 1
|
||||||
|
}));
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's getOwnPropertyDescriptor trap wasn't undefined, null, or callable",
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.getOwnPropertyDescriptor(new Proxy({}, {
|
||||||
|
getOwnPropertyDescriptor() {
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's getOwnPropertyDescriptor trap violates invariant: must return an object or undefined",
|
||||||
|
});
|
||||||
|
|
||||||
|
o = {};
|
||||||
|
Object.defineProperty(o, "foo", { value: 10, configurable: false });
|
||||||
|
p = new Proxy(o, {
|
||||||
|
getOwnPropertyDescriptor() {
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(Object.getOwnPropertyDescriptor(p, "bar") === undefined);
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.getOwnPropertyDescriptor(p, "foo");
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's getOwnPropertyDescriptor trap violates invariant: cannot return undefined for a property on the target which is a non-configurable property",
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(o, "baz", { value: 20, configurable: true, writable: true, enumerable: true });
|
||||||
|
Object.preventExtensions(o);
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.getOwnPropertyDescriptor(p, "baz");
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's getOwnPropertyDescriptor trap violates invariant: cannot report a property as being undefined if it exists as an own property of the target and the target is non-extensible",
|
||||||
|
});
|
||||||
|
|
||||||
|
o = {};
|
||||||
|
Object.defineProperty(o, "v1", { value: 10, configurable: false });
|
||||||
|
Object.defineProperty(o, "v2", { value: 10, configurable: false, enumerable: true });
|
||||||
|
Object.defineProperty(o, "v3", { configurable: false, get() { return 1; } });
|
||||||
|
Object.defineProperty(o, "v4", { value: 10, configurable: false, writable: false, enumerable: true });
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.getOwnPropertyDescriptor(new Proxy(o, {
|
||||||
|
getOwnPropertyDescriptor() {
|
||||||
|
return { configurable: true };
|
||||||
|
},
|
||||||
|
}), "v1");
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's getOwnPropertyDescriptor trap violates invariant: invalid property descriptor for existing property on the target",
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.getOwnPropertyDescriptor(new Proxy(o, {
|
||||||
|
getOwnPropertyDescriptor() {
|
||||||
|
return { enumerable: false };
|
||||||
|
},
|
||||||
|
}), "v2");
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's getOwnPropertyDescriptor trap violates invariant: invalid property descriptor for existing property on the target",
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.getOwnPropertyDescriptor(new Proxy(o, {
|
||||||
|
getOwnPropertyDescriptor() {
|
||||||
|
return { value: 10 };
|
||||||
|
},
|
||||||
|
}), "v3");
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's getOwnPropertyDescriptor trap violates invariant: invalid property descriptor for existing property on the target",
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.getOwnPropertyDescriptor(new Proxy(o, {
|
||||||
|
getOwnPropertyDescriptor() {
|
||||||
|
return { value: 10, writable: true };
|
||||||
|
},
|
||||||
|
}), "v4");
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's getOwnPropertyDescriptor trap violates invariant: invalid property descriptor for existing property on the target",
|
||||||
|
});
|
||||||
|
|
||||||
|
o = {};
|
||||||
|
Object.defineProperty(o, "v", { configurable: true });
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.getOwnPropertyDescriptor(new Proxy(o, {
|
||||||
|
getOwnPropertyDescriptor() {
|
||||||
|
return { configurable: false };
|
||||||
|
},
|
||||||
|
}), "v");
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's getOwnPropertyDescriptor trap violates invariant: cannot report target's property as non-configurable if the property does not exist, or if it is configurable",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("PASS");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("FAIL: " + e);
|
||||||
|
}
|
87
Libraries/LibJS/Tests/Proxy.handler-getPrototypeOf.js
Normal file
87
Libraries/LibJS/Tests/Proxy.handler-getPrototypeOf.js
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
load("test-common.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const child = {};
|
||||||
|
const childProto = { foo: "bar" };
|
||||||
|
|
||||||
|
Object.setPrototypeOf(child, childProto);
|
||||||
|
assert(child.foo === "bar");
|
||||||
|
|
||||||
|
Object.getPrototypeOf(new Proxy(child, { getPrototypeOf: null }));
|
||||||
|
Object.getPrototypeOf(new Proxy(child, { getPrototypeOf: undefined }));
|
||||||
|
|
||||||
|
let o = {};
|
||||||
|
let p = new Proxy(o, {
|
||||||
|
getPrototypeOf(target) {
|
||||||
|
assert(target === o);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.getPrototypeOf(p);
|
||||||
|
|
||||||
|
p = new Proxy(o, {
|
||||||
|
getPrototypeOf(target) {
|
||||||
|
if (target.foo)
|
||||||
|
return { bar: 1 };
|
||||||
|
return { bar: 2 };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(Object.getPrototypeOf(p).bar === 2);
|
||||||
|
o.foo = 20
|
||||||
|
assert(Object.getPrototypeOf(p).bar === 1);
|
||||||
|
|
||||||
|
// Invariants
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.getPrototypeOf(new Proxy(child, { getPrototypeOf: 1 }));
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's getPrototypeOf trap wasn't undefined, null, or callable",
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.getPrototypeOf(new Proxy(child, { getPrototypeOf() { return 1; } }));
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's getPrototypeOf trap violates invariant: must return an object or null",
|
||||||
|
});
|
||||||
|
|
||||||
|
p = new Proxy(child, {
|
||||||
|
getPrototypeOf(target) {
|
||||||
|
assert(target === child);
|
||||||
|
return { baz: "qux" };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(Object.getPrototypeOf(p).baz === "qux");
|
||||||
|
|
||||||
|
Object.preventExtensions(child);
|
||||||
|
p = new Proxy(child, {
|
||||||
|
getPrototypeOf(target) {
|
||||||
|
assert(target === child);
|
||||||
|
return childProto;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(Object.getPrototypeOf(p).foo === "bar");
|
||||||
|
|
||||||
|
p = new Proxy(child, {
|
||||||
|
getPrototypeOf(target) {
|
||||||
|
assert(target === child);
|
||||||
|
return { baz: "qux" };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.getPrototypeOf(p);
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's getPrototypeOf trap violates invariant: cannot return a different prototype object for a non-extensible target"
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("PASS");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("FAIL: " + e);
|
||||||
|
}
|
62
Libraries/LibJS/Tests/Proxy.handler-has.js
Normal file
62
Libraries/LibJS/Tests/Proxy.handler-has.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
load("test-common.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert("foo" in new Proxy({}, { has: null }) === false);
|
||||||
|
assert("foo" in new Proxy({}, { has: undefined}) === false);
|
||||||
|
assert("foo" in new Proxy({}, {}) === false);
|
||||||
|
|
||||||
|
let o = {};
|
||||||
|
let p = new Proxy(o, {
|
||||||
|
has(target, prop) {
|
||||||
|
assert(target === o);
|
||||||
|
assert(prop === "foo");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
"foo" in p;
|
||||||
|
|
||||||
|
p = new Proxy(o, {
|
||||||
|
has(target, prop) {
|
||||||
|
if (target.checkedFoo)
|
||||||
|
return true;
|
||||||
|
if (prop === "foo")
|
||||||
|
target.checkedFoo = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assert("foo" in p === false);
|
||||||
|
assert("foo" in p === true);
|
||||||
|
|
||||||
|
// Invariants
|
||||||
|
|
||||||
|
o = {};
|
||||||
|
Object.defineProperty(o, "foo", { configurable: false });
|
||||||
|
Object.defineProperty(o, "bar", { value: 10, configurable: true });
|
||||||
|
p = new Proxy(o, {
|
||||||
|
has() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
"foo" in p;
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's has trap violates invariant: a property cannot be reported as non-existent if it exists on the target as a non-configurable property",
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.preventExtensions(o);
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
"bar" in p;
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's has trap violates invariant: a property cannot be reported as non-existent if it exist on the target and the target is non-extensible",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("PASS");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("FAIL: " + e);
|
||||||
|
}
|
49
Libraries/LibJS/Tests/Proxy.handler-isExtensible.js
Normal file
49
Libraries/LibJS/Tests/Proxy.handler-isExtensible.js
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
load("test-common.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert(Object.isExtensible(new Proxy({}, { isExtensible: null })) === true);
|
||||||
|
assert(Object.isExtensible(new Proxy({}, { isExtensible: undefined })) === true);
|
||||||
|
assert(Object.isExtensible(new Proxy({}, {})) === true);
|
||||||
|
|
||||||
|
let o = {};
|
||||||
|
let p = new Proxy(o, {
|
||||||
|
isExtensible(target) {
|
||||||
|
assert(target === o);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.isExtensible(p);
|
||||||
|
|
||||||
|
// Invariants
|
||||||
|
|
||||||
|
o = {};
|
||||||
|
p = new Proxy(o, {
|
||||||
|
isExtensible(proxyTarget) {
|
||||||
|
assert(proxyTarget === o);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(Object.isExtensible(p) === true);
|
||||||
|
Object.preventExtensions(o);
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.isExtensible(p);
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's isExtensible trap violates invariant: return value must match the target's extensibility",
|
||||||
|
});
|
||||||
|
|
||||||
|
p = new Proxy(o, {
|
||||||
|
isExtensible(proxyTarget) {
|
||||||
|
assert(proxyTarget === o);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert(Object.isExtensible(p) === false);
|
||||||
|
|
||||||
|
console.log("PASS");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("FAIL: " + e);
|
||||||
|
}
|
55
Libraries/LibJS/Tests/Proxy.handler-preventExtensions.js
Normal file
55
Libraries/LibJS/Tests/Proxy.handler-preventExtensions.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
load("test-common.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
let p = new Proxy({}, { preventExtensions: null });
|
||||||
|
assert(Object.preventExtensions(p) === p);
|
||||||
|
p = new Proxy({}, { preventExtensions: undefined });
|
||||||
|
assert(Object.preventExtensions(p) === p);
|
||||||
|
p = new Proxy({}, {});
|
||||||
|
assert(Object.preventExtensions(p) == p);
|
||||||
|
|
||||||
|
let o = {};
|
||||||
|
p = new Proxy(o, {
|
||||||
|
preventExtensions(target) {
|
||||||
|
assert(target === o);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.preventExtensions(o);
|
||||||
|
Object.preventExtensions(p);
|
||||||
|
|
||||||
|
// Invariants
|
||||||
|
|
||||||
|
p = new Proxy({}, {
|
||||||
|
preventExtensions() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.preventExtensions(p);
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy preventExtensions handler returned false",
|
||||||
|
});
|
||||||
|
|
||||||
|
o = {};
|
||||||
|
p = new Proxy(o, {
|
||||||
|
preventExtensions() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.preventExtensions(p);
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's preventExtensions trap violates invariant: cannot return true if the target object is extensible"
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.preventExtensions(o);
|
||||||
|
assert(Object.preventExtensions(p) === p);
|
||||||
|
|
||||||
|
console.log("PASS");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("FAIL: " + e);
|
||||||
|
}
|
66
Libraries/LibJS/Tests/Proxy.handler-set.js
Normal file
66
Libraries/LibJS/Tests/Proxy.handler-set.js
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
load("test-common.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert((new Proxy({}, { set: undefined }).foo = 1) === 1);
|
||||||
|
assert((new Proxy({}, { set: null }).foo = 1) === 1);
|
||||||
|
assert((new Proxy({}, {}).foo = 1) === 1);
|
||||||
|
|
||||||
|
let o = {};
|
||||||
|
let p = new Proxy(o, {
|
||||||
|
set(target, prop, value, receiver) {
|
||||||
|
assert(target === o);
|
||||||
|
assert(prop === "foo");
|
||||||
|
assert(value === 10);
|
||||||
|
assert(receiver === p);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
p.foo = 10;
|
||||||
|
|
||||||
|
p = new Proxy(o, {
|
||||||
|
set(target, prop, value, receiver) {
|
||||||
|
if (target[prop] === value) {
|
||||||
|
target[prop] *= 2;
|
||||||
|
} else {
|
||||||
|
target[prop] = value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
p.foo = 10;
|
||||||
|
assert(p.foo === 10);
|
||||||
|
p.foo = 10;
|
||||||
|
assert(p.foo === 20);
|
||||||
|
p.foo = 10;
|
||||||
|
assert(p.foo === 10);
|
||||||
|
|
||||||
|
// Invariants
|
||||||
|
|
||||||
|
o = {};
|
||||||
|
Object.defineProperty(o, "foo", { value: 10 });
|
||||||
|
p = new Proxy(o, {
|
||||||
|
set() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
p.foo = 12;
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's set trap violates invariant: cannot return true for a property on the target which is a non-configurable, non-writable own data property",
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(o, "bar", { get() {} });
|
||||||
|
assertThrowsError(() => {
|
||||||
|
p.bar = 12;
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's set trap violates invariant: cannot return true for a property on the target which is a non-configurable own accessor property with an undefined set attribute",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("PASS");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("FAIL: " + e);
|
||||||
|
}
|
95
Libraries/LibJS/Tests/Proxy.handler-setPrototypeOf.js
Normal file
95
Libraries/LibJS/Tests/Proxy.handler-setPrototypeOf.js
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
load("test-common.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const child = {};
|
||||||
|
const childProto = { foo: "bar" };
|
||||||
|
|
||||||
|
Object.setPrototypeOf(child, childProto);
|
||||||
|
assert(child.foo === "bar");
|
||||||
|
|
||||||
|
Object.setPrototypeOf(new Proxy(child, { setPrototypeOf: null }), childProto);
|
||||||
|
Object.setPrototypeOf(new Proxy(child, { setPrototypeOf: undefined }), childProto);
|
||||||
|
|
||||||
|
let o = {};
|
||||||
|
let theNewProto = { foo: "bar" };
|
||||||
|
let p = new Proxy(o, {
|
||||||
|
setPrototypeOf(target, newProto) {
|
||||||
|
assert(target === o);
|
||||||
|
assert(newProto === theNewProto);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.setPrototypeOf(p, theNewProto);
|
||||||
|
|
||||||
|
p = new Proxy(o, {
|
||||||
|
setPrototypeOf(target, newProto) {
|
||||||
|
if (target.shouldSet)
|
||||||
|
Object.setPrototypeOf(target, newProto);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.setPrototypeOf(p, { foo: 1 });
|
||||||
|
assert(Object.getPrototypeOf(p).foo === undefined);
|
||||||
|
p.shouldSet = true;
|
||||||
|
assert(o.shouldSet === true);
|
||||||
|
Object.setPrototypeOf(p, { foo: 1 });
|
||||||
|
assert(Object.getPrototypeOf(p).foo === 1);
|
||||||
|
|
||||||
|
// Invariants
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.setPrototypeOf(new Proxy(child, { setPrototypeOf: 1 }), {});
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's setPrototypeOf trap wasn't undefined, null, or callable",
|
||||||
|
});
|
||||||
|
|
||||||
|
p = new Proxy(child, {
|
||||||
|
setPrototypeOf(target, newProto) {
|
||||||
|
assert(target === child);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.setPrototypeOf(p, {});
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Object's setPrototypeOf method returned false"
|
||||||
|
});
|
||||||
|
assert(Object.getPrototypeOf(p) === childProto);
|
||||||
|
|
||||||
|
p = new Proxy(child, {
|
||||||
|
setPrototypeOf(target, newProto) {
|
||||||
|
assert(target === child);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(Object.setPrototypeOf(p, {}) === p);
|
||||||
|
assert(Object.getPrototypeOf(p) === childProto);
|
||||||
|
|
||||||
|
Object.preventExtensions(child);
|
||||||
|
p = new Proxy(child, {
|
||||||
|
setPrototypeOf(target, newProto) {
|
||||||
|
assert(target === child);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(Object.setPrototypeOf(p, childProto) === p);
|
||||||
|
assert(Object.getPrototypeOf(p) === childProto);
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Object.setPrototypeOf(p, {});
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy handler's setPrototypeOf trap violates invariant: the argument must match the prototype of the target if the target is non-extensible",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("PASS");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("FAIL: " + e);
|
||||||
|
}
|
30
Libraries/LibJS/Tests/Proxy.js
Normal file
30
Libraries/LibJS/Tests/Proxy.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
load("test-common.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
new Proxy({}, {});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
new Proxy();
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy requires at least two arguments",
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
Proxy();
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Proxy must be called with the \"new\" operator",
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
new Proxy(1, {});
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "Expected target argument of Proxy constructor to be object, got 1",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("PASS");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("FAIL: " + e);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue