Bug 1734244 - Implement async iteration of ReadableStream. r=mgaudet,peterv

Differential Revision: https://phabricator.services.mozilla.com/D153312
This commit is contained in:
Tom Schuster 2022-11-30 22:26:28 +00:00
parent cd7fc78979
commit 37f5b2a2a7
6 changed files with 204 additions and 495 deletions

View File

@ -833,6 +833,174 @@ void ReadableStream::Tee(JSContext* aCx,
ReadableStreamTee(aCx, this, false, aResult, aRv);
}
void ReadableStream::IteratorData::Traverse(
nsCycleCollectionTraversalCallback& cb) {
ReadableStream::IteratorData* tmp = this;
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReader);
}
void ReadableStream::IteratorData::Unlink() {
ReadableStream::IteratorData* tmp = this;
NS_IMPL_CYCLE_COLLECTION_UNLINK(mReader);
}
// https://streams.spec.whatwg.org/#rs-get-iterator
void ReadableStream::InitAsyncIteratorData(
IteratorData& aData, Iterator::IteratorType aType,
const ReadableStreamIteratorOptions& aOptions, ErrorResult& aRv) {
// Step 1. Let reader be ? AcquireReadableStreamDefaultReader(stream).
RefPtr<ReadableStreamDefaultReader> reader =
AcquireReadableStreamDefaultReader(this, aRv);
if (aRv.Failed()) {
return;
}
// Step 2. Set iterators reader to reader.
aData.mReader = reader;
// Step 3. Let preventCancel be args[0]["preventCancel"].
// Step 4. Set iterators prevent cancel to preventCancel.
aData.mPreventCancel = aOptions.mPreventCancel;
}
// https://streams.spec.whatwg.org/#rs-asynciterator-prototype-next
// Step 4.
struct IteratorReadRequest : public ReadRequest {
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IteratorReadRequest, ReadRequest)
RefPtr<Promise> mPromise;
RefPtr<ReadableStreamDefaultReader> mReader;
explicit IteratorReadRequest(Promise* aPromise,
ReadableStreamDefaultReader* aReader)
: mPromise(aPromise), mReader(aReader) {}
// chunk steps, given chunk
void ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
ErrorResult& aRv) override {
// Step 1. Resolve promise with chunk.
mPromise->MaybeResolve(aChunk);
}
// close steps
void CloseSteps(JSContext* aCx, ErrorResult& aRv) override {
// Step 1. Perform ! ReadableStreamDefaultReaderRelease(reader).
ReadableStreamDefaultReaderRelease(aCx, mReader, aRv);
if (aRv.Failed()) {
mPromise->MaybeRejectWithUndefined();
return;
}
// Step 2. Resolve promise with end of iteration.
iterator_utils::ResolvePromiseForFinished(mPromise);
}
// error steps, given e
void ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> aError,
ErrorResult& aRv) override {
// Step 1. Perform ! ReadableStreamDefaultReaderRelease(reader).
ReadableStreamDefaultReaderRelease(aCx, mReader, aRv);
if (aRv.Failed()) {
mPromise->MaybeRejectWithUndefined();
return;
}
// Step 2. Reject promise with e.
mPromise->MaybeReject(aError);
}
protected:
virtual ~IteratorReadRequest() = default;
};
NS_IMPL_CYCLE_COLLECTION_INHERITED(IteratorReadRequest, ReadRequest, mPromise,
mReader)
NS_IMPL_ADDREF_INHERITED(IteratorReadRequest, ReadRequest)
NS_IMPL_RELEASE_INHERITED(IteratorReadRequest, ReadRequest)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IteratorReadRequest)
NS_INTERFACE_MAP_END_INHERITING(ReadRequest)
// https://streams.spec.whatwg.org/#rs-asynciterator-prototype-next
already_AddRefed<Promise> ReadableStream::GetNextIterationResult(
Iterator* aIterator, ErrorResult& aRv) {
// Step 1. Let reader be iterators reader.
RefPtr<ReadableStreamDefaultReader> reader = aIterator->Data().mReader;
// Step 2. Assert: reader.[[stream]] is not undefined.
MOZ_ASSERT(reader->GetStream());
// Step 3. Let promise be a new promise.
RefPtr<Promise> promise = Promise::Create(GetParentObject(), aRv);
if (aRv.Failed()) {
return nullptr;
}
// Step 4. Let readRequest be a new read request with the following items:
RefPtr<ReadRequest> request = new IteratorReadRequest(promise, reader);
// Step 5. Perform ! ReadableStreamDefaultReaderRead(this, readRequest).
AutoJSAPI jsapi;
if (!jsapi.Init(mGlobal)) {
aRv.ThrowUnknownError("Internal error");
return nullptr;
}
ReadableStreamDefaultReaderRead(jsapi.cx(), reader, request, aRv);
if (aRv.Failed()) {
return nullptr;
}
// Step 6. Return promise.
return promise.forget();
}
// https://streams.spec.whatwg.org/#rs-asynciterator-prototype-return
already_AddRefed<Promise> ReadableStream::IteratorReturn(
JSContext* aCx, Iterator* aIterator, JS::Handle<JS::Value> aValue,
ErrorResult& aRv) {
// Step 1. Let reader be iterators reader.
RefPtr<ReadableStreamDefaultReader> reader = aIterator->Data().mReader;
// Step 2. Assert: reader.[[stream]] is not undefined.
MOZ_ASSERT(reader->GetStream());
// Step 3. Assert: reader.[[readRequests]] is empty, as the async iterator
// machinery guarantees that any previous calls to next() have settled before
// this is called.
MOZ_ASSERT(reader->ReadRequests().isEmpty());
// Step 4. If iterators prevent cancel is false:
if (!aIterator->Data().mPreventCancel) {
// Step 4.1. Let result be ! ReadableStreamReaderGenericCancel(reader, arg).
RefPtr<ReadableStream> stream(reader->GetStream());
RefPtr<Promise> result = ReadableStreamCancel(aCx, stream, aValue, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// Step 4.2. Perform ! ReadableStreamDefaultReaderRelease(reader).
ReadableStreamDefaultReaderRelease(aCx, reader, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// Step 4.3. Return result.
return result.forget();
}
// Step 5. Perform ! ReadableStreamDefaultReaderRelease(reader).
ReadableStreamDefaultReaderRelease(aCx, reader, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// Step 6. Return a promise resolved with undefined.
return Promise::CreateResolvedWithUndefined(GetParentObject(), aRv);
}
// https://streams.spec.whatwg.org/#readable-stream-add-read-into-request
void ReadableStreamAddReadIntoRequest(ReadableStream* aStream,
ReadIntoRequest* aReadIntoRequest) {

View File

@ -12,6 +12,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/IterableIterator.h"
#include "mozilla/dom/QueuingStrategyBinding.h"
#include "mozilla/dom/ReadableStreamController.h"
#include "mozilla/dom/ReadableStreamDefaultController.h"
@ -26,6 +27,7 @@ class ReadableStreamGenericReader;
class ReadableStreamDefaultReader;
class ReadableStreamGenericReader;
struct ReadableStreamGetReaderOptions;
struct ReadableStreamIteratorOptions;
struct ReadIntoRequest;
class WritableStream;
struct ReadableWritablePair;
@ -143,6 +145,25 @@ class ReadableStream : public nsISupports, public nsWrapperCache {
nsTArray<RefPtr<ReadableStream>>& aResult,
ErrorResult& aRv);
struct IteratorData {
void Traverse(nsCycleCollectionTraversalCallback& cb);
void Unlink();
RefPtr<ReadableStreamDefaultReader> mReader;
bool mPreventCancel;
};
using Iterator = AsyncIterableIterator<ReadableStream>;
void InitAsyncIteratorData(IteratorData& aData, Iterator::IteratorType aType,
const ReadableStreamIteratorOptions& aOptions,
ErrorResult& aRv);
MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> GetNextIterationResult(
Iterator* aIterator, ErrorResult& aRv);
MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> IteratorReturn(
JSContext* aCx, Iterator* aIterator, JS::Handle<JS::Value> aValue,
ErrorResult& aRv);
// Internal Slots:
private:
RefPtr<ReadableStreamController> mController;

View File

@ -1,3 +1,12 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* The origin of this IDL file is
* https://streams.spec.whatwg.org/#rs-class-definition
*/
[Exposed=*,
//Transferable See Bug 1562065
]
@ -22,8 +31,8 @@ interface ReadableStream {
[Throws]
sequence<ReadableStream> tee();
// Bug 1734244
// async iterable<any>(optional ReadableStreamIteratorOptions options = {});
[GenerateReturnMethod]
async iterable<any>(optional ReadableStreamIteratorOptions options = {});
};
enum ReadableStreamReaderMode { "byob" };
@ -32,6 +41,10 @@ dictionary ReadableStreamGetReaderOptions {
ReadableStreamReaderMode mode;
};
dictionary ReadableStreamIteratorOptions {
boolean preventCancel = false;
};
dictionary ReadableWritablePair {
required ReadableStream readable;
required WritableStream writable;

View File

@ -1,26 +0,0 @@
[idlharness.any.serviceworker.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[ReadableStream interface: async iterable<any>]
expected: FAIL
[idlharness.any.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[ReadableStream interface: async iterable<any>]
expected: FAIL
[idlharness.any.worker.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[ReadableStream interface: async iterable<any>]
expected: FAIL
[idlharness.any.sharedworker.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[ReadableStream interface: async iterable<any>]
expected: FAIL

View File

@ -1,482 +1,26 @@
[async-iterator.any.serviceworker.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Async-iterating an empty but not closed/errored stream never executes the loop body and stalls the async function]
expected: FAIL
[Cancellation behavior when manually calling return(); preventCancel = true]
expected: FAIL
[Cancellation behavior when returning inside loop body; preventCancel = true]
expected: FAIL
[Async-iterating a push source]
expected: FAIL
[Async-iterating a closed stream never executes the loop body, but works fine]
expected: FAIL
[Acquiring a reader after exhaustively async-iterating a stream]
expected: FAIL
[Async iterator instances should have the correct list of properties]
expected: FAIL
[Cancellation behavior when returning inside loop body; preventCancel = false]
expected: FAIL
[Cancellation behavior when manually calling return(); preventCancel = false]
expected: FAIL
[Cancellation behavior when throwing inside loop body; preventCancel = true]
expected: FAIL
[Cancellation behavior when breaking inside loop body; preventCancel = true]
expected: FAIL
[Cancellation behavior when throwing inside loop body; preventCancel = false]
expected: FAIL
[Async-iterating a pull source]
expected: FAIL
[Acquiring a reader and reading the remaining chunks after partially async-iterating a stream with preventCancel = true]
expected: FAIL
[Acquiring a reader after partially async-iterating a stream]
expected: FAIL
[Async-iterating a partially consumed stream]
expected: FAIL
[Cancellation behavior when breaking inside loop body; preventCancel = false]
expected: FAIL
[Async-iterating a pull source manually]
expected: FAIL
[Async-iterating an errored stream throws]
expected: FAIL
[return() should unlock the stream synchronously when preventCancel = true]
expected: FAIL
[return() should unlock the stream synchronously when preventCancel = false]
expected: FAIL
[return() rejects if the stream has errored]
expected: FAIL
[values() throws if there's already a lock]
expected: FAIL
[Async-iterating a pull source with undefined values]
expected: FAIL
[next() that succeeds; return() [no awaiting\]]
expected: FAIL
[next() that succeeds; next() that reports an error(); next() [no awaiting\]]
expected: FAIL
[next() that succeeds; next() that reports an error; next()]
expected: FAIL
[return(); return() [no awaiting\]]
expected: FAIL
[return(); return()]
expected: FAIL
[next() that succeeds; next() that reports an error(); return() [no awaiting\]]
expected: FAIL
[next() rejects if the stream errors]
expected: FAIL
[Async-iterating a push source with undefined values]
expected: FAIL
[return(); next() [no awaiting\]]
expected: FAIL
[next() that succeeds; return()]
expected: FAIL
[return(); next()]
expected: FAIL
[Acquiring a reader after return()ing from a stream that errors]
expected: FAIL
[next() that succeeds; next() that reports an error(); return()]
expected: FAIL
[return() does not rejects if the stream has not errored yet]
expected: FAIL
[close() while next() is pending]
expected: FAIL
[async-iterator.any.sharedworker.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Async-iterating an empty but not closed/errored stream never executes the loop body and stalls the async function]
expected: FAIL
[Cancellation behavior when manually calling return(); preventCancel = true]
expected: FAIL
[Cancellation behavior when returning inside loop body; preventCancel = true]
expected: FAIL
[Async-iterating a push source]
expected: FAIL
[Async-iterating a closed stream never executes the loop body, but works fine]
expected: FAIL
[Acquiring a reader after exhaustively async-iterating a stream]
expected: FAIL
[Async iterator instances should have the correct list of properties]
expected: FAIL
[Cancellation behavior when returning inside loop body; preventCancel = false]
expected: FAIL
[Cancellation behavior when manually calling return(); preventCancel = false]
expected: FAIL
[Cancellation behavior when throwing inside loop body; preventCancel = true]
expected: FAIL
[Cancellation behavior when breaking inside loop body; preventCancel = true]
expected: FAIL
[Cancellation behavior when throwing inside loop body; preventCancel = false]
expected: FAIL
[Async-iterating a pull source]
expected: FAIL
[Acquiring a reader and reading the remaining chunks after partially async-iterating a stream with preventCancel = true]
expected: FAIL
[Acquiring a reader after partially async-iterating a stream]
expected: FAIL
[Async-iterating a partially consumed stream]
expected: FAIL
[Cancellation behavior when breaking inside loop body; preventCancel = false]
expected: FAIL
[Async-iterating a pull source manually]
expected: FAIL
[Async-iterating an errored stream throws]
expected: FAIL
[return() should unlock the stream synchronously when preventCancel = true]
expected: FAIL
[return() should unlock the stream synchronously when preventCancel = false]
expected: FAIL
[return() rejects if the stream has errored]
expected: FAIL
[values() throws if there's already a lock]
expected: FAIL
[Async-iterating a pull source with undefined values]
expected: FAIL
[next() that succeeds; return() [no awaiting\]]
expected: FAIL
[next() that succeeds; next() that reports an error(); next() [no awaiting\]]
expected: FAIL
[next() that succeeds; next() that reports an error; next()]
expected: FAIL
[return(); return() [no awaiting\]]
expected: FAIL
[return(); return()]
expected: FAIL
[next() that succeeds; next() that reports an error(); return() [no awaiting\]]
expected: FAIL
[next() rejects if the stream errors]
expected: FAIL
[Async-iterating a push source with undefined values]
expected: FAIL
[return(); next() [no awaiting\]]
expected: FAIL
[next() that succeeds; return()]
expected: FAIL
[return(); next()]
expected: FAIL
[Acquiring a reader after return()ing from a stream that errors]
expected: FAIL
[next() that succeeds; next() that reports an error(); return()]
expected: FAIL
[return() does not rejects if the stream has not errored yet]
expected: FAIL
[close() while next() is pending]
expected: FAIL
[async-iterator.any.worker.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Async-iterating an empty but not closed/errored stream never executes the loop body and stalls the async function]
expected: FAIL
[Cancellation behavior when manually calling return(); preventCancel = true]
expected: FAIL
[Cancellation behavior when returning inside loop body; preventCancel = true]
expected: FAIL
[Async-iterating a push source]
expected: FAIL
[Async-iterating a closed stream never executes the loop body, but works fine]
expected: FAIL
[Acquiring a reader after exhaustively async-iterating a stream]
expected: FAIL
[Async iterator instances should have the correct list of properties]
expected: FAIL
[Cancellation behavior when returning inside loop body; preventCancel = false]
expected: FAIL
[Cancellation behavior when manually calling return(); preventCancel = false]
expected: FAIL
[Cancellation behavior when throwing inside loop body; preventCancel = true]
expected: FAIL
[Cancellation behavior when breaking inside loop body; preventCancel = true]
expected: FAIL
[Cancellation behavior when throwing inside loop body; preventCancel = false]
expected: FAIL
[Async-iterating a pull source]
expected: FAIL
[Acquiring a reader and reading the remaining chunks after partially async-iterating a stream with preventCancel = true]
expected: FAIL
[Acquiring a reader after partially async-iterating a stream]
expected: FAIL
[Async-iterating a partially consumed stream]
expected: FAIL
[Cancellation behavior when breaking inside loop body; preventCancel = false]
expected: FAIL
[Async-iterating a pull source manually]
expected: FAIL
[Async-iterating an errored stream throws]
expected: FAIL
[return() should unlock the stream synchronously when preventCancel = true]
expected: FAIL
[return() should unlock the stream synchronously when preventCancel = false]
expected: FAIL
[return() rejects if the stream has errored]
expected: FAIL
[values() throws if there's already a lock]
expected: FAIL
[Async-iterating a pull source with undefined values]
expected: FAIL
[next() that succeeds; return() [no awaiting\]]
expected: FAIL
[next() that succeeds; next() that reports an error(); next() [no awaiting\]]
expected: FAIL
[next() that succeeds; next() that reports an error; next()]
expected: FAIL
[return(); return() [no awaiting\]]
expected: FAIL
[return(); return()]
expected: FAIL
[next() that succeeds; next() that reports an error(); return() [no awaiting\]]
expected: FAIL
[next() rejects if the stream errors]
expected: FAIL
[Async-iterating a push source with undefined values]
expected: FAIL
[return(); next() [no awaiting\]]
expected: FAIL
[next() that succeeds; return()]
expected: FAIL
[return(); next()]
expected: FAIL
[Acquiring a reader after return()ing from a stream that errors]
expected: FAIL
[next() that succeeds; next() that reports an error(); return()]
expected: FAIL
[return() does not rejects if the stream has not errored yet]
expected: FAIL
[close() while next() is pending]
expected: FAIL
[async-iterator.any.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Async-iterating an empty but not closed/errored stream never executes the loop body and stalls the async function]
expected: FAIL
[Cancellation behavior when manually calling return(); preventCancel = true]
expected: FAIL
[Cancellation behavior when returning inside loop body; preventCancel = true]
expected: FAIL
[Async-iterating a push source]
expected: FAIL
[Async-iterating a closed stream never executes the loop body, but works fine]
expected: FAIL
[Acquiring a reader after exhaustively async-iterating a stream]
expected: FAIL
[Async iterator instances should have the correct list of properties]
expected: FAIL
[Cancellation behavior when returning inside loop body; preventCancel = false]
expected: FAIL
[Cancellation behavior when manually calling return(); preventCancel = false]
expected: FAIL
[Cancellation behavior when throwing inside loop body; preventCancel = true]
expected: FAIL
[Cancellation behavior when breaking inside loop body; preventCancel = true]
expected: FAIL
[Cancellation behavior when throwing inside loop body; preventCancel = false]
expected: FAIL
[Async-iterating a pull source]
expected: FAIL
[Acquiring a reader and reading the remaining chunks after partially async-iterating a stream with preventCancel = true]
expected: FAIL
[Acquiring a reader after partially async-iterating a stream]
expected: FAIL
[Async-iterating a partially consumed stream]
expected: FAIL
[Cancellation behavior when breaking inside loop body; preventCancel = false]
expected: FAIL
[Async-iterating a pull source manually]
expected: FAIL
[Async-iterating an errored stream throws]
expected: FAIL
[return() should unlock the stream synchronously when preventCancel = true]
expected: FAIL
[return() should unlock the stream synchronously when preventCancel = false]
expected: FAIL
[return() rejects if the stream has errored]
expected: FAIL
[values() throws if there's already a lock]
expected: FAIL
[Async-iterating a pull source with undefined values]
expected: FAIL
[next() that succeeds; return() [no awaiting\]]
expected: FAIL
[next() that succeeds; next() that reports an error(); next() [no awaiting\]]
expected: FAIL
[next() that succeeds; next() that reports an error; next()]
expected: FAIL
[return(); return() [no awaiting\]]
expected: FAIL
[return(); return()]
expected: FAIL
[next() that succeeds; next() that reports an error(); return() [no awaiting\]]
expected: FAIL
[next() rejects if the stream errors]
expected: FAIL
[Async-iterating a push source with undefined values]
expected: FAIL
[return(); next() [no awaiting\]]
expected: FAIL
[next() that succeeds; return()]
expected: FAIL
[return(); next()]
expected: FAIL
[Acquiring a reader after return()ing from a stream that errors]
expected: FAIL
[next() that succeeds; next() that reports an error(); return()]
expected: FAIL
[return() does not rejects if the stream has not errored yet]
expected: FAIL
[close() while next() is pending]
expected: FAIL

View File

@ -1,26 +1,15 @@
[patched-global.any.serviceworker.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[ReadableStream async iterator should use the original values of getReader() and ReadableStreamDefaultReader methods]
expected: FAIL
[patched-global.any.sharedworker.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[ReadableStream async iterator should use the original values of getReader() and ReadableStreamDefaultReader methods]
expected: FAIL
[patched-global.any.worker.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[ReadableStream async iterator should use the original values of getReader() and ReadableStreamDefaultReader methods]
expected: FAIL
[patched-global.any.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[ReadableStream async iterator should use the original values of getReader() and ReadableStreamDefaultReader methods]
expected: FAIL