diff options
author | Timothy Flynn <trflynn89@pm.me> | 2023-03-15 10:02:04 -0400 |
---|---|---|
committer | Tim Flynn <trflynn89@pm.me> | 2023-03-15 12:48:25 -0400 |
commit | 020c2b59c4a20e9e99ffbeacfa9bc2a1319d9bc2 (patch) | |
tree | 4d8716e989f7f15f34ae7ccc2ee0da051b3ab010 | |
parent | 61ecdbca543af4f0c832b66162bc198a7cef971a (diff) | |
download | serenity-020c2b59c4a20e9e99ffbeacfa9bc2a1319d9bc2.zip |
LibWeb: Support generating IDL namespaces
These are similar to prototypes and constructors in that they will now
be lazily instantiated when they are first requested.
5 files changed, 269 insertions, 24 deletions
diff --git a/Meta/CMake/libweb_generators.cmake b/Meta/CMake/libweb_generators.cmake index 91abe567db..05b0f47195 100644 --- a/Meta/CMake/libweb_generators.cmake +++ b/Meta/CMake/libweb_generators.cmake @@ -76,20 +76,33 @@ endfunction() function (generate_js_bindings target) set(LIBWEB_INPUT_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}") function(libweb_js_bindings class) - cmake_parse_arguments(PARSE_ARGV 1 LIBWEB_BINDINGS "ITERABLE;GLOBAL" "" "") + cmake_parse_arguments(PARSE_ARGV 1 LIBWEB_BINDINGS "NAMESPACE;ITERABLE;GLOBAL" "" "") get_filename_component(basename "${class}" NAME) - set(BINDINGS_SOURCES - "Bindings/${basename}Constructor.h" - "Bindings/${basename}Constructor.cpp" - "Bindings/${basename}Prototype.h" - "Bindings/${basename}Prototype.cpp" - ) - set(BINDINGS_TYPES - constructor-header - constructor-implementation - prototype-header - prototype-implementation - ) + + # FIXME: Instead of requiring a manual declaration of namespace bindings, we should ask BindingsGenerator if it's a namespace + if (LIBWEB_BINDINGS_NAMESPACE) + set(BINDINGS_SOURCES + "Bindings/${basename}Namespace.h" + "Bindings/${basename}Namespace.cpp" + ) + set(BINDINGS_TYPES + namespace-header + namespace-implementation + ) + else() + set(BINDINGS_SOURCES + "Bindings/${basename}Constructor.h" + "Bindings/${basename}Constructor.cpp" + "Bindings/${basename}Prototype.h" + "Bindings/${basename}Prototype.cpp" + ) + set(BINDINGS_TYPES + constructor-header + constructor-implementation + prototype-header + prototype-implementation + ) + endif() # FIXME: Instead of requiring a manual declaration of iterable bindings, we should ask BindingsGenerator if it's iterable if(LIBWEB_BINDINGS_ITERABLE) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index a776ff3c9b..eb2a1a4c7c 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -2653,6 +2653,151 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::values) } } +void generate_namespace_header(IDL::Interface const& interface, StringBuilder& builder) +{ + SourceGenerator generator { builder }; + + generator.set("namespace_class", interface.namespace_class); + + generator.append(R"~~~( +#pragma once + +#include <LibJS/Runtime/Object.h> + +namespace Web::Bindings { + +class @namespace_class@ final : public JS::Object { + JS_OBJECT(@namespace_class@, JS::Object); + +public: + explicit @namespace_class@(JS::Realm&); + virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override; + virtual ~@namespace_class@() override; + +private: +)~~~"); + + for (auto const& overload_set : interface.overload_sets) { + auto function_generator = generator.fork(); + function_generator.set("function.name:snakecase", make_input_acceptable_cpp(overload_set.key.to_snakecase())); + function_generator.append(R"~~~( + JS_DECLARE_NATIVE_FUNCTION(@function.name:snakecase@); +)~~~"); + if (overload_set.value.size() > 1) { + for (auto i = 0u; i < overload_set.value.size(); ++i) { + function_generator.set("overload_suffix", DeprecatedString::number(i)); + function_generator.append(R"~~~( + JS_DECLARE_NATIVE_FUNCTION(@function.name:snakecase@@overload_suffix@); +)~~~"); + } + } + } + + generator.append(R"~~~( +}; + +} // namespace Web::Bindings +)~~~"); +} + +void generate_namespace_implementation(IDL::Interface const& interface, StringBuilder& builder) +{ + SourceGenerator generator { builder }; + + generator.set("name", interface.name); + generator.set("namespace_class", interface.namespace_class); + + generator.append(R"~~~( +#include <AK/Function.h> +#include <LibIDL/Types.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/PrimitiveString.h> +#include <LibJS/Runtime/Value.h> +#include <LibWeb/Bindings/@namespace_class@.h> +#include <LibWeb/Bindings/ExceptionOrUtils.h> +#include <LibWeb/Bindings/Intrinsics.h> +#include <LibWeb/HTML/Window.h> +#include <LibWeb/HTML/WindowProxy.h> +#include <LibWeb/WebIDL/OverloadResolution.h> + +)~~~"); + + for (auto& path : interface.required_imported_paths) + generate_include_for(generator, path); + + emit_includes_for_all_imports(interface, generator, interface.pair_iterator_types.has_value()); + + generator.append(R"~~~( +// FIXME: This is a total hack until we can figure out the namespace for a given type somehow. +using namespace Web::CSS; +using namespace Web::DOM; +using namespace Web::DOMParsing; +using namespace Web::Fetch; +using namespace Web::FileAPI; +using namespace Web::Geometry; +using namespace Web::HighResolutionTime; +using namespace Web::HTML; +using namespace Web::IntersectionObserver; +using namespace Web::RequestIdleCallback; +using namespace Web::ResizeObserver; +using namespace Web::Selection; +using namespace Web::Streams; +using namespace Web::UIEvents; +using namespace Web::URL; +using namespace Web::XHR; +using namespace Web::WebGL; +using namespace Web::WebIDL; + +namespace Web::Bindings { + +@namespace_class@::@namespace_class@(JS::Realm& realm) + : Object(ConstructWithoutPrototypeTag::Tag, realm) +{ +} + +@namespace_class@::~@namespace_class@() +{ +} + +JS::ThrowCompletionOr<void> @namespace_class@::initialize(JS::Realm& realm) +{ + [[maybe_unused]] auto& vm = this->vm(); + [[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable; + + MUST_OR_THROW_OOM(Base::initialize(realm)); + +)~~~"); + + // https://webidl.spec.whatwg.org/#es-operations + for (auto const& overload_set : interface.overload_sets) { + auto function_generator = generator.fork(); + function_generator.set("function.name", overload_set.key); + function_generator.set("function.name:snakecase", make_input_acceptable_cpp(overload_set.key.to_snakecase())); + function_generator.set("function.length", DeprecatedString::number(get_shortest_function_length(overload_set.value))); + + function_generator.append(R"~~~( + define_native_function(realm, "@function.name@", @function.name:snakecase@, @function.length@, default_attributes); +)~~~"); + } + + generator.append(R"~~~( + return {}; +} +)~~~"); + + for (auto const& function : interface.functions) + generate_function(generator, function, StaticFunction::Yes, interface.namespace_class, interface.name, interface); + for (auto const& overload_set : interface.overload_sets) { + if (overload_set.value.size() == 1) + continue; + generate_overload_arbiter(generator, overload_set, interface.namespace_class); + } + + generator.append(R"~~~( +} // namespace Web::Bindings +)~~~"); +} + void generate_constructor_header(IDL::Interface const& interface, StringBuilder& builder) { SourceGenerator generator { builder }; diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/main.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/main.cpp index f48fe9ffae..24c6d8d619 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/main.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/main.cpp @@ -18,6 +18,8 @@ extern Vector<StringView> s_header_search_paths; namespace IDL { +void generate_namespace_header(IDL::Interface const&, StringBuilder&); +void generate_namespace_implementation(IDL::Interface const&, StringBuilder&); void generate_constructor_header(IDL::Interface const&, StringBuilder&); void generate_constructor_implementation(IDL::Interface const&, StringBuilder&); void generate_prototype_header(IDL::Interface const&, StringBuilder&); @@ -36,6 +38,8 @@ ErrorOr<int> serenity_main(Main::Arguments arguments) StringView output_path = "-"sv; StringView depfile_path; StringView depfile_target; + bool namespace_header_mode = false; + bool namespace_implementation_mode = false; bool constructor_header_mode = false; bool constructor_implementation_mode = false; bool prototype_header_mode = false; @@ -44,6 +48,8 @@ ErrorOr<int> serenity_main(Main::Arguments arguments) bool iterator_prototype_implementation_mode = false; bool global_mixin_header_mode = false; bool global_mixin_implementation_mode = false; + args_parser.add_option(namespace_header_mode, "Generate the namespace .h file", "namespace-header", 'N'); + args_parser.add_option(namespace_implementation_mode, "Generate the namespace .cpp file", "namespace-implementation", 'A'); args_parser.add_option(constructor_header_mode, "Generate the constructor .h file", "constructor-header", 'C'); args_parser.add_option(constructor_implementation_mode, "Generate the constructor .cpp file", "constructor-implementation", 'O'); args_parser.add_option(prototype_header_mode, "Generate the prototype .h file", "prototype-header", 'P'); @@ -136,6 +142,13 @@ ErrorOr<int> serenity_main(Main::Arguments arguments) } StringBuilder output_builder; + + if (namespace_header_mode) + IDL::generate_namespace_header(interface, output_builder); + + if (namespace_implementation_mode) + IDL::generate_namespace_implementation(interface, output_builder); + if (constructor_header_mode) IDL::generate_constructor_header(interface, output_builder); diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWindowOrWorkerInterfaces.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWindowOrWorkerInterfaces.cpp index 29cb6df08d..8c44a6a92f 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWindowOrWorkerInterfaces.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWindowOrWorkerInterfaces.cpp @@ -69,6 +69,13 @@ static ErrorOr<void> generate_forwarding_header(StringView output_path, Vector<I namespace Web::Bindings { )~~~"); + auto add_namespace = [](SourceGenerator& gen, StringView namespace_class) { + gen.set("namespace_class", namespace_class); + + gen.append(R"~~~( +class @namespace_class@;)~~~"); + }; + auto add_interface = [](SourceGenerator& gen, StringView prototype_class, StringView constructor_class, Optional<LegacyConstructor> const& legacy_constructor) { gen.set("prototype_class", prototype_class); gen.set("constructor_class", constructor_class); @@ -86,7 +93,11 @@ class @legacy_constructor_class@;)~~~"); for (auto& interface : exposed_interfaces) { auto gen = generator.fork(); - add_interface(gen, interface.prototype_class, interface.constructor_class, lookup_legacy_constructor(interface)); + + if (interface.is_namespace) + add_namespace(gen, interface.namespace_class); + else + add_interface(gen, interface.prototype_class, interface.constructor_class, lookup_legacy_constructor(interface)); } // FIXME: Special case WebAssembly. We should convert WASM to use IDL. @@ -122,17 +133,23 @@ static ErrorOr<void> generate_intrinsic_definitions(StringView output_path, Vect for (auto& interface : exposed_interfaces) { auto gen = generator.fork(); + gen.set("namespace_class", interface.namespace_class); gen.set("prototype_class", interface.prototype_class); gen.set("constructor_class", interface.constructor_class); - gen.append(R"~~~( + if (interface.is_namespace) { + gen.append(R"~~~( +#include <LibWeb/Bindings/@namespace_class@.h>)~~~"); + } else { + gen.append(R"~~~( #include <LibWeb/Bindings/@constructor_class@.h> #include <LibWeb/Bindings/@prototype_class@.h>)~~~"); - if (auto const& legacy_constructor = lookup_legacy_constructor(interface); legacy_constructor.has_value()) { - gen.set("legacy_constructor_class", legacy_constructor->constructor_class); - gen.append(R"~~~( + if (auto const& legacy_constructor = lookup_legacy_constructor(interface); legacy_constructor.has_value()) { + gen.set("legacy_constructor_class", legacy_constructor->constructor_class); + gen.append(R"~~~( #include <LibWeb/Bindings/@legacy_constructor_class@.h>)~~~"); + } } } @@ -152,6 +169,20 @@ static ErrorOr<void> generate_intrinsic_definitions(StringView output_path, Vect namespace Web::Bindings { )~~~"); + auto add_namespace = [&](SourceGenerator& gen, StringView name, StringView namespace_class) { + gen.set("interface_name", name); + gen.set("namespace_class", namespace_class); + + gen.append(R"~~~( +template<> +void Intrinsics::create_web_namespace<@namespace_class@>(JS::Realm& realm) +{ + auto namespace_object = heap().allocate<@namespace_class@>(realm, realm).release_allocated_value_but_fixme_should_propagate_errors(); + m_namespaces.set("@interface_name@"sv, namespace_object); +} +)~~~"); + }; + auto add_interface = [&](SourceGenerator& gen, StringView name, StringView prototype_class, StringView constructor_class, Optional<LegacyConstructor> const& legacy_constructor) { gen.set("interface_name", name); gen.set("prototype_class", prototype_class); @@ -190,7 +221,11 @@ void Intrinsics::create_web_prototype_and_constructor<@prototype_class@>(JS::Rea for (auto& interface : exposed_interfaces) { auto gen = generator.fork(); - add_interface(gen, interface.name, interface.prototype_class, interface.constructor_class, lookup_legacy_constructor(interface)); + + if (interface.is_namespace) + add_namespace(gen, interface.name, interface.namespace_class); + else + add_interface(gen, interface.name, interface.prototype_class, interface.constructor_class, lookup_legacy_constructor(interface)); } // FIXME: Special case WebAssembly. We should convert WASM to use IDL. @@ -254,17 +289,24 @@ static ErrorOr<void> generate_exposed_interface_implementation(StringView class_ )~~~"); for (auto& interface : exposed_interfaces) { auto gen = generator.fork(); + gen.set("namespace_class", interface.namespace_class); gen.set("prototype_class", interface.prototype_class); gen.set("constructor_class", interface.constructor_class); - gen.append(R"~~~(#include <LibWeb/Bindings/@constructor_class@.h> + if (interface.is_namespace) { + gen.append(R"~~~(#include <LibWeb/Bindings/@namespace_class@.h> +)~~~"); + } else { + + gen.append(R"~~~(#include <LibWeb/Bindings/@constructor_class@.h> #include <LibWeb/Bindings/@prototype_class@.h> )~~~"); - if (auto const& legacy_constructor = lookup_legacy_constructor(interface); legacy_constructor.has_value()) { - gen.set("legacy_constructor_class", legacy_constructor->constructor_class); - gen.append(R"~~~(#include <LibWeb/Bindings/@legacy_constructor_class@.h> + if (auto const& legacy_constructor = lookup_legacy_constructor(interface); legacy_constructor.has_value()) { + gen.set("legacy_constructor_class", legacy_constructor->constructor_class); + gen.append(R"~~~(#include <LibWeb/Bindings/@legacy_constructor_class@.h> )~~~"); + } } } @@ -290,9 +332,21 @@ void add_@global_object_snake_name@_exposed_interfaces(JS::Object& global) } }; + auto add_namespace = [](SourceGenerator& gen, StringView name, StringView namespace_class) { + gen.set("interface_name", name); + gen.set("namespace_class", namespace_class); + + gen.append(R"~~~( + global.define_intrinsic_accessor("@interface_name@", attr, [](auto& realm) -> JS::Value { return &ensure_web_namespace<@namespace_class@>(realm, "@interface_name@"sv); });)~~~"); + }; + for (auto& interface : exposed_interfaces) { auto gen = generator.fork(); - add_interface(gen, interface.name, interface.prototype_class, lookup_legacy_constructor(interface)); + + if (interface.is_namespace) + add_namespace(gen, interface.name, interface.namespace_class); + else + add_interface(gen, interface.name, interface.prototype_class, lookup_legacy_constructor(interface)); } generator.append(R"~~~( diff --git a/Userland/Libraries/LibWeb/Bindings/Intrinsics.h b/Userland/Libraries/LibWeb/Bindings/Intrinsics.h index 67b20512d5..896a143da5 100644 --- a/Userland/Libraries/LibWeb/Bindings/Intrinsics.h +++ b/Userland/Libraries/LibWeb/Bindings/Intrinsics.h @@ -25,6 +25,16 @@ public: { } + template<typename NamespaceType> + JS::Object& ensure_web_namespace(DeprecatedString const& namespace_name) + { + if (auto it = m_namespaces.find(namespace_name); it != m_namespaces.end()) + return *it->value; + + create_web_namespace<NamespaceType>(*m_realm); + return *m_namespaces.find(namespace_name)->value; + } + template<typename PrototypeType> JS::Object& ensure_web_prototype(DeprecatedString const& class_name) { @@ -48,9 +58,13 @@ public: private: virtual void visit_edges(JS::Cell::Visitor&) override; + template<typename NamespaceType> + void create_web_namespace(JS::Realm& realm); + template<typename PrototypeType> void create_web_prototype_and_constructor(JS::Realm& realm); + HashMap<DeprecatedString, JS::NonnullGCPtr<JS::Object>> m_namespaces; HashMap<DeprecatedString, JS::NonnullGCPtr<JS::Object>> m_prototypes; HashMap<DeprecatedString, JS::GCPtr<JS::NativeFunction>> m_constructors; JS::NonnullGCPtr<JS::Realm> m_realm; @@ -62,6 +76,12 @@ private: } template<typename T> +[[nodiscard]] JS::Object& ensure_web_namespace(JS::Realm& realm, DeprecatedString const& namespace_name) +{ + return host_defined_intrinsics(realm).ensure_web_namespace<T>(namespace_name); +} + +template<typename T> [[nodiscard]] JS::Object& ensure_web_prototype(JS::Realm& realm, DeprecatedString const& class_name) { return host_defined_intrinsics(realm).ensure_web_prototype<T>(class_name); |