|
|
|
@ -9,6 +9,7 @@
|
|
|
|
|
#include "js/Stream.h"
|
|
|
|
|
|
|
|
|
|
#include "gc/Heap.h"
|
|
|
|
|
#include "vm/Interpreter.h"
|
|
|
|
|
#include "vm/JSContext.h"
|
|
|
|
|
#include "vm/SelfHosting.h"
|
|
|
|
|
|
|
|
|
@ -121,10 +122,6 @@ inline static MOZ_MUST_USE bool InvokeOrNoop(JSContext* cx, HandleValue O,
|
|
|
|
|
HandleValue arg,
|
|
|
|
|
MutableHandleValue rval);
|
|
|
|
|
|
|
|
|
|
static MOZ_MUST_USE JSObject* PromiseInvokeOrNoop(JSContext* cx, HandleValue O,
|
|
|
|
|
HandlePropertyName P,
|
|
|
|
|
HandleValue arg);
|
|
|
|
|
|
|
|
|
|
static MOZ_MUST_USE JSObject* PromiseRejectedWithPendingError(JSContext* cx) {
|
|
|
|
|
RootedValue exn(cx);
|
|
|
|
|
if (!cx->isExceptionPending() || !GetAndClearException(cx, &exn)) {
|
|
|
|
@ -470,9 +467,21 @@ const Class TeeState::class_ = {"TeeState",
|
|
|
|
|
|
|
|
|
|
/*** 3.2. Class ReadableStream **********************************************/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Characterizes the family of algorithms, (startAlgorithm, pullAlgorithm,
|
|
|
|
|
* cancelAlgorithm), associated with a readable stream.
|
|
|
|
|
*
|
|
|
|
|
* See the comment on SetUpReadableStreamDefaultController().
|
|
|
|
|
*/
|
|
|
|
|
enum class SourceAlgorithms {
|
|
|
|
|
Script,
|
|
|
|
|
Tee,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static MOZ_MUST_USE bool SetUpReadableStreamDefaultController(
|
|
|
|
|
JSContext* cx, Handle<ReadableStream*> stream, HandleValue underlyingSource,
|
|
|
|
|
double highWaterMarkVal, HandleValue size);
|
|
|
|
|
JSContext* cx, Handle<ReadableStream*> stream, SourceAlgorithms algorithms,
|
|
|
|
|
HandleValue underlyingSource, HandleValue pullMethod,
|
|
|
|
|
HandleValue cancelMethod, double highWaterMark, HandleValue size);
|
|
|
|
|
|
|
|
|
|
static MOZ_MUST_USE ReadableByteStreamController*
|
|
|
|
|
CreateExternalReadableByteStreamController(
|
|
|
|
@ -503,6 +512,11 @@ static MOZ_MUST_USE bool MakeSizeAlgorithmFromSizeFunction(JSContext* cx,
|
|
|
|
|
static MOZ_MUST_USE bool ValidateAndNormalizeHighWaterMark(
|
|
|
|
|
JSContext* cx, HandleValue highWaterMarkVal, double* highWaterMark);
|
|
|
|
|
|
|
|
|
|
static MOZ_MUST_USE bool
|
|
|
|
|
SetUpReadableStreamDefaultControllerFromUnderlyingSource(
|
|
|
|
|
JSContext* cx, Handle<ReadableStream*> stream, HandleValue underlyingSource,
|
|
|
|
|
double highWaterMark, HandleValue sizeAlgorithm);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Streams spec, 3.2.3. new ReadableStream(underlyingSource = {}, strategy = {})
|
|
|
|
|
*/
|
|
|
|
@ -605,8 +619,8 @@ bool ReadableStream::constructor(JSContext* cx, unsigned argc, Value* vp) {
|
|
|
|
|
// Step 7.d: Perform
|
|
|
|
|
// ? SetUpReadableStreamDefaultControllerFromUnderlyingSource(
|
|
|
|
|
// this, underlyingSource, highWaterMark, sizeAlgorithm).
|
|
|
|
|
if (!SetUpReadableStreamDefaultController(cx, stream, underlyingSource,
|
|
|
|
|
highWaterMark, size)) {
|
|
|
|
|
if (!SetUpReadableStreamDefaultControllerFromUnderlyingSource(
|
|
|
|
|
cx, stream, underlyingSource, highWaterMark, size)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -811,11 +825,14 @@ CLASS_SPEC(ReadableStream, 0, SlotCount, 0, 0, JS_NULL_CLASS_OPS);
|
|
|
|
|
* startAlgorithm, pullAlgorithm, cancelAlgorithm
|
|
|
|
|
* [, highWaterMark [, sizeAlgorithm ] ] )
|
|
|
|
|
*
|
|
|
|
|
* The start/pull/cancelAlgorithm arguments are represented as a single
|
|
|
|
|
* underlyingSource argument; see SetUpReadableStreamDefaultController().
|
|
|
|
|
* The start/pull/cancelAlgorithm arguments are represented instead as four
|
|
|
|
|
* arguments: sourceAlgorithms, underlyingSource, pullMethod, cancelMethod.
|
|
|
|
|
* See the comment on SetUpReadableStreamDefaultController.
|
|
|
|
|
*/
|
|
|
|
|
MOZ_MUST_USE ReadableStream* CreateReadableStream(
|
|
|
|
|
JSContext* cx, HandleValue underlyingSource, double highWaterMark = 1,
|
|
|
|
|
JSContext* cx, SourceAlgorithms sourceAlgorithms,
|
|
|
|
|
HandleValue underlyingSource, HandleValue pullMethod = UndefinedHandleValue,
|
|
|
|
|
HandleValue cancelMethod = UndefinedHandleValue, double highWaterMark = 1,
|
|
|
|
|
HandleValue sizeAlgorithm = UndefinedHandleValue,
|
|
|
|
|
HandleObject proto = nullptr) {
|
|
|
|
|
cx->check(underlyingSource, sizeAlgorithm, proto);
|
|
|
|
@ -839,8 +856,10 @@ MOZ_MUST_USE ReadableStream* CreateReadableStream(
|
|
|
|
|
// Step 7: Perform ? SetUpReadableStreamDefaultController(stream,
|
|
|
|
|
// controller, startAlgorithm, pullAlgorithm, cancelAlgorithm,
|
|
|
|
|
// highWaterMark, sizeAlgorithm).
|
|
|
|
|
if (!SetUpReadableStreamDefaultController(cx, stream, underlyingSource,
|
|
|
|
|
highWaterMark, sizeAlgorithm)) {
|
|
|
|
|
|
|
|
|
|
if (!SetUpReadableStreamDefaultController(
|
|
|
|
|
cx, stream, sourceAlgorithms, underlyingSource, pullMethod,
|
|
|
|
|
cancelMethod, highWaterMark, sizeAlgorithm)) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -1250,7 +1269,8 @@ static MOZ_MUST_USE bool ReadableStreamTee(
|
|
|
|
|
// ! CreateReadableStream(startAlgorithm, pullAlgorithm,
|
|
|
|
|
// cancel1Algorithm).
|
|
|
|
|
RootedValue underlyingSource(cx, ObjectValue(*teeState));
|
|
|
|
|
branch1Stream.set(CreateReadableStream(cx, underlyingSource));
|
|
|
|
|
branch1Stream.set(
|
|
|
|
|
CreateReadableStream(cx, SourceAlgorithms::Tee, underlyingSource));
|
|
|
|
|
if (!branch1Stream) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
@ -1263,7 +1283,8 @@ static MOZ_MUST_USE bool ReadableStreamTee(
|
|
|
|
|
// Step 17: Set branch2 to
|
|
|
|
|
// ! CreateReadableStream(startAlgorithm, pullAlgorithm,
|
|
|
|
|
// cancel2Algorithm).
|
|
|
|
|
branch2Stream.set(CreateReadableStream(cx, underlyingSource));
|
|
|
|
|
branch2Stream.set(
|
|
|
|
|
CreateReadableStream(cx, SourceAlgorithms::Tee, underlyingSource));
|
|
|
|
|
if (!branch2Stream) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
@ -2446,6 +2467,12 @@ static const JSFunctionSpec ReadableStreamDefaultController_methods[] = {
|
|
|
|
|
CLASS_SPEC(ReadableStreamDefaultController, 0, SlotCount,
|
|
|
|
|
ClassSpec::DontDefineConstructor, 0, JS_NULL_CLASS_OPS);
|
|
|
|
|
|
|
|
|
|
static MOZ_MUST_USE JSObject* PromiseCall(JSContext* cx, HandleValue F,
|
|
|
|
|
HandleValue V, HandleValue arg);
|
|
|
|
|
|
|
|
|
|
static void ReadableStreamControllerClearAlgorithms(
|
|
|
|
|
ReadableStreamController* controller);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Unified implementation of ReadableStream controllers' [[CancelSteps]]
|
|
|
|
|
* internal methods.
|
|
|
|
@ -2487,26 +2514,25 @@ static MOZ_MUST_USE JSObject* ReadableStreamControllerCancelSteps(
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Step 2 of 3.8.5.1, step 3 of 3.10.5.1:
|
|
|
|
|
// Return ! PromiseInvokeOrNoop(this.[[underlying(Byte)Source]],
|
|
|
|
|
// "cancel", « reason »)
|
|
|
|
|
// Note: this special-cases the underlying source of tee'd stream's
|
|
|
|
|
// branches. Instead of storing a JSFunction as the "cancel" property on
|
|
|
|
|
// those, we check if the source is a, maybe wrapped, TeeState instance
|
|
|
|
|
// and manually dispatch to the right internal function. TeeState is fully
|
|
|
|
|
// under our control, so this isn't content-observable.
|
|
|
|
|
// Step 2 of 3.8.5.1, step 3 of 3.10.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.
|
|
|
|
|
RootedObject result(cx);
|
|
|
|
|
if (IsMaybeWrapped<TeeState>(unwrappedUnderlyingSource)) {
|
|
|
|
|
Rooted<TeeState*> unwrappedteeState(cx);
|
|
|
|
|
unwrappedteeState =
|
|
|
|
|
&unwrappedUnderlyingSource.toObject().unwrapAs<TeeState>();
|
|
|
|
|
Rooted<ReadableStreamDefaultController*> unwrappedDefaultController(cx);
|
|
|
|
|
unwrappedDefaultController =
|
|
|
|
|
&unwrappedController->as<ReadableStreamDefaultController>();
|
|
|
|
|
return ReadableStreamTee_Cancel(cx, unwrappedteeState,
|
|
|
|
|
unwrappedDefaultController, reason);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (unwrappedController->hasExternalSource()) {
|
|
|
|
|
// 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.
|
|
|
|
|
RootedValue rval(cx);
|
|
|
|
|
{
|
|
|
|
|
AutoRealm ar(cx, unwrappedController);
|
|
|
|
@ -2522,21 +2548,50 @@ static MOZ_MUST_USE JSObject* ReadableStreamControllerCancelSteps(
|
|
|
|
|
rval = source->cancel(cx, stream, wrappedReason);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make sure the ReadableStreamControllerClearAlgorithms call below is
|
|
|
|
|
// reached, even on error.
|
|
|
|
|
if (!cx->compartment()->wrap(cx, &rval)) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
result = nullptr;
|
|
|
|
|
} else {
|
|
|
|
|
result = PromiseObject::unforgeableResolve(cx, rval);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// The algorithm created in
|
|
|
|
|
// SetUpReadableByteStreamControllerFromUnderlyingSource step 5.
|
|
|
|
|
RootedValue unwrappedCancelMethod(cx, unwrappedController->cancelMethod());
|
|
|
|
|
if (unwrappedCancelMethod.isUndefined()) {
|
|
|
|
|
// CreateAlgorithmFromUnderlyingMethod step 7.
|
|
|
|
|
result = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
|
|
|
|
|
} else {
|
|
|
|
|
// CreateAlgorithmFromUnderlyingMethod steps 6.c.i-ii.
|
|
|
|
|
{
|
|
|
|
|
AutoRealm ar(cx, &unwrappedCancelMethod.toObject());
|
|
|
|
|
RootedValue underlyingSource(cx, unwrappedUnderlyingSource);
|
|
|
|
|
if (!cx->compartment()->wrap(cx, &underlyingSource)) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
RootedValue 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, underlyingSource,
|
|
|
|
|
wrappedReason);
|
|
|
|
|
}
|
|
|
|
|
if (!cx->compartment()->wrap(cx, &result)) {
|
|
|
|
|
result = nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return PromiseObject::unforgeableResolve(cx, rval);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the stream and its controller aren't in the cx compartment, we have
|
|
|
|
|
// to ensure that the underlying source is correctly wrapped before
|
|
|
|
|
// operating on it.
|
|
|
|
|
if (!cx->compartment()->wrap(cx, &unwrappedUnderlyingSource)) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
// Step 3 of 3.8.5.1, step 4 of 3.10.5.1: Perform
|
|
|
|
|
// ! ReadableByteStreamControllerClearAlgorithms(this).
|
|
|
|
|
ReadableStreamControllerClearAlgorithms(unwrappedController);
|
|
|
|
|
|
|
|
|
|
return PromiseInvokeOrNoop(cx, unwrappedUnderlyingSource, cx->names().cancel,
|
|
|
|
|
reason);
|
|
|
|
|
// Step 4 of 3.8.5.1, step 5 of 3.10.5.1: Return result.
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline static MOZ_MUST_USE bool DequeueValue(
|
|
|
|
@ -2699,7 +2754,8 @@ static MOZ_MUST_USE double ReadableStreamControllerGetDesiredSizeUnchecked(
|
|
|
|
|
inline static MOZ_MUST_USE bool ReadableStreamControllerCallPullIfNeeded(
|
|
|
|
|
JSContext* cx, Handle<ReadableStreamController*> unwrappedController) {
|
|
|
|
|
// Step 1: Let shouldPull be
|
|
|
|
|
// ! ReadableByteStreamControllerShouldCallPull(controller).
|
|
|
|
|
// ! ReadableStreamDefaultControllerShouldCallPull(controller).
|
|
|
|
|
// (ReadableByteStreamDefaultControllerShouldCallPull in 3.12.3.)
|
|
|
|
|
bool shouldPull = ReadableStreamControllerShouldCallPull(unwrappedController);
|
|
|
|
|
|
|
|
|
|
// Step 2: If shouldPull is false, return.
|
|
|
|
@ -2722,19 +2778,23 @@ inline static MOZ_MUST_USE bool ReadableStreamControllerCallPullIfNeeded(
|
|
|
|
|
// Step 5: Set controller.[[pulling]] to true.
|
|
|
|
|
unwrappedController->setPulling();
|
|
|
|
|
|
|
|
|
|
// Step 6: Let pullPromise be
|
|
|
|
|
// ! PromiseInvokeOrNoop(controller.[[underlyingByteSource]],
|
|
|
|
|
// "pull", controller).
|
|
|
|
|
// We use this variable in step 7. For ease of error-handling, we wrap it
|
|
|
|
|
// early.
|
|
|
|
|
RootedObject wrappedController(cx, unwrappedController);
|
|
|
|
|
if (!cx->compartment()->wrap(cx, &wrappedController)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
RootedValue controllerVal(cx, ObjectValue(*wrappedController));
|
|
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
RootedObject pullPromise(cx);
|
|
|
|
|
RootedValue unwrappedUnderlyingSource(
|
|
|
|
|
cx, unwrappedController->underlyingSource());
|
|
|
|
|
RootedObject pullPromise(cx);
|
|
|
|
|
|
|
|
|
|
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");
|
|
|
|
@ -2742,6 +2802,7 @@ inline static MOZ_MUST_USE bool ReadableStreamControllerCallPullIfNeeded(
|
|
|
|
|
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 =
|
|
|
|
@ -2753,12 +2814,34 @@ inline static MOZ_MUST_USE bool ReadableStreamControllerCallPullIfNeeded(
|
|
|
|
|
}
|
|
|
|
|
pullPromise = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
|
|
|
|
|
} else {
|
|
|
|
|
RootedValue underlyingSource(cx, unwrappedUnderlyingSource);
|
|
|
|
|
if (!cx->compartment()->wrap(cx, &underlyingSource)) {
|
|
|
|
|
return false;
|
|
|
|
|
// The pull algorithm created in
|
|
|
|
|
// SetUpReadableStreamDefaultControllerFromUnderlyingSource step 4.
|
|
|
|
|
RootedValue unwrappedPullMethod(cx, unwrappedController->pullMethod());
|
|
|
|
|
if (unwrappedPullMethod.isUndefined()) {
|
|
|
|
|
// CreateAlgorithmFromUnderlyingMethod step 7.
|
|
|
|
|
pullPromise = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
|
|
|
|
|
} else {
|
|
|
|
|
// CreateAlgorithmFromUnderlyingMethod step 6.b.i.
|
|
|
|
|
{
|
|
|
|
|
AutoRealm ar(cx, &unwrappedPullMethod.toObject());
|
|
|
|
|
RootedValue underlyingSource(cx, unwrappedUnderlyingSource);
|
|
|
|
|
if (!cx->compartment()->wrap(cx, &underlyingSource)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
RootedValue controller(cx, ObjectValue(*unwrappedController));
|
|
|
|
|
if (!cx->compartment()->wrap(cx, &controller)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
pullPromise =
|
|
|
|
|
PromiseCall(cx, unwrappedPullMethod, underlyingSource, controller);
|
|
|
|
|
if (!pullPromise) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!cx->compartment()->wrap(cx, &pullPromise)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pullPromise = PromiseInvokeOrNoop(cx, underlyingSource, cx->names().pull,
|
|
|
|
|
controllerVal);
|
|
|
|
|
}
|
|
|
|
|
if (!pullPromise) {
|
|
|
|
|
return false;
|
|
|
|
@ -2830,6 +2913,28 @@ static bool ReadableStreamControllerShouldCallPull(
|
|
|
|
|
return desiredSize > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Streams spec, 3.9.4.
|
|
|
|
|
* ReadableStreamDefaultControllerClearAlgorithms ( controller )
|
|
|
|
|
* and 3.12.4.
|
|
|
|
|
* ReadableByteStreamControllerClearAlgorithms ( controller )
|
|
|
|
|
*/
|
|
|
|
|
static void ReadableStreamControllerClearAlgorithms(
|
|
|
|
|
ReadableStreamController* controller) {
|
|
|
|
|
// Step 1: Set controller.[[pullAlgorithm]] to undefined.
|
|
|
|
|
controller->setPullMethod(UndefinedHandleValue);
|
|
|
|
|
|
|
|
|
|
// Step 2: Set controller.[[cancelAlgorithm]] to undefined.
|
|
|
|
|
controller->setCancelMethod(UndefinedHandleValue);
|
|
|
|
|
|
|
|
|
|
// Step 3 (of 3.9.4 only) : Set controller.[[strategySizeAlgorithm]] to
|
|
|
|
|
// undefined.
|
|
|
|
|
if (controller->is<ReadableStreamDefaultController>()) {
|
|
|
|
|
controller->as<ReadableStreamDefaultController>().setStrategySize(
|
|
|
|
|
UndefinedHandleValue);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Streams spec, 3.9.5. ReadableStreamDefaultControllerClose ( controller )
|
|
|
|
|
*/
|
|
|
|
@ -3026,18 +3131,44 @@ static MOZ_MUST_USE double ReadableStreamControllerGetDesiredSizeUnchecked(
|
|
|
|
|
* The standard algorithm takes a `controller` argument which must be a new,
|
|
|
|
|
* blank object. This implementation creates a new controller instead.
|
|
|
|
|
*
|
|
|
|
|
* The standard algorithm takes startAlgorithm, pullAlgorithm, and
|
|
|
|
|
* cancelAlgorithm as separate arguments. We will do the same, but for now all
|
|
|
|
|
* of them are passed as a single underlyingSource argument--with a few
|
|
|
|
|
* user-visible differences in behavior (bug 1507943).
|
|
|
|
|
* 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.9.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.3.9. ReadableStreamTee.
|
|
|
|
|
*
|
|
|
|
|
* Note: All arguments must be same-compartment with cx. ReadableStream
|
|
|
|
|
* controllers are always created in the same compartment as the stream.
|
|
|
|
|
*/
|
|
|
|
|
static MOZ_MUST_USE bool SetUpReadableStreamDefaultController(
|
|
|
|
|
JSContext* cx, Handle<ReadableStream*> stream, HandleValue underlyingSource,
|
|
|
|
|
double highWaterMark, HandleValue size) {
|
|
|
|
|
JSContext* cx, Handle<ReadableStream*> stream,
|
|
|
|
|
SourceAlgorithms sourceAlgorithms, HandleValue underlyingSource,
|
|
|
|
|
HandleValue pullMethod, HandleValue cancelMethod, double highWaterMark,
|
|
|
|
|
HandleValue 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));
|
|
|
|
|
|
|
|
|
@ -3070,23 +3201,20 @@ static MOZ_MUST_USE bool SetUpReadableStreamDefaultController(
|
|
|
|
|
controller->setStrategyHWM(highWaterMark);
|
|
|
|
|
|
|
|
|
|
// Step 6: Set controller.[[pullAlgorithm]] to pullAlgorithm.
|
|
|
|
|
// Step 7: Set controller.[[cancelAlgorithm]] to cancelAlgorithm.
|
|
|
|
|
//
|
|
|
|
|
// For the moment, these algorithms are represented using the
|
|
|
|
|
// underlyingSource (bug 1507943). For example, when the underlying source
|
|
|
|
|
// is a TeeState, we use the ReadableStreamTee algorithms for pulling and
|
|
|
|
|
// canceling.
|
|
|
|
|
// (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.
|
|
|
|
|
// If this is a tee stream, the startAlgorithm does nothing and returns
|
|
|
|
|
// undefined.
|
|
|
|
|
RootedValue startResult(cx);
|
|
|
|
|
if (!underlyingSource.isObject() ||
|
|
|
|
|
!underlyingSource.toObject().is<TeeState>()) {
|
|
|
|
|
if (sourceAlgorithms == SourceAlgorithms::Script) {
|
|
|
|
|
RootedValue controllerVal(cx, ObjectValue(*controller));
|
|
|
|
|
if (!InvokeOrNoop(cx, underlyingSource, cx->names().start, controllerVal,
|
|
|
|
|
&startResult)) {
|
|
|
|
@ -3123,6 +3251,61 @@ static MOZ_MUST_USE bool SetUpReadableStreamDefaultController(
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static MOZ_MUST_USE bool CreateAlgorithmFromUnderlyingMethod(
|
|
|
|
|
JSContext* cx, HandleValue underlyingObject,
|
|
|
|
|
const char* methodNameForErrorMessage, HandlePropertyName methodName,
|
|
|
|
|
MutableHandleValue method);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Streams spec, 3.9.12.
|
|
|
|
|
* SetUpReadableStreamDefaultControllerFromUnderlyingSource( stream,
|
|
|
|
|
* underlyingSource, highWaterMark, sizeAlgorithm )
|
|
|
|
|
*/
|
|
|
|
|
static MOZ_MUST_USE bool
|
|
|
|
|
SetUpReadableStreamDefaultControllerFromUnderlyingSource(
|
|
|
|
|
JSContext* cx, Handle<ReadableStream*> stream, HandleValue underlyingSource,
|
|
|
|
|
double highWaterMark, HandleValue 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 »).
|
|
|
|
|
RootedValue 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, « »).
|
|
|
|
|
RootedValue 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** 3.10. Class ReadableByteStreamController *******************************/
|
|
|
|
|
|
|
|
|
|
#if 0 // disable user-defined byte streams
|
|
|
|
@ -4027,6 +4210,64 @@ inline static MOZ_MUST_USE bool AppendToListAtSlot(
|
|
|
|
|
return list->append(cx, val);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Streams spec, 6.3.1.
|
|
|
|
|
* CreateAlgorithmFromUnderlyingMethod ( underlyingObject, methodName,
|
|
|
|
|
* algoArgCount, extraArgs )
|
|
|
|
|
*
|
|
|
|
|
* This function only partly implements the standard algorithm. We do not
|
|
|
|
|
* actually create a new JSFunction completely encapsulating the new algorithm.
|
|
|
|
|
* Instead, this just gets the specified method and checks for errors. It's the
|
|
|
|
|
* caller's responsibility to make sure that later, when the algorithm is
|
|
|
|
|
* "performed", the appropriate steps are carried out.
|
|
|
|
|
*/
|
|
|
|
|
static MOZ_MUST_USE bool CreateAlgorithmFromUnderlyingMethod(
|
|
|
|
|
JSContext* cx, HandleValue underlyingObject,
|
|
|
|
|
const char* methodNameForErrorMessage, HandlePropertyName methodName,
|
|
|
|
|
MutableHandleValue method) {
|
|
|
|
|
// Step 1: Assert: underlyingObject is not undefined.
|
|
|
|
|
MOZ_ASSERT(!underlyingObject.isUndefined());
|
|
|
|
|
|
|
|
|
|
// 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).
|
|
|
|
|
if (!GetProperty(cx, underlyingObject, methodName, method)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Step 6: If method is not undefined,
|
|
|
|
|
if (!method.isUndefined()) {
|
|
|
|
|
// Step a: If ! IsCallable(method) is false, throw a TypeError
|
|
|
|
|
// exception.
|
|
|
|
|
if (!IsCallable(method)) {
|
|
|
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
|
|
|
|
JSMSG_NOT_FUNCTION, methodNameForErrorMessage);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Step b: If algoArgCount is 0, return an algorithm that performs the
|
|
|
|
|
// following steps:
|
|
|
|
|
// Step i: Return ! PromiseCall(method, underlyingObject,
|
|
|
|
|
// extraArgs).
|
|
|
|
|
// Step c: Otherwise, return an algorithm that performs the following
|
|
|
|
|
// steps, taking an arg argument:
|
|
|
|
|
// Step i: Let fullArgs be a List consisting of arg followed by the
|
|
|
|
|
// elements of extraArgs in order.
|
|
|
|
|
// Step ii: Return ! PromiseCall(method, underlyingObject,
|
|
|
|
|
// fullArgs).
|
|
|
|
|
// (These steps are deferred to the code that performs the algorithm.
|
|
|
|
|
// See ReadableStreamControllerCancelSteps and
|
|
|
|
|
// ReadableStreamControllerCallPullIfNeeded.)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Step 7: Return an algorithm which returns a promise resolved with
|
|
|
|
|
// undefined (implicit).
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Streams spec, 6.3.2. InvokeOrNoop ( O, P, args )
|
|
|
|
|
*/
|
|
|
|
@ -4054,30 +4295,30 @@ inline static MOZ_MUST_USE bool InvokeOrNoop(JSContext* cx, HandleValue O,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Streams spec, obsolete (previously 6.4.3) PromiseInvokeOrNoop ( O, P, args )
|
|
|
|
|
* Specialized to one arg, because that's what all stream related callers use.
|
|
|
|
|
* Streams spec, 6.3.5. PromiseCall ( F, V, args )
|
|
|
|
|
* As it happens, all callers pass exactly one argument.
|
|
|
|
|
*/
|
|
|
|
|
static MOZ_MUST_USE JSObject* PromiseInvokeOrNoop(JSContext* cx, HandleValue O,
|
|
|
|
|
HandlePropertyName P,
|
|
|
|
|
HandleValue arg) {
|
|
|
|
|
cx->check(O, P, arg);
|
|
|
|
|
static MOZ_MUST_USE JSObject* PromiseCall(JSContext* cx, HandleValue F,
|
|
|
|
|
HandleValue V, HandleValue arg) {
|
|
|
|
|
cx->check(F, V, arg);
|
|
|
|
|
|
|
|
|
|
// Step 1: Assert: O is not undefined.
|
|
|
|
|
MOZ_ASSERT(!O.isUndefined());
|
|
|
|
|
// Step 1: Assert: ! IsCallable(F) is true.
|
|
|
|
|
MOZ_ASSERT(IsCallable(F));
|
|
|
|
|
|
|
|
|
|
// Step 2: Assert: ! IsPropertyKey(P) is true (implicit).
|
|
|
|
|
// Step 3: Assert: args is a List (omitted).
|
|
|
|
|
// Step 2: Assert: V is not undefined.
|
|
|
|
|
MOZ_ASSERT(!V.isUndefined());
|
|
|
|
|
|
|
|
|
|
// Step 4: Let returnValue be InvokeOrNoop(O, P, args).
|
|
|
|
|
// Step 5: If returnValue is an abrupt completion, return a promise
|
|
|
|
|
// rejected with returnValue.[[Value]].
|
|
|
|
|
RootedValue returnValue(cx);
|
|
|
|
|
if (!InvokeOrNoop(cx, O, P, arg, &returnValue)) {
|
|
|
|
|
// Step 3: Assert: args is a List (implicit).
|
|
|
|
|
// Step 4: Let returnValue be Call(F, V, args).
|
|
|
|
|
RootedValue rval(cx);
|
|
|
|
|
if (!Call(cx, F, V, arg, &rval)) {
|
|
|
|
|
// Step 5: If returnValue is an abrupt completion, return a promise rejected
|
|
|
|
|
// with returnValue.[[Value]].
|
|
|
|
|
return PromiseRejectedWithPendingError(cx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Step 6: Otherwise, return a promise resolved with returnValue.[[Value]].
|
|
|
|
|
return PromiseObject::unforgeableResolve(cx, returnValue);
|
|
|
|
|
return PromiseObject::unforgeableResolve(cx, rval);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -4153,16 +4394,30 @@ JS_PUBLIC_API JSObject* JS::NewReadableDefaultStreamObject(
|
|
|
|
|
cx->check(underlyingSource, size, proto);
|
|
|
|
|
MOZ_ASSERT(highWaterMark >= 0);
|
|
|
|
|
|
|
|
|
|
RootedObject source(cx, underlyingSource);
|
|
|
|
|
if (!source) {
|
|
|
|
|
source = NewBuiltinClassInstance<PlainObject>(cx);
|
|
|
|
|
// A copy of ReadableStream::constructor, with most of the
|
|
|
|
|
// argument-checking done implicitly by C++ type checking.
|
|
|
|
|
Rooted<ReadableStream*> stream(cx, ReadableStream::create(cx));
|
|
|
|
|
if (!stream) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
RootedValue sourceVal(cx);
|
|
|
|
|
if (underlyingSource) {
|
|
|
|
|
sourceVal.setObject(*underlyingSource);
|
|
|
|
|
} else {
|
|
|
|
|
JSObject* source = NewBuiltinClassInstance<PlainObject>(cx);
|
|
|
|
|
if (!source) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
sourceVal.setObject(*source);
|
|
|
|
|
}
|
|
|
|
|
RootedValue sourceVal(cx, ObjectValue(*source));
|
|
|
|
|
RootedValue sizeVal(cx, size ? ObjectValue(*size) : UndefinedValue());
|
|
|
|
|
return CreateReadableStream(cx, sourceVal, highWaterMark, sizeVal);
|
|
|
|
|
|
|
|
|
|
if (!SetUpReadableStreamDefaultControllerFromUnderlyingSource(
|
|
|
|
|
cx, stream, sourceVal, highWaterMark, sizeVal)) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return stream;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JS_PUBLIC_API JSObject* JS::NewReadableExternalSourceStreamObject(
|
|
|
|
|