diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/builtin/streams/WritableStreamDefaultWriter.cpp | 529 |
1 files changed, 529 insertions, 0 deletions
diff --git a/js/src/builtin/streams/WritableStreamDefaultWriter.cpp b/js/src/builtin/streams/WritableStreamDefaultWriter.cpp new file mode 100644 index 0000000000..c45948c64d --- /dev/null +++ b/js/src/builtin/streams/WritableStreamDefaultWriter.cpp @@ -0,0 +1,529 @@ +/* -*- 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/. */ + +/* Class WritableStreamDefaultWriter. */ + +#include "builtin/streams/WritableStreamDefaultWriter-inl.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_MUST_USE + +#include "jsapi.h" // JS_ReportErrorASCII, JS_ReportErrorNumberASCII + +#include "builtin/streams/ClassSpecMacro.h" // JS_STREAMS_CLASS_SPEC +#include "builtin/streams/MiscellaneousOperations.h" // js::ReturnPromiseRejectedWithPendingError +#include "builtin/streams/WritableStream.h" // js::WritableStream +#include "builtin/streams/WritableStreamOperations.h" // js::WritableStreamCloseQueuedOrInFlight +#include "builtin/streams/WritableStreamWriterOperations.h" // js::WritableStreamDefaultWriter{Abort,GetDesiredSize,Release,Write} +#include "js/CallArgs.h" // JS::CallArgs{,FromVp} +#include "js/Class.h" // js::ClassSpec, JS_NULL_CLASS_OPS +#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* +#include "js/PropertySpec.h" // JS{Function,Property}Spec, JS_{FS,PS}_END, JS_{FN,PSG} +#include "js/RootingAPI.h" // JS::Handle +#include "js/Value.h" // JS::Value +#include "vm/Compartment.h" // JS::Compartment +#include "vm/JSContext.h" // JSContext +#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined + +#include "builtin/Promise-inl.h" // js::SetSettledPromiseIsHandled +#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::UnwrapAndTypeCheck{Argument,This} +#include "vm/JSObject-inl.h" // js::NewObjectWithClassProto +#include "vm/NativeObject-inl.h" // js::ThrowIfNotConstructing +#include "vm/Realm-inl.h" // js::AutoRealm + +using JS::CallArgs; +using JS::CallArgsFromVp; +using JS::Handle; +using JS::Rooted; +using JS::Value; + +using js::ClassSpec; +using js::GetErrorMessage; +using js::PromiseObject; +using js::ReturnPromiseRejectedWithPendingError; +using js::UnwrapAndTypeCheckArgument; +using js::UnwrapAndTypeCheckThis; +using js::WritableStream; +using js::WritableStreamCloseQueuedOrInFlight; +using js::WritableStreamDefaultWriter; +using js::WritableStreamDefaultWriterGetDesiredSize; +using js::WritableStreamDefaultWriterRelease; +using js::WritableStreamDefaultWriterWrite; + +/*** 4.5. Class WritableStreamDefaultWriter *********************************/ + +/** + * Stream spec, 4.5.3. new WritableStreamDefaultWriter(stream) + * Steps 3-9. + */ +MOZ_MUST_USE WritableStreamDefaultWriter* js::CreateWritableStreamDefaultWriter( + JSContext* cx, Handle<WritableStream*> unwrappedStream, + Handle<JSObject*> proto /* = nullptr */) { + Rooted<WritableStreamDefaultWriter*> writer( + cx, NewObjectWithClassProto<WritableStreamDefaultWriter>(cx, proto)); + if (!writer) { + return nullptr; + } + + // Step 3: Set this.[[ownerWritableStream]] to stream. + { + Rooted<JSObject*> stream(cx, unwrappedStream); + if (!cx->compartment()->wrap(cx, &stream)) { + return nullptr; + } + writer->setStream(stream); + } + + // Step 4 is moved to the end. + + // Step 5: Let state be stream.[[state]]. + // Step 6: If state is "writable", + if (unwrappedStream->writable()) { + // Step 6.a: If ! WritableStreamCloseQueuedOrInFlight(stream) is false and + // stream.[[backpressure]] is true, set this.[[readyPromise]] to a + // new promise. + PromiseObject* promise; + if (!WritableStreamCloseQueuedOrInFlight(unwrappedStream) && + unwrappedStream->backpressure()) { + promise = PromiseObject::createSkippingExecutor(cx); + } + // Step 6.b: Otherwise, set this.[[readyPromise]] to a promise resolved with + // undefined. + else { + promise = PromiseResolvedWithUndefined(cx); + } + if (!promise) { + return nullptr; + } + writer->setReadyPromise(promise); + + // Step 6.c: Set this.[[closedPromise]] to a new promise. + promise = PromiseObject::createSkippingExecutor(cx); + if (!promise) { + return nullptr; + } + + writer->setClosedPromise(promise); + } + // Step 8: Otherwise, if state is "closed", + else if (unwrappedStream->closed()) { + // Step 8.a: Set this.[[readyPromise]] to a promise resolved with undefined. + PromiseObject* readyPromise = PromiseResolvedWithUndefined(cx); + if (!readyPromise) { + return nullptr; + } + + writer->setReadyPromise(readyPromise); + + // Step 8.b: Set this.[[closedPromise]] to a promise resolved with + // undefined. + PromiseObject* closedPromise = PromiseResolvedWithUndefined(cx); + if (!closedPromise) { + return nullptr; + } + + writer->setClosedPromise(closedPromise); + } else { + // Wrap stream.[[StoredError]] just once for either step 7 or step 9. + Rooted<Value> storedError(cx, unwrappedStream->storedError()); + if (!cx->compartment()->wrap(cx, &storedError)) { + return nullptr; + } + + // Step 7: Otherwise, if state is "erroring", + if (unwrappedStream->erroring()) { + // Step 7.a: Set this.[[readyPromise]] to a promise rejected with + // stream.[[storedError]]. + Rooted<JSObject*> promise( + cx, PromiseObject::unforgeableReject(cx, storedError)); + if (!promise) { + return nullptr; + } + + writer->setReadyPromise(promise); + + // Step 7.b: Set this.[[readyPromise]].[[PromiseIsHandled]] to true. + js::SetSettledPromiseIsHandled(cx, promise.as<PromiseObject>()); + + // Step 7.c: Set this.[[closedPromise]] to a new promise. + JSObject* closedPromise = PromiseObject::createSkippingExecutor(cx); + if (!closedPromise) { + return nullptr; + } + + writer->setClosedPromise(closedPromise); + } + // Step 9: Otherwise, + else { + // Step 9.a: Assert: state is "errored". + MOZ_ASSERT(unwrappedStream->errored()); + + Rooted<JSObject*> promise(cx); + + // Step 9.b: Let storedError be stream.[[storedError]]. + // Step 9.c: Set this.[[readyPromise]] to a promise rejected with + // storedError. + promise = PromiseObject::unforgeableReject(cx, storedError); + if (!promise) { + return nullptr; + } + + writer->setReadyPromise(promise); + + // Step 9.d: Set this.[[readyPromise]].[[PromiseIsHandled]] to true. + js::SetSettledPromiseIsHandled(cx, promise.as<PromiseObject>()); + + // Step 9.e: Set this.[[closedPromise]] to a promise rejected with + // storedError. + promise = PromiseObject::unforgeableReject(cx, storedError); + if (!promise) { + return nullptr; + } + + writer->setClosedPromise(promise); + + // Step 9.f: Set this.[[closedPromise]].[[PromiseIsHandled]] to true. + js::SetSettledPromiseIsHandled(cx, promise.as<PromiseObject>()); + } + } + + // Step 4 (reordered): Set stream.[[writer]] to this. + // Doing this last prevents a partially-initialized writer from being attached + // to the stream (and possibly left there on OOM). + { + AutoRealm ar(cx, unwrappedStream); + Rooted<JSObject*> wrappedWriter(cx, writer); + if (!cx->compartment()->wrap(cx, &wrappedWriter)) { + return nullptr; + } + unwrappedStream->setWriter(wrappedWriter); + } + + return writer; +} + +/** + * Streams spec, 4.5.3. + * new WritableStreamDefaultWriter(stream) + */ +bool WritableStreamDefaultWriter::constructor(JSContext* cx, unsigned argc, + Value* vp) { + MOZ_ASSERT(cx->realm()->creationOptions().getWritableStreamsEnabled(), + "WritableStream should be enabled in this realm if we reach here"); + + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "WritableStreamDefaultWriter")) { + return false; + } + + // Step 1: If ! IsWritableStream(stream) is false, throw a TypeError + // exception. + Rooted<WritableStream*> unwrappedStream( + cx, UnwrapAndTypeCheckArgument<WritableStream>( + cx, args, "WritableStreamDefaultWriter constructor", 0)); + if (!unwrappedStream) { + return false; + } + + // Step 2: If ! IsWritableStreamLocked(stream) is true, throw a TypeError + // exception. + if (unwrappedStream->isLocked()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_WRITABLESTREAM_ALREADY_LOCKED); + return false; + } + + // Implicit in the spec: Find the prototype object to use. + Rooted<JSObject*> proto(cx); + if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Null, &proto)) { + return false; + } + + // Steps 3-9. + Rooted<WritableStreamDefaultWriter*> writer( + cx, CreateWritableStreamDefaultWriter(cx, unwrappedStream, proto)); + if (!writer) { + return false; + } + + args.rval().setObject(*writer); + return true; +} + +/** + * Streams spec, 4.5.4.1. get closed + */ +static MOZ_MUST_USE bool WritableStreamDefaultWriter_closed(JSContext* cx, + unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1: If ! IsWritableStreamDefaultWriter(this) is false, return a promise + // rejected with a TypeError exception. + Rooted<WritableStreamDefaultWriter*> unwrappedWriter( + cx, UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(cx, args, + "get closed")); + if (!unwrappedWriter) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // Step 2: Return this.[[closedPromise]]. + Rooted<JSObject*> closedPromise(cx, unwrappedWriter->closedPromise()); + if (!cx->compartment()->wrap(cx, &closedPromise)) { + return false; + } + + args.rval().setObject(*closedPromise); + return true; +} + +/** + * Streams spec, 4.5.4.2. get desiredSize + */ +static MOZ_MUST_USE bool WritableStreamDefaultWriter_desiredSize(JSContext* cx, + unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1: If ! IsWritableStreamDefaultWriter(this) is false, throw a + // TypeError exception. + Rooted<WritableStreamDefaultWriter*> unwrappedWriter( + cx, UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>( + cx, args, "get desiredSize")); + if (!unwrappedWriter) { + return false; + } + + // Step 2: If this.[[ownerWritableStream]] is undefined, throw a TypeError + // exception. + if (!unwrappedWriter->hasStream()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_WRITABLESTREAMWRITER_NOT_OWNED, + "get desiredSize"); + return false; + } + + // Step 3: Return ! WritableStreamDefaultWriterGetDesiredSize(this). + if (!WritableStreamDefaultWriterGetDesiredSize(cx, unwrappedWriter, + args.rval())) { + return false; + } + + MOZ_ASSERT(args.rval().isNull() || args.rval().isNumber(), + "expected a type that'll never require wrapping"); + return true; +} + +/** + * Streams spec, 4.5.4.3. get ready + */ +static MOZ_MUST_USE bool WritableStreamDefaultWriter_ready(JSContext* cx, + unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1: If ! IsWritableStreamDefaultWriter(this) is false, return a promise + // rejected with a TypeError exception. + Rooted<WritableStreamDefaultWriter*> unwrappedWriter( + cx, UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(cx, args, + "get ready")); + if (!unwrappedWriter) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // Step 2: Return this.[[readyPromise]]. + Rooted<JSObject*> readyPromise(cx, unwrappedWriter->readyPromise()); + if (!cx->compartment()->wrap(cx, &readyPromise)) { + return false; + } + + args.rval().setObject(*readyPromise); + return true; +} + +/** + * Streams spec, 4.5.4.4. abort(reason) + */ +static MOZ_MUST_USE bool WritableStreamDefaultWriter_abort(JSContext* cx, + unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1: If ! IsWritableStreamDefaultWriter(this) is false, return a promise + // rejected with a TypeError exception. + Rooted<WritableStreamDefaultWriter*> unwrappedWriter( + cx, + UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(cx, args, "abort")); + if (!unwrappedWriter) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // Step 2: If this.[[ownerWritableStream]] is undefined, return a promise + // rejected with a TypeError exception. + if (!unwrappedWriter->hasStream()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_WRITABLESTREAMWRITER_NOT_OWNED, "abort"); + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // Step 3: Return ! WritableStreamDefaultWriterAbort(this, reason). + JSObject* promise = + WritableStreamDefaultWriterAbort(cx, unwrappedWriter, args.get(0)); + if (!promise) { + return false; + } + cx->check(promise); + + args.rval().setObject(*promise); + return true; +} + +/** + * Streams spec, 4.5.4.5. close() + */ +static MOZ_MUST_USE bool WritableStreamDefaultWriter_close(JSContext* cx, + unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1: If ! IsWritableStreamDefaultWriter(this) is false, return a promise + // rejected with a TypeError exception. + Rooted<WritableStreamDefaultWriter*> unwrappedWriter( + cx, + UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(cx, args, "close")); + if (!unwrappedWriter) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // Step 2: Let stream be this.[[ownerWritableStream]]. + // Step 3: If stream is undefined, return a promise rejected with a TypeError + // exception. + if (!unwrappedWriter->hasStream()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_WRITABLESTREAMWRITER_NOT_OWNED, "write"); + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + WritableStream* unwrappedStream = UnwrapStreamFromWriter(cx, unwrappedWriter); + if (!unwrappedStream) { + return false; + } + + // Step 4: If ! WritableStreamCloseQueuedOrInFlight(stream) is true, return a + // promise rejected with a TypeError exception. + if (WritableStreamCloseQueuedOrInFlight(unwrappedStream)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_WRITABLESTREAM_CLOSE_CLOSING_OR_CLOSED); + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // Step 5: Return ! WritableStreamDefaultWriterClose(this). + JSObject* promise = WritableStreamDefaultWriterClose(cx, unwrappedWriter); + if (!promise) { + return false; + } + cx->check(promise); + + args.rval().setObject(*promise); + return true; +} + +/** + * Streams spec, 4.5.4.6. releaseLock() + */ +static MOZ_MUST_USE bool WritableStreamDefaultWriter_releaseLock(JSContext* cx, + unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1: If ! IsWritableStreamDefaultWriter(this) is false, return a promise + // rejected with a TypeError exception. + Rooted<WritableStreamDefaultWriter*> unwrappedWriter( + cx, + UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(cx, args, "close")); + if (!unwrappedWriter) { + return false; + } + + // Step 2: Let stream be this.[[ownerWritableStream]]. + // Step 3: If stream is undefined, return. + if (!unwrappedWriter->hasStream()) { + args.rval().setUndefined(); + return true; + } + + // Step 4: Assert: stream.[[writer]] is not undefined. +#ifdef DEBUG + { + WritableStream* unwrappedStream = + UnwrapStreamFromWriter(cx, unwrappedWriter); + if (!unwrappedStream) { + return false; + } + MOZ_ASSERT(unwrappedStream->hasWriter()); + } +#endif + + // Step 5: Perform ! WritableStreamDefaultWriterRelease(this). + if (!WritableStreamDefaultWriterRelease(cx, unwrappedWriter)) { + return false; + } + + args.rval().setUndefined(); + return true; +} + +/** + * Streams spec, 4.5.4.7. write(chunk) + */ +static MOZ_MUST_USE bool WritableStreamDefaultWriter_write(JSContext* cx, + unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1: If ! IsWritableStreamDefaultWriter(this) is false, return a promise + // rejected with a TypeError exception. + Rooted<WritableStreamDefaultWriter*> unwrappedWriter( + cx, + UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(cx, args, "write")); + if (!unwrappedWriter) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // Step 2: If this.[[ownerWritableStream]] is undefined, return a promise + // rejected with a TypeError exception. + if (!unwrappedWriter->hasStream()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_WRITABLESTREAMWRITER_NOT_OWNED, "write"); + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // Step 3: Return this.[[readyPromise]]. + PromiseObject* promise = + WritableStreamDefaultWriterWrite(cx, unwrappedWriter, args.get(0)); + if (!promise) { + return false; + } + cx->check(promise); + + args.rval().setObject(*promise); + return true; +} + +static const JSPropertySpec WritableStreamDefaultWriter_properties[] = { + JS_PSG("closed", WritableStreamDefaultWriter_closed, 0), + JS_PSG("desiredSize", WritableStreamDefaultWriter_desiredSize, 0), + JS_PSG("ready", WritableStreamDefaultWriter_ready, 0), JS_PS_END}; + +static const JSFunctionSpec WritableStreamDefaultWriter_methods[] = { + JS_FN("abort", WritableStreamDefaultWriter_abort, 1, 0), + JS_FN("close", WritableStreamDefaultWriter_close, 0, 0), + JS_FN("releaseLock", WritableStreamDefaultWriter_releaseLock, 0, 0), + JS_FN("write", WritableStreamDefaultWriter_write, 1, 0), JS_FS_END}; + +JS_STREAMS_CLASS_SPEC(WritableStreamDefaultWriter, 1, SlotCount, + ClassSpec::DontDefineConstructor, 0, JS_NULL_CLASS_OPS); |