mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 10:44:56 +00:00
99238aafa7
Differential Revision: https://phabricator.services.mozilla.com/D172057
441 lines
15 KiB
C++
441 lines
15 KiB
C++
/* -*- 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 readRequest’s 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
|