summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorMoonchild <moonchild@palemoon.org>2023-10-03 17:23:19 +0000
committerMoonchild <moonchild@palemoon.org>2023-10-03 17:23:19 +0000
commit5d76f359905f10fbd5b4c53ac458e26d22ffe2a1 (patch)
tree912d084cee32880dad7b06fcd1fdc1fc71ce5b95 /js
parent682e5a4727b8bdf33a670c5d08e9cb382f019a57 (diff)
parent054f135a5455967009587e12c14908904ab4105a (diff)
downloaduxp-5d76f359905f10fbd5b4c53ac458e26d22ffe2a1.tar.gz
Merge pull request 'Initial implementation of readable streams' (#2324) from dbsoft/UXP:readablestreams into master
Reviewed-on: https://repo.palemoon.org/MoonchildProductions/UXP/pulls/2324
Diffstat (limited to 'js')
-rw-r--r--js/public/Stream.h510
-rw-r--r--js/src/builtin/Stream.cpp5488
-rw-r--r--js/src/builtin/Stream.h170
-rw-r--r--js/src/builtin/TestingFunctions.cpp12
-rw-r--r--js/src/builtin/TypedArray.js6
-rw-r--r--js/src/builtin/Utilities.js1
-rw-r--r--js/src/js.msg30
-rw-r--r--js/src/jsapi-tests/moz.build5
-rw-r--r--js/src/jsapi-tests/testIntTypesABI.cpp1
-rw-r--r--js/src/jsapi-tests/testReadableStream.cpp676
-rw-r--r--js/src/jsapi-tests/tests.cpp4
-rw-r--r--js/src/jsapi.cpp366
-rw-r--r--js/src/jsapi.h12
-rw-r--r--js/src/jsfriendapi.h9
-rw-r--r--js/src/jsnum.cpp11
-rw-r--r--js/src/jsnum.h4
-rw-r--r--js/src/jsprototypes.h11
-rw-r--r--js/src/moz.build2
-rw-r--r--js/src/shell/js.cpp9
-rw-r--r--js/src/vm/ArrayBufferObject.cpp20
-rw-r--r--js/src/vm/ArrayBufferObject.h6
-rw-r--r--js/src/vm/CommonPropertyNames.h53
-rw-r--r--js/src/vm/GlobalObject.cpp11
-rw-r--r--js/src/vm/Runtime.cpp6
-rw-r--r--js/src/vm/Runtime.h7
-rw-r--r--js/src/vm/SelfHosting.cpp22
-rw-r--r--js/src/vm/SharedArrayObject.cpp9
-rw-r--r--js/src/vm/SharedArrayObject.h6
-rw-r--r--js/xpconnect/src/XPCJSContext.cpp5
29 files changed, 7436 insertions, 36 deletions
diff --git a/js/public/Stream.h b/js/public/Stream.h
new file mode 100644
index 0000000000..b91239ac9a
--- /dev/null
+++ b/js/public/Stream.h
@@ -0,0 +1,510 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+/*
+ * JSAPI functions and callbacks related to WHATWG Stream objects.
+ *
+ * Much of the API here mirrors the JS API of ReadableStream and associated
+ * classes, e.g. ReadableStreamDefaultReader, ReadableStreamBYOBReader,
+ * ReadableStreamDefaultController, ReadableByteStreamController, and
+ * ReadableStreamBYOBRequest.
+ *
+ * There are some crucial differences, though: Functionality that's exposed
+ * as methods/accessors on controllers in JS is exposed as functions taking
+ * ReadableStream instances instead. This is because an analysis of how
+ * the API would be used showed that all functions that'd take controllers
+ * would do so by first getting the controller from the stream instance it's
+ * associated with and then call the function taking it. I.e., it would purely
+ * add boilerplate without any gains in ease of use of the API.
+ *
+ * Some functions exposed here deal with ReadableStream instances that have an
+ * embedding-provided underlying source. These instances are largely similar
+ * to byte streams as created using |new ReadableStream({type: "bytes"})|:
+ * They enable users to acquire ReadableStreamBYOBReaders and only vend chunks
+ * that're typed array instances.
+ *
+ * When creating an "external readable stream" using
+ * JS::NewReadableExternalSourceStreamObject, an underlying source and a set
+ * of flags can be passed to be stored on the stream. The underlying source is
+ * treated as an opaque void* pointer by the JS engine: it's purely meant as
+ * a reference to be used by the embedding to identify whatever actual source
+ * it uses to supply data for the stream. Similarly, the flags aren't
+ * interpreted by the JS engine, but are passed to some of the callbacks below
+ * and can be retrieved using JS::ReadableStreamGetEmbeddingFlags.
+ *
+ * External readable streams are optimized to allow the embedding to interact
+ * with them with a minimum of overhead: chunks aren't enqueued as individual
+ * typed array instances; instead, the embedding only updates the amount of
+ * data available using ReadableStreamUpdateDataAvailableFromSource.
+ * When content requests data by reading from a reader,
+ * WriteIntoReadRequestBufferCallback is invoked, asking the embedding to
+ * write data directly into the buffer we're about to hand to content.
+ *
+ * Additionally, ReadableStreamGetExternalUnderlyingSource can be used to
+ * get the void* pointer to the underlying source. This is equivalent to
+ * acquiring a reader for the stream in that it locks the stream until it
+ * is released again using JS::ReadableStreamReleaseExternalUnderlyingSource.
+ *
+ * Embeddings are expected to detect situations where an API exposed to JS
+ * takes a ReadableStream to read from that has an external underlying source.
+ * In those situations, it might be preferable to directly perform data
+ * transfers from the stream's underlying source to whatever sink the
+ * embedding uses, assuming that such direct transfers can be performed
+ * more efficiently.
+ */
+
+#ifndef js_Stream_h
+#define js_Stream_h
+
+#include "jstypes.h"
+
+#include "js/TypeDecls.h"
+
+namespace JS {
+
+/**
+ * Invoked whenever a reader desires more data from a ReadableStream's
+ * embedding-provided underlying source.
+ *
+ * The given |desiredSize| is the absolute size, not a delta from the previous
+ * desired size.
+ */
+typedef void
+(* RequestReadableStreamDataCallback)(JSContext* cx, HandleObject stream,
+ void* underlyingSource, uint8_t flags, size_t desiredSize);
+
+/**
+ * Invoked to cause the embedding to fill the given |buffer| with data from
+ * the given embedding-provided underlying source.
+ *
+ * This can only happen after the embedding has updated the amount of data
+ * available using JS::ReadableStreamUpdateDataAvailableFromSource. If at
+ * least one read request is pending when
+ * JS::ReadableStreamUpdateDataAvailableFromSource is called,
+ * the WriteIntoReadRequestBufferCallback is invoked immediately from under
+ * the call to JS::WriteIntoReadRequestBufferCallback. If not, it is invoked
+ * if and when a new read request is made.
+ *
+ * Note: This callback *must not cause GC*, because that could potentially
+ * invalidate the |buffer| pointer.
+ */
+typedef void
+(* WriteIntoReadRequestBufferCallback)(JSContext* cx, HandleObject stream,
+ void* underlyingSource, uint8_t flags, void* buffer,
+ size_t length, size_t* bytesWritten);
+
+/**
+ * Invoked in reaction to the ReadableStream being canceled to allow the
+ * embedding to free the underlying source.
+ *
+ * This is equivalent to calling |cancel| on non-external underlying sources
+ * provided to the ReadableStream constructor in JavaScript.
+ *
+ * The given |reason| is the JS::Value that was passed as an argument to
+ * ReadableStream#cancel().
+ *
+ * The returned JS::Value will be used to resolve the Promise returned by
+ * ReadableStream#cancel().
+ */
+typedef Value
+(* CancelReadableStreamCallback)(JSContext* cx, HandleObject stream,
+ void* underlyingSource, uint8_t flags, HandleValue reason);
+
+/**
+ * Invoked in reaction to a ReadableStream with an embedding-provided
+ * underlying source being closed.
+ */
+typedef void
+(* ReadableStreamClosedCallback)(JSContext* cx, HandleObject stream, void* underlyingSource,
+ uint8_t flags);
+
+/**
+ * Invoked in reaction to a ReadableStream with an embedding-provided
+ * underlying source being errored with the
+ * given reason.
+ */
+typedef void
+(* ReadableStreamErroredCallback)(JSContext* cx, HandleObject stream, void* underlyingSource,
+ uint8_t flags, HandleValue reason);
+
+/**
+ * Invoked in reaction to a ReadableStream with an embedding-provided
+ * underlying source being finalized. Only the underlying source is passed
+ * as an argument, while the ReadableStream itself is not to prevent the
+ * embedding from operating on a JSObject that might not be in a valid state
+ * anymore.
+ *
+ * Note: the ReadableStream might be finalized on a background thread. That
+ * means this callback might be invoked from an arbitrary thread, which the
+ * embedding must be able to handle.
+ */
+typedef void
+(* ReadableStreamFinalizeCallback)(void* underlyingSource, uint8_t flags);
+
+/**
+ * Sets runtime-wide callbacks to use for interacting with embedding-provided
+ * hooks for operating on ReadableStream instances.
+ *
+ * See the documentation for the individual callback types for details.
+ */
+extern JS_PUBLIC_API(void)
+SetReadableStreamCallbacks(JSContext* cx,
+ RequestReadableStreamDataCallback dataRequestCallback,
+ WriteIntoReadRequestBufferCallback writeIntoReadRequestCallback,
+ CancelReadableStreamCallback cancelCallback,
+ ReadableStreamClosedCallback closedCallback,
+ ReadableStreamErroredCallback erroredCallback,
+ ReadableStreamFinalizeCallback finalizeCallback);
+
+extern JS_PUBLIC_API(bool)
+HasReadableStreamCallbacks(JSContext* cx);
+
+/**
+ * Returns a new instance of the ReadableStream builtin class in the current
+ * compartment, configured as a default stream.
+ * If a |proto| is passed, that gets set as the instance's [[Prototype]]
+ * instead of the original value of |ReadableStream.prototype|.
+ */
+extern JS_PUBLIC_API(JSObject*)
+NewReadableDefaultStreamObject(JSContext* cx, HandleObject underlyingSource = nullptr,
+ HandleFunction size = nullptr, double highWaterMark = 1,
+ HandleObject proto = nullptr);
+
+/**
+ * Returns a new instance of the ReadableStream builtin class in the current
+ * compartment, configured as a byte stream.
+ * If a |proto| is passed, that gets set as the instance's [[Prototype]]
+ * instead of the original value of |ReadableStream.prototype|.
+ */
+extern JS_PUBLIC_API(JSObject*)
+NewReadableByteStreamObject(JSContext* cx, HandleObject underlyingSource = nullptr,
+ double highWaterMark = 0, HandleObject proto = nullptr);
+
+/**
+ * Returns a new instance of the ReadableStream builtin class in the current
+ * compartment, with the right slot layout. If a |proto| is passed, that gets
+ * set as the instance's [[Prototype]] instead of the original value of
+ * |ReadableStream.prototype|.
+ *
+ * The instance is optimized for operating as a byte stream backed by an
+ * embedding-provided underlying source, using the callbacks set via
+ * |JS::SetReadableStreamCallbacks|.
+ *
+ * The given |flags| will be passed to all applicable callbacks and can be
+ * used to disambiguate between different types of stream sources the
+ * embedding might support.
+ *
+ * Note: the embedding is responsible for ensuring that the pointer to the
+ * underlying source stays valid as long as the stream can be read from.
+ * The underlying source can be freed if the tree is canceled or errored.
+ * It can also be freed if the stream is destroyed. The embedding is notified
+ * of that using ReadableStreamFinalizeCallback.
+ */
+extern JS_PUBLIC_API(JSObject*)
+NewReadableExternalSourceStreamObject(JSContext* cx, void* underlyingSource,
+ uint8_t flags = 0, HandleObject proto = nullptr);
+
+/**
+ * Returns the flags that were passed to NewReadableExternalSourceStreamObject
+ * when creating the given stream.
+ *
+ * Asserts that the given stream has an embedding-provided underlying source.
+ */
+extern JS_PUBLIC_API(uint8_t)
+ReadableStreamGetEmbeddingFlags(const JSObject* stream);
+
+/**
+ * Returns the embedding-provided underlying source of the given |stream|.
+ *
+ * Can be used to optimize operations if both the underlying source and the
+ * intended sink are embedding-provided. In that case it might be
+ * preferrable to pipe data directly from source to sink without interacting
+ * with the stream at all.
+ *
+ * Locks the stream until ReadableStreamReleaseExternalUnderlyingSource is
+ * called.
+ *
+ * Throws an exception if the stream is locked, i.e. if a reader has been
+ * acquired for the stream, or if ReadableStreamGetExternalUnderlyingSource
+ * has been used previously without releasing the external source again.
+ *
+ * Throws an exception if the stream isn't readable, i.e if it is errored or
+ * closed. This is different from ReadableStreamGetReader because we don't
+ * have a Promise to resolve/reject, which a reader provides.
+ *
+ * Asserts that the stream has an embedding-provided underlying source.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamGetExternalUnderlyingSource(JSContext* cx, HandleObject stream, void** source);
+
+/**
+ * Releases the embedding-provided underlying source of the given |stream|,
+ * returning the stream into an unlocked state.
+ *
+ * Asserts that the stream was locked through
+ * ReadableStreamGetExternalUnderlyingSource.
+ *
+ * Asserts that the stream has an embedding-provided underlying source.
+ */
+extern JS_PUBLIC_API(void)
+ReadableStreamReleaseExternalUnderlyingSource(JSObject* stream);
+
+/**
+ * Update the amount of data available at the underlying source of the given
+ * |stream|.
+ *
+ * Can only be used for streams with an embedding-provided underlying source.
+ * The JS engine will use the given value to satisfy read requests for the
+ * stream by invoking the JS::WriteIntoReadRequestBuffer callback.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamUpdateDataAvailableFromSource(JSContext* cx, HandleObject stream,
+ uint32_t availableData);
+
+/**
+ * Returns true if the given object is an unwrapped ReadableStream object,
+ * false otherwise.
+ */
+extern JS_PUBLIC_API(bool)
+IsReadableStream(const JSObject* obj);
+
+/**
+ * Returns true if the given object is an unwrapped
+ * ReadableStreamDefaultReader or ReadableStreamBYOBReader object,
+ * false otherwise.
+ */
+extern JS_PUBLIC_API(bool)
+IsReadableStreamReader(const JSObject* obj);
+
+/**
+ * Returns true if the given object is an unwrapped
+ * ReadableStreamDefaultReader object, false otherwise.
+ */
+extern JS_PUBLIC_API(bool)
+IsReadableStreamDefaultReader(const JSObject* obj);
+
+/**
+ * Returns true if the given object is an unwrapped
+ * ReadableStreamBYOBReader object, false otherwise.
+ */
+extern JS_PUBLIC_API(bool)
+IsReadableStreamBYOBReader(const JSObject* obj);
+
+enum class ReadableStreamMode {
+ Default,
+ Byte,
+ ExternalSource
+};
+
+/**
+ * Returns the stream's ReadableStreamMode. If the mode is |Byte| or
+ * |ExternalSource|, it's possible to acquire a BYOB reader for more optimized
+ * operations.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(ReadableStreamMode)
+ReadableStreamGetMode(const JSObject* stream);
+
+enum class ReadableStreamReaderMode {
+ Default,
+ BYOB
+};
+
+/**
+ * Returns true if the given ReadableStream is readable, false if not.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamIsReadable(const JSObject* stream);
+
+/**
+ * Returns true if the given ReadableStream is locked, false if not.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamIsLocked(const JSObject* stream);
+
+/**
+ * Returns true if the given ReadableStream is disturbed, false if not.
+ *
+ * Asserts that |stream| is an ReadableStream instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamIsDisturbed(const JSObject* stream);
+
+/**
+ * Cancels the given ReadableStream with the given reason and returns a
+ * Promise resolved according to the result.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(JSObject*)
+ReadableStreamCancel(JSContext* cx, HandleObject stream, HandleValue reason);
+
+/**
+ * Creates a reader of the type specified by the mode option and locks the
+ * stream to the new reader.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(JSObject*)
+ReadableStreamGetReader(JSContext* cx, HandleObject stream, ReadableStreamReaderMode mode);
+
+/**
+ * Tees the given ReadableStream and stores the two resulting streams in
+ * outparams. Returns false if the operation fails, e.g. because the stream is
+ * locked.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamTee(JSContext* cx, HandleObject stream,
+ MutableHandleObject branch1Stream, MutableHandleObject branch2Stream);
+
+/**
+ * Retrieves the desired combined size of additional chunks to fill the given
+ * ReadableStream's queue. Stores the result in |value| and sets |hasValue| to
+ * true on success, returns false on failure.
+ *
+ * If the stream is errored, the call will succeed but no value will be stored
+ * in |value| and |hasValue| will be set to false.
+ *
+ * Note: This is semantically equivalent to the |desiredSize| getter on
+ * the stream controller's prototype in JS. We expose it with the stream
+ * itself as a target for simplicity.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(void)
+ReadableStreamGetDesiredSize(JSObject* stream, bool* hasValue, double* value);
+
+/**
+ * Closes the given ReadableStream.
+ *
+ * Throws a TypeError and returns false if the closing operation fails.
+ *
+ * Note: This is semantically equivalent to the |close| method on
+ * the stream controller's prototype in JS. We expose it with the stream
+ * itself as a target for simplicity.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamClose(JSContext* cx, HandleObject stream);
+
+/**
+ * Returns true if the given ReadableStream reader is locked, false otherwise.
+ *
+ * Asserts that |reader| is an unwrapped ReadableStreamDefaultReader or
+ * ReadableStreamBYOBReader instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamReaderIsClosed(const JSObject* reader);
+
+/**
+ * Enqueues the given chunk in the given ReadableStream.
+ *
+ * Throws a TypeError and returns false if the enqueing operation fails.
+ *
+ * Note: This is semantically equivalent to the |enqueue| method on
+ * the stream controller's prototype in JS. We expose it with the stream
+ * itself as a target for simplicity.
+ *
+ * If the ReadableStream has an underlying byte source, the given chunk must
+ * be a typed array or a DataView. Consider using
+ * ReadableByteStreamEnqueueBuffer.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamEnqueue(JSContext* cx, HandleObject stream, HandleValue chunk);
+
+/**
+ * Enqueues the given buffer as a chunk in the given ReadableStream.
+ *
+ * Throws a TypeError and returns false if the enqueing operation fails.
+ *
+ * Note: This is semantically equivalent to the |enqueue| method on
+ * the stream controller's prototype in JS. We expose it with the stream
+ * itself as a target for simplicity. Additionally, the JS version only
+ * takes typed arrays and ArrayBufferView instances as arguments, whereas
+ * this takes an ArrayBuffer, obviating the need to wrap it into a typed
+ * array.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance and |buffer|
+ * an unwrapped ArrayBuffer instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableByteStreamEnqueueBuffer(JSContext* cx, HandleObject stream, HandleObject buffer);
+
+/**
+ * Errors the given ReadableStream, causing all future interactions to fail
+ * with the given error value.
+ *
+ * Throws a TypeError and returns false if the erroring operation fails.
+ *
+ * Note: This is semantically equivalent to the |error| method on
+ * the stream controller's prototype in JS. We expose it with the stream
+ * itself as a target for simplicity.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamError(JSContext* cx, HandleObject stream, HandleValue error);
+
+/**
+ * Cancels the given ReadableStream reader's associated stream.
+ *
+ * Throws a TypeError and returns false if the given reader isn't active.
+ *
+ * Asserts that |reader| is an unwrapped ReadableStreamDefaultReader or
+ * ReadableStreamBYOBReader instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamReaderCancel(JSContext* cx, HandleObject reader, HandleValue reason);
+
+/**
+ * Cancels the given ReadableStream reader's associated stream.
+ *
+ * Throws a TypeError and returns false if the given reader has pending
+ * read or readInto (for default or byob readers, respectively) requests.
+ *
+ * Asserts that |reader| is an unwrapped ReadableStreamDefaultReader or
+ * ReadableStreamBYOBReader instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamReaderReleaseLock(JSContext* cx, HandleObject reader);
+
+/**
+ * Requests a read from the reader's associated ReadableStream and returns the
+ * resulting PromiseObject.
+ *
+ * Returns a Promise that's resolved with the read result once available or
+ * rejected immediately if the stream is errored or the operation failed.
+ *
+ * Asserts that |reader| is an unwrapped ReadableStreamDefaultReader instance.
+ */
+extern JS_PUBLIC_API(JSObject*)
+ReadableStreamDefaultReaderRead(JSContext* cx, HandleObject reader);
+
+/**
+ * Requests a read from the reader's associated ReadableStream into the given
+ * ArrayBufferView and returns the resulting PromiseObject.
+ *
+ * Returns a Promise that's resolved with the read result once available or
+ * rejected immediately if the stream is errored or the operation failed.
+ *
+ * Asserts that |reader| is an unwrapped ReadableStreamDefaultReader and
+ * |view| an unwrapped typed array or DataView instance.
+ */
+extern JS_PUBLIC_API(JSObject*)
+ReadableStreamBYOBReaderRead(JSContext* cx, HandleObject reader, HandleObject view);
+
+} // namespace JS
+
+#endif // js_Realm_h
diff --git a/js/src/builtin/Stream.cpp b/js/src/builtin/Stream.cpp
new file mode 100644
index 0000000000..c8d8e3e324
--- /dev/null
+++ b/js/src/builtin/Stream.cpp
@@ -0,0 +1,5488 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+#include "builtin/Stream.h"
+
+#include "js/Stream.h"
+
+#include "jscntxt.h"
+
+#include "gc/Heap.h"
+#include "vm/SelfHosting.h"
+
+#include "jsobjinlines.h"
+
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+enum StreamSlots {
+ StreamSlot_Controller,
+ StreamSlot_Reader,
+ StreamSlot_State,
+ StreamSlot_StoredError,
+ StreamSlotCount
+};
+
+enum ReaderSlots {
+ ReaderSlot_Stream,
+ ReaderSlot_Requests,
+ ReaderSlot_ClosedPromise,
+ ReaderSlotCount,
+};
+
+enum ReaderType {
+ ReaderType_Default,
+ ReaderType_BYOB
+};
+
+// ReadableStreamDefaultController and ReadableByteStreamController are both
+// queue containers and must have these slots at identical offsets.
+enum QueueContainerSlots {
+ QueueContainerSlot_Queue,
+ QueueContainerSlot_TotalSize,
+ QueueContainerSlotCount
+};
+
+// These slots are identical between the two types of ReadableStream
+// controllers.
+enum ControllerSlots {
+ ControllerSlot_Stream = QueueContainerSlotCount,
+ ControllerSlot_UnderlyingSource,
+ ControllerSlot_StrategyHWM,
+ ControllerSlot_Flags,
+ ControllerSlotCount
+};
+
+enum DefaultControllerSlots {
+ DefaultControllerSlot_StrategySize = ControllerSlotCount,
+ DefaultControllerSlotCount
+};
+
+enum ByteControllerSlots {
+ ByteControllerSlot_BYOBRequest = ControllerSlotCount,
+ ByteControllerSlot_PendingPullIntos,
+ ByteControllerSlot_AutoAllocateSize,
+ ByteControllerSlotCount
+};
+
+enum ControllerFlags {
+ ControllerFlag_Started = 1 << 0,
+ ControllerFlag_Pulling = 1 << 1,
+ ControllerFlag_PullAgain = 1 << 2,
+ ControllerFlag_CloseRequested = 1 << 3,
+ ControllerFlag_TeeBranch = 1 << 4,
+ ControllerFlag_TeeBranch1 = 1 << 5,
+ ControllerFlag_TeeBranch2 = 1 << 6,
+ ControllerFlag_ExternalSource = 1 << 7,
+ ControllerFlag_SourceLocked = 1 << 8,
+};
+
+// Offset at which embedding flags are stored.
+constexpr uint8_t ControllerEmbeddingFlagsOffset = 24;
+
+enum BYOBRequestSlots {
+ BYOBRequestSlot_Controller,
+ BYOBRequestSlot_View,
+ BYOBRequestSlotCount
+};
+
+template<class T>
+MOZ_ALWAYS_INLINE bool
+Is(const HandleValue v)
+{
+ return v.isObject() && v.toObject().is<T>();
+}
+
+#ifdef DEBUG
+static bool
+IsReadableStreamController(const JSObject* controller)
+{
+ return controller->is<ReadableStreamDefaultController>() ||
+ controller->is<ReadableByteStreamController>();
+}
+#endif // DEBUG
+
+static inline uint32_t
+ControllerFlags(const NativeObject* controller)
+{
+ MOZ_ASSERT(IsReadableStreamController(controller));
+ return controller->getFixedSlot(ControllerSlot_Flags).toInt32();
+}
+
+static inline void
+AddControllerFlags(NativeObject* controller, uint32_t flags)
+{
+ MOZ_ASSERT(IsReadableStreamController(controller));
+ controller->setFixedSlot(ControllerSlot_Flags,
+ Int32Value(ControllerFlags(controller) | flags));
+}
+
+static inline void
+RemoveControllerFlags(NativeObject* controller, uint32_t flags)
+{
+ MOZ_ASSERT(IsReadableStreamController(controller));
+ controller->setFixedSlot(ControllerSlot_Flags,
+ Int32Value(ControllerFlags(controller) & ~flags));
+}
+
+static inline uint32_t
+StreamState(const ReadableStream* stream)
+{
+ return stream->getFixedSlot(StreamSlot_State).toInt32();
+}
+
+static inline void
+SetStreamState(ReadableStream* stream, uint32_t state)
+{
+ MOZ_ASSERT_IF(stream->disturbed(), state & ReadableStream::Disturbed);
+ MOZ_ASSERT_IF(stream->closed() || stream->errored(), !(state & ReadableStream::Readable));
+ stream->setFixedSlot(StreamSlot_State, Int32Value(state));
+}
+
+bool
+ReadableStream::readable() const
+{
+ return StreamState(this) & Readable;
+}
+
+bool
+ReadableStream::closed() const
+{
+ return StreamState(this) & Closed;
+}
+
+bool
+ReadableStream::errored() const
+{
+ return StreamState(this) & Errored;
+}
+
+bool
+ReadableStream::disturbed() const
+{
+ return StreamState(this) & Disturbed;
+}
+
+inline static bool
+ReaderHasStream(const NativeObject* reader)
+{
+ MOZ_ASSERT(JS::IsReadableStreamReader(reader));
+ return !reader->getFixedSlot(ReaderSlot_Stream).isUndefined();
+}
+
+bool
+js::ReadableStreamReaderIsClosed(const JSObject* reader)
+{
+ return !ReaderHasStream(&reader->as<NativeObject>());
+}
+
+inline static MOZ_MUST_USE ReadableStream*
+StreamFromController(const NativeObject* controller)
+{
+ MOZ_ASSERT(IsReadableStreamController(controller));
+ return &controller->getFixedSlot(ControllerSlot_Stream).toObject().as<ReadableStream>();
+}
+
+inline static MOZ_MUST_USE NativeObject*
+ControllerFromStream(const ReadableStream* stream)
+{
+ Value controllerVal = stream->getFixedSlot(StreamSlot_Controller);
+ MOZ_ASSERT(IsReadableStreamController(&controllerVal.toObject()));
+ return &controllerVal.toObject().as<NativeObject>();
+}
+
+inline static bool
+HasController(const ReadableStream* stream)
+{
+ return !stream->getFixedSlot(StreamSlot_Controller).isUndefined();
+}
+
+JS::ReadableStreamMode
+ReadableStream::mode() const
+{
+ NativeObject* controller = ControllerFromStream(this);
+ if (controller->is<ReadableStreamDefaultController>())
+ return JS::ReadableStreamMode::Default;
+ return controller->as<ReadableByteStreamController>().hasExternalSource()
+ ? JS::ReadableStreamMode::ExternalSource
+ : JS::ReadableStreamMode::Byte;
+}
+
+inline static MOZ_MUST_USE ReadableStream*
+StreamFromReader(const NativeObject* reader)
+{
+ MOZ_ASSERT(ReaderHasStream(reader));
+ return &reader->getFixedSlot(ReaderSlot_Stream).toObject().as<ReadableStream>();
+}
+
+inline static MOZ_MUST_USE NativeObject*
+ReaderFromStream(const NativeObject* stream)
+{
+ Value readerVal = stream->getFixedSlot(StreamSlot_Reader);
+ MOZ_ASSERT(JS::IsReadableStreamReader(&readerVal.toObject()));
+ return &readerVal.toObject().as<NativeObject>();
+}
+
+inline static bool
+HasReader(const ReadableStream* stream)
+{
+ return !stream->getFixedSlot(StreamSlot_Reader).isUndefined();
+}
+
+inline static MOZ_MUST_USE JSFunction*
+NewHandler(JSContext *cx, Native handler, HandleObject target)
+{
+ RootedAtom funName(cx, cx->names().empty);
+ RootedFunction handlerFun(cx, NewNativeFunction(cx, handler, 0, funName,
+ gc::AllocKind::FUNCTION_EXTENDED,
+ GenericObject));
+ if (!handlerFun)
+ return nullptr;
+ handlerFun->setExtendedSlot(0, ObjectValue(*target));
+ return handlerFun;
+}
+
+template<class T>
+inline static MOZ_MUST_USE T*
+TargetFromHandler(JSObject& handler)
+{
+ return &handler.as<JSFunction>().getExtendedSlot(0).toObject().as<T>();
+}
+
+inline static MOZ_MUST_USE bool
+ResetQueue(JSContext* cx, HandleNativeObject container);
+
+inline static MOZ_MUST_USE bool
+InvokeOrNoop(JSContext* cx, HandleValue O, HandlePropertyName P, HandleValue arg,
+ MutableHandleValue rval);
+
+static MOZ_MUST_USE JSObject*
+PromiseInvokeOrNoop(JSContext* cx, HandleValue O, HandlePropertyName P, HandleValue arg);
+
+static MOZ_MUST_USE JSObject*
+PromiseRejectedWithPendingError(JSContext* cx) {
+ // Not much we can do about uncatchable exceptions, just bail.
+ RootedValue exn(cx);
+ if (!GetAndClearException(cx, &exn))
+ return nullptr;
+ return PromiseObject::unforgeableReject(cx, exn);
+}
+
+static bool
+ReportArgTypeError(JSContext* cx, const char* funName, const char* expectedType,
+ HandleValue arg)
+{
+ UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, arg, nullptr);
+ if (!bytes)
+ return false;
+
+ return JS_ReportErrorFlagsAndNumberLatin1(cx, JSREPORT_ERROR, GetErrorMessage,
+ nullptr, JSMSG_NOT_EXPECTED_TYPE,
+ funName, expectedType, bytes.get());
+}
+
+static MOZ_MUST_USE bool
+ReturnPromiseRejectedWithPendingError(JSContext* cx, const CallArgs& args)
+{
+ JSObject* promise = PromiseRejectedWithPendingError(cx);
+ if (!promise)
+ return false;
+
+ args.rval().setObject(*promise);
+ return true;
+}
+
+static MOZ_MUST_USE bool
+RejectNonGenericMethod(JSContext* cx, const CallArgs& args,
+ const char* className, const char* methodName)
+{
+ ReportValueError3(cx, JSMSG_INCOMPATIBLE_PROTO, JSDVG_SEARCH_STACK, args.thisv(),
+ nullptr, className, methodName);
+
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+}
+
+inline static MOZ_MUST_USE NativeObject*
+SetNewList(JSContext* cx, HandleNativeObject container, uint32_t slot)
+{
+ NativeObject* list = NewObjectWithNullTaggedProto<PlainObject>(cx);
+ if (!list)
+ return nullptr;
+ container->setFixedSlot(slot, ObjectValue(*list));
+ return list;
+}
+
+inline static MOZ_MUST_USE bool
+AppendToList(JSContext* cx, HandleNativeObject list, HandleValue value)
+{
+ uint32_t length = list->getDenseInitializedLength();
+
+ if (!list->ensureElements(cx, length + 1))
+ return false;
+
+ list->ensureDenseInitializedLength(cx, length, 1);
+ list->setDenseElement(length, value);
+
+ return true;
+}
+
+template<class T>
+inline static MOZ_MUST_USE T*
+PeekList(NativeObject* list)
+{
+ MOZ_ASSERT(list->getDenseInitializedLength() > 0);
+ return &list->getDenseElement(0).toObject().as<T>();
+}
+
+template<class T>
+inline static MOZ_MUST_USE T*
+ShiftFromList(JSContext* cx, HandleNativeObject list)
+{
+ uint32_t length = list->getDenseInitializedLength();
+ MOZ_ASSERT(length > 0);
+
+ Rooted<T*> entry(cx, &list->getDenseElement(0).toObject().as<T>());
+ list->moveDenseElements(0, 1, length - 1);
+ list->shrinkElements(cx, length - 1);
+
+ list->setDenseInitializedLength(length - 1);
+
+ return entry;
+}
+
+class ByteStreamChunk : public NativeObject
+{
+ private:
+ enum Slots {
+ Slot_Buffer = 0,
+ Slot_ByteOffset,
+ Slot_ByteLength,
+ SlotCount
+ };
+
+ public:
+ static const Class class_;
+
+ ArrayBufferObject* buffer() {
+ return &getFixedSlot(Slot_Buffer).toObject().as<ArrayBufferObject>();
+ }
+ uint32_t byteOffset() { return getFixedSlot(Slot_ByteOffset).toInt32(); }
+ void SetByteOffset(uint32_t offset) {
+ setFixedSlot(Slot_ByteOffset, Int32Value(offset));
+ }
+ uint32_t byteLength() { return getFixedSlot(Slot_ByteLength).toInt32(); }
+ void SetByteLength(uint32_t length) {
+ setFixedSlot(Slot_ByteLength, Int32Value(length));
+ }
+
+ static ByteStreamChunk* create(JSContext* cx, HandleObject buffer, uint32_t byteOffset,
+ uint32_t byteLength)
+ {
+ Rooted<ByteStreamChunk*> chunk(cx, NewObjectWithClassProto<ByteStreamChunk>(cx));
+ if (!chunk)
+ return nullptr;
+
+ chunk->setFixedSlot(Slot_Buffer, ObjectValue(*buffer));
+ chunk->setFixedSlot(Slot_ByteOffset, Int32Value(byteOffset));
+ chunk->setFixedSlot(Slot_ByteLength, Int32Value(byteLength));
+ return chunk;
+ }
+};
+
+const Class ByteStreamChunk::class_ = {
+ "ByteStreamChunk",
+ JSCLASS_HAS_RESERVED_SLOTS(SlotCount)
+};
+
+class PullIntoDescriptor : public NativeObject
+{
+ private:
+ enum Slots {
+ Slot_buffer,
+ Slot_ByteOffset,
+ Slot_ByteLength,
+ Slot_BytesFilled,
+ Slot_ElementSize,
+ Slot_Ctor,
+ Slot_ReaderType,
+ SlotCount
+ };
+ public:
+ static const Class class_;
+
+ ArrayBufferObject* buffer() {
+ return &getFixedSlot(Slot_buffer).toObject().as<ArrayBufferObject>();
+ }
+ void setBuffer(ArrayBufferObject* buffer) { setFixedSlot(Slot_buffer, ObjectValue(*buffer)); }
+ JSObject* ctor() { return getFixedSlot(Slot_Ctor).toObjectOrNull(); }
+ uint32_t byteOffset() const { return getFixedSlot(Slot_ByteOffset).toInt32(); }
+ uint32_t byteLength() const { return getFixedSlot(Slot_ByteLength).toInt32(); }
+ uint32_t bytesFilled() const { return getFixedSlot(Slot_BytesFilled).toInt32(); }
+ void setBytesFilled(int32_t bytes) { setFixedSlot(Slot_BytesFilled, Int32Value(bytes)); }
+ uint32_t elementSize() const { return getFixedSlot(Slot_ElementSize).toInt32(); }
+ uint32_t readerType() const { return getFixedSlot(Slot_ReaderType).toInt32(); }
+
+ static PullIntoDescriptor* create(JSContext* cx, HandleArrayBufferObject buffer,
+ uint32_t byteOffset, uint32_t byteLength,
+ uint32_t bytesFilled, uint32_t elementSize,
+ HandleObject ctor, uint32_t readerType)
+ {
+ Rooted<PullIntoDescriptor*> descriptor(cx, NewObjectWithClassProto<PullIntoDescriptor>(cx));
+ if (!descriptor)
+ return nullptr;
+
+ descriptor->setFixedSlot(Slot_buffer, ObjectValue(*buffer));
+ descriptor->setFixedSlot(Slot_Ctor, ObjectOrNullValue(ctor));
+ descriptor->setFixedSlot(Slot_ByteOffset, Int32Value(byteOffset));
+ descriptor->setFixedSlot(Slot_ByteLength, Int32Value(byteLength));
+ descriptor->setFixedSlot(Slot_BytesFilled, Int32Value(bytesFilled));
+ descriptor->setFixedSlot(Slot_ElementSize, Int32Value(elementSize));
+ descriptor->setFixedSlot(Slot_ReaderType, Int32Value(readerType));
+ return descriptor;
+ }
+};
+
+const Class PullIntoDescriptor::class_ = {
+ "PullIntoDescriptor",
+ JSCLASS_HAS_RESERVED_SLOTS(SlotCount)
+};
+
+class QueueEntry : public NativeObject
+{
+ private:
+ enum Slots {
+ Slot_Value = 0,
+ Slot_Size,
+ SlotCount
+ };
+
+ public:
+ static const Class class_;
+
+ Value value() { return getFixedSlot(Slot_Value); }
+ double size() { return getFixedSlot(Slot_Size).toNumber(); }
+
+ static QueueEntry* create(JSContext* cx, HandleValue value, double size)
+ {
+ Rooted<QueueEntry*> entry(cx, NewObjectWithClassProto<QueueEntry>(cx));
+ if (!entry)
+ return nullptr;
+
+ entry->setFixedSlot(Slot_Value, value);
+ entry->setFixedSlot(Slot_Size, NumberValue(size));
+
+ return entry;
+ }
+};
+
+const Class QueueEntry::class_ = {
+ "QueueEntry",
+ JSCLASS_HAS_RESERVED_SLOTS(SlotCount)
+};
+
+class TeeState : public NativeObject
+{
+ private:
+ enum Slots {
+ Slot_Flags = 0,
+ Slot_Reason1,
+ Slot_Reason2,
+ Slot_Promise,
+ Slot_Stream,
+ Slot_Branch1,
+ Slot_Branch2,
+ SlotCount
+ };
+
+ enum Flags
+ {
+ Flag_ClosedOrErrored = 1 << 0,
+ Flag_Canceled1 = 1 << 1,
+ Flag_Canceled2 = 1 << 2,
+ Flag_CloneForBranch2 = 1 << 3,
+ };
+ uint32_t flags() const { return getFixedSlot(Slot_Flags).toInt32(); }
+ void setFlags(uint32_t flags) { setFixedSlot(Slot_Flags, Int32Value(flags)); }
+
+ public:
+ static const Class class_;
+
+ bool cloneForBranch2() const { return flags() & Flag_CloneForBranch2; }
+
+ bool closedOrErrored() const { return flags() & Flag_ClosedOrErrored; }
+ void setClosedOrErrored() {
+ MOZ_ASSERT(!(flags() & Flag_ClosedOrErrored));
+ setFlags(flags() | Flag_ClosedOrErrored);
+ }
+
+ bool canceled1() const { return flags() & Flag_Canceled1; }
+ void setCanceled1(HandleValue reason) {
+ MOZ_ASSERT(!(flags() & Flag_Canceled1));
+ setFlags(flags() | Flag_Canceled1);
+ setFixedSlot(Slot_Reason1, reason);
+ }
+
+ bool canceled2() const { return flags() & Flag_Canceled2; }
+ void setCanceled2(HandleValue reason) {
+ MOZ_ASSERT(!(flags() & Flag_Canceled2));
+ setFlags(flags() | Flag_Canceled2);
+ setFixedSlot(Slot_Reason2, reason);
+ }
+
+ Value reason1() const {
+ MOZ_ASSERT(canceled1());
+ return getFixedSlot(Slot_Reason1);
+ }
+
+ Value reason2() const {
+ MOZ_ASSERT(canceled2());
+ return getFixedSlot(Slot_Reason2);
+ }
+
+ PromiseObject* promise() {
+ return &getFixedSlot(Slot_Promise).toObject().as<PromiseObject>();
+ }
+ ReadableStream* stream() {
+ return &getFixedSlot(Slot_Stream).toObject().as<ReadableStream>();
+ }
+ ReadableStreamDefaultReader* reader() {
+ return &ReaderFromStream(stream())->as<ReadableStreamDefaultReader>();
+ }
+
+ ReadableStreamDefaultController* branch1() {
+ ReadableStreamDefaultController* controller = &getFixedSlot(Slot_Branch1).toObject()
+ .as<ReadableStreamDefaultController>();
+ MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch);
+ MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch1);
+ return controller;
+ }
+ void setBranch1(ReadableStreamDefaultController* controller) {
+ MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch);
+ MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch1);
+ setFixedSlot(Slot_Branch1, ObjectValue(*controller));
+ }
+
+ ReadableStreamDefaultController* branch2() {
+ ReadableStreamDefaultController* controller = &getFixedSlot(Slot_Branch2).toObject()
+ .as<ReadableStreamDefaultController>();
+ MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch);
+ MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch2);
+ return controller;
+ }
+ void setBranch2(ReadableStreamDefaultController* controller) {
+ MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch);
+ MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch2);
+ setFixedSlot(Slot_Branch2, ObjectValue(*controller));
+ }
+
+ static TeeState* create(JSContext* cx, Handle<ReadableStream*> stream) {
+ Rooted<TeeState*> state(cx, NewObjectWithClassProto<TeeState>(cx));
+ if (!state)
+ return nullptr;
+
+ Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
+ if (!promise)
+ return nullptr;
+
+ state->setFixedSlot(Slot_Flags, Int32Value(0));
+ state->setFixedSlot(Slot_Promise, ObjectValue(*promise));
+ state->setFixedSlot(Slot_Stream, ObjectValue(*stream));
+
+ return state;
+ }
+};
+
+const Class TeeState::class_ = {
+ "TeeState",
+ JSCLASS_HAS_RESERVED_SLOTS(SlotCount)
+};
+
+#define CLASS_SPEC(cls, nCtorArgs, nSlots, specFlags, classFlags, classOps) \
+const ClassSpec cls::classSpec_ = { \
+ GenericCreateConstructor<cls::constructor, nCtorArgs, gc::AllocKind::FUNCTION>, \
+ GenericCreatePrototype, \
+ nullptr, \
+ nullptr, \
+ cls##_methods, \
+ cls##_properties, \
+ nullptr, \
+ specFlags \
+}; \
+\
+const Class cls::class_ = { \
+ #cls, \
+ JSCLASS_HAS_RESERVED_SLOTS(nSlots) | \
+ JSCLASS_HAS_CACHED_PROTO(JSProto_##cls) | \
+ classFlags, \
+ classOps, \
+ &cls::classSpec_ \
+}; \
+\
+const Class cls::protoClass_ = { \
+ "object", \
+ JSCLASS_HAS_CACHED_PROTO(JSProto_##cls), \
+ JS_NULL_CLASS_OPS, \
+ &cls::classSpec_ \
+};
+
+// Streams spec, 3.2.3., steps 1-4.
+ReadableStream*
+ReadableStream::createStream(JSContext* cx, HandleObject proto /* = nullptr */)
+{
+ Rooted<ReadableStream*> stream(cx, NewObjectWithClassProto<ReadableStream>(cx, proto));
+ if (!stream)
+ return nullptr;
+
+ // Step 1: Set this.[[state]] to "readable".
+ // Step 2: Set this.[[reader]] and this.[[storedError]] to undefined (implicit).
+ // Step 3: Set this.[[disturbed]] to false (implicit).
+ // Step 4: Set this.[[readableStreamController]] to undefined (implicit).
+ stream->setFixedSlot(StreamSlot_State, Int32Value(Readable));
+
+ return stream;
+}
+
+static MOZ_MUST_USE ReadableStreamDefaultController*
+CreateReadableStreamDefaultController(JSContext* cx, Handle<ReadableStream*> stream,
+ HandleValue underlyingSource, HandleValue size,
+ HandleValue highWaterMarkVal);
+
+// Streams spec, 3.2.3., steps 1-4, 8.
+ReadableStream*
+ReadableStream::createDefaultStream(JSContext* cx, HandleValue underlyingSource,
+ HandleValue size, HandleValue highWaterMark,
+ HandleObject proto /* = nullptr */)
+{
+ // Steps 1-4.
+ Rooted<ReadableStream*> stream(cx, createStream(cx));
+ if (!stream)
+ return nullptr;
+
+ // Step 8.b: Set this.[[readableStreamController]] to
+ // ? Construct(ReadableStreamDefaultController,
+ // « this, underlyingSource, size,
+ // highWaterMark »).
+ RootedObject controller(cx, CreateReadableStreamDefaultController(cx, stream,
+ underlyingSource,
+ size,
+ highWaterMark));
+ if (!controller)
+ return nullptr;
+
+ stream->setFixedSlot(StreamSlot_Controller, ObjectValue(*controller));
+
+ return stream;
+}
+
+static MOZ_MUST_USE ReadableByteStreamController*
+CreateReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream,
+ HandleValue underlyingByteSource,
+ HandleValue highWaterMarkVal);
+
+// Streams spec, 3.2.3., steps 1-4, 7.
+ReadableStream*
+ReadableStream::createByteStream(JSContext* cx, HandleValue underlyingSource,
+ HandleValue highWaterMark, HandleObject proto /* = nullptr */)
+{
+ // Steps 1-4.
+ Rooted<ReadableStream*> stream(cx, createStream(cx, proto));
+ if (!stream)
+ return nullptr;
+
+ // Step 7.b: Set this.[[readableStreamController]] to
+ // ? Construct(ReadableByteStreamController,
+ // « this, underlyingSource, highWaterMark »).
+ RootedObject controller(cx, CreateReadableByteStreamController(cx, stream,
+ underlyingSource,
+ highWaterMark));
+ if (!controller)
+ return nullptr;
+
+ stream->setFixedSlot(StreamSlot_Controller, ObjectValue(*controller));
+
+ return stream;
+}
+
+static MOZ_MUST_USE ReadableByteStreamController*
+CreateReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream,
+ void* underlyingSource);
+
+ReadableStream*
+ReadableStream::createExternalSourceStream(JSContext* cx, void* underlyingSource,
+ uint8_t flags, HandleObject proto /* = nullptr */)
+{
+ Rooted<ReadableStream*> stream(cx, createStream(cx, proto));
+ if (!stream)
+ return nullptr;
+
+ RootedNativeObject controller(cx, CreateReadableByteStreamController(cx, stream,
+ underlyingSource));
+ if (!controller)
+ return nullptr;
+
+ stream->setFixedSlot(StreamSlot_Controller, ObjectValue(*controller));
+ AddControllerFlags(controller, flags << ControllerEmbeddingFlagsOffset);
+
+ return stream;
+}
+
+// Streams spec, 3.2.3.
+bool
+ReadableStream::constructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedValue val(cx, args.get(0));
+ RootedValue underlyingSource(cx, args.get(0));
+ RootedValue options(cx, args.get(1));
+
+ // Do argument handling first to keep the right order of error reporting.
+ if (underlyingSource.isUndefined()) {
+ RootedObject sourceObj(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!sourceObj)
+ return false;
+ underlyingSource = ObjectValue(*sourceObj);
+ }
+ RootedValue size(cx);
+ RootedValue highWaterMark(cx);
+
+ if (!options.isUndefined()) {
+ if (!GetProperty(cx, options, cx->names().size, &size))
+ return false;
+
+ if (!GetProperty(cx, options, cx->names().highWaterMark, &highWaterMark))
+ return false;
+ }
+
+ if (!ThrowIfNotConstructing(cx, args, "ReadableStream"))
+ return false;
+
+ // Step 5: Let type be ? GetV(underlyingSource, "type").
+ RootedValue typeVal(cx);
+ if (!GetProperty(cx, underlyingSource, cx->names().type, &typeVal))
+ return false;
+
+ // Step 6: Let typeString be ? ToString(type).
+ RootedString type(cx, ToString<CanGC>(cx, typeVal));
+ if (!type)
+ return false;
+
+ int32_t notByteStream;
+ if (!CompareStrings(cx, type, cx->names().bytes, &notByteStream))
+ return false;
+
+ // Step 7.a & 8.a (reordered): If highWaterMark is undefined, let
+ // highWaterMark be 1 (or 0 for byte streams).
+ if (highWaterMark.isUndefined())
+ highWaterMark = Int32Value(notByteStream ? 1 : 0);
+
+ Rooted<ReadableStream*> stream(cx);
+
+ // Step 7: If typeString is "bytes",
+ if (!notByteStream) {
+ // Step 7.b: Set this.[[readableStreamController]] to
+ // ? Construct(ReadableByteStreamController,
+ // « this, underlyingSource, highWaterMark »).
+ stream = createByteStream(cx, underlyingSource, highWaterMark);
+ } else if (typeVal.isUndefined()) {
+ // Step 8: Otherwise, if type is undefined,
+ // Step 8.b: Set this.[[readableStreamController]] to
+ // ? Construct(ReadableStreamDefaultController,
+ // « this, underlyingSource, size, highWaterMark »).
+ stream = createDefaultStream(cx, underlyingSource, size, highWaterMark);
+ } else {
+ // Step 9: Otherwise, throw a RangeError exception.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_UNDERLYINGSOURCE_TYPE_WRONG);
+ return false;
+ }
+ if (!stream)
+ return false;
+
+ args.rval().setObject(*stream);
+ return true;
+}
+
+// Streams spec, 3.2.4.1. get locked
+static MOZ_MUST_USE bool
+ReadableStream_locked_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStream*> stream(cx, &args.thisv().toObject().as<ReadableStream>());
+
+ // Step 2: Return ! IsReadableStreamLocked(this).
+ args.rval().setBoolean(stream->locked());
+ return true;
+}
+
+static bool
+ReadableStream_locked(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStream>, ReadableStream_locked_impl>(cx, args);
+}
+
+// Streams spec, 3.2.4.2. cancel ( reason )
+static MOZ_MUST_USE bool
+ReadableStream_cancel(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ // Step 1: If ! IsReadableStream(this) is false, return a promise rejected
+ // with a TypeError exception.
+ if (!Is<ReadableStream>(args.thisv())) {
+ ReportValueError3(cx, JSMSG_INCOMPATIBLE_PROTO, JSDVG_SEARCH_STACK, args.thisv(),
+ nullptr, "cancel", "");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ Rooted<ReadableStream*> stream(cx, &args.thisv().toObject().as<ReadableStream>());
+
+ // Step 2: If ! IsReadableStreamLocked(this) is true, return a promise
+ // rejected with a TypeError exception.
+ if (stream->locked()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_NOT_LOCKED, "cancel");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 3: Return ! ReadableStreamCancel(this, reason).
+ RootedObject cancelPromise(cx, ReadableStream::cancel(cx, stream, args.get(0)));
+ if (!cancelPromise)
+ return false;
+ args.rval().setObject(*cancelPromise);
+ return true;
+}
+
+static MOZ_MUST_USE ReadableStreamDefaultReader*
+CreateReadableStreamDefaultReader(JSContext* cx, Handle<ReadableStream*> stream);
+
+static MOZ_MUST_USE ReadableStreamBYOBReader*
+CreateReadableStreamBYOBReader(JSContext* cx, Handle<ReadableStream*> stream);
+
+// Streams spec, 3.2.4.3. getReader()
+static MOZ_MUST_USE bool
+ReadableStream_getReader_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStream*> stream(cx, &args.thisv().toObject().as<ReadableStream>());
+ RootedObject reader(cx);
+
+ // Step 2: If mode is undefined, return
+ // ? AcquireReadableStreamDefaultReader(this).
+ RootedValue modeVal(cx);
+ HandleValue optionsVal = args.get(0);
+ if (!optionsVal.isUndefined()) {
+ if (!GetProperty(cx, optionsVal, cx->names().mode, &modeVal))
+ return false;
+ }
+
+ if (modeVal.isUndefined()) {
+ reader = CreateReadableStreamDefaultReader(cx, stream);
+ } else {
+ // Step 3: Set mode to ? ToString(mode) (implicit).
+ RootedString mode(cx, ToString<CanGC>(cx, modeVal));
+ if (!mode)
+ return false;
+
+ // Step 4: If mode is "byob", return ? AcquireReadableStreamBYOBReader(this).
+ int32_t notByob;
+ if (!CompareStrings(cx, mode, cx->names().byob, &notByob))
+ return false;
+ if (notByob) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_INVALID_READER_MODE);
+ // Step 5: Throw a RangeError exception.
+ return false;
+
+ }
+ reader = CreateReadableStreamBYOBReader(cx, stream);
+ }
+
+ // Reordered second part of steps 2 and 4.
+ if (!reader)
+ return false;
+ args.rval().setObject(*reader);
+ return true;
+}
+
+static bool
+ReadableStream_getReader(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStream>, ReadableStream_getReader_impl>(cx, args);
+}
+
+// Streams spec, 3.2.4.4. pipeThrough({ writable, readable }, options)
+static MOZ_MUST_USE bool
+ReadableStream_pipeThrough(JSContext* cx, unsigned argc, Value* vp)
+{
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, "pipeThrough");
+ return false;
+ // // Step 1: Perform ? Invoke(this, "pipeTo", « writable, options »).
+
+ // // Step 2: Return readable.
+ // return readable;
+}
+
+// Streams spec, 3.2.4.5. pipeTo(dest, { preventClose, preventAbort, preventCancel } = {})
+// TODO: Unimplemented since spec is not complete yet.
+static MOZ_MUST_USE bool
+ReadableStream_pipeTo(JSContext* cx, unsigned argc, Value* vp)
+{
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, "pipeTo");
+ return false;
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamTee(JSContext* cx, Handle<ReadableStream*> stream, bool cloneForBranch2,
+ MutableHandle<ReadableStream*> branch1, MutableHandle<ReadableStream*> branch2);
+
+// Streams spec, 3.2.4.6. tee()
+static MOZ_MUST_USE bool
+ReadableStream_tee_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStream*> stream(cx, &args.thisv().toObject().as<ReadableStream>());
+
+ // Step 2: Let branches be ? ReadableStreamTee(this, false).
+ Rooted<ReadableStream*> branch1(cx);
+ Rooted<ReadableStream*> branch2(cx);
+ if (!ReadableStreamTee(cx, stream, false, &branch1, &branch2))
+ return false;
+
+ // Step 3: Return ! CreateArrayFromList(branches).
+ RootedNativeObject branches(cx, NewDenseFullyAllocatedArray(cx, 2));
+ if (!branches)
+ return false;
+ branches->setDenseInitializedLength(2);
+ branches->initDenseElement(0, ObjectValue(*branch1));
+ branches->initDenseElement(1, ObjectValue(*branch2));
+
+ args.rval().setObject(*branches);
+ return true;
+}
+
+static bool
+ReadableStream_tee(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStream>, ReadableStream_tee_impl>(cx, args);
+}
+
+static const JSFunctionSpec ReadableStream_methods[] = {
+ JS_FN("cancel", ReadableStream_cancel, 1, 0),
+ JS_FN("getReader", ReadableStream_getReader, 0, 0),
+ JS_FN("pipeThrough", ReadableStream_pipeThrough, 2, 0),
+ JS_FN("pipeTo", ReadableStream_pipeTo, 1, 0),
+ JS_FN("tee", ReadableStream_tee, 0, 0),
+ JS_FS_END
+};
+
+static const JSPropertySpec ReadableStream_properties[] = {
+ JS_PSG("locked", ReadableStream_locked, 0),
+ JS_PS_END
+};
+
+CLASS_SPEC(ReadableStream, 0, StreamSlotCount, 0, 0, JS_NULL_CLASS_OPS);
+
+// Streams spec, 3.3.1. AcquireReadableStreamBYOBReader ( stream )
+// Always inlined.
+
+// Streams spec, 3.3.2. AcquireReadableStreamDefaultReader ( stream )
+// Always inlined.
+
+// Streams spec, 3.3.3. IsReadableStream ( x )
+// Using is<T> instead.
+
+// Streams spec, 3.3.4. IsReadableStreamDisturbed ( stream )
+// Using stream->disturbed() instead.
+
+// Streams spec, 3.3.5. IsReadableStreamLocked ( stream )
+bool
+ReadableStream::locked() const
+{
+ // Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
+ // Step 2: If stream.[[reader]] is undefined, return false.
+ // Step 3: Return true.
+ // Special-casing for streams with external sources. Those can be locked
+ // explicitly via JSAPI, which is indicated by a controller flag.
+ // IsReadableStreamLocked is called from the controller's constructor, at
+ // which point we can't yet call ControllerFromStream(stream), but the
+ // source also can't be locked yet.
+ if (HasController(this) &&
+ (ControllerFlags(ControllerFromStream(this)) & ControllerFlag_SourceLocked))
+ {
+ return true;
+ }
+ return HasReader(this);
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamDefaultControllerClose(JSContext* cx,
+ Handle<ReadableStreamDefaultController*> controller);
+static MOZ_MUST_USE bool
+ReadableStreamDefaultControllerEnqueue(JSContext* cx,
+ Handle<ReadableStreamDefaultController*> controller,
+ HandleValue chunk);
+
+static bool
+TeeReaderReadHandler(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ Rooted<TeeState*> teeState(cx, TargetFromHandler<TeeState>(args.callee()));
+ HandleValue resultVal = args.get(0);
+
+ // Step a: Assert: Type(result) is Object.
+ RootedObject result(cx, &resultVal.toObject());
+
+ // Step b: Let value be ? Get(result, "value").
+ RootedValue value(cx);
+ if (!GetPropertyPure(cx, result, NameToId(cx->names().value), value.address()))
+ return false;
+
+ // Step c: Let done be ? Get(result, "done").
+ RootedValue doneVal(cx);
+ if (!GetPropertyPure(cx, result, NameToId(cx->names().done), doneVal.address()))
+ return false;
+
+ // Step d: Assert: Type(done) is Boolean.
+ bool done = doneVal.toBoolean();
+
+ // Step e: If done is true and teeState.[[closedOrErrored]] is false,
+ if (done && !teeState->closedOrErrored()) {
+ // Step i: If teeState.[[canceled1]] is false,
+ if (!teeState->canceled1()) {
+ // Step 1: Perform ! ReadableStreamDefaultControllerClose(branch1).
+ Rooted<ReadableStreamDefaultController*> branch1(cx, teeState->branch1());
+ if (!ReadableStreamDefaultControllerClose(cx, branch1))
+ return false;
+ }
+
+ // Step ii: If teeState.[[canceled2]] is false,
+ if (!teeState->canceled2()) {
+ // Step 1: Perform ! ReadableStreamDefaultControllerClose(branch1).
+ Rooted<ReadableStreamDefaultController*> branch2(cx, teeState->branch2());
+ if (!ReadableStreamDefaultControllerClose(cx, branch2))
+ return false;
+ }
+
+ // Step iii: Set teeState.[[closedOrErrored]] to true.
+ teeState->setClosedOrErrored();
+ }
+
+ // Step f: If teeState.[[closedOrErrored]] is true, return.
+ if (teeState->closedOrErrored())
+ return true;
+
+ // Step g: Let value1 and value2 be value.
+ RootedValue value1(cx, value);
+ RootedValue value2(cx, value);
+
+ // Step h: If teeState.[[canceled2]] is false and cloneForBranch2 is
+ // true, set value2 to
+ // ? StructuredDeserialize(StructuredSerialize(value2),
+ // the current Realm Record).
+ // TODO: add StructuredClone() intrinsic.
+ MOZ_ASSERT(!teeState->cloneForBranch2(), "tee(cloneForBranch2=true) should not be exposed");
+
+ // Step i: If teeState.[[canceled1]] is false, perform
+ // ? ReadableStreamDefaultControllerEnqueue(branch1, value1).
+ Rooted<ReadableStreamDefaultController*> controller(cx);
+ if (!teeState->canceled1()) {
+ controller = teeState->branch1();
+ if (!ReadableStreamDefaultControllerEnqueue(cx, controller, value1))
+ return false;
+ }
+
+ // Step j: If teeState.[[canceled2]] is false,
+ // perform ? ReadableStreamDefaultControllerEnqueue(branch2, value2).
+ if (!teeState->canceled2()) {
+ controller = teeState->branch2();
+ if (!ReadableStreamDefaultControllerEnqueue(cx, controller, value2))
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static MOZ_MUST_USE JSObject*
+ReadableStreamTee_Pull(JSContext* cx, Handle<TeeState*> teeState,
+ Handle<ReadableStream*> branchStream)
+{
+ // Step 1: Let reader be F.[[reader]], branch1 be F.[[branch1]],
+ // branch2 be F.[[branch2]], teeState be F.[[teeState]], and
+ // cloneForBranch2 be F.[[cloneForBranch2]].
+
+ // Step 2: Return the result of transforming
+ // ! ReadableStreamDefaultReaderRead(reader) by a fulfillment
+ // handler which takes the argument result and performs the
+ // following steps:
+ Rooted<ReadableStreamDefaultReader*> reader(cx, teeState->reader());
+ RootedObject readPromise(cx, ReadableStreamDefaultReader::read(cx, reader));
+ if (!readPromise)
+ return nullptr;
+
+ RootedObject onFulfilled(cx, NewHandler(cx, TeeReaderReadHandler, teeState));
+ if (!onFulfilled)
+ return nullptr;
+
+ return JS::CallOriginalPromiseThen(cx, readPromise, onFulfilled, nullptr);
+}
+
+static MOZ_MUST_USE JSObject*
+ReadableStreamTee_Cancel(JSContext* cx, Handle<TeeState*> teeState,
+ Handle<ReadableStreamDefaultController*> branch, HandleValue reason)
+{
+ // Step 1: Let stream be F.[[stream]] and teeState be F.[[teeState]].
+ Rooted<ReadableStream*> stream(cx, teeState->stream());
+
+ bool bothBranchesCanceled = false;
+
+ // Step 2: Set teeState.[[canceled1]] to true.
+ // Step 3: Set teeState.[[reason1]] to reason.
+ if (ControllerFlags(branch) & ControllerFlag_TeeBranch1) {
+ teeState->setCanceled1(reason);
+ bothBranchesCanceled = teeState->canceled2();
+ } else {
+ MOZ_ASSERT(ControllerFlags(branch) & ControllerFlag_TeeBranch2);
+ teeState->setCanceled2(reason);
+ bothBranchesCanceled = teeState->canceled1();
+ }
+
+ // Step 4: If teeState.[[canceled1]] is true,
+ // Step 4: If teeState.[[canceled2]] is true,
+ if (bothBranchesCanceled) {
+ // Step a: Let compositeReason be
+ // ! CreateArrayFromList(« teeState.[[reason1]], teeState.[[reason2]] »).
+ RootedNativeObject compositeReason(cx, NewDenseFullyAllocatedArray(cx, 2));
+ if (!compositeReason)
+ return nullptr;
+
+ compositeReason->setDenseInitializedLength(2);
+ compositeReason->initDenseElement(0, teeState->reason1());
+ compositeReason->initDenseElement(1, teeState->reason2());
+ RootedValue compositeReasonVal(cx, ObjectValue(*compositeReason));
+
+ Rooted<PromiseObject*> promise(cx, teeState->promise());
+
+ // Step b: Let cancelResult be ! ReadableStreamCancel(stream, compositeReason).
+ RootedObject cancelResult(cx, ReadableStream::cancel(cx, stream, compositeReasonVal));
+ if (!cancelResult) {
+ if (!RejectPromiseWithPendingError(cx, promise))
+ return nullptr;
+ } else {
+ // Step c: Resolve teeState.[[promise]] with cancelResult.
+ RootedValue resultVal(cx, ObjectValue(*cancelResult));
+ if (!PromiseObject::resolve(cx, promise, resultVal))
+ return nullptr;
+ }
+ }
+
+ // Step 5: Return teeState.[[promise]].
+ return teeState->promise();
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamControllerError(JSContext* cx, HandleNativeObject controller, HandleValue e);
+
+// Streams spec, 3.3.6. step 21:
+// Upon rejection of reader.[[closedPromise]] with reason r,
+static bool
+TeeReaderClosedHandler(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ Rooted<TeeState*> teeState(cx, TargetFromHandler<TeeState>(args.callee()));
+ HandleValue reason = args.get(0);
+
+ // Step a: If teeState.[[closedOrErrored]] is false, then:
+ if (!teeState->closedOrErrored()) {
+ // Step a.i: Perform ! ReadableStreamDefaultControllerError(pull.[[branch1]], r).
+ Rooted<ReadableStreamDefaultController*> branch1(cx, teeState->branch1());
+ if (!ReadableStreamControllerError(cx, branch1, reason))
+ return false;
+
+ // Step a.ii: Perform ! ReadableStreamDefaultControllerError(pull.[[branch2]], r).
+ Rooted<ReadableStreamDefaultController*> branch2(cx, teeState->branch2());
+ if (!ReadableStreamControllerError(cx, branch2, reason))
+ return false;
+
+ // Step a.iii: Set teeState.[[closedOrErrored]] to true.
+ teeState->setClosedOrErrored();
+ }
+
+ return true;
+}
+
+// Streams spec, 3.3.6. ReadableStreamTee ( stream, cloneForBranch2 )
+static MOZ_MUST_USE bool
+ReadableStreamTee(JSContext* cx, Handle<ReadableStream*> stream, bool cloneForBranch2,
+ MutableHandle<ReadableStream*> branch1Stream,
+ MutableHandle<ReadableStream*> branch2Stream)
+{
+ // Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
+ // Step 2: Assert: Type(cloneForBranch2) is Boolean (implicit).
+
+ // Step 3: Let reader be ? AcquireReadableStreamDefaultReader(stream).
+ Rooted<ReadableStreamDefaultReader*> reader(cx, CreateReadableStreamDefaultReader(cx, stream));
+ if (!reader)
+ return false;
+
+ // Step 4: Let teeState be Record {[[closedOrErrored]]: false,
+ // [[canceled1]]: false,
+ // [[canceled2]]: false,
+ // [[reason1]]: undefined,
+ // [[reason2]]: undefined,
+ // [[promise]]: a new promise}.
+ Rooted<TeeState*> teeState(cx, TeeState::create(cx, stream));
+ if (!teeState)
+ return false;
+
+ // Steps 5-10 omitted because our implementation works differently.
+
+ // Step 5: Let pull be a new ReadableStreamTee pull function.
+ // Step 6: Set pull.[[reader]] to reader, pull.[[teeState]] to teeState, and
+ // pull.[[cloneForBranch2]] to cloneForBranch2.
+ // Step 7: Let cancel1 be a new ReadableStreamTee branch 1 cancel function.
+ // Step 8: Set cancel1.[[stream]] to stream and cancel1.[[teeState]] to
+ // teeState.
+
+ // Step 9: Let cancel2 be a new ReadableStreamTee branch 2 cancel function.
+ // Step 10: Set cancel2.[[stream]] to stream and cancel2.[[teeState]] to
+ // teeState.
+
+ // Step 11: Let underlyingSource1 be ! ObjectCreate(%ObjectPrototype%).
+ // Step 12: Perform ! CreateDataProperty(underlyingSource1, "pull", pull).
+ // Step 13: Perform ! CreateDataProperty(underlyingSource1, "cancel", cancel1).
+
+ // Step 14: Let branch1Stream be ! Construct(ReadableStream, underlyingSource1).
+ RootedValue hwmValue(cx, NumberValue(1));
+ RootedValue underlyingSource(cx, ObjectValue(*teeState));
+ branch1Stream.set(ReadableStream::createDefaultStream(cx, underlyingSource,
+ UndefinedHandleValue,
+ hwmValue));
+ if (!branch1Stream)
+ return false;
+
+ Rooted<ReadableStreamDefaultController*> branch1(cx);
+ branch1 = &ControllerFromStream(branch1Stream)->as<ReadableStreamDefaultController>();
+ AddControllerFlags(branch1, ControllerFlag_TeeBranch | ControllerFlag_TeeBranch1);
+ teeState->setBranch1(branch1);
+
+ // Step 15: Let underlyingSource2 be ! ObjectCreate(%ObjectPrototype%).
+ // Step 16: Perform ! CreateDataProperty(underlyingSource2, "pull", pull).
+ // Step 17: Perform ! CreateDataProperty(underlyingSource2, "cancel", cancel2).
+
+ // Step 18: Let branch2Stream be ! Construct(ReadableStream, underlyingSource2).
+ branch2Stream.set(ReadableStream::createDefaultStream(cx, underlyingSource,
+ UndefinedHandleValue,
+ hwmValue));
+ if (!branch2Stream)
+ return false;
+
+ Rooted<ReadableStreamDefaultController*> branch2(cx);
+ branch2 = &ControllerFromStream(branch2Stream)->as<ReadableStreamDefaultController>();
+ AddControllerFlags(branch2, ControllerFlag_TeeBranch | ControllerFlag_TeeBranch2);
+ teeState->setBranch2(branch2);
+
+ // Step 19: Set pull.[[branch1]] to branch1Stream.[[readableStreamController]].
+ // Step 20: Set pull.[[branch2]] to branch2Stream.[[readableStreamController]].
+
+ // Step 21: Upon rejection of reader.[[closedPromise]] with reason r,
+ RootedObject closedPromise(cx, &reader->getFixedSlot(ReaderSlot_ClosedPromise).toObject());
+
+ RootedObject onRejected(cx, NewHandler(cx, TeeReaderClosedHandler, teeState));
+ if (!onRejected)
+ return false;
+
+ if (!JS::AddPromiseReactions(cx, closedPromise, nullptr, onRejected))
+ return false;
+
+ // Step 22: Return « branch1, branch2 ».
+ return true;
+}
+
+// Streams spec, 3.4.1. ReadableStreamAddReadIntoRequest ( stream )
+static MOZ_MUST_USE PromiseObject*
+ReadableStreamAddReadIntoRequest(JSContext* cx, Handle<ReadableStream*> stream)
+{
+ // Step 1: MOZ_ASSERT: ! IsReadableStreamBYOBReader(stream.[[reader]]) is true.
+ RootedValue val(cx, stream->getFixedSlot(StreamSlot_Reader));
+ RootedNativeObject reader(cx, &val.toObject().as<ReadableStreamBYOBReader>());
+
+ // Step 2: MOZ_ASSERT: stream.[[state]] is "readable" or "closed".
+ MOZ_ASSERT(stream->readable() || stream->closed());
+
+ // Step 3: Let promise be a new promise.
+ Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
+ if (!promise)
+ return nullptr;
+
+ // Step 4: Let readIntoRequest be Record {[[promise]]: promise}.
+ // Step 5: Append readIntoRequest as the last element of stream.[[reader]].[[readIntoRequests]].
+ val = reader->getFixedSlot(ReaderSlot_Requests);
+ RootedNativeObject readIntoRequests(cx, &val.toObject().as<NativeObject>());
+ // Since [[promise]] is the Record's only field, we store it directly.
+ val = ObjectValue(*promise);
+ if (!AppendToList(cx, readIntoRequests, val))
+ return nullptr;
+
+ // Step 6: Return promise.
+ return promise;
+}
+
+// Streams spec, 3.4.2. ReadableStreamAddReadRequest ( stream )
+static MOZ_MUST_USE PromiseObject*
+ReadableStreamAddReadRequest(JSContext* cx, Handle<ReadableStream*> stream)
+{
+ MOZ_ASSERT(stream->is<ReadableStream>());
+
+ // Step 1: Assert: ! IsReadableStreamDefaultReader(stream.[[reader]]) is true.
+ RootedNativeObject reader(cx, ReaderFromStream(stream));
+
+ // Step 2: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->readable());
+
+ // Step 3: Let promise be a new promise.
+ Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
+ if (!promise)
+ return nullptr;
+
+ // Step 4: Let readRequest be Record {[[promise]]: promise}.
+ // Step 5: Append readRequest as the last element of stream.[[reader]].[[readRequests]].
+ RootedValue val(cx, reader->getFixedSlot(ReaderSlot_Requests));
+ RootedNativeObject readRequests(cx, &val.toObject().as<NativeObject>());
+
+ // Since [[promise]] is the Record's only field, we store it directly.
+ val = ObjectValue(*promise);
+ if (!AppendToList(cx, readRequests, val))
+ return nullptr;
+
+ // Step 6: Return promise.
+ return promise;
+}
+
+static MOZ_MUST_USE JSObject*
+ReadableStreamControllerCancelSteps(JSContext* cx,
+ HandleNativeObject controller, HandleValue reason);
+
+// Used for transforming the result of promise fulfillment/rejection.
+static bool
+ReturnUndefined(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setUndefined();
+ return true;
+}
+
+MOZ_MUST_USE bool
+ReadableStreamCloseInternal(JSContext* cx, Handle<ReadableStream*> stream);
+
+// Streams spec, 3.4.3. ReadableStreamCancel ( stream, reason )
+/* static */ MOZ_MUST_USE JSObject*
+ReadableStream::cancel(JSContext* cx, Handle<ReadableStream*> stream, HandleValue reason)
+{
+ // Step 1: Set stream.[[disturbed]] to true.
+ uint32_t state = StreamState(stream) | ReadableStream::Disturbed;
+ SetStreamState(stream, state);
+
+ // Step 2: If stream.[[state]] is "closed", return a new promise resolved
+ // with undefined.
+ if (stream->closed())
+ return PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
+
+ // Step 3: If stream.[[state]] is "errored", return a new promise rejected
+ // with stream.[[storedError]].
+ if (stream->errored()) {
+ RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError));
+ return PromiseObject::unforgeableReject(cx, storedError);
+ }
+
+ // Step 4: Perform ! ReadableStreamClose(stream).
+ if (!ReadableStreamCloseInternal(cx, stream))
+ return nullptr;
+
+ // Step 5: Let sourceCancelPromise be
+ // ! stream.[[readableStreamController]].[[CancelSteps]](reason).
+ RootedNativeObject controller(cx, ControllerFromStream(stream));
+ RootedObject sourceCancelPromise(cx);
+ sourceCancelPromise = ReadableStreamControllerCancelSteps(cx, controller, reason);
+ if (!sourceCancelPromise)
+ return nullptr;
+
+ // Step 6: Return the result of transforming sourceCancelPromise by a
+ // fulfillment handler that returns undefined.
+ RootedAtom funName(cx, cx->names().empty);
+ RootedFunction returnUndefined(cx, NewNativeFunction(cx, ReturnUndefined, 0, funName));
+ if (!returnUndefined)
+ return nullptr;
+ return JS::CallOriginalPromiseThen(cx, sourceCancelPromise, returnUndefined, nullptr);
+}
+
+// Streams spec, 3.4.4. ReadableStreamClose ( stream )
+MOZ_MUST_USE bool
+ReadableStreamCloseInternal(JSContext* cx, Handle<ReadableStream*> stream)
+{
+ // Step 1: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->readable());
+
+ uint32_t state = StreamState(stream);
+ // Step 2: Set stream.[[state]] to "closed".
+ SetStreamState(stream, (state & ReadableStream::Disturbed) | ReadableStream::Closed);
+
+ // Step 3: Let reader be stream.[[reader]].
+ RootedValue val(cx, stream->getFixedSlot(StreamSlot_Reader));
+
+ // Step 4: If reader is undefined, return.
+ if (val.isUndefined())
+ return true;
+
+ // Step 5: If ! IsReadableStreamDefaultReader(reader) is true,
+ RootedNativeObject reader(cx, &val.toObject().as<NativeObject>());
+ if (reader->is<ReadableStreamDefaultReader>()) {
+ // Step a: Repeat for each readRequest that is an element of
+ // reader.[[readRequests]],
+ val = reader->getFixedSlot(ReaderSlot_Requests);
+ if (!val.isUndefined()) {
+ RootedNativeObject readRequests(cx, &val.toObject().as<NativeObject>());
+ uint32_t len = readRequests->getDenseInitializedLength();
+ RootedObject readRequest(cx);
+ RootedObject resultObj(cx);
+ RootedValue resultVal(cx);
+ for (uint32_t i = 0; i < len; i++) {
+ // Step i: Resolve readRequest.[[promise]] with
+ // ! CreateIterResultObject(undefined, true).
+ readRequest = &readRequests->getDenseElement(i).toObject();
+ resultObj = CreateIterResultObject(cx, UndefinedHandleValue, true);
+ if (!resultObj)
+ return false;
+ resultVal = ObjectValue(*resultObj);
+ if (!ResolvePromise(cx, readRequest, resultVal))
+ return false;
+ }
+
+ // Step b: Set reader.[[readRequests]] to an empty List.
+ reader->setFixedSlot(ReaderSlot_Requests, UndefinedValue());
+ }
+ }
+
+ // Step 6: Resolve reader.[[closedPromise]] with undefined.
+ // Step 7: Return (implicit).
+ RootedObject closedPromise(cx, &reader->getFixedSlot(ReaderSlot_ClosedPromise).toObject());
+ if (!ResolvePromise(cx, closedPromise, UndefinedHandleValue))
+ return false;
+
+ if (stream->mode() == JS::ReadableStreamMode::ExternalSource &&
+ cx->runtime()->readableStreamClosedCallback)
+ {
+ NativeObject* controller = ControllerFromStream(stream);
+ void* source = controller->getFixedSlot(ControllerSlot_UnderlyingSource).toPrivate();
+ cx->runtime()->readableStreamClosedCallback(cx, stream, source, stream->embeddingFlags());
+ }
+
+ return true;
+}
+
+// Streams spec, 3.4.5. ReadableStreamError ( stream, e )
+MOZ_MUST_USE bool
+ReadableStreamErrorInternal(JSContext* cx, Handle<ReadableStream*> stream, HandleValue e)
+{
+ // Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
+
+ // Step 2: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->readable());
+
+ // Step 3: Set stream.[[state]] to "errored".
+ uint32_t state = StreamState(stream);
+ SetStreamState(stream, (state & ReadableStream::Disturbed) | ReadableStream::Errored);
+
+ // Step 4: Set stream.[[storedError]] to e.
+ stream->setFixedSlot(StreamSlot_StoredError, e);
+
+ // Step 5: Let reader be stream.[[reader]].
+ RootedValue val(cx, stream->getFixedSlot(StreamSlot_Reader));
+
+ // Step 6: If reader is undefined, return.
+ if (val.isUndefined())
+ return true;
+ RootedNativeObject reader(cx, &val.toObject().as<NativeObject>());
+
+ // Steps 7,8: (Identical in our implementation.)
+ // Step a: Repeat for each readRequest that is an element of
+ // reader.[[readRequests]],
+ val = reader->getFixedSlot(ReaderSlot_Requests);
+ RootedNativeObject readRequests(cx, &val.toObject().as<NativeObject>());
+ Rooted<PromiseObject*> readRequest(cx);
+ uint32_t len = readRequests->getDenseInitializedLength();
+ for (uint32_t i = 0; i < len; i++) {
+ // Step i: Reject readRequest.[[promise]] with e.
+ val = readRequests->getDenseElement(i);
+ readRequest = &val.toObject().as<PromiseObject>();
+ if (!PromiseObject::reject(cx, readRequest, e))
+ return false;
+ }
+
+ // Step b: Set reader.[[readRequests]] to a new empty List.
+ if (!SetNewList(cx, reader, ReaderSlot_Requests))
+ return false;
+
+ // Step 9: Reject reader.[[closedPromise]] with e.
+ val = reader->getFixedSlot(ReaderSlot_ClosedPromise);
+ Rooted<PromiseObject*> closedPromise(cx, &val.toObject().as<PromiseObject>());
+ if (!PromiseObject::reject(cx, closedPromise, e))
+ return false;
+
+ if (stream->mode() == JS::ReadableStreamMode::ExternalSource &&
+ cx->runtime()->readableStreamErroredCallback)
+ {
+ NativeObject* controller = ControllerFromStream(stream);
+ void* source = controller->getFixedSlot(ControllerSlot_UnderlyingSource).toPrivate();
+ cx->runtime()->readableStreamErroredCallback(cx, stream, source,
+ stream->embeddingFlags(), e);
+ }
+
+ return true;
+}
+
+// Streams spec, 3.4.6. ReadableStreamFulfillReadIntoRequest( stream, chunk, done )
+// Streams spec, 3.4.7. ReadableStreamFulfillReadRequest ( stream, chunk, done )
+// These two spec functions are identical in our implementation.
+static MOZ_MUST_USE bool
+ReadableStreamFulfillReadOrReadIntoRequest(JSContext* cx, Handle<ReadableStream*> stream,
+ HandleValue chunk, bool done)
+{
+ // Step 1: Let reader be stream.[[reader]].
+ RootedValue val(cx, stream->getFixedSlot(StreamSlot_Reader));
+ RootedNativeObject reader(cx, &val.toObject().as<NativeObject>());
+
+ // Step 2: Let readIntoRequest be the first element of
+ // reader.[[readIntoRequests]].
+ // Step 3: Remove readIntoRequest from reader.[[readIntoRequests]], shifting
+ // all other elements downward (so that the second becomes the first,
+ // and so on).
+ val = reader->getFixedSlot(ReaderSlot_Requests);
+ RootedNativeObject readIntoRequests(cx, &val.toObject().as<NativeObject>());
+ Rooted<PromiseObject*> readIntoRequest(cx);
+ readIntoRequest = ShiftFromList<PromiseObject>(cx, readIntoRequests);
+ MOZ_ASSERT(readIntoRequest);
+
+ // Step 4: Resolve readIntoRequest.[[promise]] with
+ // ! CreateIterResultObject(chunk, done).
+ RootedObject iterResult(cx, CreateIterResultObject(cx, chunk, done));
+ if (!iterResult)
+ return false;
+ val = ObjectValue(*iterResult);
+ return PromiseObject::resolve(cx, readIntoRequest, val);
+}
+
+// Streams spec, 3.4.8. ReadableStreamGetNumReadIntoRequests ( stream )
+// Streams spec, 3.4.9. ReadableStreamGetNumReadRequests ( stream )
+// (Identical implementation.)
+static uint32_t
+ReadableStreamGetNumReadRequests(ReadableStream* stream)
+{
+ // Step 1: Return the number of elements in
+ // stream.[[reader]].[[readRequests]].
+ if (!HasReader(stream))
+ return 0;
+ NativeObject* reader = ReaderFromStream(stream);
+ Value readRequests = reader->getFixedSlot(ReaderSlot_Requests);
+ return readRequests.toObject().as<NativeObject>().getDenseInitializedLength();
+}
+
+// Stream spec 3.4.10. ReadableStreamHasBYOBReader ( stream )
+static MOZ_MUST_USE bool
+ReadableStreamHasBYOBReader(ReadableStream* stream)
+{
+ // Step 1: Let reader be stream.[[reader]].
+ // Step 2: If reader is undefined, return false.
+ // Step 3: If ! IsReadableStreamBYOBReader(reader) is false, return false.
+ // Step 4: Return true.
+ Value reader = stream->getFixedSlot(StreamSlot_Reader);
+ return reader.isObject() && reader.toObject().is<ReadableStreamBYOBReader>();
+}
+
+// Streap spec 3.4.11. ReadableStreamHasDefaultReader ( stream )
+static MOZ_MUST_USE bool
+ReadableStreamHasDefaultReader(ReadableStream* stream)
+{
+ // Step 1: Let reader be stream.[[reader]].
+ // Step 2: If reader is undefined, return false.
+ // Step 3: If ! ReadableStreamDefaultReader(reader) is false, return false.
+ // Step 4: Return true.
+ Value reader = stream->getFixedSlot(StreamSlot_Reader);
+ return reader.isObject() && reader.toObject().is<ReadableStreamDefaultReader>();
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamReaderGenericInitialize(JSContext* cx,
+ HandleNativeObject reader,
+ Handle<ReadableStream*> stream);
+
+// Stream spec, 3.5.3. new ReadableStreamDefaultReader ( stream )
+// Steps 2-4.
+static MOZ_MUST_USE ReadableStreamDefaultReader*
+CreateReadableStreamDefaultReader(JSContext* cx, Handle<ReadableStream*> stream)
+{
+ Rooted<ReadableStreamDefaultReader*> reader(cx);
+ reader = NewBuiltinClassInstance<ReadableStreamDefaultReader>(cx);
+ if (!reader)
+ return nullptr;
+
+ // Step 2: If ! IsReadableStreamLocked(stream) is true, throw a TypeError
+ // exception.
+ if (stream->locked()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_LOCKED);
+ return nullptr;
+ }
+
+ // Step 3: Perform ! ReadableStreamReaderGenericInitialize(this, stream).
+ if (!ReadableStreamReaderGenericInitialize(cx, reader, stream))
+ return nullptr;
+
+ // Step 4: Set this.[[readRequests]] to a new empty List.
+ if (!SetNewList(cx, reader, ReaderSlot_Requests))
+ return nullptr;
+
+ return reader;
+}
+
+// Stream spec, 3.5.3. new ReadableStreamDefaultReader ( stream )
+bool
+ReadableStreamDefaultReader::constructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "ReadableStreamDefaultReader"))
+ return false;
+
+ // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception.
+ if (!Is<ReadableStream>(args.get(0))) {
+ ReportArgTypeError(cx, "ReadableStreamDefaultReader", "ReadableStream",
+ args.get(0));
+ return false;
+ }
+
+ Rooted<ReadableStream*> stream(cx, &args.get(0).toObject().as<ReadableStream>());
+
+ RootedObject reader(cx, CreateReadableStreamDefaultReader(cx, stream));
+ if (!reader)
+ return false;
+
+ args.rval().setObject(*reader);
+ return true;
+}
+
+// Streams spec, 3.5.4.1 get closed
+static MOZ_MUST_USE bool
+ReadableStreamDefaultReader_closed(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
+ // rejected with a TypeError exception.
+ if (!Is<ReadableStreamDefaultReader>(args.thisv()))
+ return RejectNonGenericMethod(cx, args, "ReadableStreamDefaultReader", "get closed");
+
+ // Step 2: Return this.[[closedPromise]].
+ NativeObject* reader = &args.thisv().toObject().as<NativeObject>();
+ args.rval().set(reader->getFixedSlot(ReaderSlot_ClosedPromise));
+ return true;
+}
+
+static MOZ_MUST_USE JSObject*
+ReadableStreamReaderGenericCancel(JSContext* cx, HandleNativeObject reader, HandleValue reason);
+
+// Streams spec, 3.5.4.2. cancel ( reason )
+static MOZ_MUST_USE bool
+ReadableStreamDefaultReader_cancel(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
+ // rejected with a TypeError exception.
+ if (!Is<ReadableStreamDefaultReader>(args.thisv()))
+ return RejectNonGenericMethod(cx, args, "ReadableStreamDefaultReader", "cancel");
+
+ // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
+ // rejected with a TypeError exception.
+ RootedNativeObject reader(cx, &args.thisv().toObject().as<NativeObject>());
+ if (!ReaderHasStream(reader)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMREADER_NOT_OWNED, "cancel");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 3: Return ! ReadableStreamReaderGenericCancel(this, reason).
+ JSObject* cancelPromise = ReadableStreamReaderGenericCancel(cx, reader, args.get(0));
+ if (!cancelPromise)
+ return false;
+ args.rval().setObject(*cancelPromise);
+ return true;
+}
+
+// Streams spec, 3.5.4.3 read ( )
+static MOZ_MUST_USE bool
+ReadableStreamDefaultReader_read(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
+ // rejected with a TypeError exception.
+ if (!Is<ReadableStreamDefaultReader>(args.thisv()))
+ return RejectNonGenericMethod(cx, args, "ReadableStreamDefaultReader", "read");
+
+ // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
+ // rejected with a TypeError exception.
+ Rooted<ReadableStreamDefaultReader*> reader(cx);
+ reader = &args.thisv().toObject().as<ReadableStreamDefaultReader>();
+ if (!ReaderHasStream(reader)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMREADER_NOT_OWNED, "read");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 3: Return ! ReadableStreamDefaultReaderRead(this).
+ JSObject* readPromise = ReadableStreamDefaultReader::read(cx, reader);
+ if (!readPromise)
+ return false;
+ args.rval().setObject(*readPromise);
+ return true;
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamReaderGenericRelease(JSContext* cx, HandleNativeObject reader);
+
+// Streams spec, 3.5.4.4. releaseLock ( )
+static MOZ_MUST_USE bool
+ReadableStreamDefaultReader_releaseLock_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStreamDefaultReader*> reader(cx);
+ reader = &args.thisv().toObject().as<ReadableStreamDefaultReader>();
+
+ // Step 2: If this.[[ownerReadableStream]] is undefined, return.
+ if (!ReaderHasStream(reader)) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ // Step 3: If this.[[readRequests]] is not empty, throw a TypeError exception.
+ Value val = reader->getFixedSlot(ReaderSlot_Requests);
+ if (!val.isUndefined()) {
+ NativeObject* readRequests = &val.toObject().as<NativeObject>();
+ uint32_t len = readRequests->getDenseInitializedLength();
+ if (len != 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMREADER_NOT_EMPTY,
+ "releaseLock");
+ return false;
+ }
+ }
+
+ // Step 4: Perform ! ReadableStreamReaderGenericRelease(this).
+ return ReadableStreamReaderGenericRelease(cx, reader);
+}
+
+static bool
+ReadableStreamDefaultReader_releaseLock(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStreamDefaultReader(this) is false,
+ // throw a TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStreamDefaultReader>,
+ ReadableStreamDefaultReader_releaseLock_impl>(cx, args);
+}
+
+static const JSFunctionSpec ReadableStreamDefaultReader_methods[] = {
+ JS_FN("cancel", ReadableStreamDefaultReader_cancel, 1, 0),
+ JS_FN("read", ReadableStreamDefaultReader_read, 0, 0),
+ JS_FN("releaseLock", ReadableStreamDefaultReader_releaseLock, 0, 0),
+ JS_FS_END
+};
+
+static const JSPropertySpec ReadableStreamDefaultReader_properties[] = {
+ JS_PSG("closed", ReadableStreamDefaultReader_closed, 0),
+ JS_PS_END
+};
+
+CLASS_SPEC(ReadableStreamDefaultReader, 1, ReaderSlotCount, ClassSpec::DontDefineConstructor, 0,
+ JS_NULL_CLASS_OPS);
+
+
+// Streams spec, 3.6.3 new ReadableStreamBYOBReader ( stream )
+// Steps 2-5.
+static MOZ_MUST_USE ReadableStreamBYOBReader*
+CreateReadableStreamBYOBReader(JSContext* cx, Handle<ReadableStream*> stream)
+{
+ // Step 2: If ! IsReadableByteStreamController(stream.[[readableStreamController]])
+ // is false, throw a TypeError exception.
+ if (!ControllerFromStream(stream)->is<ReadableByteStreamController>()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_NOT_BYTE_STREAM_CONTROLLER,
+ "ReadableStream.getReader('byob')");
+ return nullptr;
+ }
+
+ // Step 3: If ! IsReadableStreamLocked(stream) is true, throw a TypeError
+ // exception.
+ if (stream->locked()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_READABLESTREAM_LOCKED);
+ return nullptr;
+ }
+
+ Rooted<ReadableStreamBYOBReader*> reader(cx);
+ reader = NewBuiltinClassInstance<ReadableStreamBYOBReader>(cx);
+ if (!reader)
+ return nullptr;
+
+ // Step 4: Perform ! ReadableStreamReaderGenericInitialize(this, stream).
+ if (!ReadableStreamReaderGenericInitialize(cx, reader, stream))
+ return nullptr;
+
+ // Step 5: Set this.[[readIntoRequests]] to a new empty List.
+ if (!SetNewList(cx, reader, ReaderSlot_Requests))
+ return nullptr;
+
+ return reader;
+}
+
+// Streams spec, 3.6.3 new ReadableStreamBYOBReader ( stream )
+bool
+ReadableStreamBYOBReader::constructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "ReadableStreamBYOBReader"))
+ return false;
+
+ // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception.
+ if (!Is<ReadableStream>(args.get(0))) {
+ ReportArgTypeError(cx, "ReadableStreamBYOBReader", "ReadableStream", args.get(0));
+ return false;
+ }
+
+ Rooted<ReadableStream*> stream(cx, &args.get(0).toObject().as<ReadableStream>());
+ RootedObject reader(cx, CreateReadableStreamBYOBReader(cx, stream));
+ if (!reader)
+ return false;
+
+ args.rval().setObject(*reader);
+ return true;
+}
+
+// Streams spec, 3.6.4.1 get closed
+static MOZ_MUST_USE bool
+ReadableStreamBYOBReader_closed(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsReadableStreamBYOBReader(this) is false, return a promise
+ // rejected with a TypeError exception.
+ if (!Is<ReadableStreamBYOBReader>(args.thisv()))
+ return RejectNonGenericMethod(cx, args, "ReadableStreamBYOBReader", "get closed");
+
+ // Step 2: Return this.[[closedPromise]].
+ NativeObject* reader = &args.thisv().toObject().as<NativeObject>();
+ args.rval().set(reader->getFixedSlot(ReaderSlot_ClosedPromise));
+ return true;
+}
+
+// Streams spec, 3.6.4.2. cancel ( reason )
+static MOZ_MUST_USE bool
+ReadableStreamBYOBReader_cancel(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsReadableStreamBYOBReader(this) is false, return a promise
+ // rejected with a TypeError exception.
+ if (!Is<ReadableStreamBYOBReader>(args.thisv()))
+ return RejectNonGenericMethod(cx, args, "ReadableStreamBYOBReader", "cancel");
+
+ // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
+ // rejected with a TypeError exception.
+ RootedNativeObject reader(cx, &args.thisv().toObject().as<NativeObject>());
+ if (!ReaderHasStream(reader)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMREADER_NOT_OWNED, "cancel");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 3: Return ! ReadableStreamReaderGenericCancel(this, reason).
+ JSObject* cancelPromise = ReadableStreamReaderGenericCancel(cx, reader, args.get(0));
+ if (!cancelPromise)
+ return false;
+ args.rval().setObject(*cancelPromise);
+ return true;
+}
+
+// Streams spec, 3.6.4.3 read ( )
+static MOZ_MUST_USE bool
+ReadableStreamBYOBReader_read(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue viewVal = args.get(0);
+
+ // Step 1: If ! IsReadableStreamBYOBReader(this) is false, return a promise
+ // rejected with a TypeError exception.
+ if (!Is<ReadableStreamBYOBReader>(args.thisv()))
+ return RejectNonGenericMethod(cx, args, "ReadableStreamBYOBReader", "read");
+
+ // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
+ // rejected with a TypeError exception.
+ Rooted<ReadableStreamBYOBReader*> reader(cx);
+ reader = &args.thisv().toObject().as<ReadableStreamBYOBReader>();
+ if (!ReaderHasStream(reader)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMREADER_NOT_OWNED, "read");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 3: If Type(view) is not Object, return a promise rejected with a
+ // TypeError exception.
+ // Step 4: If view does not have a [[ViewedArrayBuffer]] internal slot,
+ // return a promise rejected with a TypeError exception.
+ if (!Is<ArrayBufferViewObject>(viewVal)) {
+ ReportArgTypeError(cx, "ReadableStreamBYOBReader.read", "Typed Array", viewVal);
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ Rooted<ArrayBufferViewObject*> view(cx, &viewVal.toObject().as<ArrayBufferViewObject>());
+
+ // Step 5: If view.[[ByteLength]] is 0, return a promise rejected with a
+ // TypeError exception.
+ // Note: It's ok to use the length in number of elements here because all we
+ // want to know is whether it's < 0.
+ if (JS_GetArrayBufferViewByteLength(view) == 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMBYOBREADER_READ_EMPTY_VIEW);
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 6: Return ! ReadableStreamBYOBReaderRead(this, view).
+ JSObject* readPromise = ReadableStreamBYOBReader::read(cx, reader, view);
+ if (!readPromise)
+ return false;
+ args.rval().setObject(*readPromise);
+ return true;
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamReaderGenericRelease(JSContext* cx, HandleNativeObject reader);
+
+// Streams spec, 3.6.4.4. releaseLock ( )
+static MOZ_MUST_USE bool
+ReadableStreamBYOBReader_releaseLock_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStreamBYOBReader*> reader(cx);
+ reader = &args.thisv().toObject().as<ReadableStreamBYOBReader>();
+
+ // Step 2: If this.[[ownerReadableStream]] is undefined, return.
+ if (!ReaderHasStream(reader)) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ // Step 3: If this.[[readRequests]] is not empty, throw a TypeError exception.
+ Value val = reader->getFixedSlot(ReaderSlot_Requests);
+ if (!val.isUndefined()) {
+ NativeObject* readRequests = &val.toObject().as<NativeObject>();
+ uint32_t len = readRequests->getDenseInitializedLength();
+ if (len != 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMREADER_NOT_EMPTY, "releaseLock");
+ return false;
+ }
+ }
+
+ // Step 4: Perform ! ReadableStreamReaderGenericRelease(this).
+ return ReadableStreamReaderGenericRelease(cx, reader);
+}
+
+static bool
+ReadableStreamBYOBReader_releaseLock(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStreamBYOBReader(this) is false,
+ // throw a TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStreamBYOBReader>,
+ ReadableStreamBYOBReader_releaseLock_impl>(cx, args);
+}
+
+static const JSPropertySpec ReadableStreamBYOBReader_properties[] = {
+ JS_PSG("closed", ReadableStreamBYOBReader_closed, 0),
+ JS_PS_END
+};
+
+static const JSFunctionSpec ReadableStreamBYOBReader_methods[] = {
+ JS_FN("cancel", ReadableStreamBYOBReader_cancel, 1, 0),
+ JS_FN("read", ReadableStreamBYOBReader_read, 1, 0),
+ JS_FN("releaseLock", ReadableStreamBYOBReader_releaseLock, 0, 0),
+ JS_FS_END
+};
+
+CLASS_SPEC(ReadableStreamBYOBReader, 1, 3, ClassSpec::DontDefineConstructor, 0, JS_NULL_CLASS_OPS);
+
+inline static MOZ_MUST_USE bool
+ReadableStreamControllerCallPullIfNeeded(JSContext* cx, HandleNativeObject controller);
+
+// Streams spec, 3.7.1. IsReadableStreamDefaultReader ( x )
+// Implemented via intrinsic_isInstanceOfBuiltin<ReadableStreamDefaultReader>()
+
+// Streams spec, 3.7.2. IsReadableStreamBYOBReader ( x )
+// Implemented via intrinsic_isInstanceOfBuiltin<ReadableStreamBYOBReader>()
+
+// Streams spec, 3.7.3. ReadableStreamReaderGenericCancel ( reader, reason )
+static MOZ_MUST_USE JSObject*
+ReadableStreamReaderGenericCancel(JSContext* cx, HandleNativeObject reader, HandleValue reason)
+{
+ // Step 1: Let stream be reader.[[ownerReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromReader(reader));
+
+ // Step 2: Assert: stream is not undefined (implicit).
+
+ // Step 3: Return ! ReadableStreamCancel(stream, reason).
+ return &ReadableStreamCancel(cx, stream, reason)->as<PromiseObject>();
+}
+
+// Streams spec, 3.7.4. ReadableStreamReaderGenericInitialize ( reader, stream )
+static MOZ_MUST_USE bool
+ReadableStreamReaderGenericInitialize(JSContext* cx, HandleNativeObject reader,
+ Handle<ReadableStream*> stream)
+{
+ // Step 1: Set reader.[[ownerReadableStream]] to stream.
+ reader->setFixedSlot(ReaderSlot_Stream, ObjectValue(*stream));
+
+ // Step 2: Set stream.[[reader]] to reader.
+ stream->setFixedSlot(StreamSlot_Reader, ObjectValue(*reader));
+
+ // Step 3: If stream.[[state]] is "readable",
+ RootedObject promise(cx);
+ if (stream->readable()) {
+ // Step a: Set reader.[[closedPromise]] to a new promise.
+ promise = PromiseObject::createSkippingExecutor(cx);
+ } else if (stream->closed()) {
+ // Step 4: Otherwise
+ // Step a: If stream.[[state]] is "closed",
+ // Step i: Set reader.[[closedPromise]] to a new promise resolved with
+ // undefined.
+ promise = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
+ } else {
+ // Step b: Otherwise,
+ // Step i: Assert: stream.[[state]] is "errored".
+ MOZ_ASSERT(stream->errored());
+
+ // Step ii: Set reader.[[closedPromise]] to a new promise rejected with
+ // stream.[[storedError]].
+ RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError));
+ promise = PromiseObject::unforgeableReject(cx, storedError);
+ }
+
+ if (!promise)
+ return false;
+
+ reader->setFixedSlot(ReaderSlot_ClosedPromise, ObjectValue(*promise));
+ return true;
+}
+
+// Streams spec, 3.7.5. ReadableStreamReaderGenericRelease ( reader )
+static MOZ_MUST_USE bool
+ReadableStreamReaderGenericRelease(JSContext* cx, HandleNativeObject reader)
+{
+ // Step 1: Assert: reader.[[ownerReadableStream]] is not undefined.
+ Rooted<ReadableStream*> stream(cx, StreamFromReader(reader));
+
+ // Step 2: Assert: reader.[[ownerReadableStream]].[[reader]] is reader.
+ MOZ_ASSERT(&stream->getFixedSlot(StreamSlot_Reader).toObject() == reader);
+
+ // Create an exception to reject promises with below. We don't have a
+ // clean way to do this, unfortunately.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_READABLESTREAMREADER_RELEASED);
+ RootedValue exn(cx);
+ // Not much we can do about uncatchable exceptions, just bail.
+ if (!GetAndClearException(cx, &exn))
+ return false;
+
+ // Step 3: If reader.[[ownerReadableStream]].[[state]] is "readable", reject
+ // reader.[[closedPromise]] with a TypeError exception.
+ if (stream->readable()) {
+ Value val = reader->getFixedSlot(ReaderSlot_ClosedPromise);
+ Rooted<PromiseObject*> closedPromise(cx, &val.toObject().as<PromiseObject>());
+ if (!PromiseObject::reject(cx, closedPromise, exn))
+ return false;
+ } else {
+ // Step 4: Otherwise, set reader.[[closedPromise]] to a new promise rejected
+ // with a TypeError exception.
+ RootedObject closedPromise(cx, PromiseObject::unforgeableReject(cx, exn));
+ if (!closedPromise)
+ return false;
+ reader->setFixedSlot(ReaderSlot_ClosedPromise, ObjectValue(*closedPromise));
+ }
+
+ // Step 5: Set reader.[[ownerReadableStream]].[[reader]] to undefined.
+ stream->setFixedSlot(StreamSlot_Reader, UndefinedValue());
+
+ // Step 6: Set reader.[[ownerReadableStream]] to undefined.
+ reader->setFixedSlot(ReaderSlot_Stream, UndefinedValue());
+
+ return true;
+}
+
+static MOZ_MUST_USE JSObject*
+ReadableByteStreamControllerPullInto(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ Handle<ArrayBufferViewObject*> view);
+
+// Streams spec, 3.7.6. ReadableStreamBYOBReaderRead ( reader, view )
+/* static */ MOZ_MUST_USE JSObject*
+ReadableStreamBYOBReader::read(JSContext* cx, Handle<ReadableStreamBYOBReader*> reader,
+ Handle<ArrayBufferViewObject*> view)
+{
+ MOZ_ASSERT(reader->is<ReadableStreamBYOBReader>());
+
+ // Step 1: Let stream be reader.[[ownerReadableStream]].
+ // Step 2: Assert: stream is not undefined.
+ Rooted<ReadableStream*> stream(cx, StreamFromReader(reader));
+
+ // Step 3: Set stream.[[disturbed]] to true.
+ SetStreamState(stream, StreamState(stream) | ReadableStream::Disturbed);
+
+ // Step 4: If stream.[[state]] is "errored", return a promise rejected with
+ // stream.[[storedError]].
+ if (stream->errored()) {
+ RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError));
+ return PromiseObject::unforgeableReject(cx, storedError);
+ }
+
+ // Step 5: Return ! ReadableByteStreamControllerPullInto(stream.[[readableStreamController]], view).
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &ControllerFromStream(stream)->as<ReadableByteStreamController>();
+ return ReadableByteStreamControllerPullInto(cx, controller, view);
+}
+
+static MOZ_MUST_USE JSObject*
+ReadableStreamControllerPullSteps(JSContext* cx, HandleNativeObject controller);
+
+// Streams spec, 3.7.7. ReadableStreamDefaultReaderRead ( reader )
+MOZ_MUST_USE JSObject*
+ReadableStreamDefaultReader::read(JSContext* cx, Handle<ReadableStreamDefaultReader*> reader)
+{
+ // Step 1: Let stream be reader.[[ownerReadableStream]].
+ // Step 2: Assert: stream is not undefined.
+ Rooted<ReadableStream*> stream(cx, StreamFromReader(reader));
+
+ // Step 3: Set stream.[[disturbed]] to true.
+ SetStreamState(stream, StreamState(stream) | ReadableStream::Disturbed);
+
+ // Step 4: If stream.[[state]] is "closed", return a new promise resolved with
+ // ! CreateIterResultObject(undefined, true).
+ if (stream->closed()) {
+ RootedObject iterResult(cx, CreateIterResultObject(cx, UndefinedHandleValue, true));
+ if (!iterResult)
+ return nullptr;
+ RootedValue iterResultVal(cx, ObjectValue(*iterResult));
+ return PromiseObject::unforgeableResolve(cx, iterResultVal);
+ }
+
+ // Step 5: If stream.[[state]] is "errored", return a new promise rejected with
+ // stream.[[storedError]].
+ if (stream->errored()) {
+ RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError));
+ return PromiseObject::unforgeableReject(cx, storedError);
+ }
+
+ // Step 6: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->readable());
+
+ // Step 7: Return ! stream.[[readableStreamController]].[[PullSteps]]().
+ RootedNativeObject controller(cx, ControllerFromStream(stream));
+ return ReadableStreamControllerPullSteps(cx, controller);
+}
+
+// Streams spec, 3.8.3, step 11.a.
+// and
+// Streams spec, 3.10.3, step 16.a.
+static bool
+ControllerStartHandler(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedNativeObject controller(cx, TargetFromHandler<NativeObject>(args.callee()));
+
+ // Step i: Set controller.[[started]] to true.
+ AddControllerFlags(controller, ControllerFlag_Started);
+
+ // Step ii: Assert: controller.[[pulling]] is false.
+ // Step iii: Assert: controller.[[pullAgain]] is false.
+ MOZ_ASSERT(!(ControllerFlags(controller) &
+ (ControllerFlag_Pulling | ControllerFlag_PullAgain)));
+
+ // Step iv: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(controller).
+ // or
+ // Step iv: Perform ! ReadableByteStreamControllerCallPullIfNeeded((controller).
+ if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
+ return false;
+ args.rval().setUndefined();
+ return true;
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamDefaultControllerErrorIfNeeded(JSContext* cx,
+ Handle<ReadableStreamDefaultController*> controller,
+ HandleValue e);
+
+static MOZ_MUST_USE bool
+ReadableStreamControllerError(JSContext* cx, HandleNativeObject controller, HandleValue e);
+
+// Streams spec, 3.8.3, step 11.b.
+// and
+// Streams spec, 3.10.3, step 16.b.
+static bool
+ControllerStartFailedHandler(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedNativeObject controllerObj(cx, TargetFromHandler<NativeObject>(args.callee()));
+
+ // 3.8.3, Step 11.b.i:
+ // Perform ! ReadableStreamDefaultControllerErrorIfNeeded(controller, r).
+ if (controllerObj->is<ReadableStreamDefaultController>()) {
+ Rooted<ReadableStreamDefaultController*> controller(cx);
+ controller = &controllerObj->as<ReadableStreamDefaultController>();
+ return ReadableStreamDefaultControllerErrorIfNeeded(cx, controller, args.get(0));
+ }
+
+ // 3.10.3, Step 16.b.i: If stream.[[state]] is "readable", perform
+ // ! ReadableByteStreamControllerError(controller, r).
+ if (StreamFromController(controllerObj)->readable())
+ return ReadableStreamControllerError(cx, controllerObj, args.get(0));
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static MOZ_MUST_USE bool
+ValidateAndNormalizeHighWaterMark(JSContext* cx,
+ HandleValue highWaterMarkVal,
+ double* highWaterMark);
+
+static MOZ_MUST_USE bool
+ValidateAndNormalizeQueuingStrategy(JSContext* cx,
+ HandleValue size,
+ HandleValue highWaterMarkVal,
+ double* highWaterMark);
+
+// Streams spec, 3.8.3 new ReadableStreamDefaultController ( stream, underlyingSource,
+// size, highWaterMark )
+// Steps 3 - 11.
+static MOZ_MUST_USE ReadableStreamDefaultController*
+CreateReadableStreamDefaultController(JSContext* cx, Handle<ReadableStream*> stream,
+ HandleValue underlyingSource, HandleValue size,
+ HandleValue highWaterMarkVal)
+{
+ Rooted<ReadableStreamDefaultController*> controller(cx);
+ controller = NewBuiltinClassInstance<ReadableStreamDefaultController>(cx);
+ if (!controller)
+ return nullptr;
+
+ // Step 3: Set this.[[controlledReadableStream]] to stream.
+ controller->setFixedSlot(ControllerSlot_Stream, ObjectValue(*stream));
+
+ // Step 4: Set this.[[underlyingSource]] to underlyingSource.
+ controller->setFixedSlot(ControllerSlot_UnderlyingSource, underlyingSource);
+
+ // Step 5: Perform ! ResetQueue(this).
+ if (!ResetQueue(cx, controller))
+ return nullptr;
+
+ // Step 6: Set this.[[started]], this.[[closeRequested]], this.[[pullAgain]],
+ // and this.[[pulling]] to false.
+ controller->setFixedSlot(ControllerSlot_Flags, Int32Value(0));
+
+ // Step 7: Let normalizedStrategy be
+ // ? ValidateAndNormalizeQueuingStrategy(size, highWaterMark).
+ double highWaterMark;
+ if (!ValidateAndNormalizeQueuingStrategy(cx, size, highWaterMarkVal, &highWaterMark))
+ return nullptr;
+
+ // Step 8: Set this.[[strategySize]] to normalizedStrategy.[[size]] and
+ // this.[[strategyHWM]] to normalizedStrategy.[[highWaterMark]].
+ controller->setFixedSlot(DefaultControllerSlot_StrategySize, size);
+ controller->setFixedSlot(ControllerSlot_StrategyHWM, NumberValue(highWaterMark));
+
+ // Step 9: Let controller be this (implicit).
+
+ // Step 10: Let startResult be
+ // ? InvokeOrNoop(underlyingSource, "start", « this »).
+ RootedValue startResult(cx);
+ RootedValue controllerVal(cx, ObjectValue(*controller));
+ if (!InvokeOrNoop(cx, underlyingSource, cx->names().start, controllerVal, &startResult))
+ return nullptr;
+
+ // Step 11: Let startPromise be a promise resolved with startResult:
+ RootedObject startPromise(cx, PromiseObject::unforgeableResolve(cx, startResult));
+ if (!startPromise)
+ return nullptr;
+
+ RootedObject onStartFulfilled(cx, NewHandler(cx, ControllerStartHandler, controller));
+ if (!onStartFulfilled)
+ return nullptr;
+
+ RootedObject onStartRejected(cx, NewHandler(cx, ControllerStartFailedHandler, controller));
+ if (!onStartRejected)
+ return nullptr;
+
+ if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled, onStartRejected))
+ return nullptr;
+
+ return controller;
+}
+
+// Streams spec, 3.8.3.
+// new ReadableStreamDefaultController( stream, underlyingSource, size,
+// highWaterMark )
+bool
+ReadableStreamDefaultController::constructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "ReadableStreamDefaultController"))
+ return false;
+
+ // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception.
+ HandleValue streamVal = args.get(0);
+ if (!Is<ReadableStream>(streamVal)) {
+ ReportArgTypeError(cx, "ReadableStreamDefaultController", "ReadableStream",
+ args.get(0));
+ return false;
+ }
+
+ Rooted<ReadableStream*> stream(cx, &streamVal.toObject().as<ReadableStream>());
+
+ // Step 2: If stream.[[readableStreamController]] is not undefined, throw a
+ // TypeError exception.
+ if (HasController(stream)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_CONTROLLER_SET);
+ return false;
+ }
+
+ // Steps 3-11.
+ RootedObject controller(cx, CreateReadableStreamDefaultController(cx, stream, args.get(1),
+ args.get(2), args.get(3)));
+ if (!controller)
+ return false;
+
+ args.rval().setObject(*controller);
+ return true;
+}
+
+static MOZ_MUST_USE double
+ReadableStreamControllerGetDesiredSizeUnchecked(NativeObject* controller);
+
+// Streams spec, 3.8.4.1. get desiredSize
+// and
+// Streams spec, 3.10.4.2. get desiredSize
+static MOZ_MUST_USE bool
+ReadableStreamController_desiredSize_impl(JSContext* cx, const CallArgs& args)
+{
+ RootedNativeObject controller(cx);
+ controller = &args.thisv().toObject().as<NativeObject>();
+
+ // Streams spec, 3.9.8. steps 1-4.
+ // 3.9.8. Step 1: Let stream be controller.[[controlledReadableStream]].
+ ReadableStream* stream = StreamFromController(controller);
+
+ // 3.9.8. Step 2: Let state be stream.[[state]].
+ // 3.9.8. Step 3: If state is "errored", return null.
+ if (stream->errored()) {
+ args.rval().setNull();
+ return true;
+ }
+
+ // 3.9.8. Step 4: If state is "closed", return 0.
+ if (stream->closed()) {
+ args.rval().setInt32(0);
+ return true;
+ }
+
+ // Step 2: Return ! ReadableStreamDefaultControllerGetDesiredSize(this).
+ args.rval().setNumber(ReadableStreamControllerGetDesiredSizeUnchecked(controller));
+ return true;
+}
+
+static bool
+ReadableStreamDefaultController_desiredSize(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
+ // TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStreamDefaultController>,
+ ReadableStreamController_desiredSize_impl>(cx, args);
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamDefaultControllerClose(JSContext* cx,
+ Handle<ReadableStreamDefaultController*> controller);
+
+// Unified implementation of steps 2-3 of 3.8.4.2 and 3.10.4.3.
+static MOZ_MUST_USE bool
+VerifyControllerStateForClosing(JSContext* cx, HandleNativeObject controller)
+{
+ // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
+ if (ControllerFlags(controller) & ControllerFlag_CloseRequested) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_CLOSED, "close");
+ return false;
+ }
+
+ // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable",
+ // throw a TypeError exception.
+ ReadableStream* stream = StreamFromController(controller);
+ if (!stream->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "close");
+ return false;
+ }
+
+ return true;
+}
+
+// Streams spec, 3.8.4.2 close()
+static MOZ_MUST_USE bool
+ReadableStreamDefaultController_close_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStreamDefaultController*> controller(cx);
+ controller = &args.thisv().toObject().as<ReadableStreamDefaultController>();
+
+ // Steps 2-3.
+ if (!VerifyControllerStateForClosing(cx, controller))
+ return false;
+
+ // Step 4: Perform ! ReadableStreamDefaultControllerClose(this).
+ if (!ReadableStreamDefaultControllerClose(cx, controller))
+ return false;
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ReadableStreamDefaultController_close(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
+ // TypeError exception.
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStreamDefaultController>,
+ ReadableStreamDefaultController_close_impl>(cx, args);
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamDefaultControllerEnqueue(JSContext* cx,
+ Handle<ReadableStreamDefaultController*> controller,
+ HandleValue chunk);
+
+// Streams spec, 3.8.4.3. enqueue ( chunk )
+static MOZ_MUST_USE bool
+ReadableStreamDefaultController_enqueue_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStreamDefaultController*> controller(cx);
+ controller = &args.thisv().toObject().as<ReadableStreamDefaultController>();
+
+ // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
+ if (ControllerFlags(controller) & ControllerFlag_CloseRequested) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_CLOSED, "close");
+ return false;
+ }
+
+ // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable",
+ // throw a TypeError exception.
+ ReadableStream* stream = StreamFromController(controller);
+ if (!stream->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "close");
+ return false;
+ }
+
+ // Step 4: Return ! ReadableStreamDefaultControllerEnqueue(this, chunk).
+ if (!ReadableStreamDefaultControllerEnqueue(cx, controller, args.get(0)))
+ return false;
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ReadableStreamDefaultController_enqueue(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
+ // TypeError exception.
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStreamDefaultController>,
+ ReadableStreamDefaultController_enqueue_impl>(cx, args);
+}
+
+// Streams spec, 3.8.4.4. error ( e )
+static MOZ_MUST_USE bool
+ReadableStreamDefaultController_error_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStreamDefaultController*> controller(cx);
+ controller = &args.thisv().toObject().as<ReadableStreamDefaultController>();
+
+ // Step 2: Let stream be this.[[controlledReadableStream]].
+ // Step 3: If stream.[[state]] is not "readable", throw a TypeError exception.
+ if (!StreamFromController(controller)->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "error");
+ return false;
+ }
+
+ // Step 4: Perform ! ReadableStreamDefaultControllerError(this, e).
+ if (!ReadableStreamControllerError(cx, controller, args.get(0)))
+ return false;
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ReadableStreamDefaultController_error(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
+ // TypeError exception.
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStreamDefaultController>,
+ ReadableStreamDefaultController_error_impl>(cx, args);
+}
+
+static const JSPropertySpec ReadableStreamDefaultController_properties[] = {
+ JS_PSG("desiredSize", ReadableStreamDefaultController_desiredSize, 0),
+ JS_PS_END
+};
+
+static const JSFunctionSpec ReadableStreamDefaultController_methods[] = {
+ JS_FN("close", ReadableStreamDefaultController_close, 0, 0),
+ JS_FN("enqueue", ReadableStreamDefaultController_enqueue, 1, 0),
+ JS_FN("error", ReadableStreamDefaultController_error, 1, 0),
+ JS_FS_END
+};
+
+CLASS_SPEC(ReadableStreamDefaultController, 4, 7, ClassSpec::DontDefineConstructor, 0,
+ JS_NULL_CLASS_OPS);
+
+/**
+ * Unified implementation of ReadableStream controllers' [[CancelSteps]] internal
+ * methods.
+ * Streams spec, 3.8.5.1. [[CancelSteps]] ( reason )
+ * and
+ * Streams spec, 3.10.5.1. [[CancelSteps]] ( reason )
+ */
+static MOZ_MUST_USE JSObject*
+ReadableStreamControllerCancelSteps(JSContext* cx, HandleNativeObject controller,
+ HandleValue reason)
+{
+ MOZ_ASSERT(IsReadableStreamController(controller));
+
+ // Step 1 of 3.10.5.1: If this.[[pendingPullIntos]] is not empty,
+ if (!controller->is<ReadableStreamDefaultController>()) {
+ Value val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos);
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+
+ if (pendingPullIntos->getDenseInitializedLength() != 0) {
+ // Step a: Let firstDescriptor be the first element of
+ // this.[[pendingPullIntos]].
+ // Step b: Set firstDescriptor.[[bytesFilled]] to 0.
+ Rooted<PullIntoDescriptor*> firstDescriptor(cx);
+ firstDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos);
+ firstDescriptor->setBytesFilled(0);
+ }
+ }
+
+ // Step 1 of 3.8.5.1, step 2 of 3.10.5.1: Perform ! ResetQueue(this).
+ if (!ResetQueue(cx, controller))
+ return nullptr;
+
+ // Step 2 of 3.8.5.1, step 3 of 3.10.5.1:
+ // Return ! PromiseInvokeOrNoop(this.[[underlying(Byte)Source]],
+ // "cancel", « reason »)
+ RootedValue underlyingSource(cx);
+ underlyingSource = controller->getFixedSlot(ControllerSlot_UnderlyingSource);
+
+ if (Is<TeeState>(underlyingSource)) {
+ Rooted<TeeState*> teeState(cx, &underlyingSource.toObject().as<TeeState>());
+ Rooted<ReadableStreamDefaultController*> defaultController(cx);
+ defaultController = &controller->as<ReadableStreamDefaultController>();
+ return ReadableStreamTee_Cancel(cx, teeState, defaultController, reason);
+ }
+
+ if (ControllerFlags(controller) & ControllerFlag_ExternalSource) {
+ void* source = underlyingSource.toPrivate();
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+ RootedValue rval(cx);
+ rval = cx->runtime()->readableStreamCancelCallback(cx, stream, source,
+ stream->embeddingFlags(), reason);
+ return PromiseObject::unforgeableResolve(cx, rval);
+ }
+
+ return PromiseInvokeOrNoop(cx, underlyingSource, cx->names().cancel, reason);
+}
+
+inline static MOZ_MUST_USE bool
+DequeueValue(JSContext* cx, HandleNativeObject container, MutableHandleValue chunk);
+
+// Streams spec, 3.8.5.2. ReadableStreamDefaultController [[PullSteps]]()
+static JSObject*
+ReadableStreamDefaultControllerPullSteps(JSContext* cx, HandleNativeObject controller)
+{
+ MOZ_ASSERT(controller->is<ReadableStreamDefaultController>());
+
+ // Step 1: Let stream be this.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 2: If this.[[queue]] is not empty,
+ RootedNativeObject queue(cx);
+ RootedValue val(cx, controller->getFixedSlot(QueueContainerSlot_Queue));
+ if (val.isObject())
+ queue = &val.toObject().as<NativeObject>();
+
+ if (queue && queue->getDenseInitializedLength() != 0) {
+ // Step a: Let chunk be ! DequeueValue(this.[[queue]]).
+ RootedValue chunk(cx);
+ if (!DequeueValue(cx, controller, &chunk))
+ return nullptr;
+
+ // Step b: If this.[[closeRequested]] is true and this.[[queue]] is empty,
+ // perform ! ReadableStreamClose(stream).
+ bool closeRequested = ControllerFlags(controller) & ControllerFlag_CloseRequested;
+ if (closeRequested && queue->getDenseInitializedLength() == 0) {
+ if (!ReadableStreamCloseInternal(cx, stream))
+ return nullptr;
+ }
+
+ // Step c: Otherwise, perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this).
+ else {
+ if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
+ return nullptr;
+ }
+
+ // Step d: Return a promise resolved with ! CreateIterResultObject(chunk, false).
+ RootedObject iterResultObj(cx, CreateIterResultObject(cx, chunk, false));
+ if (!iterResultObj)
+ return nullptr;
+ RootedValue iterResult(cx, ObjectValue(*iterResultObj));
+ return PromiseObject::unforgeableResolve(cx, iterResult);
+ }
+
+ // Step 3: Let pendingPromise be ! ReadableStreamAddReadRequest(stream).
+ Rooted<PromiseObject*> pendingPromise(cx, ReadableStreamAddReadRequest(cx, stream));
+ if (!pendingPromise)
+ return nullptr;
+
+ // Step 4: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this).
+ if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
+ return nullptr;
+
+ // Step 5: Return pendingPromise.
+ return pendingPromise;
+}
+
+// Streams spec, 3.9.2 and 3.12.3. step 7:
+// Upon fulfillment of pullPromise,
+static bool
+ControllerPullHandler(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedNativeObject controller(cx, TargetFromHandler<NativeObject>(args.callee()));
+ uint32_t flags = ControllerFlags(controller);
+
+ // Step a: Set controller.[[pulling]] to false.
+ // Step b.i: Set controller.[[pullAgain]] to false.
+ RemoveControllerFlags(controller, ControllerFlag_Pulling | ControllerFlag_PullAgain);
+
+ // Step b: If controller.[[pullAgain]] is true,
+ if (flags & ControllerFlag_PullAgain) {
+ // Step ii: Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller).
+ if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+// Streams spec, 3.9.2 and 3.12.3. step 8:
+// Upon rejection of pullPromise with reason e,
+static bool
+ControllerPullFailedHandler(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedNativeObject controller(cx, TargetFromHandler<NativeObject>(args.callee()));
+ HandleValue e = args.get(0);
+
+ // Step a: If controller.[[controlledReadableStream]].[[state]] is "readable",
+ // perform ! ReadableByteStreamControllerError(controller, e).
+ if (StreamFromController(controller)->readable()) {
+ if (!ReadableStreamControllerError(cx, controller, e))
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ReadableStreamControllerShouldCallPull(NativeObject* controller);
+
+static MOZ_MUST_USE double
+ReadableStreamControllerGetDesiredSizeUnchecked(NativeObject* controller);
+
+// Streams spec, 3.9.2 ReadableStreamDefaultControllerCallPullIfNeeded ( controller )
+// and
+// Streams spec, 3.12.3. ReadableByteStreamControllerCallPullIfNeeded ( controller )
+inline static MOZ_MUST_USE bool
+ReadableStreamControllerCallPullIfNeeded(JSContext* cx, HandleNativeObject controller)
+{
+ // Step 1: Let shouldPull be
+ // ! ReadableByteStreamControllerShouldCallPull(controller).
+ bool shouldPull = ReadableStreamControllerShouldCallPull(controller);
+
+ // Step 2: If shouldPull is false, return.
+ if (!shouldPull)
+ return true;
+
+ // Step 3: If controller.[[pulling]] is true,
+ if (ControllerFlags(controller) & ControllerFlag_Pulling) {
+ // Step a: Set controller.[[pullAgain]] to true.
+ AddControllerFlags(controller, ControllerFlag_PullAgain);
+
+ // Step b: Return.
+ return true;
+ }
+
+ // Step 4: Assert: controller.[[pullAgain]] is false.
+ MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_PullAgain));
+
+ // Step 5: Set controller.[[pulling]] to true.
+ AddControllerFlags(controller, ControllerFlag_Pulling);
+
+ // Step 6: Let pullPromise be
+ // ! PromiseInvokeOrNoop(controller.[[underlyingByteSource]], "pull", controller).
+ RootedObject pullPromise(cx);
+ RootedValue underlyingSource(cx);
+ underlyingSource = controller->getFixedSlot(ControllerSlot_UnderlyingSource);
+ RootedValue controllerVal(cx, ObjectValue(*controller));
+
+ if (Is<TeeState>(underlyingSource)) {
+ Rooted<TeeState*> teeState(cx, &underlyingSource.toObject().as<TeeState>());
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+ pullPromise = ReadableStreamTee_Pull(cx, teeState, stream);
+ } else if (ControllerFlags(controller) & ControllerFlag_ExternalSource) {
+ void* source = underlyingSource.toPrivate();
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+ double desiredSize = ReadableStreamControllerGetDesiredSizeUnchecked(controller);
+ cx->runtime()->readableStreamDataRequestCallback(cx, stream, source,
+ stream->embeddingFlags(), desiredSize);
+ pullPromise = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
+ } else {
+ pullPromise = PromiseInvokeOrNoop(cx, underlyingSource, cx->names().pull, controllerVal);
+ }
+ if (!pullPromise)
+ return false;
+
+ RootedObject onPullFulfilled(cx, NewHandler(cx, ControllerPullHandler, controller));
+ if (!onPullFulfilled)
+ return false;
+
+ RootedObject onPullRejected(cx, NewHandler(cx, ControllerPullFailedHandler, controller));
+ if (!onPullRejected)
+ return false;
+
+ return JS::AddPromiseReactions(cx, pullPromise, onPullFulfilled, onPullRejected);
+
+ // Steps 7-8 implemented in functions above.
+}
+
+// Streams spec, 3.9.3. ReadableStreamDefaultControllerShouldCallPull ( controller )
+// and
+// Streams spec, 3.12.24. ReadableByteStreamControllerShouldCallPull ( controller )
+static bool
+ReadableStreamControllerShouldCallPull(NativeObject* controller)
+{
+ // Step 1: Let stream be controller.[[controlledReadableStream]].
+ ReadableStream* stream = StreamFromController(controller);
+
+ // Step 2: If stream.[[state]] is "closed" or stream.[[state]] is "errored",
+ // return false.
+ // or, equivalently
+ // Step 2: If stream.[[state]] is not "readable", return false.
+ if (!stream->readable())
+ return false;
+
+ // Step 3: If controller.[[closeRequested]] is true, return false.
+ uint32_t flags = ControllerFlags(controller);
+ if (flags & ControllerFlag_CloseRequested)
+ return false;
+
+ // Step 4: If controller.[[started]] is false, return false.
+ if (!(flags & ControllerFlag_Started))
+ return false;
+
+ // Step 5: If ! IsReadableStreamLocked(stream) is true and
+ // ! ReadableStreamGetNumReadRequests(stream) > 0, return true.
+ // Steps 5-6 of 3.12.24 are equivalent in our implementation.
+ if (stream->locked() && ReadableStreamGetNumReadRequests(stream) > 0)
+ return true;
+
+ // Step 6: Let desiredSize be ReadableStreamDefaultControllerGetDesiredSize(controller).
+ double desiredSize = ReadableStreamControllerGetDesiredSizeUnchecked(controller);
+
+ // Step 7: If desiredSize > 0, return true.
+ // Step 8: Return false.
+ // Steps 7-8 of 3.12.24 are equivalent in our implementation.
+ return desiredSize > 0;
+}
+
+// Streams spec, 3.9.4. ReadableStreamDefaultControllerClose ( controller )
+static MOZ_MUST_USE bool
+ReadableStreamDefaultControllerClose(JSContext* cx,
+ Handle<ReadableStreamDefaultController*> controller)
+{
+ // Step 1: Let stream be controller.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 2: Assert: controller.[[closeRequested]] is false.
+ MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));
+
+ // Step 3: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->readable());
+
+ // Step 4: Set controller.[[closeRequested]] to true.
+ AddControllerFlags(controller, ControllerFlag_CloseRequested);
+
+ // Step 5: If controller.[[queue]] is empty, perform ! ReadableStreamClose(stream).
+ RootedNativeObject queue(cx);
+ queue = &controller->getFixedSlot(QueueContainerSlot_Queue).toObject().as<NativeObject>();
+ if (queue->getDenseInitializedLength() == 0)
+ return ReadableStreamCloseInternal(cx, stream);
+
+ return true;
+}
+
+static MOZ_MUST_USE bool
+EnqueueValueWithSize(JSContext* cx, HandleNativeObject container, HandleValue value,
+ HandleValue sizeVal);
+
+// Streams spec, 3.9.5. ReadableStreamDefaultControllerEnqueue ( controller, chunk )
+static MOZ_MUST_USE bool
+ReadableStreamDefaultControllerEnqueue(JSContext* cx,
+ Handle<ReadableStreamDefaultController*> controller,
+ HandleValue chunk)
+{
+ // Step 1: Let stream be controller.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 2: Assert: controller.[[closeRequested]] is false.
+ MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));
+
+ // Step 3: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->readable());
+
+ // Step 4: If ! IsReadableStreamLocked(stream) is true and
+ // ! ReadableStreamGetNumReadRequests(stream) > 0, perform
+ // ! ReadableStreamFulfillReadRequest(stream, chunk, false).
+ if (stream->locked() && ReadableStreamGetNumReadRequests(stream) > 0) {
+ if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, chunk, false))
+ return false;
+ } else {
+ // Step 5: Otherwise,
+ // Step a: Let chunkSize be 1.
+ RootedValue chunkSize(cx, NumberValue(1));
+ bool success = true;
+
+ // Step b: If controller.[[strategySize]] is not undefined,
+ RootedValue strategySize(cx);
+ strategySize = controller->getFixedSlot(DefaultControllerSlot_StrategySize);
+ if (!strategySize.isUndefined()) {
+ // Step i: Set chunkSize to Call(stream.[[strategySize]], undefined, chunk).
+ success = Call(cx, strategySize, UndefinedHandleValue, chunk, &chunkSize);
+ }
+
+ // Step c: Let enqueueResult be
+ // EnqueueValueWithSize(controller, chunk, chunkSize).
+ if (success)
+ success = EnqueueValueWithSize(cx, controller, chunk, chunkSize);
+
+ if (!success) {
+ // Step b.ii: If chunkSize is an abrupt completion,
+ // and
+ // Step d: If enqueueResult is an abrupt completion,
+ RootedValue exn(cx);
+ if (!cx->getPendingException(&exn))
+ return false;
+
+ // Step b.ii.1: Perform
+ // ! ReadableStreamDefaultControllerErrorIfNeeded(controller,
+ // chunkSize.[[Value]]).
+ if (!ReadableStreamDefaultControllerErrorIfNeeded(cx, controller, exn))
+ return false;
+
+ // Step b.ii.2: Return chunkSize.
+ return false;
+ }
+ }
+
+ // Step 6: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(controller).
+ if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
+ return false;
+
+ // Step 7: Return.
+ return true;
+}
+
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerClearPendingPullIntos(JSContext* cx, HandleNativeObject controller);
+
+// Streams spec, 3.9.6. ReadableStreamDefaultControllerError ( controller, e )
+// and
+// Streams spec, 3.12.10. ReadableByteStreamControllerError ( controller, e )
+static MOZ_MUST_USE bool
+ReadableStreamControllerError(JSContext* cx, HandleNativeObject controller, HandleValue e)
+{
+ MOZ_ASSERT(IsReadableStreamController(controller));
+
+ // Step 1: Let stream be controller.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 2: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->readable());
+
+ // Step 3 of 3.12.10:
+ // Perform ! ReadableByteStreamControllerClearPendingPullIntos(controller).
+ if (controller->is<ReadableByteStreamController>()) {
+ Rooted<ReadableByteStreamController*> byteStreamController(cx);
+ byteStreamController = &controller->as<ReadableByteStreamController>();
+ if (!ReadableByteStreamControllerClearPendingPullIntos(cx, byteStreamController))
+ return false;
+ }
+
+ // Step 3 (or 4): Perform ! ResetQueue(controller).
+ if (!ResetQueue(cx, controller))
+ return false;
+
+ // Step 4 (or 5): Perform ! ReadableStreamError(stream, e).
+ return ReadableStreamErrorInternal(cx, stream, e);
+}
+
+// Streams spec, 3.9.7. ReadableStreamDefaultControllerErrorIfNeeded ( controller, e ) nothrow
+static MOZ_MUST_USE bool
+ReadableStreamDefaultControllerErrorIfNeeded(JSContext* cx,
+ Handle<ReadableStreamDefaultController*> controller,
+ HandleValue e)
+{
+ // Step 1: If controller.[[controlledReadableStream]].[[state]] is "readable",
+ // perform ! ReadableStreamDefaultControllerError(controller, e).
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+ if (stream->readable())
+ return ReadableStreamControllerError(cx, controller, e);
+ return true;
+}
+
+// Streams spec, 3.9.8. ReadableStreamDefaultControllerGetDesiredSize ( controller )
+// and
+// Streams spec 3.12.13. ReadableByteStreamControllerGetDesiredSize ( controller )
+static MOZ_MUST_USE double
+ReadableStreamControllerGetDesiredSizeUnchecked(NativeObject* controller)
+{
+ // Steps 1-4 done at callsites, so only assert that they have been done.
+#if DEBUG
+ ReadableStream* stream = StreamFromController(controller);
+ MOZ_ASSERT(!(stream->errored() || stream->closed()));
+#endif // DEBUG
+
+ // Step 5: Return controller.[[strategyHWM]] − controller.[[queueTotalSize]].
+ double strategyHWM = controller->getFixedSlot(ControllerSlot_StrategyHWM).toNumber();
+ double queueSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ return strategyHWM - queueSize;
+}
+
+// Streams spec, 3.10.3 new ReadableByteStreamController ( stream, underlyingSource,
+// highWaterMark )
+// Steps 3 - 16.
+static MOZ_MUST_USE ReadableByteStreamController*
+CreateReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream,
+ HandleValue underlyingByteSource,
+ HandleValue highWaterMarkVal)
+{
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = NewBuiltinClassInstance<ReadableByteStreamController>(cx);
+ if (!controller)
+ return nullptr;
+
+ // Step 3: Set this.[[controlledReadableStream]] to stream.
+ controller->setFixedSlot(ControllerSlot_Stream, ObjectValue(*stream));
+
+ // Step 4: Set this.[[underlyingByteSource]] to underlyingByteSource.
+ controller->setFixedSlot(ControllerSlot_UnderlyingSource, underlyingByteSource);
+
+ // Step 5: Set this.[[pullAgain]], and this.[[pulling]] to false.
+ controller->setFixedSlot(ControllerSlot_Flags, Int32Value(0));
+
+ // Step 6: Perform ! ReadableByteStreamControllerClearPendingPullIntos(this).
+ if (!ReadableByteStreamControllerClearPendingPullIntos(cx, controller))
+ return nullptr;
+
+ // Step 7: Perform ! ResetQueue(this).
+ if (!ResetQueue(cx, controller))
+ return nullptr;
+
+ // Step 8: Set this.[[started]] and this.[[closeRequested]] to false.
+ // These should be false by default, unchanged since step 5.
+ MOZ_ASSERT(ControllerFlags(controller) == 0);
+
+ // Step 9: Set this.[[strategyHWM]] to
+ // ? ValidateAndNormalizeHighWaterMark(highWaterMark).
+ double highWaterMark;
+ if (!ValidateAndNormalizeHighWaterMark(cx, highWaterMarkVal, &highWaterMark))
+ return nullptr;
+ controller->setFixedSlot(ControllerSlot_StrategyHWM, NumberValue(highWaterMark));
+
+ // Step 10: Let autoAllocateChunkSize be
+ // ? GetV(underlyingByteSource, "autoAllocateChunkSize").
+ RootedValue autoAllocateChunkSize(cx);
+ if (!GetProperty(cx, underlyingByteSource, cx->names().autoAllocateChunkSize,
+ &autoAllocateChunkSize))
+ {
+ return nullptr;
+ }
+
+ // Step 11: If autoAllocateChunkSize is not undefined,
+ if (!autoAllocateChunkSize.isUndefined()) {
+ // Step a: If ! IsInteger(autoAllocateChunkSize) is false, or if
+ // autoAllocateChunkSize ≤ 0, throw a RangeError exception.
+ if (!IsInteger(autoAllocateChunkSize) || autoAllocateChunkSize.toNumber() <= 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNKSIZE);
+ return nullptr;
+ }
+ }
+
+ // Step 12: Set this.[[autoAllocateChunkSize]] to autoAllocateChunkSize.
+ controller->setFixedSlot(ByteControllerSlot_AutoAllocateSize, autoAllocateChunkSize);
+
+ // Step 13: Set this.[[pendingPullIntos]] to a new empty List.
+ if (!SetNewList(cx, controller, ByteControllerSlot_PendingPullIntos))
+ return nullptr;
+
+ // Step 14: Let controller be this (implicit).
+
+ // Step 15: Let startResult be
+ // ? InvokeOrNoop(underlyingSource, "start", « this »).
+ RootedValue startResult(cx);
+ RootedValue controllerVal(cx, ObjectValue(*controller));
+ if (!InvokeOrNoop(cx, underlyingByteSource, cx->names().start, controllerVal, &startResult))
+ return nullptr;
+
+ // Step 16: Let startPromise be a promise resolved with startResult:
+ RootedObject startPromise(cx, PromiseObject::unforgeableResolve(cx, startResult));
+ if (!startPromise)
+ return nullptr;
+
+ RootedObject onStartFulfilled(cx, NewHandler(cx, ControllerStartHandler, controller));
+ if (!onStartFulfilled)
+ return nullptr;
+
+ RootedObject onStartRejected(cx, NewHandler(cx, ControllerStartFailedHandler, controller));
+ if (!onStartRejected)
+ return nullptr;
+
+ if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled, onStartRejected))
+ return nullptr;
+
+ return controller;
+}
+
+bool
+ReadableByteStreamController::hasExternalSource() {
+ return ControllerFlags(this) & ControllerFlag_ExternalSource;
+}
+
+// Streams spec, 3.10.3.
+// new ReadableByteStreamController ( stream, underlyingByteSource,
+// highWaterMark )
+bool
+ReadableByteStreamController::constructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "ReadableByteStreamController"))
+ return false;
+
+ // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception.
+ HandleValue streamVal = args.get(0);
+ if (!Is<ReadableStream>(streamVal)) {
+ ReportArgTypeError(cx, "ReadableStreamDefaultController", "ReadableStream",
+ args.get(0));
+ return false;
+ }
+
+ Rooted<ReadableStream*> stream(cx, &streamVal.toObject().as<ReadableStream>());
+
+ // Step 2: If stream.[[readableStreamController]] is not undefined, throw a
+ // TypeError exception.
+ if (HasController(stream)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_CONTROLLER_SET);
+ return false;
+ }
+
+ RootedObject controller(cx, CreateReadableByteStreamController(cx, stream, args.get(1),
+ args.get(2)));
+ if (!controller)
+ return false;
+
+ args.rval().setObject(*controller);
+ return true;
+}
+
+// Version of the ReadableByteStreamConstructor that's specialized for
+// handling external, embedding-provided, underlying sources.
+static MOZ_MUST_USE ReadableByteStreamController*
+CreateReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream,
+ void* underlyingSource)
+{
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = NewBuiltinClassInstance<ReadableByteStreamController>(cx);
+ if (!controller)
+ return nullptr;
+
+ // Step 3: Set this.[[controlledReadableStream]] to stream.
+ controller->setFixedSlot(ControllerSlot_Stream, ObjectValue(*stream));
+
+ // Step 4: Set this.[[underlyingByteSource]] to underlyingByteSource.
+ controller->setFixedSlot(ControllerSlot_UnderlyingSource, PrivateValue(underlyingSource));
+
+ // Step 5: Set this.[[pullAgain]], and this.[[pulling]] to false.
+ controller->setFixedSlot(ControllerSlot_Flags, Int32Value(ControllerFlag_ExternalSource));
+
+ // Step 6: Perform ! ReadableByteStreamControllerClearPendingPullIntos(this).
+ // Omitted.
+
+ // Step 7: Perform ! ResetQueue(this).
+ controller->setFixedSlot(QueueContainerSlot_TotalSize, Int32Value(0));
+
+ // Step 8: Set this.[[started]] and this.[[closeRequested]] to false.
+ // Step 9: Set this.[[strategyHWM]] to
+ // ? ValidateAndNormalizeHighWaterMark(highWaterMark).
+ controller->setFixedSlot(ControllerSlot_StrategyHWM, Int32Value(0));
+
+ // Step 10: Let autoAllocateChunkSize be
+ // ? GetV(underlyingByteSource, "autoAllocateChunkSize").
+ // Step 11: If autoAllocateChunkSize is not undefined,
+ // Step 12: Set this.[[autoAllocateChunkSize]] to autoAllocateChunkSize.
+ // Omitted.
+
+ // Step 13: Set this.[[pendingPullIntos]] to a new empty List.
+ if (!SetNewList(cx, controller, ByteControllerSlot_PendingPullIntos))
+ return nullptr;
+
+ // Step 14: Let controller be this (implicit).
+ // Step 15: Let startResult be
+ // ? InvokeOrNoop(underlyingSource, "start", « this »).
+ // Omitted.
+
+ // Step 16: Let startPromise be a promise resolved with startResult:
+ RootedObject startPromise(cx, PromiseObject::unforgeableResolve(cx, UndefinedHandleValue));
+ if (!startPromise)
+ return nullptr;
+
+ RootedObject onStartFulfilled(cx, NewHandler(cx, ControllerStartHandler, controller));
+ if (!onStartFulfilled)
+ return nullptr;
+
+ RootedObject onStartRejected(cx, NewHandler(cx, ControllerStartFailedHandler, controller));
+ if (!onStartRejected)
+ return nullptr;
+
+ if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled, onStartRejected))
+ return nullptr;
+
+ return controller;
+}
+
+static MOZ_MUST_USE ReadableStreamBYOBRequest*
+CreateReadableStreamBYOBRequest(JSContext* cx, Handle<ReadableByteStreamController*> controller,
+ HandleObject view);
+
+// Streams spec, 3.10.4.1. get byobRequest
+static MOZ_MUST_USE bool
+ReadableByteStreamController_byobRequest_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &args.thisv().toObject().as<ReadableByteStreamController>();
+
+ // Step 2: If this.[[byobRequest]] is undefined and this.[[pendingPullIntos]]
+ // is not empty,
+ Value val = controller->getFixedSlot(ByteControllerSlot_BYOBRequest);
+ RootedValue byobRequest(cx, val);
+ val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos);
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+
+ if (byobRequest.isUndefined() && pendingPullIntos->getDenseInitializedLength() != 0) {
+ // Step a: Let firstDescriptor be the first element of this.[[pendingPullIntos]].
+ Rooted<PullIntoDescriptor*> firstDescriptor(cx);
+ firstDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos);
+
+ // Step b: Let view be ! Construct(%Uint8Array%,
+ // « firstDescriptor.[[buffer]],
+ // firstDescriptor.[[byteOffset]] + firstDescriptor.[[bytesFilled]],
+ // firstDescriptor.[[byteLength]] − firstDescriptor.[[bytesFilled]] »).
+ RootedArrayBufferObject buffer(cx, firstDescriptor->buffer());
+ uint32_t bytesFilled = firstDescriptor->bytesFilled();
+ RootedObject view(cx, JS_NewUint8ArrayWithBuffer(cx, buffer,
+ firstDescriptor->byteOffset() + bytesFilled,
+ firstDescriptor->byteLength() - bytesFilled));
+ if (!view)
+ return false;
+
+ // Step c: Set this.[[byobRequest]] to
+ // ! Construct(ReadableStreamBYOBRequest, « this, view »).
+ RootedObject request(cx, CreateReadableStreamBYOBRequest(cx, controller, view));
+ if (!request)
+ return false;
+ byobRequest = ObjectValue(*request);
+ controller->setFixedSlot(ByteControllerSlot_BYOBRequest, byobRequest);
+ }
+
+ // Step 3: Return this.[[byobRequest]].
+ args.rval().set(byobRequest);
+ return true;
+}
+
+static bool
+ReadableByteStreamController_byobRequest(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If IsReadableByteStreamController(this) is false, throw a TypeError
+ // exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableByteStreamController>,
+ ReadableByteStreamController_byobRequest_impl>(cx, args);
+}
+
+// Streams spec, 3.10.4.2. get desiredSize
+// Combined with 3.8.4.1 above.
+
+static bool
+ReadableByteStreamController_desiredSize(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableByteStreamController(this) is false, throw a
+ // TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableByteStreamController>,
+ ReadableStreamController_desiredSize_impl>(cx, args);
+}
+
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerClose(JSContext* cx, Handle<ReadableByteStreamController*> controller);
+
+// Streams spec, 3.10.4.3. close()
+static MOZ_MUST_USE bool
+ReadableByteStreamController_close_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &args.thisv().toObject().as<ReadableByteStreamController>();
+
+ // Steps 2-3.
+ if (!VerifyControllerStateForClosing(cx, controller))
+ return false;
+
+ // Step 4: Perform ? ReadableByteStreamControllerClose(this).
+ if (!ReadableByteStreamControllerClose(cx, controller))
+ return false;
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ReadableByteStreamController_close(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableByteStreamController(this) is false, throw a
+ // TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableByteStreamController>,
+ ReadableByteStreamController_close_impl>(cx, args);
+}
+
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerEnqueue(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ HandleObject chunk);
+
+// Streams spec, 3.10.4.4. enqueue ( chunk )
+static MOZ_MUST_USE bool
+ReadableByteStreamController_enqueue_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &args.thisv().toObject().as<ReadableByteStreamController>();
+ HandleValue chunkVal = args.get(0);
+
+ // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
+ if (ControllerFlags(controller) & ControllerFlag_CloseRequested) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_CLOSED, "enqueue");
+ return false;
+ }
+
+ // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable",
+ // throw a TypeError exception.
+ if (!StreamFromController(controller)->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "enqueue");
+ return false;
+ }
+
+ // Step 4: If Type(chunk) is not Object, throw a TypeError exception.
+ // Step 5: If chunk does not have a [[ViewedArrayBuffer]] internal slot,
+ // throw a TypeError exception.
+ if (!chunkVal.isObject() || !JS_IsArrayBufferViewObject(&chunkVal.toObject())) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK,
+ "ReadableByteStreamController#enqueue");
+ return false;
+ }
+ RootedObject chunk(cx, &chunkVal.toObject());
+
+ // Step 6: Return ! ReadableByteStreamControllerEnqueue(this, chunk).
+ if (!ReadableByteStreamControllerEnqueue(cx, controller, chunk))
+ return false;
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ReadableByteStreamController_enqueue(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableByteStreamController(this) is false, throw a
+ // TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableByteStreamController>,
+ ReadableByteStreamController_enqueue_impl>(cx, args);
+}
+
+// Streams spec, 3.10.4.5. error ( e )
+static MOZ_MUST_USE bool
+ReadableByteStreamController_error_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &args.thisv().toObject().as<ReadableByteStreamController>();
+ HandleValue e = args.get(0);
+
+ // Step 2: Let stream be this.[[controlledReadableStream]].
+ // Step 3: If stream.[[state]] is not "readable", throw a TypeError exception.
+ if (!StreamFromController(controller)->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "error");
+ return false;
+ }
+
+ // Step 4: Perform ! ReadableByteStreamControllerError(this, e).
+ if (!ReadableStreamControllerError(cx, controller, e))
+ return false;
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ReadableByteStreamController_error(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableByteStreamController(this) is false, throw a
+ // TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableByteStreamController>,
+ ReadableByteStreamController_error_impl>(cx, args);
+}
+
+static const JSPropertySpec ReadableByteStreamController_properties[] = {
+ JS_PSG("byobRequest", ReadableByteStreamController_byobRequest, 0),
+ JS_PSG("desiredSize", ReadableByteStreamController_desiredSize, 0),
+ JS_PS_END
+};
+
+static const JSFunctionSpec ReadableByteStreamController_methods[] = {
+ JS_FN("close", ReadableByteStreamController_close, 0, 0),
+ JS_FN("enqueue", ReadableByteStreamController_enqueue, 1, 0),
+ JS_FN("error", ReadableByteStreamController_error, 1, 0),
+ JS_FS_END
+};
+
+static void
+ReadableByteStreamControllerFinalize(FreeOp* fop, JSObject* obj)
+{
+ ReadableByteStreamController& controller = obj->as<ReadableByteStreamController>();
+
+ if (controller.getFixedSlot(ControllerSlot_Flags).isUndefined())
+ return;
+
+ uint32_t flags = ControllerFlags(&controller);
+ if (!(flags & ControllerFlag_ExternalSource))
+ return;
+
+ uint8_t embeddingFlags = flags >> ControllerEmbeddingFlagsOffset;
+
+ void* underlyingSource = controller.getFixedSlot(ControllerSlot_UnderlyingSource).toPrivate();
+ obj->runtimeFromAnyThread()->readableStreamFinalizeCallback(underlyingSource, embeddingFlags);
+}
+
+static const ClassOps ReadableByteStreamControllerClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ ReadableByteStreamControllerFinalize,
+ nullptr, /* call */
+ nullptr, /* hasInstance */
+ nullptr, /* construct */
+ nullptr, /* trace */
+};
+
+CLASS_SPEC(ReadableByteStreamController, 3, 9, ClassSpec::DontDefineConstructor,
+ JSCLASS_BACKGROUND_FINALIZE, &ReadableByteStreamControllerClassOps);
+
+// Streams spec, 3.10.5.1. [[PullSteps]] ()
+// Unified with 3.8.5.1 above.
+
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerHandleQueueDrain(JSContext* cx, HandleNativeObject controller);
+
+// Streams spec, 3.10.5.2. [[PullSteps]] ()
+static JSObject*
+ReadableByteStreamControllerPullSteps(JSContext* cx, HandleNativeObject controller)
+{
+ // Step 1: Let stream be this.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 2: MOZ_ASSERT: ! ReadableStreamHasDefaultReader(stream) is true.
+ MOZ_ASSERT(ReadableStreamHasDefaultReader(stream));
+
+ RootedValue val(cx);
+ // Step 3: If this.[[queueTotalSize]] > 0,
+ double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ if (queueTotalSize > 0) {
+ // Step 3.a: MOZ_ASSERT: ! ReadableStreamGetNumReadRequests(_stream_) is 0.
+ MOZ_ASSERT(ReadableStreamGetNumReadRequests(stream) == 0);
+
+ RootedObject view(cx);
+
+ if (stream->mode() == JS::ReadableStreamMode::ExternalSource) {
+ val = controller->getFixedSlot(ControllerSlot_UnderlyingSource);
+ void* underlyingSource = val.toPrivate();
+
+ view = JS_NewUint8Array(cx, queueTotalSize);
+ if (!view)
+ return nullptr;
+
+ size_t bytesWritten;
+ {
+ JS::AutoCheckCannotGC noGC(cx);
+ bool dummy;
+ void* buffer = JS_GetArrayBufferViewData(view, &dummy, noGC);
+ auto cb = cx->runtime()->readableStreamWriteIntoReadRequestCallback;
+ MOZ_ASSERT(cb);
+ // TODO: use bytesWritten to correctly update the request's state.
+ cb(cx, stream, underlyingSource, stream->embeddingFlags(), buffer,
+ queueTotalSize, &bytesWritten);
+ }
+
+ queueTotalSize = queueTotalSize - bytesWritten;
+ } else {
+ // Step 3.b: Let entry be the first element of this.[[queue]].
+ // Step 3.c: Remove entry from this.[[queue]], shifting all other elements
+ // downward (so that the second becomes the first, and so on).
+ val = controller->getFixedSlot(QueueContainerSlot_Queue);
+ RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());
+ Rooted<ByteStreamChunk*> entry(cx, ShiftFromList<ByteStreamChunk>(cx, queue));
+ MOZ_ASSERT(entry);
+
+ queueTotalSize = queueTotalSize - entry->byteLength();
+
+ // Step 3.f: Let view be ! Construct(%Uint8Array%, « entry.[[buffer]],
+ // entry.[[byteOffset]], entry.[[byteLength]] »).
+ // (reordered)
+ RootedObject buffer(cx, entry->buffer());
+
+ uint32_t byteOffset = entry->byteOffset();
+ view = JS_NewUint8ArrayWithBuffer(cx, buffer, byteOffset, entry->byteLength());
+ if (!view)
+ return nullptr;
+ }
+
+ // Step 3.d: Set this.[[queueTotalSize]] to
+ // this.[[queueTotalSize]] − entry.[[byteLength]].
+ // (reordered)
+ controller->setFixedSlot(QueueContainerSlot_TotalSize, Int32Value(queueTotalSize));
+
+ // Step 3.e: Perform ! ReadableByteStreamControllerHandleQueueDrain(this).
+ // (reordered)
+ if (!ReadableByteStreamControllerHandleQueueDrain(cx, controller))
+ return nullptr;
+
+ // Step 3.g: Return a promise resolved with ! CreateIterResultObject(view, false).
+ val.setObject(*view);
+ RootedObject iterResult(cx, CreateIterResultObject(cx, val, false));
+ if (!iterResult)
+ return nullptr;
+ val.setObject(*iterResult);
+
+ return PromiseObject::unforgeableResolve(cx, val);
+ }
+
+ // Step 4: Let autoAllocateChunkSize be this.[[autoAllocateChunkSize]].
+ val = controller->getFixedSlot(ByteControllerSlot_AutoAllocateSize);
+
+ // Step 5: If autoAllocateChunkSize is not undefined,
+ if (!val.isUndefined()) {
+ double autoAllocateChunkSize = val.toNumber();
+
+ // Step 5.a: Let buffer be Construct(%ArrayBuffer%, « autoAllocateChunkSize »).
+ RootedObject bufferObj(cx, JS_NewArrayBuffer(cx, autoAllocateChunkSize));
+
+ // Step 5.b: If buffer is an abrupt completion,
+ // return a promise rejected with buffer.[[Value]].
+ if (!bufferObj)
+ return PromiseRejectedWithPendingError(cx);
+
+ RootedArrayBufferObject buffer(cx, &bufferObj->as<ArrayBufferObject>());
+
+ // Step 5.c: Let pullIntoDescriptor be Record {[[buffer]]: buffer.[[Value]],
+ // [[byteOffset]]: 0,
+ // [[byteLength]]: autoAllocateChunkSize,
+ // [[bytesFilled]]: 0, [[elementSize]]: 1,
+ // [[ctor]]: %Uint8Array%,
+ // [[readerType]]: `"default"`}.
+ RootedObject pullIntoDescriptor(cx);
+ pullIntoDescriptor = PullIntoDescriptor::create(cx, buffer, 0,
+ autoAllocateChunkSize, 0, 1,
+ nullptr,
+ ReaderType_Default);
+ if (!pullIntoDescriptor)
+ return PromiseRejectedWithPendingError(cx);
+
+ // Step 5.d: Append pullIntoDescriptor as the last element of this.[[pendingPullIntos]].
+ val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos);
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+ val = ObjectValue(*pullIntoDescriptor);
+ if (!AppendToList(cx, pendingPullIntos, val))
+ return nullptr;
+ }
+
+ // Step 6: Let promise be ! ReadableStreamAddReadRequest(stream).
+ Rooted<PromiseObject*> promise(cx, ReadableStreamAddReadRequest(cx, stream));
+ if (!promise)
+ return nullptr;
+
+ // Step 7: Perform ! ReadableByteStreamControllerCallPullIfNeeded(this).
+ if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
+ return nullptr;
+
+ // Step 8: Return promise.
+ return promise;
+}
+
+/**
+ * Unified implementation of ReadableStream controllers' [[PullSteps]] internal
+ * methods.
+ * Streams spec, 3.8.5.2. [[PullSteps]] ()
+ * and
+ * Streams spec, 3.10.5.2. [[PullSteps]] ()
+ */
+static MOZ_MUST_USE JSObject*
+ReadableStreamControllerPullSteps(JSContext* cx, HandleNativeObject controller)
+{
+ MOZ_ASSERT(IsReadableStreamController(controller));
+
+ if (controller->is<ReadableStreamDefaultController>())
+ return ReadableStreamDefaultControllerPullSteps(cx, controller);
+
+ return ReadableByteStreamControllerPullSteps(cx, controller);
+}
+
+
+static MOZ_MUST_USE ReadableStreamBYOBRequest*
+CreateReadableStreamBYOBRequest(JSContext* cx, Handle<ReadableByteStreamController*> controller,
+ HandleObject view)
+{
+ MOZ_ASSERT(controller);
+ MOZ_ASSERT(JS_IsArrayBufferViewObject(view));
+
+ Rooted<ReadableStreamBYOBRequest*> request(cx);
+ request = NewBuiltinClassInstance<ReadableStreamBYOBRequest>(cx);
+ if (!request)
+ return nullptr;
+
+ // Step 1: Set this.[[associatedReadableByteStreamController]] to controller.
+ request->setFixedSlot(BYOBRequestSlot_Controller, ObjectValue(*controller));
+
+ // Step 2: Set this.[[view]] to view.
+ request->setFixedSlot(BYOBRequestSlot_View, ObjectValue(*view));
+
+ return request;
+}
+
+// Streams spec, 3.11.3. new ReadableStreamBYOBRequest ( controller, view )
+bool
+ReadableStreamBYOBRequest::constructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue controllerVal = args.get(0);
+ HandleValue viewVal = args.get(1);
+
+ if (!ThrowIfNotConstructing(cx, args, "ReadableStreamBYOBRequest"))
+ return false;
+
+ // TODO: open PR against spec to add these checks.
+ // They're expected to have happened in code using requests.
+ if (!Is<ReadableByteStreamController>(controllerVal)) {
+ ReportArgTypeError(cx, "ReadableStreamBYOBRequest",
+ "ReadableByteStreamController", args.get(0));
+ return false;
+ }
+
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &controllerVal.toObject().as<ReadableByteStreamController>();
+
+ if (!viewVal.isObject() || !JS_IsArrayBufferViewObject(&viewVal.toObject())) {
+ ReportArgTypeError(cx, "ReadableStreamBYOBRequest", "ArrayBuffer view",
+ args.get(1));
+ return false;
+ }
+
+ RootedArrayBufferObject view(cx, &viewVal.toObject().as<ArrayBufferObject>());
+
+ RootedObject request(cx, CreateReadableStreamBYOBRequest(cx, controller, view));
+ if (!request)
+ return false;
+
+ args.rval().setObject(*request);
+ return true;
+}
+
+// Streams spec, 3.11.4.1 get view
+static MOZ_MUST_USE bool
+ReadableStreamBYOBRequest_view_impl(JSContext* cx, const CallArgs& args)
+{
+ // Step 2: Return this.[[view]].
+ NativeObject* request = &args.thisv().toObject().as<NativeObject>();
+ args.rval().set(request->getFixedSlot(BYOBRequestSlot_View));
+ return true;
+}
+
+static bool
+ReadableStreamBYOBRequest_view(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStreamBYOBRequest(this) is false, throw a TypeError
+ // exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStreamBYOBRequest>,
+ ReadableStreamBYOBRequest_view_impl>(cx, args);
+}
+
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerRespond(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ HandleValue bytesWrittenVal);
+
+// Streams spec, 3.11.4.2. respond ( bytesWritten )
+static MOZ_MUST_USE bool
+ReadableStreamBYOBRequest_respond_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStreamBYOBRequest*> request(cx);
+ request = &args.thisv().toObject().as<ReadableStreamBYOBRequest>();
+ HandleValue bytesWritten = args.get(0);
+
+ // Step 2: If this.[[associatedReadableByteStreamController]] is undefined,
+ // throw a TypeError exception.
+ RootedValue controllerVal(cx, request->getFixedSlot(BYOBRequestSlot_Controller));
+ if (controllerVal.isUndefined()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMBYOBREQUEST_NO_CONTROLLER, "respond");
+ return false;
+ }
+
+ // Step 3: Return ?
+ // ReadableByteStreamControllerRespond(this.[[associatedReadableByteStreamController]],
+ // bytesWritten).
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &controllerVal.toObject().as<ReadableByteStreamController>();
+
+ if (!ReadableByteStreamControllerRespond(cx, controller, bytesWritten))
+ return false;
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ReadableStreamBYOBRequest_respond(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStreamBYOBRequest(this) is false, throw a TypeError
+ // exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStreamBYOBRequest>,
+ ReadableStreamBYOBRequest_respond_impl>(cx, args);
+}
+
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerRespondWithNewView(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ HandleObject view);
+
+// Streams spec, 3.11.4.3. respondWithNewView ( view )
+static MOZ_MUST_USE bool
+ReadableStreamBYOBRequest_respondWithNewView_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStreamBYOBRequest*> request(cx);
+ request = &args.thisv().toObject().as<ReadableStreamBYOBRequest>();
+ HandleValue viewVal = args.get(0);
+
+ // Step 2: If this.[[associatedReadableByteStreamController]] is undefined,
+ // throw a TypeError exception.
+ RootedValue controllerVal(cx, request->getFixedSlot(BYOBRequestSlot_Controller));
+ if (controllerVal.isUndefined()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMBYOBREQUEST_NO_CONTROLLER, "respondWithNewView");
+ return false;
+ }
+
+ // Step 3: If Type(chunk) is not Object, throw a TypeError exception.
+ // Step 4: If view does not have a [[ViewedArrayBuffer]] internal slot, throw
+ // a TypeError exception.
+ if (!viewVal.isObject() || !JS_IsArrayBufferViewObject(&viewVal.toObject())) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK,
+ "ReadableStreamBYOBRequest#respondWithNewView");
+ return false;
+ }
+
+ // Step 5: Return ?
+ // ReadableByteStreamControllerRespondWithNewView(this.[[associatedReadableByteStreamController]],
+ // view).
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &controllerVal.toObject().as<ReadableByteStreamController>();
+ RootedObject view(cx, &viewVal.toObject());
+
+ if (!ReadableByteStreamControllerRespondWithNewView(cx, controller, view))
+ return false;
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ReadableStreamBYOBRequest_respondWithNewView(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStreamBYOBRequest(this) is false, throw a TypeError
+ // exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStreamBYOBRequest>,
+ ReadableStreamBYOBRequest_respondWithNewView_impl>(cx, args);
+}
+
+static const JSPropertySpec ReadableStreamBYOBRequest_properties[] = {
+ JS_PSG("view", ReadableStreamBYOBRequest_view, 0),
+ JS_PS_END
+};
+
+static const JSFunctionSpec ReadableStreamBYOBRequest_methods[] = {
+ JS_FN("respond", ReadableStreamBYOBRequest_respond, 1, 0),
+ JS_FN("respondWithNewView", ReadableStreamBYOBRequest_respondWithNewView, 1, 0),
+ JS_FS_END
+};
+
+CLASS_SPEC(ReadableStreamBYOBRequest, 3, 2, ClassSpec::DontDefineConstructor, 0,
+ JS_NULL_CLASS_OPS);
+
+// Streams spec, 3.12.1. IsReadableStreamBYOBRequest ( x )
+// Implemented via is<ReadableStreamBYOBRequest>()
+
+// Streams spec, 3.12.2. IsReadableByteStreamController ( x )
+// Implemented via is<ReadableByteStreamController>()
+
+// Streams spec, 3.12.3. ReadableByteStreamControllerCallPullIfNeeded ( controller )
+// Unified with 3.9.2 above.
+
+static void
+ReadableByteStreamControllerInvalidateBYOBRequest(NativeObject* controller);
+
+// Streams spec, 3.12.4. ReadableByteStreamControllerClearPendingPullIntos ( controller )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerClearPendingPullIntos(JSContext* cx, HandleNativeObject controller)
+{
+ MOZ_ASSERT(controller->is<ReadableByteStreamController>());
+
+ // Step 1: Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller).
+ ReadableByteStreamControllerInvalidateBYOBRequest(controller);
+
+ // Step 2: Set controller.[[pendingPullIntos]] to a new empty List.
+ return SetNewList(cx, controller, ByteControllerSlot_PendingPullIntos);
+}
+
+// Streams spec, 3.12.5. ReadableByteStreamControllerClose ( controller )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerClose(JSContext* cx, Handle<ReadableByteStreamController*> controller)
+{
+ // Step 1: Let stream be controller.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 2: Assert: controller.[[closeRequested]] is false.
+ MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));
+
+ // Step 3: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->readable());
+
+ // Step 4: If controller.[[queueTotalSize]] > 0,
+ double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ if (queueTotalSize > 0) {
+ // Step a: Set controller.[[closeRequested]] to true.
+ AddControllerFlags(controller, ControllerFlag_CloseRequested);
+
+ // Step b: Return
+ return true;
+ }
+
+ // Step 5: If controller.[[pendingPullIntos]] is not empty,
+ RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+ if (pendingPullIntos->getDenseInitializedLength() != 0) {
+ // Step a: Let firstPendingPullInto be the first element of
+ // controller.[[pendingPullIntos]].
+ Rooted<PullIntoDescriptor*> firstPendingPullInto(cx);
+ firstPendingPullInto = PeekList<PullIntoDescriptor>(pendingPullIntos);
+
+ // Step b: If firstPendingPullInto.[[bytesFilled]] > 0,
+ if (firstPendingPullInto->bytesFilled() > 0) {
+ // Step i: Let e be a new TypeError exception. {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLEBYTESTREAMCONTROLLER_CLOSE_PENDING_PULL);
+ RootedValue e(cx);
+ // Not much we can do about uncatchable exceptions, just bail.
+ if (!cx->getPendingException(&e))
+ return false;
+ // Step ii: Perform ! ReadableByteStreamControllerError(controller, e).
+ if (!ReadableStreamControllerError(cx, controller, e))
+ return false;
+
+ // Step iii: Throw e.
+ return false;
+ }
+ }
+
+ // Step 6: Perform ! ReadableStreamClose(stream).
+ return ReadableStreamCloseInternal(cx, stream);
+}
+
+static MOZ_MUST_USE JSObject*
+ReadableByteStreamControllerConvertPullIntoDescriptor(JSContext* cx,
+ Handle<PullIntoDescriptor*> pullIntoDescriptor);
+
+// Streams spec, 3.12.6. ReadableByteStreamControllerCommitPullIntoDescriptor ( stream, pullIntoDescriptor )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerCommitPullIntoDescriptor(JSContext* cx, Handle<ReadableStream*> stream,
+ Handle<PullIntoDescriptor*> pullIntoDescriptor)
+{
+ // Step 1: MOZ_ASSERT: stream.[[state]] is not "errored".
+ MOZ_ASSERT(!stream->errored());
+
+ // Step 2: Let done be false.
+ bool done = false;
+
+ // Step 3: If stream.[[state]] is "closed",
+ if (stream->closed()) {
+ // Step a: MOZ_ASSERT: pullIntoDescriptor.[[bytesFilled]] is 0.
+ MOZ_ASSERT(pullIntoDescriptor->bytesFilled() == 0);
+
+ // Step b: Set done to true.
+ done = true;
+ }
+
+ // Step 4: Let filledView be
+ // ! ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor).
+ RootedObject filledView(cx);
+ filledView = ReadableByteStreamControllerConvertPullIntoDescriptor(cx, pullIntoDescriptor);
+ if (!filledView)
+ return false;
+
+ // Step 5: If pullIntoDescriptor.[[readerType]] is "default",
+ uint32_t readerType = pullIntoDescriptor->readerType();
+ RootedValue filledViewVal(cx, ObjectValue(*filledView));
+ if (readerType == ReaderType_Default) {
+ // Step a: Perform ! ReadableStreamFulfillReadRequest(stream, filledView, done).
+ if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, filledViewVal, done))
+ return false;
+ } else {
+ // Step 6: Otherwise,
+ // Step a: MOZ_ASSERT: pullIntoDescriptor.[[readerType]] is "byob".
+ MOZ_ASSERT(readerType == ReaderType_BYOB);
+
+ // Step b: Perform ! ReadableStreamFulfillReadIntoRequest(stream, filledView, done).
+ if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, filledViewVal, done))
+ return false;
+ }
+
+ return true;
+}
+
+// Streams spec, 3.12.7. ReadableByteStreamControllerConvertPullIntoDescriptor ( pullIntoDescriptor )
+static MOZ_MUST_USE JSObject*
+ReadableByteStreamControllerConvertPullIntoDescriptor(JSContext* cx,
+ Handle<PullIntoDescriptor*> pullIntoDescriptor)
+{
+ // Step 1: Let bytesFilled be pullIntoDescriptor.[[bytesFilled]].
+ uint32_t bytesFilled = pullIntoDescriptor->bytesFilled();
+
+ // Step 2: Let elementSize be pullIntoDescriptor.[[elementSize]].
+ uint32_t elementSize = pullIntoDescriptor->elementSize();
+
+ // Step 3: Assert: bytesFilled <= pullIntoDescriptor.[[byteLength]].
+ MOZ_ASSERT(bytesFilled <= pullIntoDescriptor->byteLength());
+
+ // Step 4: Assert: bytesFilled mod elementSize is 0.
+ MOZ_ASSERT(bytesFilled % elementSize == 0);
+
+ // Step 5: Return ! Construct(pullIntoDescriptor.[[ctor]],
+ // pullIntoDescriptor.[[buffer]],
+ // pullIntoDescriptor.[[byteOffset]],
+ // bytesFilled / elementSize).
+ RootedObject ctor(cx, pullIntoDescriptor->ctor());
+ if (!ctor) {
+ if (!GetBuiltinConstructor(cx, JSProto_Uint8Array, &ctor))
+ return nullptr;
+ }
+ RootedObject buffer(cx, pullIntoDescriptor->buffer());
+ uint32_t byteOffset = pullIntoDescriptor->byteOffset();
+ FixedConstructArgs<3> args(cx);
+ args[0].setObject(*buffer);
+ args[1].setInt32(byteOffset);
+ args[2].setInt32(bytesFilled / elementSize);
+ return JS_New(cx, ctor, args);
+}
+
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerEnqueueChunkToQueue(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ HandleObject buffer, uint32_t byteOffset,
+ uint32_t byteLength);
+
+static MOZ_MUST_USE ArrayBufferObject*
+TransferArrayBuffer(JSContext* cx, HandleObject buffer);
+
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller);
+
+// Streams spec, 3.12.8. ReadableByteStreamControllerEnqueue ( controller, chunk )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerEnqueue(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ HandleObject chunk)
+{
+ // Step 1: Let stream be controller.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 2: Assert: controller.[[closeRequested]] is false.
+ MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));
+
+ // Step 3: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->readable());
+
+ // To make enqueuing chunks via JSAPI nicer, we want to be able to deal
+ // with ArrayBuffer objects in addition to ArrayBuffer views here.
+ // This cannot happen when enqueuing happens via
+ // ReadableByteStreamController_enqueue because that throws if invoked
+ // with anything but an ArrayBuffer view.
+
+ Rooted<ArrayBufferObject*> buffer(cx);
+ uint32_t byteOffset;
+ uint32_t byteLength;
+
+ if (chunk->is<ArrayBufferObject>()) {
+ // Steps 4-6 for ArrayBuffer objects.
+ buffer = &chunk->as<ArrayBufferObject>();
+ byteOffset = 0;
+ byteLength = buffer->byteLength();
+ } else {
+ // Step 4: Let buffer be chunk.[[ViewedArrayBuffer]].
+ bool dummy;
+ JSObject* bufferObj = JS_GetArrayBufferViewBuffer(cx, chunk, &dummy);
+ if (!bufferObj)
+ return false;
+ buffer = &bufferObj->as<ArrayBufferObject>();
+
+ // Step 5: Let byteOffset be chunk.[[ByteOffset]].
+ byteOffset = JS_GetArrayBufferViewByteOffset(chunk);
+
+ // Step 6: Let byteLength be chunk.[[ByteLength]].
+ byteLength = JS_GetArrayBufferViewByteLength(chunk);
+ }
+
+ // Step 7: Let transferredBuffer be ! TransferArrayBuffer(buffer).
+ RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer));
+ if (!transferredBuffer)
+ return false;
+
+ // Step 8: If ! ReadableStreamHasDefaultReader(stream) is true
+ if (ReadableStreamHasDefaultReader(stream)) {
+ // Step a: If ! ReadableStreamGetNumReadRequests(stream) is 0,
+ if (ReadableStreamGetNumReadRequests(stream) == 0) {
+ // Step i: Perform
+ // ! ReadableByteStreamControllerEnqueueChunkToQueue(controller,
+ // transferredBuffer,
+ // byteOffset,
+ // byteLength).
+ if (!ReadableByteStreamControllerEnqueueChunkToQueue(cx, controller, transferredBuffer,
+ byteOffset, byteLength))
+ {
+ return false;
+ }
+ } else {
+ // Step b: Otherwise,
+ // Step i: Assert: controller.[[queue]] is empty.
+#if DEBUG
+ RootedValue val(cx, controller->getFixedSlot(QueueContainerSlot_Queue));
+ RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());
+ MOZ_ASSERT(queue->getDenseInitializedLength() == 0);
+#endif // DEBUG
+
+ // Step ii: Let transferredView be
+ // ! Construct(%Uint8Array%, transferredBuffer, byteOffset, byteLength).
+ RootedObject transferredView(cx, JS_NewUint8ArrayWithBuffer(cx, transferredBuffer,
+ byteOffset, byteLength));
+ if (!transferredView)
+ return false;
+
+ // Step iii: Perform ! ReadableStreamFulfillReadRequest(stream, transferredView, false).
+ RootedValue chunk(cx, ObjectValue(*transferredView));
+ if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, chunk, false))
+ return false;
+ }
+ } else if (ReadableStreamHasBYOBReader(stream)) {
+ // Step 9: Otherwise,
+ // Step a: If ! ReadableStreamHasBYOBReader(stream) is true,
+ // Step i: Perform
+ // ! ReadableByteStreamControllerEnqueueChunkToQueue(controller,
+ // transferredBuffer,
+ // byteOffset,
+ // byteLength).
+ if (!ReadableByteStreamControllerEnqueueChunkToQueue(cx, controller, transferredBuffer,
+ byteOffset, byteLength))
+ {
+ return false;
+ }
+
+ // Step ii: Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
+ if (!ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(cx, controller))
+ return false;
+ } else {
+ // Step b: Otherwise,
+ // Step i: Assert: ! IsReadableStreamLocked(stream) is false.
+ MOZ_ASSERT(!stream->locked());
+
+ // Step ii: Perform
+ // ! ReadableByteStreamControllerEnqueueChunkToQueue(controller,
+ // transferredBuffer,
+ // byteOffset,
+ // byteLength).
+ if (!ReadableByteStreamControllerEnqueueChunkToQueue(cx, controller, transferredBuffer,
+ byteOffset, byteLength))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// Streams spec, 3.12.9.
+// ReadableByteStreamControllerEnqueueChunkToQueue ( controller, buffer,
+// byteOffset, byteLength )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerEnqueueChunkToQueue(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ HandleObject buffer, uint32_t byteOffset,
+ uint32_t byteLength)
+{
+ MOZ_ASSERT(controller->is<ReadableByteStreamController>(), "must operate on ReadableByteStreamController");
+
+ // Step 1: Append Record {[[buffer]]: buffer,
+ // [[byteOffset]]: byteOffset,
+ // [[byteLength]]: byteLength}
+ // as the last element of controller.[[queue]].
+ RootedValue val(cx, controller->getFixedSlot(QueueContainerSlot_Queue));
+ RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());
+
+ Rooted<ByteStreamChunk*> chunk(cx);
+ chunk = ByteStreamChunk::create(cx, buffer, byteOffset, byteLength);
+ if (!chunk)
+ return false;
+
+ RootedValue chunkVal(cx, ObjectValue(*chunk));
+ if (!AppendToList(cx, queue, chunkVal))
+ return false;
+
+ // Step 2: Add byteLength to controller.[[queueTotalSize]].
+ double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ controller->setFixedSlot(QueueContainerSlot_TotalSize,
+ NumberValue(queueTotalSize + byteLength));
+
+ return true;
+}
+
+// Streams spec, 3.12.10. ReadableByteStreamControllerError ( controller, e )
+// Unified with 3.9.6 above.
+
+// Streams spec, 3.12.11. ReadableByteStreamControllerFillHeadPullIntoDescriptor ( controler, size, pullIntoDescriptor )
+static void
+ReadableByteStreamControllerFillHeadPullIntoDescriptor(ReadableByteStreamController* controller, uint32_t size,
+ Handle<PullIntoDescriptor*> pullIntoDescriptor)
+{
+ // Step 1: Assert: either controller.[[pendingPullIntos]] is empty, or the
+ // first element of controller.[[pendingPullIntos]] is pullIntoDescriptor.
+#if DEBUG
+ Value val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos);
+ NativeObject* pendingPullIntos = &val.toObject().as<NativeObject>();
+ MOZ_ASSERT(pendingPullIntos->getDenseInitializedLength() == 0 ||
+ &pendingPullIntos->getDenseElement(0).toObject() == pullIntoDescriptor);
+#endif // DEBUG
+
+ // Step 2: Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller).
+ ReadableByteStreamControllerInvalidateBYOBRequest(controller);
+
+ // Step 3: Set pullIntoDescriptor.[[bytesFilled]] to pullIntoDescriptor.[[bytesFilled]] + size.
+ pullIntoDescriptor->setBytesFilled(pullIntoDescriptor->bytesFilled() + size);
+}
+
+// Streams spec, 3.12.12. ReadableByteStreamControllerFillPullIntoDescriptorFromQueue ( controller, pullIntoDescriptor )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ Handle<PullIntoDescriptor*> pullIntoDescriptor,
+ bool* ready)
+{
+ *ready = false;
+
+ // Step 1: Let elementSize be pullIntoDescriptor.[[elementSize]].
+ uint32_t elementSize = pullIntoDescriptor->elementSize();
+
+ // Step 2: Let currentAlignedBytes be pullIntoDescriptor.[[bytesFilled]] −
+ // (pullIntoDescriptor.[[bytesFilled]] mod elementSize).
+ uint32_t bytesFilled = pullIntoDescriptor->bytesFilled();
+ uint32_t currentAlignedBytes = bytesFilled - (bytesFilled % elementSize);
+
+ // Step 3: Let maxBytesToCopy be min(controller.[[queueTotalSize]],
+ // pullIntoDescriptor.[[byteLength]] − pullIntoDescriptor.[[bytesFilled]]).
+ uint32_t byteLength = pullIntoDescriptor->byteLength();
+
+ // The queue size could be negative or overflow uint32_t. We cannot
+ // validly have a maxBytesToCopy value that'd overflow uint32_t, though,
+ // so just clamp to that.
+ Value sizeVal = controller->getFixedSlot(QueueContainerSlot_TotalSize);
+ uint32_t queueTotalSize = JS::ToUint32(sizeVal.toNumber());
+ uint32_t maxBytesToCopy = std::min(queueTotalSize, byteLength - bytesFilled);
+
+ // Step 4: Let maxBytesFilled be pullIntoDescriptor.[[bytesFilled]] + maxBytesToCopy.
+ uint32_t maxBytesFilled = bytesFilled + maxBytesToCopy;
+
+ // Step 5: Let maxAlignedBytes be maxBytesFilled − (maxBytesFilled mod elementSize).
+ uint32_t maxAlignedBytes = maxBytesFilled - (maxBytesFilled % elementSize);
+
+ // Step 6: Let totalBytesToCopyRemaining be maxBytesToCopy.
+ uint32_t totalBytesToCopyRemaining = maxBytesToCopy;
+
+ // Step 7: Let ready be false (implicit).
+
+ // Step 8: If maxAlignedBytes > currentAlignedBytes,
+ if (maxAlignedBytes > currentAlignedBytes) {
+ // Step a: Set totalBytesToCopyRemaining to maxAlignedBytes −
+ // pullIntoDescriptor.[[bytesFilled]].
+ totalBytesToCopyRemaining = maxAlignedBytes - bytesFilled;
+
+ // Step b: Let ready be true.
+ *ready = true;
+ }
+
+ if (ControllerFlags(controller) & ControllerFlag_ExternalSource) {
+ // TODO: it probably makes sense to eagerly drain the underlying source.
+ // We have a buffer lying around anyway, whereas the source might be
+ // able to free or reuse buffers once their content is copied into
+ // our buffer.
+ if (!ready)
+ return true;
+
+ Value val = controller->getFixedSlot(ControllerSlot_UnderlyingSource);
+ void* underlyingSource = val.toPrivate();
+
+ RootedArrayBufferObject targetBuffer(cx, pullIntoDescriptor->buffer());
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ size_t bytesWritten;
+ {
+ JS::AutoCheckCannotGC noGC(cx);
+ bool dummy;
+ uint8_t* buffer = JS_GetArrayBufferData(targetBuffer, &dummy, noGC);
+ buffer += bytesFilled;
+ auto cb = cx->runtime()->readableStreamWriteIntoReadRequestCallback;
+ MOZ_ASSERT(cb);
+ cb(cx, stream, underlyingSource, stream->embeddingFlags(), buffer,
+ totalBytesToCopyRemaining, &bytesWritten);
+ pullIntoDescriptor->setBytesFilled(bytesFilled + bytesWritten);
+ }
+
+ queueTotalSize -= bytesWritten;
+ controller->setFixedSlot(QueueContainerSlot_TotalSize, Int32Value(queueTotalSize));
+
+ return true;
+ }
+
+ // Step 9: Let queue be controller.[[queue]].
+ RootedValue val(cx, controller->getFixedSlot(QueueContainerSlot_Queue));
+ RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());
+
+ // Step 10: Repeat the following steps while totalBytesToCopyRemaining > 0,
+ Rooted<ByteStreamChunk*> headOfQueue(cx);
+ while (totalBytesToCopyRemaining > 0) {
+ MOZ_ASSERT(queue->getDenseInitializedLength() != 0);
+
+ // Step a: Let headOfQueue be the first element of queue.
+ headOfQueue = PeekList<ByteStreamChunk>(queue);
+
+ // Step b: Let bytesToCopy be min(totalBytesToCopyRemaining,
+ // headOfQueue.[[byteLength]]).
+ uint32_t byteLength = headOfQueue->byteLength();
+ uint32_t bytesToCopy = std::min(totalBytesToCopyRemaining, byteLength);
+
+ // Step c: Let destStart be pullIntoDescriptor.[[byteOffset]] +
+ // pullIntoDescriptor.[[bytesFilled]].
+ uint32_t destStart = pullIntoDescriptor->byteOffset() + bytesFilled;
+
+ // Step d: Perform ! CopyDataBlockBytes(pullIntoDescriptor.[[buffer]].[[ArrayBufferData]],
+ // destStart,
+ // headOfQueue.[[buffer]].[[ArrayBufferData]],
+ // headOfQueue.[[byteOffset]],
+ // bytesToCopy).
+ RootedArrayBufferObject sourceBuffer(cx, headOfQueue->buffer());
+ uint32_t sourceOffset = headOfQueue->byteOffset();
+ RootedArrayBufferObject targetBuffer(cx, pullIntoDescriptor->buffer());
+ ArrayBufferObject::copyData(targetBuffer, destStart, sourceBuffer, sourceOffset,
+ bytesToCopy);
+
+ // Step e: If headOfQueue.[[byteLength]] is bytesToCopy,
+ if (byteLength == bytesToCopy) {
+ // Step i: Remove the first element of queue, shifting all other elements
+ // downward (so that the second becomes the first, and so on).
+ headOfQueue = ShiftFromList<ByteStreamChunk>(cx, queue);
+ MOZ_ASSERT(headOfQueue);
+ } else {
+ // Step f: Otherwise,
+ // Step i: Set headOfQueue.[[byteOffset]] to headOfQueue.[[byteOffset]] +
+ // bytesToCopy.
+ headOfQueue->SetByteOffset(sourceOffset + bytesToCopy);
+
+ // Step ii: Set headOfQueue.[[byteLength]] to headOfQueue.[[byteLength]] −
+ // bytesToCopy.
+ headOfQueue->SetByteLength(byteLength - bytesToCopy);
+ }
+
+ // Step g: Set controller.[[queueTotalSize]] to
+ // controller.[[queueTotalSize]] − bytesToCopy.
+ queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ queueTotalSize -= bytesToCopy;
+ controller->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(queueTotalSize));
+
+ // Step h: Perform ! ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller,
+ // bytesToCopy,
+ // pullIntoDescriptor).
+ ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, bytesToCopy,
+ pullIntoDescriptor);
+ bytesFilled += bytesToCopy;
+ MOZ_ASSERT(bytesFilled == pullIntoDescriptor->bytesFilled());
+
+ // Step i: Set totalBytesToCopyRemaining to totalBytesToCopyRemaining − bytesToCopy.
+ totalBytesToCopyRemaining -= bytesToCopy;
+ }
+
+ // Step 11: If ready is false,
+ if (!*ready) {
+ // Step a: Assert: controller.[[queueTotalSize]] is 0.
+ MOZ_ASSERT(controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber() == 0);
+
+ // Step b: Assert: pullIntoDescriptor.[[bytesFilled]] > 0.
+ MOZ_ASSERT(bytesFilled > 0, "should have filled some bytes");
+
+ // Step c: Assert: pullIntoDescriptor.[[bytesFilled]] <
+ // pullIntoDescriptor.[[elementSize]].
+ MOZ_ASSERT(bytesFilled < elementSize);
+ }
+
+ // Step 12: Return ready.
+ return true;
+}
+
+// Streams spec 3.12.13. ReadableByteStreamControllerGetDesiredSize ( controller )
+// Unified with 3.9.8 above.
+
+// Streams spec, 3.12.14. ReadableByteStreamControllerHandleQueueDrain ( controller )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerHandleQueueDrain(JSContext* cx, HandleNativeObject controller)
+{
+ MOZ_ASSERT(controller->is<ReadableByteStreamController>());
+
+ // Step 1: Assert: controller.[[controlledReadableStream]].[[state]] is "readable".
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+ MOZ_ASSERT(stream->readable());
+
+ // Step 2: If controller.[[queueTotalSize]] is 0 and
+ // controller.[[closeRequested]] is true,
+ double totalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ bool closeRequested = ControllerFlags(controller) & ControllerFlag_CloseRequested;
+ if (totalSize == 0 && closeRequested) {
+ // Step a: Perform ! ReadableStreamClose(controller.[[controlledReadableStream]]).
+ return ReadableStreamCloseInternal(cx, stream);
+ }
+
+ // Step 3: Otherwise,
+ // Step a: Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller).
+ return ReadableStreamControllerCallPullIfNeeded(cx, controller);
+}
+
+// Streams spec 3.12.15. ReadableByteStreamControllerInvalidateBYOBRequest ( controller )
+static void
+ReadableByteStreamControllerInvalidateBYOBRequest(NativeObject* controller)
+{
+ MOZ_ASSERT(controller->is<ReadableByteStreamController>());
+
+ // Step 1: If controller.[[byobRequest]] is undefined, return.
+ Value byobRequestVal = controller->getFixedSlot(ByteControllerSlot_BYOBRequest);
+ if (byobRequestVal.isUndefined())
+ return;
+
+ NativeObject* byobRequest = &byobRequestVal.toObject().as<NativeObject>();
+ // Step 2: Set controller.[[byobRequest]].[[associatedReadableByteStreamController]]
+ // to undefined.
+ byobRequest->setFixedSlot(BYOBRequestSlot_Controller, UndefinedValue());
+
+ // Step 3: Set controller.[[byobRequest]].[[view]] to undefined.
+ byobRequest->setFixedSlot(BYOBRequestSlot_View, UndefinedValue());
+
+ // Step 4: Set controller.[[byobRequest]] to undefined.
+ controller->setFixedSlot(ByteControllerSlot_BYOBRequest, UndefinedValue());
+}
+
+static MOZ_MUST_USE PullIntoDescriptor*
+ReadableByteStreamControllerShiftPendingPullInto(JSContext* cx, HandleNativeObject controller);
+
+// Streams spec 3.12.16. ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue ( controller )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller)
+{
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 1: Assert: controller.[[closeRequested]] is false.
+ MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));
+
+ // Step 2: Repeat the following steps while controller.[[pendingPullIntos]]
+ // is not empty,
+ RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+ Rooted<PullIntoDescriptor*> pullIntoDescriptor(cx);
+ while (pendingPullIntos->getDenseInitializedLength() != 0) {
+ // Step a: If controller.[[queueTotalSize]] is 0, return.
+ double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ if (queueTotalSize == 0)
+ return true;
+
+ // Step b: Let pullIntoDescriptor be the first element of
+ // controller.[[pendingPullIntos]].
+ pullIntoDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos);
+
+ // Step c: If ! ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, pullIntoDescriptor)
+ // is true,
+ bool ready;
+ if (!ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(cx, controller,
+ pullIntoDescriptor,
+ &ready))
+ {
+ return false;
+ }
+ if (ready) {
+ // Step i: Perform ! ReadableByteStreamControllerShiftPendingPullInto(controller).
+ if (!ReadableByteStreamControllerShiftPendingPullInto(cx, controller))
+ return false;
+
+ // Step ii: Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[controlledReadableStream]],
+ // pullIntoDescriptor).
+ if (!ReadableByteStreamControllerCommitPullIntoDescriptor(cx, stream,
+ pullIntoDescriptor))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+// Streams spec, 3.12.17. ReadableByteStreamControllerPullInto ( controller, view )
+static MOZ_MUST_USE JSObject*
+ReadableByteStreamControllerPullInto(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ Handle<ArrayBufferViewObject*> view)
+{
+ MOZ_ASSERT(controller->is<ReadableByteStreamController>());
+
+ // Step 1: Let stream be controller.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 2: Let elementSize be 1.
+ uint32_t elementSize = 1;
+
+ RootedObject ctor(cx);
+ // Step 4: If view has a [[TypedArrayName]] internal slot (i.e., it is not a
+ // DataView),
+ if (view->is<TypedArrayObject>()) {
+ JSProtoKey protoKey = StandardProtoKeyOrNull(view);
+ MOZ_ASSERT(protoKey);
+
+ if (!GetBuiltinConstructor(cx, protoKey, &ctor))
+ return nullptr;
+ elementSize = 1 << TypedArrayShift(view->as<TypedArrayObject>().type());
+ } else {
+ // Step 3: Let ctor be %DataView% (reordered).
+ if (!GetBuiltinConstructor(cx, JSProto_DataView, &ctor))
+ return nullptr;
+ }
+
+ // Step 5: Let pullIntoDescriptor be Record {[[buffer]]: view.[[ViewedArrayBuffer]],
+ // [[byteOffset]]: view.[[ByteOffset]],
+ // [[byteLength]]: view.[[ByteLength]],
+ // [[bytesFilled]]: 0,
+ // [[elementSize]]: elementSize,
+ // [[ctor]]: ctor,
+ // [[readerType]]: "byob"}.
+ bool dummy;
+ RootedArrayBufferObject buffer(cx, &JS_GetArrayBufferViewBuffer(cx, view, &dummy)
+ ->as<ArrayBufferObject>());
+ if (!buffer)
+ return nullptr;
+
+ uint32_t byteOffset = JS_GetArrayBufferViewByteOffset(view);
+ uint32_t byteLength = JS_GetArrayBufferViewByteLength(view);
+ Rooted<PullIntoDescriptor*> pullIntoDescriptor(cx);
+ pullIntoDescriptor = PullIntoDescriptor::create(cx, buffer, byteOffset, byteLength, 0,
+ elementSize, ctor,
+ ReaderType_BYOB);
+ if (!pullIntoDescriptor)
+ return nullptr;
+
+ // Step 6: If controller.[[pendingPullIntos]] is not empty,
+ RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+ if (pendingPullIntos->getDenseInitializedLength() != 0) {
+ // Step a: Set pullIntoDescriptor.[[buffer]] to
+ // ! TransferArrayBuffer(pullIntoDescriptor.[[buffer]]).
+ RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer));
+ if (!transferredBuffer)
+ return nullptr;
+ pullIntoDescriptor->setBuffer(transferredBuffer);
+
+ // Step b: Append pullIntoDescriptor as the last element of
+ // controller.[[pendingPullIntos]].
+ val = ObjectValue(*pullIntoDescriptor);
+ if (!AppendToList(cx, pendingPullIntos, val))
+ return nullptr;
+
+ // Step c: Return ! ReadableStreamAddReadIntoRequest(stream).
+ return ReadableStreamAddReadIntoRequest(cx, stream);
+ }
+
+ // Step 7: If stream.[[state]] is "closed",
+ if (stream->closed()) {
+ // Step a: Let emptyView be ! Construct(ctor, pullIntoDescriptor.[[buffer]],
+ // pullIntoDescriptor.[[byteOffset]], 0).
+ FixedConstructArgs<3> args(cx);
+ args[0].setObject(*buffer);
+ args[1].setInt32(byteOffset);
+ args[2].setInt32(0);
+ RootedObject emptyView(cx, JS_New(cx, ctor, args));
+ if (!emptyView)
+ return nullptr;
+
+ // Step b: Return a promise resolved with
+ // ! CreateIterResultObject(emptyView, true).
+ RootedValue val(cx, ObjectValue(*emptyView));
+ RootedObject iterResult(cx, CreateIterResultObject(cx, val, true));
+ if (!iterResult)
+ return nullptr;
+ val = ObjectValue(*iterResult);
+ return PromiseObject::unforgeableResolve(cx, val);
+ }
+
+ // Step 8: If controller.[[queueTotalSize]] > 0,
+ double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ if (queueTotalSize > 0) {
+ // Step a: If ! ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller,
+ // pullIntoDescriptor)
+ // is true,
+ bool ready;
+ if (!ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(cx, controller,
+ pullIntoDescriptor, &ready))
+ {
+ return nullptr;
+ }
+
+ if (ready) {
+ // Step i: Let filledView be
+ // ! ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor).
+ RootedObject filledView(cx);
+ filledView = ReadableByteStreamControllerConvertPullIntoDescriptor(cx,
+ pullIntoDescriptor);
+ if (!filledView)
+ return nullptr;
+
+ // Step ii: Perform ! ReadableByteStreamControllerHandleQueueDrain(controller).
+ if (!ReadableByteStreamControllerHandleQueueDrain(cx, controller))
+ return nullptr;
+
+ // Step iii: Return a promise resolved with
+ // ! CreateIterResultObject(filledView, false).
+ val = ObjectValue(*filledView);
+ RootedObject iterResult(cx, CreateIterResultObject(cx, val, false));
+ if (!iterResult)
+ return nullptr;
+ val = ObjectValue(*iterResult);
+ return PromiseObject::unforgeableResolve(cx, val);
+ }
+
+ // Step b: If controller.[[closeRequested]] is true,
+ if (ControllerFlags(controller) & ControllerFlag_CloseRequested) {
+ // Step i: Let e be a TypeError exception.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_CLOSED, "read");
+
+ // Not much we can do about uncatchable exceptions, just bail.
+ RootedValue e(cx);
+ if (!GetAndClearException(cx, &e))
+ return nullptr;
+
+ // Step ii: Perform ! ReadableByteStreamControllerError(controller, e).
+ if (!ReadableStreamControllerError(cx, controller, e))
+ return nullptr;
+
+ // Step iii: Return a promise rejected with e.
+ return PromiseObject::unforgeableReject(cx, e);
+ }
+ }
+
+ // Step 9: Set pullIntoDescriptor.[[buffer]] to
+ // ! TransferArrayBuffer(pullIntoDescriptor.[[buffer]]).
+ RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer));
+ if (!transferredBuffer)
+ return nullptr;
+ pullIntoDescriptor->setBuffer(transferredBuffer);
+
+ // Step 10: Append pullIntoDescriptor as the last element of
+ // controller.[[pendingPullIntos]].
+ val = ObjectValue(*pullIntoDescriptor);
+ if (!AppendToList(cx, pendingPullIntos, val))
+ return nullptr;
+
+ // Step 11: Let promise be ! ReadableStreamAddReadIntoRequest(stream).
+ Rooted<PromiseObject*> promise(cx, ReadableStreamAddReadIntoRequest(cx, stream));
+ if (!promise)
+ return nullptr;
+
+ // Step 12: Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller).
+ if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
+ return nullptr;
+
+ // Step 13: Return promise.
+ return promise;
+}
+
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerRespondInternal(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ double bytesWritten);
+
+// Streams spec 3.12.18. ReadableByteStreamControllerRespond( controller, bytesWritten )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerRespond(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ HandleValue bytesWrittenVal)
+{
+ MOZ_ASSERT(controller->is<ReadableByteStreamController>());
+
+ // Step 1: Let bytesWritten be ? ToNumber(bytesWritten).
+ double bytesWritten;
+ if (!ToNumber(cx, bytesWrittenVal, &bytesWritten))
+ return false;
+
+ // Step 2: If ! IsFiniteNonNegativeNumber(bytesWritten) is false,
+ if (bytesWritten < 0 || mozilla::IsNaN(bytesWritten) || mozilla::IsInfinite(bytesWritten)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, "bytesWritten");
+ return false;
+ }
+
+ // Step 3: Assert: controller.[[pendingPullIntos]] is not empty.
+#if DEBUG
+ Value val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos);
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+ MOZ_ASSERT(pendingPullIntos->getDenseInitializedLength() != 0);
+#endif // DEBUG
+
+ // Step 4: Perform ? ReadableByteStreamControllerRespondInternal(controller, bytesWritten).
+ return ReadableByteStreamControllerRespondInternal(cx, controller, bytesWritten);
+}
+
+// Streams spec 3.12.19. ReadableByteStreamControllerRespondInClosedState( controller, firstDescriptor )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerRespondInClosedState(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ Handle<PullIntoDescriptor*> firstDescriptor)
+{
+ // Step 1: Set firstDescriptor.[[buffer]] to
+ // ! TransferArrayBuffer(firstDescriptor.[[buffer]]).
+ RootedArrayBufferObject buffer(cx, firstDescriptor->buffer());
+ RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer));
+ if (!transferredBuffer)
+ return false;
+ firstDescriptor->setBuffer(transferredBuffer);
+
+ // Step 2: Assert: firstDescriptor.[[bytesFilled]] is 0.
+ MOZ_ASSERT(firstDescriptor->bytesFilled() == 0);
+
+ // Step 3: Let stream be controller.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 4: If ReadableStreamHasBYOBReader(stream) is true,
+ if (ReadableStreamHasBYOBReader(stream)) {
+ // Step a: Repeat the following steps while
+ // ! ReadableStreamGetNumReadIntoRequests(stream) > 0,
+ Rooted<PullIntoDescriptor*> descriptor(cx);
+ while (ReadableStreamGetNumReadRequests(stream) > 0) {
+ // Step i: Let pullIntoDescriptor be
+ // ! ReadableByteStreamControllerShiftPendingPullInto(controller).
+ descriptor = ReadableByteStreamControllerShiftPendingPullInto(cx, controller);
+ if (!descriptor)
+ return false;
+
+ // Step ii: Perform !
+ // ReadableByteStreamControllerCommitPullIntoDescriptor(stream,
+ // pullIntoDescriptor).
+ if (!ReadableByteStreamControllerCommitPullIntoDescriptor(cx, stream, descriptor))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// Streams spec 3.12.20.
+// ReadableByteStreamControllerRespondInReadableState( controller, bytesWritten, pullIntoDescriptor )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerRespondInReadableState(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ uint32_t bytesWritten,
+ Handle<PullIntoDescriptor*> pullIntoDescriptor)
+{
+ // Step 1: If pullIntoDescriptor.[[bytesFilled]] + bytesWritten > pullIntoDescriptor.[[byteLength]],
+ // throw a RangeError exception.
+ uint32_t bytesFilled = pullIntoDescriptor->bytesFilled();
+ uint32_t byteLength = pullIntoDescriptor->byteLength();
+ if (bytesFilled + bytesWritten > byteLength) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_BYTESWRITTEN);
+ return false;
+ }
+
+ // Step 2: Perform ! ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller,
+ // bytesWritten,
+ // pullIntoDescriptor).
+ ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, bytesWritten,
+ pullIntoDescriptor);
+ bytesFilled += bytesWritten;
+
+ // Step 3: If pullIntoDescriptor.[[bytesFilled]] <
+ // pullIntoDescriptor.[[elementSize]], return.
+ uint32_t elementSize = pullIntoDescriptor->elementSize();
+ if (bytesFilled < elementSize)
+ return true;
+
+ // Step 4: Perform ! ReadableByteStreamControllerShiftPendingPullInto(controller).
+ if (!ReadableByteStreamControllerShiftPendingPullInto(cx, controller))
+ return false;
+
+ // Step 5: Let remainderSize be pullIntoDescriptor.[[bytesFilled]] mod
+ // pullIntoDescriptor.[[elementSize]].
+ uint32_t remainderSize = bytesFilled % elementSize;
+
+ // Step 6: If remainderSize > 0,
+ RootedArrayBufferObject buffer(cx, pullIntoDescriptor->buffer());
+ if (remainderSize > 0) {
+ // Step a: Let end be pullIntoDescriptor.[[byteOffset]] +
+ // pullIntoDescriptor.[[bytesFilled]].
+ uint32_t end = pullIntoDescriptor->byteOffset() + bytesFilled;
+
+ // Step b: Let remainder be ? CloneArrayBuffer(pullIntoDescriptor.[[buffer]],
+ // end − remainderSize,
+ // remainderSize, %ArrayBuffer%).
+ // TODO: this really, really should just use a slot to store the remainder.
+ RootedObject remainderObj(cx, JS_NewArrayBuffer(cx, remainderSize));
+ if (!remainderObj)
+ return false;
+ RootedArrayBufferObject remainder(cx, &remainderObj->as<ArrayBufferObject>());
+ ArrayBufferObject::copyData(remainder, 0, buffer, end - remainderSize, remainderSize);
+
+ // Step c: Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(controller,
+ // remainder, 0,
+ // remainder.[[ByteLength]]).
+ // Note: `remainderSize` is equivalent to remainder.[[ByteLength]].
+ if (!ReadableByteStreamControllerEnqueueChunkToQueue(cx, controller, remainder, 0,
+ remainderSize))
+ {
+ return false;
+ }
+ }
+
+ // Step 7: Set pullIntoDescriptor.[[buffer]] to
+ // ! TransferArrayBuffer(pullIntoDescriptor.[[buffer]]).
+ RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer));
+ if (!transferredBuffer)
+ return false;
+ pullIntoDescriptor->setBuffer(transferredBuffer);
+
+ // Step 8: Set pullIntoDescriptor.[[bytesFilled]] to pullIntoDescriptor.[[bytesFilled]] −
+ // remainderSize.
+ pullIntoDescriptor->setBytesFilled(bytesFilled - remainderSize);
+
+ // Step 9: Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[controlledReadableStream]],
+ // pullIntoDescriptor).
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+ if (!ReadableByteStreamControllerCommitPullIntoDescriptor(cx, stream, pullIntoDescriptor))
+ return false;
+
+ // Step 10: Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
+ return ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(cx, controller);
+}
+
+// Streams spec, 3.12.21. ReadableByteStreamControllerRespondInternal ( controller, bytesWritten )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerRespondInternal(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ double bytesWritten)
+{
+ // Step 1: Let firstDescriptor be the first element of controller.[[pendingPullIntos]].
+ RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+ Rooted<PullIntoDescriptor*> firstDescriptor(cx);
+ firstDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos);
+
+ // Step 2: Let stream be controller.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 3: If stream.[[state]] is "closed",
+ if (stream->closed()) {
+ // Step a: If bytesWritten is not 0, throw a TypeError exception.
+ if (bytesWritten != 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMBYOBREQUEST_RESPOND_CLOSED);
+ return false;
+ }
+
+ // Step b: Perform
+ // ! ReadableByteStreamControllerRespondInClosedState(controller, firstDescriptor).
+ return ReadableByteStreamControllerRespondInClosedState(cx, controller, firstDescriptor);
+ }
+
+ // Step 4: Otherwise,
+ // Step a: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->readable());
+
+ // Step b: Perform ? ReadableByteStreamControllerRespondInReadableState(controller,
+ // bytesWritten,
+ // firstDescriptor).
+ return ReadableByteStreamControllerRespondInReadableState(cx, controller, bytesWritten,
+ firstDescriptor);
+}
+
+// Streams spec, 3.12.22. ReadableByteStreamControllerRespondWithNewView ( controller, view )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerRespondWithNewView(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ HandleObject view)
+{
+ // Step 1: Assert: controller.[[pendingPullIntos]] is not empty.
+ RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+ MOZ_ASSERT(pendingPullIntos->getDenseInitializedLength() != 0);
+
+ // Step 2: Let firstDescriptor be the first element of controller.[[pendingPullIntos]].
+ Rooted<PullIntoDescriptor*> firstDescriptor(cx);
+ firstDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos);
+
+ // Step 3: If firstDescriptor.[[byteOffset]] + firstDescriptor.[[bytesFilled]]
+ // is not view.[[ByteOffset]], throw a RangeError exception.
+ uint32_t byteOffset = uint32_t(JS_GetArrayBufferViewByteOffset(view));
+ if (firstDescriptor->byteOffset() + firstDescriptor->bytesFilled() != byteOffset) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_OFFSET);
+ return false;
+ }
+
+ // Step 4: If firstDescriptor.[[byteLength]] is not view.[[ByteLength]],
+ // throw a RangeError exception.
+ uint32_t byteLength = JS_GetArrayBufferViewByteLength(view);
+ if (firstDescriptor->byteLength() != byteLength) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_SIZE);
+ return false;
+ }
+
+ // Step 5: Set firstDescriptor.[[buffer]] to view.[[ViewedArrayBuffer]].
+ bool dummy;
+ RootedArrayBufferObject buffer(cx,
+ &AsArrayBuffer(JS_GetArrayBufferViewBuffer(cx, view, &dummy)));
+ if (!buffer)
+ return false;
+ firstDescriptor->setBuffer(buffer);
+
+ // Step 6: Perform ? ReadableByteStreamControllerRespondInternal(controller,
+ // view.[[ByteLength]]).
+ return ReadableByteStreamControllerRespondInternal(cx, controller, byteLength);
+}
+
+// Streams spec, 3.12.23. ReadableByteStreamControllerShiftPendingPullInto ( controller )
+static MOZ_MUST_USE PullIntoDescriptor*
+ReadableByteStreamControllerShiftPendingPullInto(JSContext* cx, HandleNativeObject controller)
+{
+ MOZ_ASSERT(controller->is<ReadableByteStreamController>());
+
+ // Step 1: Let descriptor be the first element of controller.[[pendingPullIntos]].
+ // Step 2: Remove descriptor from controller.[[pendingPullIntos]], shifting
+ // all other elements downward (so that the second becomes the first,
+ // and so on).
+ RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+ Rooted<PullIntoDescriptor*> descriptor(cx);
+ descriptor = ShiftFromList<PullIntoDescriptor>(cx, pendingPullIntos);
+ MOZ_ASSERT(descriptor);
+
+ // Step 3: Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller).
+ ReadableByteStreamControllerInvalidateBYOBRequest(controller);
+
+ // Step 4: Return descriptor.
+ return descriptor;
+}
+
+// Streams spec, 3.12.24. ReadableByteStreamControllerShouldCallPull ( controller )
+// Unified with 3.9.3 above.
+
+// Streams spec, 6.1.2. new ByteLengthQueuingStrategy({ highWaterMark })
+bool
+js::ByteLengthQueuingStrategy::constructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedObject strategy(cx, NewBuiltinClassInstance<ByteLengthQueuingStrategy>(cx));
+ if (!strategy)
+ return false;
+
+ RootedObject argObj(cx, ToObject(cx, args.get(0)));
+ if (!argObj)
+ return false;
+
+ RootedValue highWaterMark(cx);
+ if (!GetProperty(cx, argObj, argObj, cx->names().highWaterMark, &highWaterMark))
+ return false;
+
+ if (!SetProperty(cx, strategy, cx->names().highWaterMark, highWaterMark))
+ return false;
+
+ args.rval().setObject(*strategy);
+ return true;
+}
+
+// Streams spec 6.1.3.1. size ( chunk )
+bool
+ByteLengthQueuingStrategy_size(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: Return ? GetV(chunk, "byteLength").
+ return GetProperty(cx, args.get(0), cx->names().byteLength, args.rval());
+}
+
+static const JSPropertySpec ByteLengthQueuingStrategy_properties[] = {
+ JS_PS_END
+};
+
+static const JSFunctionSpec ByteLengthQueuingStrategy_methods[] = {
+ JS_FN("size", ByteLengthQueuingStrategy_size, 1, 0),
+ JS_FS_END
+};
+
+CLASS_SPEC(ByteLengthQueuingStrategy, 1, 0, 0, 0, JS_NULL_CLASS_OPS);
+
+// Streams spec, 6.2.2. new CountQueuingStrategy({ highWaterMark })
+bool
+js::CountQueuingStrategy::constructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<CountQueuingStrategy*> strategy(cx, NewBuiltinClassInstance<CountQueuingStrategy>(cx));
+ if (!strategy)
+ return false;
+
+ RootedObject argObj(cx, ToObject(cx, args.get(0)));
+ if (!argObj)
+ return false;
+
+ RootedValue highWaterMark(cx);
+ if (!GetProperty(cx, argObj, argObj, cx->names().highWaterMark, &highWaterMark))
+ return false;
+
+ if (!SetProperty(cx, strategy, cx->names().highWaterMark, highWaterMark))
+ return false;
+
+ args.rval().setObject(*strategy);
+ return true;
+}
+
+// Streams spec 6.2.3.1. size ( chunk )
+bool
+CountQueuingStrategy_size(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: Return 1.
+ args.rval().setInt32(1);
+ return true;
+}
+
+static const JSPropertySpec CountQueuingStrategy_properties[] = {
+ JS_PS_END
+};
+
+static const JSFunctionSpec CountQueuingStrategy_methods[] = {
+ JS_FN("size", CountQueuingStrategy_size, 0, 0),
+ JS_FS_END
+};
+
+CLASS_SPEC(CountQueuingStrategy, 1, 0, 0, 0, JS_NULL_CLASS_OPS);
+
+#undef CLASS_SPEC
+
+// Streams spec, 6.3.1. DequeueValue ( container ) nothrow
+inline static MOZ_MUST_USE bool
+DequeueValue(JSContext* cx, HandleNativeObject container, MutableHandleValue chunk)
+{
+ // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
+ // slots.
+ MOZ_ASSERT(IsReadableStreamController(container));
+
+ // Step 2: Assert: queue is not empty.
+ RootedValue val(cx, container->getFixedSlot(QueueContainerSlot_Queue));
+ RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());
+ MOZ_ASSERT(queue->getDenseInitializedLength() > 0);
+
+ // Step 3. Let pair be the first element of queue.
+ // Step 4. Remove pair from queue, shifting all other elements downward
+ // (so that the second becomes the first, and so on).
+ Rooted<QueueEntry*> pair(cx, ShiftFromList<QueueEntry>(cx, queue));
+ MOZ_ASSERT(pair);
+
+ // Step 5: Set container.[[queueTotalSize]] to
+ // container.[[queueTotalSize]] − pair.[[size]].
+ // Step 6: If container.[[queueTotalSize]] < 0, set
+ // container.[[queueTotalSize]] to 0.
+ // (This can occur due to rounding errors.)
+ double totalSize = container->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+
+ totalSize -= pair->size();
+ if (totalSize < 0)
+ totalSize = 0;
+ container->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(totalSize));
+
+ // Step 7: Return pair.[[value]].
+ chunk.set(pair->value());
+ return true;
+}
+
+// Streams spec, 6.3.2. EnqueueValueWithSize ( container, value, size ) throws
+static MOZ_MUST_USE bool
+EnqueueValueWithSize(JSContext* cx, HandleNativeObject container, HandleValue value,
+ HandleValue sizeVal)
+{
+ // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
+ // slots.
+ MOZ_ASSERT(IsReadableStreamController(container));
+
+ // Step 2: Let size be ? ToNumber(size).
+ double size;
+ if (!ToNumber(cx, sizeVal, &size))
+ return false;
+
+ // Step 3: If ! IsFiniteNonNegativeNumber(size) is false, throw a RangeError
+ // exception.
+ if (size < 0 || mozilla::IsNaN(size) || mozilla::IsInfinite(size)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, "size");
+ return false;
+ }
+
+ // Step 4: Append Record {[[value]]: value, [[size]]: size} as the last element
+ // of container.[[queue]].
+ RootedValue val(cx, container->getFixedSlot(QueueContainerSlot_Queue));
+ RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());
+
+ QueueEntry* entry = QueueEntry::create(cx, value, size);
+ if (!entry)
+ return false;
+ val = ObjectValue(*entry);
+ if (!AppendToList(cx, queue, val))
+ return false;
+
+ // Step 5: Set container.[[queueTotalSize]] to
+ // container.[[queueTotalSize]] + size.
+ double totalSize = container->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ container->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(totalSize + size));
+
+ return true;
+}
+
+// Streams spec, 6.3.3. PeekQueueValue ( container ) nothrow
+// Used by WritableStream.
+// static MOZ_MUST_USE Value
+// PeekQueueValue(NativeObject* container)
+// {
+// // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
+// // slots.
+// MOZ_ASSERT(IsReadableStreamController(container));
+
+// // Step 2: Assert: queue is not empty.
+// Value val = container->getFixedSlot(QueueContainerSlot_Queue);
+// NativeObject* queue = &val.toObject().as<NativeObject>();
+// MOZ_ASSERT(queue->getDenseInitializedLength() > 0);
+
+// // Step 3: Let pair be the first element of container.[[queue]].
+// QueueEntry* pair = PeekList<QueueEntry>(queue);
+
+// // Step 4: Return pair.[[value]].
+// return pair->value();
+// }
+
+/**
+ * Streams spec, 6.3.4. ResetQueue ( container ) nothrow
+ */
+inline static MOZ_MUST_USE bool
+ResetQueue(JSContext* cx, HandleNativeObject container)
+{
+ // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
+ // slots.
+ MOZ_ASSERT(IsReadableStreamController(container));
+
+ // Step 2: Set container.[[queue]] to a new empty List.
+ if (!SetNewList(cx, container, QueueContainerSlot_Queue))
+ return false;
+
+ // Step 3: Set container.[[queueTotalSize]] to 0.
+ container->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(0));
+
+ return true;
+}
+
+
+/**
+ * Streams spec, 6.4.1. InvokeOrNoop ( O, P, args )
+ */
+inline static MOZ_MUST_USE bool
+InvokeOrNoop(JSContext* cx, HandleValue O, HandlePropertyName P, HandleValue arg,
+ MutableHandleValue rval)
+{
+ // Step 1: Assert: P is a valid property key (omitted).
+ // Step 2: If args was not passed, let args be a new empty List (omitted).
+ // Step 3: Let method be ? GetV(O, P).
+ RootedValue method(cx);
+ if (!GetProperty(cx, O, P, &method))
+ return false;
+
+ // Step 4: If method is undefined, return.
+ if (method.isUndefined())
+ return true;
+
+ // Step 5: Return ? Call(method, O, args).
+ return Call(cx, method, O, arg, rval);
+}
+
+/**
+ * Streams spec, 6.4.3. PromiseInvokeOrNoop ( O, P, args )
+ * Specialized to one arg, because that's what all stream related callers use.
+ */
+static MOZ_MUST_USE JSObject*
+PromiseInvokeOrNoop(JSContext* cx, HandleValue O, HandlePropertyName P, HandleValue arg)
+{
+ // Step 1: Assert: O is not undefined.
+ MOZ_ASSERT(!O.isUndefined());
+
+ // Step 2: Assert: ! IsPropertyKey(P) is true (implicit).
+ // Step 3: Assert: args is a List (omitted).
+
+ // Step 4: Let returnValue be InvokeOrNoop(O, P, args).
+ // Step 5: If returnValue is an abrupt completion, return a promise
+ // rejected with returnValue.[[Value]].
+ RootedValue returnValue(cx);
+ if (!InvokeOrNoop(cx, O, P, arg, &returnValue))
+ return PromiseRejectedWithPendingError(cx);
+
+ // Step 6: Otherwise, return a promise resolved with returnValue.[[Value]].
+ return PromiseObject::unforgeableResolve(cx, returnValue);
+}
+
+/**
+ * Streams spec, 6.4.4 TransferArrayBuffer ( O )
+ */
+static MOZ_MUST_USE ArrayBufferObject*
+TransferArrayBuffer(JSContext* cx, HandleObject buffer)
+{
+ // Step 1 (implicit).
+
+ // Step 2.
+ MOZ_ASSERT(buffer->is<ArrayBufferObject>());
+
+ // Step 3.
+ MOZ_ASSERT(!JS_IsDetachedArrayBufferObject(buffer));
+
+ // Step 5 (reordered).
+ uint32_t size = buffer->as<ArrayBufferObject>().byteLength();
+
+ // Steps 4, 6.
+ void* contents = JS_StealArrayBufferContents(cx, buffer);
+ if (!contents)
+ return nullptr;
+ MOZ_ASSERT(JS_IsDetachedArrayBufferObject(buffer));
+
+ // Step 7.
+ RootedObject transferredBuffer(cx, JS_NewArrayBufferWithContents(cx, size, contents));
+ if (!transferredBuffer)
+ return nullptr;
+ return &transferredBuffer->as<ArrayBufferObject>();
+}
+
+// Streams spec, 6.4.5. ValidateAndNormalizeHighWaterMark ( highWaterMark )
+static MOZ_MUST_USE bool
+ValidateAndNormalizeHighWaterMark(JSContext* cx, HandleValue highWaterMarkVal, double* highWaterMark)
+{
+ // Step 1: Set highWaterMark to ? ToNumber(highWaterMark).
+ if (!ToNumber(cx, highWaterMarkVal, highWaterMark))
+ return false;
+
+ // Step 2: If highWaterMark is NaN, throw a TypeError exception.
+ // Step 3: If highWaterMark < 0, throw a RangeError exception.
+ if (mozilla::IsNaN(*highWaterMark) || *highWaterMark < 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_STREAM_INVALID_HIGHWATERMARK);
+ return false;
+ }
+
+ // Step 4: Return highWaterMark.
+ return true;
+}
+
+// Streams spec, 6.4.6. ValidateAndNormalizeQueuingStrategy ( size, highWaterMark )
+static MOZ_MUST_USE bool
+ValidateAndNormalizeQueuingStrategy(JSContext* cx, HandleValue size,
+ HandleValue highWaterMarkVal, double* highWaterMark)
+{
+ // Step 1: If size is not undefined and ! IsCallable(size) is false, throw a
+ // TypeError exception.
+ if (!size.isUndefined() && !IsCallable(size)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION,
+ "ReadableStream argument options.size");
+ return false;
+ }
+
+ // Step 2: Let highWaterMark be ? ValidateAndNormalizeHighWaterMark(highWaterMark).
+ if (!ValidateAndNormalizeHighWaterMark(cx, highWaterMarkVal, highWaterMark))
+ return false;
+
+ // Step 3: Return Record {[[size]]: size, [[highWaterMark]]: highWaterMark}.
+ return true;
+}
+
+MOZ_MUST_USE bool
+js::ReadableStreamReaderCancel(JSContext* cx, HandleObject readerObj, HandleValue reason)
+{
+ MOZ_ASSERT(IsReadableStreamReader(readerObj));
+ RootedNativeObject reader(cx, &readerObj->as<NativeObject>());
+ MOZ_ASSERT(StreamFromReader(reader));
+ return ReadableStreamReaderGenericCancel(cx, reader, reason);
+}
+
+MOZ_MUST_USE bool
+js::ReadableStreamReaderReleaseLock(JSContext* cx, HandleObject readerObj)
+{
+ MOZ_ASSERT(IsReadableStreamReader(readerObj));
+ RootedNativeObject reader(cx, &readerObj->as<NativeObject>());
+ MOZ_ASSERT(ReadableStreamGetNumReadRequests(StreamFromReader(reader)) == 0);
+ return ReadableStreamReaderGenericRelease(cx, reader);
+}
+
+MOZ_MUST_USE bool
+ReadableStream::enqueue(JSContext* cx, Handle<ReadableStream*> stream, HandleValue chunk)
+{
+ Rooted<ReadableStreamDefaultController*> controller(cx);
+ controller = &ControllerFromStream(stream)->as<ReadableStreamDefaultController>();
+
+ MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));
+ MOZ_ASSERT(stream->readable());
+
+ return ReadableStreamDefaultControllerEnqueue(cx, controller, chunk);
+}
+
+MOZ_MUST_USE bool
+ReadableStream::enqueueBuffer(JSContext* cx, Handle<ReadableStream*> stream,
+ Handle<ArrayBufferObject*> chunk)
+{
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &ControllerFromStream(stream)->as<ReadableByteStreamController>();
+
+ MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));
+ MOZ_ASSERT(stream->readable());
+
+ return ReadableByteStreamControllerEnqueue(cx, controller, chunk);
+}
+
+void
+ReadableStream::desiredSize(bool* hasSize, double* size) const
+{
+ if (errored()) {
+ *hasSize = false;
+ return;
+ }
+
+ *hasSize = true;
+
+ if (closed()) {
+ *size = 0;
+ return;
+ }
+
+ NativeObject* controller = ControllerFromStream(this);
+ *size = ReadableStreamControllerGetDesiredSizeUnchecked(controller);
+}
+
+/*static */ bool
+ReadableStream::getExternalSource(JSContext* cx, Handle<ReadableStream*> stream, void** source)
+{
+ MOZ_ASSERT(stream->mode() == JS::ReadableStreamMode::ExternalSource);
+ if (stream->locked()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_READABLESTREAM_LOCKED);
+ return false;
+ }
+ if (!stream->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE,
+ "ReadableStreamGetExternalUnderlyingSource");
+ return false;
+ }
+
+ auto controller = &ControllerFromStream(stream)->as<ReadableByteStreamController>();
+ AddControllerFlags(controller, ControllerFlag_SourceLocked);
+ *source = controller->getFixedSlot(ControllerSlot_UnderlyingSource).toPrivate();
+ return true;
+}
+
+void
+ReadableStream::releaseExternalSource()
+{
+ MOZ_ASSERT(mode() == JS::ReadableStreamMode::ExternalSource);
+ MOZ_ASSERT(locked());
+ auto controller = ControllerFromStream(this);
+ MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_SourceLocked);
+ RemoveControllerFlags(controller, ControllerFlag_SourceLocked);
+}
+
+uint8_t
+ReadableStream::embeddingFlags() const
+{
+ uint8_t flags = ControllerFlags(ControllerFromStream(this)) >> ControllerEmbeddingFlagsOffset;
+ MOZ_ASSERT_IF(flags, mode() == JS::ReadableStreamMode::ExternalSource);
+ return flags;
+}
+
+// Streams spec, 3.10.4.4. steps 1-3
+// and
+// Streams spec, 3.12.8. steps 8-9
+//
+// Adapted to handling updates signaled by the embedding for streams with
+// external underlying sources.
+//
+// The remaining steps of those two functions perform checks and asserts that
+// don't apply to streams with external underlying sources.
+MOZ_MUST_USE bool
+ReadableStream::updateDataAvailableFromSource(JSContext* cx, Handle<ReadableStream*> stream,
+ uint32_t availableData)
+{
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &ControllerFromStream(stream)->as<ReadableByteStreamController>();
+
+ // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
+ if (ControllerFlags(controller) & ControllerFlag_CloseRequested) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_CLOSED, "enqueue");
+ return false;
+ }
+
+ // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable",
+ // throw a TypeError exception.
+ if (!StreamFromController(controller)->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "enqueue");
+ return false;
+ }
+
+ RemoveControllerFlags(controller, ControllerFlag_Pulling | ControllerFlag_PullAgain);
+
+#if DEBUG
+ uint32_t oldAvailableData = controller->getFixedSlot(QueueContainerSlot_TotalSize).toInt32();
+#endif // DEBUG
+ controller->setFixedSlot(QueueContainerSlot_TotalSize, Int32Value(availableData));
+
+ // Step 8.a: If ! ReadableStreamGetNumReadRequests(stream) is 0,
+ // Reordered because for externally-sourced streams it applies regardless
+ // of reader type.
+ if (ReadableStreamGetNumReadRequests(stream) == 0)
+ return true;
+
+ // Step 8: If ! ReadableStreamHasDefaultReader(stream) is true
+ if (ReadableStreamHasDefaultReader(stream)) {
+ // Step b: Otherwise,
+ // Step i: Assert: controller.[[queue]] is empty.
+ MOZ_ASSERT(oldAvailableData == 0);
+
+ // Step ii: Let transferredView be
+ // ! Construct(%Uint8Array%, transferredBuffer, byteOffset, byteLength).
+ JSObject* viewObj = JS_NewUint8Array(cx, availableData);
+ Rooted<ArrayBufferViewObject*> transferredView(cx, &viewObj->as<ArrayBufferViewObject>());
+ if (!transferredView)
+ return false;
+
+ Value val = controller->getFixedSlot(ControllerSlot_UnderlyingSource);
+ void* underlyingSource = val.toPrivate();
+
+ size_t bytesWritten;
+ {
+ JS::AutoCheckCannotGC noGC(cx);
+ bool dummy;
+ void* buffer = JS_GetArrayBufferViewData(transferredView, &dummy, noGC);
+ auto cb = cx->runtime()->readableStreamWriteIntoReadRequestCallback;
+ MOZ_ASSERT(cb);
+ // TODO: use bytesWritten to correctly update the request's state.
+ cb(cx, stream, underlyingSource, stream->embeddingFlags(), buffer,
+ availableData, &bytesWritten);
+ }
+
+ // Step iii: Perform ! ReadableStreamFulfillReadRequest(stream, transferredView, false).
+ RootedValue chunk(cx, ObjectValue(*transferredView));
+ if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, chunk, false))
+ return false;
+
+ controller->setFixedSlot(QueueContainerSlot_TotalSize,
+ Int32Value(availableData - bytesWritten));
+ } else if (ReadableStreamHasBYOBReader(stream)) {
+ // Step 9: Otherwise,
+ // Step a: If ! ReadableStreamHasBYOBReader(stream) is true,
+ // Step i: Perform
+ // (Not needed for external underlying sources.)
+
+ // Step ii: Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
+ if (!ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(cx, controller))
+ return false;
+ } else {
+ // Step b: Otherwise,
+ // Step i: Assert: ! IsReadableStreamLocked(stream) is false.
+ MOZ_ASSERT(!stream->locked());
+
+ // Step ii: Perform
+ // ! ReadableByteStreamControllerEnqueueChunkToQueue(controller,
+ // transferredBuffer,
+ // byteOffset,
+ // byteLength).
+ // (Not needed for external underlying sources.)
+ }
+
+ return true;
+}
+
+MOZ_MUST_USE bool
+ReadableStream::close(JSContext* cx, Handle<ReadableStream*> stream)
+{
+ RootedNativeObject controllerObj(cx, ControllerFromStream(stream));
+ if (!VerifyControllerStateForClosing(cx, controllerObj))
+ return false;
+
+ if (controllerObj->is<ReadableStreamDefaultController>()) {
+ Rooted<ReadableStreamDefaultController*> controller(cx);
+ controller = &controllerObj->as<ReadableStreamDefaultController>();
+ return ReadableStreamDefaultControllerClose(cx, controller);
+ }
+
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &controllerObj->as<ReadableByteStreamController>();
+ return ReadableByteStreamControllerClose(cx, controller);
+}
+
+MOZ_MUST_USE bool
+ReadableStream::error(JSContext* cx, Handle<ReadableStream*> stream, HandleValue reason)
+{
+ // Step 3: If stream.[[state]] is not "readable", throw a TypeError exception.
+ if (!stream->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "error");
+ return false;
+ }
+
+ // Step 4: Perform ! ReadableStreamDefaultControllerError(this, e).
+ RootedNativeObject controller(cx, ControllerFromStream(stream));
+ return ReadableStreamControllerError(cx, controller, reason);
+}
+
+MOZ_MUST_USE bool
+ReadableStream::tee(JSContext* cx, Handle<ReadableStream*> stream, bool cloneForBranch2,
+ MutableHandle<ReadableStream*> branch1Stream,
+ MutableHandle<ReadableStream*> branch2Stream)
+{
+ return ReadableStreamTee(cx, stream, false, branch1Stream, branch2Stream);
+}
+
+MOZ_MUST_USE NativeObject*
+ReadableStream::getReader(JSContext* cx, Handle<ReadableStream*> stream,
+ JS::ReadableStreamReaderMode mode)
+{
+ if (mode == JS::ReadableStreamReaderMode::Default)
+ return CreateReadableStreamDefaultReader(cx, stream);
+ return CreateReadableStreamBYOBReader(cx, stream);
+}
+
+JS_FRIEND_API(JSObject*)
+js::UnwrapReadableStream(JSObject* obj)
+{
+ if (JSObject* unwrapped = CheckedUnwrap(obj))
+ return unwrapped->is<ReadableStream>() ? unwrapped : nullptr;
+ return nullptr;
+}
diff --git a/js/src/builtin/Stream.h b/js/src/builtin/Stream.h
new file mode 100644
index 0000000000..0726e78361
--- /dev/null
+++ b/js/src/builtin/Stream.h
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+#ifndef builtin_Stream_h
+#define builtin_Stream_h
+
+#include "builtin/Promise.h"
+#include "vm/NativeObject.h"
+
+
+namespace js {
+
+class AutoSetNewObjectMetadata;
+
+class ReadableStream : public NativeObject
+{
+ public:
+ static ReadableStream* createDefaultStream(JSContext* cx, HandleValue underlyingSource,
+ HandleValue size, HandleValue highWaterMark,
+ HandleObject proto = nullptr);
+ static ReadableStream* createByteStream(JSContext* cx, HandleValue underlyingSource,
+ HandleValue highWaterMark,
+ HandleObject proto = nullptr);
+ static ReadableStream* createExternalSourceStream(JSContext* cx, void* underlyingSource,
+ uint8_t flags, HandleObject proto = nullptr);
+
+ bool readable() const;
+ bool closed() const;
+ bool errored() const;
+ bool disturbed() const;
+
+ bool locked() const;
+
+ void desiredSize(bool* hasSize, double* size) const;
+
+ JS::ReadableStreamMode mode() const;
+
+ static MOZ_MUST_USE bool close(JSContext* cx, Handle<ReadableStream*> stream);
+ static MOZ_MUST_USE JSObject* cancel(JSContext* cx, Handle<ReadableStream*> stream,
+ HandleValue reason);
+ static MOZ_MUST_USE bool error(JSContext* cx, Handle<ReadableStream*> stream,
+ HandleValue error);
+
+ static MOZ_MUST_USE NativeObject* getReader(JSContext* cx, Handle<ReadableStream*> stream,
+ JS::ReadableStreamReaderMode mode);
+
+ static MOZ_MUST_USE bool tee(JSContext* cx,
+ Handle<ReadableStream*> stream, bool cloneForBranch2,
+ MutableHandle<ReadableStream*> branch1Stream,
+ MutableHandle<ReadableStream*> branch2Stream);
+
+ static MOZ_MUST_USE bool enqueue(JSContext* cx, Handle<ReadableStream*> stream,
+ HandleValue chunk);
+ static MOZ_MUST_USE bool enqueueBuffer(JSContext* cx, Handle<ReadableStream*> stream,
+ Handle<ArrayBufferObject*> chunk);
+ static MOZ_MUST_USE bool getExternalSource(JSContext* cx, Handle<ReadableStream*> stream,
+ void** source);
+ void releaseExternalSource();
+ uint8_t embeddingFlags() const;
+ static MOZ_MUST_USE bool updateDataAvailableFromSource(JSContext* cx,
+ Handle<ReadableStream*> stream,
+ uint32_t availableData);
+
+ enum State {
+ Readable = 1 << 0,
+ Closed = 1 << 1,
+ Errored = 1 << 2,
+ Disturbed = 1 << 3
+ };
+
+ private:
+ static MOZ_MUST_USE ReadableStream* createStream(JSContext* cx, HandleObject proto = nullptr);
+
+ public:
+ static bool constructor(JSContext* cx, unsigned argc, Value* vp);
+ static const ClassSpec classSpec_;
+ static const Class class_;
+ static const ClassSpec protoClassSpec_;
+ static const Class protoClass_;
+};
+
+class ReadableStreamDefaultReader : public NativeObject
+{
+ public:
+ static MOZ_MUST_USE JSObject* read(JSContext* cx, Handle<ReadableStreamDefaultReader*> reader);
+
+ static bool constructor(JSContext* cx, unsigned argc, Value* vp);
+ static const ClassSpec classSpec_;
+ static const Class class_;
+ static const ClassSpec protoClassSpec_;
+ static const Class protoClass_;
+};
+
+class ReadableStreamBYOBReader : public NativeObject
+{
+ public:
+ static MOZ_MUST_USE JSObject* read(JSContext* cx, Handle<ReadableStreamBYOBReader*> reader,
+ Handle<ArrayBufferViewObject*> view);
+
+ static bool constructor(JSContext* cx, unsigned argc, Value* vp);
+ static const ClassSpec classSpec_;
+ static const Class class_;
+ static const ClassSpec protoClassSpec_;
+ static const Class protoClass_;
+};
+
+bool ReadableStreamReaderIsClosed(const JSObject* reader);
+
+MOZ_MUST_USE bool ReadableStreamReaderCancel(JSContext* cx, HandleObject reader,
+ HandleValue reason);
+
+MOZ_MUST_USE bool ReadableStreamReaderReleaseLock(JSContext* cx, HandleObject reader);
+
+class ReadableStreamDefaultController : public NativeObject
+{
+ public:
+ static bool constructor(JSContext* cx, unsigned argc, Value* vp);
+ static const ClassSpec classSpec_;
+ static const Class class_;
+ static const ClassSpec protoClassSpec_;
+ static const Class protoClass_;
+};
+
+class ReadableByteStreamController : public NativeObject
+{
+ public:
+ bool hasExternalSource();
+
+ static bool constructor(JSContext* cx, unsigned argc, Value* vp);
+ static const ClassSpec classSpec_;
+ static const Class class_;
+ static const ClassSpec protoClassSpec_;
+ static const Class protoClass_;
+};
+
+class ReadableStreamBYOBRequest : public NativeObject
+{
+ public:
+ static bool constructor(JSContext* cx, unsigned argc, Value* vp);
+ static const ClassSpec classSpec_;
+ static const Class class_;
+ static const ClassSpec protoClassSpec_;
+ static const Class protoClass_;
+};
+
+class ByteLengthQueuingStrategy : public NativeObject
+{
+ public:
+ static bool constructor(JSContext* cx, unsigned argc, Value* vp);
+ static const ClassSpec classSpec_;
+ static const Class class_;
+ static const ClassSpec protoClassSpec_;
+ static const Class protoClass_;
+};
+
+class CountQueuingStrategy : public NativeObject
+{
+ public:
+ static bool constructor(JSContext* cx, unsigned argc, Value* vp);
+ static const ClassSpec classSpec_;
+ static const Class class_;
+ static const ClassSpec protoClassSpec_;
+ static const Class protoClass_;
+};
+
+} // namespace js
+
+#endif /* builtin_Stream_h */
diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp
index 2608733853..f7af8b6fe9 100644
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1441,6 +1441,14 @@ RejectPromise(JSContext* cx, unsigned argc, Value* vp)
return result;
}
+static bool
+StreamsAreEnabled(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(cx->options().streams());
+ return true;
+}
+
static unsigned finalizeCount = 0;
static void
@@ -4144,6 +4152,10 @@ JS_FN_HELP("rejectPromise", RejectPromise, 2, 0,
"rejectPromise(promise, reason)",
" Reject a Promise by calling the JSAPI function JS::RejectPromise."),
+JS_FN_HELP("streamsAreEnabled", StreamsAreEnabled, 0, 0,
+"streamsAreEnabled()",
+" Returns a boolean indicating whether WHATWG Streams are enabled for the current compartment."),
+
JS_FN_HELP("makeFinalizeObserver", MakeFinalizeObserver, 0, 0,
"makeFinalizeObserver()",
" Get a special object whose finalization increases the counter returned\n"
diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js
index 57f6d738ca..a8510faa71 100644
--- a/js/src/builtin/TypedArray.js
+++ b/js/src/builtin/TypedArray.js
@@ -2,8 +2,6 @@
* 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/. */
-#include "TypedObjectConstants.h"
-
function ViewedArrayBufferIfReified(tarray) {
assert(IsTypedArray(tarray), "non-typed array asked for its buffer");
@@ -1781,7 +1779,7 @@ function ArrayBufferSlice(start, end) {
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
// Steps 19-21.
- ArrayBufferCopyData(new_, O, first | 0, newLen | 0, isWrapped);
+ ArrayBufferCopyData(new_, 0, O, first | 0, newLen | 0, isWrapped);
// Step 22.
return new_;
@@ -1863,7 +1861,7 @@ function SharedArrayBufferSlice(start, end) {
ThrowTypeError(JSMSG_SHORT_SHARED_ARRAY_BUFFER_RETURNED, newLen, actualLen);
// Steps 16-18.
- SharedArrayBufferCopyData(new_, O, first | 0, newLen | 0, isWrapped);
+ SharedArrayBufferCopyData(new_, 0, O, first | 0, newLen | 0, isWrapped);
// Step 19.
return new_;
diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js
index 51c5a574fd..1cc15ed68e 100644
--- a/js/src/builtin/Utilities.js
+++ b/js/src/builtin/Utilities.js
@@ -23,6 +23,7 @@
*/
#include "SelfHostingDefines.h"
+#include "TypedObjectConstants.h"
// Assertions and debug printing, defined here instead of in the header above
// to make `assert` invisible to C++.
diff --git a/js/src/js.msg b/js/src/js.msg
index e651b10137..b8aa77902d 100644
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -613,11 +613,39 @@ MSG_DEF(JSMSG_RETURN_NOT_CALLABLE, 0, JSEXN_TYPEERR, "property 'return' of i
MSG_DEF(JSMSG_ITERATOR_NO_THROW, 0, JSEXN_TYPEERR, "iterator does not have a 'throw' method")
// Async Iteration
-MSG_DEF(JSMSG_FOR_AWAIT_NOT_OF, 0, JSEXN_TYPEERR, "'for await' loop should be used with 'of'")
+MSG_DEF(JSMSG_FOR_AWAIT_NOT_OF, 0, JSEXN_SYNTAXERR, "'for await' loop should be used with 'of'")
MSG_DEF(JSMSG_NOT_AN_ASYNC_GENERATOR, 0, JSEXN_TYPEERR, "Not an async generator")
MSG_DEF(JSMSG_NOT_AN_ASYNC_ITERATOR, 0, JSEXN_TYPEERR, "Not an async from sync iterator")
MSG_DEF(JSMSG_GET_ASYNC_ITER_RETURNED_PRIMITIVE, 0, JSEXN_TYPEERR, "[Symbol.asyncIterator]() returned a non-object value")
+// ReadableStream
+MSG_DEF(JSMSG_READABLESTREAM_UNDERLYINGSOURCE_TYPE_WRONG,0, JSEXN_RANGEERR,"'underlyingSource.type' must be \"bytes\" or undefined.")
+MSG_DEF(JSMSG_READABLESTREAM_INVALID_READER_MODE, 0, JSEXN_RANGEERR,"'mode' must be \"byob\" or undefined.")
+MSG_DEF(JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, 1, JSEXN_RANGEERR, "'{0}' must be a finite, non-negative number.")
+MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_BYTESWRITTEN, 0, JSEXN_RANGEERR, "'bytesWritten' exceeds remaining length.")
+MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_SIZE, 0, JSEXN_RANGEERR, "view size does not match requested data.")
+MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_OFFSET, 0, JSEXN_RANGEERR, "view offset does not match requested position.")
+MSG_DEF(JSMSG_READABLESTREAM_NOT_LOCKED, 1, JSEXN_TYPEERR, "'{0}' may only be called on a locked stream.")
+MSG_DEF(JSMSG_READABLESTREAM_LOCKED, 0, JSEXN_TYPEERR, "A Reader may only be created for an unlocked ReadableStream.")
+MSG_DEF(JSMSG_READABLESTREAM_NOT_BYTE_STREAM_CONTROLLER, 1, JSEXN_TYPEERR, "{0} requires a ReadableByteStreamController.")
+MSG_DEF(JSMSG_READABLESTREAM_NOT_DEFAULT_CONTROLLER, 1, JSEXN_TYPEERR, "{0} requires a ReadableStreamDefaultController.")
+MSG_DEF(JSMSG_READABLESTREAM_CONTROLLER_SET, 0, JSEXN_TYPEERR, "The ReadableStream already has a controller defined.")
+MSG_DEF(JSMSG_READABLESTREAMREADER_NOT_OWNED, 1, JSEXN_TYPEERR, "The ReadableStream reader method '{0}' may only be called on a reader owned by a stream.")
+MSG_DEF(JSMSG_READABLESTREAMREADER_NOT_EMPTY, 1, JSEXN_TYPEERR, "The ReadableStream reader method '{0}' may not be called on a reader with read requests.")
+MSG_DEF(JSMSG_READABLESTREAMBYOBREADER_READ_EMPTY_VIEW, 0, JSEXN_TYPEERR, "ReadableStreamBYOBReader.read() was passed an empty TypedArrayBuffer view.")
+MSG_DEF(JSMSG_READABLESTREAMREADER_RELEASED, 0, JSEXN_TYPEERR, "The ReadableStream reader was released.")
+MSG_DEF(JSMSG_READABLESTREAMCONTROLLER_CLOSED, 1, JSEXN_TYPEERR, "'{0}' called on a stream already closing.")
+MSG_DEF(JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, 1, JSEXN_TYPEERR, "'{0}' may only be called on a stream in the 'readable' state.")
+MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNKSIZE,0, JSEXN_RANGEERR, "ReadableByteStreamController requires a positive integer or undefined for 'autoAllocateChunkSize'.")
+MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK, 1, JSEXN_TYPEERR, "{0} passed a bad chunk.")
+MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_CLOSE_PENDING_PULL, 0, JSEXN_TYPEERR, "The ReadableByteStreamController cannot be closed while the buffer is being filled.")
+MSG_DEF(JSMSG_READABLESTREAMBYOBREQUEST_NO_CONTROLLER, 1, JSEXN_TYPEERR, "ReadableStreamBYOBRequest method '{0}' called on a request with no controller.")
+MSG_DEF(JSMSG_READABLESTREAMBYOBREQUEST_RESPOND_CLOSED, 0, JSEXN_TYPEERR, "ReadableStreamBYOBRequest method 'respond' called with non-zero number of bytes with a closed controller.")
+MSG_DEF(JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, 1, JSEXN_TYPEERR, "ReadableStream method {0} not yet implemented")
+
+// Other Stream-related
+MSG_DEF(JSMSG_STREAM_INVALID_HIGHWATERMARK, 0, JSEXN_RANGEERR, "'highWaterMark' must be a non-negative, non-NaN number.")
+
// BigInt
MSG_DEF(JSMSG_BIGINT_TO_NUMBER, 0, JSEXN_TYPEERR, "can't convert BigInt to number")
MSG_DEF(JSMSG_NUMBER_TO_BIGINT, 0, JSEXN_RANGEERR, "can't convert non-finite number to BigInt")
diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build
index 35bc69b85b..1e5f43aa5a 100644
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -126,6 +126,11 @@ if CONFIG['ENABLE_ION']:
'testJitRValueAlloc.cpp',
]
+if CONFIG['ENABLE_STREAMS']:
+ UNIFIED_SOURCES += [
+ 'testReadableStream.cpp',
+ ]
+
DEFINES['EXPORT_JS_API'] = True
LOCAL_INCLUDES += [
diff --git a/js/src/jsapi-tests/testIntTypesABI.cpp b/js/src/jsapi-tests/testIntTypesABI.cpp
index 066e7ad1b8..420a07c7f8 100644
--- a/js/src/jsapi-tests/testIntTypesABI.cpp
+++ b/js/src/jsapi-tests/testIntTypesABI.cpp
@@ -29,6 +29,7 @@
#include "js/RequiredDefines.h"
#include "js/RootingAPI.h"
#include "js/SliceBudget.h"
+#include "js/Stream.h"
#include "js/StructuredClone.h"
#include "js/TracingAPI.h"
#include "js/TrackedOptimizationInfo.h"
diff --git a/js/src/jsapi-tests/testReadableStream.cpp b/js/src/jsapi-tests/testReadableStream.cpp
new file mode 100644
index 0000000000..f1d373d5d2
--- /dev/null
+++ b/js/src/jsapi-tests/testReadableStream.cpp
@@ -0,0 +1,676 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#include "jsapi.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace JS;
+
+char test_buffer_data[] = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+static JSObject*
+NewDefaultStream(JSContext* cx, HandleObject source = nullptr, HandleFunction size = nullptr,
+ double highWaterMark = 1, HandleObject proto = nullptr)
+{
+ RootedObject stream(cx, NewReadableDefaultStreamObject(cx, source, size, highWaterMark,
+ proto));
+ MOZ_ASSERT_IF(stream, IsReadableStream(stream));
+ return stream;
+}
+
+static JSObject*
+NewByteStream(JSContext* cx, double highWaterMark = 0, HandleObject proto = nullptr)
+{
+ RootedObject source(cx, JS_NewPlainObject(cx));
+ MOZ_ASSERT(source);
+
+ RootedObject stream(cx, NewReadableByteStreamObject(cx, source, highWaterMark, proto));
+ MOZ_ASSERT_IF(stream, IsReadableStream(stream));
+ return stream;
+}
+
+static bool dataRequestCBCalled = false;
+static void
+DataRequestCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags,
+ size_t desiredSize)
+{
+ MOZ_ASSERT(!dataRequestCBCalled, "Invalid test setup");
+ dataRequestCBCalled = true;
+}
+
+static bool writeIntoRequestBufferCBCalled = false;
+static void
+WriteIntoRequestBufferCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags,
+ void* buffer, size_t length, size_t* bytesWritten)
+{
+ MOZ_ASSERT(!writeIntoRequestBufferCBCalled, "Invalid test setup");
+ MOZ_ASSERT(length <= sizeof(test_buffer_data));
+ memcpy(buffer, test_buffer_data, length);
+ writeIntoRequestBufferCBCalled = true;
+ *bytesWritten = length;
+}
+
+static bool cancelStreamCBCalled = false;
+static Value cancelStreamReason;
+static Value
+CancelStreamCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags,
+ HandleValue reason)
+{
+ MOZ_ASSERT(!cancelStreamCBCalled, "Invalid test setup");
+ cancelStreamCBCalled = true;
+ cancelStreamReason = reason;
+ return reason;
+}
+
+static bool streamClosedCBCalled = false;
+static Value streamClosedReason;
+static void
+StreamClosedCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags)
+{
+ MOZ_ASSERT(!streamClosedCBCalled, "Invalid test setup");
+ streamClosedCBCalled = true;
+}
+
+static bool streamErroredCBCalled = false;
+static Value streamErroredReason;
+static void
+StreamErroredCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags,
+ HandleValue reason)
+{
+ MOZ_ASSERT(!streamErroredCBCalled, "Invalid test setup");
+ streamErroredCBCalled = true;
+ streamErroredReason = reason;
+}
+
+static bool finalizeStreamCBCalled = false;
+static void* finalizedStreamUnderlyingSource;
+static void
+FinalizeStreamCB(void* underlyingSource, uint8_t flags)
+{
+ MOZ_ASSERT(!finalizeStreamCBCalled, "Invalid test setup");
+ finalizeStreamCBCalled = true;
+ finalizedStreamUnderlyingSource = underlyingSource;
+}
+
+static void
+ResetCallbacks()
+{
+ dataRequestCBCalled = false;
+ writeIntoRequestBufferCBCalled = false;
+ cancelStreamReason = UndefinedValue();
+ cancelStreamCBCalled = false;
+ streamClosedCBCalled = false;
+ streamErroredCBCalled = false;
+ finalizeStreamCBCalled = false;
+}
+
+static bool
+GetIterResult(JSContext* cx, HandleObject promise, MutableHandleValue value, bool* done)
+{
+ RootedObject iterResult(cx, &GetPromiseResult(promise).toObject());
+
+ bool found;
+ if (!JS_HasProperty(cx, iterResult, "value", &found))
+ return false;
+ MOZ_ASSERT(found);
+ if (!JS_HasProperty(cx, iterResult, "done", &found))
+ return false;
+ MOZ_ASSERT(found);
+
+ RootedValue doneVal(cx);
+ if (!JS_GetProperty(cx, iterResult, "value", value))
+ return false;
+ if (!JS_GetProperty(cx, iterResult, "done", &doneVal))
+ return false;
+
+ *done = doneVal.toBoolean();
+ MOZ_ASSERT_IF(*done, value.isUndefined());
+
+ return true;
+}
+
+static JSObject*
+GetReadChunk(JSContext* cx, HandleObject readRequest)
+{
+ MOZ_ASSERT(GetPromiseState(readRequest) == PromiseState::Fulfilled);
+ RootedValue resultVal(cx, GetPromiseResult(readRequest));
+ MOZ_ASSERT(resultVal.isObject());
+ RootedObject result(cx, &resultVal.toObject());
+ RootedValue chunkVal(cx);
+ JS_GetProperty(cx, result, "value", &chunkVal);
+ return &chunkVal.toObject();
+}
+
+BEGIN_TEST(testReadableStream_NewReadableStream)
+{
+ RootedObject stream(cx, NewDefaultStream(cx));
+ CHECK(stream);
+ CHECK(ReadableStreamGetMode(stream) == ReadableStreamMode::Default);
+ return true;
+}
+END_TEST(testReadableStream_NewReadableStream)
+
+BEGIN_TEST(testReadableStream_NewReadableByteStream)
+{
+ RootedObject stream(cx, NewByteStream(cx));
+ CHECK(stream);
+ CHECK(ReadableStreamGetMode(stream) == ReadableStreamMode::Byte);
+ return true;
+}
+END_TEST(testReadableStream_NewReadableByteStream)
+
+BEGIN_TEST(testReadableStream_ReadableStreamGetReaderDefault)
+{
+ RootedObject stream(cx, NewDefaultStream(cx));
+ CHECK(stream);
+
+ RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default));
+ CHECK(reader);
+ CHECK(IsReadableStreamDefaultReader(reader));
+ CHECK(ReadableStreamIsLocked(stream));
+ CHECK(!ReadableStreamReaderIsClosed(reader));
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableStreamGetReaderDefault)
+
+BEGIN_TEST(testReadableStream_ReadableStreamGetReaderBYOB)
+{
+ RootedObject stream(cx, NewByteStream(cx));
+ CHECK(stream);
+
+ RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::BYOB));
+ CHECK(reader);
+ CHECK(IsReadableStreamBYOBReader(reader));
+ CHECK(ReadableStreamIsLocked(stream));
+ CHECK(!ReadableStreamReaderIsClosed(reader));
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableStreamGetReaderBYOB)
+
+BEGIN_TEST(testReadableStream_ReadableStreamTee)
+{
+ RootedObject stream(cx, NewDefaultStream(cx));
+ CHECK(stream);
+
+ RootedObject leftStream(cx);
+ RootedObject rightStream(cx);
+ CHECK(ReadableStreamTee(cx, stream, &leftStream, &rightStream));
+ CHECK(ReadableStreamIsLocked(stream));
+ CHECK(leftStream);
+ CHECK(IsReadableStream(leftStream));
+ CHECK(rightStream);
+ CHECK(IsReadableStream(rightStream));
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableStreamTee)
+
+BEGIN_TEST(testReadableStream_ReadableStreamEnqueue)
+{
+ RootedObject stream(cx, NewDefaultStream(cx));
+ CHECK(stream);
+
+ RootedObject chunk(cx, JS_NewPlainObject(cx));
+ CHECK(chunk);
+ RootedValue chunkVal(cx, ObjectValue(*chunk));
+ CHECK(ReadableStreamEnqueue(cx, stream, chunkVal));
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableStreamEnqueue)
+
+BEGIN_TEST(testReadableStream_ReadableByteStreamEnqueue)
+{
+ RootedObject stream(cx, NewDefaultStream(cx));
+ CHECK(stream);
+
+ RootedObject chunk(cx, JS_NewUint8Array(cx, 42));
+ CHECK(chunk);
+ CHECK(!ReadableByteStreamEnqueueBuffer(cx, stream, chunk));
+ CHECK(JS_IsExceptionPending(cx));
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableByteStreamEnqueue)
+
+BEGIN_TEST(testReadableStream_ReadableStreamDefaultReaderRead)
+{
+ RootedObject stream(cx, NewDefaultStream(cx));
+ CHECK(stream);
+ RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default));
+ CHECK(reader);
+
+ RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader));
+ CHECK(request);
+ CHECK(IsPromiseObject(request));
+ CHECK(GetPromiseState(request) == PromiseState::Pending);
+
+ RootedObject chunk(cx, JS_NewPlainObject(cx));
+ CHECK(chunk);
+ RootedValue chunkVal(cx, ObjectValue(*chunk));
+ CHECK(ReadableStreamEnqueue(cx, stream, chunkVal));
+
+ CHECK(GetReadChunk(cx, request) == chunk);
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableStreamDefaultReaderRead)
+
+BEGIN_TEST(testReadableStream_ReadableByteStreamDefaultReaderRead)
+{
+ RootedObject stream(cx, NewByteStream(cx));
+ CHECK(stream);
+
+ RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default));
+ CHECK(reader);
+
+ RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader));
+ CHECK(request);
+ CHECK(IsPromiseObject(request));
+ CHECK(GetPromiseState(request) == PromiseState::Pending);
+
+ size_t length = sizeof(test_buffer_data);
+ RootedObject buffer(cx, JS_NewArrayBufferWithExternalContents(cx, length, test_buffer_data));
+ CHECK(buffer);
+ RootedObject chunk(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, 0, length));
+ CHECK(chunk);
+ bool isShared;
+ CHECK(!JS_IsDetachedArrayBufferObject(buffer));
+
+ CHECK(ReadableByteStreamEnqueueBuffer(cx, stream, chunk));
+
+ CHECK(JS_IsDetachedArrayBufferObject(buffer));
+ RootedObject readChunk(cx, GetReadChunk(cx, request));
+ CHECK(JS_IsUint8Array(readChunk));
+ void* readBufferData;
+ {
+ JS::AutoCheckCannotGC autoNoGC(cx);
+ readBufferData = JS_GetArrayBufferViewData(readChunk, &isShared, autoNoGC);
+ }
+ CHECK(readBufferData);
+ CHECK(!memcmp(test_buffer_data, readBufferData, length));
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableByteStreamDefaultReaderRead)
+
+BEGIN_TEST(testReadableStream_ReadableByteStreamBYOBReaderRead)
+{
+ RootedObject stream(cx, NewByteStream(cx));
+ CHECK(stream);
+
+ RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::BYOB));
+ CHECK(reader);
+
+ size_t length = sizeof(test_buffer_data);
+ RootedObject targetArray(cx, JS_NewUint8Array(cx, length));
+ bool isShared;
+
+ RootedObject request(cx, ReadableStreamBYOBReaderRead(cx, reader, targetArray));
+ CHECK(request);
+ CHECK(IsPromiseObject(request));
+ CHECK(GetPromiseState(request) == PromiseState::Pending);
+ CHECK(JS_IsDetachedArrayBufferObject(JS_GetArrayBufferViewBuffer(cx, targetArray, &isShared)));
+
+ RootedObject buffer(cx, JS_NewArrayBufferWithExternalContents(cx, length, test_buffer_data));
+ CHECK(buffer);
+ CHECK(!JS_IsDetachedArrayBufferObject(buffer));
+
+ CHECK(ReadableByteStreamEnqueueBuffer(cx, stream, buffer));
+
+ CHECK(JS_IsDetachedArrayBufferObject(buffer));
+ RootedObject readChunk(cx, GetReadChunk(cx, request));
+ CHECK(JS_IsUint8Array(readChunk));
+ void* readBufferData;
+ {
+ JS::AutoCheckCannotGC autoNoGC(cx);
+ readBufferData = JS_GetArrayBufferViewData(readChunk, &isShared, autoNoGC);
+ }
+ CHECK(readBufferData);
+ CHECK(!memcmp(test_buffer_data, readBufferData, length));
+ // TODO: eliminate the memcpy that happens here.
+// CHECK(readBufferData == test_buffer_data);
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableByteStreamBYOBReaderRead)
+
+BEGIN_TEST(testReadableStream_ReadableStreamDefaultReaderClose)
+{
+ SetReadableStreamCallbacks(cx, &DataRequestCB, &WriteIntoRequestBufferCB,
+ &CancelStreamCB, &StreamClosedCB, &StreamErroredCB,
+ &FinalizeStreamCB);
+ RootedObject stream(cx, NewDefaultStream(cx));
+ CHECK(stream);
+ RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default));
+ CHECK(reader);
+
+ RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader));
+ CHECK(request);
+ CHECK(IsPromiseObject(request));
+ CHECK(GetPromiseState(request) == PromiseState::Pending);
+
+ CHECK(ReadableStreamClose(cx, stream));
+
+ bool done;
+ RootedValue value(cx);
+ CHECK(GetPromiseState(request) == PromiseState::Fulfilled);
+ CHECK(GetIterResult(cx, request, &value, &done));
+ CHECK(value.isUndefined());
+ CHECK(done);
+
+ // The callbacks are only invoked for external streams.
+ CHECK(!streamClosedCBCalled);
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableStreamDefaultReaderClose)
+
+BEGIN_TEST(testReadableStream_ReadableStreamDefaultReaderError)
+{
+ ResetCallbacks();
+ SetReadableStreamCallbacks(cx, &DataRequestCB, &WriteIntoRequestBufferCB,
+ &CancelStreamCB, &StreamClosedCB, &StreamErroredCB,
+ &FinalizeStreamCB);
+ RootedObject stream(cx, NewDefaultStream(cx));
+ CHECK(stream);
+ RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default));
+ CHECK(reader);
+
+ RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader));
+ CHECK(request);
+ CHECK(IsPromiseObject(request));
+ CHECK(GetPromiseState(request) == PromiseState::Pending);
+
+ CHECK(ReadableStreamIsLocked(stream));
+ CHECK(ReadableStreamIsReadable(stream));
+ RootedValue error(cx, Int32Value(42));
+ CHECK(ReadableStreamError(cx, stream, error));
+
+ CHECK(GetPromiseState(request) == PromiseState::Rejected);
+ RootedValue reason(cx, GetPromiseResult(request));
+ CHECK(reason.isInt32());
+ CHECK(reason.toInt32() == 42);
+
+ // The callbacks are only invoked for external streams.
+ CHECK(!streamErroredCBCalled);
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableStreamDefaultReaderError)
+
+static JSObject*
+NewExternalSourceStream(JSContext* cx, void* underlyingSource,
+ RequestReadableStreamDataCallback dataRequestCallback,
+ WriteIntoReadRequestBufferCallback writeIntoReadRequestCallback,
+ CancelReadableStreamCallback cancelCallback,
+ ReadableStreamClosedCallback closedCallback,
+ ReadableStreamErroredCallback erroredCallback,
+ ReadableStreamFinalizeCallback finalizeCallback)
+{
+ SetReadableStreamCallbacks(cx, dataRequestCallback, writeIntoReadRequestCallback,
+ cancelCallback, closedCallback, erroredCallback,
+ finalizeCallback);
+ RootedObject stream(cx, NewReadableExternalSourceStreamObject(cx, underlyingSource));
+ MOZ_ASSERT_IF(stream, IsReadableStream(stream));
+ return stream;
+}
+
+BEGIN_TEST(testReadableStream_CreateReadableByteStreamWithExternalSource)
+{
+ ResetCallbacks();
+
+ RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB,
+ &WriteIntoRequestBufferCB, &CancelStreamCB,
+ &StreamClosedCB, &StreamErroredCB,
+ &FinalizeStreamCB));
+ CHECK(stream);
+ CHECK(ReadableStreamGetMode(stream) == JS::ReadableStreamMode::ExternalSource);
+ void* underlyingSource;
+ CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &underlyingSource));
+ CHECK(underlyingSource == &test_buffer_data);
+ CHECK(ReadableStreamIsLocked(stream));
+ ReadableStreamReleaseExternalUnderlyingSource(stream);
+
+ return true;
+}
+END_TEST(testReadableStream_CreateReadableByteStreamWithExternalSource)
+
+BEGIN_TEST(testReadableStream_ExternalSourceCancel)
+{
+ ResetCallbacks();
+
+ RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB,
+ &WriteIntoRequestBufferCB, &CancelStreamCB,
+ &StreamClosedCB, &StreamErroredCB,
+ &FinalizeStreamCB));
+ CHECK(stream);
+ RootedValue reason(cx, Int32Value(42));
+ CHECK(ReadableStreamCancel(cx, stream, reason));
+ CHECK(cancelStreamCBCalled);
+ CHECK(cancelStreamReason == reason);
+
+ return true;
+}
+END_TEST(testReadableStream_ExternalSourceCancel)
+
+BEGIN_TEST(testReadableStream_ExternalSourceGetReader)
+{
+ ResetCallbacks();
+
+ RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB,
+ &WriteIntoRequestBufferCB, &CancelStreamCB,
+ &StreamClosedCB, &StreamErroredCB,
+ &FinalizeStreamCB));
+ CHECK(stream);
+
+ RootedValue streamVal(cx, ObjectValue(*stream));
+ CHECK(JS_SetProperty(cx, global, "stream", streamVal));
+ RootedValue rval(cx);
+ EVAL("stream.getReader()", &rval);
+ CHECK(rval.isObject());
+ RootedObject reader(cx, &rval.toObject());
+ CHECK(IsReadableStreamDefaultReader(reader));
+
+ return true;
+}
+END_TEST(testReadableStream_ExternalSourceGetReader)
+
+BEGIN_TEST(testReadableStream_ExternalSourceUpdateAvailableData)
+{
+ ResetCallbacks();
+
+ RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB,
+ &WriteIntoRequestBufferCB, &CancelStreamCB,
+ &StreamClosedCB, &StreamErroredCB,
+ &FinalizeStreamCB));
+ CHECK(stream);
+
+ ReadableStreamUpdateDataAvailableFromSource(cx, stream, 1024);
+
+ return true;
+}
+END_TEST(testReadableStream_ExternalSourceUpdateAvailableData)
+
+struct ReadFromExternalSourceFixture : public JSAPITest
+{
+ virtual ~ReadFromExternalSourceFixture() {}
+
+ bool readWithoutDataAvailable(const char* evalSrc, const char* evalSrc2,
+ uint32_t writtenLength)
+ {
+ ResetCallbacks();
+ definePrint();
+
+ RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB,
+ &WriteIntoRequestBufferCB,
+ &CancelStreamCB,
+ &StreamClosedCB, &StreamErroredCB,
+ &FinalizeStreamCB));
+ CHECK(stream);
+ js::RunJobs(cx);
+ void* underlyingSource;
+ CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &underlyingSource));
+ CHECK(underlyingSource == &test_buffer_data);
+ CHECK(ReadableStreamIsLocked(stream));
+ ReadableStreamReleaseExternalUnderlyingSource(stream);
+
+ RootedValue streamVal(cx, ObjectValue(*stream));
+ CHECK(JS_SetProperty(cx, global, "stream", streamVal));
+
+ RootedValue rval(cx);
+ EVAL(evalSrc, &rval);
+ CHECK(dataRequestCBCalled);
+ CHECK(!writeIntoRequestBufferCBCalled);
+ CHECK(rval.isObject());
+ RootedObject promise(cx, &rval.toObject());
+ CHECK(IsPromiseObject(promise));
+ CHECK(GetPromiseState(promise) == PromiseState::Pending);
+
+ size_t length = sizeof(test_buffer_data);
+ ReadableStreamUpdateDataAvailableFromSource(cx, stream, length);
+
+ CHECK(writeIntoRequestBufferCBCalled);
+ CHECK(GetPromiseState(promise) == PromiseState::Fulfilled);
+ RootedValue iterVal(cx);
+ bool done;
+ if (!GetIterResult(cx, promise, &iterVal, &done))
+ return false;
+
+ CHECK(!done);
+ RootedObject chunk(cx, &iterVal.toObject());
+ CHECK(JS_IsUint8Array(chunk));
+
+ {
+ JS::AutoCheckCannotGC noGC(cx);
+ bool dummy;
+ void* buffer = JS_GetArrayBufferViewData(chunk, &dummy, noGC);
+ CHECK(!memcmp(buffer, test_buffer_data, writtenLength));
+ }
+
+ dataRequestCBCalled = false;
+ writeIntoRequestBufferCBCalled = false;
+ EVAL(evalSrc2, &rval);
+ CHECK(dataRequestCBCalled);
+ CHECK(!writeIntoRequestBufferCBCalled);
+
+ return true;
+ }
+
+ bool readWithDataAvailable(const char* evalSrc, uint32_t writtenLength) {
+ ResetCallbacks();
+ definePrint();
+
+ RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB,
+ &WriteIntoRequestBufferCB, &CancelStreamCB,
+ &StreamClosedCB, &StreamErroredCB,
+ &FinalizeStreamCB));
+ CHECK(stream);
+ void* underlyingSource;
+ CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &underlyingSource));
+ CHECK(underlyingSource == &test_buffer_data);
+ CHECK(ReadableStreamIsLocked(stream));
+ ReadableStreamReleaseExternalUnderlyingSource(stream);
+
+ size_t length = sizeof(test_buffer_data);
+ ReadableStreamUpdateDataAvailableFromSource(cx, stream, length);
+
+ RootedValue streamVal(cx, ObjectValue(*stream));
+ CHECK(JS_SetProperty(cx, global, "stream", streamVal));
+
+ RootedValue rval(cx);
+ EVAL(evalSrc, &rval);
+ CHECK(writeIntoRequestBufferCBCalled);
+ CHECK(rval.isObject());
+ RootedObject promise(cx, &rval.toObject());
+ CHECK(IsPromiseObject(promise));
+ CHECK(GetPromiseState(promise) == PromiseState::Fulfilled);
+ RootedValue iterVal(cx);
+ bool done;
+ if (!GetIterResult(cx, promise, &iterVal, &done))
+ return false;
+
+ CHECK(!done);
+ RootedObject chunk(cx, &iterVal.toObject());
+ CHECK(JS_IsUint8Array(chunk));
+
+ {
+ JS::AutoCheckCannotGC noGC(cx);
+ bool dummy;
+ void* buffer = JS_GetArrayBufferViewData(chunk, &dummy, noGC);
+ CHECK(!memcmp(buffer, test_buffer_data, writtenLength));
+ }
+
+ return true;
+ }
+};
+
+BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceReadDefaultWithoutDataAvailable)
+{
+ return readWithoutDataAvailable("r = stream.getReader(); r.read()", "r.read()", sizeof(test_buffer_data));
+}
+END_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceReadDefaultWithoutDataAvailable)
+
+BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceCloseWithPendingRead)
+{
+ CHECK(readWithoutDataAvailable("r = stream.getReader(); request0 = r.read(); "
+ "request1 = r.read(); request0", "r.read()",
+ sizeof(test_buffer_data)));
+
+ RootedValue val(cx);
+ CHECK(JS_GetProperty(cx, global, "request1", &val));
+ CHECK(val.isObject());
+ RootedObject request(cx, &val.toObject());
+ CHECK(IsPromiseObject(request));
+ CHECK(GetPromiseState(request) == PromiseState::Pending);
+
+ CHECK(JS_GetProperty(cx, global, "stream", &val));
+ RootedObject stream(cx, &val.toObject());
+ ReadableStreamClose(cx, stream);
+
+ val = GetPromiseResult(request);
+ MOZ_ASSERT(val.isObject());
+ RootedObject result(cx, &val.toObject());
+
+ JS_GetProperty(cx, result, "done", &val);
+ CHECK(val.isBoolean());
+ CHECK(val.toBoolean() == true);
+
+ JS_GetProperty(cx, result, "value", &val);
+ CHECK(val.isUndefined());
+ return true;
+}
+END_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceCloseWithPendingRead)
+
+BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceReadDefaultWithDataAvailable)
+{
+ return readWithDataAvailable("r = stream.getReader(); r.read()", sizeof(test_buffer_data));
+}
+END_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceReadDefaultWithDataAvailable)
+
+BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceReadBYOBWithoutDataAvailable)
+{
+ return readWithoutDataAvailable("r = stream.getReader({mode: 'byob'}); r.read(new Uint8Array(63))", "r.read(new Uint8Array(10))", 10);
+}
+END_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceReadBYOBWithoutDataAvailable)
+
+BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceReadBYOBWithDataAvailable)
+{
+ return readWithDataAvailable("r = stream.getReader({mode: 'byob'}); r.read(new Uint8Array(10))", 10);
+}
+END_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceReadBYOBWithDataAvailable)
diff --git a/js/src/jsapi-tests/tests.cpp b/js/src/jsapi-tests/tests.cpp
index b75fa6602d..61e79eab6d 100644
--- a/js/src/jsapi-tests/tests.cpp
+++ b/js/src/jsapi-tests/tests.cpp
@@ -81,6 +81,10 @@ JSObject* JSAPITest::createGlobal(JSPrincipals* principals)
/* Create the global object. */
JS::RootedObject newGlobal(cx);
JS::CompartmentOptions options;
+#ifdef ENABLE_STREAMS
+ options.creationOptions().setStreamsEnabled(true);
+#endif
+ printf("enabled\n");
options.behaviors().setVersion(JSVERSION_LATEST);
newGlobal = JS_NewGlobalObject(cx, getGlobalClass(), principals, JS::FireOnNewGlobalHook,
options);
diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp
index 0fd82f7c2d..9067ca3d3d 100644
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -46,6 +46,7 @@
#include "builtin/MapObject.h"
#include "builtin/Promise.h"
#include "builtin/RegExp.h"
+#include "builtin/Stream.h"
#include "builtin/SymbolObject.h"
#ifdef ENABLE_BINARYDATA
# include "builtin/TypedObject.h"
@@ -5095,7 +5096,6 @@ CallOriginalPromiseThenImpl(JSContext* cx, JS::HandleObject promiseObj,
return false;
}
return true;
-
}
JS_PUBLIC_API(JSObject*)
@@ -5126,7 +5126,7 @@ JS::AddPromiseReactions(JSContext* cx, JS::HandleObject promiseObj,
* Unforgeable version of Promise.all for internal use.
*
* Takes a dense array of Promise objects and returns a promise that's
- * resolved with an array of resolution values when all those promises ahve
+ * resolved with an array of resolution values when all those promises have
* been resolved, or rejected with the rejection value of the first rejected
* promise.
*
@@ -5141,6 +5141,368 @@ JS::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises)
return js::GetWaitForAllPromise(cx, promises);
}
+JS_PUBLIC_API(JSObject*)
+JS::NewReadableDefaultStreamObject(JSContext* cx,
+ JS::HandleObject underlyingSource /* = nullptr */,
+ JS::HandleFunction size /* = nullptr */,
+ double highWaterMark /* = 1 */,
+ JS::HandleObject proto /* = nullptr */)
+{
+ MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+
+ RootedObject source(cx, underlyingSource);
+ if (!source) {
+ source = NewBuiltinClassInstance<PlainObject>(cx);
+ if (!source)
+ return nullptr;
+ }
+ RootedValue sourceVal(cx, ObjectValue(*source));
+ RootedValue sizeVal(cx, size ? ObjectValue(*size) : UndefinedValue());
+ RootedValue highWaterMarkVal(cx, NumberValue(highWaterMark));
+ return ReadableStream::createDefaultStream(cx, sourceVal, sizeVal, highWaterMarkVal, proto);
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::NewReadableByteStreamObject(JSContext* cx,
+ JS::HandleObject underlyingSource /* = nullptr */,
+ double highWaterMark /* = 1 */,
+ JS::HandleObject proto /* = nullptr */)
+{
+ MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+
+ RootedObject source(cx, underlyingSource);
+ if (!source) {
+ source = NewBuiltinClassInstance<PlainObject>(cx);
+ if (!source)
+ return nullptr;
+ }
+ RootedValue sourceVal(cx, ObjectValue(*source));
+ RootedValue highWaterMarkVal(cx, NumberValue(highWaterMark));
+ return ReadableStream::createByteStream(cx, sourceVal, highWaterMarkVal, proto);
+}
+
+extern JS_PUBLIC_API(void)
+JS::SetReadableStreamCallbacks(JSContext* cx,
+ JS::RequestReadableStreamDataCallback dataRequestCallback,
+ JS::WriteIntoReadRequestBufferCallback writeIntoReadRequestCallback,
+ JS::CancelReadableStreamCallback cancelCallback,
+ JS::ReadableStreamClosedCallback closedCallback,
+ JS::ReadableStreamErroredCallback erroredCallback,
+ JS::ReadableStreamFinalizeCallback finalizeCallback)
+{
+ MOZ_ASSERT(dataRequestCallback);
+ MOZ_ASSERT(writeIntoReadRequestCallback);
+ MOZ_ASSERT(cancelCallback);
+ MOZ_ASSERT(closedCallback);
+ MOZ_ASSERT(erroredCallback);
+ MOZ_ASSERT(finalizeCallback);
+
+ JSRuntime* rt = cx->runtime();
+
+ MOZ_ASSERT(!rt->readableStreamDataRequestCallback);
+ MOZ_ASSERT(!rt->readableStreamWriteIntoReadRequestCallback);
+ MOZ_ASSERT(!rt->readableStreamCancelCallback);
+ MOZ_ASSERT(!rt->readableStreamClosedCallback);
+ MOZ_ASSERT(!rt->readableStreamErroredCallback);
+ MOZ_ASSERT(!rt->readableStreamFinalizeCallback);
+
+ rt->readableStreamDataRequestCallback = dataRequestCallback;
+ rt->readableStreamWriteIntoReadRequestCallback = writeIntoReadRequestCallback;
+ rt->readableStreamCancelCallback = cancelCallback;
+ rt->readableStreamClosedCallback = closedCallback;
+ rt->readableStreamErroredCallback = erroredCallback;
+ rt->readableStreamFinalizeCallback = finalizeCallback;
+}
+
+JS_PUBLIC_API(bool)
+JS::HasReadableStreamCallbacks(JSContext* cx)
+{
+ return cx->runtime()->readableStreamDataRequestCallback;
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::NewReadableExternalSourceStreamObject(JSContext* cx, void* underlyingSource,
+ uint8_t flags /* = 0 */,
+ HandleObject proto /* = nullptr */)
+{
+ MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+
+#ifdef DEBUG
+ JSRuntime* rt = cx->runtime();
+ MOZ_ASSERT(rt->readableStreamDataRequestCallback);
+ MOZ_ASSERT(rt->readableStreamWriteIntoReadRequestCallback);
+ MOZ_ASSERT(rt->readableStreamCancelCallback);
+ MOZ_ASSERT(rt->readableStreamClosedCallback);
+ MOZ_ASSERT(rt->readableStreamErroredCallback);
+ MOZ_ASSERT(rt->readableStreamFinalizeCallback);
+#endif // DEBUG
+
+ return ReadableStream::createExternalSourceStream(cx, underlyingSource, flags, proto);
+}
+
+JS_PUBLIC_API(uint8_t)
+JS::ReadableStreamGetEmbeddingFlags(const JSObject* stream)
+{
+ return stream->as<ReadableStream>().embeddingFlags();
+}
+
+JS_PUBLIC_API(bool)
+JS::IsReadableStream(const JSObject* obj)
+{
+ return obj->is<ReadableStream>();
+}
+
+JS_PUBLIC_API(bool)
+JS::IsReadableStreamReader(const JSObject* obj)
+{
+ return obj->is<ReadableStreamDefaultReader>() || obj->is<ReadableStreamBYOBReader>();
+}
+
+JS_PUBLIC_API(bool)
+JS::IsReadableStreamDefaultReader(const JSObject* obj)
+{
+ return obj->is<ReadableStreamDefaultReader>();
+}
+
+JS_PUBLIC_API(bool)
+JS::IsReadableStreamBYOBReader(const JSObject* obj)
+{
+ return obj->is<ReadableStreamBYOBReader>();
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamIsReadable(const JSObject* stream)
+{
+ return stream->as<ReadableStream>().readable();
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamIsLocked(const JSObject* stream)
+{
+ return stream->as<ReadableStream>().locked();
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamIsDisturbed(const JSObject* stream)
+{
+ return stream->as<ReadableStream>().disturbed();
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::ReadableStreamCancel(JSContext* cx, HandleObject streamObj, HandleValue reason)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, streamObj);
+ assertSameCompartment(cx, reason);
+
+ Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
+ return ReadableStream::cancel(cx, stream, reason);
+}
+
+JS_PUBLIC_API(JS::ReadableStreamMode)
+JS::ReadableStreamGetMode(const JSObject* stream)
+{
+ return stream->as<ReadableStream>().mode();
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::ReadableStreamGetReader(JSContext* cx, HandleObject streamObj, ReadableStreamReaderMode mode)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, streamObj);
+
+ Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
+ return ReadableStream::getReader(cx, stream, mode);
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamGetExternalUnderlyingSource(JSContext* cx, HandleObject streamObj, void** source)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, streamObj);
+
+ Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
+ return ReadableStream::getExternalSource(cx, stream, source);
+}
+
+JS_PUBLIC_API(void)
+JS::ReadableStreamReleaseExternalUnderlyingSource(JSObject* stream)
+{
+ stream->as<ReadableStream>().releaseExternalSource();
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamUpdateDataAvailableFromSource(JSContext* cx, JS::HandleObject streamObj,
+ uint32_t availableData)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, streamObj);
+
+ Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
+ return ReadableStream::updateDataAvailableFromSource(cx, stream, availableData);
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamTee(JSContext* cx, HandleObject streamObj,
+ MutableHandleObject branch1Obj, MutableHandleObject branch2Obj)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, streamObj);
+
+ Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
+ Rooted<ReadableStream*> branch1Stream(cx);
+ Rooted<ReadableStream*> branch2Stream(cx);
+
+ if (!ReadableStream::tee(cx, stream, false, &branch1Stream, &branch2Stream))
+ return false;
+
+ branch1Obj.set(branch1Stream);
+ branch2Obj.set(branch2Stream);
+
+ return true;
+}
+
+JS_PUBLIC_API(void)
+JS::ReadableStreamGetDesiredSize(JSObject* streamObj, bool* hasValue, double* value)
+{
+ streamObj->as<ReadableStream>().desiredSize(hasValue, value);
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamClose(JSContext* cx, HandleObject streamObj)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, streamObj);
+
+ Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
+ return ReadableStream::close(cx, stream);
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamEnqueue(JSContext* cx, HandleObject streamObj, HandleValue chunk)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, streamObj);
+ assertSameCompartment(cx, chunk);
+
+ Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
+ if (stream->mode() != JS::ReadableStreamMode::Default) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_NOT_DEFAULT_CONTROLLER,
+ "JS::ReadableStreamEnqueue");
+ return false;
+ }
+ return ReadableStream::enqueue(cx, stream, chunk);
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableByteStreamEnqueueBuffer(JSContext* cx, HandleObject streamObj, HandleObject chunkObj)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, streamObj);
+ assertSameCompartment(cx, chunkObj);
+
+ Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
+ if (stream->mode() != JS::ReadableStreamMode::Byte) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_NOT_BYTE_STREAM_CONTROLLER,
+ "JS::ReadableByteStreamEnqueueBuffer");
+ return false;
+ }
+
+ Rooted<ArrayBufferObject*> buffer(cx);
+ if (chunkObj->is<ArrayBufferViewObject>()) {
+ bool dummy;
+ buffer = &JS_GetArrayBufferViewBuffer(cx, chunkObj, &dummy)->as<ArrayBufferObject>();
+ } else if (chunkObj->is<ArrayBufferObject>()) {
+ buffer = &chunkObj->as<ArrayBufferObject>();
+ } else {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK,
+ "JS::ReadableByteStreamEnqueueBuffer");
+ return false;
+ }
+
+ return ReadableStream::enqueueBuffer(cx, stream, buffer);
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamError(JSContext* cx, HandleObject streamObj, HandleValue error)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, streamObj);
+ assertSameCompartment(cx, error);
+
+ Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
+ return js::ReadableStream::error(cx, stream, error);
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamReaderIsClosed(const JSObject* reader)
+{
+ return js::ReadableStreamReaderIsClosed(reader);
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamReaderCancel(JSContext* cx, HandleObject reader, HandleValue reason)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, reader);
+ assertSameCompartment(cx, reason);
+
+ return js::ReadableStreamReaderCancel(cx, reader, reason);
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamReaderReleaseLock(JSContext* cx, HandleObject reader)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, reader);
+
+ return js::ReadableStreamReaderReleaseLock(cx, reader);
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::ReadableStreamDefaultReaderRead(JSContext* cx, HandleObject readerObj)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, readerObj);
+
+ Rooted<ReadableStreamDefaultReader*> reader(cx, &readerObj->as<ReadableStreamDefaultReader>());
+ return js::ReadableStreamDefaultReader::read(cx, reader);
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::ReadableStreamBYOBReaderRead(JSContext* cx, HandleObject readerObj, HandleObject viewObj)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, readerObj);
+ assertSameCompartment(cx, viewObj);
+
+ Rooted<ReadableStreamBYOBReader*> reader(cx, &readerObj->as<ReadableStreamBYOBReader>());
+ Rooted<ArrayBufferViewObject*> view(cx, &viewObj->as<ArrayBufferViewObject>());
+ return js::ReadableStreamBYOBReader::read(cx, reader, view);
+}
+
JS_PUBLIC_API(void)
JS::SetAsyncTaskCallbacks(JSContext* cx, JS::StartAsyncTaskCallback start,
JS::FinishAsyncTaskCallback finish)
diff --git a/js/src/jsapi.h b/js/src/jsapi.h
index f80d2602e6..951b612502 100644
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -35,6 +35,7 @@
#include "js/Principals.h"
#include "js/Realm.h"
#include "js/RootingAPI.h"
+#include "js/Stream.h"
#include "js/TracingAPI.h"
#include "js/UniquePtr.h"
#include "js/Utility.h"
@@ -1161,6 +1162,16 @@ class JS_PUBLIC_API(ContextOptions) {
return *this;
}
+ bool streams() const { return streams_; }
+ ContextOptions& setStreams(bool flag) {
+ streams_ = flag;
+ return *this;
+ }
+ ContextOptions& toggleStreams() {
+ streams_ = !streams_;
+ return *this;
+ }
+
bool nativeRegExp() const { return nativeRegExp_; }
ContextOptions& setNativeRegExp(bool flag) {
nativeRegExp_ = flag;
@@ -1243,6 +1254,7 @@ class JS_PUBLIC_API(ContextOptions) {
bool strictMode_ : 1;
bool extraWarnings_ : 1;
bool arrayProtoValues_ : 1;
+ bool streams_ : 1;
};
JS_PUBLIC_API(ContextOptions&)
diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h
index b4c4e15801..c463019dac 100644
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -1787,6 +1787,9 @@ UnwrapArrayBufferView(JSObject* obj);
extern JS_FRIEND_API(JSObject*)
UnwrapSharedArrayBuffer(JSObject* obj);
+extern JS_FRIEND_API(JSObject*)
+UnwrapReadableStream(JSObject* obj);
+
namespace detail {
@@ -2005,6 +2008,12 @@ JS_IsArrayBufferViewObject(JSObject* obj);
extern JS_FRIEND_API(uint32_t)
JS_GetArrayBufferViewByteLength(JSObject* obj);
+/**
+ * More generic name for JS_GetTypedArrayByteOffset to cover DataViews as well
+ */
+extern JS_FRIEND_API(uint32_t)
+JS_GetArrayBufferViewByteOffset(JSObject* obj);
+
/*
* Return a pointer to the start of the data referenced by a typed array. The
* data is still owned by the typed array, and should not be modified on
diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp
index fd23e6ccd5..beff701bd0 100644
--- a/js/src/jsnum.cpp
+++ b/js/src/jsnum.cpp
@@ -1003,6 +1003,13 @@ static const JSFunctionSpec number_methods[] = {
JS_FS_END
};
+bool
+js::IsInteger(const Value& val)
+{
+ return val.isInt32() ||
+ (mozilla::IsFinite(val.toDouble()) && JS::ToInteger(val.toDouble()) == val.toDouble());
+}
+
// ES6 draft ES6 15.7.3.12
static bool
Number_isInteger(JSContext* cx, unsigned argc, Value* vp)
@@ -1013,9 +1020,7 @@ Number_isInteger(JSContext* cx, unsigned argc, Value* vp)
return true;
}
Value val = args[0];
- args.rval().setBoolean(val.isInt32() ||
- (mozilla::IsFinite(val.toDouble()) &&
- JS::ToInteger(val.toDouble()) == val.toDouble()));
+ args.rval().setBoolean(js::IsInteger(val));
return true;
}
diff --git a/js/src/jsnum.h b/js/src/jsnum.h
index ee07d0a35d..7c96bfb652 100644
--- a/js/src/jsnum.h
+++ b/js/src/jsnum.h
@@ -72,6 +72,10 @@ Int32ToString(ExclusiveContext* cx, int32_t i);
extern JSAtom*
Int32ToAtom(ExclusiveContext* cx, int32_t si);
+// ES6 15.7.3.12
+extern bool
+IsInteger(const Value& val);
+
/*
* Convert an integer or double (contained in the given value) to a string and
* append to the given buffer.
diff --git a/js/src/jsprototypes.h b/js/src/jsprototypes.h
index 52d0d8ea4a..9822a47b6c 100644
--- a/js/src/jsprototypes.h
+++ b/js/src/jsprototypes.h
@@ -108,6 +108,17 @@ IF_SAB(real,imaginary)(Atomics, InitAtomicsClass, OCLASP(Atomics)) \
imaginary(WasmMemory, dummy, dummy) \
imaginary(WasmTable, dummy, dummy) \
real(Promise, InitViaClassSpec, OCLASP(Promise)) \
+ real(ReadableStream, InitViaClassSpec, &js::ReadableStream::class_) \
+ real(ReadableStreamDefaultReader, InitViaClassSpec, &js::ReadableStreamDefaultReader::class_) \
+ real(ReadableStreamBYOBReader, InitViaClassSpec, &js::ReadableStreamBYOBReader::class_) \
+ real(ReadableStreamDefaultController, InitViaClassSpec, &js::ReadableStreamDefaultController::class_) \
+ real(ReadableByteStreamController, InitViaClassSpec, &js::ReadableByteStreamController::class_) \
+ real(ReadableStreamBYOBRequest, InitViaClassSpec, &js::ReadableStreamBYOBRequest::class_) \
+ imaginary(WritableStream, dummy, dummy) \
+ imaginary(WritableStreamDefaultWriter, dummy, dummy) \
+ imaginary(WritableStreamDefaultController,dummy, dummy) \
+ real(ByteLengthQueuingStrategy, InitViaClassSpec, &js::ByteLengthQueuingStrategy::class_) \
+ real(CountQueuingStrategy, InitViaClassSpec, &js::CountQueuingStrategy::class_) \
#define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro)
diff --git a/js/src/moz.build b/js/src/moz.build
index 5429888a2c..d42cf59730 100644
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -92,6 +92,7 @@ EXPORTS.js += [
'../public/Result.h',
'../public/RootingAPI.h',
'../public/SliceBudget.h',
+ '../public/Stream.h',
'../public/StructuredClone.h',
'../public/SweepingAPI.h',
'../public/TraceKind.h',
@@ -384,6 +385,7 @@ main_deunified_sources = [
SOURCES += [
'builtin/BigInt.cpp',
'builtin/RegExp.cpp',
+ 'builtin/Stream.cpp',
'frontend/Parser.cpp',
'gc/StoreBuffer.cpp',
'jsarray.cpp',
diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp
index a77e128a52..1f89e38482 100644
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -318,6 +318,7 @@ static bool enableUnboxedArrays = false;
static bool enableSharedMemory = SHARED_MEMORY_DEFAULT;
static bool enableWasmAlwaysBaseline = false;
static bool enableArrayProtoValues = true;
+static bool enableStreams = false;
static bool printTiming = false;
static const char* jsCacheDir = nullptr;
static const char* jsCacheAsmJSPath = nullptr;
@@ -7565,6 +7566,7 @@ SetContextOptions(JSContext* cx, const OptionParser& op)
enableUnboxedArrays = op.getBoolOption("unboxed-arrays");
enableWasmAlwaysBaseline = op.getBoolOption("wasm-always-baseline");
enableArrayProtoValues = !op.getBoolOption("no-array-proto-values");
+ enableStreams = op.getBoolOption("enable-streams");
JS::ContextOptionsRef(cx).setBaseline(enableBaseline)
.setIon(enableIon)
@@ -7573,7 +7575,8 @@ SetContextOptions(JSContext* cx, const OptionParser& op)
.setWasmAlwaysBaseline(enableWasmAlwaysBaseline)
.setNativeRegExp(enableNativeRegExp)
.setUnboxedArrays(enableUnboxedArrays)
- .setArrayProtoValues(enableArrayProtoValues);
+ .setArrayProtoValues(enableArrayProtoValues)
+ .setStreams(enableStreams);
if (op.getBoolOption("wasm-check-bce"))
jit::JitOptions.wasmAlwaysCheckBounds = true;
@@ -7845,7 +7848,8 @@ SetWorkerContextOptions(JSContext* cx)
.setWasmAlwaysBaseline(enableWasmAlwaysBaseline)
.setNativeRegExp(enableNativeRegExp)
.setUnboxedArrays(enableUnboxedArrays)
- .setArrayProtoValues(enableArrayProtoValues);
+ .setArrayProtoValues(enableArrayProtoValues)
+ .setStreams(enableStreams);
cx->setOffthreadIonCompilationEnabled(offthreadCompilation);
cx->profilingScripts = enableCodeCoverage || enableDisassemblyDumps;
@@ -8019,6 +8023,7 @@ main(int argc, char** argv, char** envp)
|| !op.addBoolOption('\0', "wasm-always-baseline", "Enable wasm baseline compiler when possible")
|| !op.addBoolOption('\0', "wasm-check-bce", "Always generate wasm bounds check, even redundant ones.")
|| !op.addBoolOption('\0', "no-array-proto-values", "Remove Array.prototype.values")
+ || !op.addBoolOption('\0', "enable-streams", "Enable WHATWG Streams")
#ifdef ENABLE_SHARED_ARRAY_BUFFER
|| !op.addStringOption('\0', "shared-memory", "on/off",
"SharedArrayBuffer and Atomics "
diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp
index db1d7c798f..6ace034adb 100644
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -1255,15 +1255,16 @@ ArrayBufferObject::finalize(FreeOp* fop, JSObject* obj)
}
/* static */ void
-ArrayBufferObject::copyData(Handle<ArrayBufferObject*> toBuffer,
- Handle<ArrayBufferObject*> fromBuffer,
- uint32_t fromIndex, uint32_t count)
+ArrayBufferObject::copyData(Handle<ArrayBufferObject*> toBuffer, uint32_t toIndex,
+ Handle<ArrayBufferObject*> fromBuffer, uint32_t fromIndex,
+ uint32_t count)
{
MOZ_ASSERT(toBuffer->byteLength() >= count);
+ MOZ_ASSERT(toBuffer->byteLength() >= toIndex + count);
MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex);
MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count);
- memcpy(toBuffer->dataPointer(), fromBuffer->dataPointer() + fromIndex, count);
+ memcpy(toBuffer->dataPointer() + toIndex, fromBuffer->dataPointer() + fromIndex, count);
}
/* static */ void
@@ -1890,6 +1891,17 @@ JS_GetArrayBufferViewByteLength(JSObject* obj)
: obj->as<TypedArrayObject>().byteLength();
}
+JS_FRIEND_API(uint32_t)
+JS_GetArrayBufferViewByteOffset(JSObject* obj)
+{
+ obj = CheckedUnwrap(obj);
+ if (!obj)
+ return 0;
+ return obj->is<DataViewObject>()
+ ? obj->as<DataViewObject>().byteOffset()
+ : obj->as<TypedArrayObject>().byteOffset();
+}
+
JS_FRIEND_API(JSObject*)
JS_GetObjectAsArrayBufferView(JSObject* obj, uint32_t* length, bool* isSharedMemory, uint8_t** data)
{
diff --git a/js/src/vm/ArrayBufferObject.h b/js/src/vm/ArrayBufferObject.h
index 4ff7962cfb..f4010c6c77 100644
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferObject.h
@@ -261,9 +261,9 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
template<typename T>
static bool createTypedArrayFromBuffer(JSContext* cx, unsigned argc, Value* vp);
- static void copyData(Handle<ArrayBufferObject*> toBuffer,
- Handle<ArrayBufferObject*> fromBuffer,
- uint32_t fromIndex, uint32_t count);
+ static void copyData(Handle<ArrayBufferObject*> toBuffer, uint32_t toIndex,
+ Handle<ArrayBufferObject*> fromBuffer, uint32_t fromIndex,
+ uint32_t count);
static void trace(JSTracer* trc, JSObject* obj);
static void objectMoved(JSObject* obj, const JSObject* old);
diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h
index f05a4db9be..ebfad89fa0 100644
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -18,6 +18,8 @@
macro(apply, apply, "apply") \
macro(args, args, "args") \
macro(arguments, arguments, "arguments") \
+ macro(AcquireReadableStreamBYOBReader, AcquireReadableStreamBYOBReader, "AcquireReadableStreamBYOBReader") \
+ macro(AcquireReadableStreamDefaultReader, AcquireReadableStreamDefaultReader, "AcquireReadableStreamDefaultReader") \
macro(ArrayBufferSpecies, ArrayBufferSpecies, "ArrayBufferSpecies") \
macro(ArrayIterator, ArrayIterator, "Array Iterator") \
macro(ArrayIteratorNext, ArrayIteratorNext, "ArrayIteratorNext") \
@@ -35,6 +37,7 @@
macro(AsyncGeneratorFunction, AsyncGeneratorFunction, "AsyncGeneratorFunction") \
macro(AsyncWrapped, AsyncWrapped, "AsyncWrapped") \
macro(async, async, "async") \
+ macro(autoAllocateChunkSize, autoAllocateChunkSize, "autoAllocateChunkSize") \
macro(await, await, "await") \
macro(bigint64, bigint64, "bigint64") \
macro(biguint64, biguint64, "biguint64") \
@@ -48,6 +51,7 @@
macro(buffer, buffer, "buffer") \
macro(builder, builder, "builder") \
macro(by, by, "by") \
+ macro(byob, byob, "byob") \
macro(byteAlignment, byteAlignment, "byteAlignment") \
macro(byteLength, byteLength, "byteLength") \
macro(byteOffset, byteOffset, "byteOffset") \
@@ -59,6 +63,7 @@
macro(callee, callee, "callee") \
macro(caller, caller, "caller") \
macro(callFunction, callFunction, "callFunction") \
+ macro(cancel, cancel, "cancel") \
macro(case, case_, "case") \
macro(caseFirst, caseFirst, "caseFirst") \
macro(catch, catch_, "catch") \
@@ -180,6 +185,7 @@
macro(has, has, "has") \
macro(hasOwn, hasOwn, "hasOwn") \
macro(hasOwnProperty, hasOwnProperty, "hasOwnProperty") \
+ macro(highWaterMark, highWaterMark, "highWaterMark") \
macro(hour, hour, "hour") \
macro(hourCycle, hourCycle, "hourCycle") \
macro(if, if_, "if") \
@@ -250,6 +256,7 @@
macro(minusSign, minusSign, "minusSign") \
macro(minute, minute, "minute") \
macro(missingArguments, missingArguments, "missingArguments") \
+ macro(mode, mode, "mode") \
macro(module, module, "module") \
macro(Module, Module, "Module") \
macro(ModuleInstantiate, ModuleInstantiate, "ModuleInstantiate") \
@@ -312,7 +319,52 @@
macro(prototype, prototype, "prototype") \
macro(proxy, proxy, "proxy") \
macro(public, public_, "public") \
+ macro(pull, pull, "pull") \
macro(raw, raw, "raw") \
+ macro(ReadableByteStreamControllerGetDesiredSize, \
+ ReadableByteStreamControllerGetDesiredSize, \
+ "ReadableByteStreamControllerGetDesiredSize") \
+ macro(ReadableByteStreamController_close, \
+ ReadableByteStreamController_close, \
+ "ReadableByteStreamController_close") \
+ macro(ReadableByteStreamController_enqueue, \
+ ReadableByteStreamController_enqueue, \
+ "ReadableByteStreamController_enqueue") \
+ macro(ReadableByteStreamController_error, \
+ ReadableByteStreamController_error, \
+ "ReadableByteStreamController_error") \
+ macro(ReadableStreamBYOBReader_cancel, \
+ ReadableStreamBYOBReader_cancel, \
+ "ReadableStreamBYOBReader_cancel") \
+ macro(ReadableStreamBYOBReader_read, \
+ ReadableStreamBYOBReader_read, \
+ "ReadableStreamBYOBReader_read") \
+ macro(ReadableStreamBYOBReader_releaseLock, \
+ ReadableStreamBYOBReader_releaseLock, \
+ "ReadableStreamBYOBReader_releaseLock") \
+ macro(ReadableStream_cancel, ReadableStream_cancel, "ReadableStream_cancel") \
+ macro(ReadableStreamDefaultControllerGetDesiredSize, \
+ ReadableStreamDefaultControllerGetDesiredSize, \
+ "ReadableStreamDefaultControllerGetDesiredSize") \
+ macro(ReadableStreamDefaultController_close, \
+ ReadableStreamDefaultController_close, \
+ "ReadableStreamDefaultController_close") \
+ macro(ReadableStreamDefaultController_enqueue, \
+ ReadableStreamDefaultController_enqueue, \
+ "ReadableStreamDefaultController_enqueue") \
+ macro(ReadableStreamDefaultController_error, \
+ ReadableStreamDefaultController_error, \
+ "ReadableStreamDefaultController_error") \
+ macro(ReadableStreamDefaultReader_cancel, \
+ ReadableStreamDefaultReader_cancel, \
+ "ReadableStreamDefaultReader_cancel") \
+ macro(ReadableStreamDefaultReader_read, \
+ ReadableStreamDefaultReader_read, \
+ "ReadableStreamDefaultReader_read") \
+ macro(ReadableStreamDefaultReader_releaseLock, \
+ ReadableStreamDefaultReader_releaseLock, \
+ "ReadableStreamDefaultReader_releaseLock") \
+ macro(ReadableStreamTee, ReadableStreamTee, "ReadableStreamTee") \
macro(reason, reason, "reason") \
macro(RegExpFlagsGetter, RegExpFlagsGetter, "RegExpFlagsGetter") \
macro(RegExpStringIterator, RegExpStringIterator, "RegExp String Iterator") \
@@ -347,6 +399,7 @@
macro(StarGeneratorNext, StarGeneratorNext, "StarGeneratorNext") \
macro(StarGeneratorReturn, StarGeneratorReturn, "StarGeneratorReturn") \
macro(StarGeneratorThrow, StarGeneratorThrow, "StarGeneratorThrow") \
+ macro(start, start, "start") \
macro(startTimestamp, startTimestamp, "startTimestamp") \
macro(state, state, "state") \
macro(static, static_, "static") \
diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp
index b7d3344b3e..1321be9e9b 100644
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -23,6 +23,7 @@
#include "builtin/Promise.h"
#include "builtin/RegExp.h"
#include "builtin/SelfHostingDefines.h"
+#include "builtin/Stream.h"
#include "builtin/SymbolObject.h"
#include "builtin/TypedObject.h"
#include "builtin/WeakMapObject.h"
@@ -98,6 +99,16 @@ GlobalObject::skipDeselectedConstructor(JSContext* cx, JSProtoKey key)
case JSProto_WebAssembly:
return !wasm::HasSupport(cx);
+ case JSProto_ReadableStream:
+ case JSProto_ReadableStreamDefaultReader:
+ case JSProto_ReadableStreamBYOBReader:
+ case JSProto_ReadableStreamDefaultController:
+ case JSProto_ReadableByteStreamController:
+ case JSProto_ReadableStreamBYOBRequest:
+ case JSProto_ByteLengthQueuingStrategy:
+ case JSProto_CountQueuingStrategy:
+ return !cx->options().streams();
+
#ifdef ENABLE_SHARED_ARRAY_BUFFER
case JSProto_Atomics:
case JSProto_SharedArrayBuffer:
diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp
index 053b7c44b0..c5deff940c 100644
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -156,6 +156,12 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
startAsyncTaskCallback(nullptr),
finishAsyncTaskCallback(nullptr),
promiseTasksToDestroy(mutexid::PromiseTaskPtrVector),
+ readableStreamDataRequestCallback(nullptr),
+ readableStreamWriteIntoReadRequestCallback(nullptr),
+ readableStreamCancelCallback(nullptr),
+ readableStreamClosedCallback(nullptr),
+ readableStreamErroredCallback(nullptr),
+ readableStreamFinalizeCallback(nullptr),
exclusiveAccessLock(mutexid::RuntimeExclusiveAccess),
#ifdef DEBUG
mainThreadHasExclusiveAccess(false),
diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h
index 962f0e1a1e..13523844c1 100644
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -655,6 +655,13 @@ struct JSRuntime : public JS::shadow::Runtime,
JS::FinishAsyncTaskCallback finishAsyncTaskCallback;
js::ExclusiveData<js::PromiseTaskPtrVector> promiseTasksToDestroy;
+ JS::RequestReadableStreamDataCallback readableStreamDataRequestCallback;
+ JS::WriteIntoReadRequestBufferCallback readableStreamWriteIntoReadRequestCallback;
+ JS::CancelReadableStreamCallback readableStreamCancelCallback;
+ JS::ReadableStreamClosedCallback readableStreamClosedCallback;
+ JS::ReadableStreamErroredCallback readableStreamErroredCallback;
+ JS::ReadableStreamFinalizeCallback readableStreamFinalizeCallback;
+
private:
/*
* Lock taken when using per-runtime or per-zone data that could otherwise
diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp
index e9c72d1bf1..e006846752 100644
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -36,6 +36,7 @@
#include "builtin/Promise.h"
#include "builtin/Reflect.h"
#include "builtin/SelfHostingDefines.h"
+#include "builtin/Stream.h"
#include "builtin/TypedObject.h"
#include "builtin/WeakSetObject.h"
#include "gc/Marking.h"
@@ -71,7 +72,6 @@ using JS::AutoCheckCannotGC;
using mozilla::IsInRange;
using mozilla::Maybe;
using mozilla::PodMove;
-using mozilla::Maybe;
static void
selfHosting_WarningReporter(JSContext* cx, JSErrorReport* report)
@@ -1098,9 +1098,9 @@ static bool
intrinsic_ArrayBufferCopyData(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
- MOZ_ASSERT(args.length() == 5);
+ MOZ_ASSERT(args.length() == 6);
- bool isWrapped = args[4].toBoolean();
+ bool isWrapped = args[5].toBoolean();
Rooted<T*> toBuffer(cx);
if (!isWrapped) {
toBuffer = &args[0].toObject().as<T>();
@@ -1114,11 +1114,12 @@ intrinsic_ArrayBufferCopyData(JSContext* cx, unsigned argc, Value* vp)
}
toBuffer = toBufferObj.as<T>();
}
- Rooted<T*> fromBuffer(cx, &args[1].toObject().as<T>());
- uint32_t fromIndex = uint32_t(args[2].toInt32());
- uint32_t count = uint32_t(args[3].toInt32());
+ uint32_t toIndex = uint32_t(args[1].toInt32());
+ Rooted<T*> fromBuffer(cx, &args[2].toObject().as<T>());
+ uint32_t fromIndex = uint32_t(args[3].toInt32());
+ uint32_t count = uint32_t(args[4].toInt32());
- T::copyData(toBuffer, fromBuffer, fromIndex, count);
+ T::copyData(toBuffer, toIndex, fromBuffer, fromIndex, count);
args.rval().setUndefined();
return true;
@@ -2426,14 +2427,14 @@ static const JSFunctionSpec intrinsic_functions[] = {
intrinsic_PossiblyWrappedArrayBufferByteLength<ArrayBufferObject>, 1,0,
IntrinsicPossiblyWrappedArrayBufferByteLength),
JS_FN("ArrayBufferCopyData",
- intrinsic_ArrayBufferCopyData<ArrayBufferObject>, 5,0),
+ intrinsic_ArrayBufferCopyData<ArrayBufferObject>, 6,0),
JS_FN("SharedArrayBufferByteLength",
intrinsic_ArrayBufferByteLength<SharedArrayBufferObject>, 1,0),
JS_FN("PossiblyWrappedSharedArrayBufferByteLength",
intrinsic_PossiblyWrappedArrayBufferByteLength<SharedArrayBufferObject>, 1,0),
JS_FN("SharedArrayBufferCopyData",
- intrinsic_ArrayBufferCopyData<SharedArrayBufferObject>, 5,0),
+ intrinsic_ArrayBufferCopyData<SharedArrayBufferObject>, 6,0),
JS_FN("IsUint8TypedArray", intrinsic_IsUint8TypedArray, 1,0),
JS_FN("IsInt8TypedArray", intrinsic_IsInt8TypedArray, 1,0),
@@ -2489,6 +2490,9 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("CallWeakSetMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<WeakSetObject>>, 2, 0),
+ JS_FN("IsReadableStreamBYOBRequest",
+ intrinsic_IsInstanceOfBuiltin<ReadableStreamBYOBRequest>, 1, 0),
+
// See builtin/TypedObject.h for descriptors of the typedobj functions.
JS_FN("NewOpaqueTypedObject", js::NewOpaqueTypedObject, 1, 0),
JS_FN("NewDerivedTypedObject", js::NewDerivedTypedObject, 3, 0),
diff --git a/js/src/vm/SharedArrayObject.cpp b/js/src/vm/SharedArrayObject.cpp
index 8e55001d13..44fe3b790d 100644
--- a/js/src/vm/SharedArrayObject.cpp
+++ b/js/src/vm/SharedArrayObject.cpp
@@ -331,15 +331,16 @@ SharedArrayBufferObject::addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSi
}
/* static */ void
-SharedArrayBufferObject::copyData(Handle<SharedArrayBufferObject*> toBuffer,
- Handle<SharedArrayBufferObject*> fromBuffer,
- uint32_t fromIndex, uint32_t count)
+SharedArrayBufferObject::copyData(Handle<SharedArrayBufferObject*> toBuffer, uint32_t toIndex,
+ Handle<SharedArrayBufferObject*> fromBuffer, uint32_t fromIndex,
+ uint32_t count)
{
MOZ_ASSERT(toBuffer->byteLength() >= count);
+ MOZ_ASSERT(toBuffer->byteLength() >= toIndex + count);
MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex);
MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count);
- jit::AtomicOperations::memcpySafeWhenRacy(toBuffer->dataPointerShared(),
+ jit::AtomicOperations::memcpySafeWhenRacy(toBuffer->dataPointerShared() + toIndex,
fromBuffer->dataPointerShared() + fromIndex,
count);
}
diff --git a/js/src/vm/SharedArrayObject.h b/js/src/vm/SharedArrayObject.h
index 05db688184..19048336cb 100644
--- a/js/src/vm/SharedArrayObject.h
+++ b/js/src/vm/SharedArrayObject.h
@@ -146,9 +146,9 @@ class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared
static void addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSizeOf mallocSizeOf,
JS::ClassInfo* info);
- static void copyData(Handle<SharedArrayBufferObject*> toBuffer,
- Handle<SharedArrayBufferObject*> fromBuffer,
- uint32_t fromIndex, uint32_t count);
+ static void copyData(Handle<SharedArrayBufferObject*> toBuffer, uint32_t toIndex,
+ Handle<SharedArrayBufferObject*> fromBuffer, uint32_t fromIndex,
+ uint32_t count);
SharedArrayRawBuffer* rawBufferObject() const;
diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp
index 8ecc14b650..cf7785e34c 100644
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -1438,6 +1438,8 @@ ReloadPrefsCallback(const char* pref, void* data)
bool werror = Preferences::GetBool(JS_OPTIONS_DOT_STR "werror");
bool extraWarnings = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict");
+
+ bool streams = Preferences::GetBool(JS_OPTIONS_DOT_STR "streams");
bool unboxedObjects = Preferences::GetBool(JS_OPTIONS_DOT_STR "unboxed_objects");
@@ -1463,7 +1465,8 @@ ReloadPrefsCallback(const char* pref, void* data)
.setDumpStackOnDebuggeeWouldRun(dumpStackOnDebuggeeWouldRun)
.setWerror(werror)
.setExtraWarnings(extraWarnings)
- .setArrayProtoValues(arrayProtoValues);
+ .setArrayProtoValues(arrayProtoValues)
+ .setStreams(streams);
JS_SetParallelParsingEnabled(cx, parallelParsing);
JS_SetOffthreadIonCompilationEnabled(cx, offthreadIonCompilation);