summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb
diff options
context:
space:
mode:
authorAndreas Kling <kling@serenityos.org>2022-08-11 16:27:43 +0200
committerAndreas Kling <kling@serenityos.org>2022-09-06 00:27:09 +0200
commit91ed6125fd333293ba2f294b6a92a675da233441 (patch)
tree129020718a568b65210f01c85ffcb12357fa25a0 /Userland/Libraries/LibWeb
parentf6c61940f6569d095a028f73bc8a1b00702275ec (diff)
downloadserenity-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.cpp321
-rw-r--r--Userland/Libraries/LibWeb/Bindings/LegacyPlatformObject.h40
-rw-r--r--Userland/Libraries/LibWeb/CMakeLists.txt1
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