Bug 1507943 - Fix ReadableStream constructor handling of "pull" and "cancel" methods. r=arai

Differential Revision: https://phabricator.services.mozilla.com/D13747

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jason Orendorff 2018-12-07 20:03:16 +00:00
Родитель 31a9f32c95
Коммит 4c4dad7b5a
15 изменённых файлов: 378 добавлений и 234 удалений

Просмотреть файл

@ -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(

Просмотреть файл

@ -242,19 +242,34 @@ class ReadableStreamController : public StreamController {
* Memory layout for ReadableStream controllers, starting after the slots
* reserved for queue container usage.
*
* UnderlyingSource is usually treated as an opaque value. It might be a
* wrapped object from another compartment, but that case is handled
* correctly by all operations the controller might invoke on it.
* Storage of the internal slots listed in the standard is fairly
* straightforward except for [[pullAlgorithm]] and [[cancelAlgorithm]].
* These algorithms are not stored as JSFunction objects. Rather, there are
* three cases:
*
* The only case where we don't treat underlyingSource as an opaque value is
* if it's a TeeState. All functions operating on TeeState properly handle
* TeeState instances from other compartments.
* - Streams created with `new ReadableStream`: The methods are stored
* in Slot_PullMethod and Slot_CancelMethod. The underlying source
* object (`this` for these methods) is in Slot_UnderlyingSource.
*
* - External source streams. Slot_UnderlyingSource is a PrivateValue
* pointing to the JS::ReadableStreamUnderlyingSource object. The
* algorithms are implemented using the .pull() and .cancel() methods
* of that object. Slot_Pull/CancelMethod are undefined.
*
* - Tee streams. Slot_UnderlyingSource is a TeeState object. The
* pull/cancel algorithms are implemented as separate functions in
* Stream.cpp. Slot_Pull/CancelMethod are undefined.
*
* UnderlyingSource, PullMethod, and CancelMethod can be wrappers to objects
* in other compartments.
*
* StrategyHWM and Flags are both primitive (numeric) values.
*/
enum Slots {
Slot_Stream = StreamController::SlotCount,
Slot_UnderlyingSource,
Slot_PullMethod,
Slot_CancelMethod,
Slot_StrategyHWM,
Slot_Flags,
SlotCount
@ -281,6 +296,14 @@ class ReadableStreamController : public StreamController {
void setUnderlyingSource(const Value& underlyingSource) {
setFixedSlot(Slot_UnderlyingSource, underlyingSource);
}
Value pullMethod() const { return getFixedSlot(Slot_PullMethod); }
void setPullMethod(const Value& pullMethod) {
setFixedSlot(Slot_PullMethod, pullMethod);
}
Value cancelMethod() const { return getFixedSlot(Slot_CancelMethod); }
void setCancelMethod(const Value& cancelMethod) {
setFixedSlot(Slot_CancelMethod, cancelMethod);
}
JS::ReadableStreamUnderlyingSource* externalSource() const {
static_assert(alignof(JS::ReadableStreamUnderlyingSource) >= 2,
"External underling sources are stored as PrivateValues, "

Просмотреть файл

@ -0,0 +1,2 @@
load(libdir + "web-platform-testharness.js");
load_web_platform_test("streams/readable-streams/constructor.js");

Просмотреть файл

@ -1,13 +1,4 @@
[bad-underlying-sources.dedicatedworker.html]
[Underlying source: throwing pull getter (initial pull)]
expected: FAIL
[Underlying source pull: throwing getter (second pull does not result in a second get)]
expected: FAIL
[Underlying source cancel: throwing getter]
expected: FAIL
[Underlying source: calling error twice should not throw]
expected: FAIL

Просмотреть файл

@ -1,13 +1,4 @@
[bad-underlying-sources.html]
[Underlying source: throwing pull getter (initial pull)]
expected: FAIL
[Underlying source pull: throwing getter (second pull does not result in a second get)]
expected: FAIL
[Underlying source cancel: throwing getter]
expected: FAIL
[Underlying source: calling error twice should not throw]
expected: FAIL

Просмотреть файл

@ -1,13 +1,4 @@
[bad-underlying-sources.serviceworker.https.html]
[Underlying source: throwing pull getter (initial pull)]
expected: FAIL
[Underlying source pull: throwing getter (second pull does not result in a second get)]
expected: FAIL
[Underlying source cancel: throwing getter]
expected: FAIL
[Underlying source: calling error twice should not throw]
expected: FAIL

Просмотреть файл

@ -1,13 +1,4 @@
[bad-underlying-sources.sharedworker.html]
[Underlying source: throwing pull getter (initial pull)]
expected: FAIL
[Underlying source pull: throwing getter (second pull does not result in a second get)]
expected: FAIL
[Underlying source cancel: throwing getter]
expected: FAIL
[Underlying source: calling error twice should not throw]
expected: FAIL

Просмотреть файл

@ -1,19 +0,0 @@
[constructor.dedicatedworker.html]
[ReadableStream constructor should stop after get on pull fails]
expected: FAIL
[ReadableStream constructor should stop after validate on pull fails]
expected: FAIL
[ReadableStream constructor should stop after get on cancel fails]
expected: FAIL
[ReadableStream constructor should stop after validate on cancel fails]
expected: FAIL
[ReadableStream constructor should stop after get on start fails]
expected: FAIL
[ReadableStream constructor should stop after validate on start fails]
expected: FAIL

Просмотреть файл

@ -1,19 +0,0 @@
[constructor.html]
[ReadableStream constructor should stop after get on pull fails]
expected: FAIL
[ReadableStream constructor should stop after validate on pull fails]
expected: FAIL
[ReadableStream constructor should stop after get on cancel fails]
expected: FAIL
[ReadableStream constructor should stop after validate on cancel fails]
expected: FAIL
[ReadableStream constructor should stop after get on start fails]
expected: FAIL
[ReadableStream constructor should stop after validate on start fails]
expected: FAIL

Просмотреть файл

@ -1,19 +0,0 @@
[constructor.serviceworker.https.html]
[ReadableStream constructor should stop after get on pull fails]
expected: FAIL
[ReadableStream constructor should stop after validate on pull fails]
expected: FAIL
[ReadableStream constructor should stop after get on cancel fails]
expected: FAIL
[ReadableStream constructor should stop after validate on cancel fails]
expected: FAIL
[ReadableStream constructor should stop after get on start fails]
expected: FAIL
[ReadableStream constructor should stop after validate on start fails]
expected: FAIL

Просмотреть файл

@ -1,19 +0,0 @@
[constructor.sharedworker.html]
[ReadableStream constructor should stop after get on pull fails]
expected: FAIL
[ReadableStream constructor should stop after validate on pull fails]
expected: FAIL
[ReadableStream constructor should stop after get on cancel fails]
expected: FAIL
[ReadableStream constructor should stop after validate on cancel fails]
expected: FAIL
[ReadableStream constructor should stop after get on start fails]
expected: FAIL
[ReadableStream constructor should stop after validate on start fails]
expected: FAIL

Просмотреть файл

@ -2,9 +2,3 @@
[ReadableStream instances should have the correct list of properties]
expected: FAIL
[ReadableStream constructor will not tolerate initial garbage as cancel argument]
expected: FAIL
[ReadableStream constructor will not tolerate initial garbage as pull argument]
expected: FAIL

Просмотреть файл

@ -2,9 +2,3 @@
[ReadableStream instances should have the correct list of properties]
expected: FAIL
[ReadableStream constructor will not tolerate initial garbage as cancel argument]
expected: FAIL
[ReadableStream constructor will not tolerate initial garbage as pull argument]
expected: FAIL

Просмотреть файл

@ -2,9 +2,3 @@
[ReadableStream instances should have the correct list of properties]
expected: FAIL
[ReadableStream constructor will not tolerate initial garbage as cancel argument]
expected: FAIL
[ReadableStream constructor will not tolerate initial garbage as pull argument]
expected: FAIL

Просмотреть файл

@ -2,9 +2,3 @@
[ReadableStream instances should have the correct list of properties]
expected: FAIL
[ReadableStream constructor will not tolerate initial garbage as cancel argument]
expected: FAIL
[ReadableStream constructor will not tolerate initial garbage as pull argument]
expected: FAIL