summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb
diff options
context:
space:
mode:
authorLinus Groh <mail@linusgroh.de>2021-02-17 22:36:59 +0100
committerAndreas Kling <kling@serenityos.org>2021-02-17 23:45:07 +0100
commit8c7d1986b80857dc17c5cb4cb9d1ea44ecb39c50 (patch)
tree77040d2a1d1c40cdb7ea08225a75c4f4e526c788 /Userland/Libraries/LibWeb
parente3577f871bffac17f09a3cb201c2cd4dfca5a90a (diff)
downloadserenity-8c7d1986b80857dc17c5cb4cb9d1ea44ecb39c50.zip
LibWeb: Actually construct impl and wrapper in construct() :^)
FooConstructor::construct() is no longer a dummy but now generates either code to throw an exception (for interfaces without constructor) or code to construct the wrapper and its impl object. Constructor overloads are not currenly handled, but that's not something we need right now anyway. Instead of regular create() this uses a new static function create_with_global_object() and passes the WindowObject, which may be needed - e.g. for XMLHttpRequest, which has an IDL and JavaScript constructor with no arguments, but needs a DOM::Window in its create().
Diffstat (limited to 'Userland/Libraries/LibWeb')
-rw-r--r--Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp322
1 files changed, 190 insertions, 132 deletions
diff --git a/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp b/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp
index 81509d2880..2cd040f43f 100644
--- a/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp
+++ b/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp
@@ -520,6 +520,140 @@ static bool is_wrappable_type(const IDL::Type& type)
return false;
}
+template<typename ParameterType>
+static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter, const String& js_name, const String& js_suffix, const String& cpp_name, bool return_void = false, bool legacy_null_to_empty_string = false, bool optional = false)
+{
+ auto scoped_generator = generator.fork();
+ scoped_generator.set("cpp_name", make_input_acceptable_cpp(cpp_name));
+ scoped_generator.set("js_name", js_name);
+ scoped_generator.set("js_suffix", js_suffix);
+ scoped_generator.set("legacy_null_to_empty_string", legacy_null_to_empty_string ? "true" : "false");
+ scoped_generator.set("parameter.type.name", parameter.type.name);
+
+ if (return_void)
+ scoped_generator.set("return_statement", "return;");
+ else
+ scoped_generator.set("return_statement", "return {};");
+
+ // FIXME: Add support for optional to all types
+ if (parameter.type.name == "DOMString") {
+ if (!optional) {
+ scoped_generator.append(R"~~~(
+ auto @cpp_name@ = @js_name@@js_suffix@.to_string(global_object, @legacy_null_to_empty_string@);
+ if (vm.exception())
+ @return_statement@
+)~~~");
+ } else {
+ scoped_generator.append(R"~~~(
+ String @cpp_name@;
+ if (!@js_name@@js_suffix@.is_undefined()) {
+ @cpp_name@ = @js_name@@js_suffix@.to_string(global_object, @legacy_null_to_empty_string@);
+ if (vm.exception())
+ @return_statement@
+ }
+)~~~");
+ }
+ } else if (parameter.type.name == "EventListener") {
+ scoped_generator.append(R"~~~(
+ if (!@js_name@@js_suffix@.is_function()) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotA, "Function");
+ @return_statement@
+ }
+ auto @cpp_name@ = adopt(*new EventListener(JS::make_handle(&@js_name@@js_suffix@.as_function())));
+)~~~");
+ } else if (is_wrappable_type(parameter.type)) {
+ scoped_generator.append(R"~~~(
+ auto @cpp_name@_object = @js_name@@js_suffix@.to_object(global_object);
+ if (vm.exception())
+ @return_statement@
+
+ if (!is<@parameter.type.name@Wrapper>(@cpp_name@_object)) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotA, "@parameter.type.name@");
+ @return_statement@
+ }
+
+ auto& @cpp_name@ = static_cast<@parameter.type.name@Wrapper*>(@cpp_name@_object)->impl();
+)~~~");
+ } else if (parameter.type.name == "double") {
+ scoped_generator.append(R"~~~(
+ auto @cpp_name@ = @js_name@@js_suffix@.to_double(global_object);
+ if (vm.exception())
+ @return_statement@
+)~~~");
+ } else if (parameter.type.name == "boolean") {
+ scoped_generator.append(R"~~~(
+ auto @cpp_name@ = @js_name@@js_suffix@.to_boolean();
+)~~~");
+ } else if (parameter.type.name == "unsigned long") {
+ scoped_generator.append(R"~~~(
+ auto @cpp_name@ = @js_name@@js_suffix@.to_u32(global_object);
+ if (vm.exception())
+ @return_statement@
+)~~~");
+ } else if (parameter.type.name == "EventHandler") {
+ // x.onfoo = function() { ... }
+ scoped_generator.append(R"~~~(
+ HTML::EventHandler @cpp_name@;
+ if (@js_name@@js_suffix@.is_function()) {
+ @cpp_name@.callback = JS::make_handle(&@js_name@@js_suffix@.as_function());
+ } else if (@js_name@@js_suffix@.is_string()) {
+ @cpp_name@.string = @js_name@@js_suffix@.as_string().string();
+ } else {
+ @return_statement@
+ }
+)~~~");
+ } else {
+ dbgln("Unimplemented JS-to-C++ conversion: {}", parameter.type.name);
+ ASSERT_NOT_REACHED();
+ }
+};
+
+template<typename FunctionType>
+static void generate_argument_count_check(SourceGenerator& generator, FunctionType& function)
+{
+ auto argument_count_check_generator = generator.fork();
+ argument_count_check_generator.set("function.name", function.name);
+ argument_count_check_generator.set("function.nargs", String::number(function.length()));
+
+ if (function.length() == 0)
+ return;
+ if (function.length() == 1) {
+ argument_count_check_generator.set(".bad_arg_count", "JS::ErrorType::BadArgCountOne");
+ argument_count_check_generator.set(".arg_count_suffix", "");
+ } else {
+ argument_count_check_generator.set(".bad_arg_count", "JS::ErrorType::BadArgCountMany");
+ argument_count_check_generator.set(".arg_count_suffix", String::formatted(", \"{}\"", function.length()));
+ }
+
+ argument_count_check_generator.append(R"~~~(
+ if (vm.argument_count() < @function.nargs@) {
+ vm.throw_exception<JS::TypeError>(global_object, @.bad_arg_count@, "@function.name@"@.arg_count_suffix@);
+ return {};
+ }
+)~~~");
+};
+
+static void generate_arguments(SourceGenerator& generator, const Vector<IDL::Parameter>& parameters, StringBuilder& arguments_builder, bool return_void = false)
+{
+ auto arguments_generator = generator.fork();
+
+ Vector<String> parameter_names;
+ size_t argument_index = 0;
+ for (auto& parameter : parameters) {
+ parameter_names.append(make_input_acceptable_cpp(snake_name(parameter.name)));
+ arguments_generator.set("argument.index", String::number(argument_index));
+
+ arguments_generator.append(R"~~~(
+ auto arg@argument.index@ = vm.argument(@argument.index@);
+)~~~");
+ // FIXME: Parameters can have [LegacyNullToEmptyString] attached.
+ generate_to_cpp(generator, parameter, "arg", String::number(argument_index), snake_name(parameter.name), return_void, false, parameter.optional);
+ ++argument_index;
+ }
+
+ arguments_builder.join(", ", parameter_names);
+};
+
static void generate_header(const IDL::Interface& interface)
{
StringBuilder builder;
@@ -566,6 +700,8 @@ namespace Web::Bindings {
class @wrapper_class@ : public @wrapper_base_class@ {
JS_OBJECT(@wrapper_class@, @wrapper_base_class@);
public:
+ static @wrapper_class@* create(JS::GlobalObject&, @fully_qualified_name@&);
+
@wrapper_class@(JS::GlobalObject&, @fully_qualified_name@&);
virtual void initialize(JS::GlobalObject&) override;
virtual ~@wrapper_class@() override;
@@ -656,6 +792,11 @@ using namespace Web::HTML;
namespace Web::Bindings {
+@wrapper_class@* @wrapper_class@::create(JS::GlobalObject& global_object, @fully_qualified_name@& impl)
+{
+ return global_object.heap().allocate<@wrapper_class@>(global_object, global_object, impl);
+}
+
)~~~");
if (interface.wrapper_base_class == "Wrapper") {
@@ -798,12 +939,52 @@ JS::Value @constructor_class@::call()
JS::Value @constructor_class@::construct(Function&)
{
+)~~~");
+
+ if (interface.constructors.is_empty()) {
+ // No constructor
+ generator.set("constructor.length", "0");
+ generator.append(R"~~~(
+ vm().throw_exception<JS::TypeError>(global_object(), JS::ErrorType::NotAConstructor, "@name@");
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
+)~~~");
+ } else if (interface.constructors.size() == 1) {
+ // Single constructor
+
+ auto& constructor = interface.constructors[0];
+ generator.set("constructor.length", String::number(constructor.length()));
+
+ generator.append(R"~~~(
+ [[maybe_unused]] auto& vm = this->vm();
+ auto& global_object = this->global_object();
+
+ auto& window = static_cast<WindowObject&>(global_object);
+)~~~");
+
+ if (!constructor.parameters.is_empty()) {
+ generate_argument_count_check(generator, constructor);
+
+ StringBuilder arguments_builder;
+ generate_arguments(generator, constructor.parameters, arguments_builder);
+ generator.set(".constructor_arguments", arguments_builder.string_view());
+
+ generator.append(R"~~~(
+ auto impl = @fully_qualified_name@::create_with_global_object(window, @.constructor_arguments@);
+)~~~");
+ } else {
+ generator.append(R"~~~(
+ auto impl = @fully_qualified_name@::create_with_global_object(window);
+)~~~");
+ }
+ generator.append(R"~~~(
+ return @wrapper_class@::create(global_object, impl);
+)~~~");
+ } else {
+ // Multiple constructor overloads - can't do that yet.
+ TODO();
+ }
+
+ generator.append(R"~~~(
}
void @constructor_class@::initialize(JS::GlobalObject& global_object)
@@ -814,7 +995,7 @@ void @constructor_class@::initialize(JS::GlobalObject& global_object)
NativeFunction::initialize(global_object);
define_property(vm.names.prototype, &window.ensure_web_prototype<@prototype_class@>("@name@"), 0);
- define_property(vm.names.length, JS::Value(1), JS::Attribute::Configurable);
+ define_property(vm.names.length, JS::Value(@constructor.length@), JS::Attribute::Configurable);
)~~~");
@@ -1061,112 +1242,6 @@ static @fully_qualified_name@* impl_from(JS::VM& vm, JS::GlobalObject& global_ob
)~~~");
}
- auto generate_to_cpp = [&](auto& parameter, auto& js_name, const auto& js_suffix, auto cpp_name, bool return_void = false, bool legacy_null_to_empty_string = false, bool optional = false) {
- auto scoped_generator = generator.fork();
- scoped_generator.set("cpp_name", make_input_acceptable_cpp(cpp_name));
- scoped_generator.set("js_name", js_name);
- scoped_generator.set("js_suffix", js_suffix);
- scoped_generator.set("legacy_null_to_empty_string", legacy_null_to_empty_string ? "true" : "false");
- scoped_generator.set("parameter.type.name", parameter.type.name);
-
- if (return_void)
- scoped_generator.set("return_statement", "return;");
- else
- scoped_generator.set("return_statement", "return {};");
-
- // FIXME: Add support for optional to all types
- if (parameter.type.name == "DOMString") {
- if (!optional) {
- scoped_generator.append(R"~~~(
- auto @cpp_name@ = @js_name@@js_suffix@.to_string(global_object, @legacy_null_to_empty_string@);
- if (vm.exception())
- @return_statement@
-)~~~");
- } else {
- scoped_generator.append(R"~~~(
- String @cpp_name@;
- if (!@js_name@@js_suffix@.is_undefined()) {
- @cpp_name@ = @js_name@@js_suffix@.to_string(global_object, @legacy_null_to_empty_string@);
- if (vm.exception())
- @return_statement@
- }
-)~~~");
- }
- } else if (parameter.type.name == "EventListener") {
- scoped_generator.append(R"~~~(
- if (!@js_name@@js_suffix@.is_function()) {
- vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotA, "Function");
- @return_statement@
- }
- auto @cpp_name@ = adopt(*new EventListener(JS::make_handle(&@js_name@@js_suffix@.as_function())));
-)~~~");
- } else if (is_wrappable_type(parameter.type)) {
- scoped_generator.append(R"~~~(
- auto @cpp_name@_object = @js_name@@js_suffix@.to_object(global_object);
- if (vm.exception())
- @return_statement@
-
- if (!is<@parameter.type.name@Wrapper>(@cpp_name@_object)) {
- vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotA, "@parameter.type.name@");
- @return_statement@
- }
-
- auto& @cpp_name@ = static_cast<@parameter.type.name@Wrapper*>(@cpp_name@_object)->impl();
-)~~~");
- } else if (parameter.type.name == "double") {
- scoped_generator.append(R"~~~(
- auto @cpp_name@ = @js_name@@js_suffix@.to_double(global_object);
- if (vm.exception())
- @return_statement@
-)~~~");
- } else if (parameter.type.name == "boolean") {
- scoped_generator.append(R"~~~(
- auto @cpp_name@ = @js_name@@js_suffix@.to_boolean();
-)~~~");
- } else if (parameter.type.name == "unsigned long") {
- scoped_generator.append(R"~~~(
- auto @cpp_name@ = @js_name@@js_suffix@.to_u32(global_object);
- if (vm.exception())
- @return_statement@
-)~~~");
- } else if (parameter.type.name == "EventHandler") {
- // x.onfoo = function() { ... }
- scoped_generator.append(R"~~~(
- HTML::EventHandler @cpp_name@;
- if (@js_name@@js_suffix@.is_function()) {
- @cpp_name@.callback = JS::make_handle(&@js_name@@js_suffix@.as_function());
- } else if (@js_name@@js_suffix@.is_string()) {
- @cpp_name@.string = @js_name@@js_suffix@.as_string().string();
- } else {
- @return_statement@
- }
-)~~~");
- } else {
- dbgln("Unimplemented JS-to-C++ conversion: {}", parameter.type.name);
- ASSERT_NOT_REACHED();
- }
- };
-
- auto generate_arguments = [&](auto& parameters, auto& arguments_builder, bool return_void = false) {
- auto arguments_generator = generator.fork();
-
- Vector<String> parameter_names;
- size_t argument_index = 0;
- for (auto& parameter : parameters) {
- parameter_names.append(make_input_acceptable_cpp(snake_name(parameter.name)));
- arguments_generator.set("argument.index", String::number(argument_index));
-
- arguments_generator.append(R"~~~(
- auto arg@argument.index@ = vm.argument(@argument.index@);
-)~~~");
- // FIXME: Parameters can have [LegacyNullToEmptyString] attached.
- generate_to_cpp(parameter, "arg", String::number(argument_index), snake_name(parameter.name), return_void, false, parameter.optional);
- ++argument_index;
- }
-
- arguments_builder.join(", ", parameter_names);
- };
-
auto generate_return_statement = [&](auto& return_type) {
auto scoped_generator = generator.fork();
scoped_generator.set("return_type", return_type.name);
@@ -1292,7 +1367,7 @@ JS_DEFINE_NATIVE_SETTER(@prototype_class@::@attribute.setter_callback@)
return;
)~~~");
- generate_to_cpp(attribute, "value", "", "cpp_value", true, attribute.extended_attributes.contains("LegacyNullToEmptyString"));
+ generate_to_cpp(generator, attribute, "value", "", "cpp_value", true, attribute.extended_attributes.contains("LegacyNullToEmptyString"));
if (attribute.extended_attributes.contains("Reflect")) {
if (attribute.type.name != "boolean") {
@@ -1324,7 +1399,6 @@ JS_DEFINE_NATIVE_SETTER(@prototype_class@::@attribute.setter_callback@)
auto function_generator = generator.fork();
function_generator.set("function.name", function.name);
function_generator.set("function.name:snakecase", snake_name(function.name));
- function_generator.set("function.nargs", String::number(function.length()));
function_generator.append(R"~~~(
JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::@function.name:snakecase@)
@@ -1334,26 +1408,10 @@ JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::@function.name:snakecase@)
return {};
)~~~");
- if (function.length() > 0) {
- if (function.length() == 1) {
- function_generator.set(".bad_arg_count", "JS::ErrorType::BadArgCountOne");
- function_generator.set(".arg_count_suffix", "");
- } else {
- function_generator.set(".bad_arg_count", "JS::ErrorType::BadArgCountMany");
- function_generator.set(".arg_count_suffix", String::formatted(", \"{}\"", function.length()));
- }
-
- function_generator.append(R"~~~(
- if (vm.argument_count() < @function.nargs@) {
- vm.throw_exception<JS::TypeError>(global_object, @.bad_arg_count@, "@function.name@"@.arg_count_suffix@);
- return {};
- }
-)~~~");
- }
+ generate_argument_count_check(generator, function);
StringBuilder arguments_builder;
- generate_arguments(function.parameters, arguments_builder);
-
+ generate_arguments(generator, function.parameters, arguments_builder);
function_generator.set(".arguments", arguments_builder.string_view());
if (function.return_type.name != "undefined") {