diff options
author | Andreas Kling <kling@serenityos.org> | 2022-08-11 16:27:43 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-09-06 00:27:09 +0200 |
commit | 91ed6125fd333293ba2f294b6a92a675da233441 (patch) | |
tree | 129020718a568b65210f01c85ffcb12357fa25a0 /Userland/Libraries/LibWeb | |
parent | f6c61940f6569d095a028f73bc8a1b00702275ec (diff) | |
download | serenity-91ed6125fd333293ba2f294b6a92a675da233441.zip |
LibWeb: Add Bindings::LegacyPlatformObject base class
This will be inherited by "legacy platform objects", i.e objects that
need to hijack indexed and/or named property access as described in the
IDL spec: https://webidl.spec.whatwg.org/#dfn-legacy-platform-object
Instead of overriding JS::Object virtuals, subclasses only need to
implement a very simple interface for property queries.
Note that this code is taken verbatim from code generator output.
I didn't write any of this now, so it's effectively "moved" code.
Diffstat (limited to 'Userland/Libraries/LibWeb')
-rw-r--r-- | Userland/Libraries/LibWeb/Bindings/LegacyPlatformObject.cpp | 321 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Bindings/LegacyPlatformObject.h | 40 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CMakeLists.txt | 1 |
3 files changed, 362 insertions, 0 deletions
diff --git a/Userland/Libraries/LibWeb/Bindings/LegacyPlatformObject.cpp b/Userland/Libraries/LibWeb/Bindings/LegacyPlatformObject.cpp new file mode 100644 index 0000000000..ef146e315b --- /dev/null +++ b/Userland/Libraries/LibWeb/Bindings/LegacyPlatformObject.cpp @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibJS/Runtime/GlobalObject.h> +#include <LibWeb/Bindings/ExceptionOrUtils.h> +#include <LibWeb/Bindings/LegacyPlatformObject.h> + +namespace Web::Bindings { + +LegacyPlatformObject::LegacyPlatformObject(JS::Object& prototype) + : PlatformObject(prototype) +{ +} + +LegacyPlatformObject::~LegacyPlatformObject() = default; + +JS::ThrowCompletionOr<bool> LegacyPlatformObject::is_named_property_exposed_on_object(JS::PropertyKey const& property_key) const +{ + [[maybe_unused]] auto& vm = this->vm(); + + // The spec doesn't say anything about the type of the property name here. + // Numbers can be converted to a string, which is fine and what other engines do. + // However, since a symbol cannot be converted to a string, it cannot be a supported property name. Return early if it's a symbol. + if (property_key.is_symbol()) + return false; + + // 1. If P is not a supported property name of O, then return false. + // NOTE: This is in it's own variable to enforce the type. + // FIXME: Can this throw? + Vector<String> supported_property_names = this->supported_property_names(); + auto property_key_string = property_key.to_string(); + if (!supported_property_names.contains_slow(property_key_string)) + return false; + + // 2. If O has an own property named P, then return false. + // NOTE: This has to be done manually instead of using Object::has_own_property, as that would use the overridden internal_get_own_property. + auto own_property_named_p = MUST(Object::internal_get_own_property(property_key)); + + if (own_property_named_p.has_value()) + return false; + + // NOTE: Step 3 is not here as the interface doesn't have the LegacyOverrideBuiltIns extended attribute. + // 4. Let prototype be O.[[GetPrototypeOf]](). + auto* prototype = TRY(internal_get_prototype_of()); + + // 5. While prototype is not null: + while (prototype) { + // FIXME: 1. If prototype is not a named properties object, and prototype has an own property named P, then return false. + // (It currently does not check for named property objects) + bool prototype_has_own_property_named_p = TRY(prototype->has_own_property(property_key)); + if (prototype_has_own_property_named_p) + return false; + + // 2. Set prototype to prototype.[[GetPrototypeOf]](). + prototype = TRY(prototype->internal_get_prototype_of()); + } + + // 6. Return true. + return true; +} + +JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> LegacyPlatformObject::legacy_platform_object_get_own_property_for_get_own_property_slot(JS::PropertyKey const& property_name) const +{ + + [[maybe_unused]] auto& global_object = this->global_object(); + + if (property_name.is_number()) { + // 1. Let index be the result of calling ToUint32(P). + u32 index = property_name.as_number(); + + // 2. If index is a supported property index, then: + // FIXME: Can this throw? + if (is_supported_property_index(index)) { + + auto value = TRY(throw_dom_exception_if_needed(global_object, [&] { return item_value(index); })); + + // 5. Let desc be a newly created Property Descriptor with no fields. + JS::PropertyDescriptor descriptor; + + // 6. Set desc.[[Value]] to the result of converting value to an ECMAScript value. + descriptor.value = value; + + descriptor.writable = false; + + // 8. Set desc.[[Enumerable]] and desc.[[Configurable]] to true. + descriptor.enumerable = true; + descriptor.configurable = true; + + // 9. Return desc. + return descriptor; + } + + // 3. Set ignoreNamedProps to true. + return TRY(Object::internal_get_own_property(property_name)); + } + + // 1. If the result of running the named property visibility algorithm with property name P and object O is true, then: + if (TRY(is_named_property_exposed_on_object(property_name))) { + // FIXME: It's unfortunate that this is done twice, once in is_named_property_exposed_on_object and here. + auto property_name_string = property_name.to_string(); + + auto value = TRY(throw_dom_exception_if_needed(global_object, [&] { return named_item_value(property_name_string); })); + + // 5. Let desc be a newly created Property Descriptor with no fields. + JS::PropertyDescriptor descriptor; + + // 6. Set desc.[[Value]] to the result of converting value to an ECMAScript value. + descriptor.value = value; + + descriptor.writable = false; + + descriptor.enumerable = false; + + // 9. Set desc.[[Configurable]] to true. + descriptor.configurable = true; + + // 10. Return desc. + return descriptor; + } + + return TRY(Object::internal_get_own_property(property_name)); +} + +JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> LegacyPlatformObject::legacy_platform_object_get_own_property_for_set_slot(JS::PropertyKey const& property_name) const +{ + if (!property_name.is_number()) + return TRY(Object::internal_get_own_property(property_name)); + + // 1. Let index be the result of calling ToUint32(P). + u32 index = property_name.as_number(); + + // 2. If index is a supported property index, then: + // FIXME: Can this throw? + if (is_supported_property_index(index)) { + + auto value = TRY(throw_dom_exception_if_needed(global_object(), [&] { return item_value(index); })); + + // 5. Let desc be a newly created Property Descriptor with no fields. + JS::PropertyDescriptor descriptor; + + // 6. Set desc.[[Value]] to the result of converting value to an ECMAScript value. + descriptor.value = value; + + descriptor.writable = false; + + // 8. Set desc.[[Enumerable]] and desc.[[Configurable]] to true. + descriptor.enumerable = true; + descriptor.configurable = true; + + // 9. Return desc. + return descriptor; + } + + // 3. Set ignoreNamedProps to true. + return TRY(Object::internal_get_own_property(property_name)); +} + +JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> LegacyPlatformObject::internal_get_own_property(JS::PropertyKey const& property_name) const +{ + // 1. Return LegacyPlatformObjectGetOwnProperty(O, P, false). + return TRY(legacy_platform_object_get_own_property_for_get_own_property_slot(property_name)); +} + +JS::ThrowCompletionOr<bool> LegacyPlatformObject::internal_set(JS::PropertyKey const& property_name, JS::Value value, JS::Value receiver) +{ + [[maybe_unused]] auto& global_object = this->global_object(); + + // 2. Let ownDesc be LegacyPlatformObjectGetOwnProperty(O, P, true). + auto own_descriptor = TRY(legacy_platform_object_get_own_property_for_set_slot(property_name)); + + // 3. Perform ? OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc). + // NOTE: The spec says "perform" instead of "return", meaning nothing will be returned on this path according to the spec, which isn't possible to do. + // Let's treat it as though it says "return" instead of "perform". + return ordinary_set_with_own_descriptor(property_name, value, receiver, own_descriptor); +} + +JS::ThrowCompletionOr<bool> LegacyPlatformObject::internal_define_own_property(JS::PropertyKey const& property_name, JS::PropertyDescriptor const& property_descriptor) +{ + [[maybe_unused]] auto& vm = this->vm(); + [[maybe_unused]] auto& global_object = this->global_object(); + + if (property_name.is_number()) { + // 1. If the result of calling IsDataDescriptor(Desc) is false, then return false. + if (!property_descriptor.is_data_descriptor()) + return false; + + return false; + } + + if (property_name.is_string()) { + auto& property_name_as_string = property_name.as_string(); + + // 1. Let creating be true if P is not a supported property name, and false otherwise. + // NOTE: This is in it's own variable to enforce the type. + // FIXME: Can this throw? + Vector<String> supported_property_names = this->supported_property_names(); + [[maybe_unused]] bool creating = !supported_property_names.contains_slow(property_name_as_string); + + // NOTE: This has to be done manually instead of using Object::has_own_property, as that would use the overridden internal_get_own_property. + auto own_property_named_p = TRY(Object::internal_get_own_property(property_name)); + + if (!own_property_named_p.has_value()) { + + if (!creating) + return false; + } + } + + // property_descriptor is a const&, thus we need to create a copy here to set [[Configurable]] + JS::PropertyDescriptor descriptor_copy(property_descriptor); + descriptor_copy.configurable = true; + + // 4. Return OrdinaryDefineOwnProperty(O, P, Desc). + return Object::internal_define_own_property(property_name, descriptor_copy); +} + +JS::ThrowCompletionOr<bool> LegacyPlatformObject::internal_delete(JS::PropertyKey const& property_name) +{ + [[maybe_unused]] auto& global_object = this->global_object(); + + if (property_name.is_number()) { + // 1. Let index be the result of calling ToUint32(P). + u32 index = property_name.as_number(); + + // 2. If index is not a supported property index, then return true. + // FIXME: Can this throw? + if (!is_supported_property_index(index)) + return true; + + // 3. Return false. + return false; + } + + if (TRY(is_named_property_exposed_on_object(property_name))) { + + return false; + } + + // 3. If O has an own property with name P, then: + auto own_property_named_p_descriptor = TRY(Object::internal_get_own_property(property_name)); + + if (own_property_named_p_descriptor.has_value()) { + // 1. If the property is not configurable, then return false. + // 2. Otherwise, remove the property from O. + if (*own_property_named_p_descriptor->configurable) + storage_delete(property_name); + else + return false; + } + + // 4. Return true. + return true; +} + +JS::ThrowCompletionOr<bool> LegacyPlatformObject::internal_prevent_extensions() +{ + // 1. Return false. + return false; +} + +JS::ThrowCompletionOr<JS::MarkedVector<JS::Value>> LegacyPlatformObject::internal_own_property_keys() const +{ + auto& vm = this->vm(); + + // 1. Let keys be a new empty list of ECMAScript String and Symbol values. + JS::MarkedVector<JS::Value> keys { heap() }; + + for (u64 index = 0; index <= NumericLimits<u32>::max(); ++index) { + if (is_supported_property_index(index)) + keys.append(js_string(vm, String::number(index))); + else + break; + } + + for (auto& named_property : supported_property_names()) { + if (TRY(is_named_property_exposed_on_object(named_property))) + keys.append(js_string(vm, named_property)); + } + + // 4. For each P of O’s own property keys that is a String, in ascending chronological order of property creation, append P to keys. + for (auto& it : shape().property_table_ordered()) { + if (it.key.is_string()) + keys.append(it.key.to_value(vm)); + } + + // 5. For each P of O’s own property keys that is a Symbol, in ascending chronological order of property creation, append P to keys. + for (auto& it : shape().property_table_ordered()) { + if (it.key.is_symbol()) + keys.append(it.key.to_value(vm)); + } + + // FIXME: 6. Assert: keys has no duplicate items. + + // 7. Return keys. + return { move(keys) }; +} + +JS::Value LegacyPlatformObject::item_value(size_t) const +{ + return JS::js_undefined(); +} + +JS::Value LegacyPlatformObject::named_item_value(FlyString const&) const +{ + return JS::js_undefined(); +} + +Vector<String> LegacyPlatformObject::supported_property_names() const +{ + return {}; +} + +bool LegacyPlatformObject::is_supported_property_index(u32) const +{ + return false; +} + +} diff --git a/Userland/Libraries/LibWeb/Bindings/LegacyPlatformObject.h b/Userland/Libraries/LibWeb/Bindings/LegacyPlatformObject.h new file mode 100644 index 0000000000..c8969d438e --- /dev/null +++ b/Userland/Libraries/LibWeb/Bindings/LegacyPlatformObject.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibWeb/Bindings/PlatformObject.h> + +namespace Web::Bindings { + +// https://webidl.spec.whatwg.org/#dfn-legacy-platform-object +class LegacyPlatformObject : public PlatformObject { + JS_OBJECT(LegacyPlatformObject, PlatformObject); + +public: + virtual ~LegacyPlatformObject() override; + + virtual JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> internal_get_own_property(JS::PropertyKey const&) const override; + virtual JS::ThrowCompletionOr<bool> internal_set(JS::PropertyKey const&, JS::Value, JS::Value) override; + virtual JS::ThrowCompletionOr<bool> internal_define_own_property(JS::PropertyKey const&, JS::PropertyDescriptor const&) override; + virtual JS::ThrowCompletionOr<bool> internal_delete(JS::PropertyKey const&) override; + virtual JS::ThrowCompletionOr<bool> internal_prevent_extensions() override; + virtual JS::ThrowCompletionOr<JS::MarkedVector<JS::Value>> internal_own_property_keys() const override; + + JS::ThrowCompletionOr<bool> is_named_property_exposed_on_object(JS::PropertyKey const&) const; + JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> legacy_platform_object_get_own_property_for_get_own_property_slot(JS::PropertyKey const&) const; + JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> legacy_platform_object_get_own_property_for_set_slot(JS::PropertyKey const&) const; + + virtual JS::Value item_value(size_t index) const; + virtual JS::Value named_item_value(FlyString const& name) const; + virtual Vector<String> supported_property_names() const; + virtual bool is_supported_property_index(u32) const; + +protected: + explicit LegacyPlatformObject(JS::Object& prototype); +}; + +} diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 5c4b549abd..4983599a2c 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -11,6 +11,7 @@ set(SOURCES Bindings/EventWrapperFactory.cpp Bindings/IDLAbstractOperations.cpp Bindings/ImageConstructor.cpp + Bindings/LegacyPlatformObject.cpp Bindings/LocationConstructor.cpp Bindings/LocationObject.cpp Bindings/MainThreadVM.cpp |