summaryrefslogtreecommitdiffstats
path: root/dom/streams/ReadableStreamDefaultReader.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/streams/ReadableStreamDefaultReader.cpp440
1 files changed, 440 insertions, 0 deletions
diff --git a/dom/streams/ReadableStreamDefaultReader.cpp b/dom/streams/ReadableStreamDefaultReader.cpp
new file mode 100644
index 0000000000..ff95fb37a8
--- /dev/null
+++ b/dom/streams/ReadableStreamDefaultReader.cpp
@@ -0,0 +1,440 @@
+/* -*- 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