gecko-dev/dom/streams/ReadableStreamDefaultReader.cpp

441 lines
15 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.

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#include "mozilla/dom/ReadableStreamDefaultReader.h"
#include "mozilla/dom/AutoEntryScript.h"
#include "mozilla/dom/ReadableStream.h"
#include "mozilla/dom/RootedDictionary.h"
#include "js/PropertyAndElement.h"
#include "js/TypeDecls.h"
#include "js/Value.h"
#include "jsapi.h"
#include "mozilla/dom/ReadableStreamDefaultReaderBinding.h"
#include "mozilla/dom/UnderlyingSourceBinding.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsISupports.h"
#include "nsWrapperCache.h"
namespace mozilla::dom {
using namespace streams_abstract;
NS_IMPL_CYCLE_COLLECTION(ReadableStreamGenericReader, mClosedPromise, mStream,
mGlobal)
NS_IMPL_CYCLE_COLLECTING_ADDREF(ReadableStreamGenericReader)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ReadableStreamGenericReader)
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ReadableStreamGenericReader)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadableStreamGenericReader)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_INHERITED(ReadableStreamDefaultReader,
ReadableStreamGenericReader,
mReadRequests)
NS_IMPL_ADDREF_INHERITED(ReadableStreamDefaultReader,
ReadableStreamGenericReader)
NS_IMPL_RELEASE_INHERITED(ReadableStreamDefaultReader,
ReadableStreamGenericReader)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadableStreamDefaultReader)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_END_INHERITING(ReadableStreamGenericReader)
ReadableStreamDefaultReader::ReadableStreamDefaultReader(nsISupports* aGlobal)
: ReadableStreamGenericReader(do_QueryInterface(aGlobal)),
nsWrapperCache() {}
ReadableStreamDefaultReader::~ReadableStreamDefaultReader() {
mReadRequests.clear();
}
JSObject* ReadableStreamDefaultReader::WrapObject(
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
return ReadableStreamDefaultReader_Binding::Wrap(aCx, this, aGivenProto);
}
namespace streams_abstract {
// https://streams.spec.whatwg.org/#readable-stream-reader-generic-initialize
bool ReadableStreamReaderGenericInitialize(ReadableStreamGenericReader* aReader,
ReadableStream* aStream) {
// Step 1.
aReader->SetStream(aStream);
// Step 2.
aStream->SetReader(aReader);
aReader->SetClosedPromise(
Promise::CreateInfallible(aReader->GetParentObject()));
switch (aStream->State()) {
// Step 3.
case ReadableStream::ReaderState::Readable:
// Step 3.1
// Promise created above.
return true;
// Step 4.
case ReadableStream::ReaderState::Closed:
// Step 4.1.
aReader->ClosedPromise()->MaybeResolve(JS::UndefinedHandleValue);
return true;
// Step 5.
case ReadableStream::ReaderState::Errored: {
// Step 5.1 Implicit
// Step 5.2
JS::RootingContext* rcx = RootingCx();
JS::Rooted<JS::Value> rootedError(rcx, aStream->StoredError());
aReader->ClosedPromise()->MaybeReject(rootedError);
// Step 5.3
aReader->ClosedPromise()->SetSettledPromiseIsHandled();
return true;
}
default:
MOZ_ASSERT_UNREACHABLE("Unknown ReaderState");
return false;
}
}
} // namespace streams_abstract
// https://streams.spec.whatwg.org/#default-reader-constructor &&
// https://streams.spec.whatwg.org/#set-up-readable-stream-default-reader
/* static */
already_AddRefed<ReadableStreamDefaultReader>
ReadableStreamDefaultReader::Constructor(const GlobalObject& aGlobal,
ReadableStream& aStream,
ErrorResult& aRv) {
RefPtr<ReadableStreamDefaultReader> reader =
new ReadableStreamDefaultReader(aGlobal.GetAsSupports());
// https://streams.spec.whatwg.org/#set-up-readable-stream-default-reader
// Step 1.
if (aStream.Locked()) {
aRv.ThrowTypeError(
"Cannot create a new reader for a readable stream already locked by "
"another reader.");
return nullptr;
}
// Step 2.
RefPtr<ReadableStream> streamPtr = &aStream;
if (!ReadableStreamReaderGenericInitialize(reader, streamPtr)) {
return nullptr;
}
// Step 3.
reader->mReadRequests.clear();
return reader.forget();
}
void Read_ReadRequest::ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
ErrorResult& aRv) {
// https://streams.spec.whatwg.org/#default-reader-read Step 3.
// chunk steps, given chunk:
// Step 1. Resolve promise with «[ "value" → chunk, "done" → false ]».
// Value may need to be wrapped if stream and reader are in different
// compartments.
JS::Rooted<JS::Value> chunk(aCx, aChunk);
if (!JS_WrapValue(aCx, &chunk)) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
RootedDictionary<ReadableStreamReadResult> result(aCx);
result.mValue = chunk;
result.mDone.Construct(false);
// Ensure that the object is created with the current global.
JS::Rooted<JS::Value> value(aCx);
if (!ToJSValue(aCx, std::move(result), &value)) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
mPromise->MaybeResolve(value);
}
void Read_ReadRequest::CloseSteps(JSContext* aCx, ErrorResult& aRv) {
// https://streams.spec.whatwg.org/#default-reader-read Step 3.
// close steps:
// Step 1. Resolve promise with «[ "value" → undefined, "done" → true ]».
RootedDictionary<ReadableStreamReadResult> result(aCx);
result.mValue.setUndefined();
result.mDone.Construct(true);
JS::Rooted<JS::Value> value(aCx);
if (!ToJSValue(aCx, std::move(result), &value)) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
mPromise->MaybeResolve(value);
}
void Read_ReadRequest::ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> e,
ErrorResult& aRv) {
// https://streams.spec.whatwg.org/#default-reader-read Step 3.
// error steps:
// Step 1. Reject promise with e.
mPromise->MaybeReject(e);
}
NS_IMPL_CYCLE_COLLECTION(ReadRequest)
NS_IMPL_CYCLE_COLLECTION_INHERITED(Read_ReadRequest, ReadRequest, mPromise)
NS_IMPL_CYCLE_COLLECTING_ADDREF(ReadRequest)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ReadRequest)
NS_IMPL_ADDREF_INHERITED(Read_ReadRequest, ReadRequest)
NS_IMPL_RELEASE_INHERITED(Read_ReadRequest, ReadRequest)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadRequest)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Read_ReadRequest)
NS_INTERFACE_MAP_END_INHERITING(ReadRequest)
namespace streams_abstract {
// https://streams.spec.whatwg.org/#readable-stream-default-reader-read
void ReadableStreamDefaultReaderRead(JSContext* aCx,
ReadableStreamGenericReader* aReader,
ReadRequest* aRequest, ErrorResult& aRv) {
// Step 1.
ReadableStream* stream = aReader->GetStream();
// Step 2.
MOZ_ASSERT(stream);
// Step 3.
stream->SetDisturbed(true);
switch (stream->State()) {
// Step 4.
case ReadableStream::ReaderState::Closed: {
aRequest->CloseSteps(aCx, aRv);
return;
}
case ReadableStream::ReaderState::Errored: {
JS::Rooted<JS::Value> storedError(aCx, stream->StoredError());
aRequest->ErrorSteps(aCx, storedError, aRv);
return;
}
case ReadableStream::ReaderState::Readable: {
RefPtr<ReadableStreamController> controller(stream->Controller());
MOZ_ASSERT(controller);
controller->PullSteps(aCx, aRequest, aRv);
return;
}
}
}
} // namespace streams_abstract
// Return a raw pointer here to avoid refcounting, but make sure it's safe
// (the object should be kept alive by the callee).
// https://streams.spec.whatwg.org/#default-reader-read
already_AddRefed<Promise> ReadableStreamDefaultReader::Read(ErrorResult& aRv) {
// Step 1.
if (!mStream) {
aRv.ThrowTypeError("Reading is not possible after calling releaseLock.");
return nullptr;
}
// Step 2.
RefPtr<Promise> promise = Promise::CreateInfallible(GetParentObject());
// Step 3.
RefPtr<ReadRequest> request = new Read_ReadRequest(promise);
// Step 4.
AutoEntryScript aes(mGlobal, "ReadableStreamDefaultReader::Read");
JSContext* cx = aes.cx();
ReadableStreamDefaultReaderRead(cx, this, request, aRv);
if (aRv.Failed()) {
return nullptr;
}
// Step 5.
return promise.forget();
}
namespace streams_abstract {
// https://streams.spec.whatwg.org/#readable-stream-reader-generic-release
void ReadableStreamReaderGenericRelease(ReadableStreamGenericReader* aReader,
ErrorResult& aRv) {
// Step 1. Let stream be reader.[[stream]].
RefPtr<ReadableStream> stream = aReader->GetStream();
// Step 2. Assert: stream is not undefined.
MOZ_ASSERT(stream);
// Step 3. Assert: stream.[[reader]] is reader.
MOZ_ASSERT(stream->GetReader() == aReader);
// Step 4. If stream.[[state]] is "readable", reject reader.[[closedPromise]]
// with a TypeError exception.
if (stream->State() == ReadableStream::ReaderState::Readable) {
aReader->ClosedPromise()->MaybeRejectWithTypeError(
"Releasing lock on readable stream");
} else {
// Step 5. Otherwise, set reader.[[closedPromise]] to a promise rejected
// with a TypeError exception.
RefPtr<Promise> promise = Promise::CreateRejectedWithTypeError(
aReader->GetParentObject(), "Lock Released"_ns, aRv);
aReader->SetClosedPromise(promise.forget());
}
// Step 6. Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
aReader->ClosedPromise()->SetSettledPromiseIsHandled();
// Step 7. Perform ! stream.[[controller]].[[ReleaseSteps]]().
stream->Controller()->ReleaseSteps();
// Step 8. Set stream.[[reader]] to undefined.
stream->SetReader(nullptr);
// Step 9. Set reader.[[stream]] to undefined.
aReader->SetStream(nullptr);
}
// https://streams.spec.whatwg.org/#abstract-opdef-readablestreamdefaultreadererrorreadrequests
void ReadableStreamDefaultReaderErrorReadRequests(
JSContext* aCx, ReadableStreamDefaultReader* aReader,
JS::Handle<JS::Value> aError, ErrorResult& aRv) {
// Step 1. Let readRequests be reader.[[readRequests]].
LinkedList<RefPtr<ReadRequest>> readRequests =
std::move(aReader->ReadRequests());
// Step 2. Set reader.[[readRequests]] to a new empty list.
// Note: The std::move already cleared this anyway.
aReader->ReadRequests().clear();
// Step 3. For each readRequest of readRequests,
while (RefPtr<ReadRequest> readRequest = readRequests.popFirst()) {
// Step 3.1. Perform readRequests error steps, given e.
readRequest->ErrorSteps(aCx, aError, aRv);
if (aRv.Failed()) {
return;
}
}
}
// https://streams.spec.whatwg.org/#abstract-opdef-readablestreamdefaultreaderrelease
void ReadableStreamDefaultReaderRelease(JSContext* aCx,
ReadableStreamDefaultReader* aReader,
ErrorResult& aRv) {
// Step 1. Perform ! ReadableStreamReaderGenericRelease(reader).
ReadableStreamReaderGenericRelease(aReader, aRv);
if (aRv.Failed()) {
return;
}
// Step 2. Let e be a new TypeError exception.
ErrorResult rv;
rv.ThrowTypeError("Releasing lock");
JS::Rooted<JS::Value> error(aCx);
MOZ_ALWAYS_TRUE(ToJSValue(aCx, std::move(rv), &error));
// Step 3. Perform ! ReadableStreamDefaultReaderErrorReadRequests(reader, e).
ReadableStreamDefaultReaderErrorReadRequests(aCx, aReader, error, aRv);
}
} // namespace streams_abstract
// https://streams.spec.whatwg.org/#default-reader-release-lock
void ReadableStreamDefaultReader::ReleaseLock(ErrorResult& aRv) {
// Step 1. If this.[[stream]] is undefined, return.
if (!mStream) {
return;
}
AutoJSAPI jsapi;
if (!jsapi.Init(mGlobal)) {
return aRv.ThrowUnknownError("Internal error");
}
JSContext* cx = jsapi.cx();
// Step 2. Perform ! ReadableStreamDefaultReaderRelease(this).
RefPtr<ReadableStreamDefaultReader> thisRefPtr = this;
ReadableStreamDefaultReaderRelease(cx, thisRefPtr, aRv);
}
// https://streams.spec.whatwg.org/#generic-reader-closed
already_AddRefed<Promise> ReadableStreamGenericReader::Closed() const {
// Step 1.
return do_AddRef(mClosedPromise);
}
// https://streams.spec.whatwg.org/#readable-stream-reader-generic-cancel
MOZ_CAN_RUN_SCRIPT
static already_AddRefed<Promise> ReadableStreamGenericReaderCancel(
JSContext* aCx, ReadableStreamGenericReader* aReader,
JS::Handle<JS::Value> aReason, ErrorResult& aRv) {
// Step 1 (Strong ref for below call).
RefPtr<ReadableStream> stream = aReader->GetStream();
// Step 2.
MOZ_ASSERT(stream);
// Step 3.
return ReadableStreamCancel(aCx, stream, aReason, aRv);
}
already_AddRefed<Promise> ReadableStreamGenericReader::Cancel(
JSContext* aCx, JS::Handle<JS::Value> aReason, ErrorResult& aRv) {
// Step 1. If this.[[stream]] is undefined,
// return a promise rejected with a TypeError exception.
if (!mStream) {
aRv.ThrowTypeError("Canceling is not possible after calling releaseLock.");
return nullptr;
}
// Step 2. Return ! ReadableStreamReaderGenericCancel(this, reason).
return ReadableStreamGenericReaderCancel(aCx, this, aReason, aRv);
}
namespace streams_abstract {
// https://streams.spec.whatwg.org/#set-up-readable-stream-default-reader
void SetUpReadableStreamDefaultReader(ReadableStreamDefaultReader* aReader,
ReadableStream* aStream,
ErrorResult& aRv) {
// Step 1.
if (IsReadableStreamLocked(aStream)) {
return aRv.ThrowTypeError(
"Cannot get a new reader for a readable stream already locked by "
"another reader.");
}
// Step 2.
if (!ReadableStreamReaderGenericInitialize(aReader, aStream)) {
return;
}
// Step 3.
aReader->ReadRequests().clear();
}
} // namespace streams_abstract
// https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-a-chunk
// To read a chunk from a ReadableStreamDefaultReader reader, given a read
// request readRequest, perform ! ReadableStreamDefaultReaderRead(reader,
// readRequest).
void ReadableStreamDefaultReader::ReadChunk(JSContext* aCx,
ReadRequest& aRequest,
ErrorResult& aRv) {
ReadableStreamDefaultReaderRead(aCx, this, &aRequest, aRv);
}
} // namespace mozilla::dom