summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/streams/ReadableStreamInternals.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/src/builtin/streams/ReadableStreamInternals.cpp473
1 files changed, 473 insertions, 0 deletions
diff --git a/js/src/builtin/streams/ReadableStreamInternals.cpp b/js/src/builtin/streams/ReadableStreamInternals.cpp
new file mode 100644
index 0000000000..a6f3fbc826
--- /dev/null
+++ b/js/src/builtin/streams/ReadableStreamInternals.cpp
@@ -0,0 +1,473 @@
+/* -*- 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/. */
+
+/* The interface between readable streams and controllers. */
+
+#include "builtin/streams/ReadableStreamInternals.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include <stdint.h> // uint32_t
+
+#include "jsfriendapi.h" // js::AssertSameCompartment
+
+#include "builtin/streams/ReadableStreamController.h" // js::ReadableStreamController{,CancelSteps}
+#include "builtin/streams/ReadableStreamReader.h" // js::ReadableStream{,Default}Reader, js::ForAuthorCodeBool
+#include "gc/AllocKind.h" // js::gc::AllocKind
+#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
+#include "js/GCAPI.h" // JS::AutoSuppressGCAnalysis
+#include "js/Promise.h" // JS::CallOriginalPromiseThen, JS::ResolvePromise
+#include "js/Result.h" // JS_TRY_VAR_OR_RETURN_NULL
+#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
+#include "js/Stream.h" // JS::ReadableStreamUnderlyingSource, JS::ReadableStreamMode
+#include "js/Value.h" // JS::Value, JS::{Boolean,Object}Value, JS::UndefinedHandleValue
+#include "vm/JSContext.h" // JSContext
+#include "vm/JSFunction.h" // JSFunction, js::NewNativeFunction
+#include "vm/NativeObject.h" // js::NativeObject, js::PlainObject
+#include "vm/ObjectGroup.h" // js::GenericObject
+#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
+#include "vm/Realm.h" // JS::Realm
+#include "vm/StringType.h" // js::PropertyName
+
+#include "builtin/Promise-inl.h" // js::SetSettledPromiseIsHandled
+#include "builtin/streams/MiscellaneousOperations-inl.h" // js::{Reject,Resolve}UnwrappedPromiseWithUndefined
+#include "builtin/streams/ReadableStreamReader-inl.h" // js::js::UnwrapReaderFromStream{,NoThrow}
+#include "vm/Compartment-inl.h" // JS::Compartment::wrap
+#include "vm/JSContext-inl.h" // JSContext::check
+#include "vm/List-inl.h" // js::ListObject, js::AppendToListInFixedSlot, js::StoreNewListInFixedSlot
+#include "vm/PlainObject-inl.h" // js::PlainObject::createWithTemplate
+#include "vm/Realm-inl.h" // JS::Realm
+
+using JS::BooleanValue;
+using JS::CallArgs;
+using JS::CallArgsFromVp;
+using JS::Handle;
+using JS::ObjectValue;
+using JS::ResolvePromise;
+using JS::Rooted;
+using JS::UndefinedHandleValue;
+using JS::Value;
+
+using js::PlainObject;
+using js::ReadableStream;
+
+/*** 3.5. The interface between readable streams and controllers ************/
+
+/**
+ * Streams spec, 3.5.1.
+ * ReadableStreamAddReadIntoRequest ( stream, forAuthorCode )
+ * Streams spec, 3.5.2.
+ * ReadableStreamAddReadRequest ( stream, forAuthorCode )
+ *
+ * Our implementation does not pass around forAuthorCode parameters in the same
+ * places as the standard, but the effect is the same. See the comment on
+ * `ReadableStreamReader::forAuthorCode()`.
+ */
+MOZ_MUST_USE js::PromiseObject* js::ReadableStreamAddReadOrReadIntoRequest(
+ JSContext* cx, Handle<ReadableStream*> unwrappedStream) {
+ // Step 1: Assert: ! IsReadableStream{BYOB,Default}Reader(stream.[[reader]])
+ // is true.
+ // (Only default readers exist so far.)
+ Rooted<ReadableStreamReader*> unwrappedReader(
+ cx, UnwrapReaderFromStream(cx, unwrappedStream));
+ if (!unwrappedReader) {
+ return nullptr;
+ }
+ MOZ_ASSERT(unwrappedReader->is<ReadableStreamDefaultReader>());
+
+ // Step 2 of 3.5.1: Assert: stream.[[state]] is "readable" or "closed".
+ // Step 2 of 3.5.2: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(unwrappedStream->readable() || unwrappedStream->closed());
+ MOZ_ASSERT_IF(unwrappedReader->is<ReadableStreamDefaultReader>(),
+ unwrappedStream->readable());
+
+ // Step 3: Let promise be a new promise.
+ Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
+ if (!promise) {
+ return nullptr;
+ }
+
+ // Step 4: Let read{Into}Request be
+ // Record {[[promise]]: promise, [[forAuthorCode]]: forAuthorCode}.
+ // Step 5: Append read{Into}Request as the last element of
+ // stream.[[reader]].[[read{Into}Requests]].
+ // Since we don't need the [[forAuthorCode]] field (see the comment on
+ // `ReadableStreamReader::forAuthorCode()`), we elide the Record and store
+ // only the promise.
+ if (!AppendToListInFixedSlot(cx, unwrappedReader,
+ ReadableStreamReader::Slot_Requests, promise)) {
+ return nullptr;
+ }
+
+ // Step 6: Return promise.
+ return promise;
+}
+
+/**
+ * Used for transforming the result of promise fulfillment/rejection.
+ */
+static bool ReturnUndefined(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * Streams spec, 3.5.3. ReadableStreamCancel ( stream, reason )
+ */
+MOZ_MUST_USE JSObject* js::ReadableStreamCancel(
+ JSContext* cx, Handle<ReadableStream*> unwrappedStream,
+ Handle<Value> reason) {
+ AssertSameCompartment(cx, reason);
+
+ // Step 1: Set stream.[[disturbed]] to true.
+ unwrappedStream->setDisturbed();
+
+ // Step 2: If stream.[[state]] is "closed", return a promise resolved with
+ // undefined.
+ if (unwrappedStream->closed()) {
+ return PromiseResolvedWithUndefined(cx);
+ }
+
+ // Step 3: 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 4: Perform ! ReadableStreamClose(stream).
+ if (!ReadableStreamCloseInternal(cx, unwrappedStream)) {
+ return nullptr;
+ }
+
+ // Step 5: Let sourceCancelPromise be
+ // ! stream.[[readableStreamController]].[[CancelSteps]](reason).
+ Rooted<ReadableStreamController*> unwrappedController(
+ cx, unwrappedStream->controller());
+ Rooted<JSObject*> sourceCancelPromise(
+ cx, ReadableStreamControllerCancelSteps(cx, unwrappedController, reason));
+ if (!sourceCancelPromise) {
+ return nullptr;
+ }
+
+ // Step 6: Return the result of reacting to sourceCancelPromise with a
+ // fulfillment step that returns undefined.
+ Handle<PropertyName*> funName = cx->names().empty;
+ Rooted<JSFunction*> returnUndefined(
+ cx, NewNativeFunction(cx, ReturnUndefined, 0, funName,
+ gc::AllocKind::FUNCTION, GenericObject));
+ if (!returnUndefined) {
+ return nullptr;
+ }
+ return JS::CallOriginalPromiseThen(cx, sourceCancelPromise, returnUndefined,
+ nullptr);
+}
+
+/**
+ * Streams spec, 3.5.4. ReadableStreamClose ( stream )
+ */
+MOZ_MUST_USE bool js::ReadableStreamCloseInternal(
+ JSContext* cx, Handle<ReadableStream*> unwrappedStream) {
+ // Step 1: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(unwrappedStream->readable());
+
+ // Step 2: Set stream.[[state]] to "closed".
+ unwrappedStream->setClosed();
+
+ // Step 4: If reader is undefined, return (reordered).
+ if (!unwrappedStream->hasReader()) {
+ return true;
+ }
+
+ // Step 3: Let reader be stream.[[reader]].
+ Rooted<ReadableStreamReader*> unwrappedReader(
+ cx, UnwrapReaderFromStream(cx, unwrappedStream));
+ if (!unwrappedReader) {
+ return false;
+ }
+
+ // Step 5: If ! IsReadableStreamDefaultReader(reader) is true,
+ if (unwrappedReader->is<ReadableStreamDefaultReader>()) {
+ ForAuthorCodeBool forAuthorCode = unwrappedReader->forAuthorCode();
+
+ // Step a: Repeat for each readRequest that is an element of
+ // reader.[[readRequests]],
+ Rooted<ListObject*> unwrappedReadRequests(cx, unwrappedReader->requests());
+ uint32_t len = unwrappedReadRequests->length();
+ Rooted<JSObject*> readRequest(cx);
+ Rooted<JSObject*> resultObj(cx);
+ Rooted<Value> resultVal(cx);
+ for (uint32_t i = 0; i < len; i++) {
+ // Step i: Resolve readRequest.[[promise]] with
+ // ! ReadableStreamCreateReadResult(undefined, true,
+ // readRequest.[[forAuthorCode]]).
+ readRequest = &unwrappedReadRequests->getAs<JSObject>(i);
+ if (!cx->compartment()->wrap(cx, &readRequest)) {
+ return false;
+ }
+
+ resultObj = js::ReadableStreamCreateReadResult(cx, UndefinedHandleValue,
+ true, forAuthorCode);
+ if (!resultObj) {
+ return false;
+ }
+ resultVal = ObjectValue(*resultObj);
+ if (!ResolvePromise(cx, readRequest, resultVal)) {
+ return false;
+ }
+ }
+
+ // Step b: Set reader.[[readRequests]] to an empty List.
+ unwrappedReader->clearRequests();
+ }
+
+ // Step 6: Resolve reader.[[closedPromise]] with undefined.
+ if (!ResolveUnwrappedPromiseWithUndefined(cx,
+ unwrappedReader->closedPromise())) {
+ return false;
+ }
+
+ if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource) {
+ // Make sure we're in the stream's compartment.
+ AutoRealm ar(cx, unwrappedStream);
+ JS::ReadableStreamUnderlyingSource* source =
+ unwrappedStream->controller()->externalSource();
+ source->onClosed(cx, unwrappedStream);
+ }
+
+ return true;
+}
+
+/**
+ * Streams spec, 3.5.5. ReadableStreamCreateReadResult ( value, done,
+ * forAuthorCode )
+ */
+MOZ_MUST_USE PlainObject* js::ReadableStreamCreateReadResult(
+ JSContext* cx, Handle<Value> value, bool done,
+ ForAuthorCodeBool forAuthorCode) {
+ // Step 1: Let prototype be null.
+ // Step 2: If forAuthorCode is true, set prototype to %ObjectPrototype%.
+ Rooted<PlainObject*> templateObject(
+ cx,
+ forAuthorCode == ForAuthorCodeBool::Yes
+ ? cx->realm()->getOrCreateIterResultTemplateObject(cx)
+ : cx->realm()->getOrCreateIterResultWithoutPrototypeTemplateObject(
+ cx));
+ if (!templateObject) {
+ return nullptr;
+ }
+
+ // Step 3: Assert: Type(done) is Boolean (implicit).
+
+ // Step 4: Let obj be ObjectCreate(prototype).
+ PlainObject* obj;
+ JS_TRY_VAR_OR_RETURN_NULL(
+ cx, obj, PlainObject::createWithTemplate(cx, templateObject));
+
+ // Step 5: Perform CreateDataProperty(obj, "value", value).
+ obj->setSlot(Realm::IterResultObjectValueSlot, value);
+
+ // Step 6: Perform CreateDataProperty(obj, "done", done).
+ obj->setSlot(Realm::IterResultObjectDoneSlot, BooleanValue(done));
+
+ // Step 7: Return obj.
+ return obj;
+}
+
+/**
+ * Streams spec, 3.5.6. ReadableStreamError ( stream, e )
+ */
+MOZ_MUST_USE bool js::ReadableStreamErrorInternal(
+ JSContext* cx, Handle<ReadableStream*> unwrappedStream, Handle<Value> e) {
+ // Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
+
+ // Step 2: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(unwrappedStream->readable());
+
+ // Step 3: Set stream.[[state]] to "errored".
+ unwrappedStream->setErrored();
+
+ // Step 4: Set stream.[[storedError]] to e.
+ {
+ AutoRealm ar(cx, unwrappedStream);
+ Rooted<Value> wrappedError(cx, e);
+ if (!cx->compartment()->wrap(cx, &wrappedError)) {
+ return false;
+ }
+ unwrappedStream->setStoredError(wrappedError);
+ }
+
+ // Step 6: If reader is undefined, return (reordered).
+ if (!unwrappedStream->hasReader()) {
+ return true;
+ }
+
+ // Step 5: Let reader be stream.[[reader]].
+ Rooted<ReadableStreamReader*> unwrappedReader(
+ cx, UnwrapReaderFromStream(cx, unwrappedStream));
+ if (!unwrappedReader) {
+ return false;
+ }
+
+ // Steps 7-8: (Identical in our implementation.)
+ // Step 7.a/8.b: Repeat for each read{Into}Request that is an element of
+ // reader.[[read{Into}Requests]],
+ {
+ Rooted<ListObject*> unwrappedReadRequests(cx, unwrappedReader->requests());
+ Rooted<JSObject*> readRequest(cx);
+ uint32_t len = unwrappedReadRequests->length();
+ for (uint32_t i = 0; i < len; i++) {
+ // Step i: Reject read{Into}Request.[[promise]] with e.
+ // Responses have to be created in the compartment from which the error
+ // was triggered, which might not be the same as the one the request was
+ // created in, so we have to wrap requests here.
+ readRequest = &unwrappedReadRequests->get(i).toObject();
+ if (!RejectUnwrappedPromiseWithError(cx, &readRequest, e)) {
+ return false;
+ }
+ }
+ }
+
+ // Step 7.b/8.c: Set reader.[[read{Into}Requests]] to a new empty List.
+ if (!StoreNewListInFixedSlot(cx, unwrappedReader,
+ ReadableStreamReader::Slot_Requests)) {
+ return false;
+ }
+
+ // Step 9: Reject reader.[[closedPromise]] with e.
+ if (!RejectUnwrappedPromiseWithError(cx, unwrappedReader->closedPromise(),
+ e)) {
+ return false;
+ }
+
+ // Step 10: Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
+ //
+ // `closedPromise` can return a CCW, but that case is filtered out by step 6,
+ // given the only place that can set [[closedPromise]] to a CCW is
+ // 3.8.5 ReadableStreamReaderGenericRelease step 4, and
+ // 3.8.5 ReadableStreamReaderGenericRelease step 6 sets
+ // stream.[[reader]] to undefined.
+ Rooted<JSObject*> closedPromise(cx, unwrappedReader->closedPromise());
+ js::SetSettledPromiseIsHandled(cx, closedPromise.as<PromiseObject>());
+
+ if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource) {
+ // Make sure we're in the stream's compartment.
+ AutoRealm ar(cx, unwrappedStream);
+ JS::ReadableStreamUnderlyingSource* source =
+ unwrappedStream->controller()->externalSource();
+
+ // Ensure that the embedding doesn't have to deal with
+ // mixed-compartment arguments to the callback.
+ Rooted<Value> error(cx, e);
+ if (!cx->compartment()->wrap(cx, &error)) {
+ return false;
+ }
+ source->onErrored(cx, unwrappedStream, error);
+ }
+
+ return true;
+}
+
+/**
+ * Streams spec, 3.5.7.
+ * ReadableStreamFulfillReadIntoRequest( stream, chunk, done )
+ * Streams spec, 3.5.8.
+ * ReadableStreamFulfillReadRequest ( stream, chunk, done )
+ * These two spec functions are identical in our implementation.
+ */
+MOZ_MUST_USE bool js::ReadableStreamFulfillReadOrReadIntoRequest(
+ JSContext* cx, Handle<ReadableStream*> unwrappedStream, Handle<Value> chunk,
+ bool done) {
+ cx->check(chunk);
+
+ // Step 1: Let reader be stream.[[reader]].
+ Rooted<ReadableStreamReader*> unwrappedReader(
+ cx, UnwrapReaderFromStream(cx, unwrappedStream));
+ if (!unwrappedReader) {
+ return false;
+ }
+
+ // Step 2: Let read{Into}Request be the first element of
+ // reader.[[read{Into}Requests]].
+ // Step 3: Remove read{Into}Request from reader.[[read{Into}Requests]],
+ // shifting all other elements downward (so that the second becomes
+ // the first, and so on).
+ Rooted<ListObject*> unwrappedReadIntoRequests(cx,
+ unwrappedReader->requests());
+ Rooted<JSObject*> readIntoRequest(
+ cx, &unwrappedReadIntoRequests->popFirstAs<JSObject>(cx));
+ MOZ_ASSERT(readIntoRequest);
+ if (!cx->compartment()->wrap(cx, &readIntoRequest)) {
+ return false;
+ }
+
+ // Step 4: Resolve read{Into}Request.[[promise]] with
+ // ! ReadableStreamCreateReadResult(chunk, done,
+ // readIntoRequest.[[forAuthorCode]]).
+ PlainObject* iterResult = ReadableStreamCreateReadResult(
+ cx, chunk, done, unwrappedReader->forAuthorCode());
+ if (!iterResult) {
+ return false;
+ }
+
+ Rooted<Value> val(cx, ObjectValue(*iterResult));
+ return ResolvePromise(cx, readIntoRequest, val);
+}
+
+/**
+ * Streams spec, 3.5.9. ReadableStreamGetNumReadIntoRequests ( stream )
+ * Streams spec, 3.5.10. ReadableStreamGetNumReadRequests ( stream )
+ * (Identical implementation.)
+ */
+uint32_t js::ReadableStreamGetNumReadRequests(ReadableStream* stream) {
+ // Step 1: Return the number of elements in
+ // stream.[[reader]].[[read{Into}Requests]].
+ if (!stream->hasReader()) {
+ return 0;
+ }
+
+ JS::AutoSuppressGCAnalysis nogc;
+ ReadableStreamReader* reader = UnwrapReaderFromStreamNoThrow(stream);
+
+ // Reader is a dead wrapper, treat it as non-existent.
+ if (!reader) {
+ return 0;
+ }
+
+ return reader->requests()->length();
+}
+
+// Streams spec, 3.5.11. ReadableStreamHasBYOBReader ( stream )
+//
+// Not implemented.
+
+/**
+ * Streams spec 3.5.12. ReadableStreamHasDefaultReader ( stream )
+ */
+MOZ_MUST_USE bool js::ReadableStreamHasDefaultReader(
+ JSContext* cx, Handle<ReadableStream*> unwrappedStream, bool* result) {
+ // Step 1: Let reader be stream.[[reader]].
+ // Step 2: If reader is undefined, return false.
+ if (!unwrappedStream->hasReader()) {
+ *result = false;
+ return true;
+ }
+ Rooted<ReadableStreamReader*> unwrappedReader(
+ cx, UnwrapReaderFromStream(cx, unwrappedStream));
+ if (!unwrappedReader) {
+ return false;
+ }
+
+ // Step 3: If ! ReadableStreamDefaultReader(reader) is false, return false.
+ // Step 4: Return true.
+ *result = unwrappedReader->is<ReadableStreamDefaultReader>();
+ return true;
+}