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:
Matthew Gaudet 2022-03-01 14:17:07 +00:00
parent f46bdbcad0
commit f83d513364
27 changed files with 7 additions and 6199 deletions

View File

@ -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_)) \

View File

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

View File

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

View File

@ -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_ = {

View File

@ -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;
}

View File

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

View File

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

View File

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

View File

@ -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);

View File

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

View File

@ -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);

View File

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

View File

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

View File

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

View File

@ -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);

View File

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

View File

@ -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;
}

View File

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

View File

@ -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;
}

View File

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

View File

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

View File

@ -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",

View File

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

View File

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

View File

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