diff --git a/Libraries/LibIDL/IDLParser.cpp b/Libraries/LibIDL/IDLParser.cpp index 1100b3c1739..19ec6099779 100644 --- a/Libraries/LibIDL/IDLParser.cpp +++ b/Libraries/LibIDL/IDLParser.cpp @@ -530,6 +530,8 @@ void Parser::parse_iterable(Interface& interface) interface.value_iterator_type = move(first_type); } + if (interface.async_value_iterator_type.has_value()) + report_parsing_error("Interfaces with an async iterable declaration must not have an iterable declaration."sv, filename, input, lexer.tell()); if (interface.set_entry_type.has_value()) report_parsing_error("Interfaces with an iterable declaration must not have a setlike declaration."sv, filename, input, lexer.tell()); @@ -537,6 +539,44 @@ void Parser::parse_iterable(Interface& interface) assert_specific(';'); } +// https://webidl.spec.whatwg.org/#idl-async-iterable-declaration +void Parser::parse_async_iterable(Interface& interface) +{ + if (interface.async_value_iterator_type.has_value()) + report_parsing_error("Interfaces must not have more than one async iterable declaration."sv, filename, input, lexer.tell()); + if (interface.set_entry_type.has_value()) + report_parsing_error("Interfaces with an async iterable declaration must not have a setlike declaration."sv, filename, input, lexer.tell()); + if (interface.value_iterator_type.has_value()) + report_parsing_error("Interfaces with an async iterable declaration must not have an iterable declaration."sv, filename, input, lexer.tell()); + if (interface.supports_indexed_properties()) + report_parsing_error("Interfaces with an async iterable declaration must not support indexed properties."sv, filename, input, lexer.tell()); + // FIXME: Reject interfaces that have a maplike declaration when we support that type. + + assert_string("async"sv); + consume_whitespace(); + assert_string("iterable"sv); + assert_specific('<'); + + auto first_type = parse_type(); + + if (lexer.next_is(',')) { + // https://webidl.spec.whatwg.org/#pair-asynchronously-iterable-declaration + report_parsing_error("FIXME: Support paired async iterable declarations."sv, filename, input, lexer.tell()); + } else { + interface.async_value_iterator_type = move(first_type); + } + + assert_specific('>'); + + if (lexer.next_is('(')) { + assert_specific('('); + interface.async_value_iterator_parameters = parse_parameters(); + assert_specific(')'); + } + + assert_specific(';'); +} + void Parser::parse_setlike(Interface& interface, bool is_readonly) { if (interface.supports_indexed_properties()) @@ -690,6 +730,11 @@ void Parser::parse_interface(Interface& interface) interface.has_unscopable_member = true; } + if (lexer.next_is("async")) { + parse_async_iterable(interface); + continue; + } + if (lexer.next_is("constructor")) { parse_constructor(extended_attributes, interface); continue; diff --git a/Libraries/LibIDL/IDLParser.h b/Libraries/LibIDL/IDLParser.h index 7c9ae148c45..9537162726b 100644 --- a/Libraries/LibIDL/IDLParser.h +++ b/Libraries/LibIDL/IDLParser.h @@ -58,6 +58,7 @@ private: void parse_deleter(HashMap& extended_attributes, Interface&); void parse_stringifier(HashMap& extended_attributes, Interface&); void parse_iterable(Interface&); + void parse_async_iterable(Interface&); void parse_setlike(Interface&, bool is_readonly); Function parse_function(HashMap& extended_attributes, Interface&, IsStatic is_static = IsStatic::No, IsSpecialOperation is_special_operation = IsSpecialOperation::No); Vector parse_parameters(); diff --git a/Libraries/LibIDL/Types.h b/Libraries/LibIDL/Types.h index c8811026117..4f37908b28c 100644 --- a/Libraries/LibIDL/Types.h +++ b/Libraries/LibIDL/Types.h @@ -285,6 +285,10 @@ public: Optional> value_iterator_type; Optional, NonnullRefPtr>> pair_iterator_types; + + Optional> async_value_iterator_type; + Vector async_value_iterator_parameters; + Optional> set_entry_type; bool is_set_readonly { false }; diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 94af3e8fd4a..eba2db98d25 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -895,6 +895,7 @@ set(SOURCES WebGL/WebGLUniformLocation.cpp WebGL/WebGLVertexArrayObject.cpp WebIDL/AbstractOperations.cpp + WebIDL/AsyncIterator.cpp WebIDL/Buffers.cpp WebIDL/CallbackType.cpp WebIDL/DOMException.cpp diff --git a/Libraries/LibWeb/WebIDL/AsyncIterator.cpp b/Libraries/LibWeb/WebIDL/AsyncIterator.cpp new file mode 100644 index 00000000000..dc70ccf0df9 --- /dev/null +++ b/Libraries/LibWeb/WebIDL/AsyncIterator.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Web::WebIDL { + +GC_DEFINE_ALLOCATOR(AsyncIterator); + +AsyncIterator::AsyncIterator(JS::Realm& realm, JS::Object::PropertyKind iteration_kind) + : PlatformObject(realm) + , m_kind(iteration_kind) +{ +} + +AsyncIterator::~AsyncIterator() = default; + +void AsyncIterator::visit_edges(JS::Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_ongoing_promise); +} + +// https://webidl.spec.whatwg.org/#ref-for-dfn-asynchronous-iterator-prototype-object%E2%91%A2 +JS::ThrowCompletionOr> AsyncIterator::iterator_next_impl() +{ + auto& realm = this->realm(); + auto& vm = this->vm(); + + // 8. Let nextSteps be the following steps: + auto next_steps = [this](JS::VM& vm) { + auto& realm = this->realm(); + + // 1. Let nextPromiseCapability be ! NewPromiseCapability(%Promise%). + auto next_promise_capability = WebIDL::create_promise(realm); + + // 2. If object’s is finished is true, then: + if (m_is_finished) { + // 1. Let result be CreateIteratorResultObject(undefined, true). + auto result = JS::create_iterator_result_object(vm, JS::js_undefined(), true); + + // 2. Perform ! Call(nextPromiseCapability.[[Resolve]], undefined, « result »). + MUST(JS::call(vm, *next_promise_capability->resolve(), JS::js_undefined(), result)); + + // 3. Return nextPromiseCapability.[[Promise]]. + return next_promise_capability->promise(); + } + + // 3. Let kind be object’s kind. + + // 4. Let nextPromise be the result of getting the next iteration result with object’s target and object. + auto& next_promise = as(*next_iteration_result(realm)->promise()); + + // 5. Let fulfillSteps be the following steps, given next: + auto fulfill_steps = [this](JS::VM& vm) { + auto next = vm.argument(0); + + // 1. Set object’s ongoing promise to null. + m_ongoing_promise = nullptr; + + // 2. If next is end of iteration, then: + if (next.is_special_empty_value()) { + // 1. Set object’s is finished to true. + m_is_finished = true; + + // 2. Return CreateIteratorResultObject(undefined, true). + return JS::create_iterator_result_object(vm, JS::js_undefined(), true); + } + // FIXME: 2. Otherwise, if interface has a pair asynchronously iterable declaration: + else if (false) { + // 1. Assert: next is a value pair. + // 2. Return the iterator result for next and kind. + } + // Otherwise: + else { + // 1. Assert: interface has a value asynchronously iterable declaration. + // 2. Assert: next is a value of the type that appears in the declaration. + + // 3. Let value be next, converted to a JavaScript value. + // 4. Return CreateIteratorResultObject(value, false). + return JS::create_iterator_result_object(vm, next, false); + } + }; + + // 6. Let onFulfilled be CreateBuiltinFunction(fulfillSteps, « »). + auto on_fulfilled = JS::NativeFunction::create(realm, move(fulfill_steps), 0); + + // 7. Let rejectSteps be the following steps, given reason: + auto reject_steps = [this](JS::VM& vm) { + auto reason = vm.argument(0); + + // 1. Set object’s ongoing promise to null. + m_ongoing_promise = nullptr; + + // 2. Set object’s is finished to true. + m_is_finished = true; + + // 3. Throw reason. + return JS::throw_completion(reason); + }; + + // 8. Let onRejected be CreateBuiltinFunction(rejectSteps, « »). + auto on_rejected = JS::NativeFunction::create(realm, move(reject_steps), 0); + + // 9. Perform PerformPromiseThen(nextPromise, onFulfilled, onRejected, nextPromiseCapability). + next_promise.perform_then(on_fulfilled, on_rejected, next_promise_capability); + + // 10. Return nextPromiseCapability.[[Promise]]. + return next_promise_capability->promise(); + }; + + // 9. Let ongoingPromise be object’s ongoing promise. + // 10. If ongoingPromise is not null, then: + if (m_ongoing_promise) { + // 1. Let afterOngoingPromiseCapability be ! NewPromiseCapability(%Promise%). + auto after_ongoing_promise_capability = WebIDL::create_promise(realm); + + // 2. Let onSettled be CreateBuiltinFunction(nextSteps, « »). + auto on_settled = JS::NativeFunction::create(realm, move(next_steps), 0); + + // 3. Perform PerformPromiseThen(ongoingPromise, onSettled, onSettled, afterOngoingPromiseCapability). + m_ongoing_promise->perform_then(on_settled, on_settled, after_ongoing_promise_capability); + + // 4. Set object’s ongoing promise to afterOngoingPromiseCapability.[[Promise]]. + m_ongoing_promise = as(*after_ongoing_promise_capability->promise()); + } + // 11. Otherwise: + else { + // 1. Set object’s ongoing promise to the result of running nextSteps. + m_ongoing_promise = as(*next_steps(vm)); + } + + // 12. Return object’s ongoing promise. + return m_ongoing_promise; +} + +// https://webidl.spec.whatwg.org/#ref-for-asynchronous-iterator-return +JS::ThrowCompletionOr> AsyncIterator::iterator_return_impl(GC::Ref return_promise_capability, JS::Value value) +{ + auto& realm = this->realm(); + auto& vm = this->vm(); + + // 8. Let returnSteps be the following steps: + auto return_steps = [this, value](JS::VM& vm) { + auto& realm = this->realm(); + + // 1. Let returnPromiseCapability be ! NewPromiseCapability(%Promise%). + auto return_promise_capability = WebIDL::create_promise(realm); + + // 2. If object’s is finished is true, then: + if (m_is_finished) { + // 1. Let result be CreateIteratorResultObject(value, true). + auto result = JS::create_iterator_result_object(vm, value, true); + + // 2. Perform ! Call(returnPromiseCapability.[[Resolve]], undefined, « result »). + MUST(JS::call(vm, *return_promise_capability->resolve(), JS::js_undefined(), result)); + + // 3. Return returnPromiseCapability.[[Promise]]. + return return_promise_capability->promise(); + } + + // 3. Set object’s is finished to true. + m_is_finished = true; + + // 4. Return the result of running the asynchronous iterator return algorithm for interface, given object’s target, object, and value. + return iterator_return(realm, value)->promise(); + }; + + // 9. Let ongoingPromise be object’s ongoing promise. + // 10. If ongoingPromise is not null, then: + if (m_ongoing_promise) { + // 1. Let afterOngoingPromiseCapability be ! NewPromiseCapability(%Promise%). + auto after_ongoing_promise_capability = WebIDL::create_promise(realm); + + // 2. Let onSettled be CreateBuiltinFunction(returnSteps, « »). + auto on_settled = JS::NativeFunction::create(realm, move(return_steps), 0); + + // 3. Perform PerformPromiseThen(ongoingPromise, onSettled, onSettled, afterOngoingPromiseCapability). + m_ongoing_promise->perform_then(on_settled, on_settled, after_ongoing_promise_capability); + + // 4. Set object’s ongoing promise to afterOngoingPromiseCapability.[[Promise]]. + m_ongoing_promise = as(*after_ongoing_promise_capability->promise()); + } + // 11. Otherwise: + else { + // 1. Set object’s ongoing promise to the result of running returnSteps. + m_ongoing_promise = as(*return_steps(vm)); + } + + // 12. Let fulfillSteps be the following steps: + auto fulfill_steps = [value](JS::VM& vm) { + // 1. Return CreateIteratorResultObject(value, true). + return JS::create_iterator_result_object(vm, value, true); + }; + + // 13. Let onFulfilled be CreateBuiltinFunction(fulfillSteps, « »). + auto on_fulfilled = JS::NativeFunction::create(realm, move(fulfill_steps), 0); + + // 14. Perform PerformPromiseThen(object’s ongoing promise, onFulfilled, undefined, returnPromiseCapability). + m_ongoing_promise->perform_then(on_fulfilled, JS::js_undefined(), return_promise_capability); + + // 15. Return returnPromiseCapability.[[Promise]]. + return return_promise_capability->promise(); +} + +GC::Ref AsyncIterator::iterator_return(JS::Realm&, JS::Value) +{ + // If this is reached, a `return` data property was generated for your async iterator, but you neglected to override this method. + VERIFY_NOT_REACHED(); +} + +} diff --git a/Libraries/LibWeb/WebIDL/AsyncIterator.h b/Libraries/LibWeb/WebIDL/AsyncIterator.h new file mode 100644 index 00000000000..ff966bdf0c1 --- /dev/null +++ b/Libraries/LibWeb/WebIDL/AsyncIterator.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Web::WebIDL { + +class AsyncIterator : public Bindings::PlatformObject { + WEB_PLATFORM_OBJECT(AsyncIterator, Bindings::PlatformObject); + GC_DECLARE_ALLOCATOR(AsyncIterator); + +public: + virtual ~AsyncIterator() override; + + // https://webidl.spec.whatwg.org/#ref-for-dfn-asynchronous-iterator-prototype-object%E2%91%A2 + template + static JS::ThrowCompletionOr> next(JS::Realm& realm, StringView interface_name) + { + auto validation_result = validate_this(realm, interface_name); + + return validation_result.visit( + [](GC::Ref iterator) { + return iterator->iterator_next_impl(); + }, + [](JS::ThrowCompletionOr> result) { + return result; + }); + } + + // https://webidl.spec.whatwg.org/#ref-for-asynchronous-iterator-return + template + static JS::ThrowCompletionOr> return_(JS::Realm& realm, StringView interface_name, JS::Value value) + { + auto return_promise_capability = WebIDL::create_promise(realm); + auto validation_result = validate_this(realm, interface_name, return_promise_capability); + + return validation_result.visit( + [&](GC::Ref iterator) { + return iterator->iterator_return_impl(return_promise_capability, value); + }, + [](JS::ThrowCompletionOr> result) { + return result; + }); + } + +protected: + AsyncIterator(JS::Realm&, JS::Object::PropertyKind); + + virtual void visit_edges(Cell::Visitor&) override; + + virtual GC::Ref next_iteration_result(JS::Realm&) = 0; + virtual GC::Ref iterator_return(JS::Realm&, JS::Value); + +private: + template + static Variant, GC::Ref> validate_this(JS::Realm& realm, StringView interface_name, GC::Ptr this_validation_promise_capability = {}) + { + // NOTE: This defines the steps to validate `this` that are common between "next" and "return". + auto& vm = realm.vm(); + + // 1. Let interface be the interface for which the asynchronous iterator prototype object exists. + // 2. Let thisValidationPromiseCapability be ! NewPromiseCapability(%Promise%). + if (!this_validation_promise_capability) + this_validation_promise_capability = WebIDL::create_promise(realm); + + // 3. Let thisValue be the this value. + auto this_value = vm.this_value(); + + // 4. Let object be Completion(ToObject(thisValue)). + // 5. IfAbruptRejectPromise(object, thisValidationPromiseCapability). + auto object = TRY_OR_REJECT(vm, this_validation_promise_capability, this_value.to_object(vm)); + + // FIXME: 6. If object is a platform object, then perform a security check, passing: + // * the platform object object, + // * the identifier "next", and + // * the type "method". + // + // If this threw an exception e, then: + // Perform ! Call(thisValidationPromiseCapability.[[Reject]], undefined, « e »). + // Return thisValidationPromiseCapability.[[Promise]]. + + // 7. If object is not a default asynchronous iterator object for interface, then: + auto* iterator = as_if(*object); + + if (!iterator) { + // 1. Let error be a new TypeError. + auto error = vm.throw_completion(JS::ErrorType::NotAnObjectOfType, interface_name); + + // 2. Perform ! Call(thisValidationPromiseCapability.[[Reject]], undefined, « error »). + // 3. Return thisValidationPromiseCapability.[[Promise]]. + TRY_OR_MUST_REJECT(vm, this_validation_promise_capability, error); + VERIFY_NOT_REACHED(); + } + + return GC::Ref { *iterator }; + } + + JS::ThrowCompletionOr> iterator_next_impl(); + JS::ThrowCompletionOr> iterator_return_impl(GC::Ref return_promise_capability, JS::Value); + + JS::Object::PropertyKind m_kind { JS::Object::PropertyKind::Value }; + GC::Ptr m_ongoing_promise; + bool m_is_finished { false }; +}; + +} diff --git a/Meta/CMake/libweb_generators.cmake b/Meta/CMake/libweb_generators.cmake index a5888db22e6..1e364ccde0d 100644 --- a/Meta/CMake/libweb_generators.cmake +++ b/Meta/CMake/libweb_generators.cmake @@ -179,7 +179,7 @@ function (generate_js_bindings target) set(generated_idl_targets ${LIBWEB_ALL_GENERATED_IDL}) list(TRANSFORM generated_idl_targets PREPEND "generate_") function(libweb_js_bindings class) - cmake_parse_arguments(PARSE_ARGV 1 LIBWEB_BINDINGS "NAMESPACE;ITERABLE;GLOBAL" "" "") + cmake_parse_arguments(PARSE_ARGV 1 LIBWEB_BINDINGS "NAMESPACE;ITERABLE;ASYNC_ITERABLE;GLOBAL" "" "") get_filename_component(basename "${class}" NAME) if (LIBWEB_BINDINGS_NAMESPACE) @@ -203,6 +203,13 @@ function (generate_js_bindings target) ) endif() + if(LIBWEB_BINDINGS_ASYNC_ITERABLE) + list(APPEND BINDINGS_SOURCES + "Bindings/${basename}AsyncIteratorPrototype.h" + "Bindings/${basename}AsyncIteratorPrototype.cpp" + ) + endif() + if(LIBWEB_BINDINGS_GLOBAL) list(APPEND BINDINGS_SOURCES "Bindings/${basename}GlobalMixin.h" diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index 8e2b80f9cdb..b6b24f5bb56 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -346,7 +346,7 @@ static void generate_include_for(auto& generator, auto& path) )~~~"); } -static void emit_includes_for_all_imports(auto& interface, auto& generator, bool is_iterator = false) +static void emit_includes_for_all_imports(auto& interface, auto& generator, bool is_iterator = false, bool is_async_iterator = false) { Queue const*> interfaces; HashTable paths_imported; @@ -374,6 +374,10 @@ static void emit_includes_for_all_imports(auto& interface, auto& generator, bool auto iterator_path = ByteString::formatted("{}Iterator", interface.fully_qualified_name.replace("::"sv, "/"sv, ReplaceMode::All)); generate_include_for_iterator(generator, iterator_path); } + if (is_async_iterator) { + auto iterator_path = ByteString::formatted("{}AsyncIterator", interface.fully_qualified_name.replace("::"sv, "/"sv, ReplaceMode::All)); + generate_include_for_iterator(generator, iterator_path); + } } template @@ -2963,6 +2967,13 @@ static void generate_prototype_or_global_mixin_declarations(IDL::Interface const )~~~"); } + if (interface.async_value_iterator_type.has_value()) { + auto iterator_generator = generator.fork(); + iterator_generator.append(R"~~~( + JS_DECLARE_NATIVE_FUNCTION(values); + )~~~"); + } + if (interface.set_entry_type.has_value()) { auto setlike_generator = generator.fork(); @@ -3591,6 +3602,16 @@ void @class_name@::initialize(JS::Realm& realm) )~~~"); } + // https://webidl.spec.whatwg.org/#define-the-asynchronous-iteration-methods + if (interface.async_value_iterator_type.has_value() && generate_unforgeables == GenerateUnforgeables::No) { + auto iterator_generator = generator.fork(); + iterator_generator.append(R"~~~( + @define_native_function@(realm, vm.names.values, values, 0, default_attributes); + + @define_direct_property@(vm.well_known_symbol_async_iterator(), get_without_side_effects(vm.names.values), JS::Attribute::Configurable | JS::Attribute::Writable); +)~~~"); + } + // https://webidl.spec.whatwg.org/#js-setlike if (interface.set_entry_type.has_value() && generate_unforgeables == GenerateUnforgeables::No) { @@ -4403,6 +4424,34 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::values) )~~~"); } + // https://webidl.spec.whatwg.org/#js-asynchronous-iterable + if (interface.async_value_iterator_type.has_value()) { + auto iterator_generator = generator.fork(); + iterator_generator.set("iterator_name"sv, MUST(String::formatted("{}AsyncIterator", interface.name))); + iterator_generator.append(R"~~~( +JS_DEFINE_NATIVE_FUNCTION(@class_name@::values) +{ + WebIDL::log_trace(vm, "@class_name@::values"); + auto& realm = *vm.current_realm(); + auto* impl = TRY(impl_from(vm)); +)~~~"); + + StringBuilder arguments_builder; + generate_arguments(generator, interface.async_value_iterator_parameters, arguments_builder, interface); + + iterator_generator.append(R"~~~( + return TRY(throw_dom_exception_if_needed(vm, [&] { return @iterator_name@::create(realm, Object::PropertyKind::Value, *impl)~~~"); + + if (!arguments_builder.is_empty()) { + iterator_generator.set("iterator_arguments"sv, MUST(arguments_builder.to_string())); + iterator_generator.append(", @iterator_arguments@"); + } + + iterator_generator.append(R"~~~(); })); +} +)~~~"); + } + if (interface.set_entry_type.has_value()) { auto setlike_generator = generator.fork(); setlike_generator.set("value_type", interface.set_entry_type.value()->name()); @@ -4687,7 +4736,7 @@ void generate_namespace_implementation(IDL::Interface const& interface, StringBu )~~~"); - emit_includes_for_all_imports(interface, generator, interface.pair_iterator_types.has_value()); + emit_includes_for_all_imports(interface, generator, interface.pair_iterator_types.has_value(), interface.async_value_iterator_type.has_value()); generate_using_namespace_definitions(generator); @@ -4919,7 +4968,7 @@ void generate_constructor_implementation(IDL::Interface const& interface, String } } - emit_includes_for_all_imports(interface, generator, interface.pair_iterator_types.has_value()); + emit_includes_for_all_imports(interface, generator, interface.pair_iterator_types.has_value(), interface.async_value_iterator_type.has_value()); generate_using_namespace_definitions(generator); @@ -5176,7 +5225,7 @@ void generate_prototype_implementation(IDL::Interface const& interface, StringBu )~~~"); } - emit_includes_for_all_imports(interface, generator, interface.pair_iterator_types.has_value()); + emit_includes_for_all_imports(interface, generator, interface.pair_iterator_types.has_value(), interface.async_value_iterator_type.has_value()); generate_using_namespace_definitions(generator); @@ -5349,6 +5398,140 @@ JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::next) )~~~"); } +void generate_async_iterator_prototype_header(IDL::Interface const& interface, StringBuilder& builder) +{ + VERIFY(interface.async_value_iterator_type.has_value()); + SourceGenerator generator { builder }; + + generator.set("prototype_class", ByteString::formatted("{}AsyncIteratorPrototype", interface.name)); + + generator.append(R"~~~( +#pragma once + +#include + +namespace Web::Bindings { + +class @prototype_class@ : public JS::Object { + JS_OBJECT(@prototype_class@, JS::Object); + GC_DECLARE_ALLOCATOR(@prototype_class@); + +public: + explicit @prototype_class@(JS::Realm&); + virtual void initialize(JS::Realm&) override; + virtual ~@prototype_class@() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(next); + )~~~"); + + if (interface.extended_attributes.contains("DefinesAsyncIteratorReturn")) { + generator.append(R"~~~( + JS_DECLARE_NATIVE_FUNCTION(return_); +)~~~"); + } + + generator.append(R"~~~( +}; + +} // namespace Web::Bindings + )~~~"); +} + +void generate_async_iterator_prototype_implementation(IDL::Interface const& interface, StringBuilder& builder) +{ + VERIFY(interface.async_value_iterator_type.has_value()); + SourceGenerator generator { builder }; + + generator.set("name", ByteString::formatted("{}AsyncIterator", interface.name)); + generator.set("parent_name", interface.parent_name); + generator.set("prototype_class", ByteString::formatted("{}AsyncIteratorPrototype", interface.name)); + generator.set("to_string_tag", ByteString::formatted("{} AsyncIterator", interface.name)); + generator.set("prototype_base_class", interface.prototype_base_class); + generator.set("fully_qualified_name", ByteString::formatted("{}AsyncIterator", interface.fully_qualified_name)); + generator.set("possible_include_path", ByteString::formatted("{}AsyncIterator", interface.name.replace("::"sv, "/"sv, ReplaceMode::All))); + + generator.append(R"~~~( +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +)~~~"); + + emit_includes_for_all_imports(interface, generator, false, true); + + generate_using_namespace_definitions(generator); + + generator.append(R"~~~( +namespace Web::Bindings { + +GC_DEFINE_ALLOCATOR(@prototype_class@); + +@prototype_class@::@prototype_class@(JS::Realm& realm) + : Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().async_iterator_prototype()) +{ +} + +@prototype_class@::~@prototype_class@() +{ +} + +void @prototype_class@::initialize(JS::Realm& realm) +{ + auto& vm = this->vm(); + Base::initialize(realm); + define_direct_property(vm.well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm, "@to_string_tag@"_string), JS::Attribute::Configurable); + + define_native_function(realm, vm.names.next, next, 0, JS::default_attributes);)~~~"); + + if (interface.extended_attributes.contains("DefinesAsyncIteratorReturn")) { + generator.append(R"~~~( + define_native_function(realm, vm.names.return_, return_, 1, JS::default_attributes);)~~~"); + } + + generator.append(R"~~~( +} + +JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::next) +{ + WebIDL::log_trace(vm, "@prototype_class@::next"); + auto& realm = *vm.current_realm(); + + return TRY(throw_dom_exception_if_needed(vm, [&] { + return WebIDL::AsyncIterator::next<@fully_qualified_name@>(realm, "@name@"sv); + })); +} +)~~~"); + + if (interface.extended_attributes.contains("DefinesAsyncIteratorReturn")) { + generator.append(R"~~~( +JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::return_) +{ + WebIDL::log_trace(vm, "@prototype_class@::return"); + auto& realm = *vm.current_realm(); + + auto value = vm.argument(0); + + return TRY(throw_dom_exception_if_needed(vm, [&] { + return WebIDL::AsyncIterator::return_<@fully_qualified_name@>(realm, "@name@"sv, value); + })); +} +)~~~"); + } + + generator.append(R"~~~( +} // namespace Web::Bindings +)~~~"); +} + void generate_global_mixin_header(IDL::Interface const& interface, StringBuilder& builder) { SourceGenerator generator { builder }; @@ -5418,7 +5601,7 @@ void generate_global_mixin_implementation(IDL::Interface const& interface, Strin )~~~"); - emit_includes_for_all_imports(interface, generator, interface.pair_iterator_types.has_value()); + emit_includes_for_all_imports(interface, generator, interface.pair_iterator_types.has_value(), interface.async_value_iterator_type.has_value()); generate_using_namespace_definitions(generator); diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.h b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.h index d82a451cb49..198c404f94c 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.h +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.h @@ -25,6 +25,8 @@ void generate_prototype_header(IDL::Interface const&, StringBuilder&); void generate_prototype_implementation(IDL::Interface const&, StringBuilder&); void generate_iterator_prototype_header(IDL::Interface const&, StringBuilder&); void generate_iterator_prototype_implementation(IDL::Interface const&, StringBuilder&); +void generate_async_iterator_prototype_header(IDL::Interface const&, StringBuilder&); +void generate_async_iterator_prototype_implementation(IDL::Interface const&, StringBuilder&); void generate_global_mixin_header(IDL::Interface const&, StringBuilder&); void generate_global_mixin_implementation(IDL::Interface const&, StringBuilder&); diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/main.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/main.cpp index e867d89caf7..804e1c43f00 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/main.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/main.cpp @@ -139,6 +139,8 @@ ErrorOr serenity_main(Main::Arguments arguments) String prototype_implementation; String iterator_prototype_header; String iterator_prototype_implementation; + String async_iterator_prototype_header; + String async_iterator_prototype_implementation; String global_mixin_header; String global_mixin_implementation; @@ -170,6 +172,14 @@ ErrorOr serenity_main(Main::Arguments arguments) TRY(write_if_changed(&IDL::generate_iterator_prototype_implementation, iterator_prototype_implementation)); } + if (interface.async_value_iterator_type.has_value()) { + async_iterator_prototype_header = TRY(String::formatted("{}AsyncIteratorPrototype.h", path_prefix)); + async_iterator_prototype_implementation = TRY(String::formatted("{}AsyncIteratorPrototype.cpp", path_prefix)); + + TRY(write_if_changed(&IDL::generate_async_iterator_prototype_header, async_iterator_prototype_header)); + TRY(write_if_changed(&IDL::generate_async_iterator_prototype_implementation, async_iterator_prototype_implementation)); + } + if (interface.extended_attributes.contains("Global")) { global_mixin_header = TRY(String::formatted("{}GlobalMixin.h", path_prefix)); global_mixin_implementation = TRY(String::formatted("{}GlobalMixin.cpp", path_prefix)); @@ -182,7 +192,7 @@ ErrorOr serenity_main(Main::Arguments arguments) auto depfile = TRY(Core::File::open_file_or_standard_stream(depfile_path, Core::File::OpenMode::Write)); StringBuilder depfile_builder; - for (StringView s : { constructor_header, constructor_implementation, prototype_header, prototype_implementation, namespace_header, namespace_implementation, iterator_prototype_header, iterator_prototype_implementation, global_mixin_header, global_mixin_implementation }) { + for (StringView s : { constructor_header, constructor_implementation, prototype_header, prototype_implementation, namespace_header, namespace_implementation, iterator_prototype_header, iterator_prototype_implementation, async_iterator_prototype_header, async_iterator_prototype_implementation, global_mixin_header, global_mixin_implementation }) { if (s.is_empty()) continue;