summaryrefslogtreecommitdiff
path: root/Userland/Libraries
diff options
context:
space:
mode:
authorAndreas Kling <kling@serenityos.org>2021-01-18 08:35:46 +0100
committerAndreas Kling <kling@serenityos.org>2021-01-18 12:18:29 +0100
commit252a98042d1f0bdf90df4adff55f3b34e8703098 (patch)
treecfed1d830c441ba9284f3b11b361b50643917482 /Userland/Libraries
parent81839ea1bd273786c944a0878e62ca5d326a295f (diff)
downloadserenity-252a98042d1f0bdf90df4adff55f3b34e8703098.zip
LibWeb: Generate constructor and prototype classes for IDL interfaces
This patch adds a FooPrototype and FooConstructor class for each IDL interface we generate JS bindings for. These classes are very primitive and don't do everything they should yet, but we have to start somewhere. :^) Work towards #4789
Diffstat (limited to 'Userland/Libraries')
-rw-r--r--Userland/Libraries/LibWeb/CMakeLists.txt34
-rw-r--r--Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp231
2 files changed, 265 insertions, 0 deletions
diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt
index 1131811c04..7e97391a76 100644
--- a/Userland/Libraries/LibWeb/CMakeLists.txt
+++ b/Userland/Libraries/LibWeb/CMakeLists.txt
@@ -226,6 +226,8 @@ endfunction(add_wrapper_sources)
function(libweb_js_wrapper class)
get_filename_component(basename ${class} NAME)
add_wrapper_sources(Bindings/${basename}Wrapper.cpp Bindings/${basename}Wrapper.h)
+ add_wrapper_sources(Bindings/${basename}Constructor.cpp Bindings/${basename}Constructor.h)
+ add_wrapper_sources(Bindings/${basename}Prototype.cpp Bindings/${basename}Prototype.h)
add_custom_command(
OUTPUT Bindings/${basename}Wrapper.h
COMMAND ${write_if_different} Bindings/${basename}Wrapper.h CodeGenerators/WrapperGenerator --header ${CMAKE_CURRENT_SOURCE_DIR}/${class}.idl
@@ -240,8 +242,40 @@ function(libweb_js_wrapper class)
DEPENDS WrapperGenerator
MAIN_DEPENDENCY ${class}.idl
)
+ add_custom_command(
+ OUTPUT Bindings/${basename}Constructor.h
+ COMMAND ${write_if_different} Bindings/${basename}Constructor.h CodeGenerators/WrapperGenerator --constructor-header ${CMAKE_CURRENT_SOURCE_DIR}/${class}.idl
+ VERBATIM
+ DEPENDS WrapperGenerator
+ MAIN_DEPENDENCY ${class}.idl
+ )
+ add_custom_command(
+ OUTPUT Bindings/${basename}Constructor.cpp
+ COMMAND ${write_if_different} Bindings/${basename}Constructor.cpp CodeGenerators/WrapperGenerator --constructor-implementation ${CMAKE_CURRENT_SOURCE_DIR}/${class}.idl
+ VERBATIM
+ DEPENDS WrapperGenerator
+ MAIN_DEPENDENCY ${class}.idl
+ )
+ add_custom_command(
+ OUTPUT Bindings/${basename}Prototype.h
+ COMMAND ${write_if_different} Bindings/${basename}Prototype.h CodeGenerators/WrapperGenerator --prototype-header ${CMAKE_CURRENT_SOURCE_DIR}/${class}.idl
+ VERBATIM
+ DEPENDS WrapperGenerator
+ MAIN_DEPENDENCY ${class}.idl
+ )
+ add_custom_command(
+ OUTPUT Bindings/${basename}Prototype.cpp
+ COMMAND ${write_if_different} Bindings/${basename}Prototype.cpp CodeGenerators/WrapperGenerator --prototype-implementation ${CMAKE_CURRENT_SOURCE_DIR}/${class}.idl
+ VERBATIM
+ DEPENDS WrapperGenerator
+ MAIN_DEPENDENCY ${class}.idl
+ )
add_custom_target(generate_${basename}Wrapper.h DEPENDS Bindings/${class}Wrapper.h)
add_custom_target(generate_${basename}Wrapper.cpp DEPENDS Bindings/${class}Wrapper.cpp)
+ add_custom_target(generate_${basename}Constructor.h DEPENDS Bindings/${class}Constructor.h)
+ add_custom_target(generate_${basename}Constructor.cpp DEPENDS Bindings/${class}Constructor.cpp)
+ add_custom_target(generate_${basename}Prototype.h DEPENDS Bindings/${class}Prototype.h)
+ add_custom_target(generate_${basename}Prototype.cpp DEPENDS Bindings/${class}Prototype.cpp)
endfunction()
libweb_js_wrapper(DOM/CharacterData)
diff --git a/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp b/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp
index dbedd118b5..48355de36c 100644
--- a/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp
+++ b/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp
@@ -158,6 +158,8 @@ struct Interface {
String wrapper_class;
String wrapper_base_class;
String fully_qualified_name;
+ String constructor_class;
+ String prototype_class;
};
static OwnPtr<Interface> parse_interface(StringView filename, const StringView& input)
@@ -308,12 +310,18 @@ static OwnPtr<Interface> parse_interface(StringView filename, const StringView&
interface->wrapper_class = String::formatted("{}Wrapper", interface->name);
interface->wrapper_base_class = String::formatted("{}Wrapper", interface->parent_name.is_empty() ? String::empty() : interface->parent_name);
+ interface->constructor_class = String::formatted("{}Constructor", interface->name);
+ interface->prototype_class = String::formatted("{}Prototype", interface->name);
return interface;
}
}
+static void generate_constructor_header(const IDL::Interface&);
+static void generate_constructor_implementation(const IDL::Interface&);
+static void generate_prototype_header(const IDL::Interface&);
+static void generate_prototype_implementation(const IDL::Interface&);
static void generate_header(const IDL::Interface&);
static void generate_implementation(const IDL::Interface&);
@@ -323,8 +331,16 @@ int main(int argc, char** argv)
const char* path = nullptr;
bool header_mode = false;
bool implementation_mode = false;
+ bool constructor_header_mode = false;
+ bool constructor_implementation_mode = false;
+ bool prototype_header_mode = false;
+ bool prototype_implementation_mode = false;
args_parser.add_option(header_mode, "Generate the wrapper .h file", "header", 'H');
args_parser.add_option(implementation_mode, "Generate the wrapper .cpp file", "implementation", 'I');
+ 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');
+ args_parser.add_option(prototype_implementation_mode, "Generate the prototype .cpp file", "prototype-implementation", 'R');
args_parser.add_positional_argument(path, "IDL file", "idl-file");
args_parser.parse(argc, argv);
@@ -379,6 +395,18 @@ int main(int argc, char** argv)
if (implementation_mode)
generate_implementation(*interface);
+ if (constructor_header_mode)
+ generate_constructor_header(*interface);
+
+ if (constructor_implementation_mode)
+ generate_constructor_implementation(*interface);
+
+ if (prototype_header_mode)
+ generate_prototype_header(*interface);
+
+ if (prototype_implementation_mode)
+ generate_prototype_implementation(*interface);
+
return 0;
}
@@ -949,3 +977,206 @@ JS_DEFINE_NATIVE_FUNCTION(@wrapper_class@::@function.name:snakecase@)
outln("{}", generator.as_string_view());
}
+
+static void generate_constructor_header(const IDL::Interface& interface)
+{
+ StringBuilder builder;
+ SourceGenerator generator { builder };
+
+ generator.set("name", interface.name);
+ generator.set("fully_qualified_name", interface.fully_qualified_name);
+ generator.set("constructor_class", interface.constructor_class);
+ generator.set("constructor_class:snakecase", snake_name(interface.constructor_class));
+
+ generator.append(R"~~~(
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace Web::Bindings {
+
+class @constructor_class@ : public JS::NativeFunction {
+ JS_OBJECT(@constructor_class@, JS::NativeFunction);
+public:
+ explicit @constructor_class@(JS::GlobalObject&);
+ virtual void initialize(JS::GlobalObject&) override;
+ virtual ~@constructor_class@() override;
+
+ virtual JS::Value call() override;
+ virtual JS::Value construct(JS::Function& new_target) override;
+
+private:
+ virtual bool has_constructor() const override { return true; }
+};
+
+} // namespace Web::Bindings
+)~~~");
+
+ outln("{}", generator.as_string_view());
+}
+
+void generate_constructor_implementation(const IDL::Interface& interface)
+{
+ StringBuilder builder;
+ SourceGenerator generator { builder };
+
+ generator.set("name", interface.name);
+ generator.set("prototype_class", interface.prototype_class);
+ generator.set("wrapper_class", interface.wrapper_class);
+ generator.set("constructor_class", interface.constructor_class);
+ generator.set("prototype_class:snakecase", snake_name(interface.prototype_class));
+ generator.set("fully_qualified_name", interface.fully_qualified_name);
+
+ generator.append(R"~~~(
+#include <LibJS/Heap/Heap.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibWeb/Bindings/@constructor_class@.h>
+#include <LibWeb/Bindings/@prototype_class@.h>
+#include <LibWeb/Bindings/@wrapper_class@.h>
+#include <LibWeb/Bindings/WindowObject.h>
+#if __has_include(<LibWeb/DOM/@name@.h>)
+# include <LibWeb/DOM/@name@.h>
+#elif __has_include(<LibWeb/HTML/@name@.h>)
+# include <LibWeb/HTML/@name@.h>
+#elif __has_include(<LibWeb/UIEvents/@name@.h>)
+# include <LibWeb/UIEvents/@name@.h>
+#elif __has_include(<LibWeb/HighResolutionTime/@name@.h>)
+# include <LibWeb/HighResolutionTime/@name@.h>
+#elif __has_include(<LibWeb/SVG/@name@.h>)
+# include <LibWeb/SVG/@name@.h>
+#endif
+
+// FIXME: This is a total hack until we can figure out the namespace for a given type somehow.
+using namespace Web::DOM;
+using namespace Web::HTML;
+
+namespace Web::Bindings {
+
+@constructor_class@::@constructor_class@(JS::GlobalObject& global_object)
+ : NativeFunction(*global_object.function_prototype())
+{
+}
+
+@constructor_class@::~@constructor_class@()
+{
+}
+
+JS::Value @constructor_class@::call()
+{
+ vm().throw_exception<JS::TypeError>(global_object(), JS::ErrorType::ConstructorWithoutNew, "@name@");
+ return {};
+}
+
+JS::Value @constructor_class@::construct(Function&)
+{
+ return {};
+#if 0
+ // FIXME: It would be cool to construct stuff!
+ auto& window = static_cast<WindowObject&>(global_object());
+ return heap().allocate<@wrapper_class@>(window, window, @fully_qualified_name@::create(window.impl()));
+#endif
+}
+
+void @constructor_class@::initialize(JS::GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ auto& window = static_cast<WindowObject&>(global_object);
+ [[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable;
+
+ NativeFunction::initialize(global_object);
+ define_property(vm.names.prototype, window.web_prototype("@prototype_class@"), 0);
+ define_property(vm.names.length, JS::Value(1), JS::Attribute::Configurable);
+}
+
+} // namespace Web::Bindings
+)~~~");
+
+ outln("{}", generator.as_string_view());
+}
+
+static void generate_prototype_header(const IDL::Interface& interface)
+{
+ StringBuilder builder;
+ SourceGenerator generator { builder };
+
+ generator.set("name", interface.name);
+ generator.set("fully_qualified_name", interface.fully_qualified_name);
+ generator.set("prototype_class", interface.prototype_class);
+ generator.set("prototype_class:snakecase", snake_name(interface.prototype_class));
+
+ generator.append(R"~~~(
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace Web::Bindings {
+
+class @prototype_class@ : public JS::Object {
+ JS_OBJECT(@prototype_class@, JS::Object);
+public:
+ explicit @prototype_class@(JS::GlobalObject&);
+ virtual void initialize(JS::GlobalObject&) override;
+ virtual ~@prototype_class@() override;
+};
+
+} // namespace Web::Bindings
+ )~~~");
+
+ outln("{}", generator.as_string_view());
+}
+
+void generate_prototype_implementation(const IDL::Interface& interface)
+{
+ StringBuilder builder;
+ SourceGenerator generator { builder };
+
+ generator.set("name", interface.name);
+ generator.set("prototype_class", interface.prototype_class);
+ generator.set("wrapper_class", interface.wrapper_class);
+ generator.set("constructor_class", interface.constructor_class);
+ generator.set("prototype_class:snakecase", snake_name(interface.prototype_class));
+ generator.set("fully_qualified_name", interface.fully_qualified_name);
+
+ generator.append(R"~~~(
+#include <AK/Function.h>
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibWeb/Bindings/@prototype_class@.h>
+#if __has_include(<LibWeb/DOM/@name@.h>)
+# include <LibWeb/DOM/@name@.h>
+#elif __has_include(<LibWeb/HTML/@name@.h>)
+# include <LibWeb/HTML/@name@.h>
+#elif __has_include(<LibWeb/UIEvents/@name@.h>)
+# include <LibWeb/UIEvents/@name@.h>
+#elif __has_include(<LibWeb/HighResolutionTime/@name@.h>)
+# include <LibWeb/HighResolutionTime/@name@.h>
+#elif __has_include(<LibWeb/SVG/@name@.h>)
+# include <LibWeb/SVG/@name@.h>
+#endif
+
+// FIXME: This is a total hack until we can figure out the namespace for a given type somehow.
+using namespace Web::DOM;
+using namespace Web::HTML;
+
+namespace Web::Bindings {
+
+@prototype_class@::@prototype_class@(JS::GlobalObject& global_object)
+ : Object(*global_object.object_prototype())
+{
+}
+
+@prototype_class@::~@prototype_class@()
+{
+}
+
+void @prototype_class@::initialize(JS::GlobalObject& global_object)
+{
+ [[maybe_unused]] auto& vm = this->vm();
+ Object::initialize(global_object);
+}
+
+} // namespace Web::Bindings
+)~~~");
+
+ outln("{}", generator.as_string_view());
+}