summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/streams/ReadableStreamReader.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/src/builtin/streams/ReadableStreamReader.cpp276
1 files changed, 276 insertions, 0 deletions
diff --git a/js/src/builtin/streams/ReadableStreamReader.cpp b/js/src/builtin/streams/ReadableStreamReader.cpp
new file mode 100644
index 0000000000..f384bce83e
--- /dev/null
+++ b/js/src/builtin/streams/ReadableStreamReader.cpp
@@ -0,0 +1,276 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* ReadableStream reader abstract operations. */
+
+#include "builtin/streams/ReadableStreamReader-inl.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jsfriendapi.h" // JS_ReportErrorNumberASCII
+
+#include "builtin/Stream.h" // js::ReadableStreamController, js::ReadableStreamControllerPullSteps
+#include "builtin/streams/ReadableStream.h" // js::ReadableStream
+#include "builtin/streams/ReadableStreamController.h" // js::ReadableStreamController
+#include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStream{Cancel,CreateReadResult}
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
+#include "js/Value.h" // JS::Value, JS::UndefinedHandleValue
+#include "vm/Interpreter.h" // js::GetAndClearException
+#include "vm/JSContext.h" // JSContext
+#include "vm/PlainObject.h" // js::PlainObject
+#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
+#include "vm/Runtime.h" // JSRuntime
+
+#include "builtin/Promise-inl.h" // js::SetSettledPromiseIsHandled
+#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::UnwrapInternalSlot
+#include "vm/List-inl.h" // js::StoreNewListInFixedSlot
+#include "vm/Realm-inl.h" // js::AutoRealm
+
+using JS::Handle;
+using JS::Rooted;
+using JS::Value;
+
+using js::PromiseObject;
+using js::ReadableStreamController;
+using js::UnwrapStreamFromReader;
+
+/*** 3.8. Readable stream reader abstract operations ************************/
+
+// Streams spec, 3.8.1. IsReadableStreamDefaultReader ( x )
+// Implemented via is<ReadableStreamDefaultReader>()
+
+// Streams spec, 3.8.2. IsReadableStreamBYOBReader ( x )
+// Implemented via is<ReadableStreamBYOBReader>()
+
+/**
+ * Streams spec, 3.8.3. ReadableStreamReaderGenericCancel ( reader, reason )
+ */
+MOZ_MUST_USE JSObject* js::ReadableStreamReaderGenericCancel(
+ JSContext* cx, Handle<ReadableStreamReader*> unwrappedReader,
+ Handle<Value> reason) {
+ // Step 1: Let stream be reader.[[ownerReadableStream]].
+ // Step 2: Assert: stream is not undefined (implicit).
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, UnwrapStreamFromReader(cx, unwrappedReader));
+ if (!unwrappedStream) {
+ return nullptr;
+ }
+
+ // Step 3: Return ! ReadableStreamCancel(stream, reason).
+ return js::ReadableStreamCancel(cx, unwrappedStream, reason);
+}
+
+/**
+ * Streams spec, 3.8.4.
+ * ReadableStreamReaderGenericInitialize ( reader, stream )
+ */
+MOZ_MUST_USE bool js::ReadableStreamReaderGenericInitialize(
+ JSContext* cx, Handle<ReadableStreamReader*> reader,
+ Handle<ReadableStream*> unwrappedStream, ForAuthorCodeBool forAuthorCode) {
+ cx->check(reader);
+
+ // Step 1: Set reader.[[forAuthorCode]] to true.
+ reader->setForAuthorCode(forAuthorCode);
+
+ // Step 2: Set reader.[[ownerReadableStream]] to stream.
+ {
+ Rooted<JSObject*> readerCompartmentStream(cx, unwrappedStream);
+ if (!cx->compartment()->wrap(cx, &readerCompartmentStream)) {
+ return false;
+ }
+ reader->setStream(readerCompartmentStream);
+ }
+
+ // Step 3 is moved to the end.
+
+ // Step 4: If stream.[[state]] is "readable",
+ Rooted<PromiseObject*> promise(cx);
+ if (unwrappedStream->readable()) {
+ // Step a: Set reader.[[closedPromise]] to a new promise.
+ promise = PromiseObject::createSkippingExecutor(cx);
+ } else if (unwrappedStream->closed()) {
+ // Step 5: Otherwise, if stream.[[state]] is "closed",
+ // Step a: Set reader.[[closedPromise]] to a promise resolved with
+ // undefined.
+ promise = PromiseResolvedWithUndefined(cx);
+ } else {
+ // Step 6: Otherwise,
+ // Step a: Assert: stream.[[state]] is "errored".
+ MOZ_ASSERT(unwrappedStream->errored());
+
+ // Step b: Set reader.[[closedPromise]] to a promise rejected with
+ // stream.[[storedError]].
+ Rooted<Value> storedError(cx, unwrappedStream->storedError());
+ if (!cx->compartment()->wrap(cx, &storedError)) {
+ return false;
+ }
+ promise = PromiseObject::unforgeableReject(cx, storedError);
+ if (!promise) {
+ return false;
+ }
+
+ // Step c. Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
+ js::SetSettledPromiseIsHandled(cx, promise);
+ }
+
+ if (!promise) {
+ return false;
+ }
+
+ reader->setClosedPromise(promise);
+
+ // Step 4 of caller 3.6.3. new ReadableStreamDefaultReader(stream):
+ // Step 5 of caller 3.7.3. new ReadableStreamBYOBReader(stream):
+ // Set this.[[read{Into}Requests]] to a new empty List.
+ if (!StoreNewListInFixedSlot(cx, reader,
+ ReadableStreamReader::Slot_Requests)) {
+ return false;
+ }
+
+ // Step 3: Set stream.[[reader]] to reader.
+ // Doing this last prevents a partially-initialized reader from being
+ // attached to the stream (and possibly left there on OOM).
+ {
+ AutoRealm ar(cx, unwrappedStream);
+ Rooted<JSObject*> streamCompartmentReader(cx, reader);
+ if (!cx->compartment()->wrap(cx, &streamCompartmentReader)) {
+ return false;
+ }
+ unwrappedStream->setReader(streamCompartmentReader);
+ }
+
+ return true;
+}
+
+/**
+ * Streams spec, 3.8.5. ReadableStreamReaderGenericRelease ( reader )
+ */
+MOZ_MUST_USE bool js::ReadableStreamReaderGenericRelease(
+ JSContext* cx, Handle<ReadableStreamReader*> unwrappedReader) {
+ // Step 1: Assert: reader.[[ownerReadableStream]] is not undefined.
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, UnwrapStreamFromReader(cx, unwrappedReader));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ // Step 2: Assert: reader.[[ownerReadableStream]].[[reader]] is reader.
+#ifdef DEBUG
+ // The assertion is weakened a bit to allow for nuked wrappers.
+ ReadableStreamReader* unwrappedReader2 =
+ UnwrapReaderFromStreamNoThrow(unwrappedStream);
+ MOZ_ASSERT_IF(unwrappedReader2, unwrappedReader2 == unwrappedReader);
+#endif
+
+ // Create an exception to reject promises with below. We don't have a
+ // clean way to do this, unfortunately.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMREADER_RELEASED);
+ Rooted<Value> exn(cx);
+ if (!cx->isExceptionPending() || !GetAndClearException(cx, &exn)) {
+ // Uncatchable error. Die immediately without resolving
+ // reader.[[closedPromise]].
+ return false;
+ }
+
+ // Step 3: If reader.[[ownerReadableStream]].[[state]] is "readable", reject
+ // reader.[[closedPromise]] with a TypeError exception.
+ Rooted<PromiseObject*> unwrappedClosedPromise(cx);
+ if (unwrappedStream->readable()) {
+ unwrappedClosedPromise = UnwrapInternalSlot<PromiseObject>(
+ cx, unwrappedReader, ReadableStreamReader::Slot_ClosedPromise);
+ if (!unwrappedClosedPromise) {
+ return false;
+ }
+
+ AutoRealm ar(cx, unwrappedClosedPromise);
+ if (!cx->compartment()->wrap(cx, &exn)) {
+ return false;
+ }
+ if (!PromiseObject::reject(cx, unwrappedClosedPromise, exn)) {
+ return false;
+ }
+ } else {
+ // Step 4: Otherwise, set reader.[[closedPromise]] to a new promise
+ // rejected with a TypeError exception.
+ Rooted<JSObject*> closedPromise(cx,
+ PromiseObject::unforgeableReject(cx, exn));
+ if (!closedPromise) {
+ return false;
+ }
+ unwrappedClosedPromise = &closedPromise->as<PromiseObject>();
+
+ AutoRealm ar(cx, unwrappedReader);
+ if (!cx->compartment()->wrap(cx, &closedPromise)) {
+ return false;
+ }
+ unwrappedReader->setClosedPromise(closedPromise);
+ }
+
+ // Step 5: Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
+ js::SetSettledPromiseIsHandled(cx, unwrappedClosedPromise);
+
+ // Step 6: Set reader.[[ownerReadableStream]].[[reader]] to undefined.
+ unwrappedStream->clearReader();
+
+ // Step 7: Set reader.[[ownerReadableStream]] to undefined.
+ unwrappedReader->clearStream();
+
+ return true;
+}
+
+/**
+ * Streams spec, 3.8.7.
+ * ReadableStreamDefaultReaderRead ( reader [, forAuthorCode ] )
+ */
+MOZ_MUST_USE PromiseObject* js::ReadableStreamDefaultReaderRead(
+ JSContext* cx, Handle<ReadableStreamDefaultReader*> unwrappedReader) {
+ // Step 1: If forAuthorCode was not passed, set it to false (implicit).
+
+ // Step 2: Let stream be reader.[[ownerReadableStream]].
+ // Step 3: Assert: stream is not undefined.
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, UnwrapStreamFromReader(cx, unwrappedReader));
+ if (!unwrappedStream) {
+ return nullptr;
+ }
+
+ // Step 4: Set stream.[[disturbed]] to true.
+ unwrappedStream->setDisturbed();
+
+ // Step 5: If stream.[[state]] is "closed", return a promise resolved with
+ // ! ReadableStreamCreateReadResult(undefined, true, forAuthorCode).
+ if (unwrappedStream->closed()) {
+ PlainObject* iterResult = ReadableStreamCreateReadResult(
+ cx, UndefinedHandleValue, true, unwrappedReader->forAuthorCode());
+ if (!iterResult) {
+ return nullptr;
+ }
+
+ Rooted<Value> iterResultVal(cx, JS::ObjectValue(*iterResult));
+ return PromiseObject::unforgeableResolveWithNonPromise(cx, iterResultVal);
+ }
+
+ // Step 6: If stream.[[state]] is "errored", return a promise rejected
+ // with stream.[[storedError]].
+ if (unwrappedStream->errored()) {
+ Rooted<Value> storedError(cx, unwrappedStream->storedError());
+ if (!cx->compartment()->wrap(cx, &storedError)) {
+ return nullptr;
+ }
+ return PromiseObject::unforgeableReject(cx, storedError);
+ }
+
+ // Step 7: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(unwrappedStream->readable());
+
+ // Step 8: Return ! stream.[[readableStreamController]].[[PullSteps]]().
+ Rooted<ReadableStreamController*> unwrappedController(
+ cx, unwrappedStream->controller());
+ return ReadableStreamControllerPullSteps(cx, unwrappedController);
+}