From 8c7d1986b80857dc17c5cb4cb9d1ea44ecb39c50 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Wed, 17 Feb 2021 22:36:59 +0100 Subject: [PATCH] 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(). --- .../CodeGenerators/WrapperGenerator.cpp | 322 +++++++++++------- 1 file 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 +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(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(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 +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(global_object, @.bad_arg_count@, "@function.name@"@.arg_count_suffix@); + return {}; + } +)~~~"); +}; + +static void generate_arguments(SourceGenerator& generator, const Vector& parameters, StringBuilder& arguments_builder, bool return_void = false) +{ + auto arguments_generator = generator.fork(); + + Vector 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(global_object(), JS::ErrorType::NotAConstructor, "@name@"); return {}; -#if 0 - // FIXME: It would be cool to construct stuff! - auto& window = static_cast(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(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(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(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 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(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") {