mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 23:02:20 +00:00
Bug 1755391 - Remove JS Streams implementation of Writable Stream and PipeTo r=jandem
Differential Revision: https://phabricator.services.mozilla.com/D139858
This commit is contained in:
parent
f46bdbcad0
commit
f83d513364
@ -116,11 +116,6 @@
|
||||
&js::ReadableStreamDefaultController::class_)) \
|
||||
IF_JS_STREAMS(REAL(ReadableByteStreamController, \
|
||||
&js::ReadableByteStreamController::class_)) \
|
||||
IF_JS_STREAMS(REAL(WritableStream, &js::WritableStream::class_)) \
|
||||
IF_JS_STREAMS(REAL(WritableStreamDefaultController, \
|
||||
&js::WritableStreamDefaultController::class_)) \
|
||||
IF_JS_STREAMS(REAL(WritableStreamDefaultWriter, \
|
||||
&js::WritableStreamDefaultWriter::class_)) \
|
||||
IF_JS_STREAMS( \
|
||||
REAL(ByteLengthQueuingStrategy, &js::ByteLengthQueuingStrategy::class_)) \
|
||||
IF_JS_STREAMS(REAL(CountQueuingStrategy, &js::CountQueuingStrategy::class_)) \
|
||||
|
@ -1,52 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
/* ReadableStream pipe-to operation captured state. */
|
||||
|
||||
#ifndef builtin_streams_PipeToState_inl_h
|
||||
#define builtin_streams_PipeToState_inl_h
|
||||
|
||||
#include "builtin/streams/PipeToState.h"
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
|
||||
#include "jstypes.h" // JS_PUBLIC_API
|
||||
|
||||
#include "js/RootingAPI.h" // JS::Handle
|
||||
#include "vm/JSContext.h" // JSContext
|
||||
#include "vm/Runtime.h" // JSRuntime
|
||||
|
||||
#include "vm/Compartment-inl.h" // js::UnwrapAndDowncastValue
|
||||
#include "vm/JSContext-inl.h" // JSContext::check
|
||||
|
||||
struct JS_PUBLIC_API JSContext;
|
||||
class JS_PUBLIC_API JSObject;
|
||||
|
||||
namespace js {
|
||||
|
||||
/**
|
||||
* Returns the unwrapped |AbortSignal| instance associated with a given pipe-to
|
||||
* operation.
|
||||
*
|
||||
* The pipe-to operation must be known to have had an |AbortSignal| associated
|
||||
* with it.
|
||||
*
|
||||
* If the signal is a wrapper, it will be unwrapped, so the result might not be
|
||||
* an object from the currently active compartment.
|
||||
*/
|
||||
[[nodiscard]] inline JSObject* UnwrapSignalFromPipeToState(
|
||||
JSContext* cx, JS::Handle<PipeToState*> pipeToState) {
|
||||
cx->check(pipeToState);
|
||||
|
||||
MOZ_ASSERT(pipeToState->hasSignal());
|
||||
return UnwrapAndDowncastValue(
|
||||
cx, pipeToState->getFixedSlot(PipeToState::Slot_Signal),
|
||||
cx->runtime()->maybeAbortSignalClass());
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_PipeToState_inl_h
|
File diff suppressed because it is too large
Load Diff
@ -1,291 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
/* ReadableStream.prototype.pipeTo state. */
|
||||
|
||||
#ifndef builtin_streams_PipeToState_h
|
||||
#define builtin_streams_PipeToState_h
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
#include "mozilla/WrappingOperations.h" // mozilla::WrapToSigned
|
||||
|
||||
#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/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.
|
||||
*/
|
||||
class PipeToState : public NativeObject {
|
||||
public:
|
||||
/**
|
||||
* Memory layout for PipeToState instances.
|
||||
*/
|
||||
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,
|
||||
|
||||
/**
|
||||
* The |PromiseObject*| of the last write performed to the destinationg
|
||||
* |WritableStream| using the writer in |Slot_Writer|. If no writes have
|
||||
* yet been performed, this slot contains |undefined|.
|
||||
*
|
||||
* This promise is created inside a handler function in the same compartment
|
||||
* and realm as this |PipeToState|, so it is always a |PromiseObject*| and
|
||||
* never a wrapper around one.
|
||||
*/
|
||||
Slot_LastWriteRequest,
|
||||
|
||||
/**
|
||||
* Either |undefined| or an |AbortSignal| instance specified by the user,
|
||||
* whose controller may be used to externally abort the piping algorithm.
|
||||
*
|
||||
* This signal is user-provided, so it may be a wrapper around an
|
||||
* |AbortSignal| not from the same compartment as this.
|
||||
*/
|
||||
Slot_Signal,
|
||||
|
||||
SlotCount,
|
||||
};
|
||||
|
||||
// The set of possible actions to be passed to the "shutdown with an action"
|
||||
// algorithm.
|
||||
//
|
||||
// We store actions as numbers because 1) handler functions already devote
|
||||
// their extra slots to target and extra value; and 2) storing a full function
|
||||
// pointer would require an extra slot, while storing as number packs into
|
||||
// existing flag storage.
|
||||
enum class ShutdownAction {
|
||||
/** The action used during |abortAlgorithm|.*/
|
||||
AbortAlgorithm,
|
||||
|
||||
/**
|
||||
* The action taken when |source| errors and aborting is not prevented, to
|
||||
* abort |dest| with |source|'s error.
|
||||
*/
|
||||
AbortDestStream,
|
||||
|
||||
/**
|
||||
* The action taken when |dest| becomes errored or closed and canceling is
|
||||
* not prevented, to cancel |source| with |dest|'s error.
|
||||
*/
|
||||
CancelSource,
|
||||
|
||||
/**
|
||||
* The action taken when |source| closes and closing is not prevented, to
|
||||
* close the writer while propagating any error in it.
|
||||
*/
|
||||
CloseWriterWithErrorPropagation,
|
||||
|
||||
};
|
||||
|
||||
private:
|
||||
enum Flags : uint32_t {
|
||||
/**
|
||||
* The action passed to the "shutdown with an action" algorithm.
|
||||
*
|
||||
* Note that because only the first "shutdown" and "shutdown with an action"
|
||||
* operation has any effect, we can store this action in |PipeToState| in
|
||||
* the first invocation of either operation without worrying about it being
|
||||
* overwritten.
|
||||
*
|
||||
* Purely for convenience, we encode this in the lowest bits so that the
|
||||
* result of a mask is the underlying value of the correct |ShutdownAction|.
|
||||
*/
|
||||
Flag_ShutdownActionBits = 0b0000'0011,
|
||||
|
||||
Flag_ShuttingDown = 0b0000'0100,
|
||||
|
||||
Flag_PendingRead = 0b0000'1000,
|
||||
#ifdef DEBUG
|
||||
Flag_PendingReadWouldBeRejected = 0b0001'0000,
|
||||
#endif
|
||||
|
||||
Flag_PreventClose = 0b0010'0000,
|
||||
Flag_PreventAbort = 0b0100'0000,
|
||||
Flag_PreventCancel = 0b1000'0000,
|
||||
};
|
||||
|
||||
uint32_t flags() const { return getFixedSlot(Slot_Flags).toInt32(); }
|
||||
void setFlags(uint32_t flags) {
|
||||
setFixedSlot(Slot_Flags, JS::Int32Value(mozilla::WrapToSigned(flags)));
|
||||
}
|
||||
|
||||
// Flags start out zeroed, so the initially-stored shutdown action value will
|
||||
// be this value. (This is also the value of an *initialized* shutdown
|
||||
// action, but it doesn't seem worth the trouble to store an extra bit to
|
||||
// detect this specific action being recorded multiple times, purely for
|
||||
// assertions.)
|
||||
static constexpr ShutdownAction UninitializedAction =
|
||||
ShutdownAction::AbortAlgorithm;
|
||||
|
||||
static_assert(Flag_ShutdownActionBits & 1,
|
||||
"shutdown action bits must be low-order bits so that we can "
|
||||
"cast ShutdownAction values directly to bits to store");
|
||||
|
||||
static constexpr uint32_t MaxAction =
|
||||
static_cast<uint32_t>(ShutdownAction::CloseWriterWithErrorPropagation);
|
||||
|
||||
static_assert(MaxAction <= Flag_ShutdownActionBits,
|
||||
"max action shouldn't overflow available bits to store it");
|
||||
|
||||
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>();
|
||||
}
|
||||
|
||||
PromiseObject* lastWriteRequest() const {
|
||||
const auto& slot = getFixedSlot(Slot_LastWriteRequest);
|
||||
if (slot.isUndefined()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &slot.toObject().as<PromiseObject>();
|
||||
}
|
||||
|
||||
void updateLastWriteRequest(PromiseObject* writeRequest) {
|
||||
MOZ_ASSERT(writeRequest != nullptr);
|
||||
setFixedSlot(Slot_LastWriteRequest, JS::ObjectValue(*writeRequest));
|
||||
}
|
||||
|
||||
bool hasSignal() const {
|
||||
JS::Value v = getFixedSlot(Slot_Signal);
|
||||
MOZ_ASSERT(v.isObject() || v.isUndefined());
|
||||
return v.isObject();
|
||||
}
|
||||
|
||||
bool shuttingDown() const { return flags() & Flag_ShuttingDown; }
|
||||
void setShuttingDown() {
|
||||
MOZ_ASSERT(!shuttingDown());
|
||||
setFlags(flags() | Flag_ShuttingDown);
|
||||
}
|
||||
|
||||
ShutdownAction shutdownAction() const {
|
||||
MOZ_ASSERT(shuttingDown(),
|
||||
"must be shutting down to have a shutdown action");
|
||||
|
||||
uint32_t bits = flags() & Flag_ShutdownActionBits;
|
||||
static_assert(Flag_ShutdownActionBits & 1,
|
||||
"shutdown action bits are assumed to be low-order bits that "
|
||||
"don't have to be shifted down to ShutdownAction's range");
|
||||
|
||||
MOZ_ASSERT(bits <= MaxAction, "bits must encode a valid action");
|
||||
|
||||
return static_cast<ShutdownAction>(bits);
|
||||
}
|
||||
|
||||
void setShutdownAction(ShutdownAction action) {
|
||||
MOZ_ASSERT(shuttingDown(),
|
||||
"must be protected by the |shuttingDown| boolean to save the "
|
||||
"shutdown action");
|
||||
MOZ_ASSERT(shutdownAction() == UninitializedAction,
|
||||
"should only set shutdown action once");
|
||||
|
||||
setFlags(flags() | static_cast<uint32_t>(action));
|
||||
}
|
||||
|
||||
bool preventClose() const { return flags() & Flag_PreventClose; }
|
||||
bool preventAbort() const { return flags() & Flag_PreventAbort; }
|
||||
bool preventCancel() const { return flags() & Flag_PreventCancel; }
|
||||
|
||||
bool hasPendingRead() const { return flags() & Flag_PendingRead; }
|
||||
void setPendingRead() {
|
||||
MOZ_ASSERT(!hasPendingRead());
|
||||
setFlags(flags() | Flag_PendingRead);
|
||||
}
|
||||
void clearPendingRead() {
|
||||
MOZ_ASSERT(hasPendingRead());
|
||||
setFlags(flags() & ~Flag_PendingRead);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
bool pendingReadWouldBeRejected() const {
|
||||
return flags() & Flag_PendingReadWouldBeRejected;
|
||||
}
|
||||
void setPendingReadWouldBeRejected() {
|
||||
MOZ_ASSERT(!pendingReadWouldBeRejected());
|
||||
setFlags(flags() | Flag_PendingReadWouldBeRejected);
|
||||
}
|
||||
#endif
|
||||
|
||||
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
|
||||
|
||||
#endif // builtin_streams_PipeToState_h
|
@ -20,8 +20,7 @@
|
||||
#include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStreamCancel
|
||||
#include "builtin/streams/ReadableStreamOperations.h" // js::ReadableStream{PipeTo,Tee}
|
||||
#include "builtin/streams/ReadableStreamReader.h" // js::CreateReadableStream{BYOB,Default}Reader, js::ForAuthorCodeBool
|
||||
#include "builtin/streams/WritableStream.h" // js::WritableStream
|
||||
#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
|
||||
#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
|
||||
#include "js/Class.h" // JSCLASS_SLOT0_IS_NSISUPPORTS, JS_NULL_CLASS_OPS
|
||||
#include "js/Conversions.h" // JS::ToBoolean
|
||||
#include "js/ErrorReport.h" // JS_ReportErrorNumberASCII
|
||||
@ -55,14 +54,12 @@ using js::NewBuiltinClassInstance;
|
||||
using js::NewDenseFullyAllocatedArray;
|
||||
using js::PlainObject;
|
||||
using js::ReadableStream;
|
||||
using js::ReadableStreamPipeTo;
|
||||
using js::ReadableStreamTee;
|
||||
using js::ReturnPromiseRejectedWithPendingError;
|
||||
using js::ToString;
|
||||
using js::UnwrapAndTypeCheckArgument;
|
||||
using js::UnwrapAndTypeCheckThis;
|
||||
using js::UnwrapAndTypeCheckValue;
|
||||
using js::WritableStream;
|
||||
|
||||
using JS::CallArgs;
|
||||
using JS::CallArgsFromVp;
|
||||
@ -369,125 +366,6 @@ enum class ReadableStreamReaderMode { Byob };
|
||||
return true;
|
||||
}
|
||||
|
||||
// Streams spec, 3.2.5.5.
|
||||
// pipeThrough({ writable, readable },
|
||||
// { preventClose, preventAbort, preventCancel, signal })
|
||||
//
|
||||
// Not implemented.
|
||||
|
||||
/**
|
||||
* Streams spec, 3.2.5.6.
|
||||
* pipeTo(dest, { preventClose, preventAbort, preventCancel, signal } = {})
|
||||
*/
|
||||
static bool ReadableStream_pipeTo(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Implicit in the spec: argument default values.
|
||||
Rooted<Value> options(cx, args.get(1));
|
||||
if (options.isUndefined()) {
|
||||
JSObject* emptyObj = NewPlainObject(cx);
|
||||
if (!emptyObj) {
|
||||
return false;
|
||||
}
|
||||
options.setObject(*emptyObj);
|
||||
}
|
||||
// Step 3 (reordered).
|
||||
// Implicit in the spec: get the values of the named parameters inside the
|
||||
// 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> signalVal(cx);
|
||||
{
|
||||
// (P)(Re)use the |signal| root.
|
||||
auto& v = signalVal;
|
||||
|
||||
if (!GetProperty(cx, options, cx->names().preventClose, &v)) {
|
||||
return false;
|
||||
}
|
||||
preventClose = JS::ToBoolean(v);
|
||||
|
||||
if (!GetProperty(cx, options, cx->names().preventAbort, &v)) {
|
||||
return false;
|
||||
}
|
||||
preventAbort = JS::ToBoolean(v);
|
||||
|
||||
if (!GetProperty(cx, options, cx->names().preventCancel, &v)) {
|
||||
return false;
|
||||
}
|
||||
preventCancel = JS::ToBoolean(v);
|
||||
}
|
||||
if (!GetProperty(cx, options, cx->names().signal, &signalVal)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 1: If ! IsReadableStream(this) is false, return a promise rejected
|
||||
// with a TypeError exception.
|
||||
Rooted<ReadableStream*> unwrappedThis(
|
||||
cx, UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "pipeTo"));
|
||||
if (!unwrappedThis) {
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 2: If ! IsWritableStream(dest) is false, return a promise rejected
|
||||
// with a TypeError exception.
|
||||
Rooted<WritableStream*> unwrappedDest(
|
||||
cx, UnwrapAndTypeCheckArgument<WritableStream>(cx, args, "pipeTo", 0));
|
||||
if (!unwrappedDest) {
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 3: Set preventClose to ! ToBoolean(preventClose), set preventAbort to
|
||||
// ! ToBoolean(preventAbort), and set preventCancel to
|
||||
// ! ToBoolean(preventCancel).
|
||||
// This already happened above.
|
||||
|
||||
// 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.
|
||||
Rooted<JSObject*> signal(cx, nullptr);
|
||||
if (!signalVal.isUndefined()) {
|
||||
if (!UnwrapAndTypeCheckValue(
|
||||
cx, signalVal, cx->runtime()->maybeAbortSignalClass(), [cx] {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAM_PIPETO_BAD_SIGNAL);
|
||||
})) {
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Note: |signal| can be a wrapper.
|
||||
signal = &signalVal.toObject();
|
||||
}
|
||||
|
||||
// Step 5: If ! IsReadableStreamLocked(this) is true, return a promise
|
||||
// rejected with a TypeError exception.
|
||||
if (unwrappedThis->locked()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAM_LOCKED_METHOD, "pipeTo");
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 6: If ! IsWritableStreamLocked(dest) is true, return a promise
|
||||
// rejected with a TypeError exception.
|
||||
if (unwrappedDest->isLocked()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WRITABLESTREAM_ALREADY_LOCKED);
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 7: Return
|
||||
// ! ReadableStreamPipeTo(this, dest, preventClose, preventAbort,
|
||||
// preventCancel, signal).
|
||||
JSObject* promise =
|
||||
ReadableStreamPipeTo(cx, unwrappedThis, unwrappedDest, preventClose,
|
||||
preventAbort, preventCancel, signal);
|
||||
if (!promise) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*promise);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.2.5.7. tee()
|
||||
*/
|
||||
@ -538,28 +416,6 @@ static const JSPropertySpec ReadableStream_properties[] = {
|
||||
JS_STRING_SYM_PS(toStringTag, "ReadableStream", JSPROP_READONLY),
|
||||
JS_PS_END};
|
||||
|
||||
static bool FinishReadableStreamClassInit(JSContext* cx, Handle<JSObject*> ctor,
|
||||
Handle<JSObject*> proto) {
|
||||
// This function and everything below should be replaced with
|
||||
//
|
||||
// JS_STREAMS_CLASS_SPEC(ReadableStream, 0, SlotCount, 0,
|
||||
// JSCLASS_SLOT0_IS_NSISUPPORTS,
|
||||
// JS_NULL_CLASS_OPS);
|
||||
//
|
||||
// when "pipeTo" is always enabled.
|
||||
const auto& rco = cx->realm()->creationOptions();
|
||||
if (rco.getStreamsEnabled() && rco.getWritableStreamsEnabled() &&
|
||||
rco.getReadableStreamPipeToEnabled()) {
|
||||
Rooted<jsid> pipeTo(cx, NameToId(cx->names().pipeTo));
|
||||
if (!DefineFunction(cx, proto, pipeTo, ReadableStream_pipeTo, 2,
|
||||
JSPROP_RESOLVING | JSPROP_ENUMERATE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const ClassSpec ReadableStream::classSpec_ = {
|
||||
js::GenericCreateConstructor<ReadableStream::constructor, 0,
|
||||
js::gc::AllocKind::FUNCTION>,
|
||||
@ -568,7 +424,7 @@ const ClassSpec ReadableStream::classSpec_ = {
|
||||
nullptr,
|
||||
ReadableStream_methods,
|
||||
ReadableStream_properties,
|
||||
FinishReadableStreamClassInit,
|
||||
nullptr,
|
||||
0};
|
||||
|
||||
const JSClass ReadableStream::class_ = {
|
||||
|
@ -10,9 +10,8 @@
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
|
||||
|
||||
#include "builtin/Array.h" // js::NewDenseFullyAllocatedArray
|
||||
#include "builtin/Promise.h" // js::RejectPromiseWithPendingError
|
||||
#include "builtin/streams/PipeToState.h" // js::PipeToState
|
||||
#include "builtin/Array.h" // js::NewDenseFullyAllocatedArray
|
||||
#include "builtin/Promise.h" // js::RejectPromiseWithPendingError
|
||||
#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
|
||||
@ -632,48 +631,3 @@ static bool TeeReaderErroredHandler(JSContext* cx, unsigned argc,
|
||||
// Step 19: Return « branch1, branch2 ».
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.4.10.
|
||||
* ReadableStreamPipeTo ( source, dest, preventClose, preventAbort,
|
||||
* preventCancel, signal )
|
||||
*/
|
||||
PromiseObject* js::ReadableStreamPipeTo(JSContext* cx,
|
||||
Handle<ReadableStream*> unwrappedSource,
|
||||
Handle<WritableStream*> unwrappedDest,
|
||||
bool preventClose, bool preventAbort,
|
||||
bool preventCancel,
|
||||
Handle<JSObject*> signal) {
|
||||
cx->check(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;
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ class PromiseObject;
|
||||
class ReadableStream;
|
||||
class ReadableStreamDefaultController;
|
||||
class TeeState;
|
||||
class WritableStream;
|
||||
|
||||
[[nodiscard]] extern PromiseObject* ReadableStreamTee_Pull(
|
||||
JSContext* cx, JS::Handle<TeeState*> unwrappedTeeState);
|
||||
@ -35,11 +34,6 @@ class WritableStream;
|
||||
bool cloneForBranch2, JS::MutableHandle<ReadableStream*> branch1Stream,
|
||||
JS::MutableHandle<ReadableStream*> branch2Stream);
|
||||
|
||||
[[nodiscard]] extern PromiseObject* ReadableStreamPipeTo(
|
||||
JSContext* cx, JS::Handle<ReadableStream*> unwrappedSource,
|
||||
JS::Handle<WritableStream*> unwrappedDest, bool preventClose,
|
||||
bool preventAbort, bool preventCancel, JS::Handle<JSObject*> signal);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_ReadableStreamOperations_h
|
||||
|
@ -11,14 +11,12 @@
|
||||
|
||||
#include "builtin/streams/StreamController.h" // js::StreamController
|
||||
#include "builtin/streams/ReadableStreamController.h" // js::Readable{ByteStream,StreamDefault}Controller
|
||||
#include "builtin/streams/WritableStreamDefaultController.h" // js::WritableStreamDefaultController
|
||||
#include "vm/JSObject.h" // JSObject
|
||||
#include "vm/JSObject.h" // JSObject
|
||||
|
||||
template <>
|
||||
inline bool JSObject::is<js::StreamController>() const {
|
||||
return is<js::ReadableStreamDefaultController>() ||
|
||||
is<js::ReadableByteStreamController>() ||
|
||||
is<js::WritableStreamDefaultController>();
|
||||
is<js::ReadableByteStreamController>();
|
||||
}
|
||||
|
||||
#endif // builtin_streams_ReadableStreamController_inl_h
|
||||
|
@ -1,46 +0,0 @@
|
||||
/* -*- 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 WritableStream. */
|
||||
|
||||
#ifndef builtin_streams_WritableStream_inl_h
|
||||
#define builtin_streams_WritableStream_inl_h
|
||||
|
||||
#include "builtin/streams/WritableStream.h"
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
|
||||
#include "jstypes.h" // JS_PUBLIC_API
|
||||
|
||||
#include "builtin/streams/WritableStreamDefaultWriter.h" // js::WritableStreamDefaultWriter
|
||||
#include "js/RootingAPI.h" // JS::Handle
|
||||
#include "js/Value.h" // JS::{,Object}Value
|
||||
|
||||
#include "vm/Compartment-inl.h" // js::UnwrapInternalSlot
|
||||
|
||||
struct JS_PUBLIC_API JSContext;
|
||||
|
||||
namespace js {
|
||||
|
||||
/**
|
||||
* Returns the writer associated with the given stream.
|
||||
*
|
||||
* Must only be called on WritableStreams that already have a writer
|
||||
* associated with them.
|
||||
*
|
||||
* If the writer is a wrapper, it will be unwrapped, so the result might not be
|
||||
* an object from the currently active compartment.
|
||||
*/
|
||||
[[nodiscard]] inline WritableStreamDefaultWriter* UnwrapWriterFromStream(
|
||||
JSContext* cx, JS::Handle<WritableStream*> unwrappedStream) {
|
||||
MOZ_ASSERT(unwrappedStream->hasWriter());
|
||||
return UnwrapInternalSlot<WritableStreamDefaultWriter>(
|
||||
cx, unwrappedStream, WritableStream::Slot_Writer);
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_WritableStream_inl_h
|
@ -1,280 +0,0 @@
|
||||
/* -*- 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 WritableStream. */
|
||||
|
||||
#include "builtin/streams/WritableStream.h"
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
|
||||
#include "jspubtd.h" // JSProto_WritableStream
|
||||
|
||||
#include "builtin/streams/ClassSpecMacro.h" // JS_STREAMS_CLASS_SPEC
|
||||
#include "builtin/streams/MiscellaneousOperations.h" // js::MakeSizeAlgorithmFromSizeFunction, js::ReturnPromiseRejectedWithPendingError, js::ValidateAndNormalizeHighWaterMark
|
||||
#include "builtin/streams/WritableStreamDefaultControllerOperations.h" // js::SetUpWritableStreamDefaultControllerFromUnderlyingSink
|
||||
#include "builtin/streams/WritableStreamDefaultWriter.h" // js::CreateWritableStreamDefaultWriter
|
||||
#include "builtin/streams/WritableStreamOperations.h" // js::WritableStream{Abort,Close{,QueuedOrInFlight}}
|
||||
#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
|
||||
#include "js/Class.h" // JS{Function,Property}Spec, JS_{FS,PS}_END, JSCLASS_SLOT0_IS_NSISUPPORTS, JS_NULL_CLASS_OPS
|
||||
#include "js/ErrorReport.h" // JS_ReportErrorNumberASCII
|
||||
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
|
||||
#include "js/RealmOptions.h" // JS::RealmCreationOptions
|
||||
#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
|
||||
#include "js/Value.h" // JS::{,Object}Value
|
||||
#include "vm/JSContext.h" // JSContext
|
||||
#include "vm/JSObject.h" // js::GetPrototypeFromBuiltinConstructor
|
||||
#include "vm/ObjectOperations.h" // js::GetProperty
|
||||
#include "vm/PlainObject.h" // js::PlainObject
|
||||
#include "vm/Realm.h" // JS::Realm
|
||||
|
||||
#include "vm/Compartment-inl.h" // js::UnwrapAndTypeCheckThis
|
||||
#include "vm/JSContext-inl.h" // JSContext::check
|
||||
#include "vm/JSObject-inl.h" // js::NewBuiltinClassInstance
|
||||
#include "vm/NativeObject-inl.h" // js::ThrowIfNotConstructing
|
||||
|
||||
using js::CreateWritableStreamDefaultWriter;
|
||||
using js::GetErrorMessage;
|
||||
using js::ReturnPromiseRejectedWithPendingError;
|
||||
using js::UnwrapAndTypeCheckThis;
|
||||
using js::WritableStream;
|
||||
using js::WritableStreamAbort;
|
||||
using js::WritableStreamClose;
|
||||
using js::WritableStreamCloseQueuedOrInFlight;
|
||||
|
||||
using JS::CallArgs;
|
||||
using JS::CallArgsFromVp;
|
||||
using JS::Handle;
|
||||
using JS::ObjectValue;
|
||||
using JS::Rooted;
|
||||
using JS::Value;
|
||||
|
||||
/*** 4.2. Class WritableStream **********************************************/
|
||||
|
||||
/**
|
||||
* Streams spec, 4.2.3. new WritableStream(underlyingSink = {}, strategy = {})
|
||||
*/
|
||||
bool WritableStream::constructor(JSContext* cx, unsigned argc, Value* vp) {
|
||||
MOZ_ASSERT(cx->realm()->creationOptions().getWritableStreamsEnabled(),
|
||||
"WritableStream should be enabled in this realm if we reach here");
|
||||
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
if (!ThrowIfNotConstructing(cx, args, "WritableStream")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Implicit in the spec: argument default values.
|
||||
Rooted<Value> underlyingSink(cx, args.get(0));
|
||||
if (underlyingSink.isUndefined()) {
|
||||
JSObject* emptyObj = NewPlainObject(cx);
|
||||
if (!emptyObj) {
|
||||
return false;
|
||||
}
|
||||
underlyingSink = ObjectValue(*emptyObj);
|
||||
}
|
||||
|
||||
Rooted<Value> strategy(cx, args.get(1));
|
||||
if (strategy.isUndefined()) {
|
||||
JSObject* emptyObj = NewPlainObject(cx);
|
||||
if (!emptyObj) {
|
||||
return false;
|
||||
}
|
||||
strategy = ObjectValue(*emptyObj);
|
||||
}
|
||||
|
||||
// Implicit in the spec: Set this to
|
||||
// OrdinaryCreateFromConstructor(NewTarget, ...).
|
||||
// Step 1: Perform ! InitializeWritableStream(this).
|
||||
Rooted<JSObject*> proto(cx);
|
||||
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WritableStream,
|
||||
&proto)) {
|
||||
return false;
|
||||
}
|
||||
Rooted<WritableStream*> stream(cx,
|
||||
WritableStream::create(cx, nullptr, proto));
|
||||
if (!stream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: Let size be ? GetV(strategy, "size").
|
||||
Rooted<Value> size(cx);
|
||||
if (!GetProperty(cx, strategy, cx->names().size, &size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Let highWaterMark be ? GetV(strategy, "highWaterMark").
|
||||
Rooted<Value> highWaterMarkVal(cx);
|
||||
if (!GetProperty(cx, strategy, cx->names().highWaterMark,
|
||||
&highWaterMarkVal)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 4: Let type be ? GetV(underlyingSink, "type").
|
||||
Rooted<Value> type(cx);
|
||||
if (!GetProperty(cx, underlyingSink, cx->names().type, &type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 5: If type is not undefined, throw a RangeError exception.
|
||||
if (!type.isUndefined()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAM_UNDERLYINGSINK_TYPE_WRONG);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 6: Let sizeAlgorithm be ? MakeSizeAlgorithmFromSizeFunction(size).
|
||||
if (!MakeSizeAlgorithmFromSizeFunction(cx, size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 7: If highWaterMark is undefined, let highWaterMark be 1.
|
||||
double highWaterMark;
|
||||
if (highWaterMarkVal.isUndefined()) {
|
||||
highWaterMark = 1;
|
||||
} else {
|
||||
// Step 8: Set highWaterMark to ?
|
||||
// ValidateAndNormalizeHighWaterMark(highWaterMark).
|
||||
if (!ValidateAndNormalizeHighWaterMark(cx, highWaterMarkVal,
|
||||
&highWaterMark)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 9: Perform
|
||||
// ? SetUpWritableStreamDefaultControllerFromUnderlyingSink(
|
||||
// this, underlyingSink, highWaterMark, sizeAlgorithm).
|
||||
if (!SetUpWritableStreamDefaultControllerFromUnderlyingSink(
|
||||
cx, stream, underlyingSink, highWaterMark, size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*stream);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.2.5.1. get locked
|
||||
*/
|
||||
static bool WritableStream_locked(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: If ! WritableStream(this) is false, throw a TypeError exception.
|
||||
Rooted<WritableStream*> unwrappedStream(
|
||||
cx, UnwrapAndTypeCheckThis<WritableStream>(cx, args, "get locked"));
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: Return ! IsWritableStreamLocked(this).
|
||||
args.rval().setBoolean(unwrappedStream->isLocked());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.2.5.2. abort(reason)
|
||||
*/
|
||||
static bool WritableStream_abort(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: If ! IsWritableStream(this) is false, return a promise rejected
|
||||
// with a TypeError exception.
|
||||
Rooted<WritableStream*> unwrappedStream(
|
||||
cx, UnwrapAndTypeCheckThis<WritableStream>(cx, args, "abort"));
|
||||
if (!unwrappedStream) {
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 2: If ! IsWritableStreamLocked(this) is true, return a promise
|
||||
// rejected with a TypeError exception.
|
||||
if (unwrappedStream->isLocked()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_CANT_USE_LOCKED_WRITABLESTREAM, "abort");
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 3: Return ! WritableStreamAbort(this, reason).
|
||||
JSObject* promise = WritableStreamAbort(cx, unwrappedStream, args.get(0));
|
||||
if (!promise) {
|
||||
return false;
|
||||
}
|
||||
cx->check(promise);
|
||||
|
||||
args.rval().setObject(*promise);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.2.5.3. close()
|
||||
*/
|
||||
static bool WritableStream_close(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: If ! IsWritableStream(this) is false, return a promise rejected
|
||||
// with a TypeError exception.
|
||||
Rooted<WritableStream*> unwrappedStream(
|
||||
cx, UnwrapAndTypeCheckThis<WritableStream>(cx, args, "close"));
|
||||
if (!unwrappedStream) {
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 2: If ! IsWritableStreamLocked(this) is true, return a promise
|
||||
// rejected with a TypeError exception.
|
||||
if (unwrappedStream->isLocked()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_CANT_USE_LOCKED_WRITABLESTREAM, "close");
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 3: If ! WritableStreamCloseQueuedOrInFlight(this) is true, return a
|
||||
// promise rejected with a TypeError exception.
|
||||
if (WritableStreamCloseQueuedOrInFlight(unwrappedStream)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WRITABLESTREAM_CLOSE_CLOSING_OR_CLOSED);
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 4: Return ! WritableStreamClose(this).
|
||||
JSObject* promise = WritableStreamClose(cx, unwrappedStream);
|
||||
if (!promise) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*promise);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.2.5.4. getWriter()
|
||||
*/
|
||||
static bool WritableStream_getWriter(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: If ! WritableStream(this) is false, throw a TypeError exception.
|
||||
Rooted<WritableStream*> unwrappedStream(
|
||||
cx, UnwrapAndTypeCheckThis<WritableStream>(cx, args, "getWriter"));
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* writer = CreateWritableStreamDefaultWriter(cx, unwrappedStream);
|
||||
if (!writer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*writer);
|
||||
return true;
|
||||
}
|
||||
|
||||
static const JSFunctionSpec WritableStream_methods[] = {
|
||||
JS_FN("abort", WritableStream_abort, 1, 0),
|
||||
JS_FN("close", WritableStream_close, 0, 0),
|
||||
JS_FN("getWriter", WritableStream_getWriter, 0, 0), JS_FS_END};
|
||||
|
||||
static const JSPropertySpec WritableStream_properties[] = {
|
||||
JS_PSG("locked", WritableStream_locked, 0), JS_PS_END};
|
||||
|
||||
JS_STREAMS_CLASS_SPEC(WritableStream, 0, SlotCount, 0,
|
||||
JSCLASS_SLOT0_IS_NSISUPPORTS, JS_NULL_CLASS_OPS);
|
@ -1,430 +0,0 @@
|
||||
/* -*- 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 WritableStream. */
|
||||
|
||||
#ifndef builtin_streams_WritableStream_h
|
||||
#define builtin_streams_WritableStream_h
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
#include "mozilla/Casting.h" // mozilla::AssertedCast
|
||||
#include "mozilla/MathAlgorithms.h" // mozilla::IsPowerOfTwo
|
||||
|
||||
#include <stdint.h> // uint32_t
|
||||
|
||||
#include "jstypes.h" // JS_PUBLIC_API
|
||||
#include "js/Class.h" // JSClass, js::ClassSpec
|
||||
#include "js/RootingAPI.h" // JS::Handle
|
||||
#include "js/Value.h" // JS::{,Int32,Object,Undefined}Value
|
||||
#include "vm/List.h" // js::ListObject
|
||||
#include "vm/NativeObject.h" // js::NativeObject
|
||||
|
||||
struct JS_PUBLIC_API JSContext;
|
||||
|
||||
namespace js {
|
||||
|
||||
class PromiseObject;
|
||||
class WritableStreamDefaultController;
|
||||
class WritableStreamDefaultWriter;
|
||||
|
||||
class WritableStream : public NativeObject {
|
||||
public:
|
||||
enum Slots {
|
||||
/**
|
||||
* Optional pointer to make the stream participate in Gecko's cycle
|
||||
* collection. See also JSCLASS_SLOT0_IS_NSISUPPORTS.
|
||||
*/
|
||||
Slot_ISupports,
|
||||
|
||||
/**
|
||||
* A WritableStream's associated controller is always created from under the
|
||||
* stream's constructor and thus cannot be in a different compartment.
|
||||
*/
|
||||
Slot_Controller,
|
||||
|
||||
/**
|
||||
* Either |undefined| if no writer has been created yet for |this|, or a
|
||||
* |WritableStreamDefaultWriter| object that writes to this. Writers are
|
||||
* created under |WritableStream.prototype.getWriter|, which may not be
|
||||
* same-compartment with |this|, so this object may be a wrapper.
|
||||
*/
|
||||
Slot_Writer,
|
||||
|
||||
/**
|
||||
* A bit field that stores both [[state]] and the [[backpressure]] spec
|
||||
* fields in a WritableStream::State 32-bit integer.
|
||||
*/
|
||||
Slot_State,
|
||||
|
||||
/**
|
||||
* Either |undefined| if this stream hasn't yet started erroring, or an
|
||||
* arbitrary value indicating the reason for the error (e.g. the
|
||||
* reason-value passed to a related |abort(reason)| or |error(e)| function).
|
||||
*
|
||||
* This value can be an arbitrary user-provided value, so it might be a
|
||||
* cross-comaprtment wrapper.
|
||||
*/
|
||||
Slot_StoredError,
|
||||
|
||||
/**
|
||||
* Very briefly for newborn writable streams before they are initialized,
|
||||
* |undefined|.
|
||||
*
|
||||
* After initialization, a |ListObject| consisting of the value of the
|
||||
* [[inFlightWriteRequest]] spec field (if it is not |undefined|) followed
|
||||
* by the elements of the [[queue]] List. |this| and the |ListObject| are
|
||||
* same-compartment.
|
||||
*
|
||||
* After a stream has gone irrevocably into an error state (specifically,
|
||||
* |stream.[[state]]| is "errored") and requests can no longer be enqueued,
|
||||
* |undefined| yet again.
|
||||
*
|
||||
* If the |HaveInFlightWriteRequest| flag is set, the first element of this
|
||||
* List is the non-|undefined| value of [[inFlightWriteRequest]]. If it is
|
||||
* unset, [[inFlightWriteRequest]] has the value |undefined|.
|
||||
*/
|
||||
Slot_WriteRequests,
|
||||
|
||||
/**
|
||||
* A slot storing both [[closeRequest]] and [[inFlightCloseRequest]]. This
|
||||
* value is created under |WritableStreamDefaultWriterClose|, so it may be a
|
||||
* wrapper around a promise rather than directly a |PromiseObject|.
|
||||
*
|
||||
* If this slot has the value |undefined|, then [[inFlightCloseRequest]]
|
||||
* and [[closeRequest]] are both |undefined|. Otherwise one field has the
|
||||
* value |undefined| and the other has the value of this slot, and the value
|
||||
* of the |HaveInFlightCloseRequest| flag indicates which field is set.
|
||||
*/
|
||||
Slot_CloseRequest,
|
||||
|
||||
/**
|
||||
* In the spec the [[pendingAbortRequest]] field is either |undefined| or
|
||||
* Record { [[promise]]: Object, [[reason]]: value, [[wasAlreadyErroring]]:
|
||||
* boolean }. We represent this as follows:
|
||||
*
|
||||
* 1) If Slot_PendingAbortRequestPromise contains |undefined|, then the
|
||||
* spec field is |undefined|;
|
||||
* 2) Otherwise Slot_PendingAbortRequestPromise contains the value of
|
||||
* [[pendingAbortRequest]].[[promise]], Slot_PendingAbortRequestReason
|
||||
* contains the value of [[pendingAbortRequest]].[[reason]], and the
|
||||
* |PendingAbortRequestWasAlreadyErroring| flag stores the value of
|
||||
* [[pendingAbortRequest]].[[wasAlreadyErroring]].
|
||||
*/
|
||||
Slot_PendingAbortRequestPromise,
|
||||
Slot_PendingAbortRequestReason,
|
||||
|
||||
SlotCount
|
||||
};
|
||||
|
||||
private:
|
||||
enum State : uint32_t {
|
||||
Writable = 0x0000'0000,
|
||||
Closed = 0x0000'0001,
|
||||
Erroring = 0x0000'0002,
|
||||
Errored = 0x0000'0003,
|
||||
StateBits = 0x0000'0003,
|
||||
StateMask = 0x0000'00ff,
|
||||
|
||||
Backpressure = 0x0000'0100,
|
||||
HaveInFlightWriteRequest = 0x0000'0200,
|
||||
HaveInFlightCloseRequest = 0x0000'0400,
|
||||
PendingAbortRequestWasAlreadyErroring = 0x0000'0800,
|
||||
FlagBits = Backpressure | HaveInFlightWriteRequest |
|
||||
HaveInFlightCloseRequest | PendingAbortRequestWasAlreadyErroring,
|
||||
FlagMask = 0x0000'ff00,
|
||||
|
||||
SettableBits = uint32_t(StateBits | FlagBits)
|
||||
};
|
||||
|
||||
bool stateIsInitialized() const { return getFixedSlot(Slot_State).isInt32(); }
|
||||
|
||||
State state() const {
|
||||
MOZ_ASSERT(stateIsInitialized());
|
||||
|
||||
uint32_t v = getFixedSlot(Slot_State).toInt32();
|
||||
MOZ_ASSERT((v & ~SettableBits) == 0);
|
||||
|
||||
return static_cast<State>(v & StateMask);
|
||||
}
|
||||
|
||||
State flags() const {
|
||||
MOZ_ASSERT(stateIsInitialized());
|
||||
|
||||
uint32_t v = getFixedSlot(Slot_State).toInt32();
|
||||
MOZ_ASSERT((v & ~SettableBits) == 0);
|
||||
|
||||
return static_cast<State>(v & FlagMask);
|
||||
}
|
||||
|
||||
void initWritableState() {
|
||||
MOZ_ASSERT(!stateIsInitialized());
|
||||
|
||||
setFixedSlot(Slot_State, JS::Int32Value(Writable));
|
||||
|
||||
MOZ_ASSERT(writable());
|
||||
MOZ_ASSERT(!backpressure());
|
||||
}
|
||||
|
||||
void setState(State newState) {
|
||||
MOZ_ASSERT(stateIsInitialized());
|
||||
MOZ_ASSERT((newState & ~StateBits) == 0);
|
||||
MOZ_ASSERT(newState <= Errored);
|
||||
|
||||
#ifdef DEBUG
|
||||
{
|
||||
auto current = state();
|
||||
if (current == Writable) {
|
||||
MOZ_ASSERT(newState == Closed || newState == Erroring);
|
||||
} else if (current == Erroring) {
|
||||
MOZ_ASSERT(newState == Errored || newState == Closed);
|
||||
} else if (current == Closed || current == Errored) {
|
||||
MOZ_ASSERT_UNREACHABLE(
|
||||
"closed/errored stream shouldn't undergo state transitions");
|
||||
} else {
|
||||
MOZ_ASSERT_UNREACHABLE("smashed state bits?");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t newValue = static_cast<uint32_t>(newState) |
|
||||
(getFixedSlot(Slot_State).toInt32() & FlagMask);
|
||||
setFixedSlot(Slot_State,
|
||||
JS::Int32Value(mozilla::AssertedCast<int32_t>(newValue)));
|
||||
}
|
||||
|
||||
void setFlag(State flag, bool set) {
|
||||
MOZ_ASSERT(stateIsInitialized());
|
||||
MOZ_ASSERT(mozilla::IsPowerOfTwo(uint32_t(flag)));
|
||||
MOZ_ASSERT((flag & FlagBits) != 0);
|
||||
|
||||
uint32_t v = getFixedSlot(Slot_State).toInt32();
|
||||
MOZ_ASSERT((v & ~SettableBits) == 0);
|
||||
|
||||
uint32_t newValue = set ? (v | flag) : (v & ~flag);
|
||||
setFixedSlot(Slot_State,
|
||||
JS::Int32Value(mozilla::AssertedCast<int32_t>(newValue)));
|
||||
}
|
||||
|
||||
public:
|
||||
bool writable() const { return state() == Writable; }
|
||||
|
||||
bool closed() const { return state() == Closed; }
|
||||
void setClosed() { setState(Closed); }
|
||||
|
||||
bool erroring() const { return state() == Erroring; }
|
||||
void setErroring() { setState(Erroring); }
|
||||
|
||||
bool errored() const { return state() == Errored; }
|
||||
void setErrored() { setState(Errored); }
|
||||
|
||||
bool backpressure() const { return flags() & Backpressure; }
|
||||
void setBackpressure(bool pressure) { setFlag(Backpressure, pressure); }
|
||||
|
||||
bool haveInFlightWriteRequest() const {
|
||||
return flags() & HaveInFlightWriteRequest;
|
||||
}
|
||||
void setHaveInFlightWriteRequest() {
|
||||
MOZ_ASSERT(!haveInFlightWriteRequest());
|
||||
MOZ_ASSERT(writeRequests()->length() > 0);
|
||||
setFlag(HaveInFlightWriteRequest, true);
|
||||
}
|
||||
|
||||
bool haveInFlightCloseRequest() const {
|
||||
return flags() & HaveInFlightCloseRequest;
|
||||
}
|
||||
|
||||
bool hasController() const {
|
||||
return !getFixedSlot(Slot_Controller).isUndefined();
|
||||
}
|
||||
inline WritableStreamDefaultController* controller() const;
|
||||
inline void setController(WritableStreamDefaultController* controller);
|
||||
void clearController() {
|
||||
setFixedSlot(Slot_Controller, JS::UndefinedValue());
|
||||
}
|
||||
|
||||
bool hasWriter() const { return !getFixedSlot(Slot_Writer).isUndefined(); }
|
||||
bool isLocked() const { return hasWriter(); }
|
||||
void setWriter(JSObject* writer) {
|
||||
MOZ_ASSERT(!hasWriter());
|
||||
setFixedSlot(Slot_Writer, JS::ObjectValue(*writer));
|
||||
}
|
||||
void clearWriter() { setFixedSlot(Slot_Writer, JS::UndefinedValue()); }
|
||||
|
||||
JS::Value storedError() const { return getFixedSlot(Slot_StoredError); }
|
||||
void setStoredError(JS::Handle<JS::Value> value) {
|
||||
setFixedSlot(Slot_StoredError, value);
|
||||
}
|
||||
void clearStoredError() {
|
||||
setFixedSlot(Slot_StoredError, JS::UndefinedValue());
|
||||
}
|
||||
|
||||
JS::Value inFlightWriteRequest() const {
|
||||
MOZ_ASSERT(stateIsInitialized());
|
||||
|
||||
// The in-flight write request is the first element of |writeRequests()| --
|
||||
// if there is a request in flight.
|
||||
if (haveInFlightWriteRequest()) {
|
||||
MOZ_ASSERT(writeRequests()->length() > 0);
|
||||
return writeRequests()->get(0);
|
||||
}
|
||||
|
||||
return JS::UndefinedValue();
|
||||
}
|
||||
|
||||
void clearInFlightWriteRequest(JSContext* cx);
|
||||
|
||||
JS::Value closeRequest() const {
|
||||
JS::Value v = getFixedSlot(Slot_CloseRequest);
|
||||
if (v.isUndefined()) {
|
||||
// In principle |haveInFlightCloseRequest()| only distinguishes whether
|
||||
// the close-request slot is [[closeRequest]] or [[inFlightCloseRequest]].
|
||||
// In practice, for greater implementation strictness to try to head off
|
||||
// more bugs, we require that the HaveInFlightCloseRequest flag be unset
|
||||
// when [[closeRequest]] and [[inFlightCloseRequest]] are both undefined.
|
||||
MOZ_ASSERT(!haveInFlightCloseRequest());
|
||||
return JS::UndefinedValue();
|
||||
}
|
||||
|
||||
if (!haveInFlightCloseRequest()) {
|
||||
return v;
|
||||
}
|
||||
|
||||
return JS::UndefinedValue();
|
||||
}
|
||||
|
||||
void setCloseRequest(JSObject* closeRequest) {
|
||||
MOZ_ASSERT(!haveCloseRequestOrInFlightCloseRequest());
|
||||
setFixedSlot(Slot_CloseRequest, JS::ObjectValue(*closeRequest));
|
||||
MOZ_ASSERT(!haveInFlightCloseRequest());
|
||||
}
|
||||
|
||||
void clearCloseRequest() {
|
||||
MOZ_ASSERT(!haveInFlightCloseRequest());
|
||||
MOZ_ASSERT(!getFixedSlot(Slot_CloseRequest).isUndefined());
|
||||
setFixedSlot(Slot_CloseRequest, JS::UndefinedValue());
|
||||
}
|
||||
|
||||
JS::Value inFlightCloseRequest() const {
|
||||
JS::Value v = getFixedSlot(Slot_CloseRequest);
|
||||
if (v.isUndefined()) {
|
||||
// In principle |haveInFlightCloseRequest()| only distinguishes whether
|
||||
// the close-request slot is [[closeRequest]] or [[inFlightCloseRequest]].
|
||||
// In practice, for greater implementation strictness to try to head off
|
||||
// more bugs, we require that the HaveInFlightCloseRequest flag be unset
|
||||
// when [[closeRequest]] and [[inFlightCloseRequest]] are both undefined.
|
||||
MOZ_ASSERT(!haveInFlightCloseRequest());
|
||||
return JS::UndefinedValue();
|
||||
}
|
||||
|
||||
if (haveInFlightCloseRequest()) {
|
||||
return v;
|
||||
}
|
||||
|
||||
return JS::UndefinedValue();
|
||||
}
|
||||
|
||||
bool haveCloseRequestOrInFlightCloseRequest() const {
|
||||
// Slot_CloseRequest suffices to store both [[closeRequest]] and
|
||||
// [[inFlightCloseRequest]], with the precisely-set field determined by
|
||||
// |haveInFlightCloseRequest()|. If both are undefined, then per above, for
|
||||
// extra implementation rigor, |haveInFlightCloseRequest()| will be false,
|
||||
// so additionally assert that.
|
||||
if (getFixedSlot(Slot_CloseRequest).isUndefined()) {
|
||||
MOZ_ASSERT(!haveInFlightCloseRequest());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void convertCloseRequestToInFlightCloseRequest() {
|
||||
MOZ_ASSERT(stateIsInitialized());
|
||||
MOZ_ASSERT(!haveInFlightCloseRequest());
|
||||
setFlag(HaveInFlightCloseRequest, true);
|
||||
MOZ_ASSERT(haveInFlightCloseRequest());
|
||||
}
|
||||
|
||||
void clearInFlightCloseRequest() {
|
||||
MOZ_ASSERT(stateIsInitialized());
|
||||
MOZ_ASSERT(haveInFlightCloseRequest());
|
||||
MOZ_ASSERT(!getFixedSlot(Slot_CloseRequest).isUndefined());
|
||||
|
||||
// As noted above, for greater rigor we require HaveInFlightCloseRequest be
|
||||
// unset when [[closeRequest]] and [[inFlightCloseRequest]] are both
|
||||
// undefined.
|
||||
setFlag(HaveInFlightCloseRequest, false);
|
||||
setFixedSlot(Slot_CloseRequest, JS::UndefinedValue());
|
||||
}
|
||||
|
||||
ListObject* writeRequests() const {
|
||||
MOZ_ASSERT(!getFixedSlot(Slot_WriteRequests).isUndefined(),
|
||||
"shouldn't be accessing [[writeRequests]] on a newborn and "
|
||||
"uninitialized stream, or on a stream that's errored and no "
|
||||
"longer has any write requests");
|
||||
return &getFixedSlot(Slot_WriteRequests).toObject().as<ListObject>();
|
||||
}
|
||||
void clearWriteRequests() {
|
||||
// Setting [[writeRequests]] to an empty List in the irrevocably-in-error
|
||||
// case (in which [[writeRequests]] is never again accessed) is optimized to
|
||||
// just clearing the field. See the comment on the slot constant above.
|
||||
MOZ_ASSERT(stateIsInitialized());
|
||||
MOZ_ASSERT(!haveInFlightWriteRequest(),
|
||||
"must clear the in-flight request flag before clearing "
|
||||
"requests");
|
||||
setFixedSlot(Slot_WriteRequests, JS::UndefinedValue());
|
||||
}
|
||||
|
||||
bool hasPendingAbortRequest() const {
|
||||
MOZ_ASSERT(stateIsInitialized());
|
||||
return !getFixedSlot(Slot_PendingAbortRequestPromise).isUndefined();
|
||||
}
|
||||
JSObject* pendingAbortRequestPromise() const {
|
||||
MOZ_ASSERT(hasPendingAbortRequest());
|
||||
return &getFixedSlot(Slot_PendingAbortRequestPromise).toObject();
|
||||
}
|
||||
JS::Value pendingAbortRequestReason() const {
|
||||
MOZ_ASSERT(hasPendingAbortRequest());
|
||||
return getFixedSlot(Slot_PendingAbortRequestReason);
|
||||
}
|
||||
bool pendingAbortRequestWasAlreadyErroring() const {
|
||||
MOZ_ASSERT(hasPendingAbortRequest());
|
||||
return flags() & PendingAbortRequestWasAlreadyErroring;
|
||||
}
|
||||
|
||||
void setPendingAbortRequest(JSObject* promise, const JS::Value& reason,
|
||||
bool wasAlreadyErroring) {
|
||||
MOZ_ASSERT(!hasPendingAbortRequest());
|
||||
MOZ_ASSERT(!(flags() & PendingAbortRequestWasAlreadyErroring));
|
||||
setFixedSlot(Slot_PendingAbortRequestPromise, JS::ObjectValue(*promise));
|
||||
setFixedSlot(Slot_PendingAbortRequestReason, reason);
|
||||
setFlag(PendingAbortRequestWasAlreadyErroring, wasAlreadyErroring);
|
||||
}
|
||||
|
||||
void clearPendingAbortRequest() {
|
||||
MOZ_ASSERT(stateIsInitialized());
|
||||
MOZ_ASSERT(hasPendingAbortRequest());
|
||||
|
||||
// [[pendingAbortRequest]] is { [[promise]], [[reason]] } in the spec but
|
||||
// separate slots in our implementation, so both must be cleared.
|
||||
setFixedSlot(Slot_PendingAbortRequestPromise, JS::UndefinedValue());
|
||||
setFixedSlot(Slot_PendingAbortRequestReason, JS::UndefinedValue());
|
||||
}
|
||||
|
||||
[[nodiscard]] static WritableStream* create(
|
||||
JSContext* cx, void* nsISupportsObject_alreadyAddreffed = nullptr,
|
||||
JS::Handle<JSObject*> proto = nullptr);
|
||||
|
||||
static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp);
|
||||
|
||||
static const ClassSpec classSpec_;
|
||||
static const JSClass class_;
|
||||
static const ClassSpec protoClassSpec_;
|
||||
static const JSClass protoClass_;
|
||||
};
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_WritableStream_h
|
@ -1,83 +0,0 @@
|
||||
/* -*- 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 WritableStreamDefaultController. */
|
||||
|
||||
#include "builtin/streams/WritableStreamDefaultController.h"
|
||||
|
||||
#include "builtin/streams/ClassSpecMacro.h" // JS_STREAMS_CLASS_SPEC
|
||||
#include "builtin/streams/WritableStream.h" // js::WritableStream
|
||||
#include "builtin/streams/WritableStreamDefaultControllerOperations.h" // js::WritableStreamDefaultControllerError
|
||||
#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
|
||||
#include "js/Class.h" // js::ClassSpec, JS_NULL_CLASS_OPS
|
||||
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
|
||||
#include "js/PropertySpec.h" // JS{Function,Property}Spec, JS_{FS,PS}_END
|
||||
#include "js/Value.h" // JS::Value
|
||||
|
||||
#include "vm/Compartment-inl.h" // js::UnwrapAndTypeCheckThis
|
||||
|
||||
using JS::CallArgs;
|
||||
using JS::CallArgsFromVp;
|
||||
using JS::Rooted;
|
||||
using JS::Value;
|
||||
|
||||
using js::ClassSpec;
|
||||
using js::UnwrapAndTypeCheckThis;
|
||||
using js::WritableStreamDefaultController;
|
||||
using js::WritableStreamDefaultControllerError;
|
||||
|
||||
/*** 4.7. Class WritableStreamDefaultController *****************************/
|
||||
|
||||
/**
|
||||
* Streams spec, 4.7.3.
|
||||
* new WritableStreamDefaultController()
|
||||
*/
|
||||
bool WritableStreamDefaultController::constructor(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
// Step 1: Throw a TypeError.
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_BOGUS_CONSTRUCTOR,
|
||||
"WritableStreamDefaultController");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.7.4.1. error(e)
|
||||
*/
|
||||
static bool WritableStreamDefaultController_error(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
// Step 1: If ! IsWritableStreamDefaultController(this) is false, throw a
|
||||
// TypeError exception.
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
Rooted<WritableStreamDefaultController*> unwrappedController(
|
||||
cx, UnwrapAndTypeCheckThis<WritableStreamDefaultController>(cx, args,
|
||||
"error"));
|
||||
if (!unwrappedController) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: Let state be this.[[controlledWritableStream]].[[state]].
|
||||
// Step 3: If state is not "writable", return.
|
||||
if (unwrappedController->stream()->writable()) {
|
||||
// Step 4: Perform ! WritableStreamDefaultControllerError(this, e).
|
||||
if (!WritableStreamDefaultControllerError(cx, unwrappedController,
|
||||
args.get(0))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
static const JSPropertySpec WritableStreamDefaultController_properties[] = {
|
||||
JS_PS_END};
|
||||
|
||||
static const JSFunctionSpec WritableStreamDefaultController_methods[] = {
|
||||
JS_FN("error", WritableStreamDefaultController_error, 1, 0), JS_FS_END};
|
||||
|
||||
JS_STREAMS_CLASS_SPEC(WritableStreamDefaultController, 0, SlotCount,
|
||||
ClassSpec::DontDefineConstructor, 0, JS_NULL_CLASS_OPS);
|
@ -1,186 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
/* WritableStream controller classes and functions. */
|
||||
|
||||
#ifndef builtin_streams_WritableStreamDefaultController_h
|
||||
#define builtin_streams_WritableStreamDefaultController_h
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
|
||||
#include <stdint.h> // uint32_t
|
||||
|
||||
#include "builtin/streams/StreamController.h" // js::StreamController
|
||||
#include "builtin/streams/WritableStream.h" // js::WritableStream
|
||||
#include "js/Class.h" // JSClass, js::ClassSpec
|
||||
#include "js/RootingAPI.h" // JS::Handle
|
||||
#include "js/Stream.h" // JS::WritableStreamUnderlyingSink
|
||||
#include "js/Value.h" // JS::Value, JS::{Number,Object,Private,Undefined}Value, JS::UndefinedHandleValue
|
||||
#include "vm/NativeObject.h" // js::NativeObject
|
||||
|
||||
namespace js {
|
||||
|
||||
class WritableStreamDefaultController : public StreamController {
|
||||
public:
|
||||
/**
|
||||
* Memory layout for WritableStream default controllers, starting after the
|
||||
* slots reserved for queue container usage. (Note that this is the only
|
||||
* writable stream controller class in the spec: ReadableByteStreamController
|
||||
* exists, but WritableByteStreamController does not.)
|
||||
*/
|
||||
enum Slots {
|
||||
/**
|
||||
* The stream that this controller controls. Stream and controller are
|
||||
* initialized at the same time underneath the |WritableStream| constructor,
|
||||
* so they are same-compartment with each other.
|
||||
*/
|
||||
Slot_Stream = StreamController::SlotCount,
|
||||
|
||||
/**
|
||||
* The underlying sink object that this controller and its associated stream
|
||||
* write to.
|
||||
*
|
||||
* This is a user-provided value, the first argument passed to
|
||||
* |new WritableStream|, so it may be a cross-compartment wrapper around an
|
||||
* object from another realm.
|
||||
*/
|
||||
Slot_UnderlyingSink,
|
||||
|
||||
/** Number stored as DoubleValue. */
|
||||
Slot_StrategyHWM,
|
||||
|
||||
/**
|
||||
* Either undefined if each chunk has size 1, or a callable object to be
|
||||
* invoked on each chunk to determine its size. See
|
||||
* MakeSizeAlgorithmFromSizeFunction.
|
||||
*/
|
||||
Slot_StrategySize,
|
||||
|
||||
/**
|
||||
* Slots containing the core of each of the write/close/abort algorithms the
|
||||
* spec creates from the underlying sink passed in when creating a
|
||||
* |WritableStream|. ("core", as in the value produced by
|
||||
* |CreateAlgorithmFromUnderlyingMethod| after validating the user-provided
|
||||
* input.)
|
||||
*
|
||||
* These slots are initialized underneath the |WritableStream| constructor,
|
||||
* so they are same-compartment with both stream and controller. (They
|
||||
* could be wrappers around arbitrary callable objects from other
|
||||
* compartments, tho.)
|
||||
*/
|
||||
Slot_WriteMethod,
|
||||
Slot_CloseMethod,
|
||||
Slot_AbortMethod,
|
||||
|
||||
/** Bit field stored as Int32Value. */
|
||||
Slot_Flags,
|
||||
|
||||
SlotCount
|
||||
};
|
||||
|
||||
enum ControllerFlags {
|
||||
Flag_Started = 0b0001,
|
||||
Flag_ExternalSink = 0b0010,
|
||||
};
|
||||
|
||||
WritableStream* stream() const {
|
||||
return &getFixedSlot(Slot_Stream).toObject().as<WritableStream>();
|
||||
}
|
||||
void setStream(WritableStream* stream) {
|
||||
setFixedSlot(Slot_Stream, JS::ObjectValue(*stream));
|
||||
}
|
||||
|
||||
JS::Value underlyingSink() const { return getFixedSlot(Slot_UnderlyingSink); }
|
||||
void setUnderlyingSink(const JS::Value& underlyingSink) {
|
||||
setFixedSlot(Slot_UnderlyingSink, underlyingSink);
|
||||
}
|
||||
|
||||
JS::WritableStreamUnderlyingSink* externalSink() const {
|
||||
static_assert(alignof(JS::WritableStreamUnderlyingSink) >= 2,
|
||||
"external underling sinks are stored as PrivateValues, so "
|
||||
"they must have even addresses");
|
||||
MOZ_ASSERT(hasExternalSink());
|
||||
return static_cast<JS::WritableStreamUnderlyingSink*>(
|
||||
underlyingSink().toPrivate());
|
||||
}
|
||||
void setExternalSink(JS::WritableStreamUnderlyingSink* underlyingSink) {
|
||||
setUnderlyingSink(JS::PrivateValue(underlyingSink));
|
||||
addFlags(Flag_ExternalSink);
|
||||
}
|
||||
static void clearUnderlyingSink(
|
||||
JS::Handle<WritableStreamDefaultController*> controller,
|
||||
bool finalizeSink = true) {
|
||||
if (controller->hasExternalSink()) {
|
||||
if (finalizeSink) {
|
||||
controller->externalSink()->finalize();
|
||||
}
|
||||
controller->setFlags(controller->flags() & ~Flag_ExternalSink);
|
||||
}
|
||||
controller->setUnderlyingSink(JS::UndefinedHandleValue);
|
||||
}
|
||||
|
||||
JS::Value writeMethod() const { return getFixedSlot(Slot_WriteMethod); }
|
||||
void setWriteMethod(const JS::Value& writeMethod) {
|
||||
setFixedSlot(Slot_WriteMethod, writeMethod);
|
||||
}
|
||||
void clearWriteMethod() { setWriteMethod(JS::UndefinedValue()); }
|
||||
|
||||
JS::Value closeMethod() const { return getFixedSlot(Slot_CloseMethod); }
|
||||
void setCloseMethod(const JS::Value& closeMethod) {
|
||||
setFixedSlot(Slot_CloseMethod, closeMethod);
|
||||
}
|
||||
void clearCloseMethod() { setCloseMethod(JS::UndefinedValue()); }
|
||||
|
||||
JS::Value abortMethod() const { return getFixedSlot(Slot_AbortMethod); }
|
||||
void setAbortMethod(const JS::Value& abortMethod) {
|
||||
setFixedSlot(Slot_AbortMethod, abortMethod);
|
||||
}
|
||||
void clearAbortMethod() { setAbortMethod(JS::UndefinedValue()); }
|
||||
|
||||
double strategyHWM() const {
|
||||
return getFixedSlot(Slot_StrategyHWM).toDouble();
|
||||
}
|
||||
void setStrategyHWM(double highWaterMark) {
|
||||
setFixedSlot(Slot_StrategyHWM, DoubleValue(highWaterMark));
|
||||
}
|
||||
|
||||
JS::Value strategySize() const { return getFixedSlot(Slot_StrategySize); }
|
||||
void setStrategySize(const JS::Value& size) {
|
||||
setFixedSlot(Slot_StrategySize, size);
|
||||
}
|
||||
void clearStrategySize() { setStrategySize(JS::UndefinedValue()); }
|
||||
|
||||
uint32_t flags() const { return getFixedSlot(Slot_Flags).toInt32(); }
|
||||
void setFlags(uint32_t flags) { setFixedSlot(Slot_Flags, Int32Value(flags)); }
|
||||
void addFlags(uint32_t flags) { setFlags(this->flags() | flags); }
|
||||
void removeFlags(uint32_t flags) { setFlags(this->flags() & ~flags); }
|
||||
|
||||
bool started() const { return flags() & Flag_Started; }
|
||||
void setStarted() { addFlags(Flag_Started); }
|
||||
|
||||
bool hasExternalSink() const { return flags() & Flag_ExternalSink; }
|
||||
|
||||
static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp);
|
||||
static const ClassSpec classSpec_;
|
||||
static const JSClass class_;
|
||||
static const ClassSpec protoClassSpec_;
|
||||
static const JSClass protoClass_;
|
||||
};
|
||||
|
||||
inline WritableStreamDefaultController* WritableStream::controller() const {
|
||||
return &getFixedSlot(Slot_Controller)
|
||||
.toObject()
|
||||
.as<WritableStreamDefaultController>();
|
||||
}
|
||||
|
||||
inline void WritableStream::setController(
|
||||
WritableStreamDefaultController* controller) {
|
||||
setFixedSlot(Slot_Controller, JS::ObjectValue(*controller));
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_WritableStreamDefaultController_h
|
File diff suppressed because it is too large
Load Diff
@ -1,107 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
/* Writable stream default controller abstract operations. */
|
||||
|
||||
#ifndef builtin_streams_WritableStreamDefaultControllerOperations_h
|
||||
#define builtin_streams_WritableStreamDefaultControllerOperations_h
|
||||
|
||||
#include "jstypes.h" // JS_PUBLIC_API
|
||||
#include "js/RootingAPI.h" // JS::Handle
|
||||
#include "js/Value.h" // JS::Value
|
||||
|
||||
struct JS_PUBLIC_API JSContext;
|
||||
|
||||
namespace js {
|
||||
|
||||
class WritableStream;
|
||||
class WritableStreamDefaultController;
|
||||
|
||||
extern JSObject* WritableStreamControllerAbortSteps(
|
||||
JSContext* cx,
|
||||
JS::Handle<WritableStreamDefaultController*> unwrappedController,
|
||||
JS::Handle<JS::Value> reason);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamControllerErrorSteps(
|
||||
JSContext* cx,
|
||||
JS::Handle<WritableStreamDefaultController*> unwrappedController);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamControllerStartHandler(JSContext* cx,
|
||||
unsigned argc,
|
||||
JS::Value* vp);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamControllerStartFailedHandler(
|
||||
JSContext* cx, unsigned argc, JS::Value* vp);
|
||||
|
||||
/**
|
||||
* Characterizes the family of algorithms, (startAlgorithm, writeAlgorithm,
|
||||
* closeAlgorithm, abortAlgorithm), associated with a writable stream.
|
||||
*
|
||||
* See the comment on SetUpWritableStreamDefaultController().
|
||||
*/
|
||||
enum class SinkAlgorithms {
|
||||
Script,
|
||||
Transform,
|
||||
};
|
||||
|
||||
[[nodiscard]] extern bool SetUpWritableStreamDefaultController(
|
||||
JSContext* cx, JS::Handle<WritableStream*> stream,
|
||||
SinkAlgorithms algorithms, JS::Handle<JS::Value> underlyingSink,
|
||||
JS::Handle<JS::Value> writeMethod, JS::Handle<JS::Value> closeMethod,
|
||||
JS::Handle<JS::Value> abortMethod, double highWaterMark,
|
||||
JS::Handle<JS::Value> size);
|
||||
|
||||
[[nodiscard]] extern bool
|
||||
SetUpWritableStreamDefaultControllerFromUnderlyingSink(
|
||||
JSContext* cx, JS::Handle<WritableStream*> stream,
|
||||
JS::Handle<JS::Value> underlyingSink, double highWaterMark,
|
||||
JS::Handle<JS::Value> sizeAlgorithm);
|
||||
|
||||
extern void WritableStreamDefaultControllerClearAlgorithms(
|
||||
WritableStreamDefaultController* unwrappedController);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamDefaultControllerClose(
|
||||
JSContext* cx,
|
||||
JS::Handle<WritableStreamDefaultController*> unwrappedController);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamDefaultControllerGetChunkSize(
|
||||
JSContext* cx,
|
||||
JS::Handle<WritableStreamDefaultController*> unwrappedController,
|
||||
JS::Handle<JS::Value> chunk, JS::MutableHandle<JS::Value> returnValue);
|
||||
|
||||
extern double WritableStreamDefaultControllerGetDesiredSize(
|
||||
const WritableStreamDefaultController* controller);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamDefaultControllerWrite(
|
||||
JSContext* cx,
|
||||
JS::Handle<WritableStreamDefaultController*> unwrappedController,
|
||||
JS::Handle<JS::Value> chunk, JS::Handle<JS::Value> chunkSize);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamDefaultControllerErrorIfNeeded(
|
||||
JSContext* cx,
|
||||
JS::Handle<WritableStreamDefaultController*> unwrappedController,
|
||||
JS::Handle<JS::Value> error);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamDefaultControllerProcessClose(
|
||||
JSContext* cx,
|
||||
JS::Handle<WritableStreamDefaultController*> unwrappedController);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamDefaultControllerProcessWrite(
|
||||
JSContext* cx,
|
||||
JS::Handle<WritableStreamDefaultController*> unwrappedController,
|
||||
JS::Handle<JS::Value> chunk);
|
||||
|
||||
extern bool WritableStreamDefaultControllerGetBackpressure(
|
||||
const WritableStreamDefaultController* unwrappedController);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamDefaultControllerError(
|
||||
JSContext* cx,
|
||||
JS::Handle<WritableStreamDefaultController*> unwrappedController,
|
||||
JS::Handle<JS::Value> error);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_WritableStreamDefaultControllerOperations_h
|
@ -1,39 +0,0 @@
|
||||
/* -*- 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 WritableStreamDefaultWriter. */
|
||||
|
||||
#ifndef builtin_streams_WritableStreamDefaultWriter_inl_h
|
||||
#define builtin_streams_WritableStreamDefaultWriter_inl_h
|
||||
|
||||
#include "builtin/streams/WritableStreamDefaultWriter.h"
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
|
||||
#include "builtin/streams/WritableStream.h" // js::WritableStream
|
||||
#include "js/RootingAPI.h" // JS::Handle
|
||||
#include "js/Value.h" // JS::ObjectValue
|
||||
#include "vm/NativeObject.h" // js::NativeObject
|
||||
|
||||
#include "vm/Compartment-inl.h" // js::UnwrapInternalSlot
|
||||
|
||||
struct JS_PUBLIC_API JSContext;
|
||||
|
||||
namespace js {
|
||||
|
||||
/**
|
||||
* Returns the stream associated with the given reader.
|
||||
*/
|
||||
[[nodiscard]] inline WritableStream* UnwrapStreamFromWriter(
|
||||
JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter) {
|
||||
MOZ_ASSERT(unwrappedWriter->hasStream());
|
||||
return UnwrapInternalSlot<WritableStream>(
|
||||
cx, unwrappedWriter, WritableStreamDefaultWriter::Slot_Stream);
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_WritableStreamDefaultWriter_inl_h
|
@ -1,528 +0,0 @@
|
||||
/* -*- 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 WritableStreamDefaultWriter. */
|
||||
|
||||
#include "builtin/streams/WritableStreamDefaultWriter-inl.h"
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
|
||||
#include "builtin/streams/ClassSpecMacro.h" // JS_STREAMS_CLASS_SPEC
|
||||
#include "builtin/streams/MiscellaneousOperations.h" // js::ReturnPromiseRejectedWithPendingError
|
||||
#include "builtin/streams/WritableStream.h" // js::WritableStream
|
||||
#include "builtin/streams/WritableStreamOperations.h" // js::WritableStreamCloseQueuedOrInFlight
|
||||
#include "builtin/streams/WritableStreamWriterOperations.h" // js::WritableStreamDefaultWriter{Abort,GetDesiredSize,Release,Write}
|
||||
#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
|
||||
#include "js/Class.h" // js::ClassSpec, JS_NULL_CLASS_OPS
|
||||
#include "js/ErrorReport.h" // JS_ReportErrorNumberASCII
|
||||
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
|
||||
#include "js/PropertySpec.h" // JS{Function,Property}Spec, JS_{FS,PS}_END, JS_{FN,PSG}
|
||||
#include "js/RootingAPI.h" // JS::Handle
|
||||
#include "js/Value.h" // JS::Value
|
||||
#include "vm/Compartment.h" // JS::Compartment
|
||||
#include "vm/JSContext.h" // JSContext
|
||||
#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
|
||||
|
||||
#include "builtin/Promise-inl.h" // js::SetSettledPromiseIsHandled
|
||||
#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::UnwrapAndTypeCheck{Argument,This}
|
||||
#include "vm/JSObject-inl.h" // js::NewObjectWithClassProto
|
||||
#include "vm/NativeObject-inl.h" // js::ThrowIfNotConstructing
|
||||
#include "vm/Realm-inl.h" // js::AutoRealm
|
||||
|
||||
using JS::CallArgs;
|
||||
using JS::CallArgsFromVp;
|
||||
using JS::Handle;
|
||||
using JS::Rooted;
|
||||
using JS::Value;
|
||||
|
||||
using js::ClassSpec;
|
||||
using js::GetErrorMessage;
|
||||
using js::PromiseObject;
|
||||
using js::ReturnPromiseRejectedWithPendingError;
|
||||
using js::UnwrapAndTypeCheckArgument;
|
||||
using js::UnwrapAndTypeCheckThis;
|
||||
using js::WritableStream;
|
||||
using js::WritableStreamCloseQueuedOrInFlight;
|
||||
using js::WritableStreamDefaultWriter;
|
||||
using js::WritableStreamDefaultWriterGetDesiredSize;
|
||||
using js::WritableStreamDefaultWriterRelease;
|
||||
using js::WritableStreamDefaultWriterWrite;
|
||||
|
||||
/*** 4.5. Class WritableStreamDefaultWriter *********************************/
|
||||
|
||||
/**
|
||||
* Stream spec, 4.5.3. new WritableStreamDefaultWriter(stream)
|
||||
* Steps 3-9.
|
||||
*/
|
||||
[[nodiscard]] WritableStreamDefaultWriter*
|
||||
js::CreateWritableStreamDefaultWriter(JSContext* cx,
|
||||
Handle<WritableStream*> unwrappedStream,
|
||||
Handle<JSObject*> proto /* = nullptr */) {
|
||||
Rooted<WritableStreamDefaultWriter*> writer(
|
||||
cx, NewObjectWithClassProto<WritableStreamDefaultWriter>(cx, proto));
|
||||
if (!writer) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 3: Set this.[[ownerWritableStream]] to stream.
|
||||
{
|
||||
Rooted<JSObject*> stream(cx, unwrappedStream);
|
||||
if (!cx->compartment()->wrap(cx, &stream)) {
|
||||
return nullptr;
|
||||
}
|
||||
writer->setStream(stream);
|
||||
}
|
||||
|
||||
// Step 4 is moved to the end.
|
||||
|
||||
// Step 5: Let state be stream.[[state]].
|
||||
// Step 6: If state is "writable",
|
||||
if (unwrappedStream->writable()) {
|
||||
// Step 6.a: If ! WritableStreamCloseQueuedOrInFlight(stream) is false and
|
||||
// stream.[[backpressure]] is true, set this.[[readyPromise]] to a
|
||||
// new promise.
|
||||
PromiseObject* promise;
|
||||
if (!WritableStreamCloseQueuedOrInFlight(unwrappedStream) &&
|
||||
unwrappedStream->backpressure()) {
|
||||
promise = PromiseObject::createSkippingExecutor(cx);
|
||||
}
|
||||
// Step 6.b: Otherwise, set this.[[readyPromise]] to a promise resolved with
|
||||
// undefined.
|
||||
else {
|
||||
promise = PromiseResolvedWithUndefined(cx);
|
||||
}
|
||||
if (!promise) {
|
||||
return nullptr;
|
||||
}
|
||||
writer->setReadyPromise(promise);
|
||||
|
||||
// Step 6.c: Set this.[[closedPromise]] to a new promise.
|
||||
promise = PromiseObject::createSkippingExecutor(cx);
|
||||
if (!promise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
writer->setClosedPromise(promise);
|
||||
}
|
||||
// Step 8: Otherwise, if state is "closed",
|
||||
else if (unwrappedStream->closed()) {
|
||||
// Step 8.a: Set this.[[readyPromise]] to a promise resolved with undefined.
|
||||
PromiseObject* readyPromise = PromiseResolvedWithUndefined(cx);
|
||||
if (!readyPromise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
writer->setReadyPromise(readyPromise);
|
||||
|
||||
// Step 8.b: Set this.[[closedPromise]] to a promise resolved with
|
||||
// undefined.
|
||||
PromiseObject* closedPromise = PromiseResolvedWithUndefined(cx);
|
||||
if (!closedPromise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
writer->setClosedPromise(closedPromise);
|
||||
} else {
|
||||
// Wrap stream.[[StoredError]] just once for either step 7 or step 9.
|
||||
Rooted<Value> storedError(cx, unwrappedStream->storedError());
|
||||
if (!cx->compartment()->wrap(cx, &storedError)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 7: Otherwise, if state is "erroring",
|
||||
if (unwrappedStream->erroring()) {
|
||||
// Step 7.a: Set this.[[readyPromise]] to a promise rejected with
|
||||
// stream.[[storedError]].
|
||||
Rooted<JSObject*> promise(
|
||||
cx, PromiseObject::unforgeableReject(cx, storedError));
|
||||
if (!promise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
writer->setReadyPromise(promise);
|
||||
|
||||
// Step 7.b: Set this.[[readyPromise]].[[PromiseIsHandled]] to true.
|
||||
js::SetSettledPromiseIsHandled(cx, promise.as<PromiseObject>());
|
||||
|
||||
// Step 7.c: Set this.[[closedPromise]] to a new promise.
|
||||
JSObject* closedPromise = PromiseObject::createSkippingExecutor(cx);
|
||||
if (!closedPromise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
writer->setClosedPromise(closedPromise);
|
||||
}
|
||||
// Step 9: Otherwise,
|
||||
else {
|
||||
// Step 9.a: Assert: state is "errored".
|
||||
MOZ_ASSERT(unwrappedStream->errored());
|
||||
|
||||
Rooted<JSObject*> promise(cx);
|
||||
|
||||
// Step 9.b: Let storedError be stream.[[storedError]].
|
||||
// Step 9.c: Set this.[[readyPromise]] to a promise rejected with
|
||||
// storedError.
|
||||
promise = PromiseObject::unforgeableReject(cx, storedError);
|
||||
if (!promise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
writer->setReadyPromise(promise);
|
||||
|
||||
// Step 9.d: Set this.[[readyPromise]].[[PromiseIsHandled]] to true.
|
||||
js::SetSettledPromiseIsHandled(cx, promise.as<PromiseObject>());
|
||||
|
||||
// Step 9.e: Set this.[[closedPromise]] to a promise rejected with
|
||||
// storedError.
|
||||
promise = PromiseObject::unforgeableReject(cx, storedError);
|
||||
if (!promise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
writer->setClosedPromise(promise);
|
||||
|
||||
// Step 9.f: Set this.[[closedPromise]].[[PromiseIsHandled]] to true.
|
||||
js::SetSettledPromiseIsHandled(cx, promise.as<PromiseObject>());
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4 (reordered): Set stream.[[writer]] to this.
|
||||
// Doing this last prevents a partially-initialized writer from being attached
|
||||
// to the stream (and possibly left there on OOM).
|
||||
{
|
||||
AutoRealm ar(cx, unwrappedStream);
|
||||
Rooted<JSObject*> wrappedWriter(cx, writer);
|
||||
if (!cx->compartment()->wrap(cx, &wrappedWriter)) {
|
||||
return nullptr;
|
||||
}
|
||||
unwrappedStream->setWriter(wrappedWriter);
|
||||
}
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.5.3.
|
||||
* new WritableStreamDefaultWriter(stream)
|
||||
*/
|
||||
bool WritableStreamDefaultWriter::constructor(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
MOZ_ASSERT(cx->realm()->creationOptions().getWritableStreamsEnabled(),
|
||||
"WritableStream should be enabled in this realm if we reach here");
|
||||
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
if (!ThrowIfNotConstructing(cx, args, "WritableStreamDefaultWriter")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 1: If ! IsWritableStream(stream) is false, throw a TypeError
|
||||
// exception.
|
||||
Rooted<WritableStream*> unwrappedStream(
|
||||
cx, UnwrapAndTypeCheckArgument<WritableStream>(
|
||||
cx, args, "WritableStreamDefaultWriter constructor", 0));
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: If ! IsWritableStreamLocked(stream) is true, throw a TypeError
|
||||
// exception.
|
||||
if (unwrappedStream->isLocked()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WRITABLESTREAM_ALREADY_LOCKED);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Implicit in the spec: Find the prototype object to use.
|
||||
Rooted<JSObject*> proto(cx);
|
||||
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Null, &proto)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Steps 3-9.
|
||||
Rooted<WritableStreamDefaultWriter*> writer(
|
||||
cx, CreateWritableStreamDefaultWriter(cx, unwrappedStream, proto));
|
||||
if (!writer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*writer);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.5.4.1. get closed
|
||||
*/
|
||||
[[nodiscard]] static bool WritableStreamDefaultWriter_closed(JSContext* cx,
|
||||
unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: If ! IsWritableStreamDefaultWriter(this) is false, return a promise
|
||||
// rejected with a TypeError exception.
|
||||
Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
|
||||
cx, UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(cx, args,
|
||||
"get closed"));
|
||||
if (!unwrappedWriter) {
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 2: Return this.[[closedPromise]].
|
||||
Rooted<JSObject*> closedPromise(cx, unwrappedWriter->closedPromise());
|
||||
if (!cx->compartment()->wrap(cx, &closedPromise)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*closedPromise);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.5.4.2. get desiredSize
|
||||
*/
|
||||
[[nodiscard]] static bool WritableStreamDefaultWriter_desiredSize(JSContext* cx,
|
||||
unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: If ! IsWritableStreamDefaultWriter(this) is false, throw a
|
||||
// TypeError exception.
|
||||
Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
|
||||
cx, UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(
|
||||
cx, args, "get desiredSize"));
|
||||
if (!unwrappedWriter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: If this.[[ownerWritableStream]] is undefined, throw a TypeError
|
||||
// exception.
|
||||
if (!unwrappedWriter->hasStream()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WRITABLESTREAMWRITER_NOT_OWNED,
|
||||
"get desiredSize");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Return ! WritableStreamDefaultWriterGetDesiredSize(this).
|
||||
if (!WritableStreamDefaultWriterGetDesiredSize(cx, unwrappedWriter,
|
||||
args.rval())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(args.rval().isNull() || args.rval().isNumber(),
|
||||
"expected a type that'll never require wrapping");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.5.4.3. get ready
|
||||
*/
|
||||
[[nodiscard]] static bool WritableStreamDefaultWriter_ready(JSContext* cx,
|
||||
unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: If ! IsWritableStreamDefaultWriter(this) is false, return a promise
|
||||
// rejected with a TypeError exception.
|
||||
Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
|
||||
cx, UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(cx, args,
|
||||
"get ready"));
|
||||
if (!unwrappedWriter) {
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 2: Return this.[[readyPromise]].
|
||||
Rooted<JSObject*> readyPromise(cx, unwrappedWriter->readyPromise());
|
||||
if (!cx->compartment()->wrap(cx, &readyPromise)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*readyPromise);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.5.4.4. abort(reason)
|
||||
*/
|
||||
[[nodiscard]] static bool WritableStreamDefaultWriter_abort(JSContext* cx,
|
||||
unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: If ! IsWritableStreamDefaultWriter(this) is false, return a promise
|
||||
// rejected with a TypeError exception.
|
||||
Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
|
||||
cx,
|
||||
UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(cx, args, "abort"));
|
||||
if (!unwrappedWriter) {
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 2: If this.[[ownerWritableStream]] is undefined, return a promise
|
||||
// rejected with a TypeError exception.
|
||||
if (!unwrappedWriter->hasStream()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WRITABLESTREAMWRITER_NOT_OWNED, "abort");
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 3: Return ! WritableStreamDefaultWriterAbort(this, reason).
|
||||
JSObject* promise =
|
||||
WritableStreamDefaultWriterAbort(cx, unwrappedWriter, args.get(0));
|
||||
if (!promise) {
|
||||
return false;
|
||||
}
|
||||
cx->check(promise);
|
||||
|
||||
args.rval().setObject(*promise);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.5.4.5. close()
|
||||
*/
|
||||
[[nodiscard]] static bool WritableStreamDefaultWriter_close(JSContext* cx,
|
||||
unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: If ! IsWritableStreamDefaultWriter(this) is false, return a promise
|
||||
// rejected with a TypeError exception.
|
||||
Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
|
||||
cx,
|
||||
UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(cx, args, "close"));
|
||||
if (!unwrappedWriter) {
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 2: Let stream be this.[[ownerWritableStream]].
|
||||
// Step 3: If stream is undefined, return a promise rejected with a TypeError
|
||||
// exception.
|
||||
if (!unwrappedWriter->hasStream()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WRITABLESTREAMWRITER_NOT_OWNED, "write");
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
WritableStream* unwrappedStream = UnwrapStreamFromWriter(cx, unwrappedWriter);
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 4: If ! WritableStreamCloseQueuedOrInFlight(stream) is true, return a
|
||||
// promise rejected with a TypeError exception.
|
||||
if (WritableStreamCloseQueuedOrInFlight(unwrappedStream)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WRITABLESTREAM_CLOSE_CLOSING_OR_CLOSED);
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 5: Return ! WritableStreamDefaultWriterClose(this).
|
||||
JSObject* promise = WritableStreamDefaultWriterClose(cx, unwrappedWriter);
|
||||
if (!promise) {
|
||||
return false;
|
||||
}
|
||||
cx->check(promise);
|
||||
|
||||
args.rval().setObject(*promise);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.5.4.6. releaseLock()
|
||||
*/
|
||||
[[nodiscard]] static bool WritableStreamDefaultWriter_releaseLock(JSContext* cx,
|
||||
unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: If ! IsWritableStreamDefaultWriter(this) is false, return a promise
|
||||
// rejected with a TypeError exception.
|
||||
Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
|
||||
cx,
|
||||
UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(cx, args, "close"));
|
||||
if (!unwrappedWriter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: Let stream be this.[[ownerWritableStream]].
|
||||
// Step 3: If stream is undefined, return.
|
||||
if (!unwrappedWriter->hasStream()) {
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 4: Assert: stream.[[writer]] is not undefined.
|
||||
#ifdef DEBUG
|
||||
{
|
||||
WritableStream* unwrappedStream =
|
||||
UnwrapStreamFromWriter(cx, unwrappedWriter);
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT(unwrappedStream->hasWriter());
|
||||
}
|
||||
#endif
|
||||
|
||||
// Step 5: Perform ! WritableStreamDefaultWriterRelease(this).
|
||||
if (!WritableStreamDefaultWriterRelease(cx, unwrappedWriter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.5.4.7. write(chunk)
|
||||
*/
|
||||
[[nodiscard]] static bool WritableStreamDefaultWriter_write(JSContext* cx,
|
||||
unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: If ! IsWritableStreamDefaultWriter(this) is false, return a promise
|
||||
// rejected with a TypeError exception.
|
||||
Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
|
||||
cx,
|
||||
UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(cx, args, "write"));
|
||||
if (!unwrappedWriter) {
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 2: If this.[[ownerWritableStream]] is undefined, return a promise
|
||||
// rejected with a TypeError exception.
|
||||
if (!unwrappedWriter->hasStream()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WRITABLESTREAMWRITER_NOT_OWNED, "write");
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 3: Return this.[[readyPromise]].
|
||||
PromiseObject* promise =
|
||||
WritableStreamDefaultWriterWrite(cx, unwrappedWriter, args.get(0));
|
||||
if (!promise) {
|
||||
return false;
|
||||
}
|
||||
cx->check(promise);
|
||||
|
||||
args.rval().setObject(*promise);
|
||||
return true;
|
||||
}
|
||||
|
||||
static const JSPropertySpec WritableStreamDefaultWriter_properties[] = {
|
||||
JS_PSG("closed", WritableStreamDefaultWriter_closed, 0),
|
||||
JS_PSG("desiredSize", WritableStreamDefaultWriter_desiredSize, 0),
|
||||
JS_PSG("ready", WritableStreamDefaultWriter_ready, 0), JS_PS_END};
|
||||
|
||||
static const JSFunctionSpec WritableStreamDefaultWriter_methods[] = {
|
||||
JS_FN("abort", WritableStreamDefaultWriter_abort, 1, 0),
|
||||
JS_FN("close", WritableStreamDefaultWriter_close, 0, 0),
|
||||
JS_FN("releaseLock", WritableStreamDefaultWriter_releaseLock, 0, 0),
|
||||
JS_FN("write", WritableStreamDefaultWriter_write, 1, 0), JS_FS_END};
|
||||
|
||||
JS_STREAMS_CLASS_SPEC(WritableStreamDefaultWriter, 1, SlotCount,
|
||||
ClassSpec::DontDefineConstructor, 0, JS_NULL_CLASS_OPS);
|
@ -1,111 +0,0 @@
|
||||
/* -*- 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 WritableStreamDefaultWriter. */
|
||||
|
||||
#ifndef builtin_streams_WritableStreamDefaultWriter_h
|
||||
#define builtin_streams_WritableStreamDefaultWriter_h
|
||||
|
||||
#include "jstypes.h" // JS_PUBLIC_API
|
||||
#include "js/Class.h" // JSClass, js::ClassSpec
|
||||
#include "js/Value.h" // JS::{,Object,Undefined}Value
|
||||
#include "vm/NativeObject.h" // js::NativeObject
|
||||
|
||||
struct JS_PUBLIC_API JSContext;
|
||||
class JS_PUBLIC_API JSObject;
|
||||
|
||||
namespace js {
|
||||
|
||||
class PromiseObject;
|
||||
class WritableStream;
|
||||
|
||||
class WritableStreamDefaultWriter : public NativeObject {
|
||||
public:
|
||||
/**
|
||||
* Memory layout of Stream Writer instances.
|
||||
*
|
||||
* See https://streams.spec.whatwg.org/#default-writer-internal-slots for
|
||||
* details.
|
||||
*/
|
||||
enum Slots {
|
||||
/**
|
||||
* A promise that is resolved when the stream this writes to becomes closed.
|
||||
*
|
||||
* This promise is ordinarily created while this writer is being created; in
|
||||
* this case this promise is not a wrapper and is same-compartment with
|
||||
* this. However, if the writer is closed and then this writer releases its
|
||||
* lock on the stream, this promise will be recreated within whatever realm
|
||||
* is in force when the lock is released:
|
||||
*
|
||||
* var ws = new WritableStream({});
|
||||
* var w = ws.getWriter();
|
||||
* var c = w.closed;
|
||||
* w.close().then(() => {
|
||||
* w.releaseLock(); // changes this slot, and |w.closed|
|
||||
* assertEq(c === w.closed, false);
|
||||
* });
|
||||
*
|
||||
* So this field *may* potentially contain a wrapper around a promise.
|
||||
*/
|
||||
Slot_ClosedPromise,
|
||||
|
||||
/**
|
||||
* The stream that this writer writes to. Because writers are created under
|
||||
* |WritableStream.prototype.getWriter| which may not be same-compartment
|
||||
* with the stream, this is potentially a wrapper.
|
||||
*/
|
||||
Slot_Stream,
|
||||
|
||||
/**
|
||||
* The promise returned by the |writer.ready| getter property, a promise
|
||||
* signaling that the related stream is accepting writes.
|
||||
*
|
||||
* This value repeatedly changes as the related stream changes back and
|
||||
* forth between being writable and temporarily filled (or, ultimately,
|
||||
* errored or aborted). These changes are invoked by a number of user-
|
||||
* visible functions, so this may be a wrapper around a promise in another
|
||||
* realm.
|
||||
*/
|
||||
Slot_ReadyPromise,
|
||||
|
||||
SlotCount,
|
||||
};
|
||||
|
||||
JSObject* closedPromise() const {
|
||||
return &getFixedSlot(Slot_ClosedPromise).toObject();
|
||||
}
|
||||
void setClosedPromise(JSObject* wrappedPromise) {
|
||||
setFixedSlot(Slot_ClosedPromise, JS::ObjectValue(*wrappedPromise));
|
||||
}
|
||||
|
||||
bool hasStream() const { return !getFixedSlot(Slot_Stream).isUndefined(); }
|
||||
void setStream(JSObject* stream) {
|
||||
setFixedSlot(Slot_Stream, JS::ObjectValue(*stream));
|
||||
}
|
||||
void clearStream() { setFixedSlot(Slot_Stream, JS::UndefinedValue()); }
|
||||
|
||||
JSObject* readyPromise() const {
|
||||
return &getFixedSlot(Slot_ReadyPromise).toObject();
|
||||
}
|
||||
void setReadyPromise(JSObject* wrappedPromise) {
|
||||
setFixedSlot(Slot_ReadyPromise, JS::ObjectValue(*wrappedPromise));
|
||||
}
|
||||
|
||||
static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp);
|
||||
static const ClassSpec classSpec_;
|
||||
static const JSClass class_;
|
||||
static const ClassSpec protoClassSpec_;
|
||||
static const JSClass protoClass_;
|
||||
};
|
||||
|
||||
[[nodiscard]] extern WritableStreamDefaultWriter*
|
||||
CreateWritableStreamDefaultWriter(JSContext* cx,
|
||||
JS::Handle<WritableStream*> unwrappedStream,
|
||||
JS::Handle<JSObject*> proto = nullptr);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_WritableStreamDefaultWriter_h
|
@ -1,922 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
/* Writable stream abstract operations. */
|
||||
|
||||
#include "builtin/streams/WritableStreamOperations.h"
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
|
||||
#include <stdint.h> // uint32_t
|
||||
|
||||
#include "builtin/streams/MiscellaneousOperations.h" // js::PromiseRejectedWithPendingError
|
||||
#include "builtin/streams/WritableStream.h" // js::WritableStream
|
||||
#include "builtin/streams/WritableStreamDefaultController.h" // js::WritableStreamDefaultController{,Close}, js::WritableStream::controller
|
||||
#include "builtin/streams/WritableStreamDefaultControllerOperations.h" // js::WritableStreamControllerErrorSteps
|
||||
#include "builtin/streams/WritableStreamWriterOperations.h" // js::WritableStreamDefaultWriterEnsureReadyPromiseRejected
|
||||
#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
|
||||
#include "js/ErrorReport.h" // JS_ReportErrorNumberASCII
|
||||
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
|
||||
#include "js/Promise.h" // JS::{Reject,Resolve}Promise
|
||||
#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
|
||||
#include "js/Value.h" // JS::Value, JS::ObjecValue, JS::UndefinedHandleValue
|
||||
#include "vm/Compartment.h" // JS::Compartment
|
||||
#include "vm/JSContext.h" // JSContext
|
||||
#include "vm/List.h" // js::ListObject
|
||||
#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
|
||||
|
||||
#include "builtin/HandlerFunction-inl.h" // js::NewHandler, js::TargetFromHandler
|
||||
#include "builtin/Promise-inl.h" // js::SetSettledPromiseIsHandled
|
||||
#include "builtin/streams/MiscellaneousOperations-inl.h" // js::ResolveUnwrappedPromiseWithUndefined, js::RejectUnwrappedPromiseWithError
|
||||
#include "builtin/streams/WritableStream-inl.h" // js::UnwrapWriterFromStream
|
||||
#include "builtin/streams/WritableStreamDefaultWriter-inl.h" // js::WritableStreamDefaultWriter::closedPromise
|
||||
#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::UnwrapAndDowncastObject
|
||||
#include "vm/JSContext-inl.h" // JSContext::check
|
||||
#include "vm/JSObject-inl.h" // js::NewObjectWithClassProto
|
||||
#include "vm/List-inl.h" // js::{AppendTo,StoreNew}ListInFixedSlot
|
||||
#include "vm/Realm-inl.h" // js::AutoRealm
|
||||
|
||||
using js::ExtraFromHandler;
|
||||
using js::PromiseObject;
|
||||
using js::TargetFromHandler;
|
||||
using js::UnwrapAndDowncastObject;
|
||||
using js::WritableStream;
|
||||
using js::WritableStreamDefaultController;
|
||||
using js::WritableStreamRejectCloseAndClosedPromiseIfNeeded;
|
||||
|
||||
using JS::CallArgs;
|
||||
using JS::CallArgsFromVp;
|
||||
using JS::Handle;
|
||||
using JS::ObjectValue;
|
||||
using JS::RejectPromise;
|
||||
using JS::ResolvePromise;
|
||||
using JS::Rooted;
|
||||
using JS::UndefinedHandleValue;
|
||||
using JS::Value;
|
||||
|
||||
/*** 4.3. General writable stream abstract operations. **********************/
|
||||
|
||||
/**
|
||||
* Streams spec, 4.3.4. InitializeWritableStream ( stream )
|
||||
*/
|
||||
/* static */ [[nodiscard]] WritableStream* WritableStream::create(
|
||||
JSContext* cx, void* nsISupportsObject_alreadyAddreffed /* = nullptr */,
|
||||
Handle<JSObject*> proto /* = nullptr */) {
|
||||
cx->check(proto);
|
||||
|
||||
// In the spec, InitializeWritableStream is always passed a newly created
|
||||
// WritableStream object. We instead create it here and return it below.
|
||||
Rooted<WritableStream*> stream(
|
||||
cx, NewObjectWithClassProto<WritableStream>(cx, proto));
|
||||
if (!stream) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static_assert(Slot_ISupports == 0,
|
||||
"Must use right slot for JSCLASS_SLOT0_IS_NSISUPPORTS");
|
||||
JS::SetObjectISupports(stream, nsISupportsObject_alreadyAddreffed);
|
||||
|
||||
stream->initWritableState();
|
||||
|
||||
// Step 1: Set stream.[[state]] to "writable".
|
||||
MOZ_ASSERT(stream->writable());
|
||||
|
||||
// Step 2: Set stream.[[storedError]], stream.[[writer]],
|
||||
// stream.[[writableStreamController]],
|
||||
// stream.[[inFlightWriteRequest]], stream.[[closeRequest]],
|
||||
// stream.[[inFlightCloseRequest]] and stream.[[pendingAbortRequest]]
|
||||
// to undefined.
|
||||
MOZ_ASSERT(stream->storedError().isUndefined());
|
||||
MOZ_ASSERT(!stream->hasWriter());
|
||||
MOZ_ASSERT(!stream->hasController());
|
||||
MOZ_ASSERT(!stream->haveInFlightWriteRequest());
|
||||
MOZ_ASSERT(stream->inFlightWriteRequest().isUndefined());
|
||||
MOZ_ASSERT(stream->closeRequest().isUndefined());
|
||||
MOZ_ASSERT(stream->inFlightCloseRequest().isUndefined());
|
||||
MOZ_ASSERT(!stream->hasPendingAbortRequest());
|
||||
|
||||
// Step 3: Set stream.[[writeRequests]] to a new empty List.
|
||||
if (!StoreNewListInFixedSlot(cx, stream,
|
||||
WritableStream::Slot_WriteRequests)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 4: Set stream.[[backpressure]] to false.
|
||||
MOZ_ASSERT(!stream->backpressure());
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
void WritableStream::clearInFlightWriteRequest(JSContext* cx) {
|
||||
MOZ_ASSERT(stateIsInitialized());
|
||||
MOZ_ASSERT(haveInFlightWriteRequest());
|
||||
|
||||
writeRequests()->popFirst(cx);
|
||||
setFlag(HaveInFlightWriteRequest, false);
|
||||
|
||||
MOZ_ASSERT(!haveInFlightWriteRequest());
|
||||
MOZ_ASSERT(inFlightWriteRequest().isUndefined());
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.3.6.
|
||||
* WritableStreamAbort ( stream, reason )
|
||||
*
|
||||
* Note: The object (a promise) returned by this function is in the current
|
||||
* compartment and does not require special wrapping to be put to use.
|
||||
*/
|
||||
JSObject* js::WritableStreamAbort(JSContext* cx,
|
||||
Handle<WritableStream*> unwrappedStream,
|
||||
Handle<Value> reason) {
|
||||
cx->check(reason);
|
||||
|
||||
// Step 1: Let state be stream.[[state]].
|
||||
// Step 2: If state is "closed" or "errored", return a promise resolved with
|
||||
// undefined.
|
||||
if (unwrappedStream->closed() || unwrappedStream->errored()) {
|
||||
return PromiseResolvedWithUndefined(cx);
|
||||
}
|
||||
|
||||
// Step 3: If stream.[[pendingAbortRequest]] is not undefined, return
|
||||
// stream.[[pendingAbortRequest]].[[promise]].
|
||||
if (unwrappedStream->hasPendingAbortRequest()) {
|
||||
Rooted<JSObject*> pendingPromise(
|
||||
cx, unwrappedStream->pendingAbortRequestPromise());
|
||||
if (!cx->compartment()->wrap(cx, &pendingPromise)) {
|
||||
return nullptr;
|
||||
}
|
||||
return pendingPromise;
|
||||
}
|
||||
|
||||
// Step 4: Assert: state is "writable" or "erroring".
|
||||
MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
|
||||
|
||||
// Step 7: Let promise be a new promise (reordered).
|
||||
Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
|
||||
if (!promise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 5: Let wasAlreadyErroring be false.
|
||||
// Step 6: If state is "erroring",
|
||||
// Step 6.a: Set wasAlreadyErroring to true.
|
||||
// Step 6.b: Set reason to undefined.
|
||||
bool wasAlreadyErroring = unwrappedStream->erroring();
|
||||
Handle<Value> pendingReason =
|
||||
wasAlreadyErroring ? UndefinedHandleValue : reason;
|
||||
|
||||
// Step 8: Set stream.[[pendingAbortRequest]] to
|
||||
// Record {[[promise]]: promise, [[reason]]: reason,
|
||||
// [[wasAlreadyErroring]]: wasAlreadyErroring}.
|
||||
{
|
||||
AutoRealm ar(cx, unwrappedStream);
|
||||
|
||||
Rooted<JSObject*> wrappedPromise(cx, promise);
|
||||
Rooted<Value> wrappedPendingReason(cx, pendingReason);
|
||||
|
||||
JS::Compartment* comp = cx->compartment();
|
||||
if (!comp->wrap(cx, &wrappedPromise) ||
|
||||
!comp->wrap(cx, &wrappedPendingReason)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
unwrappedStream->setPendingAbortRequest(
|
||||
wrappedPromise, wrappedPendingReason, wasAlreadyErroring);
|
||||
}
|
||||
|
||||
// Step 9: If wasAlreadyErroring is false, perform
|
||||
// ! WritableStreamStartErroring(stream, reason).
|
||||
if (!wasAlreadyErroring) {
|
||||
if (!WritableStreamStartErroring(cx, unwrappedStream, pendingReason)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 10: Return promise.
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.3.7.
|
||||
* WritableStreamClose ( stream )
|
||||
*
|
||||
* Note: The object (a promise) returned by this function is in the current
|
||||
* compartment and does not require special wrapping to be put to use.
|
||||
*/
|
||||
JSObject* js::WritableStreamClose(JSContext* cx,
|
||||
Handle<WritableStream*> unwrappedStream) {
|
||||
// Step 1: Let state be stream.[[state]].
|
||||
// Step 2: If state is "closed" or "errored", return a promise rejected with a
|
||||
// TypeError exception.
|
||||
if (unwrappedStream->closed() || unwrappedStream->errored()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WRITABLESTREAM_CLOSED_OR_ERRORED);
|
||||
return PromiseRejectedWithPendingError(cx);
|
||||
}
|
||||
|
||||
// Step 3: Assert: state is "writable" or "erroring".
|
||||
MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
|
||||
|
||||
// Step 4: Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false.
|
||||
MOZ_ASSERT(!WritableStreamCloseQueuedOrInFlight(unwrappedStream));
|
||||
|
||||
// Step 5: Let promise be a new promise.
|
||||
Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
|
||||
if (!promise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 6: Set stream.[[closeRequest]] to promise.
|
||||
{
|
||||
AutoRealm ar(cx, unwrappedStream);
|
||||
Rooted<JSObject*> wrappedPromise(cx, promise);
|
||||
if (!cx->compartment()->wrap(cx, &wrappedPromise)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
unwrappedStream->setCloseRequest(promise);
|
||||
}
|
||||
|
||||
// Step 7: Let writer be stream.[[writer]].
|
||||
// Step 8: If writer is not undefined, and stream.[[backpressure]] is true,
|
||||
// and state is "writable", resolve writer.[[readyPromise]] with
|
||||
// undefined.
|
||||
if (unwrappedStream->hasWriter() && unwrappedStream->backpressure() &&
|
||||
unwrappedStream->writable()) {
|
||||
Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
|
||||
cx, UnwrapWriterFromStream(cx, unwrappedStream));
|
||||
if (!unwrappedWriter) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!ResolveUnwrappedPromiseWithUndefined(
|
||||
cx, unwrappedWriter->readyPromise())) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 9: Perform
|
||||
// ! WritableStreamDefaultControllerClose(
|
||||
// stream.[[writableStreamController]]).
|
||||
Rooted<WritableStreamDefaultController*> unwrappedController(
|
||||
cx, unwrappedStream->controller());
|
||||
if (!WritableStreamDefaultControllerClose(cx, unwrappedController)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 10: Return promise.
|
||||
return promise;
|
||||
}
|
||||
|
||||
/*** 4.4. Writable stream abstract operations used by controllers ***********/
|
||||
|
||||
/**
|
||||
* Streams spec, 4.4.1.
|
||||
* WritableStreamAddWriteRequest ( stream )
|
||||
*/
|
||||
[[nodiscard]] PromiseObject* js::WritableStreamAddWriteRequest(
|
||||
JSContext* cx, Handle<WritableStream*> unwrappedStream) {
|
||||
// Step 1: Assert: ! IsWritableStreamLocked(stream) is true.
|
||||
MOZ_ASSERT(unwrappedStream->isLocked());
|
||||
|
||||
// Step 2: Assert: stream.[[state]] is "writable".
|
||||
MOZ_ASSERT(unwrappedStream->writable());
|
||||
|
||||
// Step 3: Let promise be a new promise.
|
||||
Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
|
||||
if (!promise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 4: Append promise as the last element of stream.[[writeRequests]].
|
||||
if (!AppendToListInFixedSlot(cx, unwrappedStream,
|
||||
WritableStream::Slot_WriteRequests, promise)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 5: Return promise.
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.4.2.
|
||||
* WritableStreamDealWithRejection ( stream, error )
|
||||
*/
|
||||
[[nodiscard]] bool js::WritableStreamDealWithRejection(
|
||||
JSContext* cx, Handle<WritableStream*> unwrappedStream,
|
||||
Handle<Value> error) {
|
||||
cx->check(error);
|
||||
|
||||
// Step 1: Let state be stream.[[state]].
|
||||
// Step 2: If state is "writable",
|
||||
if (unwrappedStream->writable()) {
|
||||
// Step 2a: Perform ! WritableStreamStartErroring(stream, error).
|
||||
// Step 2b: Return.
|
||||
return WritableStreamStartErroring(cx, unwrappedStream, error);
|
||||
}
|
||||
|
||||
// Step 3: Assert: state is "erroring".
|
||||
MOZ_ASSERT(unwrappedStream->erroring());
|
||||
|
||||
// Step 4: Perform ! WritableStreamFinishErroring(stream).
|
||||
return WritableStreamFinishErroring(cx, unwrappedStream);
|
||||
}
|
||||
|
||||
static bool WritableStreamHasOperationMarkedInFlight(
|
||||
const WritableStream* unwrappedStream);
|
||||
|
||||
/**
|
||||
* Streams spec, 4.4.3.
|
||||
* WritableStreamStartErroring ( stream, reason )
|
||||
*/
|
||||
[[nodiscard]] bool js::WritableStreamStartErroring(
|
||||
JSContext* cx, Handle<WritableStream*> unwrappedStream,
|
||||
Handle<Value> reason) {
|
||||
cx->check(reason);
|
||||
|
||||
// Step 1: Assert: stream.[[storedError]] is undefined.
|
||||
MOZ_ASSERT(unwrappedStream->storedError().isUndefined());
|
||||
|
||||
// Step 2: Assert: stream.[[state]] is "writable".
|
||||
MOZ_ASSERT(unwrappedStream->writable());
|
||||
|
||||
// Step 3: Let controller be stream.[[writableStreamController]].
|
||||
// Step 4: Assert: controller is not undefined.
|
||||
MOZ_ASSERT(unwrappedStream->hasController());
|
||||
Rooted<WritableStreamDefaultController*> unwrappedController(
|
||||
cx, unwrappedStream->controller());
|
||||
|
||||
// Step 5: Set stream.[[state]] to "erroring".
|
||||
unwrappedStream->setErroring();
|
||||
|
||||
// Step 6: Set stream.[[storedError]] to reason.
|
||||
{
|
||||
AutoRealm ar(cx, unwrappedStream);
|
||||
Rooted<Value> wrappedReason(cx, reason);
|
||||
if (!cx->compartment()->wrap(cx, &wrappedReason)) {
|
||||
return false;
|
||||
}
|
||||
unwrappedStream->setStoredError(wrappedReason);
|
||||
}
|
||||
|
||||
// Step 7: Let writer be stream.[[writer]].
|
||||
// Step 8: If writer is not undefined, perform
|
||||
// ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(
|
||||
// writer, reason).
|
||||
if (unwrappedStream->hasWriter()) {
|
||||
Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
|
||||
cx, UnwrapWriterFromStream(cx, unwrappedStream));
|
||||
if (!unwrappedWriter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!WritableStreamDefaultWriterEnsureReadyPromiseRejected(
|
||||
cx, unwrappedWriter, reason)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 9: If ! WritableStreamHasOperationMarkedInFlight(stream) is false and
|
||||
// controller.[[started]] is true, perform
|
||||
// ! WritableStreamFinishErroring(stream).
|
||||
if (!WritableStreamHasOperationMarkedInFlight(unwrappedStream) &&
|
||||
unwrappedController->started()) {
|
||||
if (!WritableStreamFinishErroring(cx, unwrappedStream)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.4.4 WritableStreamFinishErroring ( stream )
|
||||
* Step 13: Upon fulfillment of promise, [...]
|
||||
*/
|
||||
static bool AbortRequestPromiseFulfilledHandler(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 13.a: Resolve abortRequest.[[promise]] with undefined.
|
||||
Rooted<JSObject*> abortRequestPromise(cx, TargetFromHandler<JSObject>(args));
|
||||
if (!ResolvePromise(cx, abortRequestPromise, UndefinedHandleValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 13.b: Perform
|
||||
// ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
|
||||
Rooted<WritableStream*> unwrappedStream(
|
||||
cx, UnwrapAndDowncastObject<WritableStream>(
|
||||
cx, ExtraFromHandler<JSObject>(args)));
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!WritableStreamRejectCloseAndClosedPromiseIfNeeded(cx, unwrappedStream)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.4.4 WritableStreamFinishErroring ( stream )
|
||||
* Step 14: Upon rejection of promise with reason reason, [...]
|
||||
*/
|
||||
static bool AbortRequestPromiseRejectedHandler(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 14.a: Reject abortRequest.[[promise]] with reason.
|
||||
Rooted<JSObject*> abortRequestPromise(cx, TargetFromHandler<JSObject>(args));
|
||||
if (!RejectPromise(cx, abortRequestPromise, args.get(0))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 14.b: Perform
|
||||
// ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
|
||||
Rooted<WritableStream*> unwrappedStream(
|
||||
cx, UnwrapAndDowncastObject<WritableStream>(
|
||||
cx, ExtraFromHandler<JSObject>(args)));
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!WritableStreamRejectCloseAndClosedPromiseIfNeeded(cx, unwrappedStream)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.4.4.
|
||||
* WritableStreamFinishErroring ( stream )
|
||||
*/
|
||||
[[nodiscard]] bool js::WritableStreamFinishErroring(
|
||||
JSContext* cx, Handle<WritableStream*> unwrappedStream) {
|
||||
// Step 1: Assert: stream.[[state]] is "erroring".
|
||||
MOZ_ASSERT(unwrappedStream->erroring());
|
||||
|
||||
// Step 2: Assert: ! WritableStreamHasOperationMarkedInFlight(stream) is
|
||||
// false.
|
||||
MOZ_ASSERT(!WritableStreamHasOperationMarkedInFlight(unwrappedStream));
|
||||
|
||||
// Step 3: Set stream.[[state]] to "errored".
|
||||
unwrappedStream->setErrored();
|
||||
|
||||
// Step 4: Perform ! stream.[[writableStreamController]].[[ErrorSteps]]().
|
||||
{
|
||||
Rooted<WritableStreamDefaultController*> unwrappedController(
|
||||
cx, unwrappedStream->controller());
|
||||
if (!WritableStreamControllerErrorSteps(cx, unwrappedController)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Let storedError be stream.[[storedError]].
|
||||
Rooted<Value> storedError(cx, unwrappedStream->storedError());
|
||||
if (!cx->compartment()->wrap(cx, &storedError)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 6: Repeat for each writeRequest that is an element of
|
||||
// stream.[[writeRequests]],
|
||||
{
|
||||
Rooted<ListObject*> unwrappedWriteRequests(
|
||||
cx, unwrappedStream->writeRequests());
|
||||
Rooted<JSObject*> writeRequest(cx);
|
||||
uint32_t len = unwrappedWriteRequests->length();
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
// Step 6.a: Reject writeRequest with storedError.
|
||||
writeRequest = &unwrappedWriteRequests->get(i).toObject();
|
||||
if (!RejectUnwrappedPromiseWithError(cx, &writeRequest, storedError)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 7: Set stream.[[writeRequests]] to an empty List.
|
||||
// We optimize this to discard the list entirely. (A brief scan of the
|
||||
// streams spec should verify that [[writeRequests]] is never accessed on a
|
||||
// stream when |stream.[[state]] === "errored"|, set in step 3 above.)
|
||||
unwrappedStream->clearWriteRequests();
|
||||
|
||||
// Step 8: If stream.[[pendingAbortRequest]] is undefined,
|
||||
if (!unwrappedStream->hasPendingAbortRequest()) {
|
||||
// Step 8.a: Perform
|
||||
// ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
|
||||
// Step 8.b: Return.
|
||||
return WritableStreamRejectCloseAndClosedPromiseIfNeeded(cx,
|
||||
unwrappedStream);
|
||||
}
|
||||
|
||||
// Step 9: Let abortRequest be stream.[[pendingAbortRequest]].
|
||||
// Step 10: Set stream.[[pendingAbortRequest]] to undefined.
|
||||
Rooted<Value> abortRequestReason(
|
||||
cx, unwrappedStream->pendingAbortRequestReason());
|
||||
if (!cx->compartment()->wrap(cx, &abortRequestReason)) {
|
||||
return false;
|
||||
}
|
||||
Rooted<JSObject*> abortRequestPromise(
|
||||
cx, unwrappedStream->pendingAbortRequestPromise());
|
||||
bool wasAlreadyErroring =
|
||||
unwrappedStream->pendingAbortRequestWasAlreadyErroring();
|
||||
unwrappedStream->clearPendingAbortRequest();
|
||||
|
||||
// Step 11: If abortRequest.[[wasAlreadyErroring]] is true,
|
||||
if (wasAlreadyErroring) {
|
||||
// Step 11.a: Reject abortRequest.[[promise]] with storedError.
|
||||
if (!RejectUnwrappedPromiseWithError(cx, &abortRequestPromise,
|
||||
storedError)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 11.b: Perform
|
||||
// ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
|
||||
// Step 11.c: Return.
|
||||
return WritableStreamRejectCloseAndClosedPromiseIfNeeded(cx,
|
||||
unwrappedStream);
|
||||
}
|
||||
|
||||
// Step 12: Let promise be
|
||||
// ! stream.[[writableStreamController]].[[AbortSteps]](
|
||||
// abortRequest.[[reason]]).
|
||||
Rooted<WritableStreamDefaultController*> unwrappedController(
|
||||
cx, unwrappedStream->controller());
|
||||
Rooted<JSObject*> promise(
|
||||
cx, WritableStreamControllerAbortSteps(cx, unwrappedController,
|
||||
abortRequestReason));
|
||||
if (!promise) {
|
||||
return false;
|
||||
}
|
||||
cx->check(promise);
|
||||
|
||||
if (!cx->compartment()->wrap(cx, &abortRequestPromise)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<JSObject*> stream(cx, unwrappedStream);
|
||||
if (!cx->compartment()->wrap(cx, &stream)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 13: Upon fulfillment of promise, [...]
|
||||
// Step 14: Upon rejection of promise with reason reason, [...]
|
||||
Rooted<JSObject*> onFulfilled(
|
||||
cx, NewHandlerWithExtra(cx, AbortRequestPromiseFulfilledHandler,
|
||||
abortRequestPromise, stream));
|
||||
if (!onFulfilled) {
|
||||
return false;
|
||||
}
|
||||
Rooted<JSObject*> onRejected(
|
||||
cx, NewHandlerWithExtra(cx, AbortRequestPromiseRejectedHandler,
|
||||
abortRequestPromise, stream));
|
||||
if (!onRejected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return JS::AddPromiseReactions(cx, promise, onFulfilled, onRejected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.4.5.
|
||||
* WritableStreamFinishInFlightWrite ( stream )
|
||||
*/
|
||||
[[nodiscard]] bool js::WritableStreamFinishInFlightWrite(
|
||||
JSContext* cx, Handle<WritableStream*> unwrappedStream) {
|
||||
// Step 1: Assert: stream.[[inFlightWriteRequest]] is not undefined.
|
||||
MOZ_ASSERT(unwrappedStream->haveInFlightWriteRequest());
|
||||
|
||||
// Step 2: Resolve stream.[[inFlightWriteRequest]] with undefined.
|
||||
if (!ResolveUnwrappedPromiseWithUndefined(
|
||||
cx, &unwrappedStream->inFlightWriteRequest().toObject())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Set stream.[[inFlightWriteRequest]] to undefined.
|
||||
unwrappedStream->clearInFlightWriteRequest(cx);
|
||||
MOZ_ASSERT(!unwrappedStream->haveInFlightWriteRequest());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.4.6.
|
||||
* WritableStreamFinishInFlightWriteWithError ( stream, error )
|
||||
*/
|
||||
[[nodiscard]] bool js::WritableStreamFinishInFlightWriteWithError(
|
||||
JSContext* cx, Handle<WritableStream*> unwrappedStream,
|
||||
Handle<Value> error) {
|
||||
cx->check(error);
|
||||
|
||||
// Step 1: Assert: stream.[[inFlightWriteRequest]] is not undefined.
|
||||
MOZ_ASSERT(unwrappedStream->haveInFlightWriteRequest());
|
||||
|
||||
// Step 2: Reject stream.[[inFlightWriteRequest]] with error.
|
||||
if (!RejectUnwrappedPromiseWithError(
|
||||
cx, &unwrappedStream->inFlightWriteRequest().toObject(), error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Set stream.[[inFlightWriteRequest]] to undefined.
|
||||
unwrappedStream->clearInFlightWriteRequest(cx);
|
||||
|
||||
// Step 4: Assert: stream.[[state]] is "writable" or "erroring".
|
||||
MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
|
||||
|
||||
// Step 5: Perform ! WritableStreamDealWithRejection(stream, error).
|
||||
return WritableStreamDealWithRejection(cx, unwrappedStream, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.4.7.
|
||||
* WritableStreamFinishInFlightClose ( stream )
|
||||
*/
|
||||
[[nodiscard]] bool js::WritableStreamFinishInFlightClose(
|
||||
JSContext* cx, Handle<WritableStream*> unwrappedStream) {
|
||||
// Step 1: Assert: stream.[[inFlightCloseRequest]] is not undefined.
|
||||
MOZ_ASSERT(unwrappedStream->haveInFlightCloseRequest());
|
||||
|
||||
// Step 2: Resolve stream.[[inFlightCloseRequest]] with undefined.
|
||||
if (!ResolveUnwrappedPromiseWithUndefined(
|
||||
cx, &unwrappedStream->inFlightCloseRequest().toObject())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Set stream.[[inFlightCloseRequest]] to undefined.
|
||||
unwrappedStream->clearInFlightCloseRequest();
|
||||
MOZ_ASSERT(unwrappedStream->inFlightCloseRequest().isUndefined());
|
||||
|
||||
// Step 4: Let state be stream.[[state]].
|
||||
// Step 5: Assert: stream.[[state]] is "writable" or "erroring".
|
||||
MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
|
||||
|
||||
// Step 6: If state is "erroring",
|
||||
if (unwrappedStream->erroring()) {
|
||||
// Step 6.a: Set stream.[[storedError]] to undefined.
|
||||
unwrappedStream->clearStoredError();
|
||||
|
||||
// Step 6.b: If stream.[[pendingAbortRequest]] is not undefined,
|
||||
if (unwrappedStream->hasPendingAbortRequest()) {
|
||||
// Step 6.b.i: Resolve stream.[[pendingAbortRequest]].[[promise]] with
|
||||
// undefined.
|
||||
if (!ResolveUnwrappedPromiseWithUndefined(
|
||||
cx, unwrappedStream->pendingAbortRequestPromise())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 6.b.ii: Set stream.[[pendingAbortRequest]] to undefined.
|
||||
unwrappedStream->clearPendingAbortRequest();
|
||||
}
|
||||
}
|
||||
|
||||
// Step 7: Set stream.[[state]] to "closed".
|
||||
unwrappedStream->setClosed();
|
||||
|
||||
// Step 8: Let writer be stream.[[writer]].
|
||||
// Step 9: If writer is not undefined, resolve writer.[[closedPromise]] with
|
||||
// undefined.
|
||||
if (unwrappedStream->hasWriter()) {
|
||||
WritableStreamDefaultWriter* unwrappedWriter =
|
||||
UnwrapWriterFromStream(cx, unwrappedStream);
|
||||
if (!unwrappedWriter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ResolveUnwrappedPromiseWithUndefined(
|
||||
cx, unwrappedWriter->closedPromise())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 10: Assert: stream.[[pendingAbortRequest]] is undefined.
|
||||
MOZ_ASSERT(!unwrappedStream->hasPendingAbortRequest());
|
||||
|
||||
// Step 11: Assert: stream.[[storedError]] is undefined.
|
||||
MOZ_ASSERT(unwrappedStream->storedError().isUndefined());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.4.8.
|
||||
* WritableStreamFinishInFlightCloseWithError ( stream, error )
|
||||
*/
|
||||
[[nodiscard]] bool js::WritableStreamFinishInFlightCloseWithError(
|
||||
JSContext* cx, Handle<WritableStream*> unwrappedStream,
|
||||
Handle<Value> error) {
|
||||
cx->check(error);
|
||||
|
||||
// Step 1: Assert: stream.[[inFlightCloseRequest]] is not undefined.
|
||||
MOZ_ASSERT(unwrappedStream->haveInFlightCloseRequest());
|
||||
MOZ_ASSERT(!unwrappedStream->inFlightCloseRequest().isUndefined());
|
||||
|
||||
// Step 2: Reject stream.[[inFlightCloseRequest]] with error.
|
||||
if (!RejectUnwrappedPromiseWithError(
|
||||
cx, &unwrappedStream->inFlightCloseRequest().toObject(), error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Set stream.[[inFlightCloseRequest]] to undefined.
|
||||
unwrappedStream->clearInFlightCloseRequest();
|
||||
|
||||
// Step 4: Assert: stream.[[state]] is "writable" or "erroring".
|
||||
MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
|
||||
|
||||
// Step 5: If stream.[[pendingAbortRequest]] is not undefined,
|
||||
if (unwrappedStream->hasPendingAbortRequest()) {
|
||||
// Step 5.a: Reject stream.[[pendingAbortRequest]].[[promise]] with error.
|
||||
if (!RejectUnwrappedPromiseWithError(
|
||||
cx, unwrappedStream->pendingAbortRequestPromise(), error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 5.b: Set stream.[[pendingAbortRequest]] to undefined.
|
||||
unwrappedStream->clearPendingAbortRequest();
|
||||
}
|
||||
|
||||
// Step 6: Perform ! WritableStreamDealWithRejection(stream, error).
|
||||
return WritableStreamDealWithRejection(cx, unwrappedStream, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.4.9.
|
||||
* WritableStreamCloseQueuedOrInFlight ( stream )
|
||||
*/
|
||||
bool js::WritableStreamCloseQueuedOrInFlight(
|
||||
const WritableStream* unwrappedStream) {
|
||||
// Step 1: If stream.[[closeRequest]] is undefined and
|
||||
// stream.[[inFlightCloseRequest]] is undefined, return false.
|
||||
// Step 2: Return true.
|
||||
return unwrappedStream->haveCloseRequestOrInFlightCloseRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.4.10.
|
||||
* WritableStreamHasOperationMarkedInFlight ( stream )
|
||||
*/
|
||||
bool WritableStreamHasOperationMarkedInFlight(
|
||||
const WritableStream* unwrappedStream) {
|
||||
// Step 1: If stream.[[inFlightWriteRequest]] is undefined and
|
||||
// controller.[[inFlightCloseRequest]] is undefined, return false.
|
||||
// Step 2: Return true.
|
||||
return unwrappedStream->haveInFlightWriteRequest() ||
|
||||
unwrappedStream->haveInFlightCloseRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.4.11.
|
||||
* WritableStreamMarkCloseRequestInFlight ( stream )
|
||||
*/
|
||||
void js::WritableStreamMarkCloseRequestInFlight(
|
||||
WritableStream* unwrappedStream) {
|
||||
// Step 1: Assert: stream.[[inFlightCloseRequest]] is undefined.
|
||||
MOZ_ASSERT(!unwrappedStream->haveInFlightCloseRequest());
|
||||
|
||||
// Step 2: Assert: stream.[[closeRequest]] is not undefined.
|
||||
MOZ_ASSERT(!unwrappedStream->closeRequest().isUndefined());
|
||||
|
||||
// Step 3: Set stream.[[inFlightCloseRequest]] to stream.[[closeRequest]].
|
||||
// Step 4: Set stream.[[closeRequest]] to undefined.
|
||||
unwrappedStream->convertCloseRequestToInFlightCloseRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.4.12.
|
||||
* WritableStreamMarkFirstWriteRequestInFlight ( stream )
|
||||
*/
|
||||
void js::WritableStreamMarkFirstWriteRequestInFlight(
|
||||
WritableStream* unwrappedStream) {
|
||||
// Step 1: Assert: stream.[[inFlightWriteRequest]] is undefined.
|
||||
MOZ_ASSERT(!unwrappedStream->haveInFlightWriteRequest());
|
||||
|
||||
// Step 2: Assert: stream.[[writeRequests]] is not empty.
|
||||
MOZ_ASSERT(unwrappedStream->writeRequests()->length() > 0);
|
||||
|
||||
// Step 3: Let writeRequest be the first element of stream.[[writeRequests]].
|
||||
// Step 4: Remove writeRequest from stream.[[writeRequests]], shifting all
|
||||
// other elements downward (so that the second becomes the first, and
|
||||
// so on).
|
||||
// Step 5: Set stream.[[inFlightWriteRequest]] to writeRequest.
|
||||
// In our implementation, we model [[inFlightWriteRequest]] as merely the
|
||||
// first element of [[writeRequests]], plus a flag indicating there's an
|
||||
// in-flight request. Set the flag and be done with it.
|
||||
unwrappedStream->setHaveInFlightWriteRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.4.13.
|
||||
* WritableStreamRejectCloseAndClosedPromiseIfNeeded ( stream )
|
||||
*/
|
||||
[[nodiscard]] bool js::WritableStreamRejectCloseAndClosedPromiseIfNeeded(
|
||||
JSContext* cx, Handle<WritableStream*> unwrappedStream) {
|
||||
// Step 1: Assert: stream.[[state]] is "errored".
|
||||
MOZ_ASSERT(unwrappedStream->errored());
|
||||
|
||||
Rooted<Value> storedError(cx, unwrappedStream->storedError());
|
||||
if (!cx->compartment()->wrap(cx, &storedError)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: If stream.[[closeRequest]] is not undefined,
|
||||
if (!unwrappedStream->closeRequest().isUndefined()) {
|
||||
// Step 2.a: Assert: stream.[[inFlightCloseRequest]] is undefined.
|
||||
MOZ_ASSERT(unwrappedStream->inFlightCloseRequest().isUndefined());
|
||||
|
||||
// Step 2.b: Reject stream.[[closeRequest]] with stream.[[storedError]].
|
||||
if (!RejectUnwrappedPromiseWithError(
|
||||
cx, &unwrappedStream->closeRequest().toObject(), storedError)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2.c: Set stream.[[closeRequest]] to undefined.
|
||||
unwrappedStream->clearCloseRequest();
|
||||
}
|
||||
|
||||
// Step 3: Let writer be stream.[[writer]].
|
||||
// Step 4: If writer is not undefined,
|
||||
if (unwrappedStream->hasWriter()) {
|
||||
Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
|
||||
cx, UnwrapWriterFromStream(cx, unwrappedStream));
|
||||
if (!unwrappedWriter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 4.a: Reject writer.[[closedPromise]] with stream.[[storedError]].
|
||||
if (!RejectUnwrappedPromiseWithError(cx, unwrappedWriter->closedPromise(),
|
||||
storedError)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 4.b: Set writer.[[closedPromise]].[[PromiseIsHandled]] to true.
|
||||
Rooted<PromiseObject*> unwrappedClosedPromise(
|
||||
cx, UnwrapAndDowncastObject<PromiseObject>(
|
||||
cx, unwrappedWriter->closedPromise()));
|
||||
if (!unwrappedClosedPromise) {
|
||||
return false;
|
||||
}
|
||||
|
||||
js::SetSettledPromiseIsHandled(cx, unwrappedClosedPromise);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.4.14.
|
||||
* WritableStreamUpdateBackpressure ( stream, backpressure )
|
||||
*/
|
||||
[[nodiscard]] bool js::WritableStreamUpdateBackpressure(
|
||||
JSContext* cx, Handle<WritableStream*> unwrappedStream, bool backpressure) {
|
||||
// Step 1: Assert: stream.[[state]] is "writable".
|
||||
MOZ_ASSERT(unwrappedStream->writable());
|
||||
|
||||
// Step 2: Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false.
|
||||
MOZ_ASSERT(!WritableStreamCloseQueuedOrInFlight(unwrappedStream));
|
||||
|
||||
// Step 3: Let writer be stream.[[writer]].
|
||||
// Step 4: If writer is not undefined and backpressure is not
|
||||
// stream.[[backpressure]],
|
||||
if (unwrappedStream->hasWriter() &&
|
||||
backpressure != unwrappedStream->backpressure()) {
|
||||
Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
|
||||
cx, UnwrapWriterFromStream(cx, unwrappedStream));
|
||||
if (!unwrappedWriter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 4.a: If backpressure is true, set writer.[[readyPromise]] to a new
|
||||
// promise.
|
||||
if (backpressure) {
|
||||
Rooted<JSObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
|
||||
if (!promise) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AutoRealm ar(cx, unwrappedWriter);
|
||||
if (!cx->compartment()->wrap(cx, &promise)) {
|
||||
return false;
|
||||
}
|
||||
unwrappedWriter->setReadyPromise(promise);
|
||||
} else {
|
||||
// Step 4.b: Otherwise,
|
||||
// Step 4.b.i: Assert: backpressure is false. (guaranteed by type)
|
||||
// Step 4.b.ii: Resolve writer.[[readyPromise]] with undefined.
|
||||
if (!ResolveUnwrappedPromiseWithUndefined(
|
||||
cx, unwrappedWriter->readyPromise())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Set stream.[[backpressure]] to backpressure.
|
||||
unwrappedStream->setBackpressure(backpressure);
|
||||
|
||||
return true;
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
/* Writable stream abstract operations. */
|
||||
|
||||
#ifndef builtin_streams_WritableStreamOperations_h
|
||||
#define builtin_streams_WritableStreamOperations_h
|
||||
|
||||
#include "jstypes.h" // JS_PUBLIC_API
|
||||
#include "js/RootingAPI.h" // JS::Handle
|
||||
#include "js/Value.h" // JS::Value
|
||||
|
||||
struct JS_PUBLIC_API JSContext;
|
||||
|
||||
namespace js {
|
||||
|
||||
class PromiseObject;
|
||||
class WritableStream;
|
||||
|
||||
extern JSObject* WritableStreamAbort(
|
||||
JSContext* cx, JS::Handle<WritableStream*> unwrappedStream,
|
||||
JS::Handle<JS::Value> reason);
|
||||
|
||||
extern JSObject* WritableStreamClose(
|
||||
JSContext* cx, JS::Handle<WritableStream*> unwrappedStream);
|
||||
|
||||
[[nodiscard]] extern PromiseObject* WritableStreamAddWriteRequest(
|
||||
JSContext* cx, JS::Handle<WritableStream*> unwrappedStream);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamDealWithRejection(
|
||||
JSContext* cx, JS::Handle<WritableStream*> unwrappedStream,
|
||||
JS::Handle<JS::Value> error);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamStartErroring(
|
||||
JSContext* cx, JS::Handle<WritableStream*> unwrappedStream,
|
||||
JS::Handle<JS::Value> reason);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamFinishErroring(
|
||||
JSContext* cx, JS::Handle<WritableStream*> unwrappedStream);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamFinishInFlightWrite(
|
||||
JSContext* cx, JS::Handle<WritableStream*> unwrappedStream);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamFinishInFlightWriteWithError(
|
||||
JSContext* cx, JS::Handle<WritableStream*> unwrappedStream,
|
||||
JS::Handle<JS::Value> error);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamFinishInFlightClose(
|
||||
JSContext* cx, JS::Handle<WritableStream*> unwrappedStream);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamFinishInFlightCloseWithError(
|
||||
JSContext* cx, JS::Handle<WritableStream*> unwrappedStream,
|
||||
JS::Handle<JS::Value> error);
|
||||
|
||||
extern bool WritableStreamCloseQueuedOrInFlight(
|
||||
const WritableStream* unwrappedStream);
|
||||
|
||||
extern void WritableStreamMarkCloseRequestInFlight(
|
||||
WritableStream* unwrappedStream);
|
||||
|
||||
extern void WritableStreamMarkFirstWriteRequestInFlight(
|
||||
WritableStream* unwrappedStream);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamRejectCloseAndClosedPromiseIfNeeded(
|
||||
JSContext* cx, JS::Handle<WritableStream*> unwrappedStream);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamUpdateBackpressure(
|
||||
JSContext* cx, JS::Handle<WritableStream*> unwrappedStream,
|
||||
bool backpressure);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_WritableStreamOperations_h
|
@ -1,444 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
/* Writable stream writer abstract operations. */
|
||||
|
||||
#include "builtin/streams/WritableStreamWriterOperations.h"
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
|
||||
#include "builtin/streams/MiscellaneousOperations.h" // js::PromiseRejectedWithPendingError
|
||||
#include "builtin/streams/WritableStream.h" // js::WritableStream
|
||||
#include "builtin/streams/WritableStreamDefaultController.h" // js::WritableStream::controller
|
||||
#include "builtin/streams/WritableStreamDefaultControllerOperations.h" // js::WritableStreamDefaultController{Close,GetDesiredSize}
|
||||
#include "builtin/streams/WritableStreamDefaultWriter.h" // js::WritableStreamDefaultWriter
|
||||
#include "builtin/streams/WritableStreamOperations.h" // js::WritableStream{Abort,CloseQueuedOrInFlight}
|
||||
#include "js/ErrorReport.h" // JS_ReportErrorNumberASCII
|
||||
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
|
||||
#include "js/Promise.h" // JS::PromiseState
|
||||
#include "js/Value.h" // JS::Value, JS::{Int32,Null}Value
|
||||
#include "vm/Compartment.h" // JS::Compartment
|
||||
#include "vm/Interpreter.h" // js::GetAndClearException
|
||||
#include "vm/JSContext.h" // JSContext
|
||||
#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
|
||||
|
||||
#include "builtin/Promise-inl.h" // js::SetSettledPromiseIsHandled
|
||||
#include "builtin/streams/MiscellaneousOperations-inl.h" // js::ResolveUnwrappedPromiseWithUndefined
|
||||
#include "builtin/streams/WritableStream-inl.h" // js::WritableStream::setCloseRequest
|
||||
#include "builtin/streams/WritableStreamDefaultWriter-inl.h" // js::UnwrapStreamFromWriter
|
||||
#include "vm/Compartment-inl.h" // js::UnwrapAnd{DowncastObject,TypeCheckThis}
|
||||
#include "vm/JSContext-inl.h" // JSContext::check
|
||||
#include "vm/Realm-inl.h" // js::AutoRealm
|
||||
|
||||
using JS::Handle;
|
||||
using JS::Int32Value;
|
||||
using JS::MutableHandle;
|
||||
using JS::NullValue;
|
||||
using JS::NumberValue;
|
||||
using JS::Rooted;
|
||||
using JS::Value;
|
||||
|
||||
using js::AutoRealm;
|
||||
using js::PromiseObject;
|
||||
using js::UnwrapAndDowncastObject;
|
||||
using js::WritableStreamDefaultWriter;
|
||||
|
||||
/*** 4.6. Writable stream writer abstract operations ************************/
|
||||
|
||||
/**
|
||||
* Streams spec, 4.6.2.
|
||||
* WritableStreamDefaultWriterAbort ( writer, reason )
|
||||
*/
|
||||
JSObject* js::WritableStreamDefaultWriterAbort(
|
||||
JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter,
|
||||
Handle<Value> reason) {
|
||||
cx->check(reason);
|
||||
|
||||
// Step 1: Let stream be writer.[[ownerWritableStream]].
|
||||
// Step 2: Assert: stream is not undefined.
|
||||
MOZ_ASSERT(unwrappedWriter->hasStream());
|
||||
Rooted<WritableStream*> unwrappedStream(
|
||||
cx, UnwrapStreamFromWriter(cx, unwrappedWriter));
|
||||
if (!unwrappedStream) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 3: Return ! WritableStreamAbort(stream, reason).
|
||||
return WritableStreamAbort(cx, unwrappedStream, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.6.3.
|
||||
* WritableStreamDefaultWriterClose ( writer )
|
||||
*/
|
||||
PromiseObject* js::WritableStreamDefaultWriterClose(
|
||||
JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter) {
|
||||
// Step 1: Let stream be writer.[[ownerWritableStream]].
|
||||
// Step 2: Assert: stream is not undefined.
|
||||
MOZ_ASSERT(unwrappedWriter->hasStream());
|
||||
Rooted<WritableStream*> unwrappedStream(
|
||||
cx, UnwrapStreamFromWriter(cx, unwrappedWriter));
|
||||
if (!unwrappedStream) {
|
||||
return PromiseRejectedWithPendingError(cx);
|
||||
}
|
||||
|
||||
// Step 3: Let state be stream.[[state]].
|
||||
// Step 4: If state is "closed" or "errored", return a promise rejected with a
|
||||
// TypeError exception.
|
||||
if (unwrappedStream->closed() || unwrappedStream->errored()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WRITABLESTREAM_CLOSED_OR_ERRORED);
|
||||
return PromiseRejectedWithPendingError(cx);
|
||||
}
|
||||
|
||||
// Step 5: Assert: state is "writable" or "erroring".
|
||||
MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
|
||||
|
||||
// Step 6: Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false.
|
||||
MOZ_ASSERT(!WritableStreamCloseQueuedOrInFlight(unwrappedStream));
|
||||
|
||||
// Step 7: Let promise be a new promise.
|
||||
Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
|
||||
if (!promise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 8: Set stream.[[closeRequest]] to promise.
|
||||
{
|
||||
AutoRealm ar(cx, unwrappedStream);
|
||||
Rooted<JSObject*> closeRequest(cx, promise);
|
||||
if (!cx->compartment()->wrap(cx, &closeRequest)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
unwrappedStream->setCloseRequest(closeRequest);
|
||||
}
|
||||
|
||||
// Step 9: If stream.[[backpressure]] is true and state is "writable", resolve
|
||||
// writer.[[readyPromise]] with undefined.
|
||||
if (unwrappedStream->backpressure() && unwrappedStream->writable()) {
|
||||
if (!ResolveUnwrappedPromiseWithUndefined(
|
||||
cx, unwrappedWriter->readyPromise())) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 10: Perform
|
||||
// ! WritableStreamDefaultControllerClose(
|
||||
// stream.[[writableStreamController]]).
|
||||
Rooted<WritableStreamDefaultController*> unwrappedController(
|
||||
cx, unwrappedStream->controller());
|
||||
if (!WritableStreamDefaultControllerClose(cx, unwrappedController)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 11: Return promise.
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec.
|
||||
* WritableStreamDefaultWriterCloseWithErrorPropagation ( writer )
|
||||
*/
|
||||
PromiseObject* js::WritableStreamDefaultWriterCloseWithErrorPropagation(
|
||||
JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter) {
|
||||
// Step 1: Let stream be writer.[[ownerWritableStream]].
|
||||
// Step 2: Assert: stream is not undefined.
|
||||
WritableStream* unwrappedStream = UnwrapStreamFromWriter(cx, unwrappedWriter);
|
||||
if (!unwrappedStream) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 3: Let state be stream.[[state]].
|
||||
// Step 4: If ! WritableStreamCloseQueuedOrInFlight(stream) is true or state
|
||||
// is "closed", return a promise resolved with undefined.
|
||||
if (WritableStreamCloseQueuedOrInFlight(unwrappedStream) ||
|
||||
unwrappedStream->closed()) {
|
||||
return PromiseResolvedWithUndefined(cx);
|
||||
}
|
||||
|
||||
// Step 5: If state is "errored", return a promise rejected with
|
||||
// stream.[[storedError]].
|
||||
if (unwrappedStream->errored()) {
|
||||
Rooted<Value> storedError(cx, unwrappedStream->storedError());
|
||||
if (!cx->compartment()->wrap(cx, &storedError)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return PromiseObject::unforgeableReject(cx, storedError);
|
||||
}
|
||||
|
||||
// Step 6: Assert: state is "writable" or "erroring".
|
||||
MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
|
||||
|
||||
// Step 7: Return ! WritableStreamDefaultWriterClose(writer).
|
||||
return WritableStreamDefaultWriterClose(cx, unwrappedWriter);
|
||||
}
|
||||
|
||||
using GetField = JSObject* (WritableStreamDefaultWriter::*)() const;
|
||||
using SetField = void (WritableStreamDefaultWriter::*)(JSObject*);
|
||||
|
||||
static bool EnsurePromiseRejected(
|
||||
JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter,
|
||||
GetField getField, SetField setField, Handle<Value> error) {
|
||||
cx->check(error);
|
||||
|
||||
Rooted<PromiseObject*> unwrappedPromise(
|
||||
cx, UnwrapAndDowncastObject<PromiseObject>(
|
||||
cx, (unwrappedWriter->*getField)()));
|
||||
if (!unwrappedPromise) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4.6.{5,6} step 1: If writer.[[<field>]].[[PromiseState]] is "pending",
|
||||
// reject writer.[[<field>]] with error.
|
||||
if (unwrappedPromise->state() == JS::PromiseState::Pending) {
|
||||
if (!RejectUnwrappedPromiseWithError(cx, unwrappedPromise, error)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// 4.6.{5,6} step 2: Otherwise, set writer.[[<field>]] to a promise rejected
|
||||
// with error.
|
||||
Rooted<JSObject*> rejectedWithError(
|
||||
cx, PromiseObject::unforgeableReject(cx, error));
|
||||
if (!rejectedWithError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
AutoRealm ar(cx, unwrappedWriter);
|
||||
if (!cx->compartment()->wrap(cx, &rejectedWithError)) {
|
||||
return false;
|
||||
}
|
||||
(unwrappedWriter->*setField)(rejectedWithError);
|
||||
}
|
||||
|
||||
// Directly-unobservable rejected promises aren't collapsed like resolved
|
||||
// promises, and this promise is created in the current realm, so it's
|
||||
// always an actual Promise.
|
||||
unwrappedPromise = &rejectedWithError->as<PromiseObject>();
|
||||
}
|
||||
|
||||
// 4.6.{5,6} step 3: Set writer.[[<field>]].[[PromiseIsHandled]] to true.
|
||||
js::SetSettledPromiseIsHandled(cx, unwrappedPromise);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.6.5.
|
||||
* WritableStreamDefaultWriterEnsureClosedPromiseRejected( writer, error )
|
||||
*/
|
||||
[[nodiscard]] bool js::WritableStreamDefaultWriterEnsureClosedPromiseRejected(
|
||||
JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter,
|
||||
Handle<Value> error) {
|
||||
return EnsurePromiseRejected(
|
||||
cx, unwrappedWriter, &WritableStreamDefaultWriter::closedPromise,
|
||||
&WritableStreamDefaultWriter::setClosedPromise, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.6.6.
|
||||
* WritableStreamDefaultWriterEnsureReadyPromiseRejected( writer, error )
|
||||
*/
|
||||
[[nodiscard]] bool js::WritableStreamDefaultWriterEnsureReadyPromiseRejected(
|
||||
JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter,
|
||||
Handle<Value> error) {
|
||||
return EnsurePromiseRejected(
|
||||
cx, unwrappedWriter, &WritableStreamDefaultWriter::readyPromise,
|
||||
&WritableStreamDefaultWriter::setReadyPromise, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.6.7.
|
||||
* WritableStreamDefaultWriterGetDesiredSize ( writer )
|
||||
*/
|
||||
bool js::WritableStreamDefaultWriterGetDesiredSize(
|
||||
JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter,
|
||||
MutableHandle<Value> size) {
|
||||
// Step 1: Let stream be writer.[[ownerWritableStream]].
|
||||
const WritableStream* unwrappedStream =
|
||||
UnwrapStreamFromWriter(cx, unwrappedWriter);
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: Let state be stream.[[state]].
|
||||
// Step 3: If state is "errored" or "erroring", return null.
|
||||
if (unwrappedStream->errored() || unwrappedStream->erroring()) {
|
||||
size.setNull();
|
||||
}
|
||||
// Step 4: If state is "closed", return 0.
|
||||
else if (unwrappedStream->closed()) {
|
||||
size.setInt32(0);
|
||||
}
|
||||
// Step 5: Return
|
||||
// ! WritableStreamDefaultControllerGetDesiredSize(
|
||||
// stream.[[writableStreamController]]).
|
||||
else {
|
||||
size.setNumber(WritableStreamDefaultControllerGetDesiredSize(
|
||||
unwrappedStream->controller()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.6.8.
|
||||
* WritableStreamDefaultWriterRelease ( writer )
|
||||
*/
|
||||
bool js::WritableStreamDefaultWriterRelease(
|
||||
JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter) {
|
||||
// Step 1: Let stream be writer.[[ownerWritableStream]].
|
||||
// Step 2: Assert: stream is not undefined.
|
||||
MOZ_ASSERT(unwrappedWriter->hasStream());
|
||||
Rooted<WritableStream*> unwrappedStream(
|
||||
cx, UnwrapStreamFromWriter(cx, unwrappedWriter));
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Assert: stream.[[writer]] is writer.
|
||||
#ifdef DEBUG
|
||||
{
|
||||
WritableStreamDefaultWriter* unwrappedStreamWriter =
|
||||
UnwrapWriterFromStream(cx, unwrappedStream);
|
||||
if (!unwrappedStreamWriter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(unwrappedStreamWriter == unwrappedWriter);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Step 4: Let releasedError be a new TypeError.
|
||||
Rooted<Value> releasedError(cx);
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WRITABLESTREAM_CANT_RELEASE_ALREADY_CLOSED);
|
||||
if (!cx->isExceptionPending() || !GetAndClearException(cx, &releasedError)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 5: Perform
|
||||
// ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(
|
||||
// writer, releasedError).
|
||||
if (!WritableStreamDefaultWriterEnsureReadyPromiseRejected(
|
||||
cx, unwrappedWriter, releasedError)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 6: Perform
|
||||
// ! WritableStreamDefaultWriterEnsureClosedPromiseRejected(
|
||||
// writer, releasedError).
|
||||
if (!WritableStreamDefaultWriterEnsureClosedPromiseRejected(
|
||||
cx, unwrappedWriter, releasedError)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 7: Set stream.[[writer]] to undefined.
|
||||
unwrappedStream->clearWriter();
|
||||
|
||||
// Step 8: Set writer.[[ownerWritableStream]] to undefined.
|
||||
unwrappedWriter->clearStream();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 4.6.9.
|
||||
* WritableStreamDefaultWriterWrite ( writer, chunk )
|
||||
*/
|
||||
PromiseObject* js::WritableStreamDefaultWriterWrite(
|
||||
JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter,
|
||||
Handle<Value> chunk) {
|
||||
cx->check(chunk);
|
||||
|
||||
// Step 1: Let stream be writer.[[ownerWritableStream]].
|
||||
// Step 2: Assert: stream is not undefined.
|
||||
MOZ_ASSERT(unwrappedWriter->hasStream());
|
||||
Rooted<WritableStream*> unwrappedStream(
|
||||
cx, UnwrapStreamFromWriter(cx, unwrappedWriter));
|
||||
if (!unwrappedStream) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 3: Let controller be stream.[[writableStreamController]].
|
||||
Rooted<WritableStreamDefaultController*> unwrappedController(
|
||||
cx, unwrappedStream->controller());
|
||||
|
||||
// Step 4: Let chunkSize be
|
||||
// ! WritableStreamDefaultControllerGetChunkSize(controller, chunk).
|
||||
Rooted<Value> chunkSize(cx);
|
||||
if (!WritableStreamDefaultControllerGetChunkSize(cx, unwrappedController,
|
||||
chunk, &chunkSize)) {
|
||||
return nullptr;
|
||||
}
|
||||
cx->check(chunkSize);
|
||||
|
||||
// Step 5: If stream is not equal to writer.[[ownerWritableStream]], return a
|
||||
// promise rejected with a TypeError exception.
|
||||
// (This is just an obscure way of saying "If step 4 caused writer's lock on
|
||||
// stream to be released", or concretely, "If writer.[[ownerWritableStream]]
|
||||
// is [now, newly] undefined".)
|
||||
if (!unwrappedWriter->hasStream()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WRITABLESTREAM_RELEASED_DURING_WRITE);
|
||||
return PromiseRejectedWithPendingError(cx);
|
||||
}
|
||||
|
||||
auto RejectWithStoredError =
|
||||
[](JSContext* cx,
|
||||
Handle<WritableStream*> unwrappedStream) -> PromiseObject* {
|
||||
Rooted<Value> storedError(cx, unwrappedStream->storedError());
|
||||
if (!cx->compartment()->wrap(cx, &storedError)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return PromiseObject::unforgeableReject(cx, storedError);
|
||||
};
|
||||
|
||||
// Step 6: Let state be stream.[[state]].
|
||||
// Step 7: If state is "errored", return a promise rejected with
|
||||
// stream.[[storedError]].
|
||||
if (unwrappedStream->errored()) {
|
||||
return RejectWithStoredError(cx, unwrappedStream);
|
||||
}
|
||||
|
||||
// Step 8: If ! WritableStreamCloseQueuedOrInFlight(stream) is true or state
|
||||
// is "closed", return a promise rejected with a TypeError exception
|
||||
// indicating that the stream is closing or closed.
|
||||
if (WritableStreamCloseQueuedOrInFlight(unwrappedStream) ||
|
||||
unwrappedStream->closed()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WRITABLESTREAM_WRITE_CLOSING_OR_CLOSED);
|
||||
return PromiseRejectedWithPendingError(cx);
|
||||
}
|
||||
|
||||
// Step 9: If state is "erroring", return a promise rejected with
|
||||
// stream.[[storedError]].
|
||||
if (unwrappedStream->erroring()) {
|
||||
return RejectWithStoredError(cx, unwrappedStream);
|
||||
}
|
||||
|
||||
// Step 10: Assert: state is "writable".
|
||||
MOZ_ASSERT(unwrappedStream->writable());
|
||||
|
||||
// Step 11: Let promise be ! WritableStreamAddWriteRequest(stream).
|
||||
Rooted<PromiseObject*> promise(
|
||||
cx, WritableStreamAddWriteRequest(cx, unwrappedStream));
|
||||
if (!promise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 12: Perform
|
||||
// ! WritableStreamDefaultControllerWrite(controller, chunk,
|
||||
// chunkSize).
|
||||
if (!WritableStreamDefaultControllerWrite(cx, unwrappedController, chunk,
|
||||
chunkSize)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 13: Return promise.
|
||||
return promise;
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
/* Writable stream writer abstract operations. */
|
||||
|
||||
#ifndef builtin_streams_WritableStreamWriterOperations_h
|
||||
#define builtin_streams_WritableStreamWriterOperations_h
|
||||
|
||||
#include "js/RootingAPI.h" // JS::{,Mutable}Handle
|
||||
#include "js/Value.h" // JS::Value
|
||||
|
||||
struct JS_PUBLIC_API JSContext;
|
||||
class JS_PUBLIC_API JSObject;
|
||||
|
||||
namespace js {
|
||||
|
||||
class PromiseObject;
|
||||
class WritableStreamDefaultWriter;
|
||||
|
||||
extern JSObject* WritableStreamDefaultWriterAbort(
|
||||
JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter,
|
||||
JS::Handle<JS::Value> reason);
|
||||
|
||||
extern PromiseObject* WritableStreamDefaultWriterClose(
|
||||
JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter);
|
||||
|
||||
extern PromiseObject* WritableStreamDefaultWriterCloseWithErrorPropagation(
|
||||
JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter);
|
||||
|
||||
[[nodiscard]] extern bool
|
||||
WritableStreamDefaultWriterEnsureClosedPromiseRejected(
|
||||
JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter,
|
||||
JS::Handle<JS::Value> error);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamDefaultWriterEnsureReadyPromiseRejected(
|
||||
JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter,
|
||||
JS::Handle<JS::Value> error);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamDefaultWriterGetDesiredSize(
|
||||
JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter,
|
||||
JS::MutableHandle<JS::Value> size);
|
||||
|
||||
[[nodiscard]] extern bool WritableStreamDefaultWriterRelease(
|
||||
JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter);
|
||||
|
||||
extern PromiseObject* WritableStreamDefaultWriterWrite(
|
||||
JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter,
|
||||
JS::Handle<JS::Value> chunk);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_WritableStreamWriterOperations_h
|
@ -441,7 +441,6 @@ if not CONFIG["MOZ_DOM_STREAMS"]:
|
||||
UNIFIED_SOURCES += [
|
||||
"builtin/Stream.cpp",
|
||||
"builtin/streams/MiscellaneousOperations.cpp",
|
||||
"builtin/streams/PipeToState.cpp",
|
||||
"builtin/streams/PullIntoDescriptor.cpp",
|
||||
"builtin/streams/QueueingStrategies.cpp",
|
||||
"builtin/streams/QueueWithSizes.cpp",
|
||||
@ -455,12 +454,6 @@ if not CONFIG["MOZ_DOM_STREAMS"]:
|
||||
"builtin/streams/ReadableStreamReader.cpp",
|
||||
"builtin/streams/StreamAPI.cpp",
|
||||
"builtin/streams/TeeState.cpp",
|
||||
"builtin/streams/WritableStream.cpp",
|
||||
"builtin/streams/WritableStreamDefaultController.cpp",
|
||||
"builtin/streams/WritableStreamDefaultControllerOperations.cpp",
|
||||
"builtin/streams/WritableStreamDefaultWriter.cpp",
|
||||
"builtin/streams/WritableStreamOperations.cpp",
|
||||
"builtin/streams/WritableStreamWriterOperations.cpp",
|
||||
]
|
||||
|
||||
# builtin/RegExp.cpp cannot be built in unified mode because it causes huge
|
||||
|
@ -622,8 +622,6 @@ bool shell::enableAsyncStackCaptureDebuggeeOnly = false;
|
||||
bool shell::enableStreams = false;
|
||||
bool shell::enableReadableByteStreams = false;
|
||||
bool shell::enableBYOBStreamReaders = false;
|
||||
bool shell::enableWritableStreams = false;
|
||||
bool shell::enableReadableStreamPipeTo = false;
|
||||
bool shell::enableWeakRefs = false;
|
||||
bool shell::enableToSource = false;
|
||||
bool shell::enablePropertyErrorMessageFix = false;
|
||||
@ -4237,8 +4235,6 @@ static void SetStandardRealmOptions(JS::RealmOptions& options) {
|
||||
.setStreamsEnabled(enableStreams)
|
||||
.setReadableByteStreamsEnabled(enableReadableByteStreams)
|
||||
.setBYOBStreamReadersEnabled(enableBYOBStreamReaders)
|
||||
.setWritableStreamsEnabled(enableWritableStreams)
|
||||
.setReadableStreamPipeToEnabled(enableReadableStreamPipeTo)
|
||||
.setWeakRefsEnabled(enableWeakRefs
|
||||
? JS::WeakRefSpecifier::EnabledWithCleanupSome
|
||||
: JS::WeakRefSpecifier::Disabled)
|
||||
@ -7031,20 +7027,6 @@ static bool NewGlobal(JSContext* cx, unsigned argc, Value* vp) {
|
||||
immutablePrototype = v.toBoolean();
|
||||
}
|
||||
|
||||
if (!JS_GetProperty(cx, opts, "enableWritableStreams", &v)) {
|
||||
return false;
|
||||
}
|
||||
if (v.isBoolean()) {
|
||||
creationOptions.setWritableStreamsEnabled(v.toBoolean());
|
||||
}
|
||||
|
||||
if (!JS_GetProperty(cx, opts, "enableReadableStreamPipeTo", &v)) {
|
||||
return false;
|
||||
}
|
||||
if (v.isBoolean()) {
|
||||
creationOptions.setReadableStreamPipeToEnabled(v.toBoolean());
|
||||
}
|
||||
|
||||
if (!JS_GetProperty(cx, opts, "systemPrincipal", &v)) {
|
||||
return false;
|
||||
}
|
||||
@ -11056,8 +11038,6 @@ static bool SetContextOptions(JSContext* cx, const OptionParser& op) {
|
||||
enableStreams = !op.getBoolOption("no-streams");
|
||||
enableReadableByteStreams = op.getBoolOption("enable-readable-byte-streams");
|
||||
enableBYOBStreamReaders = op.getBoolOption("enable-byob-stream-readers");
|
||||
enableWritableStreams = op.getBoolOption("enable-writable-streams");
|
||||
enableReadableStreamPipeTo = op.getBoolOption("enable-readablestream-pipeto");
|
||||
enableWeakRefs = !op.getBoolOption("disable-weak-refs");
|
||||
enableToSource = !op.getBoolOption("disable-tosource");
|
||||
enablePropertyErrorMessageFix =
|
||||
@ -12043,11 +12023,6 @@ int main(int argc, char** argv) {
|
||||
!op.addBoolOption('\0', "enable-byob-stream-readers",
|
||||
"Enable support for getting BYOB readers for WHATWG "
|
||||
"ReadableStreams of type \"bytes\"") ||
|
||||
!op.addBoolOption('\0', "enable-writable-streams",
|
||||
"Enable support for WHATWG WritableStreams") ||
|
||||
!op.addBoolOption('\0', "enable-readablestream-pipeto",
|
||||
"Enable support for "
|
||||
"WHATWG ReadableStream.prototype.pipeTo") ||
|
||||
!op.addBoolOption('\0', "disable-weak-refs", "Disable weak references") ||
|
||||
!op.addBoolOption('\0', "disable-tosource", "Disable toSource/uneval") ||
|
||||
!op.addBoolOption('\0', "disable-property-error-message-fix",
|
||||
|
@ -126,7 +126,7 @@ extern bool enableAsyncStackCaptureDebuggeeOnly;
|
||||
extern bool enableStreams;
|
||||
extern bool enableReadableByteStreams;
|
||||
extern bool enableBYOBStreamReaders;
|
||||
extern bool enableWritableStreams;
|
||||
|
||||
extern bool enableReadableStreamPipeTo;
|
||||
extern bool enableWeakRefs;
|
||||
extern bool enableToSource;
|
||||
|
@ -36,9 +36,6 @@
|
||||
#include "builtin/streams/ReadableStream.h" // js::ReadableStream
|
||||
#include "builtin/streams/ReadableStreamController.h" // js::Readable{StreamDefault,ByteStream}Controller
|
||||
#include "builtin/streams/ReadableStreamReader.h" // js::ReadableStreamDefaultReader
|
||||
#include "builtin/streams/WritableStream.h" // js::WritableStream
|
||||
#include "builtin/streams/WritableStreamDefaultController.h" // js::WritableStreamDefaultController
|
||||
#include "builtin/streams/WritableStreamDefaultWriter.h" // js::WritableStreamDefaultWriter
|
||||
#include "builtin/Symbol.h"
|
||||
#include "builtin/WeakMapObject.h"
|
||||
#include "builtin/WeakRefObject.h"
|
||||
@ -196,14 +193,6 @@ bool GlobalObject::skipDeselectedConstructor(JSContext* cx, JSProtoKey key) {
|
||||
case JSProto_ByteLengthQueuingStrategy:
|
||||
case JSProto_CountQueuingStrategy:
|
||||
return !cx->realm()->creationOptions().getStreamsEnabled();
|
||||
|
||||
case JSProto_WritableStream:
|
||||
case JSProto_WritableStreamDefaultController:
|
||||
case JSProto_WritableStreamDefaultWriter: {
|
||||
const auto& realmOptions = cx->realm()->creationOptions();
|
||||
return !realmOptions.getStreamsEnabled() ||
|
||||
!realmOptions.getWritableStreamsEnabled();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Return true if the given constructor has been disabled at run-time.
|
||||
|
@ -7079,16 +7079,6 @@ static bool IsAsmJSCompilerAvailable(JSContext* cx) {
|
||||
|
||||
static bool EstablishPreconditions(JSContext* cx,
|
||||
frontend::ParserBase& parser) {
|
||||
if (!IsAsmJSCompilerAvailable(cx)) {
|
||||
if (cx->realm() && cx->realm()->debuggerObservesAsmJS()) {
|
||||
return TypeFailureWarning(
|
||||
parser, "Asm.js optimizer disabled because debugger is active");
|
||||
}
|
||||
return TypeFailureWarning(parser,
|
||||
"Asm.js optimizer disabled because the compiler "
|
||||
"is disabled or unavailable");
|
||||
}
|
||||
|
||||
switch (parser.options().asmJSOption) {
|
||||
case AsmJSOption::DisabledByAsmJSPref:
|
||||
return TypeFailureWarning(
|
||||
|
Loading…
Reference in New Issue
Block a user