summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/streams/WritableStreamWriterOperations.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/builtin/streams/WritableStreamWriterOperations.cpp')
-rw-r--r--js/src/builtin/streams/WritableStreamWriterOperations.cpp446
1 files changed, 446 insertions, 0 deletions
diff --git a/js/src/builtin/streams/WritableStreamWriterOperations.cpp b/js/src/builtin/streams/WritableStreamWriterOperations.cpp
new file mode 100644
index 0000000000..cf8a6d1c84
--- /dev/null
+++ b/js/src/builtin/streams/WritableStreamWriterOperations.cpp
@@ -0,0 +1,446 @@
+/* -*- 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/. */
+
+/* Writable stream writer abstract operations. */
+
+#include "builtin/streams/WritableStreamWriterOperations.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jsapi.h" // JS_ReportErrorNumberASCII, JS_ReportErrorASCII
+
+#include "builtin/streams/MiscellaneousOperations.h" // js::PromiseRejectedWithPendingError
+#include "builtin/streams/WritableStream.h" // js::WritableStream
+#include "builtin/streams/WritableStreamDefaultController.h" // js::WritableStream::controller
+#include "builtin/streams/WritableStreamDefaultControllerOperations.h" // js::WritableStreamDefaultController{Close,GetDesiredSize}
+#include "builtin/streams/WritableStreamDefaultWriter.h" // js::WritableStreamDefaultWriter
+#include "builtin/streams/WritableStreamOperations.h" // js::WritableStream{Abort,CloseQueuedOrInFlight}
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/Promise.h" // JS::PromiseState
+#include "js/Value.h" // JS::Value, JS::{Int32,Null}Value
+#include "vm/Compartment.h" // JS::Compartment
+#include "vm/Interpreter.h" // js::GetAndClearException
+#include "vm/JSContext.h" // JSContext
+#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
+
+#include "builtin/Promise-inl.h" // js::SetSettledPromiseIsHandled
+#include "builtin/streams/MiscellaneousOperations-inl.h" // js::ResolveUnwrappedPromiseWithUndefined
+#include "builtin/streams/WritableStream-inl.h" // js::WritableStream::setCloseRequest
+#include "builtin/streams/WritableStreamDefaultWriter-inl.h" // js::UnwrapStreamFromWriter
+#include "vm/Compartment-inl.h" // js::UnwrapAnd{DowncastObject,TypeCheckThis}
+#include "vm/JSContext-inl.h" // JSContext::check
+#include "vm/Realm-inl.h" // js::AutoRealm
+
+using JS::Handle;
+using JS::Int32Value;
+using JS::MutableHandle;
+using JS::NullValue;
+using JS::NumberValue;
+using JS::Rooted;
+using JS::Value;
+
+using js::AutoRealm;
+using js::PromiseObject;
+using js::UnwrapAndDowncastObject;
+using js::WritableStreamDefaultWriter;
+
+/*** 4.6. Writable stream writer abstract operations ************************/
+
+/**
+ * Streams spec, 4.6.2.
+ * WritableStreamDefaultWriterAbort ( writer, reason )
+ */
+JSObject* js::WritableStreamDefaultWriterAbort(
+ JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter,
+ Handle<Value> reason) {
+ cx->check(reason);
+
+ // Step 1: Let stream be writer.[[ownerWritableStream]].
+ // Step 2: Assert: stream is not undefined.
+ MOZ_ASSERT(unwrappedWriter->hasStream());
+ Rooted<WritableStream*> unwrappedStream(
+ cx, UnwrapStreamFromWriter(cx, unwrappedWriter));
+ if (!unwrappedStream) {
+ return nullptr;
+ }
+
+ // Step 3: Return ! WritableStreamAbort(stream, reason).
+ return WritableStreamAbort(cx, unwrappedStream, reason);
+}
+
+/**
+ * Streams spec, 4.6.3.
+ * WritableStreamDefaultWriterClose ( writer )
+ */
+PromiseObject* js::WritableStreamDefaultWriterClose(
+ JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter) {
+ // Step 1: Let stream be writer.[[ownerWritableStream]].
+ // Step 2: Assert: stream is not undefined.
+ MOZ_ASSERT(unwrappedWriter->hasStream());
+ Rooted<WritableStream*> unwrappedStream(
+ cx, UnwrapStreamFromWriter(cx, unwrappedWriter));
+ if (!unwrappedStream) {
+ return PromiseRejectedWithPendingError(cx);
+ }
+
+ // Step 3: Let state be stream.[[state]].
+ // Step 4: If state is "closed" or "errored", return a promise rejected with a
+ // TypeError exception.
+ if (unwrappedStream->closed() || unwrappedStream->errored()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WRITABLESTREAM_CLOSED_OR_ERRORED);
+ return PromiseRejectedWithPendingError(cx);
+ }
+
+ // Step 5: Assert: state is "writable" or "erroring".
+ MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
+
+ // Step 6: Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false.
+ MOZ_ASSERT(!WritableStreamCloseQueuedOrInFlight(unwrappedStream));
+
+ // Step 7: Let promise be a new promise.
+ Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
+ if (!promise) {
+ return nullptr;
+ }
+
+ // Step 8: Set stream.[[closeRequest]] to promise.
+ {
+ AutoRealm ar(cx, unwrappedStream);
+ Rooted<JSObject*> closeRequest(cx, promise);
+ if (!cx->compartment()->wrap(cx, &closeRequest)) {
+ return nullptr;
+ }
+
+ unwrappedStream->setCloseRequest(closeRequest);
+ }
+
+ // Step 9: If stream.[[backpressure]] is true and state is "writable", resolve
+ // writer.[[readyPromise]] with undefined.
+ if (unwrappedStream->backpressure() && unwrappedStream->writable()) {
+ if (!ResolveUnwrappedPromiseWithUndefined(
+ cx, unwrappedWriter->readyPromise())) {
+ return nullptr;
+ }
+ }
+
+ // Step 10: Perform
+ // ! WritableStreamDefaultControllerClose(
+ // stream.[[writableStreamController]]).
+ Rooted<WritableStreamDefaultController*> unwrappedController(
+ cx, unwrappedStream->controller());
+ if (!WritableStreamDefaultControllerClose(cx, unwrappedController)) {
+ return nullptr;
+ }
+
+ // Step 11: Return promise.
+ return promise;
+}
+
+/**
+ * Streams spec.
+ * WritableStreamDefaultWriterCloseWithErrorPropagation ( writer )
+ */
+PromiseObject* js::WritableStreamDefaultWriterCloseWithErrorPropagation(
+ JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter) {
+ // Step 1: Let stream be writer.[[ownerWritableStream]].
+ // Step 2: Assert: stream is not undefined.
+ WritableStream* unwrappedStream = UnwrapStreamFromWriter(cx, unwrappedWriter);
+ if (!unwrappedStream) {
+ return nullptr;
+ }
+
+ // Step 3: Let state be stream.[[state]].
+ // Step 4: If ! WritableStreamCloseQueuedOrInFlight(stream) is true or state
+ // is "closed", return a promise resolved with undefined.
+ if (WritableStreamCloseQueuedOrInFlight(unwrappedStream) ||
+ unwrappedStream->closed()) {
+ return PromiseResolvedWithUndefined(cx);
+ }
+
+ // Step 5: If 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 6: Assert: state is "writable" or "erroring".
+ MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
+
+ // Step 7: Return ! WritableStreamDefaultWriterClose(writer).
+ return WritableStreamDefaultWriterClose(cx, unwrappedWriter);
+}
+
+using GetField = JSObject* (WritableStreamDefaultWriter::*)() const;
+using SetField = void (WritableStreamDefaultWriter::*)(JSObject*);
+
+static bool EnsurePromiseRejected(
+ JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter,
+ GetField getField, SetField setField, Handle<Value> error) {
+ cx->check(error);
+
+ Rooted<PromiseObject*> unwrappedPromise(
+ cx, UnwrapAndDowncastObject<PromiseObject>(
+ cx, (unwrappedWriter->*getField)()));
+ if (!unwrappedPromise) {
+ return false;
+ }
+
+ // 4.6.{5,6} step 1: If writer.[[<field>]].[[PromiseState]] is "pending",
+ // reject writer.[[<field>]] with error.
+ if (unwrappedPromise->state() == JS::PromiseState::Pending) {
+ if (!RejectUnwrappedPromiseWithError(cx, unwrappedPromise, error)) {
+ return false;
+ }
+ } else {
+ // 4.6.{5,6} step 2: Otherwise, set writer.[[<field>]] to a promise rejected
+ // with error.
+ Rooted<JSObject*> rejectedWithError(
+ cx, PromiseObject::unforgeableReject(cx, error));
+ if (!rejectedWithError) {
+ return false;
+ }
+
+ {
+ AutoRealm ar(cx, unwrappedWriter);
+ if (!cx->compartment()->wrap(cx, &rejectedWithError)) {
+ return false;
+ }
+ (unwrappedWriter->*setField)(rejectedWithError);
+ }
+
+ // Directly-unobservable rejected promises aren't collapsed like resolved
+ // promises, and this promise is created in the current realm, so it's
+ // always an actual Promise.
+ unwrappedPromise = &rejectedWithError->as<PromiseObject>();
+ }
+
+ // 4.6.{5,6} step 3: Set writer.[[<field>]].[[PromiseIsHandled]] to true.
+ js::SetSettledPromiseIsHandled(cx, unwrappedPromise);
+ return true;
+}
+
+/**
+ * Streams spec, 4.6.5.
+ * WritableStreamDefaultWriterEnsureClosedPromiseRejected( writer, error )
+ */
+MOZ_MUST_USE bool js::WritableStreamDefaultWriterEnsureClosedPromiseRejected(
+ JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter,
+ Handle<Value> error) {
+ return EnsurePromiseRejected(
+ cx, unwrappedWriter, &WritableStreamDefaultWriter::closedPromise,
+ &WritableStreamDefaultWriter::setClosedPromise, error);
+}
+
+/**
+ * Streams spec, 4.6.6.
+ * WritableStreamDefaultWriterEnsureReadyPromiseRejected( writer, error )
+ */
+MOZ_MUST_USE bool js::WritableStreamDefaultWriterEnsureReadyPromiseRejected(
+ JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter,
+ Handle<Value> error) {
+ return EnsurePromiseRejected(
+ cx, unwrappedWriter, &WritableStreamDefaultWriter::readyPromise,
+ &WritableStreamDefaultWriter::setReadyPromise, error);
+}
+
+/**
+ * Streams spec, 4.6.7.
+ * WritableStreamDefaultWriterGetDesiredSize ( writer )
+ */
+bool js::WritableStreamDefaultWriterGetDesiredSize(
+ JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter,
+ MutableHandle<Value> size) {
+ // Step 1: Let stream be writer.[[ownerWritableStream]].
+ const WritableStream* unwrappedStream =
+ UnwrapStreamFromWriter(cx, unwrappedWriter);
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ // Step 2: Let state be stream.[[state]].
+ // Step 3: If state is "errored" or "erroring", return null.
+ if (unwrappedStream->errored() || unwrappedStream->erroring()) {
+ size.setNull();
+ }
+ // Step 4: If state is "closed", return 0.
+ else if (unwrappedStream->closed()) {
+ size.setInt32(0);
+ }
+ // Step 5: Return
+ // ! WritableStreamDefaultControllerGetDesiredSize(
+ // stream.[[writableStreamController]]).
+ else {
+ size.setNumber(WritableStreamDefaultControllerGetDesiredSize(
+ unwrappedStream->controller()));
+ }
+
+ return true;
+}
+
+/**
+ * Streams spec, 4.6.8.
+ * WritableStreamDefaultWriterRelease ( writer )
+ */
+bool js::WritableStreamDefaultWriterRelease(
+ JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter) {
+ // Step 1: Let stream be writer.[[ownerWritableStream]].
+ // Step 2: Assert: stream is not undefined.
+ MOZ_ASSERT(unwrappedWriter->hasStream());
+ Rooted<WritableStream*> unwrappedStream(
+ cx, UnwrapStreamFromWriter(cx, unwrappedWriter));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ // Step 3: Assert: stream.[[writer]] is writer.
+#ifdef DEBUG
+ {
+ WritableStreamDefaultWriter* unwrappedStreamWriter =
+ UnwrapWriterFromStream(cx, unwrappedStream);
+ if (!unwrappedStreamWriter) {
+ return false;
+ }
+
+ MOZ_ASSERT(unwrappedStreamWriter == unwrappedWriter);
+ }
+#endif
+
+ // Step 4: Let releasedError be a new TypeError.
+ Rooted<Value> releasedError(cx);
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WRITABLESTREAM_CANT_RELEASE_ALREADY_CLOSED);
+ if (!cx->isExceptionPending() || !GetAndClearException(cx, &releasedError)) {
+ return false;
+ }
+
+ // Step 5: Perform
+ // ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(
+ // writer, releasedError).
+ if (!WritableStreamDefaultWriterEnsureReadyPromiseRejected(
+ cx, unwrappedWriter, releasedError)) {
+ return false;
+ }
+
+ // Step 6: Perform
+ // ! WritableStreamDefaultWriterEnsureClosedPromiseRejected(
+ // writer, releasedError).
+ if (!WritableStreamDefaultWriterEnsureClosedPromiseRejected(
+ cx, unwrappedWriter, releasedError)) {
+ return false;
+ }
+
+ // Step 7: Set stream.[[writer]] to undefined.
+ unwrappedStream->clearWriter();
+
+ // Step 8: Set writer.[[ownerWritableStream]] to undefined.
+ unwrappedWriter->clearStream();
+ return true;
+}
+
+/**
+ * Streams spec, 4.6.9.
+ * WritableStreamDefaultWriterWrite ( writer, chunk )
+ */
+PromiseObject* js::WritableStreamDefaultWriterWrite(
+ JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter,
+ Handle<Value> chunk) {
+ cx->check(chunk);
+
+ // Step 1: Let stream be writer.[[ownerWritableStream]].
+ // Step 2: Assert: stream is not undefined.
+ MOZ_ASSERT(unwrappedWriter->hasStream());
+ Rooted<WritableStream*> unwrappedStream(
+ cx, UnwrapStreamFromWriter(cx, unwrappedWriter));
+ if (!unwrappedStream) {
+ return nullptr;
+ }
+
+ // Step 3: Let controller be stream.[[writableStreamController]].
+ Rooted<WritableStreamDefaultController*> unwrappedController(
+ cx, unwrappedStream->controller());
+
+ // Step 4: Let chunkSize be
+ // ! WritableStreamDefaultControllerGetChunkSize(controller, chunk).
+ Rooted<Value> chunkSize(cx);
+ if (!WritableStreamDefaultControllerGetChunkSize(cx, unwrappedController,
+ chunk, &chunkSize)) {
+ return nullptr;
+ }
+ cx->check(chunkSize);
+
+ // Step 5: If stream is not equal to writer.[[ownerWritableStream]], return a
+ // promise rejected with a TypeError exception.
+ // (This is just an obscure way of saying "If step 4 caused writer's lock on
+ // stream to be released", or concretely, "If writer.[[ownerWritableStream]]
+ // is [now, newly] undefined".)
+ if (!unwrappedWriter->hasStream()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WRITABLESTREAM_RELEASED_DURING_WRITE);
+ return PromiseRejectedWithPendingError(cx);
+ }
+
+ auto RejectWithStoredError =
+ [](JSContext* cx,
+ Handle<WritableStream*> unwrappedStream) -> PromiseObject* {
+ Rooted<Value> storedError(cx, unwrappedStream->storedError());
+ if (!cx->compartment()->wrap(cx, &storedError)) {
+ return nullptr;
+ }
+
+ return PromiseObject::unforgeableReject(cx, storedError);
+ };
+
+ // Step 6: Let state be stream.[[state]].
+ // Step 7: If state is "errored", return a promise rejected with
+ // stream.[[storedError]].
+ if (unwrappedStream->errored()) {
+ return RejectWithStoredError(cx, unwrappedStream);
+ }
+
+ // Step 8: If ! WritableStreamCloseQueuedOrInFlight(stream) is true or state
+ // is "closed", return a promise rejected with a TypeError exception
+ // indicating that the stream is closing or closed.
+ if (WritableStreamCloseQueuedOrInFlight(unwrappedStream) ||
+ unwrappedStream->closed()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WRITABLESTREAM_WRITE_CLOSING_OR_CLOSED);
+ return PromiseRejectedWithPendingError(cx);
+ }
+
+ // Step 9: If state is "erroring", return a promise rejected with
+ // stream.[[storedError]].
+ if (unwrappedStream->erroring()) {
+ return RejectWithStoredError(cx, unwrappedStream);
+ }
+
+ // Step 10: Assert: state is "writable".
+ MOZ_ASSERT(unwrappedStream->writable());
+
+ // Step 11: Let promise be ! WritableStreamAddWriteRequest(stream).
+ Rooted<PromiseObject*> promise(
+ cx, WritableStreamAddWriteRequest(cx, unwrappedStream));
+ if (!promise) {
+ return nullptr;
+ }
+
+ // Step 12: Perform
+ // ! WritableStreamDefaultControllerWrite(controller, chunk,
+ // chunkSize).
+ if (!WritableStreamDefaultControllerWrite(cx, unwrappedController, chunk,
+ chunkSize)) {
+ return nullptr;
+ }
+
+ // Step 13: Return promise.
+ return promise;
+}