diff --git a/js/src/builtin/streams/WritableStream.cpp b/js/src/builtin/streams/WritableStream.cpp index 905ca3abc621..87403fa893da 100644 --- a/js/src/builtin/streams/WritableStream.cpp +++ b/js/src/builtin/streams/WritableStream.cpp @@ -19,7 +19,7 @@ #include "builtin/streams/MiscellaneousOperations.h" // js::MakeSizeAlgorithmFromSizeFunction, js::ReturnPromiseRejectedWithPendingError, js::ValidateAndNormalizeHighWaterMark #include "builtin/streams/WritableStreamDefaultControllerOperations.h" // js::SetUpWritableStreamDefaultControllerFromUnderlyingSink #include "builtin/streams/WritableStreamDefaultWriter.h" // js::CreateWritableStreamDefaultWriter -#include "builtin/streams/WritableStreamOperations.h" // js::WritableStreamAbort +#include "builtin/streams/WritableStreamOperations.h" // js::WritableStream{Abort,Close{,QueuedOrInFlight}} #include "js/CallArgs.h" // JS::CallArgs{,FromVp} #include "js/Class.h" // JS{Function,Property}Spec, JS_{FS,PS}_END, JSCLASS_PRIVATE_IS_NSISUPPORTS, JSCLASS_HAS_PRIVATE, JS_NULL_CLASS_OPS #include "js/RealmOptions.h" // JS::RealmCreationOptions @@ -42,6 +42,8 @@ using js::ReturnPromiseRejectedWithPendingError; using js::UnwrapAndTypeCheckThis; using js::WritableStream; using js::WritableStreamAbort; +using js::WritableStreamClose; +using js::WritableStreamCloseQueuedOrInFlight; using JS::CallArgs; using JS::CallArgsFromVp; @@ -189,7 +191,7 @@ static bool WritableStream_abort(JSContext* cx, unsigned argc, Value* vp) { // rejected with a TypeError exception. if (unwrappedStream->isLocked()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, - JSMSG_CANT_ABORT_LOCKED_WRITABLESTREAM); + JSMSG_CANT_USE_LOCKED_WRITABLESTREAM, "abort"); return ReturnPromiseRejectedWithPendingError(cx, args); } @@ -205,7 +207,47 @@ static bool WritableStream_abort(JSContext* cx, unsigned argc, Value* vp) { } /** - * Streams spec, 4.2.5.3. getWriter() + * Streams spec, 4.2.5.3. close() + */ +static bool WritableStream_close(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1: If ! IsWritableStream(this) is false, return a promise rejected + // with a TypeError exception. + Rooted unwrappedStream( + cx, UnwrapAndTypeCheckThis(cx, args, "close")); + if (!unwrappedStream) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // Step 2: If ! IsWritableStreamLocked(this) is true, return a promise + // rejected with a TypeError exception. + if (unwrappedStream->isLocked()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_CANT_USE_LOCKED_WRITABLESTREAM, "close"); + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // Step 3: If ! WritableStreamCloseQueuedOrInFlight(this) 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 4: Return ! WritableStreamClose(this). + JSObject* promise = WritableStreamClose(cx, unwrappedStream); + if (!promise) { + return false; + } + + args.rval().setObject(*promise); + return true; +} + +/** + * Streams spec, 4.2.5.4. getWriter() */ static bool WritableStream_getWriter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); @@ -228,6 +270,7 @@ static bool WritableStream_getWriter(JSContext* cx, unsigned argc, Value* vp) { static const JSFunctionSpec WritableStream_methods[] = { JS_FN("abort", WritableStream_abort, 1, 0), + JS_FN("close", WritableStream_close, 0, 0), JS_FN("getWriter", WritableStream_getWriter, 0, 0), JS_FS_END}; static const JSPropertySpec WritableStream_properties[] = { diff --git a/js/src/builtin/streams/WritableStreamOperations.cpp b/js/src/builtin/streams/WritableStreamOperations.cpp index 566dbc804a23..794ff97f8069 100644 --- a/js/src/builtin/streams/WritableStreamOperations.cpp +++ b/js/src/builtin/streams/WritableStreamOperations.cpp @@ -14,10 +14,12 @@ #include // uint32_t #include "jsapi.h" // JS_ReportErrorASCII, JS_SetPrivate +#include "jsfriendapi.h" // js::GetErrorMessage, JSMSG_* #include "builtin/Promise.h" // js::PromiseObject +#include "builtin/streams/MiscellaneousOperations.h" // js::PromiseRejectedWithPendingError #include "builtin/streams/WritableStream.h" // js::WritableStream -#include "builtin/streams/WritableStreamDefaultController.h" // js::WritableStreamDefaultController, js::WritableStream::controller +#include "builtin/streams/WritableStreamDefaultController.h" // js::WritableStreamDefaultController{,Close}, js::WritableStream::controller #include "builtin/streams/WritableStreamDefaultControllerOperations.h" // js::WritableStreamControllerErrorSteps #include "builtin/streams/WritableStreamWriterOperations.h" // js::WritableStreamDefaultWriterEnsureReadyPromiseRejected #include "js/CallArgs.h" // JS::CallArgs{,FromVp} @@ -197,6 +199,78 @@ JSObject* js::WritableStreamAbort(JSContext* cx, return promise; } +/** + * Streams spec, 4.3.7. + * WritableStreamClose ( stream ) + * + * Note: The object (a promise) returned by this function is in the current + * compartment and does not require special wrapping to be put to use. + */ +JSObject* js::WritableStreamClose(JSContext* cx, + Handle unwrappedStream) { + // Step 1: Let state be stream.[[state]]. + // Step 2: 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 3: Assert: state is "writable" or "erroring". + MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring()); + + // Step 4: Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false. + MOZ_ASSERT(!WritableStreamCloseQueuedOrInFlight(unwrappedStream)); + + // Step 5: Let promise be a new promise. + Rooted promise(cx, PromiseObject::createSkippingExecutor(cx)); + if (!promise) { + return nullptr; + } + + // Step 6: Set stream.[[closeRequest]] to promise. + { + AutoRealm ar(cx, unwrappedStream); + Rooted wrappedPromise(cx, promise); + if (!cx->compartment()->wrap(cx, &wrappedPromise)) { + return nullptr; + } + + unwrappedStream->setCloseRequest(promise); + } + + // Step 7: Let writer be stream.[[writer]]. + // Step 8: If writer is not undefined, and stream.[[backpressure]] is true, + // and state is "writable", resolve writer.[[readyPromise]] with + // undefined. + if (unwrappedStream->hasWriter() && unwrappedStream->backpressure() && + unwrappedStream->writable()) { + Rooted unwrappedWriter( + cx, UnwrapWriterFromStream(cx, unwrappedStream)); + if (!unwrappedWriter) { + return nullptr; + } + + if (!ResolveUnwrappedPromiseWithUndefined( + cx, unwrappedWriter->readyPromise())) { + return nullptr; + } + } + + // Step 9: Perform + // ! WritableStreamDefaultControllerClose( + // stream.[[writableStreamController]]). + Rooted unwrappedController( + cx, unwrappedStream->controller()); + if (!WritableStreamDefaultControllerClose(cx, unwrappedController)) { + return nullptr; + } + + // Step 10: Return promise. + return promise; +} + /*** 4.4. Writable stream abstract operations used by controllers ***********/ /** diff --git a/js/src/builtin/streams/WritableStreamOperations.h b/js/src/builtin/streams/WritableStreamOperations.h index 651235a4b15a..149caca4019c 100644 --- a/js/src/builtin/streams/WritableStreamOperations.h +++ b/js/src/builtin/streams/WritableStreamOperations.h @@ -26,6 +26,9 @@ extern JSObject* WritableStreamAbort( JSContext* cx, JS::Handle unwrappedStream, JS::Handle reason); +extern JSObject* WritableStreamClose( + JSContext* cx, JS::Handle unwrappedStream); + extern MOZ_MUST_USE PromiseObject* WritableStreamAddWriteRequest( JSContext* cx, JS::Handle unwrappedStream); diff --git a/js/src/js.msg b/js/src/js.msg index 01981bb22a4e..6938e0b5420d 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -689,7 +689,7 @@ MSG_DEF(JSMSG_WRITABLESTREAMWRITER_NOT_OWNED, 1, JSEXN_TYPEERR, "the MSG_DEF(JSMSG_WRITABLESTREAM_CLOSED_OR_ERRORED, 0, JSEXN_TYPEERR, "writable stream is already closed or errored") MSG_DEF(JSMSG_WRITABLESTREAM_RELEASED_DURING_WRITE, 0, JSEXN_TYPEERR, "writer's lock on the stream was released before writing completed") MSG_DEF(JSMSG_WRITABLESTREAM_WRITE_CLOSING_OR_CLOSED, 0, JSEXN_TYPEERR, "can't write to a stream that's currently closing or already closed") -MSG_DEF(JSMSG_CANT_ABORT_LOCKED_WRITABLESTREAM, 0, JSEXN_TYPEERR, "can't abort a WritableStream that's locked to a writer") +MSG_DEF(JSMSG_CANT_USE_LOCKED_WRITABLESTREAM, 1, JSEXN_TYPEERR, "can't {0} a WritableStream that's locked to a writer") MSG_DEF(JSMSG_WRITABLESTREAM_CLOSE_CLOSING_OR_CLOSED, 0, JSEXN_TYPEERR, "can't close a stream that's currently closing or already closed") MSG_DEF(JSMSG_WRITABLESTREAM_CANT_RELEASE_ALREADY_CLOSED,0, JSEXN_TYPEERR, "writer has already been released and can't be closed") MSG_DEF(JSMSG_WRITABLESTREAM_ALREADY_LOCKED, 0, JSEXN_TYPEERR, "writable stream is already locked by another writer")