mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-22 12:35:14 +00:00
LibIDL+LibWeb: Begin support for async iterator
in IDL
This adds support for async iterators of the form: async iterable<value_type>; async iterable<value_type>(/* arguments... */); It does not yet support the value pairs of the form: async iterable<key_type, value_type>; async iterable<key_type, value_type>(/* arguments... */); Async iterators have an optional `return` data property. There's not a particularly good way to know what interfaces implement this property. So this adds a new extended attribute, DefinesAsyncIteratorReturn, which interfaces can use to declare their support.
This commit is contained in:
parent
7f4fce1d60
commit
ec805e6fd4
10 changed files with 593 additions and 7 deletions
|
@ -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;
|
||||
|
|
|
@ -58,6 +58,7 @@ private:
|
|||
void parse_deleter(HashMap<ByteString, ByteString>& extended_attributes, Interface&);
|
||||
void parse_stringifier(HashMap<ByteString, ByteString>& extended_attributes, Interface&);
|
||||
void parse_iterable(Interface&);
|
||||
void parse_async_iterable(Interface&);
|
||||
void parse_setlike(Interface&, bool is_readonly);
|
||||
Function parse_function(HashMap<ByteString, ByteString>& extended_attributes, Interface&, IsStatic is_static = IsStatic::No, IsSpecialOperation is_special_operation = IsSpecialOperation::No);
|
||||
Vector<Parameter> parse_parameters();
|
||||
|
|
|
@ -285,6 +285,10 @@ public:
|
|||
|
||||
Optional<NonnullRefPtr<Type const>> value_iterator_type;
|
||||
Optional<Tuple<NonnullRefPtr<Type const>, NonnullRefPtr<Type const>>> pair_iterator_types;
|
||||
|
||||
Optional<NonnullRefPtr<Type const>> async_value_iterator_type;
|
||||
Vector<Parameter> async_value_iterator_parameters;
|
||||
|
||||
Optional<NonnullRefPtr<Type const>> set_entry_type;
|
||||
bool is_set_readonly { false };
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
217
Libraries/LibWeb/WebIDL/AsyncIterator.cpp
Normal file
217
Libraries/LibWeb/WebIDL/AsyncIterator.cpp
Normal file
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/Iterator.h>
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
#include <LibWeb/WebIDL/AsyncIterator.h>
|
||||
|
||||
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<GC::Ptr<JS::Object>> 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<JS::Promise>(*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<JS::Promise>(*after_ongoing_promise_capability->promise());
|
||||
}
|
||||
// 11. Otherwise:
|
||||
else {
|
||||
// 1. Set object’s ongoing promise to the result of running nextSteps.
|
||||
m_ongoing_promise = as<JS::Promise>(*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<GC::Ptr<JS::Object>> AsyncIterator::iterator_return_impl(GC::Ref<WebIDL::Promise> 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<JS::Promise>(*after_ongoing_promise_capability->promise());
|
||||
}
|
||||
// 11. Otherwise:
|
||||
else {
|
||||
// 1. Set object’s ongoing promise to the result of running returnSteps.
|
||||
m_ongoing_promise = as<JS::Promise>(*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<WebIDL::Promise> 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();
|
||||
}
|
||||
|
||||
}
|
116
Libraries/LibWeb/WebIDL/AsyncIterator.h
Normal file
116
Libraries/LibWeb/WebIDL/AsyncIterator.h
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/StringView.h>
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
#include <LibJS/Runtime/PromiseCapability.h>
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibWeb/WebIDL/Promise.h>
|
||||
|
||||
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<typename AsyncIteratorInterface>
|
||||
static JS::ThrowCompletionOr<GC::Ptr<JS::Object>> next(JS::Realm& realm, StringView interface_name)
|
||||
{
|
||||
auto validation_result = validate_this<AsyncIteratorInterface>(realm, interface_name);
|
||||
|
||||
return validation_result.visit(
|
||||
[](GC::Ref<AsyncIteratorInterface> iterator) {
|
||||
return iterator->iterator_next_impl();
|
||||
},
|
||||
[](JS::ThrowCompletionOr<GC::Ptr<JS::Object>> result) {
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#ref-for-asynchronous-iterator-return
|
||||
template<typename AsyncIteratorInterface>
|
||||
static JS::ThrowCompletionOr<GC::Ptr<JS::Object>> return_(JS::Realm& realm, StringView interface_name, JS::Value value)
|
||||
{
|
||||
auto return_promise_capability = WebIDL::create_promise(realm);
|
||||
auto validation_result = validate_this<AsyncIteratorInterface>(realm, interface_name, return_promise_capability);
|
||||
|
||||
return validation_result.visit(
|
||||
[&](GC::Ref<AsyncIteratorInterface> iterator) {
|
||||
return iterator->iterator_return_impl(return_promise_capability, value);
|
||||
},
|
||||
[](JS::ThrowCompletionOr<GC::Ptr<JS::Object>> result) {
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
AsyncIterator(JS::Realm&, JS::Object::PropertyKind);
|
||||
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
virtual GC::Ref<WebIDL::Promise> next_iteration_result(JS::Realm&) = 0;
|
||||
virtual GC::Ref<WebIDL::Promise> iterator_return(JS::Realm&, JS::Value);
|
||||
|
||||
private:
|
||||
template<typename AsyncIteratorInterface>
|
||||
static Variant<JS::Completion, GC::Ref<JS::Object>, GC::Ref<AsyncIteratorInterface>> validate_this(JS::Realm& realm, StringView interface_name, GC::Ptr<WebIDL::Promise> 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<AsyncIteratorInterface>(*object);
|
||||
|
||||
if (!iterator) {
|
||||
// 1. Let error be a new TypeError.
|
||||
auto error = vm.throw_completion<JS::TypeError>(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<GC::Ptr<JS::Object>> iterator_next_impl();
|
||||
JS::ThrowCompletionOr<GC::Ptr<JS::Object>> iterator_return_impl(GC::Ref<WebIDL::Promise> return_promise_capability, JS::Value);
|
||||
|
||||
JS::Object::PropertyKind m_kind { JS::Object::PropertyKind::Value };
|
||||
GC::Ptr<JS::Promise> m_ongoing_promise;
|
||||
bool m_is_finished { false };
|
||||
};
|
||||
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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<RemoveCVReference<decltype(interface)> const*> interfaces;
|
||||
HashTable<ByteString> 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<typename ParameterType>
|
||||
|
@ -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 <LibJS/Runtime/Object.h>
|
||||
|
||||
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 <AK/Function.h>
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
#include <LibJS/Runtime/Error.h>
|
||||
#include <LibJS/Runtime/FunctionObject.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/TypedArray.h>
|
||||
#include <LibWeb/Bindings/@prototype_class@.h>
|
||||
#include <LibWeb/Bindings/ExceptionOrUtils.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/WebIDL/AsyncIterator.h>
|
||||
#include <LibWeb/WebIDL/Tracing.h>
|
||||
)~~~");
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -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&);
|
||||
|
||||
|
|
|
@ -139,6 +139,8 @@ ErrorOr<int> 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<int> 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<int> 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;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue