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:
parent
61ecdbca54
commit
020c2b59c4
Notes:
sideshowbarker
2024-07-17 02:42:21 +09:00
Author: https://github.com/trflynn89 Commit: https://github.com/SerenityOS/serenity/commit/020c2b59c4 Pull-request: https://github.com/SerenityOS/serenity/pull/17870 Reviewed-by: https://github.com/linusg
5 changed files with 269 additions and 24 deletions
|
@ -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)
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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"~~~(
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue