diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/builtin/streams/WritableStreamDefaultControllerOperations.cpp | 1014 |
1 files changed, 1014 insertions, 0 deletions
diff --git a/js/src/builtin/streams/WritableStreamDefaultControllerOperations.cpp b/js/src/builtin/streams/WritableStreamDefaultControllerOperations.cpp new file mode 100644 index 0000000000..704d4e4dac --- /dev/null +++ b/js/src/builtin/streams/WritableStreamDefaultControllerOperations.cpp @@ -0,0 +1,1014 @@ +/* -*- 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 default controller abstract operations. */ + +#include "builtin/streams/WritableStreamDefaultControllerOperations.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_MUST_USE + +#include "jsapi.h" // JS_ReportErrorASCII + +#include "builtin/streams/MiscellaneousOperations.h" // js::CreateAlgorithmFromUnderlyingMethod, js::InvokeOrNoop +#include "builtin/streams/QueueWithSizes.h" // js::{EnqueueValueWithSize,QueueIsEmpty,ResetQueue} +#include "builtin/streams/WritableStream.h" // js::WritableStream +#include "builtin/streams/WritableStreamDefaultController.h" // js::WritableStreamDefaultController +#include "builtin/streams/WritableStreamOperations.h" // js::WritableStream{CloseQueuedOrInFlight,DealWithRejection,{Start,Finish}Erroring,UpdateBackpressure,Mark{Close,FirstWrite}RequestInFlight} +#include "js/CallArgs.h" // JS::CallArgs{,FromVp} +#include "js/Promise.h" // JS::AddPromiseReactions +#include "js/RootingAPI.h" // JS::Handle, JS::Rooted +#include "js/Value.h" // JS::{,Int32,Magic,Object}Value, JS::UndefinedHandleValue, JS_WRITABLESTREAM_CLOSE_RECORD +#include "vm/Compartment.h" // JS::Compartment +#include "vm/JSContext.h" // JSContext +#include "vm/JSObject.h" // JSObject +#include "vm/List.h" // js::ListObject +#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined +#include "vm/Runtime.h" // JSAtomState + +#include "builtin/HandlerFunction-inl.h" // js::TargetFromHandler +#include "builtin/streams/MiscellaneousOperations-inl.h" // js::PromiseCall +#include "builtin/streams/QueueWithSizes-inl.h" // js::PeekQueueValue +#include "vm/Compartment-inl.h" // JS::Compartment::wrap +#include "vm/JSContext-inl.h" // JSContext::check +#include "vm/JSObject-inl.h" // js::IsCallable, js::NewBuiltinClassInstance, js::NewObjectWithClassProto +#include "vm/Realm-inl.h" // js::AutoRealm + +using JS::CallArgs; +using JS::CallArgsFromVp; +using JS::Handle; +using JS::Int32Value; +using JS::MagicValue; +using JS::ObjectValue; +using JS::Rooted; +using JS::UndefinedHandleValue; +using JS::Value; + +using js::IsCallable; +using js::ListObject; +using js::NewHandler; +using js::PeekQueueValue; +using js::PromiseObject; +using js::PromiseResolvedWithUndefined; +using js::TargetFromHandler; +using js::WritableStream; +using js::WritableStreamCloseQueuedOrInFlight; +using js::WritableStreamDefaultController; +using js::WritableStreamFinishErroring; +using js::WritableStreamMarkCloseRequestInFlight; +using js::WritableStreamMarkFirstWriteRequestInFlight; +using js::WritableStreamUpdateBackpressure; + +/*** 4.7. Writable stream default controller internal methods ***************/ + +/** + * Streams spec, 4.7.5.1. + * [[AbortSteps]]( reason ) + */ +JSObject* js::WritableStreamControllerAbortSteps( + JSContext* cx, Handle<WritableStreamDefaultController*> unwrappedController, + Handle<Value> reason) { + cx->check(reason); + + // Step 1: Let result be the result of performing this.[[abortAlgorithm]], + // passing reason. + // CreateAlgorithmFromUnderlyingMethod(underlyingSink, "abort", 1, « ») + Rooted<Value> unwrappedAbortMethod(cx, unwrappedController->abortMethod()); + Rooted<JSObject*> result(cx); + if (unwrappedAbortMethod.isUndefined()) { + // CreateAlgorithmFromUnderlyingMethod step 7. + result = PromiseResolvedWithUndefined(cx); + if (!result) { + return nullptr; + } + } else { + // CreateAlgorithmFromUnderlyingMethod step 6.c.i-ii. + { + AutoRealm ar(cx, unwrappedController); + cx->check(unwrappedAbortMethod); + + Rooted<Value> underlyingSink(cx, unwrappedController->underlyingSink()); + cx->check(underlyingSink); + + Rooted<Value> wrappedReason(cx, reason); + if (!cx->compartment()->wrap(cx, &wrappedReason)) { + return nullptr; + } + + result = + PromiseCall(cx, unwrappedAbortMethod, underlyingSink, wrappedReason); + if (!result) { + return nullptr; + } + } + if (!cx->compartment()->wrap(cx, &result)) { + return nullptr; + } + } + + // Step 2: Perform ! WritableStreamDefaultControllerClearAlgorithms(this). + WritableStreamDefaultControllerClearAlgorithms(unwrappedController); + + // Step 3: Return result. + return result; +} + +/** + * Streams spec, 4.7.5.2. + * [[ErrorSteps]]() + */ +bool js::WritableStreamControllerErrorSteps( + JSContext* cx, + Handle<WritableStreamDefaultController*> unwrappedController) { + // Step 1: Perform ! ResetQueue(this). + return ResetQueue(cx, unwrappedController); +} + +/*** 4.8. Writable stream default controller abstract operations ************/ + +static MOZ_MUST_USE bool WritableStreamDefaultControllerAdvanceQueueIfNeeded( + JSContext* cx, + Handle<WritableStreamDefaultController*> unwrappedController); + +/** + * Streams spec, 4.8.2. SetUpWritableStreamDefaultController, step 16: + * Upon fulfillment of startPromise, [...] + */ +bool js::WritableStreamControllerStartHandler(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + Rooted<WritableStreamDefaultController*> unwrappedController( + cx, TargetFromHandler<WritableStreamDefaultController>(args)); + + // Step a: Assert: stream.[[state]] is "writable" or "erroring". +#ifdef DEBUG + const auto* unwrappedStream = unwrappedController->stream(); + MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring()); +#endif + + // Step b: Set controller.[[started]] to true. + unwrappedController->setStarted(); + + // Step c: Perform + // ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller). + if (!WritableStreamDefaultControllerAdvanceQueueIfNeeded( + cx, unwrappedController)) { + return false; + } + + args.rval().setUndefined(); + return true; +} + +/** + * Streams spec, 4.8.2. SetUpWritableStreamDefaultController, step 17: + * Upon rejection of startPromise with reason r, [...] + */ +bool js::WritableStreamControllerStartFailedHandler(JSContext* cx, + unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + Rooted<WritableStreamDefaultController*> unwrappedController( + cx, TargetFromHandler<WritableStreamDefaultController>(args)); + + Rooted<WritableStream*> unwrappedStream(cx, unwrappedController->stream()); + + // Step a: Assert: stream.[[state]] is "writable" or "erroring". + MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring()); + + // Step b: Set controller.[[started]] to true. + unwrappedController->setStarted(); + + // Step c: Perform ! WritableStreamDealWithRejection(stream, r). + if (!WritableStreamDealWithRejection(cx, unwrappedStream, args.get(0))) { + return false; + } + + args.rval().setUndefined(); + return true; +} + +/** + * Streams spec, 4.8.2. + * SetUpWritableStreamDefaultController(stream, controller, + * startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, + * highWaterMark, sizeAlgorithm ) + * + * The standard algorithm takes a `controller` argument which must be a new, + * blank object. This implementation creates a new controller instead. + * + * In the spec, four algorithms (startAlgorithm, writeAlgorithm, closeAlgorithm, + * abortAlgorithm) are passed as arguments to this routine. This implementation + * passes these "algorithms" as data, using five arguments: sinkAlgorithms, + * underlyingSink, writeMethod, closeMethod, and abortMethod. The sinkAlgorithms + * argument tells how to interpret the other three: + * + * - SinkAlgorithms::Script - We're creating a stream from a JS source. The + * caller is `new WritableStream(underlyingSink)` or + * `JS::NewWritableDefaultStreamObject`. `underlyingSink` is the sink; + * `writeMethod`, `closeMethod`, and `abortMethod` are its .write, .close, + * and .abort methods, which the caller has already extracted and + * type-checked: each one must be either a callable JS object or undefined. + * + * Script streams use the start/write/close/abort algorithms defined in + * 4.8.3. SetUpWritableStreamDefaultControllerFromUnderlyingSink, which + * call JS methods of the underlyingSink. + * + * - SinkAlgorithms::Transform - We're creating a transform stream. + * `underlyingSink` is a Transform object. `writeMethod`, `closeMethod, and + * `abortMethod` are undefined. + * + * Transform streams use the write/close/abort algorithms given in + * 5.3.2 InitializeTransformStream. + * + * An additional sizeAlgorithm in the spec is an algorithm used to compute the + * size of a chunk. Per MakeSizeAlgorithmFromSizeFunction, we just save the + * |size| value used to create that algorithm, then -- inline -- perform the + * requisite algorithm steps. (Hence the unadorned name |size|.) + * + * Note: All arguments must be same-compartment with cx. WritableStream + * controllers are always created in the same compartment as the stream. + */ +MOZ_MUST_USE bool js::SetUpWritableStreamDefaultController( + JSContext* cx, Handle<WritableStream*> stream, + SinkAlgorithms sinkAlgorithms, Handle<Value> underlyingSink, + Handle<Value> writeMethod, Handle<Value> closeMethod, + Handle<Value> abortMethod, double highWaterMark, Handle<Value> size) { + cx->check(stream); + cx->check(underlyingSink); + cx->check(writeMethod); + MOZ_ASSERT(writeMethod.isUndefined() || IsCallable(writeMethod)); + cx->check(closeMethod); + MOZ_ASSERT(closeMethod.isUndefined() || IsCallable(closeMethod)); + cx->check(abortMethod); + MOZ_ASSERT(abortMethod.isUndefined() || IsCallable(abortMethod)); + MOZ_ASSERT(highWaterMark >= 0); + cx->check(size); + MOZ_ASSERT(size.isUndefined() || IsCallable(size)); + + // Done elsewhere in the standard: Create the new controller. + Rooted<WritableStreamDefaultController*> controller( + cx, NewBuiltinClassInstance<WritableStreamDefaultController>(cx)); + if (!controller) { + return false; + } + + // Step 1: Assert: ! IsWritableStream(stream) is true. + // (guaranteed by |stream|'s type) + + // Step 2: Assert: stream.[[writableStreamController]] is undefined. + MOZ_ASSERT(!stream->hasController()); + + // Step 3: Set controller.[[controlledWritableStream]] to stream. + controller->setStream(stream); + + // Step 4: Set stream.[[writableStreamController]] to controller. + stream->setController(controller); + + // Step 5: Perform ! ResetQueue(controller). + if (!ResetQueue(cx, controller)) { + return false; + } + + // Step 6: Set controller.[[started]] to false. + controller->setFlags(0); + MOZ_ASSERT(!controller->started()); + + // Step 7: Set controller.[[strategySizeAlgorithm]] to sizeAlgorithm. + controller->setStrategySize(size); + + // Step 8: Set controller.[[strategyHWM]] to highWaterMark. + controller->setStrategyHWM(highWaterMark); + + // Step 9: Set controller.[[writeAlgorithm]] to writeAlgorithm. + // Step 10: Set controller.[[closeAlgorithm]] to closeAlgorithm. + // Step 11: Set controller.[[abortAlgorithm]] to abortAlgorithm. + // (In this implementation, all [[*Algorithm]] are determined by the + // underlyingSink in combination with the corresponding *Method field.) + controller->setUnderlyingSink(underlyingSink); + controller->setWriteMethod(writeMethod); + controller->setCloseMethod(closeMethod); + controller->setAbortMethod(abortMethod); + + // Step 12: Let backpressure be + // ! WritableStreamDefaultControllerGetBackpressure(controller). + bool backpressure = + WritableStreamDefaultControllerGetBackpressure(controller); + + // Step 13: Perform ! WritableStreamUpdateBackpressure(stream, backpressure). + if (!WritableStreamUpdateBackpressure(cx, stream, backpressure)) { + return false; + } + + // Step 14: Let startResult be the result of performing startAlgorithm. (This + // may throw an exception.) + Rooted<Value> startResult(cx); + if (sinkAlgorithms == SinkAlgorithms::Script) { + Rooted<Value> controllerVal(cx, ObjectValue(*controller)); + if (!InvokeOrNoop(cx, underlyingSink, cx->names().start, controllerVal, + &startResult)) { + return false; + } + } + + // Step 15: Let startPromise be a promise resolved with startResult. + Rooted<JSObject*> startPromise( + cx, PromiseObject::unforgeableResolve(cx, startResult)); + if (!startPromise) { + return false; + } + + // Step 16: Upon fulfillment of startPromise, + // Assert: stream.[[state]] is "writable" or "erroring". + // Set controller.[[started]] to true. + // Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller). + // Step 17: Upon rejection of startPromise with reason r, + // Assert: stream.[[state]] is "writable" or "erroring". + // Set controller.[[started]] to true. + // Perform ! WritableStreamDealWithRejection(stream, r). + Rooted<JSObject*> onStartFulfilled( + cx, NewHandler(cx, WritableStreamControllerStartHandler, controller)); + if (!onStartFulfilled) { + return false; + } + Rooted<JSObject*> onStartRejected( + cx, + NewHandler(cx, WritableStreamControllerStartFailedHandler, controller)); + if (!onStartRejected) { + return false; + } + + return JS::AddPromiseReactions(cx, startPromise, onStartFulfilled, + onStartRejected); +} + +/** + * Streams spec, 4.8.3. + * SetUpWritableStreamDefaultControllerFromUnderlyingSink( stream, + * underlyingSink, highWaterMark, sizeAlgorithm ) + */ +MOZ_MUST_USE bool js::SetUpWritableStreamDefaultControllerFromUnderlyingSink( + JSContext* cx, Handle<WritableStream*> stream, Handle<Value> underlyingSink, + double highWaterMark, Handle<Value> sizeAlgorithm) { + cx->check(stream); + cx->check(underlyingSink); + cx->check(sizeAlgorithm); + + // Step 1: Assert: underlyingSink is not undefined. + MOZ_ASSERT(!underlyingSink.isUndefined()); + + // Step 2: Let controller be ObjectCreate(the original value of + // WritableStreamDefaultController's prototype property). + // (Deferred to SetUpWritableStreamDefaultController.) + + // Step 3: Let startAlgorithm be the following steps: + // a. Return ? InvokeOrNoop(underlyingSink, "start", + // « controller »). + SinkAlgorithms sinkAlgorithms = SinkAlgorithms::Script; + + // Step 4: Let writeAlgorithm be + // ? CreateAlgorithmFromUnderlyingMethod(underlyingSink, "write", 1, + // « controller »). + Rooted<Value> writeMethod(cx); + if (!CreateAlgorithmFromUnderlyingMethod(cx, underlyingSink, + "WritableStream sink.write method", + cx->names().write, &writeMethod)) { + return false; + } + + // Step 5: Let closeAlgorithm be + // ? CreateAlgorithmFromUnderlyingMethod(underlyingSink, "close", 0, + // « »). + Rooted<Value> closeMethod(cx); + if (!CreateAlgorithmFromUnderlyingMethod(cx, underlyingSink, + "WritableStream sink.close method", + cx->names().close, &closeMethod)) { + return false; + } + + // Step 6: Let abortAlgorithm be + // ? CreateAlgorithmFromUnderlyingMethod(underlyingSink, "abort", 1, + // « »). + Rooted<Value> abortMethod(cx); + if (!CreateAlgorithmFromUnderlyingMethod(cx, underlyingSink, + "WritableStream sink.abort method", + cx->names().abort, &abortMethod)) { + return false; + } + + // Step 6. Perform ? SetUpWritableStreamDefaultController(stream, + // controller, startAlgorithm, writeAlgorithm, closeAlgorithm, + // abortAlgorithm, highWaterMark, sizeAlgorithm). + return SetUpWritableStreamDefaultController( + cx, stream, sinkAlgorithms, underlyingSink, writeMethod, closeMethod, + abortMethod, highWaterMark, sizeAlgorithm); +} + +/** + * Streams spec, 4.8.4. + * WritableStreamDefaultControllerClearAlgorithms ( controller ) + */ +void js::WritableStreamDefaultControllerClearAlgorithms( + WritableStreamDefaultController* unwrappedController) { + // Note: This operation will be performed multiple times in some edge cases, + // so it can't assert that the various algorithms initially haven't been + // cleared. + + // Step 1: Set controller.[[writeAlgorithm]] to undefined. + unwrappedController->clearWriteMethod(); + + // Step 2: Set controller.[[closeAlgorithm]] to undefined. + unwrappedController->clearCloseMethod(); + + // Step 3: Set controller.[[abortAlgorithm]] to undefined. + unwrappedController->clearAbortMethod(); + + // Step 4: Set controller.[[strategySizeAlgorithm]] to undefined. + unwrappedController->clearStrategySize(); +} + +/** + * Streams spec, 4.8.5. + * WritableStreamDefaultControllerClose ( controller ) + */ +bool js::WritableStreamDefaultControllerClose( + JSContext* cx, + Handle<WritableStreamDefaultController*> unwrappedController) { + // Step 1: Perform ! EnqueueValueWithSize(controller, "close", 0). + { + Rooted<Value> v(cx, MagicValue(JS_WRITABLESTREAM_CLOSE_RECORD)); + Rooted<Value> size(cx, Int32Value(0)); + if (!EnqueueValueWithSize(cx, unwrappedController, v, size)) { + return false; + } + } + + // Step 2: Perform + // ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller). + return WritableStreamDefaultControllerAdvanceQueueIfNeeded( + cx, unwrappedController); +} + +/** + * Streams spec, 4.8.6. + * WritableStreamDefaultControllerGetChunkSize ( controller, chunk ) + */ +bool js::WritableStreamDefaultControllerGetChunkSize( + JSContext* cx, Handle<WritableStreamDefaultController*> unwrappedController, + Handle<Value> chunk, MutableHandle<Value> returnValue) { + cx->check(chunk); + + // Step 1: Let returnValue be the result of performing + // controller.[[strategySizeAlgorithm]], passing in chunk, and + // interpreting the result as an ECMAScript completion value. + + // We don't store a literal [[strategySizeAlgorithm]], only the value that if + // passed through |MakeSizeAlgorithmFromSizeFunction| wouldn't have triggered + // an error. Perform the algorithm that function would return. + Rooted<Value> unwrappedStrategySize(cx, unwrappedController->strategySize()); + if (unwrappedStrategySize.isUndefined()) { + // 6.3.8 step 1: If size is undefined, return an algorithm that returns 1. + // ...and then from this function... + // Step 3: Return returnValue.[[Value]]. + returnValue.setInt32(1); + return true; + } + + MOZ_ASSERT(IsCallable(unwrappedStrategySize)); + + { + bool success; + { + AutoRealm ar(cx, unwrappedController); + cx->check(unwrappedStrategySize); + + Rooted<Value> wrappedChunk(cx, chunk); + if (!cx->compartment()->wrap(cx, &wrappedChunk)) { + return false; + } + + // 6.3.8 step 3 (of |MakeSizeAlgorithmFromSizeFunction|): + // Return an algorithm that performs the following steps, taking a + // chunk argument: + // a. Return ? Call(size, undefined, « chunk »). + success = Call(cx, unwrappedStrategySize, UndefinedHandleValue, + wrappedChunk, returnValue); + } + + // Step 3: (If returnValue is [not] an abrupt completion, ) + // Return returnValue.[[Value]]. (reordered for readability) + if (success) { + return cx->compartment()->wrap(cx, returnValue); + } + } + + // Step 2: If returnValue is an abrupt completion, + if (!cx->isExceptionPending() || !cx->getPendingException(returnValue)) { + // Uncatchable error. Die immediately without erroring the stream. + return false; + } + cx->check(returnValue); + + cx->clearPendingException(); + + // Step 2.a: Perform + // ! WritableStreamDefaultControllerErrorIfNeeded( + // controller, returnValue.[[Value]]). + if (!WritableStreamDefaultControllerErrorIfNeeded(cx, unwrappedController, + returnValue)) { + return false; + } + + // Step 2.b: Return 1. + returnValue.setInt32(1); + return true; +} + +/** + * Streams spec, 4.8.7. + * WritableStreamDefaultControllerGetDesiredSize ( controller ) + */ +double js::WritableStreamDefaultControllerGetDesiredSize( + const WritableStreamDefaultController* controller) { + return controller->strategyHWM() - controller->queueTotalSize(); +} + +/** + * Streams spec, 4.8.8. + * WritableStreamDefaultControllerWrite ( controller, chunk, chunkSize ) + */ +bool js::WritableStreamDefaultControllerWrite( + JSContext* cx, Handle<WritableStreamDefaultController*> unwrappedController, + Handle<Value> chunk, Handle<Value> chunkSize) { + MOZ_ASSERT(!chunk.isMagic()); + cx->check(chunk); + cx->check(chunkSize); + + // Step 1: Let writeRecord be Record {[[chunk]]: chunk}. + // Step 2: Let enqueueResult be + // EnqueueValueWithSize(controller, writeRecord, chunkSize). + bool succeeded = + EnqueueValueWithSize(cx, unwrappedController, chunk, chunkSize); + + // Step 3: If enqueueResult is an abrupt completion, + if (!succeeded) { + Rooted<Value> enqueueResult(cx); + if (!cx->isExceptionPending() || !cx->getPendingException(&enqueueResult)) { + // Uncatchable error. Die immediately without erroring the stream. + return false; + } + cx->check(enqueueResult); + + cx->clearPendingException(); + + // Step 3.a: Perform ! WritableStreamDefaultControllerErrorIfNeeded( + // controller, enqueueResult.[[Value]]). + // Step 3.b: Return. + return WritableStreamDefaultControllerErrorIfNeeded(cx, unwrappedController, + enqueueResult); + } + + // Step 4: Let stream be controller.[[controlledWritableStream]]. + Rooted<WritableStream*> unwrappedStream(cx, unwrappedController->stream()); + + // Step 5: If ! WritableStreamCloseQueuedOrInFlight(stream) is false and + // stream.[[state]] is "writable", + if (!WritableStreamCloseQueuedOrInFlight(unwrappedStream) && + unwrappedStream->writable()) { + // Step 5.a: Let backpressure be + // ! WritableStreamDefaultControllerGetBackpressure(controller). + bool backpressure = + WritableStreamDefaultControllerGetBackpressure(unwrappedController); + + // Step 5.b: Perform + // ! WritableStreamUpdateBackpressure(stream, backpressure). + if (!WritableStreamUpdateBackpressure(cx, unwrappedStream, backpressure)) { + return false; + } + } + + // Step 6: Perform + // ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller). + return WritableStreamDefaultControllerAdvanceQueueIfNeeded( + cx, unwrappedController); +} + +static MOZ_MUST_USE bool WritableStreamDefaultControllerProcessIfNeeded( + JSContext* cx, + Handle<WritableStreamDefaultController*> unwrappedController); + +/** + * Streams spec, 4.8.9. + * WritableStreamDefaultControllerAdvanceQueueIfNeeded ( controller ) + */ +MOZ_MUST_USE bool WritableStreamDefaultControllerAdvanceQueueIfNeeded( + JSContext* cx, + Handle<WritableStreamDefaultController*> unwrappedController) { + // Step 2: If controller.[[started]] is false, return. + if (!unwrappedController->started()) { + return true; + } + + // Step 1: Let stream be controller.[[controlledWritableStream]]. + Rooted<WritableStream*> unwrappedStream(cx, unwrappedController->stream()); + + // Step 3: If stream.[[inFlightWriteRequest]] is not undefined, return. + if (!unwrappedStream->inFlightWriteRequest().isUndefined()) { + return true; + } + + // Step 4: Let state be stream.[[state]]. + // Step 5: Assert: state is not "closed" or "errored". + // Step 6: If state is "erroring", + MOZ_ASSERT(!unwrappedStream->closed()); + MOZ_ASSERT(!unwrappedStream->errored()); + if (unwrappedStream->erroring()) { + // Step 6a: Perform ! WritableStreamFinishErroring(stream). + // Step 6b: Return. + return WritableStreamFinishErroring(cx, unwrappedStream); + } + + // Step 7: If controller.[[queue]] is empty, return. + // Step 8: Let writeRecord be ! PeekQueueValue(controller). + // Step 9: If writeRecord is "close", perform + // ! WritableStreamDefaultControllerProcessClose(controller). + // Step 10: Otherwise, perform + // ! WritableStreamDefaultControllerProcessWrite( + // controller, writeRecord.[[chunk]]). + return WritableStreamDefaultControllerProcessIfNeeded(cx, + unwrappedController); +} + +/** + * Streams spec, 4.8.10. + * WritableStreamDefaultControllerErrorIfNeeded ( controller, error ) + */ +bool js::WritableStreamDefaultControllerErrorIfNeeded( + JSContext* cx, Handle<WritableStreamDefaultController*> unwrappedController, + Handle<Value> error) { + cx->check(error); + + // Step 1: If controller.[[controlledWritableStream]].[[state]] is "writable", + // perform ! WritableStreamDefaultControllerError(controller, error). + if (unwrappedController->stream()->writable()) { + if (!WritableStreamDefaultControllerError(cx, unwrappedController, error)) { + return false; + } + } + + return true; +} + +// 4.8.11 step 5: Let sinkClosePromise be the result of performing +// controller.[[closeAlgorithm]]. +static MOZ_MUST_USE JSObject* PerformCloseAlgorithm( + JSContext* cx, + Handle<WritableStreamDefaultController*> unwrappedController) { + // 4.8.3 step 5: Let closeAlgorithm be + // ? CreateAlgorithmFromUnderlyingMethod(underlyingSink, + // "close", 0, « »). + + // Step 1: Assert: underlyingObject is not undefined. + // Step 2: Assert: ! IsPropertyKey(methodName) is true (implicit). + // Step 3: Assert: algoArgCount is 0 or 1 (omitted). + // Step 4: Assert: extraArgs is a List (omitted). + // Step 5: Let method be ? GetV(underlyingObject, methodName). + // + // These steps were performed in |CreateAlgorithmFromUnderlyingMethod|. The + // spec stores away algorithms for later invocation; we instead store the + // value that determines the algorithm to be created -- either |undefined|, or + // a callable object that's called with context-specific arguments. + + // Step 7: (If method is undefined,) Return an algorithm which returns a + // promise resolved with undefined (implicit). + if (unwrappedController->closeMethod().isUndefined()) { + return PromiseResolvedWithUndefined(cx); + } + + // Step 6: If method is not undefined, + + // Step 6.a: If ! IsCallable(method) is false, throw a TypeError exception. + MOZ_ASSERT(IsCallable(unwrappedController->closeMethod())); + + // Step 6.b: If algoArgCount is 0, return an algorithm that performs the + // following steps: + // Step 6.b.ii: Return ! PromiseCall(method, underlyingObject, extraArgs). + Rooted<Value> closeMethod(cx, unwrappedController->closeMethod()); + if (!cx->compartment()->wrap(cx, &closeMethod)) { + return nullptr; + } + + Rooted<Value> underlyingSink(cx, unwrappedController->underlyingSink()); + if (!cx->compartment()->wrap(cx, &underlyingSink)) { + return nullptr; + } + + return PromiseCall(cx, closeMethod, underlyingSink); +} + +// 4.8.12 step 3: Let sinkWritePromise be the result of performing +// controller.[[writeAlgorithm]], passing in chunk. +static MOZ_MUST_USE JSObject* PerformWriteAlgorithm( + JSContext* cx, Handle<WritableStreamDefaultController*> unwrappedController, + Handle<Value> chunk) { + cx->check(chunk); + + // 4.8.3 step 4: Let writeAlgorithm be + // ? CreateAlgorithmFromUnderlyingMethod(underlyingSink, + // "write", 1, + // « controller »). + + // Step 1: Assert: underlyingObject is not undefined. + // Step 2: Assert: ! IsPropertyKey(methodName) is true (implicit). + // Step 3: Assert: algoArgCount is 0 or 1 (omitted). + // Step 4: Assert: extraArgs is a List (omitted). + // Step 5: Let method be ? GetV(underlyingObject, methodName). + // + // These steps were performed in |CreateAlgorithmFromUnderlyingMethod|. The + // spec stores away algorithms for later invocation; we instead store the + // value that determines the algorithm to be created -- either |undefined|, or + // a callable object that's called with context-specific arguments. + + // Step 7: (If method is undefined,) Return an algorithm which returns a + // promise resolved with undefined (implicit). + if (unwrappedController->writeMethod().isUndefined()) { + return PromiseResolvedWithUndefined(cx); + } + + // Step 6: If method is not undefined, + + // Step 6.a: If ! IsCallable(method) is false, throw a TypeError exception. + MOZ_ASSERT(IsCallable(unwrappedController->writeMethod())); + + // Step 6.c: Otherwise (if algoArgCount is not 0), return an algorithm that + // performs the following steps, taking an arg argument: + // Step 6.c.i: Let fullArgs be a List consisting of arg followed by the + // elements of extraArgs in order. + // Step 6.c.ii: Return ! PromiseCall(method, underlyingObject, fullArgs). + Rooted<Value> writeMethod(cx, unwrappedController->writeMethod()); + if (!cx->compartment()->wrap(cx, &writeMethod)) { + return nullptr; + } + + Rooted<Value> underlyingSink(cx, unwrappedController->underlyingSink()); + if (!cx->compartment()->wrap(cx, &underlyingSink)) { + return nullptr; + } + + Rooted<Value> controller(cx, ObjectValue(*unwrappedController)); + if (!cx->compartment()->wrap(cx, &controller)) { + return nullptr; + } + + return PromiseCall(cx, writeMethod, underlyingSink, chunk, controller); +} + +/** + * Streams spec, 4.8.11 step 7: + * Upon fulfillment of sinkClosePromise, + */ +static MOZ_MUST_USE bool WritableStreamCloseHandler(JSContext* cx, + unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted<WritableStream*> unwrappedStream( + cx, TargetFromHandler<WritableStream>(args)); + + // Step 7.a: Perform ! WritableStreamFinishInFlightClose(stream). + if (!WritableStreamFinishInFlightClose(cx, unwrappedStream)) { + return false; + } + + args.rval().setUndefined(); + return true; +} + +/** + * Streams spec, 4.8.11 step 8: + * Upon rejection of sinkClosePromise with reason reason, + */ +static MOZ_MUST_USE bool WritableStreamCloseFailedHandler(JSContext* cx, + unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted<WritableStream*> unwrappedStream( + cx, TargetFromHandler<WritableStream>(args)); + + // Step 8.a: Perform + // ! WritableStreamFinishInFlightCloseWithError(stream, reason). + if (!WritableStreamFinishInFlightCloseWithError(cx, unwrappedStream, + args.get(0))) { + return false; + } + + args.rval().setUndefined(); + return true; +} + +/** + * Streams spec, 4.8.12 step 4: + * Upon fulfillment of sinkWritePromise, + */ +static MOZ_MUST_USE bool WritableStreamWriteHandler(JSContext* cx, + unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted<WritableStream*> unwrappedStream( + cx, TargetFromHandler<WritableStream>(args)); + + // Step 4.a: Perform ! WritableStreamFinishInFlightWrite(stream). + if (!WritableStreamFinishInFlightWrite(cx, unwrappedStream)) { + return false; + } + + // Step 4.b: Let state be stream.[[state]]. + // Step 4.c: Assert: state is "writable" or "erroring". + MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring()); + + // Step 4.d: Perform ! DequeueValue(controller). + DequeueValue(unwrappedStream->controller(), cx); + + // Step 4.e: If ! WritableStreamCloseQueuedOrInFlight(stream) is false and + // state is "writable", + if (!WritableStreamCloseQueuedOrInFlight(unwrappedStream) && + unwrappedStream->writable()) { + // Step 4.e.i: Let backpressure be + // ! WritableStreamDefaultControllerGetBackpressure( + // controller). + bool backpressure = WritableStreamDefaultControllerGetBackpressure( + unwrappedStream->controller()); + + // Step 4.e.ii: Perform + // ! WritableStreamUpdateBackpressure(stream, backpressure). + if (!WritableStreamUpdateBackpressure(cx, unwrappedStream, backpressure)) { + return false; + } + } + + // Step 4.f: Perform + // ! WritableStreamDefaultControllerAdvanceQueueIfNeeded( + // controller). + Rooted<WritableStreamDefaultController*> unwrappedController( + cx, unwrappedStream->controller()); + if (!WritableStreamDefaultControllerAdvanceQueueIfNeeded( + cx, unwrappedController)) { + return false; + } + + args.rval().setUndefined(); + return true; +} + +/** + * Streams spec, 4.8.12 step 5: + * Upon rejection of sinkWritePromise with reason, + */ +static MOZ_MUST_USE bool WritableStreamWriteFailedHandler(JSContext* cx, + unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted<WritableStream*> unwrappedStream( + cx, TargetFromHandler<WritableStream>(args)); + + // Step 5.a: If stream.[[state]] is "writable", perform + // ! WritableStreamDefaultControllerClearAlgorithms(controller). + if (unwrappedStream->writable()) { + WritableStreamDefaultControllerClearAlgorithms( + unwrappedStream->controller()); + } + + // Step 5.b: Perform + // ! WritableStreamFinishInFlightWriteWithError(stream, reason). + if (!WritableStreamFinishInFlightWriteWithError(cx, unwrappedStream, + args.get(0))) { + return false; + } + + args.rval().setUndefined(); + return true; +} + +/** + * Streams spec, 4.8.9 (steps 7-10), + * WritableStreamDefaultControllerAdvanceQueueIfNeeded ( controller ) + * Streams spec, 4.8.11. + * WritableStreamDefaultControllerProcessClose ( controller ) + * Streams spec, 4.8.12. + * WritableStreamDefaultControllerProcessWrite ( controller, chunk ) + */ +bool WritableStreamDefaultControllerProcessIfNeeded( + JSContext* cx, + Handle<WritableStreamDefaultController*> unwrappedController) { + // Step 7: If controller.[[queue]] is empty, return. + ListObject* unwrappedQueue = unwrappedController->queue(); + if (QueueIsEmpty(unwrappedQueue)) { + return true; + } + + // Step 8: Let writeRecord be ! PeekQueueValue(controller). + // Step 9: If writeRecord is "close", perform + // ! WritableStreamDefaultControllerProcessClose(controller). + // Step 10: Otherwise, perform + // ! WritableStreamDefaultControllerProcessWrite( + // controller, writeRecord.[[chunk]]). + Rooted<JSObject*> sinkWriteOrClosePromise(cx); + JSNative onFulfilledFunc, onRejectedFunc; + if (PeekQueueValue(unwrappedQueue).isMagic(JS_WRITABLESTREAM_CLOSE_RECORD)) { + MOZ_ASSERT(unwrappedQueue->length() == 2); + + onFulfilledFunc = WritableStreamCloseHandler; + onRejectedFunc = WritableStreamCloseFailedHandler; + + // 4.8.11 step 1: Let stream be controller.[[controlledWritableStream]]. + // 4.8.11 step 2: Perform ! WritableStreamMarkCloseRequestInFlight(stream). + WritableStreamMarkCloseRequestInFlight(unwrappedController->stream()); + + // 4.8.11 step 3: Perform ! DequeueValue(controller). + DequeueValue(unwrappedController, cx); + + // 4.8.11 step 4: Assert: controller.[[queue]] is empty. + MOZ_ASSERT(unwrappedQueue->isEmpty()); + + // 4.8.11 step 5: Let sinkClosePromise be the result of performing + // controller.[[closeAlgorithm]]. + sinkWriteOrClosePromise = PerformCloseAlgorithm(cx, unwrappedController); + } else { + onFulfilledFunc = WritableStreamWriteHandler; + onRejectedFunc = WritableStreamWriteFailedHandler; + + Rooted<Value> chunk(cx, PeekQueueValue(unwrappedQueue)); + if (!cx->compartment()->wrap(cx, &chunk)) { + return false; + } + + // 4.8.12 step 1: Let stream be controller.[[controlledWritableStream]]. + // 4.8.12 step 2: Perform + // ! WritableStreamMarkFirstWriteRequestInFlight(stream). + WritableStreamMarkFirstWriteRequestInFlight(unwrappedController->stream()); + + // 4.8.12 step 3: Let sinkWritePromise be the result of performing + // controller.[[writeAlgorithm]], passing in chunk. + sinkWriteOrClosePromise = + PerformWriteAlgorithm(cx, unwrappedController, chunk); + } + if (!sinkWriteOrClosePromise) { + return false; + } + + Rooted<JSObject*> stream(cx, unwrappedController->stream()); + if (!cx->compartment()->wrap(cx, &stream)) { + return false; + } + + // Step 7: Upon fulfillment of sinkClosePromise, + // Step 4: Upon fulfillment of sinkWritePromise, + // Step 8: Upon rejection of sinkClosePromise with reason reason, + // Step 5: Upon rejection of sinkWritePromise with reason, + Rooted<JSObject*> onFulfilled(cx, NewHandler(cx, onFulfilledFunc, stream)); + if (!onFulfilled) { + return false; + } + Rooted<JSObject*> onRejected(cx, NewHandler(cx, onRejectedFunc, stream)); + if (!onRejected) { + return false; + } + return JS::AddPromiseReactions(cx, sinkWriteOrClosePromise, onFulfilled, + onRejected); +} + +/** + * Streams spec, 4.8.13. + * WritableStreamDefaultControllerGetBackpressure ( controller ) + */ +bool js::WritableStreamDefaultControllerGetBackpressure( + const WritableStreamDefaultController* unwrappedController) { + return WritableStreamDefaultControllerGetDesiredSize(unwrappedController) <= + 0.0; +} + +/** + * Streams spec, 4.8.14. + * WritableStreamDefaultControllerError ( controller, error ) + */ +bool js::WritableStreamDefaultControllerError( + JSContext* cx, Handle<WritableStreamDefaultController*> unwrappedController, + Handle<Value> error) { + cx->check(error); + + // Step 1: Let stream be controller.[[controlledWritableStream]]. + Rooted<WritableStream*> unwrappedStream(cx, unwrappedController->stream()); + + // Step 2: Assert: stream.[[state]] is "writable". + MOZ_ASSERT(unwrappedStream->writable()); + + // Step 3: Perform + // ! WritableStreamDefaultControllerClearAlgorithms(controller). + WritableStreamDefaultControllerClearAlgorithms(unwrappedController); + + // Step 4: Perform ! WritableStreamStartErroring(stream, error). + return WritableStreamStartErroring(cx, unwrappedStream, error); +} |