diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/builtin/streams/WritableStream.h | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | js/src/builtin/streams/WritableStream.h | 425 |
1 files changed, 425 insertions, 0 deletions
diff --git a/js/src/builtin/streams/WritableStream.h b/js/src/builtin/streams/WritableStream.h new file mode 100644 index 0000000000..3ed6cd12f4 --- /dev/null +++ b/js/src/builtin/streams/WritableStream.h @@ -0,0 +1,425 @@ +/* -*- 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/Attributes.h" // MOZ_MUST_USE +#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 { + /** + * 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()); + } + + static MOZ_MUST_USE 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 |