diff options
Diffstat (limited to 'js/src/builtin/streams/ReadableStreamDefaultController.cpp')
-rw-r--r-- | js/src/builtin/streams/ReadableStreamDefaultController.cpp | 514 |
1 files changed, 514 insertions, 0 deletions
diff --git a/js/src/builtin/streams/ReadableStreamDefaultController.cpp b/js/src/builtin/streams/ReadableStreamDefaultController.cpp new file mode 100644 index 0000000000..e1765c2518 --- /dev/null +++ b/js/src/builtin/streams/ReadableStreamDefaultController.cpp @@ -0,0 +1,514 @@ +/* -*- 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 ReadableStreamDefaultController. */ + +#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF} +#include "mozilla/Attributes.h" // MOZ_MUST_USE + +#include "jsapi.h" // JS_ReportErrorNumberASCII +#include "jsfriendapi.h" // js::AssertSameCompartment + +#include "builtin/streams/ClassSpecMacro.h" // JS_STREAMS_CLASS_SPEC +#include "builtin/streams/MiscellaneousOperations.h" // js::IsMaybeWrapped +#include "builtin/streams/PullIntoDescriptor.h" // js::PullIntoDescriptor +#include "builtin/streams/QueueWithSizes.h" // js::{DequeueValue,ResetQueue} +#include "builtin/streams/ReadableStream.h" // js::ReadableStream, js::SetUpExternalReadableByteStreamController +#include "builtin/streams/ReadableStreamController.h" // js::ReadableStream{,Default}Controller, js::ReadableByteStreamController, js::CheckReadableStreamControllerCanCloseOrEnqueue, js::ReadableStreamControllerCancelSteps, js::ReadableStreamDefaultControllerPullSteps, js::ReadableStreamControllerStart{,Failed}Handler +#include "builtin/streams/ReadableStreamDefaultControllerOperations.h" // js::ReadableStreamController{CallPullIfNeeded,ClearAlgorithms,Error,GetDesiredSizeUnchecked}, js::ReadableStreamDefaultController{Close,Enqueue} +#include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStream{AddReadOrReadIntoRequest,CloseInternal,CreateReadResult} +#include "builtin/streams/ReadableStreamOperations.h" // js::ReadableStreamTee_Cancel +#include "builtin/streams/ReadableStreamReader.h" // js::ReadableStream{,Default}Reader +#include "builtin/streams/StreamController.h" // js::StreamController +#include "builtin/streams/TeeState.h" // js::TeeState +#include "js/ArrayBuffer.h" // JS::NewArrayBuffer +#include "js/Class.h" // js::ClassSpec +#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* +#include "js/PropertySpec.h" +#include "vm/Interpreter.h" +#include "vm/JSContext.h" +#include "vm/PlainObject.h" // js::PlainObject +#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined +#include "vm/SelfHosting.h" + +#include "builtin/HandlerFunction-inl.h" // js::TargetFromHandler +#include "builtin/streams/MiscellaneousOperations-inl.h" // js::PromiseCall +#include "builtin/streams/ReadableStreamReader-inl.h" // js::UnwrapReaderFromStream +#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::UnwrapAnd{DowncastObject,TypeCheckThis} +#include "vm/JSContext-inl.h" // JSContext::check +#include "vm/Realm-inl.h" // js::AutoRealm + +using js::ClassSpec; +using js::PromiseObject; +using js::ReadableStream; +using js::ReadableStreamController; +using js::ReadableStreamControllerCallPullIfNeeded; +using js::ReadableStreamControllerClearAlgorithms; +using js::ReadableStreamControllerError; +using js::ReadableStreamControllerGetDesiredSizeUnchecked; +using js::ReadableStreamDefaultController; +using js::ReadableStreamDefaultControllerClose; +using js::ReadableStreamDefaultControllerEnqueue; +using js::TargetFromHandler; +using js::UnwrapAndTypeCheckThis; + +using JS::CallArgs; +using JS::CallArgsFromVp; +using JS::Handle; +using JS::ObjectValue; +using JS::Rooted; +using JS::Value; + +/*** 3.9. Class ReadableStreamDefaultController *****************************/ + +/** + * Streams spec, 3.10.11. SetUpReadableStreamDefaultController, step 11 + * and + * Streams spec, 3.13.26. SetUpReadableByteStreamController, step 16: + * Upon fulfillment of startPromise, [...] + */ +bool js::ReadableStreamControllerStartHandler(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + Rooted<ReadableStreamController*> controller( + cx, TargetFromHandler<ReadableStreamController>(args)); + + // Step a: Set controller.[[started]] to true. + controller->setStarted(); + + // Step b: Assert: controller.[[pulling]] is false. + MOZ_ASSERT(!controller->pulling()); + + // Step c: Assert: controller.[[pullAgain]] is false. + MOZ_ASSERT(!controller->pullAgain()); + + // Step d: Perform + // ! ReadableStreamDefaultControllerCallPullIfNeeded(controller) + // (or ReadableByteStreamControllerCallPullIfNeeded(controller)). + if (!ReadableStreamControllerCallPullIfNeeded(cx, controller)) { + return false; + } + args.rval().setUndefined(); + return true; +} + +/** + * Streams spec, 3.10.11. SetUpReadableStreamDefaultController, step 12 + * and + * Streams spec, 3.13.26. SetUpReadableByteStreamController, step 17: + * Upon rejection of startPromise with reason r, [...] + */ +bool js::ReadableStreamControllerStartFailedHandler(JSContext* cx, + unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + Rooted<ReadableStreamController*> controller( + cx, TargetFromHandler<ReadableStreamController>(args)); + + // Step a: Perform + // ! ReadableStreamDefaultControllerError(controller, r) + // (or ReadableByteStreamControllerError(controller, r)). + if (!ReadableStreamControllerError(cx, controller, args.get(0))) { + return false; + } + + args.rval().setUndefined(); + return true; +} + +/** + * Streams spec, 3.9.3. + * new ReadableStreamDefaultController( stream, underlyingSource, size, + * highWaterMark ) + */ +bool ReadableStreamDefaultController::constructor(JSContext* cx, unsigned argc, + Value* vp) { + // Step 1: Throw a TypeError. + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_BOGUS_CONSTRUCTOR, + "ReadableStreamDefaultController"); + return false; +} + +/** + * Streams spec, 3.9.4.1. get desiredSize + */ +static bool ReadableStreamDefaultController_desiredSize(JSContext* cx, + unsigned argc, + Value* vp) { + // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a + // TypeError exception. + CallArgs args = CallArgsFromVp(argc, vp); + Rooted<ReadableStreamController*> unwrappedController( + cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>( + cx, args, "get desiredSize")); + if (!unwrappedController) { + return false; + } + + // 3.10.8. ReadableStreamDefaultControllerGetDesiredSize, steps 1-4. + // 3.10.8. Step 1: Let stream be controller.[[controlledReadableStream]]. + ReadableStream* unwrappedStream = unwrappedController->stream(); + + // 3.10.8. Step 2: Let state be stream.[[state]]. + // 3.10.8. Step 3: If state is "errored", return null. + if (unwrappedStream->errored()) { + args.rval().setNull(); + return true; + } + + // 3.10.8. Step 4: If state is "closed", return 0. + if (unwrappedStream->closed()) { + args.rval().setInt32(0); + return true; + } + + // Step 2: Return ! ReadableStreamDefaultControllerGetDesiredSize(this). + args.rval().setNumber( + ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController)); + return true; +} + +/** + * Unified implementation of step 2 of 3.9.4.2 and 3.9.4.3, + * and steps 2-3 of 3.11.4.3. + */ +MOZ_MUST_USE bool js::CheckReadableStreamControllerCanCloseOrEnqueue( + JSContext* cx, Handle<ReadableStreamController*> unwrappedController, + const char* action) { + // 3.9.4.2. close(), step 2, and + // 3.9.4.3. enqueue(chunk), step 2: + // If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is false, + // throw a TypeError exception. + // RSDCCanCloseOrEnqueue returns false in two cases: (1) + // controller.[[closeRequested]] is true; (2) the stream is not readable, + // i.e. already closed or errored. This amounts to exactly the same thing as + // 3.11.4.3 steps 2-3 below, and we want different error messages for the two + // cases anyway. + + // 3.11.4.3. Step 2: If this.[[closeRequested]] is true, throw a TypeError + // exception. + if (unwrappedController->closeRequested()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMCONTROLLER_CLOSED, action); + return false; + } + + // 3.11.4.3. Step 3: If this.[[controlledReadableByteStream]].[[state]] is + // not "readable", throw a TypeError exception. + ReadableStream* unwrappedStream = unwrappedController->stream(); + if (!unwrappedStream->readable()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, + action); + return false; + } + + return true; +} + +/** + * Streams spec, 3.9.4.2 close() + */ +static bool ReadableStreamDefaultController_close(JSContext* cx, unsigned argc, + Value* vp) { + // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a + // TypeError exception. + CallArgs args = CallArgsFromVp(argc, vp); + Rooted<ReadableStreamDefaultController*> unwrappedController( + cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(cx, args, + "close")); + if (!unwrappedController) { + return false; + } + + // Step 2: If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is + // false, throw a TypeError exception. + if (!CheckReadableStreamControllerCanCloseOrEnqueue(cx, unwrappedController, + "close")) { + return false; + } + + // Step 3: Perform ! ReadableStreamDefaultControllerClose(this). + if (!ReadableStreamDefaultControllerClose(cx, unwrappedController)) { + return false; + } + + args.rval().setUndefined(); + return true; +} + +/** + * Streams spec, 3.9.4.3. enqueue ( chunk ) + */ +static bool ReadableStreamDefaultController_enqueue(JSContext* cx, + unsigned argc, Value* vp) { + // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a + // TypeError exception. + CallArgs args = CallArgsFromVp(argc, vp); + Rooted<ReadableStreamDefaultController*> unwrappedController( + cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(cx, args, + "enqueue")); + if (!unwrappedController) { + return false; + } + + // Step 2: If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is + // false, throw a TypeError exception. + if (!CheckReadableStreamControllerCanCloseOrEnqueue(cx, unwrappedController, + "enqueue")) { + return false; + } + + // Step 3: Return ! ReadableStreamDefaultControllerEnqueue(this, chunk). + if (!ReadableStreamDefaultControllerEnqueue(cx, unwrappedController, + args.get(0))) { + return false; + } + args.rval().setUndefined(); + return true; +} + +/** + * Streams spec, 3.9.4.4. error ( e ) + */ +static bool ReadableStreamDefaultController_error(JSContext* cx, unsigned argc, + Value* vp) { + // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a + // TypeError exception. + CallArgs args = CallArgsFromVp(argc, vp); + Rooted<ReadableStreamDefaultController*> unwrappedController( + cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(cx, args, + "enqueue")); + if (!unwrappedController) { + return false; + } + + // Step 2: Perform ! ReadableStreamDefaultControllerError(this, e). + if (!ReadableStreamControllerError(cx, unwrappedController, args.get(0))) { + return false; + } + + args.rval().setUndefined(); + return true; +} + +static const JSPropertySpec ReadableStreamDefaultController_properties[] = { + JS_PSG("desiredSize", ReadableStreamDefaultController_desiredSize, 0), + JS_PS_END}; + +static const JSFunctionSpec ReadableStreamDefaultController_methods[] = { + JS_FN("close", ReadableStreamDefaultController_close, 0, 0), + JS_FN("enqueue", ReadableStreamDefaultController_enqueue, 1, 0), + JS_FN("error", ReadableStreamDefaultController_error, 1, 0), JS_FS_END}; + +JS_STREAMS_CLASS_SPEC(ReadableStreamDefaultController, 0, SlotCount, + ClassSpec::DontDefineConstructor, 0, JS_NULL_CLASS_OPS); + +/** + * Unified implementation of ReadableStream controllers' [[CancelSteps]] + * internal methods. + * Streams spec, 3.9.5.1. [[CancelSteps]] ( reason ) + * and + * Streams spec, 3.11.5.1. [[CancelSteps]] ( reason ) + */ +MOZ_MUST_USE JSObject* js::ReadableStreamControllerCancelSteps( + JSContext* cx, Handle<ReadableStreamController*> unwrappedController, + Handle<Value> reason) { + AssertSameCompartment(cx, reason); + + // Step 1 of 3.11.5.1: If this.[[pendingPullIntos]] is not empty, + if (!unwrappedController->is<ReadableStreamDefaultController>()) { + Rooted<ListObject*> unwrappedPendingPullIntos( + cx, unwrappedController->as<ReadableByteStreamController>() + .pendingPullIntos()); + + if (unwrappedPendingPullIntos->length() != 0) { + // Step a: Let firstDescriptor be the first element of + // this.[[pendingPullIntos]]. + PullIntoDescriptor* unwrappedDescriptor = + UnwrapAndDowncastObject<PullIntoDescriptor>( + cx, &unwrappedPendingPullIntos->get(0).toObject()); + if (!unwrappedDescriptor) { + return nullptr; + } + + // Step b: Set firstDescriptor.[[bytesFilled]] to 0. + unwrappedDescriptor->setBytesFilled(0); + } + } + + Rooted<Value> unwrappedUnderlyingSource( + cx, unwrappedController->underlyingSource()); + + // Step 1 of 3.9.5.1, step 2 of 3.11.5.1: Perform ! ResetQueue(this). + if (!ResetQueue(cx, unwrappedController)) { + return nullptr; + } + + // Step 2 of 3.9.5.1, step 3 of 3.11.5.1: Let result be the result of + // performing this.[[cancelAlgorithm]], passing reason. + // + // Our representation of cancel algorithms is a bit awkward, for + // performance, so we must figure out which algorithm is being invoked. + Rooted<JSObject*> result(cx); + if (IsMaybeWrapped<TeeState>(unwrappedUnderlyingSource)) { + // The cancel algorithm given in ReadableStreamTee step 13 or 14. + 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>()); + Rooted<ReadableStreamDefaultController*> unwrappedDefaultController( + cx, &unwrappedController->as<ReadableStreamDefaultController>()); + result = ReadableStreamTee_Cancel(cx, unwrappedTeeState, + unwrappedDefaultController, reason); + } else if (unwrappedController->hasExternalSource()) { + // An embedding-provided cancel algorithm. + Rooted<Value> rval(cx); + { + AutoRealm ar(cx, unwrappedController); + JS::ReadableStreamUnderlyingSource* source = + unwrappedController->externalSource(); + Rooted<ReadableStream*> stream(cx, unwrappedController->stream()); + Rooted<Value> wrappedReason(cx, reason); + if (!cx->compartment()->wrap(cx, &wrappedReason)) { + return nullptr; + } + + cx->check(stream, wrappedReason); + rval = source->cancel(cx, stream, wrappedReason); + } + + // Make sure the ReadableStreamControllerClearAlgorithms call below is + // reached, even on error. + if (!cx->compartment()->wrap(cx, &rval)) { + result = nullptr; + } else { + result = PromiseObject::unforgeableResolve(cx, rval); + } + } else { + // The algorithm created in + // SetUpReadableByteStreamControllerFromUnderlyingSource step 5. + Rooted<Value> unwrappedCancelMethod(cx, + unwrappedController->cancelMethod()); + if (unwrappedCancelMethod.isUndefined()) { + // CreateAlgorithmFromUnderlyingMethod step 7. + result = PromiseResolvedWithUndefined(cx); + } else { + // CreateAlgorithmFromUnderlyingMethod steps 6.c.i-ii. + { + AutoRealm ar(cx, unwrappedController); + + // |unwrappedCancelMethod| and |unwrappedUnderlyingSource| come directly + // from |unwrappedController| slots so must be same-compartment with it. + cx->check(unwrappedCancelMethod); + cx->check(unwrappedUnderlyingSource); + + Rooted<Value> wrappedReason(cx, reason); + if (!cx->compartment()->wrap(cx, &wrappedReason)) { + return nullptr; + } + + // If PromiseCall fails, don't bail out until after the + // ReadableStreamControllerClearAlgorithms call below. + result = PromiseCall(cx, unwrappedCancelMethod, + unwrappedUnderlyingSource, wrappedReason); + } + if (!cx->compartment()->wrap(cx, &result)) { + result = nullptr; + } + } + } + + // Step 3 (or 4): Perform + // ! ReadableStreamDefaultControllerClearAlgorithms(this) + // (or ReadableByteStreamControllerClearAlgorithms(this)). + ReadableStreamControllerClearAlgorithms(unwrappedController); + + // Step 4 (or 5): Return result. + return result; +} + +/** + * Streams spec, 3.9.5.2. + * ReadableStreamDefaultController [[PullSteps]]( forAuthorCode ) + */ +PromiseObject* js::ReadableStreamDefaultControllerPullSteps( + JSContext* cx, + Handle<ReadableStreamDefaultController*> unwrappedController) { + // Step 1: Let stream be this.[[controlledReadableStream]]. + Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream()); + + // Step 2: If this.[[queue]] is not empty, + Rooted<ListObject*> unwrappedQueue(cx); + Rooted<Value> val( + cx, unwrappedController->getFixedSlot(StreamController::Slot_Queue)); + if (val.isObject()) { + unwrappedQueue = &val.toObject().as<ListObject>(); + } + + if (unwrappedQueue && unwrappedQueue->length() != 0) { + // Step a: Let chunk be ! DequeueValue(this). + Rooted<Value> chunk(cx); + if (!DequeueValue(cx, unwrappedController, &chunk)) { + return nullptr; + } + + // Step b: If this.[[closeRequested]] is true and this.[[queue]] is empty, + if (unwrappedController->closeRequested() && + unwrappedQueue->length() == 0) { + // Step i: Perform ! ReadableStreamDefaultControllerClearAlgorithms(this). + ReadableStreamControllerClearAlgorithms(unwrappedController); + + // Step ii: Perform ! ReadableStreamClose(stream). + if (!ReadableStreamCloseInternal(cx, unwrappedStream)) { + return nullptr; + } + } + + // Step c: Otherwise, perform + // ! ReadableStreamDefaultControllerCallPullIfNeeded(this). + else { + if (!ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController)) { + return nullptr; + } + } + + // Step d: Return a promise resolved with + // ! ReadableStreamCreateReadResult(chunk, false, forAuthorCode). + cx->check(chunk); + ReadableStreamReader* unwrappedReader = + UnwrapReaderFromStream(cx, unwrappedStream); + if (!unwrappedReader) { + return nullptr; + } + + PlainObject* readResultObj = ReadableStreamCreateReadResult( + cx, chunk, false, unwrappedReader->forAuthorCode()); + if (!readResultObj) { + return nullptr; + } + + Rooted<Value> readResult(cx, ObjectValue(*readResultObj)); + return PromiseObject::unforgeableResolveWithNonPromise(cx, readResult); + } + + // Step 3: Let pendingPromise be + // ! ReadableStreamAddReadRequest(stream, forAuthorCode). + Rooted<PromiseObject*> pendingPromise( + cx, ReadableStreamAddReadOrReadIntoRequest(cx, unwrappedStream)); + if (!pendingPromise) { + return nullptr; + } + + // Step 4: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this). + if (!ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController)) { + return nullptr; + } + + // Step 5: Return pendingPromise. + return pendingPromise; +} |