From cdde3ba5c5e519ce4beed4d9944939e43c67da31 Mon Sep 17 00:00:00 2001 From: Idan Horowitz Date: Mon, 27 Sep 2021 23:45:29 +0300 Subject: [PATCH] LibWeb: Add partial support for IDL Iterable declarations This currently only supports pair iterables (i.e. iterable) support for value iterables (i.e. iterable) is left as TODO(). Since currently our cmake setup calls the WrapperGenerator separately and unconditionally for each (hard-coded) output file iterable wrappers have to be explicitly marked so in the CMakeLists.txt declaration, we could likely improve this in the future by querying WrapperGenerator for the outputs based on the IDL. --- .../LibWeb/WrapperGenerator.cpp | 458 ++++++++++++++++++ Userland/Libraries/LibWeb/CMakeLists.txt | 16 + 2 files changed, 474 insertions(+) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator.cpp index 6aa4320be0..7db98f6377 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -138,6 +139,7 @@ struct Interface { Vector static_functions; bool has_stringifier { false }; Optional stringifier_attribute; + Optional> iterator_types; Optional named_property_getter; Optional named_property_setter; @@ -378,6 +380,23 @@ static OwnPtr parse_interface(StringView filename, StringView const& } }; + auto parse_iterable = [&]() { + assert_string("iterable"); + assert_specific('<'); + auto first_type = parse_type(); + Optional second_type; + if (lexer.next_is(',')) { + assert_specific(','); + consume_whitespace(); + second_type = parse_type(); + } else { + TODO(); + } + assert_specific('>'); + assert_specific(';'); + interface->iterator_types = Tuple { first_type, *second_type }; + }; + auto parse_getter = [&](HashMap& extended_attributes) { assert_string("getter"); consume_whitespace(); @@ -511,6 +530,11 @@ static OwnPtr parse_interface(StringView filename, StringView const& continue; } + if (lexer.next_is("iterable")) { + parse_iterable(); + continue; + } + if (lexer.next_is("readonly") || lexer.next_is("attribute")) { parse_attribute(extended_attributes); continue; @@ -551,6 +575,10 @@ static void generate_prototype_header(IDL::Interface const&); static void generate_prototype_implementation(IDL::Interface const&); static void generate_header(IDL::Interface const&); static void generate_implementation(IDL::Interface const&); +static void generate_iterator_prototype_header(IDL::Interface const&); +static void generate_iterator_prototype_implementation(IDL::Interface const&); +static void generate_iterator_header(IDL::Interface const&); +static void generate_iterator_implementation(IDL::Interface const&); int main(int argc, char** argv) { @@ -562,12 +590,20 @@ int main(int argc, char** argv) bool constructor_implementation_mode = false; bool prototype_header_mode = false; bool prototype_implementation_mode = false; + bool iterator_header_mode = false; + bool iterator_implementation_mode = false; + bool iterator_prototype_header_mode = false; + bool iterator_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_option(iterator_header_mode, "Generate the iterator wrapper .h file", "iterator-header", 0); + args_parser.add_option(iterator_implementation_mode, "Generate the iterator wrapper .cpp file", "iterator-implementation", 0); + args_parser.add_option(iterator_prototype_header_mode, "Generate the iterator prototype .h file", "iterator-prototype-header", 0); + args_parser.add_option(iterator_prototype_implementation_mode, "Generate the iterator prototype .cpp file", "iterator-prototype-implementation", 0); args_parser.add_positional_argument(path, "IDL file", "idl-file"); args_parser.parse(argc, argv); @@ -655,6 +691,18 @@ int main(int argc, char** argv) if (prototype_implementation_mode) generate_prototype_implementation(*interface); + if (iterator_header_mode) + generate_iterator_header(*interface); + + if (iterator_implementation_mode) + generate_iterator_implementation(*interface); + + if (iterator_prototype_header_mode) + generate_iterator_prototype_header(*interface); + + if (iterator_prototype_implementation_mode) + generate_iterator_prototype_implementation(*interface); + return 0; } @@ -1061,6 +1109,16 @@ static void generate_return_statement(SourceGenerator& generator, IDL::Type cons return generate_wrap_statement(generator, "retval", return_type, "return"sv); } +static void generate_variable_statement(SourceGenerator& generator, String const& variable_name, IDL::Type const& value_type, String const& value_name) +{ + auto variable_generator = generator.fork(); + variable_generator.set("variable_name", variable_name); + variable_generator.append(R"~~~( + JS::Value @variable_name@; +)~~~"); + return generate_wrap_statement(generator, value_name, value_type, String::formatted("{} = ", variable_name)); +} + static void generate_function(SourceGenerator& generator, IDL::Function const& function, StaticFunction is_static_function, String const& class_name, String const& interface_fully_qualified_name) { auto function_generator = generator.fork(); @@ -2410,6 +2468,16 @@ private: )~~~"); } + if (interface.iterator_types.has_value()) { + auto iterator_generator = generator.fork(); + iterator_generator.append(R"~~~( + JS_DECLARE_NATIVE_FUNCTION(entries); + JS_DECLARE_NATIVE_FUNCTION(for_each); + JS_DECLARE_NATIVE_FUNCTION(keys); + JS_DECLARE_NATIVE_FUNCTION(values); + )~~~"); + } + for (auto& attribute : interface.attributes) { auto attribute_generator = generator.fork(); attribute_generator.set("attribute.name:snakecase", attribute.name.to_snakecase()); @@ -2447,6 +2515,14 @@ void generate_prototype_implementation(IDL::Interface const& interface) generator.set("prototype_class:snakecase", interface.prototype_class.to_snakecase()); generator.set("fully_qualified_name", interface.fully_qualified_name); + if (interface.iterator_types.has_value()) { + generator.set("iterator_name", String::formatted("{}Iterator", interface.name)); + generator.set("iterator_wrapper_class", String::formatted("{}IteratorWrapper", interface.name)); + generator.append(R"~~~( +#include +)~~~"); + } + generator.append(R"~~~( #include #include @@ -2524,6 +2600,38 @@ void generate_prototype_implementation(IDL::Interface const& interface) # include #endif +)~~~"); + + if (interface.iterator_types.has_value()) { + generator.append(R"~~~( +#if __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#endif +)~~~"); + } + + 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; @@ -2617,6 +2725,18 @@ void @prototype_class@::initialize(JS::GlobalObject& global_object) )~~~"); } + if (interface.iterator_types.has_value()) { + auto iterator_generator = generator.fork(); + iterator_generator.append(R"~~~( + define_native_function(vm.names.entries, entries, 0, default_attributes); + define_native_function(vm.names.forEach, for_each, 1, default_attributes); + define_native_function(vm.names.keys, keys, 0, default_attributes); + define_native_function(vm.names.values, values, 0, default_attributes); + + define_direct_property(*vm.well_known_symbol_iterator(), Object::get(vm.names.entries), JS::Attribute::Configurable | JS::Attribute::Writable); +)~~~"); + } + generator.append(R"~~~( Object::initialize(global_object); } @@ -2793,9 +2913,347 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::to_string) )~~~"); } + if (interface.iterator_types.has_value()) { + auto iterator_generator = generator.fork(); + iterator_generator.append(R"~~~( +JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::entries) +{ + auto* impl = impl_from(vm, global_object); + if (!impl) + return {}; + + return wrap(global_object, @iterator_name@::create(*impl, Object::PropertyKind::KeyAndValue)); +} + +JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::for_each) +{ + auto* impl = impl_from(vm, global_object); + if (!impl) + return {}; + + auto callback = vm.argument(0); + if (!callback.is_function()) { + vm.throw_exception(global_object, JS::ErrorType::NotAFunction, callback.to_string_without_side_effects()); + return {}; + } + + auto this_value = vm.this_value(global_object); + impl->for_each([&](auto key, auto value) { +)~~~"); + generate_variable_statement(iterator_generator, "wrapped_key", interface.iterator_types->get<0>(), "key"); + generate_variable_statement(iterator_generator, "wrapped_value", interface.iterator_types->get<1>(), "value"); + iterator_generator.append(R"~~~( + auto result = vm.call(callback.as_function(), vm.argument(1), wrapped_value, wrapped_key, this_value); + return result.is_error() ? IterationDecision::Break : IterationDecision::Continue; + }); + if (vm.exception()) + return {}; + + return JS::js_undefined(); +} + +JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::keys) +{ + auto* impl = impl_from(vm, global_object); + if (!impl) + return {}; + + return wrap(global_object, @iterator_name@::create(*impl, Object::PropertyKind::Key)); +} + +JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::values) +{ + auto* impl = impl_from(vm, global_object); + if (!impl) + return {}; + + return wrap(global_object, @iterator_name@::create(*impl, Object::PropertyKind::Value)); +} +)~~~"); + } + generator.append(R"~~~( } // namespace Web::Bindings )~~~"); outln("{}", generator.as_string_view()); } + +static void generate_iterator_header(IDL::Interface const& interface) +{ + VERIFY(interface.iterator_types.has_value()); + StringBuilder builder; + SourceGenerator generator { builder }; + + generator.set("name", String::formatted("{}Iterator", interface.name)); + generator.set("fully_qualified_name", String::formatted("{}Iterator", interface.fully_qualified_name)); + generator.set("wrapper_class", String::formatted("{}IteratorWrapper", interface.name)); + + generator.append(R"~~~( +#pragma once + +#include + +// FIXME: This is very strange. +#if __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#endif + +namespace Web::Bindings { + +class @wrapper_class@ : public Wrapper { + JS_OBJECT(@name@, Wrapper); +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; + + @fully_qualified_name@& impl() { return *m_impl; } + @fully_qualified_name@ const& impl() const { return *m_impl; } + +private: + virtual void visit_edges(Cell::Visitor&) override; // The Iterator implementation has to visit the wrapper it's iterating + + NonnullRefPtr<@fully_qualified_name@> m_impl; +}; + +@wrapper_class@* wrap(JS::GlobalObject&, @fully_qualified_name@&); + +} // namespace Web::Bindings +)~~~"); + + outln("{}", generator.as_string_view()); +} + +void generate_iterator_implementation(IDL::Interface const& interface) +{ + VERIFY(interface.iterator_types.has_value()); + StringBuilder builder; + SourceGenerator generator { builder }; + + generator.set("name", String::formatted("{}Iterator", interface.name)); + generator.set("fully_qualified_name", String::formatted("{}Iterator", interface.fully_qualified_name)); + generator.set("prototype_class", String::formatted("{}IteratorPrototype", interface.name)); + generator.set("wrapper_class", String::formatted("{}IteratorWrapper", interface.name)); + + generator.append(R"~~~( +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// 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::Geometry; +using namespace Web::HTML; +using namespace Web::RequestIdleCallback; + +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); +} + +@wrapper_class@::@wrapper_class@(JS::GlobalObject& global_object, @fully_qualified_name@& impl) + : Wrapper(static_cast(global_object).ensure_web_prototype<@prototype_class@>("@name@")) + , m_impl(impl) +{ +} + +void @wrapper_class@::initialize(JS::GlobalObject& global_object) +{ + Wrapper::initialize(global_object); +} + +@wrapper_class@::~@wrapper_class@() +{ +} + +void @wrapper_class@::visit_edges(Cell::Visitor& visitor) +{ + Wrapper::visit_edges(visitor); + impl().visit_edges(visitor); +} + +@wrapper_class@* wrap(JS::GlobalObject& global_object, @fully_qualified_name@& impl) +{ + return static_cast<@wrapper_class@*>(wrap_impl(global_object, impl)); +} + +} // namespace Web::Bindings +)~~~"); + + outln("{}", generator.as_string_view()); +} + +static void generate_iterator_prototype_header(IDL::Interface const& interface) +{ + VERIFY(interface.iterator_types.has_value()); + StringBuilder builder; + SourceGenerator generator { builder }; + + generator.set("prototype_class", String::formatted("{}IteratorPrototype", interface.name)); + + generator.append(R"~~~( +#pragma once + +#include + +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; + +private: + JS_DECLARE_NATIVE_FUNCTION(next); +}; + +} // namespace Web::Bindings + )~~~"); + + outln("{}", generator.as_string_view()); +} + +void generate_iterator_prototype_implementation(IDL::Interface const& interface) +{ + VERIFY(interface.iterator_types.has_value()); + StringBuilder builder; + SourceGenerator generator { builder }; + + generator.set("name", String::formatted("{}Iterator", interface.name)); + generator.set("prototype_class", String::formatted("{}IteratorPrototype", interface.name)); + generator.set("wrapper_class", String::formatted("{}IteratorWrapper", interface.name)); + generator.set("fully_qualified_name", String::formatted("{}Iterator", interface.fully_qualified_name)); + + generator.append(R"~~~( +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#elif __has_include() +# include +#endif + +// 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::Geometry; +using namespace Web::HTML; +using namespace Web::NavigationTiming; +using namespace Web::RequestIdleCallback; +using namespace Web::XHR; +using namespace Web::URL; + +namespace Web::Bindings { + +@prototype_class@::@prototype_class@(JS::GlobalObject& global_object) + : Object(*global_object.iterator_prototype()) +{ +} + +@prototype_class@::~@prototype_class@() +{ +} + +void @prototype_class@::initialize(JS::GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + + define_native_function(vm.names.next, next, 0, JS::Attribute::Configurable | JS::Attribute::Writable); + define_direct_property(*vm.well_known_symbol_to_string_tag(), js_string(vm, "Iterator"), JS::Attribute::Configurable); +} + +static @fully_qualified_name@* impl_from(JS::VM& vm, JS::GlobalObject& global_object) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + if (!is<@wrapper_class@>(this_object)) { + vm.throw_exception(global_object, JS::ErrorType::NotAnObjectOfType, "@fully_qualified_name@"); + return nullptr; + } + return &static_cast<@wrapper_class@*>(this_object)->impl(); +} + +JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::next) +{ + auto* impl = impl_from(vm, global_object); + if (!impl) + return {}; + + auto result = throw_dom_exception_if_needed(vm, global_object, [&] { return impl->next(); }); + + if (should_return_empty(result)) + return JS::Value(); + + return result.release_value(); +} + +} // namespace Web::Bindings +)~~~"); + + outln("{}", generator.as_string_view()); +} diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index e7c89a9f2e..c45b580383 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -271,6 +271,7 @@ serenity_lib(LibWeb web) target_link_libraries(LibWeb LibCore LibJS LibMarkdown LibGemini LibGUI LibGfx LibTextCodec LibProtocol LibImageDecoderClient LibWasm) function(libweb_js_wrapper class) + cmake_parse_arguments(PARSE_ARGV 1 LIBWEB_WRAPPER "ITERABLE" "" "") get_filename_component(basename "${class}" NAME) set(BINDINGS_SOURCES "Bindings/${basename}Wrapper.h" @@ -288,6 +289,21 @@ function(libweb_js_wrapper class) prototype-header prototype-implementation ) + # FIXME: Instead of requiring a manual declaration of iterable wrappers, we should ask WrapperGenerator if it's iterable + if(LIBWEB_WRAPPER_ITERABLE) + list(APPEND BINDINGS_SOURCES + "Bindings/${basename}IteratorWrapper.h" + "Bindings/${basename}IteratorWrapper.cpp" + "Bindings/${basename}IteratorPrototype.h" + "Bindings/${basename}IteratorPrototype.cpp" + ) + list(APPEND BINDINGS_TYPES + iterator-header + iterator-implementation + iterator-prototype-header + iterator-prototype-implementation + ) + endif() target_sources(LibWeb PRIVATE ${BINDINGS_SOURCES}) # FIXME: cmake_minimum_required(3.17) for ZIP_LISTS list(LENGTH BINDINGS_SOURCES num_bindings)