/* -*- 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 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 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::Constructor(const GlobalObject& aGlobal, ReadableStream& aStream, ErrorResult& aRv) { RefPtr 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 streamPtr = &aStream; if (!ReadableStreamReaderGenericInitialize(reader, streamPtr)) { return nullptr; } // Step 3. reader->mReadRequests.clear(); return reader.forget(); } void Read_ReadRequest::ChunkSteps(JSContext* aCx, JS::Handle 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 chunk(aCx, aChunk); if (!JS_WrapValue(aCx, &chunk)) { aRv.StealExceptionFromJSContext(aCx); return; } RootedDictionary result(aCx); result.mValue = chunk; result.mDone.Construct(false); // Ensure that the object is created with the current global. JS::Rooted 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 result(aCx); result.mValue.setUndefined(); result.mDone.Construct(true); JS::Rooted value(aCx); if (!ToJSValue(aCx, std::move(result), &value)) { aRv.StealExceptionFromJSContext(aCx); return; } mPromise->MaybeResolve(value); } void Read_ReadRequest::ErrorSteps(JSContext* aCx, JS::Handle 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 storedError(aCx, stream->StoredError()); aRequest->ErrorSteps(aCx, storedError, aRv); return; } case ReadableStream::ReaderState::Readable: { RefPtr 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 ReadableStreamDefaultReader::Read(ErrorResult& aRv) { // Step 1. if (!mStream) { aRv.ThrowTypeError("Reading is not possible after calling releaseLock."); return nullptr; } // Step 2. RefPtr promise = Promise::CreateInfallible(GetParentObject()); // Step 3. RefPtr 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 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::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 aError, ErrorResult& aRv) { // Step 1. Let readRequests be reader.[[readRequests]]. LinkedList> 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 = 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 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 thisRefPtr = this; ReadableStreamDefaultReaderRelease(cx, thisRefPtr, aRv); } // https://streams.spec.whatwg.org/#generic-reader-closed already_AddRefed 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 ReadableStreamGenericReaderCancel( JSContext* aCx, ReadableStreamGenericReader* aReader, JS::Handle aReason, ErrorResult& aRv) { // Step 1 (Strong ref for below call). RefPtr stream = aReader->GetStream(); // Step 2. MOZ_ASSERT(stream); // Step 3. return ReadableStreamCancel(aCx, stream, aReason, aRv); } already_AddRefed ReadableStreamGenericReader::Cancel( JSContext* aCx, JS::Handle 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