ladybird/Libraries/LibWeb/WebIDL/AsyncIterator.cpp
Timothy Flynn c0ead1b01a 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.
2025-04-14 17:43:11 -04:00

217 lines
8.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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 objects 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 objects kind.
// 4. Let nextPromise be the result of getting the next iteration result with objects 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 objects ongoing promise to null.
m_ongoing_promise = nullptr;
// 2. If next is end of iteration, then:
if (next.is_special_empty_value()) {
// 1. Set objects 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 objects ongoing promise to null.
m_ongoing_promise = nullptr;
// 2. Set objects 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 objects 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 objects ongoing promise to afterOngoingPromiseCapability.[[Promise]].
m_ongoing_promise = as<JS::Promise>(*after_ongoing_promise_capability->promise());
}
// 11. Otherwise:
else {
// 1. Set objects ongoing promise to the result of running nextSteps.
m_ongoing_promise = as<JS::Promise>(*next_steps(vm));
}
// 12. Return objects 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 objects 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 objects is finished to true.
m_is_finished = true;
// 4. Return the result of running the asynchronous iterator return algorithm for interface, given objects target, object, and value.
return iterator_return(realm, value)->promise();
};
// 9. Let ongoingPromise be objects 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 objects ongoing promise to afterOngoingPromiseCapability.[[Promise]].
m_ongoing_promise = as<JS::Promise>(*after_ongoing_promise_capability->promise());
}
// 11. Otherwise:
else {
// 1. Set objects 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(objects 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();
}
}