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.
This commit is contained in:
Timothy Flynn 2023-03-15 10:02:04 -04:00 committed by Tim Flynn
parent 61ecdbca54
commit 020c2b59c4
Notes: sideshowbarker 2024-07-17 02:42:21 +09:00
5 changed files with 269 additions and 24 deletions

View file

@ -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)

View file

@ -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 };

View file

@ -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);

View file

@ -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"~~~(

View file

@ -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;
@ -61,6 +75,12 @@ private:
return *verify_cast<HostDefined>(realm.host_defined())->intrinsics;
}
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)
{