Bug 1582348 - Implement steps 2-3 of InitializeWritableStream. r=arai
authorJeff Walden <jwalden@mit.edu>
Tue, 24 Sep 2019 21:23:17 +0000
changeset 494827 ee5c3743760619d0b1fa84ffd3a3f535ab975419
parent 494826 22b0c9f685ba100c7b30170de68edd5744e1762b
child 494828 e5c50125c477777274ef86ce6277ba0637c01677
push id114131
push userdluca@mozilla.com
push dateThu, 26 Sep 2019 09:47:34 +0000
treeherdermozilla-inbound@1dc1a755079a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarai
bugs1582348
milestone71.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1582348 - Implement steps 2-3 of InitializeWritableStream. r=arai Differential Revision: https://phabricator.services.mozilla.com/D46399
js/src/builtin/streams/WritableStream.h
js/src/builtin/streams/WritableStreamOperations.cpp
--- a/js/src/builtin/streams/WritableStream.h
+++ b/js/src/builtin/streams/WritableStream.h
@@ -13,47 +13,102 @@
 #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 "js/Class.h"         // JSClass, js::ClassSpec
 #include "js/RootingAPI.h"    // JS::Handle
-#include "js/Value.h"         // JS::{,Int32}Value
+#include "js/Value.h"         // JS::{,Int32,Object,Undefined}Value
+#include "vm/List.h"          // js::ListObject
 #include "vm/NativeObject.h"  // js::NativeObject
 
 struct JSContext;
 
 namespace js {
 
+class WritableStreamDefaultController;
+
 class WritableStream : public NativeObject {
  public:
   /**
    * Memory layout of WritableStream instances.
    *
    * See https://streams.spec.whatwg.org/#ws-internal-slots for details on
    * the stored state.  [[state]] and [[backpressure]] are stored in Slot_State
-   * as WritableStream::State enum values.
+   * as a composite WritableStream::State enum value.
    *
    * XXX jwalden needs fleshin' out
    */
-  enum Slots { Slot_State, SlotCount };
+  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,
+    Slot_Writer,
+    Slot_State,
+    Slot_StoredError,
+
+    /**
+     * A ListObject consisting of the value of the [[inFlightWriteRequest]] spec
+     * field (if it is not |undefined|) followed by the elements of the
+     * [[queue]] List.
+     *
+     * 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]].
+     *
+     * 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,
-    FlagBits = Backpressure,
+    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 {
@@ -128,16 +183,124 @@ class WritableStream : public NativeObje
  public:
   bool writable() const { return state() == Writable; }
   bool closed() const { return state() == Closed; }
   bool erroring() const { return state() == Erroring; }
   bool errored() const { return state() == Errored; }
 
   bool backpressure() const { return flags() & Backpressure; }
 
+  bool haveInFlightWriteRequest() const {
+    return flags() & HaveInFlightWriteRequest;
+  }
+  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(); }
+  void setWriter(JSObject* writer) {
+    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);
+  }
+
+  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();
+  }
+
+  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();
+  }
+
+  ListObject* writeRequests() const {
+    return &getFixedSlot(Slot_WriteRequests).toObject().as<ListObject>();
+  }
+  void clearWriteRequests() {
+    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;
+  }
+
   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_;
--- a/js/src/builtin/streams/WritableStreamOperations.cpp
+++ b/js/src/builtin/streams/WritableStreamOperations.cpp
@@ -10,16 +10,17 @@
 
 #include "mozilla/Assertions.h"  // MOZ_ASSERT
 #include "mozilla/Attributes.h"  // MOZ_MUST_USE
 
 #include "jsapi.h"  // JS_SetPrivate
 
 #include "builtin/streams/WritableStream.h"  // js::WritableStream
 #include "vm/JSObject-inl.h"                 // js::NewObjectWithClassProto
+#include "vm/List-inl.h"                     // js::StoreNewListInFixedSlot
 
 using js::WritableStream;
 
 using JS::Handle;
 using JS::ObjectValue;
 using JS::Rooted;
 using JS::Value;
 
@@ -41,15 +42,46 @@ MOZ_MUST_USE /* static */
     return nullptr;
   }
 
   JS_SetPrivate(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->writable());
   MOZ_ASSERT(!stream->backpressure());
 
-  // XXX jwalden need to keep fleshin' out here
   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());
+}