mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-06-14 22:31:56 +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
398f1ce2a0
commit
c0ead1b01a
Notes:
github-actions[bot]
2025-04-14 21:44:24 +00:00
Author: https://github.com/trflynn89
Commit: c0ead1b01a
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4338
Reviewed-by: https://github.com/kennethmyhra ✅
10 changed files with 593 additions and 7 deletions
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();
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue