Bug 1502355 - Fill in more of |ReadableStreamPipeTo|, as regards consing up a |PipeToState| closure to store state variables. r=arai

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jeff Walden 2020-03-03 21:07:26 +00:00
Родитель d451364e60
Коммит d92c45cab7
6 изменённых файлов: 242 добавлений и 18 удалений

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

@ -8,25 +8,119 @@
#include "builtin/streams/PipeToState.h"
#include "js/Class.h" // JSClass, JSCLASS_HAS_RESERVED_SLOTS
#include "js/RootingAPI.h" // JS::Rooted
#include "mozilla/Assertions.h" // MOZ_ASSERT
#include "jsapi.h" // JS_ReportErrorNumberASCII
#include "jsfriendapi.h" // js::GetErrorMessage, JSMSG_*
#include "builtin/Promise.h" // js::RejectPromiseWithPendingError
#include "builtin/streams/ReadableStream.h" // js::ReadableStream
#include "builtin/streams/ReadableStreamReader.h" // js::CreateReadableStreamDefaultReader
#include "builtin/streams/WritableStream.h" // js::WritableStream
#include "builtin/streams/WritableStreamDefaultWriter.h" // js::CreateWritableStreamDefaultWriter
#include "js/Class.h" // JSClass, JSCLASS_HAS_RESERVED_SLOTS
#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
#include "vm/PromiseObject.h" // js::PromiseObject
#include "vm/JSContext-inl.h" // JSContext::check
#include "vm/JSObject-inl.h" // js::NewBuiltinClassInstance
using JS::Handle;
using JS::Int32Value;
using JS::ObjectValue;
using JS::Rooted;
using js::PipeToState;
/* static */ PipeToState* PipeToState::create(JSContext* cx) {
/**
* Stream spec, 3.4.11. ReadableStreamPipeTo ( source, dest,
* preventClose, preventAbort,
* preventCancel, signal )
* Steps 4-11, 13-14.
*/
/* static */ PipeToState* PipeToState::create(
JSContext* cx, Handle<PromiseObject*> promise,
Handle<ReadableStream*> unwrappedSource,
Handle<WritableStream*> unwrappedDest, bool preventClose, bool preventAbort,
bool preventCancel, Handle<JSObject*> signal) {
cx->check(promise);
// Step 4. Assert: signal is undefined or signal is an instance of the
// AbortSignal interface.
#ifdef DEBUG
if (signal) {
// XXX jwalden need to add JSAPI hooks to recognize AbortSignal instances
}
#endif
// Step 5: Assert: ! IsReadableStreamLocked(source) is false.
MOZ_ASSERT(!unwrappedSource->locked());
// Step 6: Assert: ! IsWritableStreamLocked(dest) is false.
MOZ_ASSERT(!unwrappedDest->isLocked());
Rooted<PipeToState*> state(cx, NewBuiltinClassInstance<PipeToState>(cx));
if (!state) {
return nullptr;
}
state->setFixedSlot(Slot_Flags, Int32Value(0));
MOZ_ASSERT(state->getFixedSlot(Slot_Promise).isUndefined());
state->initFixedSlot(Slot_Promise, ObjectValue(*promise));
return state;
// Step 7: If ! IsReadableByteStreamController(
// source.[[readableStreamController]]) is true, let reader
// be either ! AcquireReadableStreamBYOBReader(source) or
// ! AcquireReadableStreamDefaultReader(source), at the user agents
// discretion.
// Step 8: Otherwise, let reader be
// ! AcquireReadableStreamDefaultReader(source).
// We don't implement byte streams, so we always acquire a default reader.
{
ReadableStreamDefaultReader* reader =
CreateReadableStreamDefaultReader(cx, unwrappedSource);
if (!reader) {
return nullptr;
}
MOZ_ASSERT(state->getFixedSlot(Slot_Reader).isUndefined());
state->initFixedSlot(Slot_Reader, ObjectValue(*reader));
}
// Step 9: Let writer be ! AcquireWritableStreamDefaultWriter(dest).
{
WritableStreamDefaultWriter* writer =
CreateWritableStreamDefaultWriter(cx, unwrappedDest);
if (!writer) {
return nullptr;
}
MOZ_ASSERT(state->getFixedSlot(Slot_Writer).isUndefined());
state->initFixedSlot(Slot_Writer, ObjectValue(*writer));
}
// Step 10: Set source.[[disturbed]] to true.
unwrappedSource->setDisturbed();
state->initFlags(preventClose, preventAbort, preventCancel);
MOZ_ASSERT(state->preventClose() == preventClose);
MOZ_ASSERT(state->preventAbort() == preventAbort);
MOZ_ASSERT(state->preventCancel() == preventCancel);
// Step 11: Let shuttingDown be false.
MOZ_ASSERT(!state->shuttingDown(), "should be set to false by initFlags");
// Step 12 ("Let promise be a new promise.") was performed by the caller and
// |promise| was its result.
// Step 13: If signal is not undefined,
// XXX jwalden need JSAPI to add an algorithm/steps to an AbortSignal
// Step 14: In parallel, using reader and writer, read all chunks from source
// and write them to dest.
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED,
"pipeTo");
return nullptr;
}
const JSClass PipeToState::class_ = {"PipeToState",

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

@ -13,12 +13,21 @@
#include <stdint.h> // uint32_t
#include "builtin/streams/ReadableStreamReader.h" // js::ReadableStreamDefaultReader
#include "builtin/streams/WritableStreamDefaultWriter.h" // js::WritableStreamDefaultWriter
#include "js/Class.h" // JSClass
#include "js/Value.h" // JS::Int32Value
#include "js/RootingAPI.h" // JS::Handle
#include "js/Value.h" // JS::Int32Value, JS::ObjectValue
#include "vm/NativeObject.h" // js::NativeObject
#include "vm/PromiseObject.h" // js::PromiseObject
class JS_PUBLIC_API JSObject;
namespace js {
class ReadableStream;
class WritableStream;
/**
* PipeToState objects implement the local variables in Streams spec 3.4.11
* ReadableStreamPipeTo across all sub-operations that occur in that algorithm.
@ -28,11 +37,51 @@ class PipeToState : public NativeObject {
/**
* Memory layout for PipeToState instances.
*/
enum Slots { Slot_Flags, SlotCount };
enum Slots {
/** Integer bit field of various flags. */
Slot_Flags = 0,
/**
* The promise resolved or rejected when the overall pipe-to operation
* completes.
*
* This promise is created directly under |ReadableStreamPipeTo|, at the
* same time the corresponding |PipeToState| is created, so it is always
* same-compartment with this and is guaranteed to hold a |PromiseObject*|
* if initialization succeeded.
*/
Slot_Promise,
/**
* A |ReadableStreamDefaultReader| used to read from the readable stream
* being piped from.
*
* This reader is created at the same time as its |PipeToState|, so this
* reader is same-compartment with this and is guaranteed to be a
* |ReadableStreamDefaultReader*| if initialization succeeds.
*/
Slot_Reader,
/**
* A |WritableStreamDefaultWriter| used to write to the writable stream
* being piped to.
*
* This writer is created at the same time as its |PipeToState|, so this
* writer is same-compartment with this and is guaranteed to be a
* |WritableStreamDefaultWriter*| if initialization succeeds.
*/
Slot_Writer,
SlotCount,
};
private:
enum Flags {
Flag_ShuttingDown = 1 << 0,
enum Flags : uint32_t {
Flag_ShuttingDown = 0b0001,
Flag_PreventClose = 0b0010,
Flag_PreventAbort = 0b0100,
Flag_PreventCancel = 0b1000,
};
uint32_t flags() const { return getFixedSlot(Slot_Flags).toInt32(); }
@ -43,13 +92,46 @@ class PipeToState : public NativeObject {
public:
static const JSClass class_;
PromiseObject* promise() const {
return &getFixedSlot(Slot_Promise).toObject().as<PromiseObject>();
}
ReadableStreamDefaultReader* reader() const {
return &getFixedSlot(Slot_Reader)
.toObject()
.as<ReadableStreamDefaultReader>();
}
WritableStreamDefaultWriter* writer() const {
return &getFixedSlot(Slot_Writer)
.toObject()
.as<WritableStreamDefaultWriter>();
}
bool shuttingDown() const { return flags() & Flag_ShuttingDown; }
void setShuttingDown() {
MOZ_ASSERT(!shuttingDown());
setFlags(flags() | Flag_ShuttingDown);
}
static PipeToState* create(JSContext* cx);
bool preventClose() const { return flags() & Flag_PreventClose; }
bool preventAbort() const { return flags() & Flag_PreventAbort; }
bool preventCancel() const { return flags() & Flag_PreventCancel; }
void initFlags(bool preventClose, bool preventAbort, bool preventCancel) {
MOZ_ASSERT(getFixedSlot(Slot_Flags).isUndefined());
uint32_t flagBits = (preventClose ? Flag_PreventClose : 0) |
(preventAbort ? Flag_PreventAbort : 0) |
(preventCancel ? Flag_PreventCancel : 0);
setFlags(flagBits);
}
static PipeToState* create(JSContext* cx, JS::Handle<PromiseObject*> promise,
JS::Handle<ReadableStream*> unwrappedSource,
JS::Handle<WritableStream*> unwrappedDest,
bool preventClose, bool preventAbort,
bool preventCancel, JS::Handle<JSObject*> signal);
};
} // namespace js

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

@ -368,10 +368,10 @@ static bool ReadableStream_pipeTo(JSContext* cx, unsigned argc, Value* vp) {
// second argument destructuring pattern. But as |ToBoolean| is infallible
// and has no observable side effects, we may as well do step 3 here too.
bool preventClose, preventAbort, preventCancel;
Rooted<Value> signal(cx);
Rooted<Value> signalVal(cx);
{
// (P)(Re)use the |signal| root.
auto& v = signal;
auto& v = signalVal;
if (!GetProperty(cx, options, cx->names().preventClose, &v)) {
return false;
@ -388,7 +388,7 @@ static bool ReadableStream_pipeTo(JSContext* cx, unsigned argc, Value* vp) {
}
preventCancel = JS::ToBoolean(v);
}
if (!GetProperty(cx, options, cx->names().signal, &signal)) {
if (!GetProperty(cx, options, cx->names().signal, &signalVal)) {
return false;
}
@ -416,7 +416,23 @@ static bool ReadableStream_pipeTo(JSContext* cx, unsigned argc, Value* vp) {
// Step 4: If signal is not undefined, and signal is not an instance of the
// AbortSignal interface, return a promise rejected with a TypeError
// exception.
// XXX jwalden need some hooks for this, or something
Rooted<JSObject*> signal(cx, nullptr);
do {
if (signalVal.isUndefined()) {
break;
}
if (signalVal.isObject()) {
// XXX jwalden need some JSAPI hooks to detect AbortSignal instances, or
// something
signal = &signalVal.toObject();
}
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAM_PIPETO_BAD_SIGNAL);
return ReturnPromiseRejectedWithPendingError(cx, args);
} while (false);
// Step 5: If ! IsReadableStreamLocked(this) is true, return a promise
// rejected with a TypeError exception.

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

@ -15,6 +15,7 @@
#include "builtin/Array.h" // js::NewDenseFullyAllocatedArray
#include "builtin/Promise.h" // js::RejectPromiseWithPendingError
#include "builtin/streams/PipeToState.h" // js::PipeToState
#include "builtin/streams/ReadableStream.h" // js::ReadableStream
#include "builtin/streams/ReadableStreamController.h" // js::ReadableStream{,Default}Controller
#include "builtin/streams/ReadableStreamDefaultControllerOperations.h" // js::ReadableStreamDefaultController{Close,Enqueue}, js::ReadableStreamControllerError, js::SourceAlgorithms
@ -617,7 +618,35 @@ PromiseObject* js::ReadableStreamPipeTo(JSContext* cx,
Handle<WritableStream*> unwrappedDest,
bool preventClose, bool preventAbort,
bool preventCancel,
Handle<Value> signal) {
JS_ReportErrorASCII(cx, "XXX ceci n'est pas une pipe");
return nullptr;
Handle<JSObject*> signal) {
// Step 1. Assert: ! IsReadableStream(source) is true.
// Step 2. Assert: ! IsWritableStream(dest) is true.
// Step 3. Assert: Type(preventClose) is Boolean, Type(preventAbort) is
// Boolean, and Type(preventCancel) is Boolean.
// (These are guaranteed by the type system.)
// Step 12: Let promise be a new promise.
//
// We reorder this so that this promise can be rejected and returned in case
// of internal error.
Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
if (!promise) {
return nullptr;
}
// Steps 4-11, 13-14.
Rooted<PipeToState*> pipeToState(
cx,
PipeToState::create(cx, promise, unwrappedSource, unwrappedDest,
preventClose, preventAbort, preventCancel, signal));
if (!pipeToState) {
if (!RejectPromiseWithPendingError(cx, promise)) {
return nullptr;
}
return promise;
}
// Step 15.
return promise;
}

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

@ -14,6 +14,8 @@
#include "js/RootingAPI.h" // JS::Handle
#include "js/Value.h" // JS::Value
class JS_PUBLIC_API JSObject;
namespace js {
class PromiseObject;
@ -38,7 +40,7 @@ extern MOZ_MUST_USE bool ReadableStreamTee(
extern MOZ_MUST_USE PromiseObject* ReadableStreamPipeTo(
JSContext* cx, JS::Handle<ReadableStream*> unwrappedSource,
JS::Handle<WritableStream*> unwrappedDest, bool preventClose,
bool preventAbort, bool preventCancel, JS::Handle<JS::Value> signal);
bool preventAbort, bool preventCancel, JS::Handle<JSObject*> signal);
} // namespace js

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

@ -695,6 +695,7 @@ MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_CLOSE_PENDING_PULL, 0, JSEXN_TYPEERR,
MSG_DEF(JSMSG_READABLESTREAMBYOBREQUEST_NO_CONTROLLER, 1, JSEXN_TYPEERR, "ReadableStreamBYOBRequest method '{0}' called on a request with no controller.")
MSG_DEF(JSMSG_READABLESTREAMBYOBREQUEST_RESPOND_CLOSED, 0, JSEXN_TYPEERR, "ReadableStreamBYOBRequest method 'respond' called with non-zero number of bytes with a closed controller.")
MSG_DEF(JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, 1, JSEXN_TYPEERR, "ReadableStream method {0} not yet implemented")
MSG_DEF(JSMSG_READABLESTREAM_PIPETO_BAD_SIGNAL, 0, JSEXN_TYPEERR, "signal must be either undefined or an AbortSignal")
// WritableStream
MSG_DEF(JSMSG_READABLESTREAM_UNDERLYINGSINK_TYPE_WRONG, 0, JSEXN_RANGEERR,"'underlyingSink.type' must be undefined.")