summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorBrian Smith <brian@dbsoft.org>2023-09-27 17:33:34 -0500
committerBrian Smith <brian@dbsoft.org>2023-09-27 17:33:34 -0500
commit0099fc16278bb9859a6ee7c7b10b78509b4d183a (patch)
treea7d6d71f75cec088f8498ddfa46a6f1cbe0e6e16 /js
parent8ce17f02ee4d9b40a576911c4bb82fa4802fb167 (diff)
downloaduxp-0099fc16278bb9859a6ee7c7b10b78509b4d183a.tar.gz
Issue #1442 - Part 4: Add JSAPI functions for working with ReadableStream.
https://bugzilla.mozilla.org/show_bug.cgi?id=1272697
Diffstat (limited to 'js')
-rw-r--r--js/public/Stream.h510
-rw-r--r--js/src/builtin/Stream.cpp916
-rw-r--r--js/src/builtin/Stream.h68
-rw-r--r--js/src/js.msg11
-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.h1
-rw-r--r--js/src/moz.build1
-rw-r--r--js/src/vm/CommonPropertyNames.h46
-rw-r--r--js/src/vm/Runtime.cpp6
-rw-r--r--js/src/vm/Runtime.h7
14 files changed, 2411 insertions, 207 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
index 8eae95bcf7..aa8ce3091e 100644
--- a/js/src/builtin/Stream.cpp
+++ b/js/src/builtin/Stream.cpp
@@ -5,6 +5,8 @@
#include "builtin/Stream.h"
+#include "js/Stream.h"
+
#include "jscntxt.h"
#include "gc/Heap.h"
@@ -73,9 +75,14 @@ enum ControllerFlags {
ControllerFlag_CloseRequested = 1 << 3,
ControllerFlag_TeeBranch = 1 << 4,
ControllerFlag_TeeBranch1 = 1 << 5,
- ControllerFlag_TeeBranch2 = 1 << 6
+ 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,
@@ -84,7 +91,7 @@ enum BYOBRequestSlots {
template<class T>
MOZ_ALWAYS_INLINE bool
-Is(HandleValue v)
+Is(const HandleValue v)
{
return v.isObject() && v.toObject().is<T>();
}
@@ -98,13 +105,6 @@ IsReadableStreamController(const JSObject* controller)
}
#endif // DEBUG
-static bool
-IsReadableStreamReader(const JSObject* reader)
-{
- return reader->is<ReadableStreamDefaultReader>() ||
- reader->is<ReadableStreamBYOBReader>();
-}
-
static inline uint32_t
ControllerFlags(const NativeObject* controller)
{
@@ -142,30 +142,43 @@ SetStreamState(ReadableStream* stream, uint32_t state)
stream->setFixedSlot(StreamSlot_State, Int32Value(state));
}
-inline bool
+bool
ReadableStream::readable() const
{
return StreamState(this) & Readable;
}
-inline bool
+bool
ReadableStream::closed() const
{
return StreamState(this) & Closed;
}
-inline bool
+bool
ReadableStream::errored() const
{
return StreamState(this) & Errored;
}
-inline bool
+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)
{
@@ -174,28 +187,51 @@ StreamFromController(const NativeObject* controller)
}
inline static MOZ_MUST_USE NativeObject*
-ControllerFromStream(ReadableStream* stream)
+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(IsReadableStreamReader(reader));
+ MOZ_ASSERT(ReaderHasStream(reader));
return &reader->getFixedSlot(ReaderSlot_Stream).toObject().as<ReadableStream>();
}
inline static MOZ_MUST_USE NativeObject*
-ReaderFromStream(NativeObject* stream)
+ReaderFromStream(const NativeObject* stream)
{
Value readerVal = stream->getFixedSlot(StreamSlot_Reader);
- MOZ_ASSERT(IsReadableStreamReader(&readerVal.toObject()));
+ 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)
{
@@ -573,7 +609,7 @@ const Class TeeState::class_ = {
JSCLASS_HAS_RESERVED_SLOTS(SlotCount)
};
-#define CLASS_SPEC(cls, nCtorArgs, nSlots, specFlags) \
+#define CLASS_SPEC(cls, nCtorArgs, nSlots, specFlags, classFlags, classOps) \
const ClassSpec cls::classSpec_ = { \
GenericCreateConstructor<cls::constructor, nCtorArgs, gc::AllocKind::FUNCTION>, \
GenericCreatePrototype, \
@@ -588,8 +624,9 @@ const ClassSpec cls::classSpec_ = { \
const Class cls::class_ = { \
#cls, \
JSCLASS_HAS_RESERVED_SLOTS(nSlots) | \
- JSCLASS_HAS_CACHED_PROTO(JSProto_##cls), \
- JS_NULL_CLASS_OPS, \
+ JSCLASS_HAS_CACHED_PROTO(JSProto_##cls) | \
+ classFlags, \
+ classOps, \
&cls::classSpec_ \
}; \
\
@@ -602,13 +639,13 @@ const Class cls::protoClass_ = { \
// Streams spec, 3.2.3., steps 1-4.
ReadableStream*
-ReadableStream::createStream(JSContext* cx)
+ReadableStream::createStream(JSContext* cx, HandleObject proto /* = nullptr */)
{
- Rooted<ReadableStream*> stream(cx, NewBuiltinClassInstance<ReadableStream>(cx));
+ Rooted<ReadableStream*> stream(cx, NewObjectWithClassProto<ReadableStream>(cx, proto));
if (!stream)
return nullptr;
- // Step 1 (reordered): Set this.[[state]] to "readable".
+ // 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).
@@ -625,17 +662,18 @@ CreateReadableStreamDefaultController(JSContext* cx, Handle<ReadableStream*> str
// Streams spec, 3.2.3., steps 1-4, 8.
ReadableStream*
ReadableStream::createDefaultStream(JSContext* cx, HandleValue underlyingSource,
- HandleValue size, HandleValue highWaterMark)
+ HandleValue size, HandleValue highWaterMark,
+ HandleObject proto /* = nullptr */)
{
-
+ // Steps 1-4.
Rooted<ReadableStream*> stream(cx, createStream(cx));
if (!stream)
return nullptr;
- // Step b: Set this.[[readableStreamController]] to
- // ? Construct(ReadableStreamDefaultController,
- // « this, underlyingSource, size,
- // highWaterMark »).
+ // Step 8.b: Set this.[[readableStreamController]] to
+ // ? Construct(ReadableStreamDefaultController,
+ // « this, underlyingSource, size,
+ // highWaterMark »).
RootedObject controller(cx, CreateReadableStreamDefaultController(cx, stream,
underlyingSource,
size,
@@ -656,16 +694,16 @@ CreateReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream
// Streams spec, 3.2.3., steps 1-4, 7.
ReadableStream*
ReadableStream::createByteStream(JSContext* cx, HandleValue underlyingSource,
- HandleValue highWaterMark)
+ HandleValue highWaterMark, HandleObject proto /* = nullptr */)
{
-
- Rooted<ReadableStream*> stream(cx, createStream(cx));
+ // Steps 1-4.
+ Rooted<ReadableStream*> stream(cx, createStream(cx, proto));
if (!stream)
return nullptr;
- // Step b: Set this.[[readableStreamController]] to
- // ? Construct(ReadableByteStreamController, « this, underlyingSource,
- // highWaterMark »).
+ // Step 7.b: Set this.[[readableStreamController]] to
+ // ? Construct(ReadableByteStreamController,
+ // « this, underlyingSource, highWaterMark »).
RootedObject controller(cx, CreateReadableByteStreamController(cx, stream,
underlyingSource,
highWaterMark));
@@ -677,6 +715,29 @@ ReadableStream::createByteStream(JSContext* cx, HandleValue underlyingSource,
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)
@@ -722,8 +783,8 @@ ReadableStream::constructor(JSContext* cx, unsigned argc, Value* vp)
if (!CompareStrings(cx, type, cx->names().bytes, &notByteStream))
return false;
- // Step 7 & 8.a (reordered): If highWaterMark is undefined, let
- // highWaterMark be 1 (or 0 for byte streams).
+ // 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);
@@ -731,9 +792,15 @@ ReadableStream::constructor(JSContext* cx, unsigned argc, Value* vp)
// 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.
@@ -748,9 +815,6 @@ ReadableStream::constructor(JSContext* cx, unsigned argc, Value* vp)
return true;
}
-static MOZ_ALWAYS_INLINE bool
-IsReadableStreamLocked(ReadableStream* stream);
-
// Streams spec, 3.2.4.1. get locked
static MOZ_MUST_USE bool
ReadableStream_locked_impl(JSContext* cx, const CallArgs& args)
@@ -758,7 +822,7 @@ 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(IsReadableStreamLocked(stream));
+ args.rval().setBoolean(stream->locked());
return true;
}
@@ -770,9 +834,6 @@ ReadableStream_locked(JSContext* cx, unsigned argc, Value* vp)
return CallNonGenericMethod<Is<ReadableStream>, ReadableStream_locked_impl>(cx, args);
}
-static MOZ_MUST_USE JSObject*
-ReadableStreamCancel(JSContext* cx, Handle<ReadableStream*> stream, HandleValue reason);
-
// Streams spec, 3.2.4.2. cancel ( reason )
static MOZ_MUST_USE bool
ReadableStream_cancel(JSContext* cx, unsigned argc, Value* vp)
@@ -790,14 +851,14 @@ ReadableStream_cancel(JSContext* cx, unsigned argc, Value* vp)
// Step 2: If ! IsReadableStreamLocked(this) is true, return a promise
// rejected with a TypeError exception.
- if (IsReadableStreamLocked(stream)) {
+ 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, ReadableStreamCancel(cx, stream, args.get(0)));
+ RootedObject cancelPromise(cx, ReadableStream::cancel(cx, stream, args.get(0)));
if (!cancelPromise)
return false;
args.rval().setObject(*cancelPromise);
@@ -936,7 +997,7 @@ static const JSPropertySpec ReadableStream_properties[] = {
JS_PS_END
};
-CLASS_SPEC(ReadableStream, 0, StreamSlotCount, 0);
+CLASS_SPEC(ReadableStream, 0, StreamSlotCount, 0, 0, JS_NULL_CLASS_OPS);
// Streams spec, 3.3.1. AcquireReadableStreamBYOBReader ( stream )
// Always inlined.
@@ -948,22 +1009,26 @@ CLASS_SPEC(ReadableStream, 0, StreamSlotCount, 0);
// Using is<T> instead.
// Streams spec, 3.3.4. IsReadableStreamDisturbed ( stream )
-static MOZ_ALWAYS_INLINE bool
-IsReadableStreamDisturbed(ReadableStream* stream)
-{
- // Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
- // Step 2: Return stream.[[disturbed]].
- return stream->disturbed();
-}
+// Using stream->disturbed() instead.
// Streams spec, 3.3.5. IsReadableStreamLocked ( stream )
-static MOZ_ALWAYS_INLINE bool
-IsReadableStreamLocked(ReadableStream* 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.
- return !stream->getFixedSlot(StreamSlot_Reader).isUndefined();
+ // 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
@@ -1056,9 +1121,6 @@ TeeReaderReadHandler(JSContext* cx, unsigned argc, Value* vp)
}
static MOZ_MUST_USE JSObject*
-ReadableStreamDefaultReaderRead(JSContext* cx, HandleNativeObject reader);
-
-static MOZ_MUST_USE JSObject*
ReadableStreamTee_Pull(JSContext* cx, Handle<TeeState*> teeState,
Handle<ReadableStream*> branchStream)
{
@@ -1071,7 +1133,7 @@ ReadableStreamTee_Pull(JSContext* cx, Handle<TeeState*> teeState,
// handler which takes the argument result and performs the
// following steps:
Rooted<ReadableStreamDefaultReader*> reader(cx, teeState->reader());
- RootedObject readPromise(cx, ReadableStreamDefaultReaderRead(cx, reader));
+ RootedObject readPromise(cx, ReadableStreamDefaultReader::read(cx, reader));
if (!readPromise)
return nullptr;
@@ -1119,7 +1181,7 @@ ReadableStreamTee_Cancel(JSContext* cx, Handle<TeeState*> teeState,
Rooted<PromiseObject*> promise(cx, teeState->promise());
// Step b: Let cancelResult be ! ReadableStreamCancel(stream, compositeReason).
- RootedObject cancelResult(cx, ReadableStreamCancel(cx, stream, compositeReasonVal));
+ RootedObject cancelResult(cx, ReadableStream::cancel(cx, stream, compositeReasonVal));
if (!cancelResult) {
if (!RejectWithPendingError(cx, promise))
return nullptr;
@@ -1314,9 +1376,6 @@ ReadableStreamAddReadRequest(JSContext* cx, Handle<ReadableStream*> stream)
return promise;
}
-static MOZ_MUST_USE bool
-ReadableStreamClose(JSContext* cx, Handle<ReadableStream*> stream);
-
static MOZ_MUST_USE JSObject*
ReadableStreamControllerCancelSteps(JSContext* cx,
HandleNativeObject controller, HandleValue reason);
@@ -1330,9 +1389,12 @@ ReturnUndefined(JSContext* cx, unsigned argc, Value* vp)
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*
-ReadableStreamCancel(JSContext* cx, Handle<ReadableStream*> stream, HandleValue 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;
@@ -1351,7 +1413,7 @@ ReadableStreamCancel(JSContext* cx, Handle<ReadableStream*> stream, HandleValue
}
// Step 4: Perform ! ReadableStreamClose(stream).
- if (!ReadableStreamClose(cx, stream))
+ if (!ReadableStreamCloseInternal(cx, stream))
return nullptr;
// Step 5: Let sourceCancelPromise be
@@ -1365,16 +1427,15 @@ ReadableStreamCancel(JSContext* cx, Handle<ReadableStream*> stream, HandleValue
// 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));
+ 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 )
-static MOZ_MUST_USE bool
-ReadableStreamClose(JSContext* cx, Handle<ReadableStream*> stream)
+MOZ_MUST_USE bool
+ReadableStreamCloseInternal(JSContext* cx, Handle<ReadableStream*> stream)
{
// Step 1: Assert: stream.[[state]] is "readable".
MOZ_ASSERT(stream->readable());
@@ -1422,12 +1483,23 @@ ReadableStreamClose(JSContext* cx, Handle<ReadableStream*> stream)
// Step 6: Resolve reader.[[closedPromise]] with undefined.
// Step 7: Return (implicit).
RootedObject closedPromise(cx, &reader->getFixedSlot(ReaderSlot_ClosedPromise).toObject());
- return ResolvePromise(cx, closedPromise, UndefinedHandleValue);
+ 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 )
-static MOZ_MUST_USE bool
-ReadableStreamError(JSContext* cx, Handle<ReadableStream*> stream, HandleValue e)
+MOZ_MUST_USE bool
+ReadableStreamErrorInternal(JSContext* cx, Handle<ReadableStream*> stream, HandleValue e)
{
// Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
@@ -1471,7 +1543,19 @@ ReadableStreamError(JSContext* cx, Handle<ReadableStream*> stream, HandleValue e
// Step 9: Reject reader.[[closedPromise]] with e.
val = reader->getFixedSlot(ReaderSlot_ClosedPromise);
Rooted<PromiseObject*> closedPromise(cx, &val.toObject().as<PromiseObject>());
- return PromiseObject::reject(cx, closedPromise, e);
+ 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 )
@@ -1509,16 +1593,13 @@ ReadableStreamFulfillReadOrReadIntoRequest(JSContext* cx, Handle<ReadableStream*
// Streams spec, 3.4.9. ReadableStreamGetNumReadRequests ( stream )
// (Identical implementation.)
static uint32_t
-ReadableStreamGetNumReadRequests(NativeObject* stream)
+ReadableStreamGetNumReadRequests(ReadableStream* stream)
{
- MOZ_ASSERT(stream->is<ReadableStream>());
-
// Step 1: Return the number of elements in
// stream.[[reader]].[[readRequests]].
- Value readerVal = stream->getFixedSlot(StreamSlot_Reader);
- NativeObject* reader = &readerVal.toObject().as<NativeObject>();
- MOZ_ASSERT(reader->is<ReadableStreamDefaultReader>() ||
- reader->is<ReadableStreamBYOBReader>());
+ if (!HasReader(stream))
+ return 0;
+ NativeObject* reader = ReaderFromStream(stream);
Value readRequests = reader->getFixedSlot(ReaderSlot_Requests);
return readRequests.toObject().as<NativeObject>().getDenseInitializedLength();
}
@@ -1564,7 +1645,7 @@ CreateReadableStreamDefaultReader(JSContext* cx, Handle<ReadableStream*> stream)
// Step 2: If ! IsReadableStreamLocked(stream) is true, throw a TypeError
// exception.
- if (IsReadableStreamLocked(stream)) {
+ if (stream->locked()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAM_LOCKED);
return nullptr;
@@ -1641,7 +1722,7 @@ ReadableStreamDefaultReader_cancel(JSContext* cx, unsigned argc, Value* vp)
// Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
// rejected with a TypeError exception.
RootedNativeObject reader(cx, &args.thisv().toObject().as<NativeObject>());
- if (reader->getFixedSlot(ReaderSlot_Stream).isUndefined()) {
+ if (!ReaderHasStream(reader)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAMREADER_NOT_OWNED, "cancel");
return ReturnPromiseRejectedWithPendingError(cx, args);
@@ -1668,15 +1749,16 @@ ReadableStreamDefaultReader_read(JSContext* cx, unsigned argc, Value* vp)
// Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
// rejected with a TypeError exception.
- RootedNativeObject reader(cx, &args.thisv().toObject().as<NativeObject>());
- if (reader->getFixedSlot(ReaderSlot_Stream).isUndefined()) {
+ 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 = ReadableStreamDefaultReaderRead(cx, reader);
+ JSObject* readPromise = ReadableStreamDefaultReader::read(cx, reader);
if (!readPromise)
return false;
args.rval().setObject(*readPromise);
@@ -1694,7 +1776,7 @@ ReadableStreamDefaultReader_releaseLock_impl(JSContext* cx, const CallArgs& args
reader = &args.thisv().toObject().as<ReadableStreamDefaultReader>();
// Step 2: If this.[[ownerReadableStream]] is undefined, return.
- if (reader->getFixedSlot(ReaderSlot_Stream).isUndefined()) {
+ if (!ReaderHasStream(reader)) {
args.rval().setUndefined();
return true;
}
@@ -1738,7 +1820,8 @@ static const JSPropertySpec ReadableStreamDefaultReader_properties[] = {
JS_PS_END
};
-CLASS_SPEC(ReadableStreamDefaultReader, 1, ReaderSlotCount, ClassSpec::DontDefineConstructor);
+CLASS_SPEC(ReadableStreamDefaultReader, 1, ReaderSlotCount, ClassSpec::DontDefineConstructor, 0,
+ JS_NULL_CLASS_OPS);
// Streams spec, 3.6.3 new ReadableStreamBYOBReader ( stream )
@@ -1750,13 +1833,14 @@ CreateReadableStreamBYOBReader(JSContext* cx, Handle<ReadableStream*> stream)
// is false, throw a TypeError exception.
if (!ControllerFromStream(stream)->is<ReadableByteStreamController>()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
- JSMSG_READABLESTREAM_NOT_BYTE_STREAM_CONTROLLER);
+ JSMSG_READABLESTREAM_NOT_BYTE_STREAM_CONTROLLER,
+ "ReadableStream.getReader('byob')");
return nullptr;
}
// Step 3: If ! IsReadableStreamLocked(stream) is true, throw a TypeError
// exception.
- if (IsReadableStreamLocked(stream)) {
+ if (stream->locked()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_READABLESTREAM_LOCKED);
return nullptr;
}
@@ -1832,7 +1916,7 @@ ReadableStreamBYOBReader_cancel(JSContext* cx, unsigned argc, Value* vp)
// Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
// rejected with a TypeError exception.
RootedNativeObject reader(cx, &args.thisv().toObject().as<NativeObject>());
- if (reader->getFixedSlot(ReaderSlot_Stream).isUndefined()) {
+ if (!ReaderHasStream(reader)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAMREADER_NOT_OWNED, "cancel");
return ReturnPromiseRejectedWithPendingError(cx, args);
@@ -1846,10 +1930,6 @@ ReadableStreamBYOBReader_cancel(JSContext* cx, unsigned argc, Value* vp)
return true;
}
-static MOZ_MUST_USE JSObject*
-ReadableStreamBYOBReaderRead(JSContext* cx, HandleNativeObject reader,
- Handle<TypedArrayObject*> view);
-
// Streams spec, 3.6.4.3 read ( )
static MOZ_MUST_USE bool
ReadableStreamBYOBReader_read(JSContext* cx, unsigned argc, Value* vp)
@@ -1864,8 +1944,9 @@ ReadableStreamBYOBReader_read(JSContext* cx, unsigned argc, Value* vp)
// Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
// rejected with a TypeError exception.
- RootedNativeObject reader(cx, &args.thisv().toObject().as<NativeObject>());
- if (reader->getFixedSlot(ReaderSlot_Stream).isUndefined()) {
+ 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);
@@ -1880,20 +1961,20 @@ ReadableStreamBYOBReader_read(JSContext* cx, unsigned argc, Value* vp)
return ReturnPromiseRejectedWithPendingError(cx, args);
}
- Rooted<TypedArrayObject*> view(cx, &viewVal.toObject().as<TypedArrayObject>());
+ 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 (view->length() == 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 = ReadableStreamBYOBReaderRead(cx, reader, view);
+ JSObject* readPromise = ReadableStreamBYOBReader::read(cx, reader, view);
if (!readPromise)
return false;
args.rval().setObject(*readPromise);
@@ -1911,7 +1992,7 @@ ReadableStreamBYOBReader_releaseLock_impl(JSContext* cx, const CallArgs& args)
reader = &args.thisv().toObject().as<ReadableStreamBYOBReader>();
// Step 2: If this.[[ownerReadableStream]] is undefined, return.
- if (reader->getFixedSlot(ReaderSlot_Stream).isUndefined()) {
+ if (!ReaderHasStream(reader)) {
args.rval().setUndefined();
return true;
}
@@ -1954,7 +2035,7 @@ static const JSFunctionSpec ReadableStreamBYOBReader_methods[] = {
JS_FS_END
};
-CLASS_SPEC(ReadableStreamBYOBReader, 1, 3, ClassSpec::DontDefineConstructor);
+CLASS_SPEC(ReadableStreamBYOBReader, 1, 3, ClassSpec::DontDefineConstructor, 0, JS_NULL_CLASS_OPS);
inline static MOZ_MUST_USE bool
ReadableStreamControllerCallPullIfNeeded(JSContext* cx, HandleNativeObject controller);
@@ -1975,7 +2056,7 @@ ReadableStreamReaderGenericCancel(JSContext* cx, HandleNativeObject reader, Hand
// Step 2: Assert: stream is not undefined (implicit).
// Step 3: Return ! ReadableStreamCancel(stream, reason).
- return ReadableStreamCancel(cx, stream, reason);
+ return &ReadableStreamCancel(cx, stream, reason)->as<PromiseObject>();
}
// Streams spec, 3.7.4. ReadableStreamReaderGenericInitialize ( reader, stream )
@@ -2064,12 +2145,12 @@ ReadableStreamReaderGenericRelease(JSContext* cx, HandleNativeObject reader)
static MOZ_MUST_USE JSObject*
ReadableByteStreamControllerPullInto(JSContext* cx,
Handle<ReadableByteStreamController*> controller,
- HandleNativeObject view);
+ Handle<ArrayBufferViewObject*> view);
// Streams spec, 3.7.6. ReadableStreamBYOBReaderRead ( reader, view )
-static MOZ_MUST_USE JSObject*
-ReadableStreamBYOBReaderRead(JSContext* cx, HandleNativeObject reader,
- Handle<TypedArrayObject*> view)
+/* static */ MOZ_MUST_USE JSObject*
+ReadableStreamBYOBReader::read(JSContext* cx, Handle<ReadableStreamBYOBReader*> reader,
+ Handle<ArrayBufferViewObject*> view)
{
MOZ_ASSERT(reader->is<ReadableStreamBYOBReader>());
@@ -2097,11 +2178,9 @@ static MOZ_MUST_USE JSObject*
ReadableStreamControllerPullSteps(JSContext* cx, HandleNativeObject controller);
// Streams spec, 3.7.7. ReadableStreamDefaultReaderRead ( reader )
-static MOZ_MUST_USE JSObject*
-ReadableStreamDefaultReaderRead(JSContext* cx, HandleNativeObject reader)
+MOZ_MUST_USE JSObject*
+ReadableStreamDefaultReader::read(JSContext* cx, Handle<ReadableStreamDefaultReader*> reader)
{
- MOZ_ASSERT(reader->is<ReadableStreamDefaultReader>());
-
// Step 1: Let stream be reader.[[ownerReadableStream]].
// Step 2: Assert: stream is not undefined.
Rooted<ReadableStream*> stream(cx, StreamFromReader(reader));
@@ -2294,12 +2373,13 @@ ReadableStreamDefaultController::constructor(JSContext* cx, unsigned argc, Value
// Step 2: If stream.[[readableStreamController]] is not undefined, throw a
// TypeError exception.
- if (!stream->getFixedSlot(StreamSlot_Controller).isUndefined()) {
+ 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)
@@ -2357,13 +2437,10 @@ static MOZ_MUST_USE bool
ReadableStreamDefaultControllerClose(JSContext* cx,
Handle<ReadableStreamDefaultController*> controller);
-// Streams spec, 3.8.4.2 close()
+// Unified implementation of steps 2-3 of 3.8.4.2 and 3.10.4.3.
static MOZ_MUST_USE bool
-ReadableStreamDefaultController_close_impl(JSContext* cx, const CallArgs& args)
+VerifyControllerStateForClosing(JSContext* cx, HandleNativeObject controller)
{
- 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,
@@ -2380,6 +2457,20 @@ ReadableStreamDefaultController_close_impl(JSContext* cx, const CallArgs& args)
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;
@@ -2452,12 +2543,10 @@ ReadableStreamDefaultController_error_impl(JSContext* cx, const CallArgs& args)
controller = &args.thisv().toObject().as<ReadableStreamDefaultController>();
// Step 2: Let stream be this.[[controlledReadableStream]].
- ReadableStream* stream = StreamFromController(controller);
-
// Step 3: If stream.[[state]] is not "readable", throw a TypeError exception.
- if (!stream->readable()) {
+ if (!StreamFromController(controller)->readable()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
- JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "close");
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "error");
return false;
}
@@ -2491,7 +2580,8 @@ static const JSFunctionSpec ReadableStreamDefaultController_methods[] = {
JS_FS_END
};
-CLASS_SPEC(ReadableStreamDefaultController, 4, 7, ClassSpec::DontDefineConstructor);
+CLASS_SPEC(ReadableStreamDefaultController, 4, 7, ClassSpec::DontDefineConstructor, 0,
+ JS_NULL_CLASS_OPS);
/**
* Unified implementation of ReadableStream controllers' [[CancelSteps]] internal
@@ -2538,6 +2628,15 @@ ReadableStreamControllerCancelSteps(JSContext* cx, HandleNativeObject controller
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);
}
@@ -2569,8 +2668,8 @@ ReadableStreamDefaultControllerPullSteps(JSContext* cx, HandleNativeObject contr
// perform ! ReadableStreamClose(stream).
bool closeRequested = ControllerFlags(controller) & ControllerFlag_CloseRequested;
if (closeRequested && queue->getDenseInitializedLength() == 0) {
- if (!ReadableStreamClose(cx, stream))
- return nullptr;
+ if (!ReadableStreamCloseInternal(cx, stream))
+ return nullptr;
}
// Step c: Otherwise, perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this).
@@ -2647,6 +2746,9 @@ ControllerPullFailedHandler(JSContext* cx, unsigned argc, Value* vp)
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 )
@@ -2687,6 +2789,13 @@ ReadableStreamControllerCallPullIfNeeded(JSContext* cx, HandleNativeObject contr
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);
}
@@ -2734,7 +2843,7 @@ ReadableStreamControllerShouldCallPull(NativeObject* controller)
// 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 (IsReadableStreamLocked(stream) && ReadableStreamGetNumReadRequests(stream) > 0)
+ if (stream->locked() && ReadableStreamGetNumReadRequests(stream) > 0)
return true;
// Step 6: Let desiredSize be ReadableStreamDefaultControllerGetDesiredSize(controller).
@@ -2767,16 +2876,12 @@ ReadableStreamDefaultControllerClose(JSContext* cx,
RootedNativeObject queue(cx);
queue = &controller->getFixedSlot(QueueContainerSlot_Queue).toObject().as<NativeObject>();
if (queue->getDenseInitializedLength() == 0)
- return ReadableStreamClose(cx, stream);
+ return ReadableStreamCloseInternal(cx, stream);
return true;
}
static MOZ_MUST_USE bool
-ReadableStreamFulfillReadOrReadIntoRequest(JSContext* cx, Handle<ReadableStream*> stream,
- HandleValue chunk, bool done);
-
-static MOZ_MUST_USE bool
EnqueueValueWithSize(JSContext* cx, HandleNativeObject container, HandleValue value,
HandleValue sizeVal);
@@ -2798,7 +2903,7 @@ ReadableStreamDefaultControllerEnqueue(JSContext* cx,
// Step 4: If ! IsReadableStreamLocked(stream) is true and
// ! ReadableStreamGetNumReadRequests(stream) > 0, perform
// ! ReadableStreamFulfillReadRequest(stream, chunk, false).
- if (IsReadableStreamLocked(stream) && ReadableStreamGetNumReadRequests(stream) > 0) {
+ if (stream->locked() && ReadableStreamGetNumReadRequests(stream) > 0) {
if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, chunk, false))
return false;
} else {
@@ -2848,9 +2953,6 @@ ReadableStreamDefaultControllerEnqueue(JSContext* cx,
}
static MOZ_MUST_USE bool
-ReadableStreamError(JSContext* cx, Handle<ReadableStream*> stream, HandleValue e);
-
-static MOZ_MUST_USE bool
ReadableByteStreamControllerClearPendingPullIntos(JSContext* cx, HandleNativeObject controller);
// Streams spec, 3.9.6. ReadableStreamDefaultControllerError ( controller, e )
@@ -2881,7 +2983,7 @@ ReadableStreamControllerError(JSContext* cx, HandleNativeObject controller, Hand
return false;
// Step 4 (or 5): Perform ! ReadableStreamError(stream, e).
- return ReadableStreamError(cx, stream, e);
+ return ReadableStreamErrorInternal(cx, stream, e);
}
// Streams spec, 3.9.7. ReadableStreamDefaultControllerErrorIfNeeded ( controller, e ) nothrow
@@ -3012,6 +3114,11 @@ CreateReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream
return controller;
}
+bool
+ReadableByteStreamController::hasExternalSource() {
+ return ControllerFlags(this) & ControllerFlag_ExternalSource;
+}
+
// Streams spec, 3.10.3.
// new ReadableByteStreamController ( stream, underlyingByteSource,
// highWaterMark )
@@ -3035,7 +3142,7 @@ ReadableByteStreamController::constructor(JSContext* cx, unsigned argc, Value* v
// Step 2: If stream.[[readableStreamController]] is not undefined, throw a
// TypeError exception.
- if (!stream->getFixedSlot(StreamSlot_Controller).isUndefined()) {
+ if (HasController(stream)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAM_CONTROLLER_SET);
return false;
@@ -3050,6 +3157,71 @@ ReadableByteStreamController::constructor(JSContext* cx, unsigned argc, Value* v
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);
@@ -3132,20 +3304,9 @@ ReadableByteStreamController_close_impl(JSContext* cx, const CallArgs& args)
Rooted<ReadableByteStreamController*> controller(cx);
controller = &args.thisv().toObject().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, "close");
+ // Steps 2-3.
+ if (!VerifyControllerStateForClosing(cx, controller))
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, "close");
- return false;
- }
// Step 4: Perform ? ReadableByteStreamControllerClose(this).
if (!ReadableByteStreamControllerClose(cx, controller))
@@ -3197,7 +3358,8 @@ ReadableByteStreamController_enqueue_impl(JSContext* cx, const CallArgs& args)
// throw a TypeError exception.
if (!chunkVal.isObject() || !JS_IsArrayBufferViewObject(&chunkVal.toObject())) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
- JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK);
+ JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK,
+ "ReadableByteStreamController#enqueue");
return false;
}
RootedObject chunk(cx, &chunkVal.toObject());
@@ -3265,7 +3427,41 @@ static const JSFunctionSpec ReadableByteStreamController_methods[] = {
JS_FS_END
};
-CLASS_SPEC(ReadableByteStreamController, 3, 9, ClassSpec::DontDefineConstructor);
+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.
@@ -3290,30 +3486,59 @@ ReadableByteStreamControllerPullSteps(JSContext* cx, HandleNativeObject controll
// Step 3.a: MOZ_ASSERT: ! ReadableStreamGetNumReadRequests(_stream_) is 0.
MOZ_ASSERT(ReadableStreamGetNumReadRequests(stream) == 0);
- // 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);
-
- // Step 3.d: Set this.[[queueTotalSize]] to this.[[queueTotalSize]] − entry.[[byteLength]].
- uint32_t byteLength = entry->byteLength();
- queueTotalSize = queueTotalSize - byteLength;
- controller->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(queueTotalSize));
+ RootedObject view(cx);
- // Step 3.e: Perform ! ReadableByteStreamControllerHandleQueueDrain(this).
- if (!ReadableByteStreamControllerHandleQueueDrain(cx, controller))
- return nullptr;
+ if (stream->mode() == JS::ReadableStreamMode::ExternalSource) {
+ val = controller->getFixedSlot(ControllerSlot_UnderlyingSource);
+ void* underlyingSource = val.toPrivate();
- // Step 3.f: Let view be ! Construct(%Uint8Array%, « entry.[[buffer]],
- // entry.[[byteOffset]], entry.[[byteLength]] »).
- RootedObject buffer(cx, entry->buffer());
+ view = JS_NewUint8Array(cx, queueTotalSize);
+ if (!view)
+ return nullptr;
- uint32_t byteOffset = entry->byteOffset();
- RootedObject view(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, byteOffset, byteLength));
- if (!view)
+ 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).
@@ -3548,7 +3773,8 @@ ReadableStreamBYOBRequest_respondWithNewView_impl(JSContext* cx, const CallArgs&
// a TypeError exception.
if (!viewVal.isObject() || !JS_IsArrayBufferViewObject(&viewVal.toObject())) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
- JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK);
+ JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK,
+ "ReadableStreamBYOBRequest#respondWithNewView");
return false;
}
@@ -3587,7 +3813,8 @@ static const JSFunctionSpec ReadableStreamBYOBRequest_methods[] = {
JS_FS_END
};
-CLASS_SPEC(ReadableStreamBYOBRequest, 3, 2, ClassSpec::DontDefineConstructor);
+CLASS_SPEC(ReadableStreamBYOBRequest, 3, 2, ClassSpec::DontDefineConstructor, 0,
+ JS_NULL_CLASS_OPS);
// Streams spec, 3.12.1. IsReadableStreamBYOBRequest ( x )
// Implemented via is<ReadableStreamBYOBRequest>()
@@ -3665,7 +3892,7 @@ ReadableByteStreamControllerClose(JSContext* cx, Handle<ReadableByteStreamContro
}
// Step 6: Perform ! ReadableStreamClose(stream).
- return ReadableStreamClose(cx, stream);
+ return ReadableStreamCloseInternal(cx, stream);
}
static MOZ_MUST_USE JSObject*
@@ -3782,17 +4009,35 @@ ReadableByteStreamControllerEnqueue(JSContext* cx,
// Step 3: Assert: stream.[[state]] is "readable".
MOZ_ASSERT(stream->readable());
- // Step 4: Let buffer be chunk.[[ViewedArrayBuffer]].
- bool dummy;
- RootedObject buffer(cx, JS_GetArrayBufferViewBuffer(cx, chunk, &dummy));
- if (!buffer)
- return false;
+ // 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]].
- uint32_t byteOffset = JS_GetArrayBufferViewByteOffset(chunk);
+ // Step 5: Let byteOffset be chunk.[[ByteOffset]].
+ byteOffset = JS_GetArrayBufferViewByteOffset(chunk);
- // Step 6: Let byteLength be chunk.[[ByteLength]].
- uint32_t byteLength = JS_GetArrayBufferViewByteLength(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));
@@ -3854,7 +4099,7 @@ ReadableByteStreamControllerEnqueue(JSContext* cx,
} else {
// Step b: Otherwise,
// Step i: Assert: ! IsReadableStreamLocked(stream) is false.
- MOZ_ASSERT(!IsReadableStreamLocked(stream));
+ MOZ_ASSERT(!stream->locked());
// Step ii: Perform
// ! ReadableByteStreamControllerEnqueueChunkToQueue(controller,
@@ -3979,6 +4224,39 @@ ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(JSContext* cx,
*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>());
@@ -4082,7 +4360,7 @@ ReadableByteStreamControllerHandleQueueDrain(JSContext* cx, HandleNativeObject c
bool closeRequested = ControllerFlags(controller) & ControllerFlag_CloseRequested;
if (totalSize == 0 && closeRequested) {
// Step a: Perform ! ReadableStreamClose(controller.[[controlledReadableStream]]).
- return ReadableStreamClose(cx, stream);
+ return ReadableStreamCloseInternal(cx, stream);
}
// Step 3: Otherwise,
@@ -4172,10 +4450,9 @@ ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(JSContext* cx,
static MOZ_MUST_USE JSObject*
ReadableByteStreamControllerPullInto(JSContext* cx,
Handle<ReadableByteStreamController*> controller,
- HandleNativeObject view)
+ Handle<ArrayBufferViewObject*> view)
{
MOZ_ASSERT(controller->is<ReadableByteStreamController>());
- MOZ_ASSERT(JS_IsArrayBufferViewObject(view));
// Step 1: Let stream be controller.[[controlledReadableStream]].
Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
@@ -4669,7 +4946,7 @@ static const JSFunctionSpec ByteLengthQueuingStrategy_methods[] = {
JS_FS_END
};
-CLASS_SPEC(ByteLengthQueuingStrategy, 1, 0, 0);
+CLASS_SPEC(ByteLengthQueuingStrategy, 1, 0, 0, 0, JS_NULL_CLASS_OPS);
// Streams spec, 6.2.2. new CountQueuingStrategy({ highWaterMark })
bool
@@ -4716,7 +4993,7 @@ static const JSFunctionSpec CountQueuingStrategy_methods[] = {
JS_FS_END
};
-CLASS_SPEC(CountQueuingStrategy, 1, 0, 0);
+CLASS_SPEC(CountQueuingStrategy, 1, 0, 0, 0, JS_NULL_CLASS_OPS);
#undef CLASS_SPEC
@@ -4955,3 +5232,258 @@ ValidateAndNormalizeQueuingStrategy(JSContext* cx, HandleValue size,
// 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);
+}
diff --git a/js/src/builtin/Stream.h b/js/src/builtin/Stream.h
index 90b61b49af..0726e78361 100644
--- a/js/src/builtin/Stream.h
+++ b/js/src/builtin/Stream.h
@@ -6,8 +6,10 @@
#ifndef builtin_Stream_h
#define builtin_Stream_h
+#include "builtin/Promise.h"
#include "vm/NativeObject.h"
+
namespace js {
class AutoSetNewObjectMetadata;
@@ -16,14 +18,50 @@ class ReadableStream : public NativeObject
{
public:
static ReadableStream* createDefaultStream(JSContext* cx, HandleValue underlyingSource,
- HandleValue size, HandleValue highWaterMark);
+ HandleValue size, HandleValue highWaterMark,
+ HandleObject proto = nullptr);
static ReadableStream* createByteStream(JSContext* cx, HandleValue underlyingSource,
- HandleValue highWaterMark);
-
- inline bool readable() const;
- inline bool closed() const;
- inline bool errored() const;
- inline bool disturbed() const;
+ 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,
@@ -33,7 +71,7 @@ class ReadableStream : public NativeObject
};
private:
- static ReadableStream* createStream(JSContext* cx);
+ static MOZ_MUST_USE ReadableStream* createStream(JSContext* cx, HandleObject proto = nullptr);
public:
static bool constructor(JSContext* cx, unsigned argc, Value* vp);
@@ -46,6 +84,8 @@ class ReadableStream : public NativeObject
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_;
@@ -56,6 +96,9 @@ class ReadableStreamDefaultReader : public NativeObject
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_;
@@ -63,6 +106,13 @@ class ReadableStreamBYOBReader : public NativeObject
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:
@@ -76,6 +126,8 @@ class ReadableStreamDefaultController : public NativeObject
class ReadableByteStreamController : public NativeObject
{
public:
+ bool hasExternalSource();
+
static bool constructor(JSContext* cx, unsigned argc, Value* vp);
static const ClassSpec classSpec_;
static const Class class_;
diff --git a/js/src/js.msg b/js/src/js.msg
index 3c7f12ae5e..b8aa77902d 100644
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -625,18 +625,19 @@ MSG_DEF(JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, 1, JSEXN_RANGEERR, "'{0}' must
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, "The ReadableStream method '{0}' may only be called on a locked stream.")
+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, 0, JSEXN_TYPEERR, "ReadableStream.getReader('byob') requires a ReadableByteStreamController.")
+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, "The ReadableStream controller method '{0}' called on a stream already closing.")
-MSG_DEF(JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, 1, JSEXN_TYPEERR, "The ReadableStream controller method '{0}' may only be called on a stream in the 'readable' state.")
+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, 0, JSEXN_TYPEERR, "ReadableByteStreamController passed a bad chunk.")
+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.")
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 a8e28a587b..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"
diff --git a/js/src/moz.build b/js/src/moz.build
index adc8aa3e0f..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',
diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h
index 2a217e357f..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") \
@@ -319,6 +321,50 @@
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") \
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