diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/builtin/streams/ReadableStreamDefaultControllerOperations.cpp | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/builtin/streams/ReadableStreamDefaultControllerOperations.cpp')
-rw-r--r-- | js/src/builtin/streams/ReadableStreamDefaultControllerOperations.cpp | 682 |
1 files changed, 682 insertions, 0 deletions
diff --git a/js/src/builtin/streams/ReadableStreamDefaultControllerOperations.cpp b/js/src/builtin/streams/ReadableStreamDefaultControllerOperations.cpp new file mode 100644 index 0000000000..1be4073498 --- /dev/null +++ b/js/src/builtin/streams/ReadableStreamDefaultControllerOperations.cpp @@ -0,0 +1,682 @@ +/* -*- 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/. */ + +/* Readable stream default controller abstract operations. */ + +#include "builtin/streams/ReadableStreamDefaultControllerOperations.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF} +#include "mozilla/Attributes.h" // MOZ_MUST_USE + +#include "jsfriendapi.h" // js::AssertSameCompartment + +#include "builtin/Stream.h" // js::ReadableByteStreamControllerClearPendingPullIntos +#include "builtin/streams/MiscellaneousOperations.h" // js::CreateAlgorithmFromUnderlyingMethod, js::InvokeOrNoop, js::IsMaybeWrapped +#include "builtin/streams/QueueWithSizes.h" // js::EnqueueValueWithSize, js::ResetQueue +#include "builtin/streams/ReadableStreamController.h" // js::ReadableStream{,Default}Controller, js::ReadableByteStreamController, js::ReadableStreamControllerStart{,Failed}Handler +#include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStream{CloseInternal,ErrorInternal,FulfillReadOrReadIntoRequest,GetNumReadRequests} +#include "builtin/streams/ReadableStreamOperations.h" // js::ReadableStreamTee_Pull, js::SetUpReadableStreamDefaultController +#include "builtin/streams/TeeState.h" // js::TeeState +#include "js/CallArgs.h" // JS::CallArgs{,FromVp} +#include "js/Promise.h" // JS::AddPromiseReactions +#include "js/RootingAPI.h" // JS::Handle, JS::Rooted +#include "js/Stream.h" // JS::ReadableStreamUnderlyingSource +#include "js/Value.h" // JS::{,Int32,Object}Value, JS::UndefinedHandleValue +#include "vm/Compartment.h" // JS::Compartment +#include "vm/Interpreter.h" // js::Call, js::GetAndClearExceptionAndStack +#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 "vm/SavedFrame.h" // js::SavedFrame + +#include "builtin/HandlerFunction-inl.h" // js::NewHandler +#include "builtin/streams/MiscellaneousOperations-inl.h" // js::PromiseCall +#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::UnwrapCalleeSlot +#include "vm/JSContext-inl.h" // JSContext::check +#include "vm/JSObject-inl.h" // js::IsCallable, js::NewBuiltinClassInstance +#include "vm/Realm-inl.h" // js::AutoRealm + +using js::ReadableByteStreamController; +using js::ReadableStream; +using js::ReadableStreamController; +using js::ReadableStreamControllerCallPullIfNeeded; +using js::ReadableStreamControllerError; +using js::ReadableStreamGetNumReadRequests; +using js::UnwrapCalleeSlot; + +using JS::CallArgs; +using JS::CallArgsFromVp; +using JS::Handle; +using JS::Rooted; +using JS::UndefinedHandleValue; +using JS::Value; + +/*** 3.10. Readable stream default controller abstract operations ***********/ + +// Streams spec, 3.10.1. IsReadableStreamDefaultController ( x ) +// Implemented via is<ReadableStreamDefaultController>() + +/** + * Streams spec, 3.10.2 and 3.13.3. step 7: + * Upon fulfillment of pullPromise, [...] + */ +static bool ControllerPullHandler(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted<ReadableStreamController*> unwrappedController( + cx, UnwrapCalleeSlot<ReadableStreamController>(cx, args, 0)); + if (!unwrappedController) { + return false; + } + + bool pullAgain = unwrappedController->pullAgain(); + + // Step a: Set controller.[[pulling]] to false. + // Step b.i: Set controller.[[pullAgain]] to false. + unwrappedController->clearPullFlags(); + + // Step b: If controller.[[pullAgain]] is true, + if (pullAgain) { + // Step ii: Perform + // ! ReadableStreamDefaultControllerCallPullIfNeeded(controller) + // (or ReadableByteStreamControllerCallPullIfNeeded(controller)). + if (!ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController)) { + return false; + } + } + + args.rval().setUndefined(); + return true; +} + +/** + * Streams spec, 3.10.2 and 3.13.3. step 8: + * Upon rejection of pullPromise with reason e, + */ +static bool ControllerPullFailedHandler(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + Handle<Value> e = args.get(0); + + Rooted<ReadableStreamController*> controller( + cx, UnwrapCalleeSlot<ReadableStreamController>(cx, args, 0)); + if (!controller) { + return false; + } + + // Step a: Perform ! ReadableStreamDefaultControllerError(controller, e). + // (ReadableByteStreamControllerError in 3.12.3.) + if (!ReadableStreamControllerError(cx, controller, e)) { + return false; + } + + args.rval().setUndefined(); + return true; +} + +static bool ReadableStreamControllerShouldCallPull( + ReadableStreamController* unwrappedController); + +/** + * Streams spec, 3.10.2 + * ReadableStreamDefaultControllerCallPullIfNeeded ( controller ) + * Streams spec, 3.13.3. + * ReadableByteStreamControllerCallPullIfNeeded ( controller ) + */ +MOZ_MUST_USE bool js::ReadableStreamControllerCallPullIfNeeded( + JSContext* cx, Handle<ReadableStreamController*> unwrappedController) { + // Step 1: Let shouldPull be + // ! ReadableStreamDefaultControllerShouldCallPull(controller). + // (ReadableByteStreamDefaultControllerShouldCallPull in 3.13.3.) + bool shouldPull = ReadableStreamControllerShouldCallPull(unwrappedController); + + // Step 2: If shouldPull is false, return. + if (!shouldPull) { + return true; + } + + // Step 3: If controller.[[pulling]] is true, + if (unwrappedController->pulling()) { + // Step a: Set controller.[[pullAgain]] to true. + unwrappedController->setPullAgain(); + + // Step b: Return. + return true; + } + + // Step 4: Assert: controller.[[pullAgain]] is false. + MOZ_ASSERT(!unwrappedController->pullAgain()); + + // Step 5: Set controller.[[pulling]] to true. + unwrappedController->setPulling(); + + // We use this variable in step 7. For ease of error-handling, we wrap it + // early. + Rooted<JSObject*> wrappedController(cx, unwrappedController); + if (!cx->compartment()->wrap(cx, &wrappedController)) { + return false; + } + + // Step 6: Let pullPromise be the result of performing + // controller.[[pullAlgorithm]]. + // Our representation of pull algorithms is a bit awkward, for performance, + // so we must figure out which algorithm is being invoked. + Rooted<JSObject*> pullPromise(cx); + Rooted<Value> unwrappedUnderlyingSource( + cx, unwrappedController->underlyingSource()); + + if (IsMaybeWrapped<TeeState>(unwrappedUnderlyingSource)) { + // The pull algorithm given in ReadableStreamTee step 12. + MOZ_ASSERT(unwrappedUnderlyingSource.toObject().is<TeeState>(), + "tee streams and controllers are always same-compartment with " + "the TeeState object"); + Rooted<TeeState*> unwrappedTeeState( + cx, &unwrappedUnderlyingSource.toObject().as<TeeState>()); + pullPromise = ReadableStreamTee_Pull(cx, unwrappedTeeState); + } else if (unwrappedController->hasExternalSource()) { + // An embedding-provided pull algorithm. + { + AutoRealm ar(cx, unwrappedController); + JS::ReadableStreamUnderlyingSource* source = + unwrappedController->externalSource(); + Rooted<ReadableStream*> stream(cx, unwrappedController->stream()); + double desiredSize = + ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController); + source->requestData(cx, stream, desiredSize); + } + pullPromise = PromiseResolvedWithUndefined(cx); + } else { + // The pull algorithm created in + // SetUpReadableStreamDefaultControllerFromUnderlyingSource step 4. + Rooted<Value> unwrappedPullMethod(cx, unwrappedController->pullMethod()); + if (unwrappedPullMethod.isUndefined()) { + // CreateAlgorithmFromUnderlyingMethod step 7. + pullPromise = PromiseResolvedWithUndefined(cx); + } else { + // CreateAlgorithmFromUnderlyingMethod step 6.b.i. + { + AutoRealm ar(cx, unwrappedController); + + // |unwrappedPullMethod| and |unwrappedUnderlyingSource| come directly + // from |unwrappedController| slots so must be same-compartment with it. + cx->check(unwrappedPullMethod); + cx->check(unwrappedUnderlyingSource); + + Rooted<Value> controller(cx, ObjectValue(*unwrappedController)); + cx->check(controller); + + pullPromise = PromiseCall(cx, unwrappedPullMethod, + unwrappedUnderlyingSource, controller); + if (!pullPromise) { + return false; + } + } + if (!cx->compartment()->wrap(cx, &pullPromise)) { + return false; + } + } + } + if (!pullPromise) { + return false; + } + + // Step 7: Upon fulfillment of pullPromise, [...] + // Step 8. Upon rejection of pullPromise with reason e, [...] + Rooted<JSObject*> onPullFulfilled( + cx, NewHandler(cx, ControllerPullHandler, wrappedController)); + if (!onPullFulfilled) { + return false; + } + Rooted<JSObject*> onPullRejected( + cx, NewHandler(cx, ControllerPullFailedHandler, wrappedController)); + if (!onPullRejected) { + return false; + } + return JS::AddPromiseReactions(cx, pullPromise, onPullFulfilled, + onPullRejected); +} + +/** + * Streams spec, 3.10.3. + * ReadableStreamDefaultControllerShouldCallPull ( controller ) + * Streams spec, 3.13.25. + * ReadableByteStreamControllerShouldCallPull ( controller ) + */ +static bool ReadableStreamControllerShouldCallPull( + ReadableStreamController* unwrappedController) { + // Step 1: Let stream be controller.[[controlledReadableStream]] + // (or [[controlledReadableByteStream]]). + ReadableStream* unwrappedStream = unwrappedController->stream(); + + // 3.10.3. Step 2: + // If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller) + // is false, return false. + // This turns out to be the same as 3.13.25 steps 2-3. + + // 3.13.25 Step 2: If stream.[[state]] is not "readable", return false. + if (!unwrappedStream->readable()) { + return false; + } + + // 3.13.25 Step 3: If controller.[[closeRequested]] is true, return false. + if (unwrappedController->closeRequested()) { + return false; + } + + // Step 3 (or 4): + // If controller.[[started]] is false, return false. + if (!unwrappedController->started()) { + return false; + } + + // 3.10.3. + // Step 4: If ! IsReadableStreamLocked(stream) is true and + // ! ReadableStreamGetNumReadRequests(stream) > 0, return true. + // + // 3.13.25. + // Step 5: If ! ReadableStreamHasDefaultReader(stream) is true and + // ! ReadableStreamGetNumReadRequests(stream) > 0, return true. + // Step 6: If ! ReadableStreamHasBYOBReader(stream) is true and + // ! ReadableStreamGetNumReadIntoRequests(stream) > 0, return true. + // + // All of these amount to the same thing in this implementation: + if (unwrappedStream->locked() && + ReadableStreamGetNumReadRequests(unwrappedStream) > 0) { + return true; + } + + // Step 5 (or 7): + // Let desiredSize be + // ! ReadableStreamDefaultControllerGetDesiredSize(controller). + // (ReadableByteStreamControllerGetDesiredSize in 3.13.25.) + double desiredSize = + ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController); + + // Step 6 (or 8): Assert: desiredSize is not null (implicit). + // Step 7 (or 9): If desiredSize > 0, return true. + // Step 8 (or 10): Return false. + return desiredSize > 0; +} + +/** + * Streams spec, 3.10.4. + * ReadableStreamDefaultControllerClearAlgorithms ( controller ) + * and 3.13.4. + * ReadableByteStreamControllerClearAlgorithms ( controller ) + */ +void js::ReadableStreamControllerClearAlgorithms( + Handle<ReadableStreamController*> controller) { + // Step 1: Set controller.[[pullAlgorithm]] to undefined. + // Step 2: Set controller.[[cancelAlgorithm]] to undefined. + // (In this implementation, the UnderlyingSource slot is part of the + // representation of these algorithms.) + controller->setPullMethod(UndefinedHandleValue); + controller->setCancelMethod(UndefinedHandleValue); + ReadableStreamController::clearUnderlyingSource(controller); + + // Step 3 (of 3.10.4 only) : Set controller.[[strategySizeAlgorithm]] to + // undefined. + if (controller->is<ReadableStreamDefaultController>()) { + controller->as<ReadableStreamDefaultController>().setStrategySize( + UndefinedHandleValue); + } +} + +/** + * Streams spec, 3.10.5. ReadableStreamDefaultControllerClose ( controller ) + */ +MOZ_MUST_USE bool js::ReadableStreamDefaultControllerClose( + JSContext* cx, + Handle<ReadableStreamDefaultController*> unwrappedController) { + // Step 1: Let stream be controller.[[controlledReadableStream]]. + Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream()); + + // Step 2: Assert: + // ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller) + // is true. + MOZ_ASSERT(!unwrappedController->closeRequested()); + MOZ_ASSERT(unwrappedStream->readable()); + + // Step 3: Set controller.[[closeRequested]] to true. + unwrappedController->setCloseRequested(); + + // Step 4: If controller.[[queue]] is empty, + Rooted<ListObject*> unwrappedQueue(cx, unwrappedController->queue()); + if (unwrappedQueue->length() == 0) { + // Step a: Perform + // ! ReadableStreamDefaultControllerClearAlgorithms(controller). + ReadableStreamControllerClearAlgorithms(unwrappedController); + + // Step b: Perform ! ReadableStreamClose(stream). + return ReadableStreamCloseInternal(cx, unwrappedStream); + } + + return true; +} + +/** + * Streams spec, 3.10.6. + * ReadableStreamDefaultControllerEnqueue ( controller, chunk ) + */ +MOZ_MUST_USE bool js::ReadableStreamDefaultControllerEnqueue( + JSContext* cx, Handle<ReadableStreamDefaultController*> unwrappedController, + Handle<Value> chunk) { + AssertSameCompartment(cx, chunk); + + // Step 1: Let stream be controller.[[controlledReadableStream]]. + Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream()); + + // Step 2: Assert: + // ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller) is + // true. + MOZ_ASSERT(!unwrappedController->closeRequested()); + MOZ_ASSERT(unwrappedStream->readable()); + + // Step 3: If ! IsReadableStreamLocked(stream) is true and + // ! ReadableStreamGetNumReadRequests(stream) > 0, perform + // ! ReadableStreamFulfillReadRequest(stream, chunk, false). + if (unwrappedStream->locked() && + ReadableStreamGetNumReadRequests(unwrappedStream) > 0) { + if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, unwrappedStream, chunk, + false)) { + return false; + } + } else { + // Step 4: Otherwise, + // Step a: Let result be the result of performing + // controller.[[strategySizeAlgorithm]], passing in chunk, and + // interpreting the result as an ECMAScript completion value. + // Step c: (on success) Let chunkSize be result.[[Value]]. + Rooted<Value> chunkSize(cx, Int32Value(1)); + bool success = true; + Rooted<Value> strategySize(cx, unwrappedController->strategySize()); + if (!strategySize.isUndefined()) { + if (!cx->compartment()->wrap(cx, &strategySize)) { + return false; + } + success = Call(cx, strategySize, UndefinedHandleValue, chunk, &chunkSize); + } + + // Step d: Let enqueueResult be + // EnqueueValueWithSize(controller, chunk, chunkSize). + if (success) { + success = EnqueueValueWithSize(cx, unwrappedController, chunk, chunkSize); + } + + // Step b: If result is an abrupt completion, + // and + // Step e: If enqueueResult is an abrupt completion, + if (!success) { + Rooted<Value> exn(cx); + Rooted<SavedFrame*> stack(cx); + if (!cx->isExceptionPending() || + !GetAndClearExceptionAndStack(cx, &exn, &stack)) { + // Uncatchable error. Die immediately without erroring the + // stream. + return false; + } + + // Step b.i: Perform ! ReadableStreamDefaultControllerError( + // controller, result.[[Value]]). + // Step e.i: Perform ! ReadableStreamDefaultControllerError( + // controller, enqueueResult.[[Value]]). + if (!ReadableStreamControllerError(cx, unwrappedController, exn)) { + return false; + } + + // Step b.ii: Return result. + // Step e.ii: Return enqueueResult. + // (I.e., propagate the exception.) + cx->setPendingException(exn, stack); + return false; + } + } + + // Step 5: Perform + // ! ReadableStreamDefaultControllerCallPullIfNeeded(controller). + return ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController); +} + +/** + * Streams spec, 3.10.7. ReadableStreamDefaultControllerError ( controller, e ) + * Streams spec, 3.13.11. ReadableByteStreamControllerError ( controller, e ) + */ +MOZ_MUST_USE bool js::ReadableStreamControllerError( + JSContext* cx, Handle<ReadableStreamController*> unwrappedController, + Handle<Value> e) { + MOZ_ASSERT(!cx->isExceptionPending()); + AssertSameCompartment(cx, e); + + // Step 1: Let stream be controller.[[controlledReadableStream]] + // (or controller.[[controlledReadableByteStream]]). + Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream()); + + // Step 2: If stream.[[state]] is not "readable", return. + if (!unwrappedStream->readable()) { + return true; + } + + // Step 3 of 3.13.10: + // Perform ! ReadableByteStreamControllerClearPendingPullIntos(controller). + if (unwrappedController->is<ReadableByteStreamController>()) { + Rooted<ReadableByteStreamController*> unwrappedByteStreamController( + cx, &unwrappedController->as<ReadableByteStreamController>()); + if (!ReadableByteStreamControllerClearPendingPullIntos( + cx, unwrappedByteStreamController)) { + return false; + } + } + + // Step 3 (or 4): Perform ! ResetQueue(controller). + if (!ResetQueue(cx, unwrappedController)) { + return false; + } + + // Step 4 (or 5): + // Perform ! ReadableStreamDefaultControllerClearAlgorithms(controller) + // (or ReadableByteStreamControllerClearAlgorithms(controller)). + ReadableStreamControllerClearAlgorithms(unwrappedController); + + // Step 5 (or 6): Perform ! ReadableStreamError(stream, e). + return ReadableStreamErrorInternal(cx, unwrappedStream, e); +} + +/** + * Streams spec, 3.10.8. + * ReadableStreamDefaultControllerGetDesiredSize ( controller ) + * Streams spec 3.13.14. + * ReadableByteStreamControllerGetDesiredSize ( controller ) + */ +MOZ_MUST_USE double js::ReadableStreamControllerGetDesiredSizeUnchecked( + ReadableStreamController* controller) { + // Steps 1-4 done at callsites, so only assert that they have been done. +#if DEBUG + ReadableStream* stream = controller->stream(); + MOZ_ASSERT(!(stream->errored() || stream->closed())); +#endif // DEBUG + + // Step 5: Return controller.[[strategyHWM]] − controller.[[queueTotalSize]]. + return controller->strategyHWM() - controller->queueTotalSize(); +} + +/** + * Streams spec, 3.10.11. + * SetUpReadableStreamDefaultController(stream, controller, + * startAlgorithm, pullAlgorithm, cancelAlgorithm, 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, three algorithms (startAlgorithm, pullAlgorithm, + * cancelAlgorithm) are passed as arguments to this routine. This + * implementation passes these "algorithms" as data, using four arguments: + * sourceAlgorithms, underlyingSource, pullMethod, and cancelMethod. The + * sourceAlgorithms argument tells how to interpret the other three: + * + * - SourceAlgorithms::Script - We're creating a stream from a JS source. + * The caller is `new ReadableStream(underlyingSource)` or + * `JS::NewReadableDefaultStreamObject`. `underlyingSource` is the + * source; `pullMethod` and `cancelMethod` are its .pull and + * .cancel 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/pull/cancel algorithms defined in + * 3.10.12. SetUpReadableStreamDefaultControllerFromUnderlyingSource, which + * call JS methods of the underlyingSource. + * + * - SourceAlgorithms::Tee - We're creating a tee stream. `underlyingSource` + * is a TeeState object. `pullMethod` and `cancelMethod` are undefined. + * + * Tee streams use the start/pull/cancel algorithms given in + * 3.4.10. ReadableStreamTee. + * + * Note: All arguments must be same-compartment with cx. ReadableStream + * controllers are always created in the same compartment as the stream. + */ +MOZ_MUST_USE bool js::SetUpReadableStreamDefaultController( + JSContext* cx, Handle<ReadableStream*> stream, + SourceAlgorithms sourceAlgorithms, Handle<Value> underlyingSource, + Handle<Value> pullMethod, Handle<Value> cancelMethod, double highWaterMark, + Handle<Value> size) { + cx->check(stream, underlyingSource, size); + MOZ_ASSERT(pullMethod.isUndefined() || IsCallable(pullMethod)); + MOZ_ASSERT(cancelMethod.isUndefined() || IsCallable(cancelMethod)); + MOZ_ASSERT_IF(sourceAlgorithms != SourceAlgorithms::Script, + pullMethod.isUndefined()); + MOZ_ASSERT_IF(sourceAlgorithms != SourceAlgorithms::Script, + cancelMethod.isUndefined()); + MOZ_ASSERT(highWaterMark >= 0); + MOZ_ASSERT(size.isUndefined() || IsCallable(size)); + + // Done elsewhere in the standard: Create the new controller. + Rooted<ReadableStreamDefaultController*> controller( + cx, NewBuiltinClassInstance<ReadableStreamDefaultController>(cx)); + if (!controller) { + return false; + } + + // Step 1: Assert: stream.[[readableStreamController]] is undefined. + MOZ_ASSERT(!stream->hasController()); + + // Step 2: Set controller.[[controlledReadableStream]] to stream. + controller->setStream(stream); + + // Step 3: Set controller.[[queue]] and controller.[[queueTotalSize]] to + // undefined (implicit), then perform ! ResetQueue(controller). + if (!ResetQueue(cx, controller)) { + return false; + } + + // Step 4: Set controller.[[started]], controller.[[closeRequested]], + // controller.[[pullAgain]], and controller.[[pulling]] to false. + controller->setFlags(0); + + // Step 5: Set controller.[[strategySizeAlgorithm]] to sizeAlgorithm + // and controller.[[strategyHWM]] to highWaterMark. + controller->setStrategySize(size); + controller->setStrategyHWM(highWaterMark); + + // Step 6: Set controller.[[pullAlgorithm]] to pullAlgorithm. + // (In this implementation, the pullAlgorithm is determined by the + // underlyingSource in combination with the pullMethod field.) + controller->setUnderlyingSource(underlyingSource); + controller->setPullMethod(pullMethod); + + // Step 7: Set controller.[[cancelAlgorithm]] to cancelAlgorithm. + controller->setCancelMethod(cancelMethod); + + // Step 8: Set stream.[[readableStreamController]] to controller. + stream->setController(controller); + + // Step 9: Let startResult be the result of performing startAlgorithm. + Rooted<Value> startResult(cx); + if (sourceAlgorithms == SourceAlgorithms::Script) { + Rooted<Value> controllerVal(cx, ObjectValue(*controller)); + if (!InvokeOrNoop(cx, underlyingSource, cx->names().start, controllerVal, + &startResult)) { + return false; + } + } + + // Step 10: Let startPromise be a promise resolved with startResult. + Rooted<JSObject*> startPromise( + cx, PromiseObject::unforgeableResolve(cx, startResult)); + if (!startPromise) { + return false; + } + + // Step 11: Upon fulfillment of startPromise, [...] + // Step 12: Upon rejection of startPromise with reason r, [...] + Rooted<JSObject*> onStartFulfilled( + cx, NewHandler(cx, ReadableStreamControllerStartHandler, controller)); + if (!onStartFulfilled) { + return false; + } + Rooted<JSObject*> onStartRejected( + cx, + NewHandler(cx, ReadableStreamControllerStartFailedHandler, controller)); + if (!onStartRejected) { + return false; + } + if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled, + onStartRejected)) { + return false; + } + + return true; +} + +/** + * Streams spec, 3.10.12. + * SetUpReadableStreamDefaultControllerFromUnderlyingSource( stream, + * underlyingSource, highWaterMark, sizeAlgorithm ) + */ +MOZ_MUST_USE bool js::SetUpReadableStreamDefaultControllerFromUnderlyingSource( + JSContext* cx, Handle<ReadableStream*> stream, + Handle<Value> underlyingSource, double highWaterMark, + Handle<Value> sizeAlgorithm) { + // Step 1: Assert: underlyingSource is not undefined. + MOZ_ASSERT(!underlyingSource.isUndefined()); + + // Step 2: Let controller be ObjectCreate(the original value of + // ReadableStreamDefaultController's prototype property). + // (Deferred to SetUpReadableStreamDefaultController.) + + // Step 3: Let startAlgorithm be the following steps: + // a. Return ? InvokeOrNoop(underlyingSource, "start", + // « controller »). + SourceAlgorithms sourceAlgorithms = SourceAlgorithms::Script; + + // Step 4: Let pullAlgorithm be + // ? CreateAlgorithmFromUnderlyingMethod(underlyingSource, "pull", + // 0, « controller »). + Rooted<Value> pullMethod(cx); + if (!CreateAlgorithmFromUnderlyingMethod(cx, underlyingSource, + "ReadableStream source.pull method", + cx->names().pull, &pullMethod)) { + return false; + } + + // Step 5. Let cancelAlgorithm be + // ? CreateAlgorithmFromUnderlyingMethod(underlyingSource, + // "cancel", 1, « »). + Rooted<Value> cancelMethod(cx); + if (!CreateAlgorithmFromUnderlyingMethod( + cx, underlyingSource, "ReadableStream source.cancel method", + cx->names().cancel, &cancelMethod)) { + return false; + } + + // Step 6. Perform ? SetUpReadableStreamDefaultController(stream, + // controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, + // highWaterMark, sizeAlgorithm). + return SetUpReadableStreamDefaultController( + cx, stream, sourceAlgorithms, underlyingSource, pullMethod, cancelMethod, + highWaterMark, sizeAlgorithm); +} |