summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimothy Flynn <trflynn89@pm.me>2023-03-15 10:02:04 -0400
committerTim Flynn <trflynn89@pm.me>2023-03-15 12:48:25 -0400
commit020c2b59c4a20e9e99ffbeacfa9bc2a1319d9bc2 (patch)
tree4d8716e989f7f15f34ae7ccc2ee0da051b3ab010
parent61ecdbca543af4f0c832b66162bc198a7cef971a (diff)
downloadserenity-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.
-rw-r--r--Meta/CMake/libweb_generators.cmake39
-rw-r--r--Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp145
-rw-r--r--Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/main.cpp13
-rw-r--r--Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWindowOrWorkerInterfaces.cpp76
-rw-r--r--Userland/Libraries/LibWeb/Bindings/Intrinsics.h20
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);