diff options
author | Linus Groh <mail@linusgroh.de> | 2021-02-17 22:36:59 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-02-17 23:45:07 +0100 |
commit | 8c7d1986b80857dc17c5cb4cb9d1ea44ecb39c50 (patch) | |
tree | 77040d2a1d1c40cdb7ea08225a75c4f4e526c788 /Userland/Libraries/LibWeb | |
parent | e3577f871bffac17f09a3cb201c2cd4dfca5a90a (diff) | |
download | serenity-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.cpp | 322 |
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") { |