From 247f12d7b0aeba335fba86b651dc77ec032bfcb5 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Tue, 19 Sep 2023 13:32:13 -0600 Subject: [PATCH] LibWeb: Insert WindowProperties object into Window's prototype chain And implement WindowProperties, the "named properties object" for Window according to the spec. This involves moving an AO out of LegacyPlatformObject and into a common place that the WindowProperties class can access. This doesn't implement the AOs on Window that actually name lookup for the unenumerable named properties on the window yet, just the scaffolding. --- .../BindingsGenerator/IDLGenerators.cpp | 186 +++++++++++++++++- .../GenerateWindowOrWorkerInterfaces.cpp | 33 +++- .../Text/expected/HTML/Window-prototype.txt | 6 + .../Text/input/HTML/Window-prototype.html | 11 ++ .../LibWeb/Bindings/LegacyPlatformObject.cpp | 52 +---- .../LibWeb/Bindings/LegacyPlatformObject.h | 6 +- Userland/Libraries/LibWeb/Forward.h | 1 + Userland/Libraries/LibWeb/HTML/Window.cpp | 12 ++ Userland/Libraries/LibWeb/HTML/Window.h | 3 + Userland/Libraries/LibWeb/HTML/Window.idl | 5 + .../LibWeb/WebIDL/AbstractOperations.cpp | 53 +++++ .../LibWeb/WebIDL/AbstractOperations.h | 2 + 12 files changed, 313 insertions(+), 57 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/HTML/Window-prototype.txt create mode 100644 Tests/LibWeb/Text/input/HTML/Window-prototype.html diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index 10d9b88a76..0da0caf253 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -2549,6 +2549,169 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::to_json) )~~~"); } +static void generate_named_properties_object_declarations(IDL::Interface const& interface, StringBuilder& builder) +{ + SourceGenerator generator { builder }; + + generator.set("named_properties_class", DeprecatedString::formatted("{}Properties", interface.name)); + + generator.append(R"~~~( +class @named_properties_class@ : public JS::Object { + JS_OBJECT(@named_properties_class@, JS::Object); +public: + explicit @named_properties_class@(JS::Realm&); + virtual void initialize(JS::Realm&) override; + virtual ~@named_properties_class@() override; + + JS::Realm& realm() const { return m_realm; } +private: + virtual JS::ThrowCompletionOr> internal_get_own_property(JS::PropertyKey const&) const override; + virtual JS::ThrowCompletionOr internal_define_own_property(JS::PropertyKey const&, JS::PropertyDescriptor const&) override; + virtual JS::ThrowCompletionOr internal_delete(JS::PropertyKey const&) override; + virtual JS::ThrowCompletionOr internal_set_prototype_of(JS::Object* prototype) override; + virtual JS::ThrowCompletionOr internal_prevent_extensions() override; + + JS::Realm& m_realm; // [[Realm]] +}; +)~~~"); +} + +static void generate_named_properties_object_definitions(IDL::Interface const& interface, StringBuilder& builder) +{ + SourceGenerator generator { builder }; + + generator.set("name", interface.name); + generator.set("parent_name", interface.parent_name); + generator.set("prototype_base_class", interface.prototype_base_class); + generator.set("named_properties_class", DeprecatedString::formatted("{}Properties", interface.name)); + + // https://webidl.spec.whatwg.org/#create-a-named-properties-object + generator.append(R"~~~( +#include + +@named_properties_class@::@named_properties_class@(JS::Realm& realm) + : JS::Object(realm, nullptr) + , m_realm(realm) +{ +} + +@named_properties_class@::~@named_properties_class@() +{ +} + +void @named_properties_class@::initialize(JS::Realm& realm) +{ + auto& vm = realm.vm(); + + // The class string of a named properties object is the concatenation of the interface's identifier and the string "Properties". + define_direct_property(vm.well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm, "@named_properties_class@"_string), JS::Attribute::Configurable); +)~~~"); + + // 1. Let proto be null + // 2. If interface is declared to inherit from another interface, then set proto to the interface prototype object in realm for the inherited interface. + // 3. Otherwise, set proto to realm.[[Intrinsics]].[[%Object.prototype%]]. + // NOTE: Steps 4-9 handled by constructor + other overridden functions + // 10. Set obj.[[Prototype]] to proto. + if (interface.prototype_base_class == "ObjectPrototype") { + generator.append(R"~~~( + + set_prototype(realm.intrinsics().object_prototype()); +)~~~"); + } else { + generator.append(R"~~~( + + set_prototype(&ensure_web_prototype<@prototype_base_class@>(realm, "@parent_name@")); +)~~~"); + } + + generator.append(R"~~~( +}; + +// https://webidl.spec.whatwg.org/#named-properties-object-getownproperty +JS::ThrowCompletionOr> @named_properties_class@::internal_get_own_property(JS::PropertyKey const& property_name) const +{ + auto& vm = this->vm(); + auto& realm = this->realm(); + + // 1. Let A be the interface for the named properties object O. + using A = @name@; + + // 2. Let object be O.[[Realm]]'s global object. + // 3. Assert: object implements A. + auto& object = verify_cast(realm.global_object()); + + // 4. If the result of running the named property visibility algorithm with property name P and object object is true, then: + if (TRY(is_named_property_exposed_on_object({ &object }, property_name))) { + auto property_name_string = property_name.to_string(); + + // 1. Let operation be the operation used to declare the named property getter. + // 2. Let value be an uninitialized variable. + // 3. If operation was defined without an identifier, then set value to the result of performing the steps listed in the interface description to determine the value of a named property with P as the name. + // 4. Otherwise, operation was defined with an identifier. Set value to the result of performing the method steps of operation with « P » as the only argument value. + auto value = TRY(throw_dom_exception_if_needed(vm, [&] { return object.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; +)~~~"); + if (interface.extended_attributes.contains("LegacyUnenumerableNamedProperties")) + generator.append(R"~~~( + // 7. If A implements an interface with the [LegacyUnenumerableNamedProperties] extended attribute, then set desc.[[Enumerable]] to false, otherwise set it to true. + descriptor.enumerable = true; +)~~~"); + else { + generator.append(R"~~~( + // 7. If A implements an interface with the [LegacyUnenumerableNamedProperties] extended attribute, then set desc.[[Enumerable]] to false, otherwise set it to true. + descriptor.enumerable = false; +)~~~"); + } + generator.append(R"~~~( + // 8. Set desc.[[Writable]] to true and desc.[[Configurable]] to true. + descriptor.writable = true; + descriptor.configurable = true; + + // 9. Return desc. + return descriptor; + } + + // 5. Return OrdinaryGetOwnProperty(O, P). + return JS::Object::internal_get_own_property(property_name); +} + +// https://webidl.spec.whatwg.org/#named-properties-object-defineownproperty +JS::ThrowCompletionOr @named_properties_class@::internal_define_own_property(JS::PropertyKey const&, JS::PropertyDescriptor const&) +{ + // 1. Return false. + return false; +} + +// https://webidl.spec.whatwg.org/#named-properties-object-delete +JS::ThrowCompletionOr @named_properties_class@::internal_delete(JS::PropertyKey const&) +{ + // 1. Return false. + return false; +} + +// https://webidl.spec.whatwg.org/#named-properties-object-setprototypeof +JS::ThrowCompletionOr @named_properties_class@::internal_set_prototype_of(JS::Object* prototype) +{ + // 1. Return ? SetImmutablePrototype(O, V). + return set_immutable_prototype(prototype); +} + +// https://webidl.spec.whatwg.org/#named-properties-object-preventextensions +JS::ThrowCompletionOr @named_properties_class@::internal_prevent_extensions() +{ + // 1. Return false. + // Note: this keeps named properties object extensible by making [[PreventExtensions]] fail. + return false; +} +)~~~"); +} + +// https://webidl.spec.whatwg.org/#interface-prototype-object static void generate_prototype_or_global_mixin_definitions(IDL::Interface const& interface, StringBuilder& builder) { SourceGenerator generator { builder }; @@ -2561,12 +2724,14 @@ static void generate_prototype_or_global_mixin_definitions(IDL::Interface const& generator.set("fully_qualified_name", interface.fully_qualified_name); generator.set("parent_name", interface.parent_name); generator.set("prototype_base_class", interface.prototype_base_class); + generator.set("prototype_name", interface.prototype_class); // Used for Global Mixin if (interface.pair_iterator_types.has_value()) { generator.set("iterator_name", DeprecatedString::formatted("{}Iterator", interface.name)); } if (is_global_interface) { + generator.set("named_properties_class", DeprecatedString::formatted("{}Properties", interface.name)); // Doing this with macros is not super nice, but simplifies codegen a lot. generator.append(R"~~~( #define define_direct_property (object.define_direct_property) @@ -2598,6 +2763,10 @@ void @class_name@::initialize(JS::Realm& realm) set_prototype(realm.intrinsics().object_prototype()); +)~~~"); + } else if (is_global_interface && interface.supports_named_properties()) { + generator.append(R"~~~( + set_prototype(&ensure_web_prototype<@prototype_name@>(realm, "@name@")); )~~~"); } else { generator.append(R"~~~( @@ -3671,6 +3840,9 @@ private: generator.append(R"~~~( }; )~~~"); + if (interface.supports_named_properties()) { + generate_named_properties_object_declarations(interface, builder); + } } else { generate_prototype_or_global_mixin_declarations(interface, builder); } @@ -3816,15 +3988,27 @@ namespace Web::Bindings { } )~~~"); - // Generate an empty prototype object for global interfaces. + // Generate a mostly empty prototype object for global interfaces. auto is_global_interface = interface.extended_attributes.contains("Global"); if (is_global_interface) { generator.append(R"~~~( void @prototype_class@::initialize(JS::Realm& realm) { Base::initialize(realm); +)~~~"); + if (interface.supports_named_properties()) { + generator.set("named_properties_class", DeprecatedString::formatted("{}Properties", interface.name)); + generator.set("namespaced_name", interface.namespaced_name); + generator.append(R"~~~( + define_direct_property(vm().well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm(), "@namespaced_name@"_string), JS::Attribute::Configurable); + set_prototype(&ensure_web_prototype<@prototype_class@>(realm, "@named_properties_class@")); +)~~~"); + } + generator.append(R"~~~( } )~~~"); + if (interface.supports_named_properties()) + generate_named_properties_object_definitions(interface, builder); } else { generate_prototype_or_global_mixin_definitions(interface, builder); } diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWindowOrWorkerInterfaces.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWindowOrWorkerInterfaces.cpp index f87872c5fc..087307a717 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWindowOrWorkerInterfaces.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWindowOrWorkerInterfaces.cpp @@ -76,7 +76,7 @@ namespace Web::Bindings { class @namespace_class@;)~~~"); }; - auto add_interface = [](SourceGenerator& gen, StringView prototype_class, StringView constructor_class, Optional const& legacy_constructor) { + auto add_interface = [](SourceGenerator& gen, StringView prototype_class, StringView constructor_class, Optional const& legacy_constructor, StringView named_properties_class) { gen.set("prototype_class", prototype_class); gen.set("constructor_class", constructor_class); @@ -89,15 +89,25 @@ class @constructor_class@;)~~~"); gen.append(R"~~~( class @legacy_constructor_class@;)~~~"); } + if (!named_properties_class.is_empty()) { + gen.set("named_properties_class", named_properties_class); + gen.append(R"~~~( +class @named_properties_class@;)~~~"); + } }; for (auto& interface : exposed_interfaces) { auto gen = generator.fork(); + String named_properties_class; + if (interface.extended_attributes.contains("Global") && interface.supports_named_properties()) { + named_properties_class = MUST(String::formatted("{}Properties", interface.name)); + } + if (interface.is_namespace) add_namespace(gen, interface.namespace_class); else - add_interface(gen, interface.prototype_class, interface.constructor_class, lookup_legacy_constructor(interface)); + add_interface(gen, interface.prototype_class, interface.constructor_class, lookup_legacy_constructor(interface), named_properties_class); } generator.append(R"~~~( @@ -178,7 +188,7 @@ void Intrinsics::create_web_namespace<@namespace_class@>(JS::Realm& realm) )~~~"); }; - auto add_interface = [](SourceGenerator& gen, StringView name, StringView prototype_class, StringView constructor_class, Optional const& legacy_constructor) { + auto add_interface = [](SourceGenerator& gen, StringView name, StringView prototype_class, StringView constructor_class, Optional const& legacy_constructor, StringView named_properties_class) { gen.set("interface_name", name); gen.set("prototype_class", prototype_class); gen.set("constructor_class", constructor_class); @@ -189,6 +199,16 @@ void Intrinsics::create_web_prototype_and_constructor<@prototype_class@>(JS::Rea { auto& vm = realm.vm(); +)~~~"); + if (!named_properties_class.is_empty()) { + gen.set("named_properties_class", named_properties_class); + gen.append(R"~~~( + auto named_properties_object = heap().allocate<@named_properties_class@>(realm, realm); + m_prototypes.set("@named_properties_class@"sv, named_properties_object); + +)~~~"); + } + gen.append(R"~~~( auto prototype = heap().allocate<@prototype_class@>(realm, realm); m_prototypes.set("@interface_name@"sv, prototype); @@ -217,10 +237,15 @@ void Intrinsics::create_web_prototype_and_constructor<@prototype_class@>(JS::Rea for (auto& interface : exposed_interfaces) { auto gen = generator.fork(); + String named_properties_class; + if (interface.extended_attributes.contains("Global") && interface.supports_named_properties()) { + named_properties_class = MUST(String::formatted("{}Properties", interface.name)); + } + if (interface.is_namespace) add_namespace(gen, interface.name, interface.namespace_class); else - add_interface(gen, interface.namespaced_name, interface.prototype_class, interface.constructor_class, lookup_legacy_constructor(interface)); + add_interface(gen, interface.namespaced_name, interface.prototype_class, interface.constructor_class, lookup_legacy_constructor(interface), named_properties_class); } generator.append(R"~~~( diff --git a/Tests/LibWeb/Text/expected/HTML/Window-prototype.txt b/Tests/LibWeb/Text/expected/HTML/Window-prototype.txt new file mode 100644 index 0000000000..4162e0107f --- /dev/null +++ b/Tests/LibWeb/Text/expected/HTML/Window-prototype.txt @@ -0,0 +1,6 @@ +[object Window] +[object Window] +[object WindowProperties] +[object EventTarget] +[object Object] +null \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/HTML/Window-prototype.html b/Tests/LibWeb/Text/input/HTML/Window-prototype.html new file mode 100644 index 0000000000..5f35f3415d --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/Window-prototype.html @@ -0,0 +1,11 @@ + + diff --git a/Userland/Libraries/LibWeb/Bindings/LegacyPlatformObject.cpp b/Userland/Libraries/LibWeb/Bindings/LegacyPlatformObject.cpp index cd8a46a49f..a531b2b870 100644 --- a/Userland/Libraries/LibWeb/Bindings/LegacyPlatformObject.cpp +++ b/Userland/Libraries/LibWeb/Bindings/LegacyPlatformObject.cpp @@ -18,52 +18,6 @@ LegacyPlatformObject::LegacyPlatformObject(JS::Realm& realm) LegacyPlatformObject::~LegacyPlatformObject() = default; -// https://webidl.spec.whatwg.org/#dfn-named-property-visibility -JS::ThrowCompletionOr LegacyPlatformObject::is_named_property_exposed_on_object(JS::PropertyKey const& property_key) const -{ - // 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. - Vector 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; - - // 3. If O implements an interface that has the [LegacyOverrideBuiltIns] extended attribute, then return true. - if (has_legacy_override_built_ins_interface_extended_attribute()) - return true; - - // 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; -} - // https://webidl.spec.whatwg.org/#LegacyPlatformObjectGetOwnProperty JS::ThrowCompletionOr> LegacyPlatformObject::legacy_platform_object_get_own_property(JS::PropertyKey const& property_name, IgnoreNamedProps ignore_named_props) const { @@ -106,7 +60,7 @@ JS::ThrowCompletionOr> LegacyPlatformObject::le // 2. If O supports named properties and ignoreNamedProps is false, then: if (supports_named_properties() && ignore_named_props == IgnoreNamedProps::No) { // 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))) { + if (TRY(WebIDL::is_named_property_exposed_on_object({ this }, 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(); @@ -322,7 +276,7 @@ JS::ThrowCompletionOr LegacyPlatformObject::internal_delete(JS::PropertyKe // 2. If O supports named properties, O does not implement an interface with the [Global] extended attribute and // the result of calling the named property visibility algorithm with property name P and object O is true, then: - if (supports_named_properties() && !has_global_interface_extended_attribute() && TRY(is_named_property_exposed_on_object(property_name))) { + if (supports_named_properties() && !has_global_interface_extended_attribute() && TRY(WebIDL::is_named_property_exposed_on_object({ this }, property_name))) { // 1. If O does not implement an interface with a named property deleter, then return false. if (!has_named_property_deleter()) return false; @@ -394,7 +348,7 @@ JS::ThrowCompletionOr> LegacyPlatformObject::interna // 3. If O supports named properties, then for each P of O’s supported property names that is visible according to the named property visibility algorithm, append P to keys. if (supports_named_properties()) { for (auto& named_property : supported_property_names()) { - if (TRY(is_named_property_exposed_on_object(named_property))) + if (TRY(WebIDL::is_named_property_exposed_on_object({ this }, named_property))) keys.append(JS::PrimitiveString::create(vm, named_property)); } } diff --git a/Userland/Libraries/LibWeb/Bindings/LegacyPlatformObject.h b/Userland/Libraries/LibWeb/Bindings/LegacyPlatformObject.h index 5f13227b97..69e8e95eb8 100644 --- a/Userland/Libraries/LibWeb/Bindings/LegacyPlatformObject.h +++ b/Userland/Libraries/LibWeb/Bindings/LegacyPlatformObject.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include namespace Web::Bindings { @@ -16,6 +17,8 @@ namespace Web::Bindings { class LegacyPlatformObject : public PlatformObject { WEB_PLATFORM_OBJECT(LegacyPlatformObject, PlatformObject); + virtual bool has_legacy_override_built_ins_interface_extended_attribute() const = 0; + public: virtual ~LegacyPlatformObject() override; @@ -26,8 +29,6 @@ public: virtual JS::ThrowCompletionOr internal_prevent_extensions() override; virtual JS::ThrowCompletionOr> internal_own_property_keys() const override; - JS::ThrowCompletionOr is_named_property_exposed_on_object(JS::PropertyKey const&) const; - enum class IgnoreNamedProps { No, Yes, @@ -80,7 +81,6 @@ protected: virtual bool has_named_property_deleter() const = 0; - virtual bool has_legacy_override_built_ins_interface_extended_attribute() const = 0; virtual bool has_legacy_unenumerable_named_properties_interface_extended_attribute() const = 0; virtual bool has_global_interface_extended_attribute() const = 0; diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 8380604898..b205bdbddb 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -30,6 +30,7 @@ enum class StateAndProperties; namespace Web::Bindings { class Intrinsics; +class LegacyPlatformObject; class OptionConstructor; enum class AudioContextLatencyCategory; diff --git a/Userland/Libraries/LibWeb/HTML/Window.cpp b/Userland/Libraries/LibWeb/HTML/Window.cpp index 4eb7b56cda..35dd0768af 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.cpp +++ b/Userland/Libraries/LibWeb/HTML/Window.cpp @@ -1437,4 +1437,16 @@ OrderedHashMap> Window::document_tree_child_ return names; } +// https://html.spec.whatwg.org/#named-access-on-the-window-object +Vector Window::supported_property_names() +{ + return {}; +} + +// https://html.spec.whatwg.org/#named-access-on-the-window-object +WebIDL::ExceptionOr Window::named_item_value(DeprecatedFlyString const& name) +{ + return JS::js_undefined(); +} + } diff --git a/Userland/Libraries/LibWeb/HTML/Window.h b/Userland/Libraries/LibWeb/HTML/Window.h index 6d090805ea..c67e02a9d3 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.h +++ b/Userland/Libraries/LibWeb/HTML/Window.h @@ -193,6 +193,9 @@ public: [[nodiscard]] OrderedHashMap> document_tree_child_navigable_target_name_property_set(); + [[nodiscard]] Vector supported_property_names(); + [[nodiscard]] WebIDL::ExceptionOr named_item_value(DeprecatedFlyString const&); + private: explicit Window(JS::Realm&); diff --git a/Userland/Libraries/LibWeb/HTML/Window.idl b/Userland/Libraries/LibWeb/HTML/Window.idl index 3ddccfadfe..ca19c0697a 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.idl +++ b/Userland/Libraries/LibWeb/HTML/Window.idl @@ -37,6 +37,11 @@ interface Window : EventTarget { readonly attribute Element? frameElement; WindowProxy? open(optional USVString url = "", optional DOMString target = "_blank", optional [LegacyNullToEmptyString] DOMString features = ""); + // Since this is the global object, the IDL named getter adds a NamedPropertiesObject exotic + // object on the prototype chain. Indeed, this does not make the global object an exotic object. + // Indexed access is taken care of by the WindowProxy exotic object. + getter object (DOMString name); + // the user agent readonly attribute Navigator navigator; [ImplementedAs=navigator] readonly attribute Navigator clientInformation; // legacy alias of .navigator diff --git a/Userland/Libraries/LibWeb/WebIDL/AbstractOperations.cpp b/Userland/Libraries/LibWeb/WebIDL/AbstractOperations.cpp index 0c1a80204d..9459ad10a2 100644 --- a/Userland/Libraries/LibWeb/WebIDL/AbstractOperations.cpp +++ b/Userland/Libraries/LibWeb/WebIDL/AbstractOperations.cpp @@ -12,6 +12,9 @@ #include #include #include +#include +#include +#include #include #include @@ -301,4 +304,54 @@ JS::Completion construct(WebIDL::CallbackType& callback, JS::MarkedVector is_named_property_exposed_on_object(Variant const& variant, JS::PropertyKey const& property_key) +{ + auto const& object = *variant.visit([](auto& o) { return static_cast(o); }); + + // 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. + Vector supported_property_names = variant.visit([](auto* o) { return o->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.JS::Object::internal_get_own_property(property_key)); + + if (own_property_named_p.has_value()) + return false; + + // 3. If O implements an interface that has the [LegacyOverrideBuiltIns] extended attribute, then return true. + if (variant.has() && variant.get()->has_legacy_override_built_ins_interface_extended_attribute()) + return true; + + // 4. Let prototype be O.[[GetPrototypeOf]](). + auto* prototype = TRY(object.internal_get_prototype_of()); + + // 5. While prototype is not null: + while (prototype) { + // 1. If prototype is not a named properties object, and prototype has an own property named P, then return false. + // FIXME: Are there other named properties objects? + if (!is(prototype)) { + 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; +} + } diff --git a/Userland/Libraries/LibWeb/WebIDL/AbstractOperations.h b/Userland/Libraries/LibWeb/WebIDL/AbstractOperations.h index 2406285247..be19603e49 100644 --- a/Userland/Libraries/LibWeb/WebIDL/AbstractOperations.h +++ b/Userland/Libraries/LibWeb/WebIDL/AbstractOperations.h @@ -61,4 +61,6 @@ JS::Completion construct(WebIDL::CallbackType& callback, Args&&... args) return construct(callback, move(arguments_list)); } +JS::ThrowCompletionOr is_named_property_exposed_on_object(Variant const& variant, JS::PropertyKey const& property_key); + }