diff options
author | Brian Smith <brian@dbsoft.org> | 2023-09-27 17:33:34 -0500 |
---|---|---|
committer | Brian Smith <brian@dbsoft.org> | 2023-09-27 17:33:34 -0500 |
commit | 0099fc16278bb9859a6ee7c7b10b78509b4d183a (patch) | |
tree | a7d6d71f75cec088f8498ddfa46a6f1cbe0e6e16 /js | |
parent | 8ce17f02ee4d9b40a576911c4bb82fa4802fb167 (diff) | |
download | uxp-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.h | 510 | ||||
-rw-r--r-- | js/src/builtin/Stream.cpp | 916 | ||||
-rw-r--r-- | js/src/builtin/Stream.h | 68 | ||||
-rw-r--r-- | js/src/js.msg | 11 | ||||
-rw-r--r-- | js/src/jsapi-tests/moz.build | 5 | ||||
-rw-r--r-- | js/src/jsapi-tests/testIntTypesABI.cpp | 1 | ||||
-rw-r--r-- | js/src/jsapi-tests/testReadableStream.cpp | 676 | ||||
-rw-r--r-- | js/src/jsapi-tests/tests.cpp | 4 | ||||
-rw-r--r-- | js/src/jsapi.cpp | 366 | ||||
-rw-r--r-- | js/src/jsapi.h | 1 | ||||
-rw-r--r-- | js/src/moz.build | 1 | ||||
-rw-r--r-- | js/src/vm/CommonPropertyNames.h | 46 | ||||
-rw-r--r-- | js/src/vm/Runtime.cpp | 6 | ||||
-rw-r--r-- | js/src/vm/Runtime.h | 7 |
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, ¬ByteStream)) 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 |