зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1795914 - Remove JS Streams implementation r=jandem
Differential Revision: https://phabricator.services.mozilla.com/D159610
This commit is contained in:
Родитель
3d10b581aa
Коммит
4852cc8cc4
|
@ -8,7 +8,6 @@
|
|||
#define mozilla_dom_BodyStream_h
|
||||
|
||||
#include "jsapi.h"
|
||||
#include "js/Stream.h"
|
||||
#include "mozilla/AlreadyAddRefed.h"
|
||||
#include "mozilla/dom/ByteStreamHelpers.h"
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#include "FetchStreamReader.h"
|
||||
#include "InternalResponse.h"
|
||||
#include "js/Stream.h"
|
||||
#include "mozilla/ConsoleReportCollector.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/AutoEntryScript.h"
|
||||
|
|
|
@ -168,24 +168,6 @@ def enable_decorators(value):
|
|||
set_config("ENABLE_DECORATORS", enable_decorators)
|
||||
set_define("ENABLE_DECORATORS", enable_decorators)
|
||||
|
||||
# Enable JS Streams
|
||||
# ===================================================
|
||||
option(
|
||||
"--enable-js-streams",
|
||||
default=False,
|
||||
help="Enable non-default JS Streams implementation",
|
||||
)
|
||||
|
||||
|
||||
@depends("--enable-js-streams")
|
||||
def enable_js_streams(value):
|
||||
if value:
|
||||
return True
|
||||
|
||||
|
||||
set_config("MOZ_JS_STREAMS", enable_js_streams)
|
||||
set_define("MOZ_JS_STREAMS", enable_js_streams)
|
||||
|
||||
# JIT support
|
||||
# =======================================================
|
||||
@depends(target, "--enable-record-tuple")
|
||||
|
|
|
@ -110,16 +110,6 @@
|
|||
REAL(AsyncFunction, CLASP(AsyncFunction)) \
|
||||
REAL(GeneratorFunction, CLASP(GeneratorFunction)) \
|
||||
REAL(AsyncGeneratorFunction, CLASP(AsyncGeneratorFunction)) \
|
||||
IF_JS_STREAMS(REAL(ReadableStream, &js::ReadableStream::class_)) \
|
||||
IF_JS_STREAMS(REAL(ReadableStreamDefaultReader, \
|
||||
&js::ReadableStreamDefaultReader::class_)) \
|
||||
IF_JS_STREAMS(REAL(ReadableStreamDefaultController, \
|
||||
&js::ReadableStreamDefaultController::class_)) \
|
||||
IF_JS_STREAMS(REAL(ReadableByteStreamController, \
|
||||
&js::ReadableByteStreamController::class_)) \
|
||||
IF_JS_STREAMS( \
|
||||
REAL(ByteLengthQueuingStrategy, &js::ByteLengthQueuingStrategy::class_)) \
|
||||
IF_JS_STREAMS(REAL(CountQueuingStrategy, &js::CountQueuingStrategy::class_)) \
|
||||
REAL(WebAssembly, OCLASP(WasmNamespace)) \
|
||||
REAL(WasmModule, OCLASP(WasmModule)) \
|
||||
REAL(WasmInstance, OCLASP(WasmInstance)) \
|
||||
|
|
|
@ -172,19 +172,6 @@ class JS_PUBLIC_API RealmCreationOptions {
|
|||
bool getCoopAndCoepEnabled() const;
|
||||
RealmCreationOptions& setCoopAndCoepEnabled(bool flag);
|
||||
|
||||
bool getStreamsEnabled() const { return streams_; }
|
||||
RealmCreationOptions& setStreamsEnabled(bool flag) {
|
||||
#ifdef MOZ_JS_STREAMS
|
||||
# ifdef MOZ_DOM_STREAMS
|
||||
# error "JS and DOM streams shouldn't be simultaneously configured"
|
||||
# endif
|
||||
streams_ = flag;
|
||||
#else
|
||||
MOZ_ASSERT(!streams_);
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
WeakRefSpecifier getWeakRefsEnabled() const { return weakRefs_; }
|
||||
RealmCreationOptions& setWeakRefsEnabled(WeakRefSpecifier spec) {
|
||||
weakRefs_ = spec;
|
||||
|
@ -280,7 +267,6 @@ class JS_PUBLIC_API RealmCreationOptions {
|
|||
bool sharedMemoryAndAtomics_ = false;
|
||||
bool defineSharedArrayBufferConstructor_ = true;
|
||||
bool coopAndCoep_ = false;
|
||||
bool streams_ = false;
|
||||
bool toSource_ = false;
|
||||
bool propertyErrorMessageFix_ = false;
|
||||
bool iteratorHelpers_ = false;
|
||||
|
|
|
@ -1,491 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*
|
||||
* JSAPI functions and callbacks related to WHATWG Stream objects.
|
||||
*
|
||||
* Much of the API here mirrors the standard algorithms and standard JS methods
|
||||
* of the objects defined in the Streams standard. One difference is that the
|
||||
* functionality of the JS controller object is exposed to C++ as functions
|
||||
* taking ReadableStream instances instead, for convenience.
|
||||
*/
|
||||
|
||||
#ifndef js_Stream_h
|
||||
#define js_Stream_h
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "jstypes.h"
|
||||
|
||||
#include "js/RootingAPI.h"
|
||||
#include "js/TypeDecls.h"
|
||||
|
||||
struct JSClass;
|
||||
|
||||
namespace JS {
|
||||
|
||||
/**
|
||||
* Abstract base class for external underlying sources.
|
||||
*
|
||||
* The term "underlying source" is defined in the Streams spec:
|
||||
* https://streams.spec.whatwg.org/#underlying-source
|
||||
*
|
||||
* A `ReadableStreamUnderlyingSource` is an underlying source that is
|
||||
* implemented in C++ rather than JS. It can be passed to
|
||||
* `JS::NewReadableExternalSourceStreamObject` to create a custom,
|
||||
* embedding-defined ReadableStream.
|
||||
*
|
||||
* There are several API difference between this class and the standard API for
|
||||
* underlying sources implemented in JS:
|
||||
*
|
||||
* - JS underlying sources can be either byte sources or non-byte sources.
|
||||
* External underlying source are always byte sources.
|
||||
*
|
||||
* - The C++ API does not bother with controller objects. Instead of using
|
||||
* controller methods, the underlying source directly calls API functions
|
||||
* like JS::ReadableStream{UpdateDataAvailableFromSource,Close,Error}.
|
||||
*
|
||||
* - 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 arrays; instead, the embedding only updates the amount
|
||||
* of data available using
|
||||
* JS::ReadableStreamUpdateDataAvailableFromSource. When JS requests data
|
||||
* from a reader, writeIntoReadRequestBuffer is invoked, asking the
|
||||
* embedding to write data directly into the buffer we're about to hand to
|
||||
* JS.
|
||||
*
|
||||
* - The C++ API provides extra callbacks onClosed() and onErrored().
|
||||
*
|
||||
* - This class has a `finalize()` method, because C++ cares about lifetimes.
|
||||
*
|
||||
* Additionally, ReadableStreamGetExternalUnderlyingSource can be used to get
|
||||
* the pointer to the underlying source. This locks the stream until it is
|
||||
* released again using JS::ReadableStreamReleaseExternalUnderlyingSource.
|
||||
*
|
||||
* Embeddings can use this to optimize away the JS `ReadableStream` overhead
|
||||
* when an embedding-defined C++ stream is passed to an embedding-defined C++
|
||||
* consumer. For example, consider a ServiceWorker piping a `fetch` Response
|
||||
* body to a TextDecoder. Instead of copying chunks of data into JS typed array
|
||||
* buffers and creating a Promise per chunk, only to immediately resolve the
|
||||
* Promises and read the data out again, the embedding can directly feed the
|
||||
* incoming data to the TextDecoder.
|
||||
*
|
||||
* Compartment safety: All methods (except `finalize`) receive `cx` and
|
||||
* `stream` arguments. SpiderMonkey enters the realm of the stream object
|
||||
* before invoking these methods, so `stream` is never a wrapper. Other
|
||||
* arguments may be wrappers.
|
||||
*/
|
||||
class JS_PUBLIC_API ReadableStreamUnderlyingSource {
|
||||
public:
|
||||
virtual ~ReadableStreamUnderlyingSource() = default;
|
||||
|
||||
/**
|
||||
* Invoked whenever a reader desires more data from this source.
|
||||
*
|
||||
* The given `desiredSize` is the absolute size, not a delta from the
|
||||
* previous desired size.
|
||||
*/
|
||||
virtual void requestData(JSContext* cx, HandleObject stream,
|
||||
size_t desiredSize) = 0;
|
||||
|
||||
/**
|
||||
* Invoked to cause the embedding to fill the given `buffer` with data from
|
||||
* this underlying source.
|
||||
*
|
||||
* This is called only 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, this method
|
||||
* is invoked immediately from under the call to
|
||||
* JS::ReadableStreamUpdateDataAvailableFromSource. If not, it is invoked
|
||||
* if and when a new read request is made.
|
||||
*
|
||||
*/
|
||||
virtual void writeIntoReadRequestBuffer(JSContext* cx, HandleObject stream,
|
||||
JS::Handle<JSObject*> aChunk,
|
||||
size_t length,
|
||||
size_t* bytesWritten) = 0;
|
||||
|
||||
/**
|
||||
* Invoked in reaction to the ReadableStream being canceled. This is
|
||||
* equivalent to the `cancel` method on non-external underlying sources
|
||||
* provided to the ReadableStream constructor in JavaScript.
|
||||
*
|
||||
* The underlying source may free up some resources in this method, but
|
||||
* `*this` must not be destroyed until `finalize()` is called.
|
||||
*
|
||||
* 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().
|
||||
*/
|
||||
virtual Value cancel(JSContext* cx, HandleObject stream,
|
||||
HandleValue reason) = 0;
|
||||
|
||||
/**
|
||||
* Invoked when the associated ReadableStream becomes closed.
|
||||
*
|
||||
* The underlying source may free up some resources in this method, but
|
||||
* `*this` must not be destroyed until `finalize()` is called.
|
||||
*/
|
||||
virtual void onClosed(JSContext* cx, HandleObject stream) = 0;
|
||||
|
||||
/**
|
||||
* Invoked when the associated ReadableStream becomes errored.
|
||||
*
|
||||
* The underlying source may free up some resources in this method, but
|
||||
* `*this` must not be destroyed until `finalize()` is called.
|
||||
*/
|
||||
virtual void onErrored(JSContext* cx, HandleObject stream,
|
||||
HandleValue reason) = 0;
|
||||
|
||||
/**
|
||||
* Invoked when the associated ReadableStream object is finalized. The
|
||||
* stream object is not passed as an argument, as it might not be in a
|
||||
* valid state anymore.
|
||||
*
|
||||
* Note: Finalization can happen on a background thread, so the embedding
|
||||
* must be prepared for `finalize()` to be invoked from any thread.
|
||||
*/
|
||||
virtual void finalize() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* The instance is a byte stream backed by an embedding-provided underlying
|
||||
* source, using the virtual methods of `underlyingSource` as callbacks. The
|
||||
* embedding must ensure that `*underlyingSource` lives as long as the new
|
||||
* stream object. The JS engine will call the finalize() method when the stream
|
||||
* object is destroyed.
|
||||
*
|
||||
* `nsISupportsObject_alreadyAddreffed` is an optional pointer that can be used
|
||||
* to make the new stream participate in Gecko's cycle collection. Here are the
|
||||
* rules for using this parameter properly:
|
||||
*
|
||||
* - `*underlyingSource` must not be a cycle-collected object. (It would lead
|
||||
* to memory leaks as the cycle collector would not be able to collect
|
||||
* cycles containing that object.)
|
||||
*
|
||||
* - `*underlyingSource` must not contain nsCOMPtrs that point to cycle-
|
||||
* collected objects. (Same reason.)
|
||||
*
|
||||
* - `*underlyingSource` may contain a pointer to a single cycle-collected
|
||||
* object.
|
||||
*
|
||||
* - The pointer may be stored in `*underlyingSource` as a raw pointer.
|
||||
*
|
||||
* - The pointer to the nsISupports interface of the same object must be
|
||||
* passed as the `nsISupportsObject_alreadyAddreffed` parameter to this
|
||||
* function. (This is how the cycle collector knows about it, so omitting
|
||||
* this would again cause leaks.)
|
||||
*
|
||||
* If `proto` is non-null, it is used as the instance's [[Prototype]] instead
|
||||
* of the original value of `ReadableStream.prototype`.
|
||||
*/
|
||||
extern JS_PUBLIC_API JSObject* NewReadableExternalSourceStreamObject(
|
||||
JSContext* cx, ReadableStreamUnderlyingSource* underlyingSource,
|
||||
void* nsISupportsObject_alreadyAddreffed = nullptr,
|
||||
HandleObject proto = nullptr);
|
||||
|
||||
/**
|
||||
* 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 |stream| is a ReadableStream object or an unwrappable wrapper
|
||||
* for one.
|
||||
*
|
||||
* Asserts that the stream has an embedding-provided underlying source.
|
||||
*/
|
||||
extern JS_PUBLIC_API bool ReadableStreamGetExternalUnderlyingSource(
|
||||
JSContext* cx, HandleObject stream,
|
||||
ReadableStreamUnderlyingSource** 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 |stream| is a ReadableStream object or an unwrappable wrapper
|
||||
* for one.
|
||||
*
|
||||
* Asserts that the stream has an embedding-provided underlying source.
|
||||
*/
|
||||
extern JS_PUBLIC_API bool ReadableStreamReleaseExternalUnderlyingSource(
|
||||
JSContext* cx, HandleObject 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 writeIntoReadRequestBuffer method.
|
||||
*
|
||||
* Asserts that |stream| is a ReadableStream object or an unwrappable wrapper
|
||||
* for one.
|
||||
*/
|
||||
extern JS_PUBLIC_API bool ReadableStreamUpdateDataAvailableFromSource(
|
||||
JSContext* cx, HandleObject stream, uint32_t availableData);
|
||||
|
||||
/**
|
||||
* Break the cycle between this object and the
|
||||
* nsISupportsObject_alreadyAddreffed passed in
|
||||
* NewReadableExternalSourceStreamObject().
|
||||
*/
|
||||
extern JS_PUBLIC_API void ReadableStreamReleaseCCObject(JSObject* stream);
|
||||
|
||||
/**
|
||||
* Returns true if the given object is a ReadableStream object or an
|
||||
* unwrappable wrapper for one, false otherwise.
|
||||
*/
|
||||
extern JS_PUBLIC_API bool IsReadableStream(JSObject* obj);
|
||||
|
||||
/**
|
||||
* Returns true if the given object is a ReadableStreamDefaultReader or
|
||||
* ReadableStreamBYOBReader object or an unwrappable wrapper for one, false
|
||||
* otherwise.
|
||||
*/
|
||||
extern JS_PUBLIC_API bool IsReadableStreamReader(JSObject* obj);
|
||||
|
||||
/**
|
||||
* Returns true if the given object is a ReadableStreamDefaultReader object
|
||||
* or an unwrappable wrapper for one, false otherwise.
|
||||
*/
|
||||
extern JS_PUBLIC_API bool IsReadableStreamDefaultReader(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 a ReadableStream object or an unwrappable wrapper
|
||||
* for one.
|
||||
*/
|
||||
extern JS_PUBLIC_API bool ReadableStreamGetMode(JSContext* cx,
|
||||
HandleObject stream,
|
||||
ReadableStreamMode* mode);
|
||||
|
||||
enum class ReadableStreamReaderMode { Default, Byob };
|
||||
|
||||
/**
|
||||
* Returns true if the given ReadableStream is readable, false if not.
|
||||
*
|
||||
* Asserts that |stream| is a ReadableStream object or an unwrappable wrapper
|
||||
* for one.
|
||||
*/
|
||||
extern JS_PUBLIC_API bool ReadableStreamIsReadable(JSContext* cx,
|
||||
HandleObject stream,
|
||||
bool* result);
|
||||
|
||||
/**
|
||||
* Returns true if the given ReadableStream is locked, false if not.
|
||||
*
|
||||
* Asserts that |stream| is a ReadableStream object or an unwrappable wrapper
|
||||
* for one.
|
||||
*/
|
||||
extern JS_PUBLIC_API bool ReadableStreamIsLocked(JSContext* cx,
|
||||
HandleObject stream,
|
||||
bool* result);
|
||||
|
||||
/**
|
||||
* Returns true if the given ReadableStream is disturbed, false if not.
|
||||
*
|
||||
* Asserts that |stream| is a ReadableStream object or an unwrappable wrapper
|
||||
* for one.
|
||||
*/
|
||||
extern JS_PUBLIC_API bool ReadableStreamIsDisturbed(JSContext* cx,
|
||||
HandleObject stream,
|
||||
bool* result);
|
||||
|
||||
/**
|
||||
* Cancels the given ReadableStream with the given reason and returns a
|
||||
* Promise resolved according to the result.
|
||||
*
|
||||
* Asserts that |stream| is a ReadableStream object or an unwrappable wrapper
|
||||
* for one.
|
||||
*/
|
||||
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 a ReadableStream object or an unwrappable wrapper
|
||||
* for one. The returned object will always be created in the
|
||||
* current cx compartment.
|
||||
*/
|
||||
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 a ReadableStream object or an unwrappable wrapper
|
||||
* for one.
|
||||
*/
|
||||
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 a ReadableStream object or an unwrappable wrapper
|
||||
* for one.
|
||||
*/
|
||||
extern JS_PUBLIC_API bool ReadableStreamGetDesiredSize(JSContext* cx,
|
||||
JSObject* stream,
|
||||
bool* hasValue,
|
||||
double* value);
|
||||
|
||||
/**
|
||||
* Close the given ReadableStream. This is equivalent to `controller.close()`
|
||||
* in JS.
|
||||
*
|
||||
* This can fail with or without an exception pending under a variety of
|
||||
* circumstances. On failure, the stream may or may not be closed, and
|
||||
* downstream consumers may or may not have been notified.
|
||||
*
|
||||
* Asserts that |stream| is a ReadableStream object or an unwrappable wrapper
|
||||
* for one.
|
||||
*/
|
||||
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 a ReadableStreamDefaultReader or
|
||||
* ReadableStreamBYOBReader object or an unwrappable wrapper for one.
|
||||
*/
|
||||
extern JS_PUBLIC_API bool ReadableStreamReaderIsClosed(JSContext* cx,
|
||||
HandleObject reader,
|
||||
bool* result);
|
||||
|
||||
/**
|
||||
* 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 a ReadableStream object or an unwrappable wrapper
|
||||
* for one.
|
||||
*/
|
||||
extern JS_PUBLIC_API bool ReadableStreamEnqueue(JSContext* cx,
|
||||
HandleObject stream,
|
||||
HandleValue chunk);
|
||||
|
||||
/**
|
||||
* 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 a ReadableStream object or an unwrappable wrapper
|
||||
* for one.
|
||||
*/
|
||||
extern JS_PUBLIC_API bool ReadableStreamError(JSContext* cx,
|
||||
HandleObject stream,
|
||||
HandleValue error);
|
||||
|
||||
/**
|
||||
* C++ equivalent of `reader.cancel(reason)`
|
||||
* (both <https://streams.spec.whatwg.org/#default-reader-cancel> and
|
||||
* <https://streams.spec.whatwg.org/#byob-reader-cancel>).
|
||||
*
|
||||
* `reader` must be a stream reader created using `JS::ReadableStreamGetReader`
|
||||
* or an unwrappable wrapper for one. (This function is meant to support using
|
||||
* C++ to read from streams. It's not meant to allow C++ code to operate on
|
||||
* readers created by scripts.)
|
||||
*/
|
||||
extern JS_PUBLIC_API bool ReadableStreamReaderCancel(JSContext* cx,
|
||||
HandleObject reader,
|
||||
HandleValue reason);
|
||||
|
||||
/**
|
||||
* C++ equivalent of `reader.releaseLock()`
|
||||
* (both <https://streams.spec.whatwg.org/#default-reader-release-lock> and
|
||||
* <https://streams.spec.whatwg.org/#byob-reader-release-lock>).
|
||||
*
|
||||
* `reader` must be a stream reader created using `JS::ReadableStreamGetReader`
|
||||
* or an unwrappable wrapper for one.
|
||||
*/
|
||||
extern JS_PUBLIC_API bool ReadableStreamReaderReleaseLock(JSContext* cx,
|
||||
HandleObject reader);
|
||||
|
||||
/**
|
||||
* C++ equivalent of the `reader.read()` method on default readers
|
||||
* (<https://streams.spec.whatwg.org/#default-reader-read>).
|
||||
*
|
||||
* The result is a new Promise object, or null on OOM.
|
||||
*
|
||||
* `reader` must be the result of calling `JS::ReadableStreamGetReader` with
|
||||
* `ReadableStreamReaderMode::Default` mode, or an unwrappable wrapper for such
|
||||
* a reader.
|
||||
*/
|
||||
extern JS_PUBLIC_API JSObject* ReadableStreamDefaultReaderRead(
|
||||
JSContext* cx, HandleObject reader);
|
||||
|
||||
} // namespace JS
|
||||
|
||||
#endif // js_Stream_h
|
|
@ -141,13 +141,6 @@ using jsid = JS::PropertyKey;
|
|||
# define IF_RECORD_TUPLE(x, ...) __VA_ARGS__
|
||||
#endif
|
||||
|
||||
// Follows the same pattern as IF_RECORD_TUPLE
|
||||
#ifdef MOZ_JS_STREAMS
|
||||
# define IF_JS_STREAMS(x, ...) x
|
||||
#else
|
||||
# define IF_JS_STREAMS(x, ...) __VA_ARGS__
|
||||
#endif
|
||||
|
||||
// Follows the same pattern as IF_RECORD_TUPLE
|
||||
#ifdef ENABLE_DECORATORS
|
||||
# define IF_DECORATORS(x, ...) x
|
||||
|
|
|
@ -1,775 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "builtin/Stream.h"
|
||||
|
||||
#include "js/Stream.h"
|
||||
|
||||
#include <stdint.h> // int32_t
|
||||
|
||||
#include "builtin/streams/ClassSpecMacro.h" // JS_STREAMS_CLASS_SPEC
|
||||
#include "builtin/streams/MiscellaneousOperations.h" // js::CreateAlgorithmFromUnderlyingMethod, js::InvokeOrNoop, js::IsMaybeWrapped, js::PromiseCall, js::PromiseRejectedWithPendingError
|
||||
#include "builtin/streams/PullIntoDescriptor.h" // js::PullIntoDescriptor
|
||||
#include "builtin/streams/QueueWithSizes.h" // js::{EnqueueValueWithSize,ResetQueue}
|
||||
#include "builtin/streams/ReadableStream.h" // js::ReadableStream, js::SetUpExternalReadableByteStreamController
|
||||
#include "builtin/streams/ReadableStreamController.h" // js::ReadableStream{,Default}Controller, js::ReadableStreamDefaultControllerPullSteps, js::ReadableStreamControllerStart{,Failed}Handler
|
||||
#include "builtin/streams/ReadableStreamDefaultControllerOperations.h" // js::ReadableStreamControllerClearAlgorithms
|
||||
#include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStream{AddReadOrReadIntoRequest,CloseInternal,CreateReadResult,ErrorInternal,FulfillReadOrReadIntoRequest,GetNumReadRequests,HasDefaultReader}
|
||||
#include "builtin/streams/ReadableStreamReader.h" // js::ReadableStream{,Default}Reader, js::CreateReadableStreamDefaultReader, js::ReadableStreamReaderGeneric{Cancel,Initialize,Release}, js::ReadableStreamDefaultReaderRead
|
||||
#include "js/ArrayBuffer.h" // JS::NewArrayBuffer
|
||||
#include "js/experimental/TypedData.h" // JS_GetArrayBufferViewData, JS_NewUint8Array{,WithBuffer}
|
||||
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
|
||||
#include "js/PropertySpec.h"
|
||||
#include "vm/Interpreter.h"
|
||||
#include "vm/JSContext.h"
|
||||
#include "vm/PlainObject.h" // js::PlainObject
|
||||
#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
|
||||
#include "vm/SelfHosting.h"
|
||||
|
||||
#include "builtin/HandlerFunction-inl.h" // js::NewHandler
|
||||
#include "builtin/streams/ReadableStreamReader-inl.h" // js::Unwrap{ReaderFromStream{,NoThrow},StreamFromReader}
|
||||
#include "vm/Compartment-inl.h"
|
||||
#include "vm/List-inl.h" // js::ListObject, js::StoreNewListInFixedSlot
|
||||
#include "vm/NativeObject-inl.h"
|
||||
|
||||
using namespace js;
|
||||
|
||||
#if 0 // disable user-defined byte streams
|
||||
|
||||
class ByteStreamChunk : public NativeObject
|
||||
{
|
||||
private:
|
||||
enum Slots {
|
||||
Slot_Buffer = 0,
|
||||
Slot_ByteOffset,
|
||||
Slot_ByteLength,
|
||||
SlotCount
|
||||
};
|
||||
|
||||
public:
|
||||
static const JSClass class_;
|
||||
|
||||
ArrayBufferObject* buffer() {
|
||||
return &getFixedSlot(Slot_Buffer).toObject().as<ArrayBufferObject>();
|
||||
}
|
||||
uint32_t byteOffset() { return getFixedSlot(Slot_ByteOffset).toInt32(); }
|
||||
void SetByteOffset(uint32_t offset) {
|
||||
setFixedSlot(Slot_ByteOffset, Int32Value(offset));
|
||||
}
|
||||
uint32_t byteLength() { return getFixedSlot(Slot_ByteLength).toInt32(); }
|
||||
void SetByteLength(uint32_t length) {
|
||||
setFixedSlot(Slot_ByteLength, Int32Value(length));
|
||||
}
|
||||
|
||||
static ByteStreamChunk* create(JSContext* cx, HandleObject buffer, uint32_t byteOffset,
|
||||
uint32_t byteLength)
|
||||
{
|
||||
Rooted<ByteStreamChunk*> chunk(cx, NewBuiltinClassInstance<ByteStreamChunk>(cx));
|
||||
if (!chunk) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
chunk->setFixedSlot(Slot_Buffer, ObjectValue(*buffer));
|
||||
chunk->setFixedSlot(Slot_ByteOffset, Int32Value(byteOffset));
|
||||
chunk->setFixedSlot(Slot_ByteLength, Int32Value(byteLength));
|
||||
return chunk;
|
||||
}
|
||||
};
|
||||
|
||||
const JSClass ByteStreamChunk::class_ = {
|
||||
"ByteStreamChunk",
|
||||
JSCLASS_HAS_RESERVED_SLOTS(SlotCount)
|
||||
};
|
||||
|
||||
#endif // user-defined byte streams
|
||||
|
||||
/*** 3.3. ReadableStreamAsyncIteratorPrototype ******************************/
|
||||
|
||||
// Not implemented.
|
||||
|
||||
/*** 3.7. Class ReadableStreamBYOBReader ************************************/
|
||||
|
||||
// Not implemented.
|
||||
|
||||
/*** 3.11. Class ReadableByteStreamController *******************************/
|
||||
|
||||
#if 0 // disable user-defined byte streams
|
||||
|
||||
/**
|
||||
* Streams spec, 3.10.3
|
||||
* new ReadableByteStreamController ( stream, underlyingSource,
|
||||
* highWaterMark )
|
||||
* Steps 3 - 16.
|
||||
*
|
||||
* Note: All arguments must be same-compartment with cx. ReadableStream
|
||||
* controllers are always created in the same compartment as the stream.
|
||||
*/
|
||||
[[nodiscard]] static ReadableByteStreamController*
|
||||
CreateReadableByteStreamController(JSContext* cx,
|
||||
Handle<ReadableStream*> stream,
|
||||
HandleValue underlyingByteSource,
|
||||
HandleValue highWaterMarkVal)
|
||||
{
|
||||
cx->check(stream, underlyingByteSource, highWaterMarkVal);
|
||||
|
||||
Rooted<ReadableByteStreamController*> controller(cx,
|
||||
NewBuiltinClassInstance<ReadableByteStreamController>(cx));
|
||||
if (!controller) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 3: Set this.[[controlledReadableStream]] to stream.
|
||||
controller->setStream(stream);
|
||||
|
||||
// Step 4: Set this.[[underlyingByteSource]] to underlyingByteSource.
|
||||
controller->setUnderlyingSource(underlyingByteSource);
|
||||
|
||||
// Step 5: Set this.[[pullAgain]], and this.[[pulling]] to false.
|
||||
controller->setFlags(0);
|
||||
|
||||
// Step 6: Perform ! ReadableByteStreamControllerClearPendingPullIntos(this).
|
||||
if (!ReadableByteStreamControllerClearPendingPullIntos(cx, controller)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 7: Perform ! ResetQueue(this).
|
||||
if (!ResetQueue(cx, controller)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 8: Set this.[[started]] and this.[[closeRequested]] to false.
|
||||
// These should be false by default, unchanged since step 5.
|
||||
MOZ_ASSERT(controller->flags() == 0);
|
||||
|
||||
// Step 9: Set this.[[strategyHWM]] to
|
||||
// ? ValidateAndNormalizeHighWaterMark(highWaterMark).
|
||||
double highWaterMark;
|
||||
if (!ValidateAndNormalizeHighWaterMark(cx, highWaterMarkVal, &highWaterMark)) {
|
||||
return nullptr;
|
||||
}
|
||||
controller->setStrategyHWM(highWaterMark);
|
||||
|
||||
// Step 10: Let autoAllocateChunkSize be
|
||||
// ? GetV(underlyingByteSource, "autoAllocateChunkSize").
|
||||
RootedValue autoAllocateChunkSize(cx);
|
||||
if (!GetProperty(cx, underlyingByteSource, cx->names().autoAllocateChunkSize,
|
||||
&autoAllocateChunkSize))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 11: If autoAllocateChunkSize is not undefined,
|
||||
if (!autoAllocateChunkSize.isUndefined()) {
|
||||
// Step a: If ! IsInteger(autoAllocateChunkSize) is false, or if
|
||||
// autoAllocateChunkSize ≤ 0, throw a RangeError exception.
|
||||
if (!IsInteger(autoAllocateChunkSize) || autoAllocateChunkSize.toNumber() <= 0) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNKSIZE);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 12: Set this.[[autoAllocateChunkSize]] to autoAllocateChunkSize.
|
||||
controller->setAutoAllocateChunkSize(autoAllocateChunkSize);
|
||||
|
||||
// Step 13: Set this.[[pendingPullIntos]] to a new empty List.
|
||||
if (!StoreNewListInFixedSlot(cx, controller,
|
||||
ReadableByteStreamController::Slot_PendingPullIntos)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 14: Let controller be this (implicit).
|
||||
|
||||
// Step 15: Let startResult be
|
||||
// ? InvokeOrNoop(underlyingSource, "start", « this »).
|
||||
RootedValue startResult(cx);
|
||||
RootedValue controllerVal(cx, ObjectValue(*controller));
|
||||
if (!InvokeOrNoop(cx, underlyingByteSource, cx->names().start, controllerVal, &startResult)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 16: Let startPromise be a promise resolved with startResult:
|
||||
RootedObject startPromise(cx, PromiseObject::unforgeableResolve(cx, startResult));
|
||||
if (!startPromise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RootedObject onStartFulfilled(cx, NewHandler(cx, ReadableStreamControllerStartHandler, 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;
|
||||
}
|
||||
|
||||
#endif // user-defined byte streams
|
||||
|
||||
/**
|
||||
* Streams spec, 3.11.3.
|
||||
* new ReadableByteStreamController ( stream, underlyingByteSource,
|
||||
* highWaterMark )
|
||||
*/
|
||||
bool ReadableByteStreamController::constructor(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
// Step 1: Throw a TypeError exception.
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_BOGUS_CONSTRUCTOR,
|
||||
"ReadableByteStreamController");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disconnect the source from a controller without calling finalize() on it,
|
||||
// unless this class is reset(). This ensures that finalize() will not be called
|
||||
// on the source if setting up the controller fails.
|
||||
class MOZ_RAII AutoClearUnderlyingSource {
|
||||
Rooted<ReadableStreamController*> controller_;
|
||||
|
||||
public:
|
||||
AutoClearUnderlyingSource(JSContext* cx, ReadableStreamController* controller)
|
||||
: controller_(cx, controller) {}
|
||||
|
||||
~AutoClearUnderlyingSource() {
|
||||
if (controller_) {
|
||||
ReadableStreamController::clearUnderlyingSource(
|
||||
controller_, /* finalizeSource */ false);
|
||||
}
|
||||
}
|
||||
|
||||
void reset() { controller_ = nullptr; }
|
||||
};
|
||||
|
||||
/**
|
||||
* Version of SetUpReadableByteStreamController that's specialized for handling
|
||||
* external, embedding-provided, underlying sources.
|
||||
*/
|
||||
[[nodiscard]] bool js::SetUpExternalReadableByteStreamController(
|
||||
JSContext* cx, Handle<ReadableStream*> stream,
|
||||
JS::ReadableStreamUnderlyingSource* source) {
|
||||
// Done elsewhere in the standard: Create the controller object.
|
||||
Rooted<ReadableByteStreamController*> controller(
|
||||
cx, NewBuiltinClassInstance<ReadableByteStreamController>(cx));
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AutoClearUnderlyingSource autoClear(cx, controller);
|
||||
|
||||
// Step 1: Assert: stream.[[readableStreamController]] is undefined.
|
||||
MOZ_ASSERT(!stream->hasController());
|
||||
|
||||
// Step 2: If autoAllocateChunkSize is not undefined, [...]
|
||||
// (It's treated as undefined.)
|
||||
|
||||
// Step 3: Set controller.[[controlledReadableByteStream]] to stream.
|
||||
controller->setStream(stream);
|
||||
|
||||
// Step 4: Set controller.[[pullAgain]] and controller.[[pulling]] to false.
|
||||
controller->setFlags(0);
|
||||
MOZ_ASSERT(!controller->pullAgain());
|
||||
MOZ_ASSERT(!controller->pulling());
|
||||
|
||||
// Step 5: Perform
|
||||
// ! ReadableByteStreamControllerClearPendingPullIntos(controller).
|
||||
// Omitted. This step is apparently redundant; see
|
||||
// <https://github.com/whatwg/streams/issues/975>.
|
||||
|
||||
// Step 6: Perform ! ResetQueue(this).
|
||||
controller->setQueueTotalSize(0);
|
||||
|
||||
// Step 7: Set controller.[[closeRequested]] and controller.[[started]] to
|
||||
// false (implicit).
|
||||
MOZ_ASSERT(!controller->closeRequested());
|
||||
MOZ_ASSERT(!controller->started());
|
||||
|
||||
// Step 8: Set controller.[[strategyHWM]] to
|
||||
// ? ValidateAndNormalizeHighWaterMark(highWaterMark).
|
||||
controller->setStrategyHWM(0);
|
||||
|
||||
// Step 9: Set controller.[[pullAlgorithm]] to pullAlgorithm.
|
||||
// Step 10: Set controller.[[cancelAlgorithm]] to cancelAlgorithm.
|
||||
// (These algorithms are given by source's virtual methods.)
|
||||
controller->setExternalSource(source);
|
||||
|
||||
// Step 11: Set controller.[[autoAllocateChunkSize]] to
|
||||
// autoAllocateChunkSize (implicit).
|
||||
MOZ_ASSERT(controller->autoAllocateChunkSize().isUndefined());
|
||||
|
||||
// Step 12: Set this.[[pendingPullIntos]] to a new empty List.
|
||||
if (!StoreNewListInFixedSlot(
|
||||
cx, controller,
|
||||
ReadableByteStreamController::Slot_PendingPullIntos)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 13: Set stream.[[readableStreamController]] to controller.
|
||||
stream->setController(controller);
|
||||
|
||||
// Step 14: Let startResult be the result of performing startAlgorithm.
|
||||
// (For external sources, this algorithm does nothing and returns undefined.)
|
||||
// Step 15: Let startPromise be a promise resolved with startResult.
|
||||
Rooted<PromiseObject*> startPromise(cx, PromiseResolvedWithUndefined(cx));
|
||||
if (!startPromise) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 16: Upon fulfillment of startPromise, [...]
|
||||
// Step 17: Upon rejection of startPromise with reason r, [...]
|
||||
RootedObject onStartFulfilled(
|
||||
cx, NewHandler(cx, ReadableStreamControllerStartHandler, controller));
|
||||
if (!onStartFulfilled) {
|
||||
return false;
|
||||
}
|
||||
RootedObject onStartRejected(
|
||||
cx,
|
||||
NewHandler(cx, ReadableStreamControllerStartFailedHandler, controller));
|
||||
if (!onStartRejected) {
|
||||
return false;
|
||||
}
|
||||
if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled,
|
||||
onStartRejected)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
autoClear.reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
static const JSPropertySpec ReadableByteStreamController_properties[] = {
|
||||
JS_PS_END};
|
||||
|
||||
static const JSFunctionSpec ReadableByteStreamController_methods[] = {
|
||||
JS_FS_END};
|
||||
|
||||
static void ReadableByteStreamControllerFinalize(JS::GCContext* gcx,
|
||||
JSObject* obj) {
|
||||
ReadableByteStreamController& controller =
|
||||
obj->as<ReadableByteStreamController>();
|
||||
|
||||
if (controller.getFixedSlot(ReadableStreamController::Slot_Flags)
|
||||
.isUndefined()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!controller.hasExternalSource()) {
|
||||
return;
|
||||
}
|
||||
|
||||
controller.externalSource()->finalize();
|
||||
}
|
||||
|
||||
static const JSClassOps ReadableByteStreamControllerClassOps = {
|
||||
nullptr, // addProperty
|
||||
nullptr, // delProperty
|
||||
nullptr, // enumerate
|
||||
nullptr, // newEnumerate
|
||||
nullptr, // resolve
|
||||
nullptr, // mayResolve
|
||||
ReadableByteStreamControllerFinalize, // finalize
|
||||
nullptr, // call
|
||||
nullptr, // construct
|
||||
nullptr, // trace
|
||||
};
|
||||
|
||||
JS_STREAMS_CLASS_SPEC(ReadableByteStreamController, 0, SlotCount,
|
||||
ClassSpec::DontDefineConstructor,
|
||||
JSCLASS_BACKGROUND_FINALIZE,
|
||||
&ReadableByteStreamControllerClassOps);
|
||||
|
||||
// Streams spec, 3.11.5.1. [[CancelSteps]] ()
|
||||
// Unified with 3.9.5.1 above.
|
||||
|
||||
[[nodiscard]] static bool ReadableByteStreamControllerHandleQueueDrain(
|
||||
JSContext* cx, Handle<ReadableStreamController*> unwrappedController);
|
||||
|
||||
/**
|
||||
* Streams spec, 3.11.5.2. [[PullSteps]] ( forAuthorCode )
|
||||
*/
|
||||
[[nodiscard]] static PromiseObject* ReadableByteStreamControllerPullSteps(
|
||||
JSContext* cx, Handle<ReadableByteStreamController*> unwrappedController) {
|
||||
// Step 1: Let stream be this.[[controlledReadableByteStream]].
|
||||
Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
|
||||
|
||||
// Step 2: Assert: ! ReadableStreamHasDefaultReader(stream) is true.
|
||||
#ifdef DEBUG
|
||||
bool result;
|
||||
if (!ReadableStreamHasDefaultReader(cx, unwrappedStream, &result)) {
|
||||
return nullptr;
|
||||
}
|
||||
MOZ_ASSERT(result);
|
||||
#endif
|
||||
|
||||
RootedValue val(cx);
|
||||
// Step 3: If this.[[queueTotalSize]] > 0,
|
||||
double queueTotalSize = unwrappedController->queueTotalSize();
|
||||
if (queueTotalSize > 0) {
|
||||
// Step 3.a: Assert: ! ReadableStreamGetNumReadRequests(_stream_) is 0.
|
||||
MOZ_ASSERT(ReadableStreamGetNumReadRequests(unwrappedStream) == 0);
|
||||
|
||||
RootedObject view(cx);
|
||||
|
||||
MOZ_RELEASE_ASSERT(unwrappedStream->mode() ==
|
||||
JS::ReadableStreamMode::ExternalSource);
|
||||
#if 0 // disable user-defined byte streams
|
||||
if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource)
|
||||
#endif // user-defined byte streams
|
||||
{
|
||||
JS::ReadableStreamUnderlyingSource* source =
|
||||
unwrappedController->externalSource();
|
||||
|
||||
view = JS_NewUint8Array(cx, queueTotalSize);
|
||||
if (!view) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t bytesWritten;
|
||||
{
|
||||
AutoRealm ar(cx, unwrappedStream);
|
||||
source->writeIntoReadRequestBuffer(cx, unwrappedStream, view,
|
||||
queueTotalSize, &bytesWritten);
|
||||
}
|
||||
|
||||
queueTotalSize = queueTotalSize - bytesWritten;
|
||||
}
|
||||
|
||||
#if 0 // disable user-defined byte streams
|
||||
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).
|
||||
Rooted<ListObject*> unwrappedQueue(cx, unwrappedController->queue());
|
||||
Rooted<ByteStreamChunk*> unwrappedEntry(cx,
|
||||
UnwrapAndDowncastObject<ByteStreamChunk>(
|
||||
cx, &unwrappedQueue->popFirstAs<JSObject>(cx)));
|
||||
if (!unwrappedEntry) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
queueTotalSize = queueTotalSize - unwrappedEntry->byteLength();
|
||||
|
||||
// Step 3.f: Let view be ! Construct(%Uint8Array%,
|
||||
// « entry.[[buffer]],
|
||||
// entry.[[byteOffset]],
|
||||
// entry.[[byteLength]] »).
|
||||
// (reordered)
|
||||
RootedObject buffer(cx, unwrappedEntry->buffer());
|
||||
if (!cx->compartment()->wrap(cx, &buffer)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32_t byteOffset = unwrappedEntry->byteOffset();
|
||||
view = JS_NewUint8ArrayWithBuffer(cx, buffer, byteOffset, unwrappedEntry->byteLength());
|
||||
if (!view) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
#endif // user-defined byte streams
|
||||
|
||||
// Step 3.d: Set this.[[queueTotalSize]] to
|
||||
// this.[[queueTotalSize]] − entry.[[byteLength]].
|
||||
// (reordered)
|
||||
unwrappedController->setQueueTotalSize(queueTotalSize);
|
||||
|
||||
// Step 3.e: Perform ! ReadableByteStreamControllerHandleQueueDrain(this).
|
||||
// (reordered)
|
||||
if (!ReadableByteStreamControllerHandleQueueDrain(cx,
|
||||
unwrappedController)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 3.g: Return a promise resolved with
|
||||
// ! ReadableStreamCreateReadResult(view, false, forAuthorCode).
|
||||
val.setObject(*view);
|
||||
ReadableStreamReader* unwrappedReader =
|
||||
UnwrapReaderFromStream(cx, unwrappedStream);
|
||||
if (!unwrappedReader) {
|
||||
return nullptr;
|
||||
}
|
||||
Rooted<PlainObject*> readResult(
|
||||
cx, ReadableStreamCreateReadResult(cx, val, false,
|
||||
unwrappedReader->forAuthorCode()));
|
||||
if (!readResult) {
|
||||
return nullptr;
|
||||
}
|
||||
val.setObject(*readResult);
|
||||
|
||||
return PromiseObject::unforgeableResolveWithNonPromise(cx, val);
|
||||
}
|
||||
|
||||
// Step 4: Let autoAllocateChunkSize be this.[[autoAllocateChunkSize]].
|
||||
val = unwrappedController->autoAllocateChunkSize();
|
||||
|
||||
// Step 5: If autoAllocateChunkSize is not undefined,
|
||||
if (!val.isUndefined()) {
|
||||
double autoAllocateChunkSize = val.toNumber();
|
||||
|
||||
// Step 5.a: Let buffer be
|
||||
// Construct(%ArrayBuffer%, « autoAllocateChunkSize »).
|
||||
JSObject* bufferObj = JS::NewArrayBuffer(cx, autoAllocateChunkSize);
|
||||
|
||||
// Step 5.b: If buffer is an abrupt completion,
|
||||
// return a promise rejected with buffer.[[Value]].
|
||||
if (!bufferObj) {
|
||||
return PromiseRejectedWithPendingError(cx);
|
||||
}
|
||||
|
||||
RootedArrayBufferObject buffer(cx, &bufferObj->as<ArrayBufferObject>());
|
||||
|
||||
// Step 5.c: Let pullIntoDescriptor be
|
||||
// Record {[[buffer]]: buffer.[[Value]],
|
||||
// [[byteOffset]]: 0,
|
||||
// [[byteLength]]: autoAllocateChunkSize,
|
||||
// [[bytesFilled]]: 0,
|
||||
// [[elementSize]]: 1,
|
||||
// [[ctor]]: %Uint8Array%,
|
||||
// [[readerType]]: `"default"`}.
|
||||
RootedObject pullIntoDescriptor(
|
||||
cx, PullIntoDescriptor::create(cx, buffer, 0, autoAllocateChunkSize, 0,
|
||||
1, nullptr, ReaderType::Default));
|
||||
if (!pullIntoDescriptor) {
|
||||
return PromiseRejectedWithPendingError(cx);
|
||||
}
|
||||
|
||||
// Step 5.d: Append pullIntoDescriptor as the last element of
|
||||
// this.[[pendingPullIntos]].
|
||||
if (!AppendToListInFixedSlot(
|
||||
cx, unwrappedController,
|
||||
ReadableByteStreamController::Slot_PendingPullIntos,
|
||||
pullIntoDescriptor)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6: Let promise be ! ReadableStreamAddReadRequest(stream,
|
||||
// forAuthorCode).
|
||||
Rooted<PromiseObject*> promise(
|
||||
cx, ReadableStreamAddReadOrReadIntoRequest(cx, unwrappedStream));
|
||||
if (!promise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 7: Perform ! ReadableByteStreamControllerCallPullIfNeeded(this).
|
||||
if (!ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 8: Return promise.
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unified implementation of ReadableStream controllers' [[PullSteps]] internal
|
||||
* methods.
|
||||
* Streams spec, 3.9.5.2. [[PullSteps]] ( forAuthorCode )
|
||||
* and
|
||||
* Streams spec, 3.11.5.2. [[PullSteps]] ( forAuthorCode )
|
||||
*/
|
||||
[[nodiscard]] PromiseObject* js::ReadableStreamControllerPullSteps(
|
||||
JSContext* cx, Handle<ReadableStreamController*> unwrappedController) {
|
||||
if (unwrappedController->is<ReadableStreamDefaultController>()) {
|
||||
Rooted<ReadableStreamDefaultController*> unwrappedDefaultController(
|
||||
cx, &unwrappedController->as<ReadableStreamDefaultController>());
|
||||
return ReadableStreamDefaultControllerPullSteps(cx,
|
||||
unwrappedDefaultController);
|
||||
}
|
||||
|
||||
Rooted<ReadableByteStreamController*> unwrappedByteController(
|
||||
cx, &unwrappedController->as<ReadableByteStreamController>());
|
||||
return ReadableByteStreamControllerPullSteps(cx, unwrappedByteController);
|
||||
}
|
||||
|
||||
/*** 3.13. Readable stream BYOB controller abstract operations **************/
|
||||
|
||||
// Streams spec, 3.13.1. IsReadableStreamBYOBRequest ( x )
|
||||
// Implemented via is<ReadableStreamBYOBRequest>()
|
||||
|
||||
// Streams spec, 3.13.2. IsReadableByteStreamController ( x )
|
||||
// Implemented via is<ReadableByteStreamController>()
|
||||
|
||||
// Streams spec, 3.13.3.
|
||||
// ReadableByteStreamControllerCallPullIfNeeded ( controller )
|
||||
// Unified with 3.9.2 above.
|
||||
|
||||
[[nodiscard]] static bool ReadableByteStreamControllerInvalidateBYOBRequest(
|
||||
JSContext* cx, Handle<ReadableByteStreamController*> unwrappedController);
|
||||
|
||||
/**
|
||||
* Streams spec, 3.13.5.
|
||||
* ReadableByteStreamControllerClearPendingPullIntos ( controller )
|
||||
*/
|
||||
[[nodiscard]] bool js::ReadableByteStreamControllerClearPendingPullIntos(
|
||||
JSContext* cx, Handle<ReadableByteStreamController*> unwrappedController) {
|
||||
// Step 1: Perform
|
||||
// ! ReadableByteStreamControllerInvalidateBYOBRequest(controller).
|
||||
if (!ReadableByteStreamControllerInvalidateBYOBRequest(cx,
|
||||
unwrappedController)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: Set controller.[[pendingPullIntos]] to a new empty List.
|
||||
return StoreNewListInFixedSlot(
|
||||
cx, unwrappedController,
|
||||
ReadableByteStreamController::Slot_PendingPullIntos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.13.6. ReadableByteStreamControllerClose ( controller )
|
||||
*/
|
||||
[[nodiscard]] bool js::ReadableByteStreamControllerClose(
|
||||
JSContext* cx, Handle<ReadableByteStreamController*> unwrappedController) {
|
||||
// Step 1: Let stream be controller.[[controlledReadableByteStream]].
|
||||
Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
|
||||
|
||||
// Step 2: Assert: controller.[[closeRequested]] is false.
|
||||
MOZ_ASSERT(!unwrappedController->closeRequested());
|
||||
|
||||
// Step 3: Assert: stream.[[state]] is "readable".
|
||||
MOZ_ASSERT(unwrappedStream->readable());
|
||||
|
||||
// Step 4: If controller.[[queueTotalSize]] > 0,
|
||||
if (unwrappedController->queueTotalSize() > 0) {
|
||||
// Step a: Set controller.[[closeRequested]] to true.
|
||||
unwrappedController->setCloseRequested();
|
||||
|
||||
// Step b: Return.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 5: If controller.[[pendingPullIntos]] is not empty,
|
||||
Rooted<ListObject*> unwrappedPendingPullIntos(
|
||||
cx, unwrappedController->pendingPullIntos());
|
||||
if (unwrappedPendingPullIntos->length() != 0) {
|
||||
// Step a: Let firstPendingPullInto be the first element of
|
||||
// controller.[[pendingPullIntos]].
|
||||
Rooted<PullIntoDescriptor*> unwrappedFirstPendingPullInto(
|
||||
cx, UnwrapAndDowncastObject<PullIntoDescriptor>(
|
||||
cx, &unwrappedPendingPullIntos->get(0).toObject()));
|
||||
if (!unwrappedFirstPendingPullInto) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step b: If firstPendingPullInto.[[bytesFilled]] > 0,
|
||||
if (unwrappedFirstPendingPullInto->bytesFilled() > 0) {
|
||||
// Step i: Let e be a new TypeError exception.
|
||||
JS_ReportErrorNumberASCII(
|
||||
cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLEBYTESTREAMCONTROLLER_CLOSE_PENDING_PULL);
|
||||
RootedValue e(cx);
|
||||
Rooted<SavedFrame*> stack(cx);
|
||||
if (!cx->isExceptionPending() ||
|
||||
!GetAndClearExceptionAndStack(cx, &e, &stack)) {
|
||||
// Uncatchable error. Die immediately without erroring the
|
||||
// stream.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step ii: Perform ! ReadableByteStreamControllerError(controller, e).
|
||||
if (!ReadableStreamControllerError(cx, unwrappedController, e)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step iii: Throw e.
|
||||
cx->setPendingException(e, stack);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6: Perform ! ReadableByteStreamControllerClearAlgorithms(controller).
|
||||
ReadableStreamControllerClearAlgorithms(unwrappedController);
|
||||
|
||||
// Step 7: Perform ! ReadableStreamClose(stream).
|
||||
return ReadableStreamCloseInternal(cx, unwrappedStream);
|
||||
}
|
||||
|
||||
// Streams spec, 3.13.11. ReadableByteStreamControllerError ( controller, e )
|
||||
// Unified with 3.10.7 above.
|
||||
|
||||
// Streams spec 3.13.14.
|
||||
// ReadableByteStreamControllerGetDesiredSize ( controller )
|
||||
// Unified with 3.10.8 above.
|
||||
|
||||
/**
|
||||
* Streams spec, 3.13.15.
|
||||
* ReadableByteStreamControllerHandleQueueDrain ( controller )
|
||||
*/
|
||||
[[nodiscard]] static bool ReadableByteStreamControllerHandleQueueDrain(
|
||||
JSContext* cx, Handle<ReadableStreamController*> unwrappedController) {
|
||||
MOZ_ASSERT(unwrappedController->is<ReadableByteStreamController>());
|
||||
|
||||
// Step 1: Assert: controller.[[controlledReadableStream]].[[state]]
|
||||
// is "readable".
|
||||
Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
|
||||
MOZ_ASSERT(unwrappedStream->readable());
|
||||
|
||||
// Step 2: If controller.[[queueTotalSize]] is 0 and
|
||||
// controller.[[closeRequested]] is true,
|
||||
if (unwrappedController->queueTotalSize() == 0 &&
|
||||
unwrappedController->closeRequested()) {
|
||||
// Step a: Perform
|
||||
// ! ReadableByteStreamControllerClearAlgorithms(controller).
|
||||
ReadableStreamControllerClearAlgorithms(unwrappedController);
|
||||
|
||||
// Step b: Perform
|
||||
// ! ReadableStreamClose(controller.[[controlledReadableStream]]).
|
||||
return ReadableStreamCloseInternal(cx, unwrappedStream);
|
||||
}
|
||||
|
||||
// Step 3: Otherwise,
|
||||
// Step a: Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller).
|
||||
return ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController);
|
||||
}
|
||||
|
||||
enum BYOBRequestSlots {
|
||||
BYOBRequestSlot_Controller,
|
||||
BYOBRequestSlot_View,
|
||||
BYOBRequestSlotCount
|
||||
};
|
||||
|
||||
/**
|
||||
* Streams spec 3.13.16.
|
||||
* ReadableByteStreamControllerInvalidateBYOBRequest ( controller )
|
||||
*/
|
||||
[[nodiscard]] static bool ReadableByteStreamControllerInvalidateBYOBRequest(
|
||||
JSContext* cx, Handle<ReadableByteStreamController*> unwrappedController) {
|
||||
// Step 1: If controller.[[byobRequest]] is undefined, return.
|
||||
RootedValue unwrappedBYOBRequestVal(cx, unwrappedController->byobRequest());
|
||||
if (unwrappedBYOBRequestVal.isUndefined()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Rooted<NativeObject*> unwrappedBYOBRequest(
|
||||
cx, UnwrapAndDowncastValue<NativeObject>(cx, unwrappedBYOBRequestVal));
|
||||
if (!unwrappedBYOBRequest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: Set controller.[[byobRequest]]
|
||||
// .[[associatedReadableByteStreamController]]
|
||||
// to undefined.
|
||||
unwrappedBYOBRequest->setFixedSlot(BYOBRequestSlot_Controller,
|
||||
UndefinedValue());
|
||||
|
||||
// Step 3: Set controller.[[byobRequest]].[[view]] to undefined.
|
||||
unwrappedBYOBRequest->setFixedSlot(BYOBRequestSlot_View, UndefinedValue());
|
||||
|
||||
// Step 4: Set controller.[[byobRequest]] to undefined.
|
||||
unwrappedController->clearBYOBRequest();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Streams spec, 3.13.25.
|
||||
// ReadableByteStreamControllerShouldCallPull ( controller )
|
||||
// Unified with 3.10.3 above.
|
|
@ -1,34 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef builtin_Stream_h
|
||||
#define builtin_Stream_h
|
||||
|
||||
#include "jstypes.h" // JS_PUBLIC_API
|
||||
#include "js/RootingAPI.h" // JS::Handle
|
||||
|
||||
struct JS_PUBLIC_API JSContext;
|
||||
|
||||
namespace js {
|
||||
|
||||
class PromiseObject;
|
||||
class ReadableByteStreamController;
|
||||
class ReadableStreamController;
|
||||
|
||||
[[nodiscard]] extern bool ReadableByteStreamControllerClearPendingPullIntos(
|
||||
JSContext* cx,
|
||||
JS::Handle<ReadableByteStreamController*> unwrappedController);
|
||||
|
||||
[[nodiscard]] extern bool ReadableByteStreamControllerClose(
|
||||
JSContext* cx,
|
||||
JS::Handle<ReadableByteStreamController*> unwrappedController);
|
||||
|
||||
[[nodiscard]] extern PromiseObject* ReadableStreamControllerPullSteps(
|
||||
JSContext* cx, JS::Handle<ReadableStreamController*> unwrappedController);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif /* builtin_Stream_h */
|
|
@ -3879,12 +3879,6 @@ static bool RejectPromise(JSContext* cx, unsigned argc, Value* vp) {
|
|||
return result;
|
||||
}
|
||||
|
||||
static bool StreamsAreEnabled(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
args.rval().setBoolean(cx->realm()->creationOptions().getStreamsEnabled());
|
||||
return true;
|
||||
}
|
||||
|
||||
static unsigned finalizeCount = 0;
|
||||
|
||||
static void finalize_counter_finalize(JS::GCContext* gcx, JSObject* obj) {
|
||||
|
@ -8326,10 +8320,6 @@ JS_FN_HELP("rejectPromise", RejectPromise, 2, 0,
|
|||
"rejectPromise(promise, reason)",
|
||||
" Reject a Promise by calling the JSAPI function JS::RejectPromise."),
|
||||
|
||||
JS_FN_HELP("streamsAreEnabled", StreamsAreEnabled, 0, 0,
|
||||
"streamsAreEnabled()",
|
||||
" Returns a boolean indicating whether WHATWG Streams are enabled for the current realm."),
|
||||
|
||||
JS_FN_HELP("makeFinalizeObserver", MakeFinalizeObserver, 0, 0,
|
||||
"makeFinalizeObserver()",
|
||||
" Get a special object whose finalization increases the counter returned\n"
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* A JS_STREAMS_CLASS_SPEC macro for defining streams classes. */
|
||||
|
||||
#ifndef builtin_streams_ClassSpecMacro_h
|
||||
#define builtin_streams_ClassSpecMacro_h
|
||||
|
||||
#include "gc/AllocKind.h" // js::gc::AllocKind
|
||||
#include "js/Class.h" // js::ClassSpec, JSClass, JSCLASS_HAS_{CACHED_PROTO,RESERVED_SLOTS}, JS_NULL_CLASS_OPS
|
||||
#include "js/ProtoKey.h" // JSProto_*
|
||||
#include "vm/GlobalObject.h" // js::GenericCreate{Constructor,Prototype}
|
||||
|
||||
#define JS_STREAMS_CLASS_SPEC(cls, nCtorArgs, nSlots, specFlags, classFlags, \
|
||||
classOps) \
|
||||
const js::ClassSpec cls::classSpec_ = { \
|
||||
js::GenericCreateConstructor<cls::constructor, nCtorArgs, \
|
||||
js::gc::AllocKind::FUNCTION>, \
|
||||
js::GenericCreatePrototype<cls>, \
|
||||
nullptr, \
|
||||
nullptr, \
|
||||
cls##_methods, \
|
||||
cls##_properties, \
|
||||
nullptr, \
|
||||
specFlags}; \
|
||||
\
|
||||
const JSClass cls::class_ = {#cls, \
|
||||
JSCLASS_HAS_RESERVED_SLOTS(nSlots) | \
|
||||
JSCLASS_HAS_CACHED_PROTO(JSProto_##cls) | \
|
||||
classFlags, \
|
||||
classOps, &cls::classSpec_}; \
|
||||
\
|
||||
const JSClass cls::protoClass_ = {#cls ".prototype", \
|
||||
JSCLASS_HAS_CACHED_PROTO(JSProto_##cls), \
|
||||
JS_NULL_CLASS_OPS, &cls::classSpec_};
|
||||
|
||||
#endif // builtin_streams_ClassSpecMacro_h
|
|
@ -1,115 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Miscellaneous operations. */
|
||||
|
||||
#ifndef builtin_streams_MiscellaneousOperations_inl_h
|
||||
#define builtin_streams_MiscellaneousOperations_inl_h
|
||||
|
||||
#include "builtin/streams/MiscellaneousOperations.h"
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
|
||||
#include "js/CallAndConstruct.h" // JS::IsCallable
|
||||
#include "js/Promise.h" // JS::{Resolve,Reject}Promise
|
||||
#include "js/RootingAPI.h" // JS::Rooted, JS::{,Mutable}Handle
|
||||
#include "js/Value.h" // JS::UndefinedHandleValue, JS::Value
|
||||
#include "vm/Compartment.h" // JS::Compartment
|
||||
#include "vm/Interpreter.h" // js::Call
|
||||
#include "vm/JSContext.h" // JSContext
|
||||
#include "vm/JSObject.h" // JSObject
|
||||
#include "vm/PromiseObject.h" // js::PromiseObject
|
||||
|
||||
#include "vm/Compartment-inl.h" // JS::Compartment::wrap
|
||||
#include "vm/JSContext-inl.h" // JSContext::check
|
||||
#include "vm/JSObject-inl.h" // js::IsCallable
|
||||
|
||||
namespace js {
|
||||
|
||||
/**
|
||||
* Streams spec, 6.3.5. PromiseCall ( F, V, args )
|
||||
* There must be 0-2 |args| arguments, all convertible to JS::Handle<JS::Value>.
|
||||
*/
|
||||
template <class... Args>
|
||||
[[nodiscard]] inline JSObject* PromiseCall(JSContext* cx,
|
||||
JS::Handle<JS::Value> F,
|
||||
JS::Handle<JS::Value> V,
|
||||
Args&&... args) {
|
||||
cx->check(F);
|
||||
cx->check(V);
|
||||
cx->check(args...);
|
||||
|
||||
// Step 1: Assert: ! IsCallable(F) is true.
|
||||
MOZ_ASSERT(IsCallable(F));
|
||||
|
||||
// Step 2: Assert: V is not undefined.
|
||||
MOZ_ASSERT(!V.isUndefined());
|
||||
|
||||
// Step 3: Assert: args is a List (implicit).
|
||||
// Step 4: Let returnValue be Call(F, V, args).
|
||||
JS::Rooted<JS::Value> rval(cx);
|
||||
if (!Call(cx, F, V, args..., &rval)) {
|
||||
// Step 5: If returnValue is an abrupt completion, return a promise rejected
|
||||
// with returnValue.[[Value]].
|
||||
return PromiseRejectedWithPendingError(cx);
|
||||
}
|
||||
|
||||
// Step 6: Otherwise, return a promise resolved with returnValue.[[Value]].
|
||||
return PromiseObject::unforgeableResolve(cx, rval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the unwrapped promise |unwrappedPromise| with |value|.
|
||||
*/
|
||||
[[nodiscard]] inline bool ResolveUnwrappedPromiseWithValue(
|
||||
JSContext* cx, JSObject* unwrappedPromise, JS::Handle<JS::Value> value) {
|
||||
cx->check(value);
|
||||
|
||||
JS::Rooted<JSObject*> promise(cx, unwrappedPromise);
|
||||
if (!cx->compartment()->wrap(cx, &promise)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return JS::ResolvePromise(cx, promise, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the unwrapped promise |unwrappedPromise| with |undefined|.
|
||||
*/
|
||||
[[nodiscard]] inline bool ResolveUnwrappedPromiseWithUndefined(
|
||||
JSContext* cx, JSObject* unwrappedPromise) {
|
||||
return ResolveUnwrappedPromiseWithValue(cx, unwrappedPromise,
|
||||
JS::UndefinedHandleValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject the unwrapped promise |unwrappedPromise| with |error|, overwriting
|
||||
* |*unwrappedPromise| with its wrapped form.
|
||||
*/
|
||||
[[nodiscard]] inline bool RejectUnwrappedPromiseWithError(
|
||||
JSContext* cx, JS::MutableHandle<JSObject*> unwrappedPromise,
|
||||
JS::Handle<JS::Value> error) {
|
||||
cx->check(error);
|
||||
|
||||
if (!cx->compartment()->wrap(cx, unwrappedPromise)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return JS::RejectPromise(cx, unwrappedPromise, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject the unwrapped promise |unwrappedPromise| with |error|.
|
||||
*/
|
||||
[[nodiscard]] inline bool RejectUnwrappedPromiseWithError(
|
||||
JSContext* cx, JSObject* unwrappedPromise, JS::Handle<JS::Value> error) {
|
||||
JS::Rooted<JSObject*> promise(cx, unwrappedPromise);
|
||||
return RejectUnwrappedPromiseWithError(cx, &promise, error);
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_MiscellaneousOperations_inl_h
|
|
@ -1,193 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Miscellaneous operations. */
|
||||
|
||||
#include "builtin/streams/MiscellaneousOperations.h"
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
#include "mozilla/FloatingPoint.h" // mozilla::IsNaN
|
||||
|
||||
#include "js/CallAndConstruct.h" // JS::IsCallable
|
||||
#include "js/Conversions.h" // JS::ToNumber
|
||||
#include "js/ErrorReport.h" // JS_ReportErrorNumberASCII
|
||||
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
|
||||
#include "js/RootingAPI.h" // JS::{,Mutable}Handle, JS::Rooted
|
||||
#include "vm/Interpreter.h" // js::{Call,GetAndClearException}
|
||||
#include "vm/JSContext.h" // JSContext
|
||||
#include "vm/ObjectOperations.h" // js::GetProperty
|
||||
#include "vm/PromiseObject.h" // js::PromiseObject
|
||||
#include "vm/StringType.h" // js::PropertyName
|
||||
|
||||
#include "vm/JSContext-inl.h" // JSContext::check
|
||||
#include "vm/JSObject-inl.h" // js::IsCallable
|
||||
|
||||
using JS::Handle;
|
||||
using JS::MutableHandle;
|
||||
using JS::ToNumber;
|
||||
using JS::Value;
|
||||
|
||||
[[nodiscard]] js::PromiseObject* js::PromiseRejectedWithPendingError(
|
||||
JSContext* cx) {
|
||||
Rooted<Value> exn(cx);
|
||||
if (!cx->isExceptionPending() || !GetAndClearException(cx, &exn)) {
|
||||
// Uncatchable error. This happens when a slow script is killed or a
|
||||
// worker is terminated. Propagate the uncatchable error. This will
|
||||
// typically kill off the calling asynchronous process: the caller
|
||||
// can't hook its continuation to the new rejected promise.
|
||||
return nullptr;
|
||||
}
|
||||
return PromiseObject::unforgeableReject(cx, exn);
|
||||
}
|
||||
|
||||
/*** 6.3. Miscellaneous operations ******************************************/
|
||||
|
||||
/**
|
||||
* Streams spec, 6.3.1.
|
||||
* CreateAlgorithmFromUnderlyingMethod ( underlyingObject, methodName,
|
||||
* algoArgCount, extraArgs )
|
||||
*
|
||||
* This function only partly implements the standard algorithm. We do not
|
||||
* actually create a new JSFunction completely encapsulating the new algorithm.
|
||||
* Instead, this just gets the specified method and checks for errors. It's the
|
||||
* caller's responsibility to make sure that later, when the algorithm is
|
||||
* "performed", the appropriate steps are carried out.
|
||||
*/
|
||||
[[nodiscard]] bool js::CreateAlgorithmFromUnderlyingMethod(
|
||||
JSContext* cx, Handle<Value> underlyingObject,
|
||||
const char* methodNameForErrorMessage, Handle<PropertyName*> methodName,
|
||||
MutableHandle<Value> method) {
|
||||
cx->check(underlyingObject);
|
||||
cx->check(methodName);
|
||||
cx->check(method);
|
||||
|
||||
// Step 1: Assert: underlyingObject is not undefined.
|
||||
MOZ_ASSERT(!underlyingObject.isUndefined());
|
||||
|
||||
// Step 2: Assert: ! IsPropertyKey(methodName) is true (implicit).
|
||||
// Step 3: Assert: algoArgCount is 0 or 1 (omitted).
|
||||
// Step 4: Assert: extraArgs is a List (omitted).
|
||||
|
||||
// Step 5: Let method be ? GetV(underlyingObject, methodName).
|
||||
if (!GetProperty(cx, underlyingObject, methodName, method)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 6: If method is not undefined,
|
||||
if (!method.isUndefined()) {
|
||||
// Step a: If ! IsCallable(method) is false, throw a TypeError
|
||||
// exception.
|
||||
if (!IsCallable(method)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_NOT_FUNCTION, methodNameForErrorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step b: If algoArgCount is 0, return an algorithm that performs the
|
||||
// following steps:
|
||||
// Step i: Return ! PromiseCall(method, underlyingObject,
|
||||
// extraArgs).
|
||||
// Step c: Otherwise, return an algorithm that performs the following
|
||||
// steps, taking an arg argument:
|
||||
// Step i: Let fullArgs be a List consisting of arg followed by the
|
||||
// elements of extraArgs in order.
|
||||
// Step ii: Return ! PromiseCall(method, underlyingObject,
|
||||
// fullArgs).
|
||||
// (These steps are deferred to the code that performs the algorithm.
|
||||
// See Perform{Write,Close}Algorithm, ReadableStreamControllerCancelSteps,
|
||||
// and ReadableStreamControllerCallPullIfNeeded.)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 7: Return an algorithm which returns a promise resolved with
|
||||
// undefined (implicit).
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 6.3.2. InvokeOrNoop ( O, P, args )
|
||||
* As it happens, all callers pass exactly one argument.
|
||||
*/
|
||||
[[nodiscard]] bool js::InvokeOrNoop(JSContext* cx, Handle<Value> O,
|
||||
Handle<PropertyName*> P, Handle<Value> arg,
|
||||
MutableHandle<Value> rval) {
|
||||
cx->check(O, P, arg);
|
||||
|
||||
// Step 1: Assert: O is not undefined.
|
||||
MOZ_ASSERT(!O.isUndefined());
|
||||
|
||||
// Step 2: Assert: ! IsPropertyKey(P) is true (implicit).
|
||||
// Step 3: Assert: args is a List (implicit).
|
||||
// Step 4: Let method be ? GetV(O, P).
|
||||
Rooted<Value> method(cx);
|
||||
if (!GetProperty(cx, O, P, &method)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 5: If method is undefined, return.
|
||||
if (method.isUndefined()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 6: Return ? Call(method, O, args).
|
||||
return Call(cx, method, O, arg, rval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 6.3.7. ValidateAndNormalizeHighWaterMark ( highWaterMark )
|
||||
*/
|
||||
[[nodiscard]] bool js::ValidateAndNormalizeHighWaterMark(
|
||||
JSContext* cx, Handle<Value> highWaterMarkVal, double* highWaterMark) {
|
||||
// Step 1: Set highWaterMark to ? ToNumber(highWaterMark).
|
||||
if (!ToNumber(cx, highWaterMarkVal, highWaterMark)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: If highWaterMark is NaN or highWaterMark < 0, throw a RangeError
|
||||
// exception.
|
||||
if (mozilla::IsNaN(*highWaterMark) || *highWaterMark < 0) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_STREAM_INVALID_HIGHWATERMARK);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Return highWaterMark.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 6.3.8. MakeSizeAlgorithmFromSizeFunction ( size )
|
||||
*
|
||||
* The standard makes a big deal of turning JavaScript functions (grubby,
|
||||
* touched by users, covered with germs) into algorithms (pristine,
|
||||
* respectable, purposeful). We don't bother. Here we only check for errors and
|
||||
* leave `size` unchanged. Then, in ReadableStreamDefaultControllerEnqueue and
|
||||
* WritableStreamDefaultControllerGetChunkSize where this value is used, we
|
||||
* check for undefined and behave as if we had "made" an "algorithm" for it.
|
||||
*/
|
||||
[[nodiscard]] bool js::MakeSizeAlgorithmFromSizeFunction(JSContext* cx,
|
||||
Handle<Value> size) {
|
||||
cx->check(size);
|
||||
|
||||
// Step 1: If size is undefined, return an algorithm that returns 1.
|
||||
if (size.isUndefined()) {
|
||||
// Deferred. Size algorithm users must check for undefined.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 2: If ! IsCallable(size) is false, throw a TypeError exception.
|
||||
if (!IsCallable(size)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION,
|
||||
"ReadableStream argument options.size");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Return an algorithm that performs the following steps, taking a
|
||||
// chunk argument:
|
||||
// a. Return ? Call(size, undefined, « chunk »).
|
||||
// Deferred. Size algorithm users must know how to call the size function.
|
||||
return true;
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Miscellaneous operations. */
|
||||
|
||||
#ifndef builtin_streams_MiscellaneousOperations_h
|
||||
#define builtin_streams_MiscellaneousOperations_h
|
||||
|
||||
#include "jstypes.h" // JS_PUBLIC_API
|
||||
#include "js/CallArgs.h" // JS::CallArgs
|
||||
#include "js/RootingAPI.h" // JS::{,Mutable}Handle
|
||||
#include "js/Value.h" // JS::Value
|
||||
#include "vm/JSObject.h" // JSObject
|
||||
#include "vm/PromiseObject.h" // js::PromiseObject
|
||||
|
||||
struct JS_PUBLIC_API JSContext;
|
||||
|
||||
namespace js {
|
||||
|
||||
class PropertyName;
|
||||
|
||||
[[nodiscard]] extern PromiseObject* PromiseRejectedWithPendingError(
|
||||
JSContext* cx);
|
||||
|
||||
[[nodiscard]] inline bool ReturnPromiseRejectedWithPendingError(
|
||||
JSContext* cx, const JS::CallArgs& args) {
|
||||
PromiseObject* promise = PromiseRejectedWithPendingError(cx);
|
||||
if (!promise) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*promise);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 6.3.1.
|
||||
* CreateAlgorithmFromUnderlyingMethod ( underlyingObject, methodName,
|
||||
* algoArgCount, extraArgs )
|
||||
*
|
||||
* This function only partly implements the standard algorithm. We do not
|
||||
* actually create a new JSFunction completely encapsulating the new algorithm.
|
||||
* Instead, this just gets the specified method and checks for errors. It's the
|
||||
* caller's responsibility to make sure that later, when the algorithm is
|
||||
* "performed", the appropriate steps are carried out.
|
||||
*/
|
||||
[[nodiscard]] extern bool CreateAlgorithmFromUnderlyingMethod(
|
||||
JSContext* cx, JS::Handle<JS::Value> underlyingObject,
|
||||
const char* methodNameForErrorMessage, JS::Handle<PropertyName*> methodName,
|
||||
JS::MutableHandle<JS::Value> method);
|
||||
|
||||
/**
|
||||
* Streams spec, 6.3.2. InvokeOrNoop ( O, P, args )
|
||||
* As it happens, all callers pass exactly one argument.
|
||||
*/
|
||||
[[nodiscard]] extern bool InvokeOrNoop(JSContext* cx, JS::Handle<JS::Value> O,
|
||||
JS::Handle<PropertyName*> P,
|
||||
JS::Handle<JS::Value> arg,
|
||||
JS::MutableHandle<JS::Value> rval);
|
||||
|
||||
/**
|
||||
* Streams spec, 6.3.7. ValidateAndNormalizeHighWaterMark ( highWaterMark )
|
||||
*/
|
||||
[[nodiscard]] extern bool ValidateAndNormalizeHighWaterMark(
|
||||
JSContext* cx, JS::Handle<JS::Value> highWaterMarkVal,
|
||||
double* highWaterMark);
|
||||
|
||||
/**
|
||||
* Streams spec, 6.3.8. MakeSizeAlgorithmFromSizeFunction ( size )
|
||||
*/
|
||||
[[nodiscard]] extern bool MakeSizeAlgorithmFromSizeFunction(
|
||||
JSContext* cx, JS::Handle<JS::Value> size);
|
||||
|
||||
template <class T>
|
||||
inline bool IsMaybeWrapped(const JS::Handle<JS::Value> v) {
|
||||
return v.isObject() && v.toObject().canUnwrapAs<T>();
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_MiscellaneousOperations_h
|
|
@ -1,48 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Pull descriptor objects for tracking byte stream pull-into requests. */
|
||||
|
||||
#include "builtin/streams/PullIntoDescriptor.h"
|
||||
|
||||
#include <stdint.h> // uint32_t
|
||||
|
||||
#include "js/Class.h" // JSClass, JSCLASS_HAS_RESERVED_SLOTS
|
||||
#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
|
||||
|
||||
#include "vm/JSObject-inl.h" // js::NewBuiltinClassInstance
|
||||
|
||||
using js::PullIntoDescriptor;
|
||||
|
||||
using JS::Handle;
|
||||
using JS::Int32Value;
|
||||
using JS::ObjectOrNullValue;
|
||||
using JS::ObjectValue;
|
||||
using JS::Rooted;
|
||||
|
||||
/* static */ PullIntoDescriptor* PullIntoDescriptor::create(
|
||||
JSContext* cx, Handle<ArrayBufferObject*> buffer, uint32_t byteOffset,
|
||||
uint32_t byteLength, uint32_t bytesFilled, uint32_t elementSize,
|
||||
Handle<JSObject*> ctor, ReaderType readerType) {
|
||||
Rooted<PullIntoDescriptor*> descriptor(
|
||||
cx, NewBuiltinClassInstance<PullIntoDescriptor>(cx));
|
||||
if (!descriptor) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
descriptor->setFixedSlot(Slot_buffer, ObjectValue(*buffer));
|
||||
descriptor->setFixedSlot(Slot_Ctor, ObjectOrNullValue(ctor));
|
||||
descriptor->setFixedSlot(Slot_ByteOffset, Int32Value(byteOffset));
|
||||
descriptor->setFixedSlot(Slot_ByteLength, Int32Value(byteLength));
|
||||
descriptor->setFixedSlot(Slot_BytesFilled, Int32Value(bytesFilled));
|
||||
descriptor->setFixedSlot(Slot_ElementSize, Int32Value(elementSize));
|
||||
descriptor->setFixedSlot(Slot_ReaderType,
|
||||
Int32Value(static_cast<int32_t>(readerType)));
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
const JSClass PullIntoDescriptor::class_ = {
|
||||
"PullIntoDescriptor", JSCLASS_HAS_RESERVED_SLOTS(SlotCount)};
|
|
@ -1,77 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Pull descriptor objects for tracking byte stream pull-into requests. */
|
||||
|
||||
#ifndef builtin_streams_PullIntoDescriptor_h
|
||||
#define builtin_streams_PullIntoDescriptor_h
|
||||
|
||||
#include <stdint.h> // int32_t, uint32_t
|
||||
|
||||
#include "js/Class.h" // JSClass
|
||||
#include "vm/ArrayBufferObject.h" // js::ArrayBufferObject;
|
||||
#include "vm/NativeObject.h" // js::NativeObject
|
||||
|
||||
namespace js {
|
||||
|
||||
enum class ReaderType : int32_t { Default = 0, BYOB = 1 };
|
||||
|
||||
class PullIntoDescriptor : public NativeObject {
|
||||
private:
|
||||
enum Slots {
|
||||
Slot_buffer,
|
||||
Slot_ByteOffset,
|
||||
Slot_ByteLength,
|
||||
Slot_BytesFilled,
|
||||
Slot_ElementSize,
|
||||
Slot_Ctor,
|
||||
Slot_ReaderType,
|
||||
SlotCount
|
||||
};
|
||||
|
||||
public:
|
||||
static const JSClass class_;
|
||||
|
||||
ArrayBufferObject* buffer() {
|
||||
return &getFixedSlot(Slot_buffer).toObject().as<ArrayBufferObject>();
|
||||
}
|
||||
void setBuffer(ArrayBufferObject* buffer) {
|
||||
setFixedSlot(Slot_buffer, ObjectValue(*buffer));
|
||||
}
|
||||
JSObject* ctor() { return getFixedSlot(Slot_Ctor).toObjectOrNull(); }
|
||||
uint32_t byteOffset() const {
|
||||
return getFixedSlot(Slot_ByteOffset).toInt32();
|
||||
}
|
||||
uint32_t byteLength() const {
|
||||
return getFixedSlot(Slot_ByteLength).toInt32();
|
||||
}
|
||||
uint32_t bytesFilled() const {
|
||||
return getFixedSlot(Slot_BytesFilled).toInt32();
|
||||
}
|
||||
void setBytesFilled(int32_t bytes) {
|
||||
setFixedSlot(Slot_BytesFilled, Int32Value(bytes));
|
||||
}
|
||||
uint32_t elementSize() const {
|
||||
return getFixedSlot(Slot_ElementSize).toInt32();
|
||||
}
|
||||
ReaderType readerType() const {
|
||||
int32_t n = getFixedSlot(Slot_ReaderType).toInt32();
|
||||
MOZ_ASSERT(n == int32_t(ReaderType::Default) ||
|
||||
n == int32_t(ReaderType::BYOB));
|
||||
return ReaderType(n);
|
||||
}
|
||||
|
||||
static PullIntoDescriptor* create(JSContext* cx,
|
||||
JS::Handle<ArrayBufferObject*> buffer,
|
||||
uint32_t byteOffset, uint32_t byteLength,
|
||||
uint32_t bytesFilled, uint32_t elementSize,
|
||||
JS::Handle<JSObject*> ctor,
|
||||
ReaderType readerType);
|
||||
};
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_PullIntoDescriptor_h
|
|
@ -1,77 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Queue-with-sizes operations. */
|
||||
|
||||
#ifndef builtin_streams_QueueWithSizes_inl_h
|
||||
#define builtin_streams_QueueWithSizes_inl_h
|
||||
|
||||
#include "builtin/streams/QueueWithSizes.h"
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
|
||||
#include "js/RootingAPI.h" // JS::Handle
|
||||
#include "js/Value.h" // JS::Value
|
||||
#include "vm/List.h" // js::ListObject
|
||||
|
||||
#include "vm/List-inl.h" // js::ListObject::*
|
||||
|
||||
struct JS_PUBLIC_API JSContext;
|
||||
|
||||
namespace js {
|
||||
|
||||
namespace detail {
|
||||
|
||||
// The *internal* representation of a queue-with-sizes is a List of even length
|
||||
// where elements (2 * n, 2 * n + 1) represent the nth (value, size) element in
|
||||
// the queue.
|
||||
|
||||
inline JS::Value QueueFirstValue(ListObject* unwrappedQueue) {
|
||||
MOZ_ASSERT(!unwrappedQueue->isEmpty(),
|
||||
"can't examine first value in an empty queue-with-sizes");
|
||||
MOZ_ASSERT((unwrappedQueue->length() % 2) == 0,
|
||||
"queue-with-sizes must consist of (value, size) element pairs and "
|
||||
"so must have even length");
|
||||
return unwrappedQueue->get(0);
|
||||
}
|
||||
|
||||
inline double QueueFirstSize(ListObject* unwrappedQueue) {
|
||||
MOZ_ASSERT(!unwrappedQueue->isEmpty(),
|
||||
"can't examine first value in an empty queue-with-sizes");
|
||||
MOZ_ASSERT((unwrappedQueue->length() % 2) == 0,
|
||||
"queue-with-sizes must consist of (value, size) element pairs and "
|
||||
"so must have even length");
|
||||
return unwrappedQueue->get(1).toDouble();
|
||||
}
|
||||
|
||||
inline void QueueRemoveFirstValueAndSize(ListObject* unwrappedQueue,
|
||||
JSContext* cx) {
|
||||
MOZ_ASSERT(!unwrappedQueue->isEmpty(),
|
||||
"can't remove first value from an empty queue-with-sizes");
|
||||
MOZ_ASSERT((unwrappedQueue->length() % 2) == 0,
|
||||
"queue-with-sizes must consist of (value, size) element pairs and "
|
||||
"so must have even length");
|
||||
unwrappedQueue->popFirstPair(cx);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool QueueAppendValueAndSize(
|
||||
JSContext* cx, JS::Handle<ListObject*> unwrappedQueue,
|
||||
JS::Handle<JS::Value> value, double size) {
|
||||
return unwrappedQueue->appendValueAndSize(cx, value, size);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Streams spec, 6.2.3. PeekQueueValue ( container ) nothrow
|
||||
*/
|
||||
inline JS::Value PeekQueueValue(ListObject* queue) {
|
||||
return detail::QueueFirstValue(queue);
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_QueueWithSizes_inl_h
|
|
@ -1,171 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Queue-with-sizes operations. */
|
||||
|
||||
#include "builtin/streams/QueueWithSizes-inl.h"
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
#include "mozilla/FloatingPoint.h" // mozilla::Is{Infinite,NaN}
|
||||
|
||||
#include "builtin/streams/StreamController.h" // js::StreamController
|
||||
#include "js/Class.h" // JSClass, JSCLASS_HAS_RESERVED_SLOTS
|
||||
#include "js/Conversions.h" // JS::ToNumber
|
||||
#include "js/ErrorReport.h" // JS_ReportErrorNumberASCII
|
||||
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
|
||||
#include "js/RootingAPI.h" // JS::Rooted
|
||||
#include "js/Value.h" // JS::Value, JS::{Number,Object}Value
|
||||
#include "vm/Compartment.h" // JSCompartment
|
||||
#include "vm/JSContext.h" // JSContext
|
||||
#include "vm/List.h" // js::ListObject
|
||||
#include "vm/NativeObject.h" // js::NativeObject
|
||||
|
||||
#include "vm/Compartment-inl.h" // JSCompartment::wrap
|
||||
#include "vm/JSContext-inl.h" // JSContext::check
|
||||
#include "vm/JSObject-inl.h" // js::NewBuiltinClassInstance
|
||||
#include "vm/List-inl.h" // js::ListObject::*, js::StoreNewListInFixedSlot
|
||||
#include "vm/Realm-inl.h" // js::AutoRealm
|
||||
|
||||
using JS::Handle;
|
||||
using JS::MutableHandle;
|
||||
using JS::NumberValue;
|
||||
using JS::ObjectValue;
|
||||
using JS::Rooted;
|
||||
using JS::ToNumber;
|
||||
using JS::Value;
|
||||
|
||||
/*** 6.2. Queue-with-sizes operations ***************************************/
|
||||
|
||||
/**
|
||||
* Streams spec, 6.2.1. DequeueValue ( container ) nothrow
|
||||
*/
|
||||
[[nodiscard]] bool js::DequeueValue(
|
||||
JSContext* cx, Handle<StreamController*> unwrappedContainer,
|
||||
MutableHandle<Value> chunk) {
|
||||
// Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
|
||||
// slots (implicit).
|
||||
// Step 2: Assert: queue is not empty.
|
||||
Rooted<ListObject*> unwrappedQueue(cx, unwrappedContainer->queue());
|
||||
|
||||
// Step 3. Let pair be the first element of queue.
|
||||
double chunkSize = detail::QueueFirstSize(unwrappedQueue);
|
||||
chunk.set(detail::QueueFirstValue(unwrappedQueue));
|
||||
|
||||
// Step 4. Remove pair from queue, shifting all other elements downward
|
||||
// (so that the second becomes the first, and so on).
|
||||
detail::QueueRemoveFirstValueAndSize(unwrappedQueue, cx);
|
||||
|
||||
// Step 5: Set container.[[queueTotalSize]] to
|
||||
// container.[[queueTotalSize]] − pair.[[size]].
|
||||
// Step 6: If container.[[queueTotalSize]] < 0, set
|
||||
// container.[[queueTotalSize]] to 0.
|
||||
// (This can occur due to rounding errors.)
|
||||
double totalSize = unwrappedContainer->queueTotalSize();
|
||||
totalSize -= chunkSize;
|
||||
if (totalSize < 0) {
|
||||
totalSize = 0;
|
||||
}
|
||||
unwrappedContainer->setQueueTotalSize(totalSize);
|
||||
|
||||
// Step 7: Return pair.[[value]].
|
||||
return cx->compartment()->wrap(cx, chunk);
|
||||
}
|
||||
|
||||
void js::DequeueValue(StreamController* unwrappedContainer, JSContext* cx) {
|
||||
// Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
|
||||
// slots (implicit).
|
||||
// Step 2: Assert: queue is not empty.
|
||||
ListObject* unwrappedQueue = unwrappedContainer->queue();
|
||||
|
||||
// Step 3. Let pair be the first element of queue.
|
||||
// (The value is being discarded, so all we must extract is the size.)
|
||||
double chunkSize = detail::QueueFirstSize(unwrappedQueue);
|
||||
|
||||
// Step 4. Remove pair from queue, shifting all other elements downward
|
||||
// (so that the second becomes the first, and so on).
|
||||
detail::QueueRemoveFirstValueAndSize(unwrappedQueue, cx);
|
||||
|
||||
// Step 5: Set container.[[queueTotalSize]] to
|
||||
// container.[[queueTotalSize]] − pair.[[size]].
|
||||
// Step 6: If container.[[queueTotalSize]] < 0, set
|
||||
// container.[[queueTotalSize]] to 0.
|
||||
// (This can occur due to rounding errors.)
|
||||
double totalSize = unwrappedContainer->queueTotalSize();
|
||||
totalSize -= chunkSize;
|
||||
if (totalSize < 0) {
|
||||
totalSize = 0;
|
||||
}
|
||||
unwrappedContainer->setQueueTotalSize(totalSize);
|
||||
|
||||
// Step 7: Return pair.[[value]]. (omitted because not used)
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 6.2.2. EnqueueValueWithSize ( container, value, size ) throws
|
||||
*/
|
||||
[[nodiscard]] bool js::EnqueueValueWithSize(
|
||||
JSContext* cx, Handle<StreamController*> unwrappedContainer,
|
||||
Handle<Value> value, Handle<Value> sizeVal) {
|
||||
cx->check(value, sizeVal);
|
||||
|
||||
// Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
|
||||
// slots (implicit).
|
||||
// Step 2: Let size be ? ToNumber(size).
|
||||
double size;
|
||||
if (!ToNumber(cx, sizeVal, &size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: If ! IsFiniteNonNegativeNumber(size) is false, throw a RangeError
|
||||
// exception.
|
||||
if (size < 0 || mozilla::IsNaN(size) || mozilla::IsInfinite(size)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, "size");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 4: Append Record {[[value]]: value, [[size]]: size} as the last
|
||||
// element of container.[[queue]].
|
||||
{
|
||||
AutoRealm ar(cx, unwrappedContainer);
|
||||
Rooted<ListObject*> unwrappedQueue(cx, unwrappedContainer->queue());
|
||||
Rooted<Value> wrappedVal(cx, value);
|
||||
if (!cx->compartment()->wrap(cx, &wrappedVal)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!detail::QueueAppendValueAndSize(cx, unwrappedQueue, wrappedVal,
|
||||
size)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Set container.[[queueTotalSize]] to
|
||||
// container.[[queueTotalSize]] + size.
|
||||
unwrappedContainer->setQueueTotalSize(unwrappedContainer->queueTotalSize() +
|
||||
size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 6.2.4. ResetQueue ( container ) nothrow
|
||||
*/
|
||||
[[nodiscard]] bool js::ResetQueue(
|
||||
JSContext* cx, Handle<StreamController*> unwrappedContainer) {
|
||||
// Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
|
||||
// slots (implicit).
|
||||
// Step 2: Set container.[[queue]] to a new empty List.
|
||||
if (!StoreNewListInFixedSlot(cx, unwrappedContainer,
|
||||
StreamController::Slot_Queue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Set container.[[queueTotalSize]] to 0.
|
||||
unwrappedContainer->setQueueTotalSize(0);
|
||||
|
||||
return true;
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Queue-with-sizes operations. */
|
||||
|
||||
#ifndef builtin_streams_QueueWithSizes_h
|
||||
#define builtin_streams_QueueWithSizes_h
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
|
||||
#include "jstypes.h" // JS_PUBLIC_API
|
||||
#include "js/RootingAPI.h" // JS::{,Mutable}Handle
|
||||
#include "js/Value.h" // JS::Value
|
||||
#include "vm/List.h" // js::ListObject
|
||||
|
||||
struct JS_PUBLIC_API JSContext;
|
||||
|
||||
namespace js {
|
||||
|
||||
class StreamController;
|
||||
|
||||
/**
|
||||
* Streams spec, 6.2.1. DequeueValue ( container ) nothrow
|
||||
*/
|
||||
[[nodiscard]] extern bool DequeueValue(
|
||||
JSContext* cx, JS::Handle<StreamController*> unwrappedContainer,
|
||||
JS::MutableHandle<JS::Value> chunk);
|
||||
|
||||
/**
|
||||
* Streams spec, 6.2.1. DequeueValue ( container ) nothrow
|
||||
* when the dequeued value is ignored.
|
||||
*/
|
||||
extern void DequeueValue(StreamController* unwrappedContainer, JSContext* cx);
|
||||
|
||||
/**
|
||||
* Streams spec, 6.2.2. EnqueueValueWithSize ( container, value, size ) throws
|
||||
*/
|
||||
[[nodiscard]] extern bool EnqueueValueWithSize(
|
||||
JSContext* cx, JS::Handle<StreamController*> unwrappedContainer,
|
||||
JS::Handle<JS::Value> value, JS::Handle<JS::Value> sizeVal);
|
||||
|
||||
/**
|
||||
* Streams spec, 6.2.4. ResetQueue ( container ) nothrow
|
||||
*/
|
||||
[[nodiscard]] extern bool ResetQueue(
|
||||
JSContext* cx, JS::Handle<StreamController*> unwrappedContainer);
|
||||
|
||||
inline bool QueueIsEmpty(ListObject* unwrappedQueue) {
|
||||
if (unwrappedQueue->isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_ASSERT((unwrappedQueue->length() % 2) == 0,
|
||||
"queue-with-sizes must consist of (value, size) element pairs and "
|
||||
"so must have even length");
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_QueueWithSizes_h
|
|
@ -1,231 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Queuing strategies. */
|
||||
|
||||
#include "builtin/streams/QueueingStrategies.h"
|
||||
|
||||
#include "builtin/streams/ClassSpecMacro.h" // JS_STREAMS_CLASS_SPEC
|
||||
#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
|
||||
#include "js/Class.h" // JS::ObjectOpResult, JS_NULL_CLASS_OPS
|
||||
#include "js/Conversions.h" // JS::ToNumber
|
||||
#include "js/PropertySpec.h" // JS{Property,Function}Spec, JS_FN, JS_FS_END, JS_PS_END
|
||||
#include "js/ProtoKey.h" // JSProto_{ByteLength,Count}QueuingStrategy
|
||||
#include "js/RootingAPI.h" // JS::{Handle,Rooted}
|
||||
#include "vm/Interpreter.h" // js::GetProperty
|
||||
#include "vm/JSObject.h" // js::GetPrototypeFromBuiltinConstructor
|
||||
#include "vm/ObjectOperations.h" // js::{Define,Get}Property
|
||||
#include "vm/Runtime.h" // JSAtomState
|
||||
#include "vm/StringType.h" // js::NameToId, PropertyName
|
||||
|
||||
#include "vm/Compartment-inl.h" // js::UnwrapAndTypeCheckThis
|
||||
#include "vm/JSObject-inl.h" // js::NewObjectWithClassProto
|
||||
#include "vm/NativeObject-inl.h" // js::ThrowIfNotConstructing
|
||||
|
||||
using js::ByteLengthQueuingStrategy;
|
||||
using js::CountQueuingStrategy;
|
||||
using js::PropertyName;
|
||||
using js::UnwrapAndTypeCheckThis;
|
||||
|
||||
using JS::CallArgs;
|
||||
using JS::CallArgsFromVp;
|
||||
using JS::Handle;
|
||||
using JS::ObjectOpResult;
|
||||
using JS::Rooted;
|
||||
using JS::ToNumber;
|
||||
using JS::ToObject;
|
||||
using JS::Value;
|
||||
|
||||
/*** 6.1. Queuing strategies ************************************************/
|
||||
|
||||
// Streams spec, 6.1.2.2. new ByteLengthQueuingStrategy({ highWaterMark })
|
||||
bool js::ByteLengthQueuingStrategy::constructor(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
if (!ThrowIfNotConstructing(cx, args, "ByteLengthQueuingStrategy")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Implicit in the spec: Create the new strategy object.
|
||||
Rooted<JSObject*> proto(cx);
|
||||
if (!GetPrototypeFromBuiltinConstructor(
|
||||
cx, args, JSProto_ByteLengthQueuingStrategy, &proto)) {
|
||||
return false;
|
||||
}
|
||||
Rooted<ByteLengthQueuingStrategy*> strategy(
|
||||
cx, NewObjectWithClassProto<ByteLengthQueuingStrategy>(cx, proto));
|
||||
if (!strategy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Implicit in the spec: Argument destructuring.
|
||||
RootedObject argObj(cx, ToObject(cx, args.get(0)));
|
||||
if (!argObj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://heycam.github.io/webidl/#es-dictionary
|
||||
// 3.2.17. Dictionary types
|
||||
// Step 4.1.2: Let esMemberValue be an ECMAScript value,
|
||||
// depending on Type(esDict): ? Get(esDict, key)
|
||||
RootedValue highWaterMarkV(cx);
|
||||
if (!GetProperty(cx, argObj, argObj, cx->names().highWaterMark,
|
||||
&highWaterMarkV)) {
|
||||
return false;
|
||||
}
|
||||
// Step 4.1.5: Otherwise, if esMemberValue is undefined and
|
||||
// member is required, then throw a TypeError.
|
||||
if (highWaterMarkV.isUndefined()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_STREAM_MISSING_HIGHWATERMARK);
|
||||
return false;
|
||||
}
|
||||
// Step 4.1.3: If esMemberValue is not undefined, then:
|
||||
// Let idlMemberValue be the result of converting esMemberValue to
|
||||
// an IDL value whose type is the type member is declared to be of.
|
||||
double highWaterMark;
|
||||
if (!ToNumber(cx, highWaterMarkV, &highWaterMark)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 1: Set this.[[highWaterMark]] to init["highWaterMark"].
|
||||
strategy->setHighWaterMark(highWaterMark);
|
||||
|
||||
args.rval().setObject(*strategy);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ByteLengthQueuingStrategy_highWaterMark(JSContext* cx,
|
||||
unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
Rooted<ByteLengthQueuingStrategy*> unwrappedStrategy(
|
||||
cx, UnwrapAndTypeCheckThis<ByteLengthQueuingStrategy>(
|
||||
cx, args, "get highWaterMark"));
|
||||
if (!unwrappedStrategy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 1: Return this.[[highWaterMark]].
|
||||
args.rval().setDouble(unwrappedStrategy->highWaterMark());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Streams spec 6.1.2.3.1. size ( chunk )
|
||||
static bool ByteLengthQueuingStrategy_size(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: Return ? GetV(chunk, "byteLength").
|
||||
return GetProperty(cx, args.get(0), cx->names().byteLength, args.rval());
|
||||
}
|
||||
|
||||
static const JSPropertySpec ByteLengthQueuingStrategy_properties[] = {
|
||||
JS_PSG("highWaterMark", ByteLengthQueuingStrategy_highWaterMark,
|
||||
JSPROP_ENUMERATE),
|
||||
JS_STRING_SYM_PS(toStringTag, "ByteLengthQueuingStrategy", JSPROP_READONLY),
|
||||
JS_PS_END};
|
||||
|
||||
static const JSFunctionSpec ByteLengthQueuingStrategy_methods[] = {
|
||||
JS_FN("size", ByteLengthQueuingStrategy_size, 1, 0), JS_FS_END};
|
||||
|
||||
JS_STREAMS_CLASS_SPEC(ByteLengthQueuingStrategy, 1, SlotCount, 0, 0,
|
||||
JS_NULL_CLASS_OPS);
|
||||
|
||||
// Streams spec, 6.1.3.2. new CountQueuingStrategy({ highWaterMark })
|
||||
bool js::CountQueuingStrategy::constructor(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
if (!ThrowIfNotConstructing(cx, args, "CountQueuingStrategy")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Implicit in the spec: Create the new strategy object.
|
||||
RootedObject proto(cx);
|
||||
if (!GetPrototypeFromBuiltinConstructor(
|
||||
cx, args, JSProto_CountQueuingStrategy, &proto)) {
|
||||
return false;
|
||||
}
|
||||
Rooted<CountQueuingStrategy*> strategy(
|
||||
cx, NewObjectWithClassProto<CountQueuingStrategy>(cx, proto));
|
||||
if (!strategy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Implicit in the spec: Argument destructuring.
|
||||
RootedObject argObj(cx, ToObject(cx, args.get(0)));
|
||||
if (!argObj) {
|
||||
return false;
|
||||
}
|
||||
// https://heycam.github.io/webidl/#es-dictionary
|
||||
// 3.2.17. Dictionary types
|
||||
// Step 4.1.2: Let esMemberValue be an ECMAScript value,
|
||||
// depending on Type(esDict): ? Get(esDict, key)
|
||||
RootedValue highWaterMarkV(cx);
|
||||
if (!GetProperty(cx, argObj, argObj, cx->names().highWaterMark,
|
||||
&highWaterMarkV)) {
|
||||
return false;
|
||||
}
|
||||
// Step 4.1.5: Otherwise, if esMemberValue is undefined and
|
||||
// member is required, then throw a TypeError.
|
||||
if (highWaterMarkV.isUndefined()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_STREAM_MISSING_HIGHWATERMARK);
|
||||
return false;
|
||||
}
|
||||
// Step 4.1.3: If esMemberValue is not undefined, then:
|
||||
// Let idlMemberValue be the result of converting esMemberValue to
|
||||
// an IDL value whose type is the type member is declared to be of.
|
||||
double highWaterMark;
|
||||
if (!ToNumber(cx, highWaterMarkV, &highWaterMark)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 1: Set this.[[highWaterMark]] to init["highWaterMark"].
|
||||
strategy->setHighWaterMark(highWaterMark);
|
||||
|
||||
args.rval().setObject(*strategy);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool CountQueuingStrategy_highWaterMark(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
Rooted<CountQueuingStrategy*> unwrappedStrategy(
|
||||
cx, UnwrapAndTypeCheckThis<CountQueuingStrategy>(cx, args,
|
||||
"get highWaterMark"));
|
||||
if (!unwrappedStrategy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 1: Return this.[[highWaterMark]].
|
||||
args.rval().setDouble(unwrappedStrategy->highWaterMark());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Streams spec 6.1.3.3.1. size ( chunk )
|
||||
static bool CountQueuingStrategy_size(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: Return 1.
|
||||
args.rval().setInt32(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
static const JSPropertySpec CountQueuingStrategy_properties[] = {
|
||||
JS_PSG("highWaterMark", CountQueuingStrategy_highWaterMark,
|
||||
JSPROP_ENUMERATE),
|
||||
JS_STRING_SYM_PS(toStringTag, "CountQueuingStrategy", JSPROP_READONLY),
|
||||
JS_PS_END};
|
||||
|
||||
static const JSFunctionSpec CountQueuingStrategy_methods[] = {
|
||||
JS_FN("size", CountQueuingStrategy_size, 0, 0), JS_FS_END};
|
||||
|
||||
JS_STREAMS_CLASS_SPEC(CountQueuingStrategy, 1, SlotCount, 0, 0,
|
||||
JS_NULL_CLASS_OPS);
|
|
@ -1,57 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Queuing strategies. */
|
||||
|
||||
#ifndef builtin_stream_QueueingStrategies_h
|
||||
#define builtin_stream_QueueingStrategies_h
|
||||
|
||||
#include "js/Class.h" // JSClass, js::ClassSpec
|
||||
#include "js/Value.h" // JS::Value
|
||||
#include "vm/JSContext.h" // JSContext
|
||||
#include "vm/NativeObject.h" // js::NativeObject
|
||||
|
||||
namespace js {
|
||||
|
||||
class ByteLengthQueuingStrategy : public NativeObject {
|
||||
public:
|
||||
enum Slots { Slot_HighWaterMark, SlotCount };
|
||||
|
||||
double highWaterMark() const {
|
||||
return getFixedSlot(Slot_HighWaterMark).toDouble();
|
||||
}
|
||||
void setHighWaterMark(double value) {
|
||||
setFixedSlot(Slot_HighWaterMark, JS::DoubleValue(value));
|
||||
}
|
||||
|
||||
static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp);
|
||||
static const ClassSpec classSpec_;
|
||||
static const JSClass class_;
|
||||
static const ClassSpec protoClassSpec_;
|
||||
static const JSClass protoClass_;
|
||||
};
|
||||
|
||||
class CountQueuingStrategy : public NativeObject {
|
||||
public:
|
||||
enum Slots { Slot_HighWaterMark, SlotCount };
|
||||
|
||||
double highWaterMark() const {
|
||||
return getFixedSlot(Slot_HighWaterMark).toDouble();
|
||||
}
|
||||
void setHighWaterMark(double value) {
|
||||
setFixedSlot(Slot_HighWaterMark, JS::DoubleValue(value));
|
||||
}
|
||||
|
||||
static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp);
|
||||
static const ClassSpec classSpec_;
|
||||
static const JSClass class_;
|
||||
static const ClassSpec protoClassSpec_;
|
||||
static const JSClass protoClass_;
|
||||
};
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_stream_QueueingStrategies_h
|
|
@ -1,439 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Class ReadableStream. */
|
||||
|
||||
#include "builtin/streams/ReadableStream.h"
|
||||
|
||||
#include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Some
|
||||
|
||||
#include "jspubtd.h" // JSProto_ReadableStream
|
||||
|
||||
#include "builtin/Array.h" // js::NewDenseFullyAllocatedArray
|
||||
#include "builtin/streams/ClassSpecMacro.h" // JS_STREAMS_CLASS_SPEC
|
||||
#include "builtin/streams/MiscellaneousOperations.h" // js::MakeSizeAlgorithmFromSizeFunction, js::ValidateAndNormalizeHighWaterMark, js::ReturnPromiseRejectedWithPendingError
|
||||
#include "builtin/streams/ReadableStreamController.h" // js::ReadableStream{,Default}Controller, js::ReadableByteStreamController
|
||||
#include "builtin/streams/ReadableStreamDefaultControllerOperations.h" // js::SetUpReadableStreamDefaultControllerFromUnderlyingSource
|
||||
#include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStreamCancel
|
||||
#include "builtin/streams/ReadableStreamOperations.h" // js::ReadableStream{PipeTo,Tee}
|
||||
#include "builtin/streams/ReadableStreamReader.h" // js::CreateReadableStream{BYOB,Default}Reader, js::ForAuthorCodeBool
|
||||
#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
|
||||
#include "js/Class.h" // JSCLASS_SLOT0_IS_NSISUPPORTS, JS_NULL_CLASS_OPS
|
||||
#include "js/Conversions.h" // JS::ToBoolean
|
||||
#include "js/ErrorReport.h" // JS_ReportErrorNumberASCII
|
||||
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
|
||||
#include "js/PropertySpec.h" // JS{Function,Property}Spec, JS_FN, JS_PSG, JS_{FS,PS}_END
|
||||
#include "js/RootingAPI.h" // JS::Handle, JS::Rooted, js::CanGC
|
||||
#include "js/Stream.h" // JS::ReadableStream{Mode,UnderlyingSource}
|
||||
#include "js/Value.h" // JS::Value
|
||||
#include "vm/Interpreter.h" // js::GetProperty
|
||||
#include "vm/JSContext.h" // JSContext
|
||||
#include "vm/JSObject.h" // js::GetPrototypeFromBuiltinConstructor
|
||||
#include "vm/ObjectOperations.h" // js::GetProperty
|
||||
#include "vm/PlainObject.h" // js::PlainObject
|
||||
#include "vm/Runtime.h" // JSAtomState, JSRuntime
|
||||
#include "vm/StringType.h" // js::EqualStrings, js::ToString
|
||||
|
||||
#include "vm/Compartment-inl.h" // js::UnwrapAndTypeCheck{Argument,This,Value}
|
||||
#include "vm/JSObject-inl.h" // js::NewBuiltinClassInstance
|
||||
#include "vm/NativeObject-inl.h" // js::ThrowIfNotConstructing
|
||||
|
||||
using mozilla::Maybe;
|
||||
using mozilla::Some;
|
||||
|
||||
using js::CanGC;
|
||||
using js::ClassSpec;
|
||||
using js::CreateReadableStreamDefaultReader;
|
||||
using js::EqualStrings;
|
||||
using js::ForAuthorCodeBool;
|
||||
using js::GetErrorMessage;
|
||||
using js::NativeObject;
|
||||
using js::NewBuiltinClassInstance;
|
||||
using js::NewDenseFullyAllocatedArray;
|
||||
using js::PlainObject;
|
||||
using js::ReadableStream;
|
||||
using js::ReadableStreamTee;
|
||||
using js::ReturnPromiseRejectedWithPendingError;
|
||||
using js::ToString;
|
||||
using js::UnwrapAndTypeCheckArgument;
|
||||
using js::UnwrapAndTypeCheckThis;
|
||||
using js::UnwrapAndTypeCheckValue;
|
||||
|
||||
using JS::CallArgs;
|
||||
using JS::CallArgsFromVp;
|
||||
using JS::Handle;
|
||||
using JS::ObjectValue;
|
||||
using JS::Rooted;
|
||||
using JS::Value;
|
||||
|
||||
/*** 3.2. Class ReadableStream **********************************************/
|
||||
|
||||
JS::ReadableStreamMode ReadableStream::mode() const {
|
||||
ReadableStreamController* controller = this->controller();
|
||||
if (controller->is<ReadableStreamDefaultController>()) {
|
||||
return JS::ReadableStreamMode::Default;
|
||||
}
|
||||
return controller->as<ReadableByteStreamController>().hasExternalSource()
|
||||
? JS::ReadableStreamMode::ExternalSource
|
||||
: JS::ReadableStreamMode::Byte;
|
||||
}
|
||||
|
||||
ReadableStream* ReadableStream::createExternalSourceStream(
|
||||
JSContext* cx, JS::ReadableStreamUnderlyingSource* source,
|
||||
void* nsISupportsObject_alreadyAddreffed /* = nullptr */,
|
||||
Handle<JSObject*> proto /* = nullptr */) {
|
||||
Rooted<ReadableStream*> stream(
|
||||
cx, create(cx, nsISupportsObject_alreadyAddreffed, proto));
|
||||
if (!stream) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!SetUpExternalReadableByteStreamController(cx, stream, source)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.2.3. new ReadableStream(underlyingSource = {}, strategy = {})
|
||||
*/
|
||||
bool ReadableStream::constructor(JSContext* cx, unsigned argc, JS::Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
if (!ThrowIfNotConstructing(cx, args, "ReadableStream")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Implicit in the spec: argument default values.
|
||||
Rooted<Value> underlyingSource(cx, args.get(0));
|
||||
if (underlyingSource.isUndefined()) {
|
||||
JSObject* emptyObj = NewPlainObject(cx);
|
||||
if (!emptyObj) {
|
||||
return false;
|
||||
}
|
||||
underlyingSource = ObjectValue(*emptyObj);
|
||||
}
|
||||
|
||||
Rooted<Value> strategy(cx, args.get(1));
|
||||
if (strategy.isUndefined()) {
|
||||
JSObject* emptyObj = NewPlainObject(cx);
|
||||
if (!emptyObj) {
|
||||
return false;
|
||||
}
|
||||
strategy = ObjectValue(*emptyObj);
|
||||
}
|
||||
|
||||
// Implicit in the spec: Set this to
|
||||
// OrdinaryCreateFromConstructor(NewTarget, ...).
|
||||
// Step 1: Perform ! InitializeReadableStream(this).
|
||||
Rooted<JSObject*> proto(cx);
|
||||
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ReadableStream,
|
||||
&proto)) {
|
||||
return false;
|
||||
}
|
||||
Rooted<ReadableStream*> stream(cx,
|
||||
ReadableStream::create(cx, nullptr, proto));
|
||||
if (!stream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: Let size be ? GetV(strategy, "size").
|
||||
Rooted<Value> size(cx);
|
||||
if (!GetProperty(cx, strategy, cx->names().size, &size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Let highWaterMark be ? GetV(strategy, "highWaterMark").
|
||||
Rooted<Value> highWaterMarkVal(cx);
|
||||
if (!GetProperty(cx, strategy, cx->names().highWaterMark,
|
||||
&highWaterMarkVal)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 4: Let type be ? GetV(underlyingSource, "type").
|
||||
Rooted<Value> type(cx);
|
||||
if (!GetProperty(cx, underlyingSource, cx->names().type, &type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 5: Let typeString be ? ToString(type).
|
||||
Rooted<JSString*> typeString(cx, ToString<CanGC>(cx, type));
|
||||
if (!typeString) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 6: If typeString is "bytes",
|
||||
bool equal;
|
||||
if (!EqualStrings(cx, typeString, cx->names().bytes, &equal)) {
|
||||
return false;
|
||||
}
|
||||
if (equal) {
|
||||
// The rest of step 6 is unimplemented, since we don't support
|
||||
// user-defined byte streams yet.
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAM_BYTES_TYPE_NOT_IMPLEMENTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 7: Otherwise, if type is undefined,
|
||||
if (type.isUndefined()) {
|
||||
// Step 7.a: Let sizeAlgorithm be ? MakeSizeAlgorithmFromSizeFunction(size).
|
||||
if (!MakeSizeAlgorithmFromSizeFunction(cx, size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 7.b: If highWaterMark is undefined, let highWaterMark be 1.
|
||||
double highWaterMark;
|
||||
if (highWaterMarkVal.isUndefined()) {
|
||||
highWaterMark = 1;
|
||||
} else {
|
||||
// Step 7.c: Set highWaterMark to ?
|
||||
// ValidateAndNormalizeHighWaterMark(highWaterMark).
|
||||
if (!ValidateAndNormalizeHighWaterMark(cx, highWaterMarkVal,
|
||||
&highWaterMark)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 7.d: Perform
|
||||
// ? SetUpReadableStreamDefaultControllerFromUnderlyingSource(
|
||||
// this, underlyingSource, highWaterMark, sizeAlgorithm).
|
||||
if (!SetUpReadableStreamDefaultControllerFromUnderlyingSource(
|
||||
cx, stream, underlyingSource, highWaterMark, size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*stream);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 8: Otherwise, throw a RangeError exception.
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAM_UNDERLYINGSOURCE_TYPE_WRONG);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.2.5.1. get locked
|
||||
*/
|
||||
[[nodiscard]] static bool ReadableStream_locked(JSContext* cx, unsigned argc,
|
||||
JS::Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception.
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "get locked"));
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: Return ! IsReadableStreamLocked(this).
|
||||
args.rval().setBoolean(unwrappedStream->locked());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.2.5.2. cancel ( reason )
|
||||
*/
|
||||
[[nodiscard]] static bool ReadableStream_cancel(JSContext* cx, unsigned argc,
|
||||
JS::Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: If ! IsReadableStream(this) is false, return a promise rejected
|
||||
// with a TypeError exception.
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "cancel"));
|
||||
if (!unwrappedStream) {
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 2: If ! IsReadableStreamLocked(this) is true, return a promise
|
||||
// rejected with a TypeError exception.
|
||||
if (unwrappedStream->locked()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAM_LOCKED_METHOD, "cancel");
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 3: Return ! ReadableStreamCancel(this, reason).
|
||||
Rooted<JSObject*> cancelPromise(
|
||||
cx, js::ReadableStreamCancel(cx, unwrappedStream, args.get(0)));
|
||||
if (!cancelPromise) {
|
||||
return false;
|
||||
}
|
||||
args.rval().setObject(*cancelPromise);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Streams spec, 3.2.5.3.
|
||||
// getIterator({ preventCancel } = {})
|
||||
//
|
||||
// Not implemented.
|
||||
|
||||
/**
|
||||
* https://streams.spec.whatwg.org/#rs-get-reader
|
||||
* ReadableStreamReader getReader(optional ReadableStreamGetReaderOptions
|
||||
* options = {});
|
||||
*/
|
||||
[[nodiscard]] static bool ReadableStream_getReader(JSContext* cx, unsigned argc,
|
||||
JS::Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Implicit |this| check.
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "getReader"));
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Implicit in the spec: Dictionary destructuring.
|
||||
// https://heycam.github.io/webidl/#es-dictionary
|
||||
// 3.2.17. Dictionary types
|
||||
|
||||
Rooted<Value> optionsVal(cx, args.get(0));
|
||||
// Step 1.
|
||||
if (!optionsVal.isNullOrUndefined() && !optionsVal.isObject()) {
|
||||
ReportValueError(cx, JSMSG_CANT_CONVERT_TO, JSDVG_IGNORE_STACK, optionsVal,
|
||||
nullptr, "dictionary");
|
||||
return false;
|
||||
}
|
||||
|
||||
Maybe<JS::ReadableStreamReaderMode> mode;
|
||||
// Step 4: ...
|
||||
//
|
||||
// - Optimized for one dictionary member.
|
||||
// - Treat non-object options as non-existing "mode" member.
|
||||
if (optionsVal.isObject()) {
|
||||
Rooted<Value> modeVal(cx);
|
||||
if (!GetProperty(cx, optionsVal, cx->names().mode, &modeVal)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 4.1.3: If esMemberValue is not undefined, then: ...
|
||||
if (!modeVal.isUndefined()) {
|
||||
// https://heycam.github.io/webidl/#es-enumeration
|
||||
// 3.2.18. Enumeration types
|
||||
|
||||
// Step 1: Let S be the result of calling ToString(V).
|
||||
Rooted<JSString*> modeStr(cx, ToString<CanGC>(cx, modeVal));
|
||||
if (!modeStr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: If S is not one of E's enumeration values,
|
||||
// then throw a TypeError.
|
||||
//
|
||||
// Note: We only have one valid value "byob".
|
||||
bool equal;
|
||||
if (!EqualStrings(cx, modeStr, cx->names().byob, &equal)) {
|
||||
return false;
|
||||
}
|
||||
if (!equal) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAM_INVALID_READER_MODE);
|
||||
return false;
|
||||
}
|
||||
|
||||
mode = Some(JS::ReadableStreamReaderMode::Byob);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1: If options["mode"] does not exist,
|
||||
// return ? AcquireReadableStreamDefaultReader(this).
|
||||
Rooted<JSObject*> reader(cx);
|
||||
if (mode.isNothing()) {
|
||||
reader = CreateReadableStreamDefaultReader(cx, unwrappedStream,
|
||||
ForAuthorCodeBool::Yes);
|
||||
} else {
|
||||
// Step 2: Assert: options["mode"] is "byob".
|
||||
MOZ_ASSERT(mode.value() == JS::ReadableStreamReaderMode::Byob);
|
||||
|
||||
// Step 3: Return ? AcquireReadableStreamBYOBReader(this).
|
||||
reader = CreateReadableStreamBYOBReader(cx, unwrappedStream,
|
||||
ForAuthorCodeBool::Yes);
|
||||
}
|
||||
|
||||
if (!reader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*reader);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.2.5.7. tee()
|
||||
*/
|
||||
static bool ReadableStream_tee(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception.
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "tee"));
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: Let branches be ? ReadableStreamTee(this, false).
|
||||
Rooted<ReadableStream*> branch1(cx);
|
||||
Rooted<ReadableStream*> branch2(cx);
|
||||
if (!ReadableStreamTee(cx, unwrappedStream, false, &branch1, &branch2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Return ! CreateArrayFromList(branches).
|
||||
Rooted<NativeObject*> branches(cx, NewDenseFullyAllocatedArray(cx, 2));
|
||||
if (!branches) {
|
||||
return false;
|
||||
}
|
||||
branches->setDenseInitializedLength(2);
|
||||
branches->initDenseElement(0, ObjectValue(*branch1));
|
||||
branches->initDenseElement(1, ObjectValue(*branch2));
|
||||
|
||||
args.rval().setObject(*branches);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Streams spec, 3.2.5.8.
|
||||
// [@@asyncIterator]({ preventCancel } = {})
|
||||
//
|
||||
// Not implemented.
|
||||
|
||||
static const JSFunctionSpec ReadableStream_methods[] = {
|
||||
JS_FN("cancel", ReadableStream_cancel, 0, JSPROP_ENUMERATE),
|
||||
JS_FN("getReader", ReadableStream_getReader, 0, JSPROP_ENUMERATE),
|
||||
// pipeTo is only conditionally supported right now, so it must be manually
|
||||
// added below if desired.
|
||||
JS_FN("tee", ReadableStream_tee, 0, JSPROP_ENUMERATE), JS_FS_END};
|
||||
|
||||
static const JSPropertySpec ReadableStream_properties[] = {
|
||||
JS_PSG("locked", ReadableStream_locked, JSPROP_ENUMERATE),
|
||||
JS_STRING_SYM_PS(toStringTag, "ReadableStream", JSPROP_READONLY),
|
||||
JS_PS_END};
|
||||
|
||||
const ClassSpec ReadableStream::classSpec_ = {
|
||||
js::GenericCreateConstructor<ReadableStream::constructor, 0,
|
||||
js::gc::AllocKind::FUNCTION>,
|
||||
js::GenericCreatePrototype<ReadableStream>,
|
||||
nullptr,
|
||||
nullptr,
|
||||
ReadableStream_methods,
|
||||
ReadableStream_properties,
|
||||
nullptr,
|
||||
0};
|
||||
|
||||
const JSClass ReadableStream::class_ = {
|
||||
"ReadableStream",
|
||||
JSCLASS_HAS_RESERVED_SLOTS(ReadableStream::SlotCount) |
|
||||
JSCLASS_HAS_CACHED_PROTO(JSProto_ReadableStream) |
|
||||
JSCLASS_SLOT0_IS_NSISUPPORTS,
|
||||
JS_NULL_CLASS_OPS, &ReadableStream::classSpec_};
|
||||
|
||||
const JSClass ReadableStream::protoClass_ = {
|
||||
"ReadableStream.prototype",
|
||||
JSCLASS_HAS_CACHED_PROTO(JSProto_ReadableStream), JS_NULL_CLASS_OPS,
|
||||
&ReadableStream::classSpec_};
|
|
@ -1,144 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Class ReadableStream. */
|
||||
|
||||
#ifndef builtin_streams_ReadableStream_h
|
||||
#define builtin_streams_ReadableStream_h
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
|
||||
|
||||
#include <stdint.h> // uint32_t
|
||||
|
||||
#include "jstypes.h" // JS_PUBLIC_API
|
||||
#include "js/Class.h" // JSClass, js::ClassSpec
|
||||
#include "js/RootingAPI.h" // JS::Handle
|
||||
#include "js/Stream.h" // JS::ReadableStream{Mode,UnderlyingSource}
|
||||
#include "js/Value.h" // JS::Int32Value, JS::ObjectValue, JS::UndefinedValue
|
||||
#include "vm/NativeObject.h" // js::NativeObject
|
||||
|
||||
class JS_PUBLIC_API JSObject;
|
||||
|
||||
namespace js {
|
||||
|
||||
class ReadableStreamController;
|
||||
|
||||
class ReadableStream : public NativeObject {
|
||||
public:
|
||||
/**
|
||||
* Memory layout of Stream instances.
|
||||
*
|
||||
* See https://streams.spec.whatwg.org/#rs-internal-slots for details on
|
||||
* the stored state. [[state]] and [[disturbed]] are stored in
|
||||
* StreamSlot_State as ReadableStream::State enum values.
|
||||
*
|
||||
* Of the stored values, Reader and StoredError might be cross-compartment
|
||||
* wrappers. This can happen if the Reader was created by applying a
|
||||
* different compartment's ReadableStream.prototype.getReader method.
|
||||
*
|
||||
* A stream's associated controller is always created from under the
|
||||
* stream's constructor and thus cannot be in a different compartment.
|
||||
*/
|
||||
enum Slots {
|
||||
/**
|
||||
* Optional pointer to make the stream participate in Gecko's cycle
|
||||
* collection. See also JSCLASS_SLOT0_IS_NSISUPPORTS.
|
||||
*/
|
||||
Slot_ISupports,
|
||||
|
||||
Slot_Controller,
|
||||
Slot_Reader,
|
||||
Slot_State,
|
||||
Slot_StoredError,
|
||||
SlotCount
|
||||
};
|
||||
|
||||
private:
|
||||
enum StateBits {
|
||||
Readable = 0,
|
||||
Closed = 1,
|
||||
Errored = 2,
|
||||
StateMask = 0x000000ff,
|
||||
Disturbed = 0x00000100
|
||||
};
|
||||
|
||||
uint32_t stateBits() const { return getFixedSlot(Slot_State).toInt32(); }
|
||||
void initStateBits(uint32_t stateBits) {
|
||||
MOZ_ASSERT((stateBits & ~Disturbed) <= Errored);
|
||||
setFixedSlot(Slot_State, JS::Int32Value(stateBits));
|
||||
}
|
||||
void setStateBits(uint32_t stateBits) {
|
||||
#ifdef DEBUG
|
||||
bool wasDisturbed = disturbed();
|
||||
bool wasClosedOrErrored = closed() || errored();
|
||||
#endif
|
||||
initStateBits(stateBits);
|
||||
MOZ_ASSERT_IF(wasDisturbed, disturbed());
|
||||
MOZ_ASSERT_IF(wasClosedOrErrored, !readable());
|
||||
}
|
||||
|
||||
StateBits state() const { return StateBits(stateBits() & StateMask); }
|
||||
void setState(StateBits state) {
|
||||
MOZ_ASSERT(state <= Errored);
|
||||
uint32_t current = stateBits() & ~StateMask;
|
||||
setStateBits(current | state);
|
||||
}
|
||||
|
||||
public:
|
||||
bool readable() const { return state() == Readable; }
|
||||
bool closed() const { return state() == Closed; }
|
||||
void setClosed() { setState(Closed); }
|
||||
bool errored() const { return state() == Errored; }
|
||||
void setErrored() { setState(Errored); }
|
||||
bool disturbed() const { return stateBits() & Disturbed; }
|
||||
void setDisturbed() { setStateBits(stateBits() | Disturbed); }
|
||||
|
||||
bool hasController() const {
|
||||
return !getFixedSlot(Slot_Controller).isUndefined();
|
||||
}
|
||||
inline ReadableStreamController* controller() const;
|
||||
inline void setController(ReadableStreamController* controller);
|
||||
void clearController() {
|
||||
setFixedSlot(Slot_Controller, JS::UndefinedValue());
|
||||
}
|
||||
|
||||
bool hasReader() const { return !getFixedSlot(Slot_Reader).isUndefined(); }
|
||||
void setReader(JSObject* reader) {
|
||||
setFixedSlot(Slot_Reader, JS::ObjectValue(*reader));
|
||||
}
|
||||
void clearReader() { setFixedSlot(Slot_Reader, JS::UndefinedValue()); }
|
||||
|
||||
JS::Value storedError() const { return getFixedSlot(Slot_StoredError); }
|
||||
void setStoredError(JS::Handle<JS::Value> value) {
|
||||
setFixedSlot(Slot_StoredError, value);
|
||||
}
|
||||
|
||||
JS::ReadableStreamMode mode() const;
|
||||
|
||||
bool locked() const;
|
||||
|
||||
[[nodiscard]] static ReadableStream* create(
|
||||
JSContext* cx, void* nsISupportsObject_alreadyAddreffed = nullptr,
|
||||
JS::Handle<JSObject*> proto = nullptr);
|
||||
static ReadableStream* createExternalSourceStream(
|
||||
JSContext* cx, JS::ReadableStreamUnderlyingSource* source,
|
||||
void* nsISupportsObject_alreadyAddreffed = nullptr,
|
||||
JS::Handle<JSObject*> proto = nullptr);
|
||||
|
||||
static bool constructor(JSContext* cx, unsigned argc, Value* vp);
|
||||
static const ClassSpec classSpec_;
|
||||
static const JSClass class_;
|
||||
static const ClassSpec protoClassSpec_;
|
||||
static const JSClass protoClass_;
|
||||
};
|
||||
|
||||
[[nodiscard]] extern bool SetUpExternalReadableByteStreamController(
|
||||
JSContext* cx, JS::Handle<ReadableStream*> stream,
|
||||
JS::ReadableStreamUnderlyingSource* source);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_ReadableStream_h
|
|
@ -1,48 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*
|
||||
* Class ReadableStreamBYOBReader.
|
||||
*
|
||||
* Byte streams and BYOB readers are unimplemented, so this is skeletal -- yet
|
||||
* helpful to ensure certain trivial tests of the functionality in wpt, that
|
||||
* don't actually test fully-constructed byte streams/BYOB readers, pass. 🙄
|
||||
*/
|
||||
|
||||
#include "builtin/streams/ReadableStream.h" // js::ReadableStream
|
||||
#include "builtin/streams/ReadableStreamReader.h" // js::CreateReadableStreamBYOBReader, js::ForAuthorCodeBool
|
||||
#include "js/ErrorReport.h" // JS_ReportErrorNumberASCII
|
||||
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
|
||||
|
||||
using JS::Handle;
|
||||
|
||||
/*** 3.7. Class ReadableStreamBYOBReader *********************************/
|
||||
|
||||
/**
|
||||
* Stream spec, 3.7.3. new ReadableStreamBYOBReader ( stream )
|
||||
* Steps 2-5.
|
||||
*/
|
||||
[[nodiscard]] JSObject* js::CreateReadableStreamBYOBReader(
|
||||
JSContext* cx, Handle<ReadableStream*> unwrappedStream,
|
||||
ForAuthorCodeBool forAuthorCode, Handle<JSObject*> proto /* = nullptr */) {
|
||||
// Step 2: If ! IsReadableByteStreamController(
|
||||
// stream.[[readableStreamController]]) is false, throw a
|
||||
// TypeError exception.
|
||||
// We don't implement byte stream controllers yet, so always throw here. Note
|
||||
// that JSMSG_READABLESTREAM_BYTES_TYPE_NOT_IMPLEMENTED can't be used here
|
||||
// because it's a RangeError (and sadly wpt actually tests this and we have a
|
||||
// spurious failure if we don't make this a TypeError).
|
||||
JS_ReportErrorNumberASCII(
|
||||
cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAM_BYOB_READER_FOR_NON_BYTE_STREAM);
|
||||
|
||||
// Step 3: If ! IsReadableStreamLocked(stream) is true, throw a TypeError
|
||||
// exception.
|
||||
// Step 4: Perform ! ReadableStreamReaderGenericInitialize(this, stream).
|
||||
// Step 5: Set this.[[readIntoRequests]] to a new empty List.
|
||||
// Steps 3-5 are presently unreachable.
|
||||
return nullptr;
|
||||
}
|
|
@ -1,266 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* ReadableStream controller classes and functions. */
|
||||
|
||||
#ifndef builtin_streams_ReadableStreamController_h
|
||||
#define builtin_streams_ReadableStreamController_h
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
|
||||
#include <stdint.h> // uint32_t
|
||||
|
||||
#include "builtin/streams/ReadableStream.h" // js::ReadableStream
|
||||
#include "builtin/streams/StreamController.h" // js::StreamController
|
||||
#include "js/Class.h" // JSClass, js::ClassSpec
|
||||
#include "js/RootingAPI.h" // JS::Handle
|
||||
#include "js/Stream.h" // JS::ReadableStreamUnderlyingSource
|
||||
#include "js/Value.h" // JS::Value, JS::{Number,Object,Private,Undefined}Value, JS::UndefinedHandleValue
|
||||
#include "vm/List.h" // js::ListObject
|
||||
#include "vm/NativeObject.h" // js::NativeObject
|
||||
|
||||
namespace js {
|
||||
|
||||
class PromiseObject;
|
||||
|
||||
class ReadableStreamController : public StreamController {
|
||||
public:
|
||||
/**
|
||||
* Memory layout for ReadableStream controllers, starting after the slots
|
||||
* reserved for queue container usage.
|
||||
*
|
||||
* Storage of the internal slots listed in the standard is fairly
|
||||
* straightforward except for [[pullAlgorithm]] and [[cancelAlgorithm]].
|
||||
* These algorithms are not stored as JSFunction objects. Rather, there are
|
||||
* three cases:
|
||||
*
|
||||
* - Streams created with `new ReadableStream`: The methods are stored
|
||||
* in Slot_PullMethod and Slot_CancelMethod. The underlying source
|
||||
* object (`this` for these methods) is in Slot_UnderlyingSource.
|
||||
*
|
||||
* - External source streams. Slot_UnderlyingSource is a PrivateValue
|
||||
* pointing to the JS::ReadableStreamUnderlyingSource object. The
|
||||
* algorithms are implemented using the .pull() and .cancel() methods
|
||||
* of that object. Slot_Pull/CancelMethod are undefined.
|
||||
*
|
||||
* - Tee streams. Slot_UnderlyingSource is a TeeState object. The
|
||||
* pull/cancel algorithms are implemented as separate functions in
|
||||
* Stream.cpp. Slot_Pull/CancelMethod are undefined.
|
||||
*
|
||||
* UnderlyingSource, PullMethod, and CancelMethod can be wrappers to objects
|
||||
* in other compartments.
|
||||
*
|
||||
* StrategyHWM and Flags are both primitive (numeric) values.
|
||||
*/
|
||||
enum Slots {
|
||||
Slot_Stream = StreamController::SlotCount,
|
||||
Slot_UnderlyingSource,
|
||||
Slot_PullMethod,
|
||||
Slot_CancelMethod,
|
||||
Slot_StrategyHWM,
|
||||
Slot_Flags,
|
||||
SlotCount
|
||||
};
|
||||
|
||||
enum ControllerFlags {
|
||||
Flag_Started = 1 << 0,
|
||||
Flag_Pulling = 1 << 1,
|
||||
Flag_PullAgain = 1 << 2,
|
||||
Flag_CloseRequested = 1 << 3,
|
||||
Flag_TeeBranch1 = 1 << 4,
|
||||
Flag_TeeBranch2 = 1 << 5,
|
||||
Flag_ExternalSource = 1 << 6,
|
||||
Flag_SourceLocked = 1 << 7,
|
||||
};
|
||||
|
||||
ReadableStream* stream() const {
|
||||
return &getFixedSlot(Slot_Stream).toObject().as<ReadableStream>();
|
||||
}
|
||||
void setStream(ReadableStream* stream) {
|
||||
setFixedSlot(Slot_Stream, JS::ObjectValue(*stream));
|
||||
}
|
||||
JS::Value underlyingSource() const {
|
||||
return getFixedSlot(Slot_UnderlyingSource);
|
||||
}
|
||||
void setUnderlyingSource(const JS::Value& underlyingSource) {
|
||||
setFixedSlot(Slot_UnderlyingSource, underlyingSource);
|
||||
}
|
||||
JS::Value pullMethod() const { return getFixedSlot(Slot_PullMethod); }
|
||||
void setPullMethod(const JS::Value& pullMethod) {
|
||||
setFixedSlot(Slot_PullMethod, pullMethod);
|
||||
}
|
||||
JS::Value cancelMethod() const { return getFixedSlot(Slot_CancelMethod); }
|
||||
void setCancelMethod(const JS::Value& cancelMethod) {
|
||||
setFixedSlot(Slot_CancelMethod, cancelMethod);
|
||||
}
|
||||
JS::ReadableStreamUnderlyingSource* externalSource() const {
|
||||
static_assert(alignof(JS::ReadableStreamUnderlyingSource) >= 2,
|
||||
"External underling sources are stored as PrivateValues, "
|
||||
"so they must have even addresses");
|
||||
MOZ_ASSERT(hasExternalSource());
|
||||
return static_cast<JS::ReadableStreamUnderlyingSource*>(
|
||||
underlyingSource().toPrivate());
|
||||
}
|
||||
void setExternalSource(JS::ReadableStreamUnderlyingSource* underlyingSource) {
|
||||
setUnderlyingSource(JS::PrivateValue(underlyingSource));
|
||||
addFlags(Flag_ExternalSource);
|
||||
}
|
||||
static void clearUnderlyingSource(
|
||||
JS::Handle<ReadableStreamController*> controller,
|
||||
bool finalizeSource = true) {
|
||||
if (controller->hasExternalSource()) {
|
||||
if (finalizeSource) {
|
||||
controller->externalSource()->finalize();
|
||||
}
|
||||
controller->setFlags(controller->flags() & ~Flag_ExternalSource);
|
||||
}
|
||||
controller->setUnderlyingSource(JS::UndefinedHandleValue);
|
||||
}
|
||||
double strategyHWM() const {
|
||||
return getFixedSlot(Slot_StrategyHWM).toNumber();
|
||||
}
|
||||
void setStrategyHWM(double highWaterMark) {
|
||||
setFixedSlot(Slot_StrategyHWM, NumberValue(highWaterMark));
|
||||
}
|
||||
uint32_t flags() const { return getFixedSlot(Slot_Flags).toInt32(); }
|
||||
void setFlags(uint32_t flags) { setFixedSlot(Slot_Flags, Int32Value(flags)); }
|
||||
void addFlags(uint32_t flags) { setFlags(this->flags() | flags); }
|
||||
void removeFlags(uint32_t flags) { setFlags(this->flags() & ~flags); }
|
||||
bool started() const { return flags() & Flag_Started; }
|
||||
void setStarted() { addFlags(Flag_Started); }
|
||||
bool pulling() const { return flags() & Flag_Pulling; }
|
||||
void setPulling() { addFlags(Flag_Pulling); }
|
||||
void clearPullFlags() { removeFlags(Flag_Pulling | Flag_PullAgain); }
|
||||
bool pullAgain() const { return flags() & Flag_PullAgain; }
|
||||
void setPullAgain() { addFlags(Flag_PullAgain); }
|
||||
bool closeRequested() const { return flags() & Flag_CloseRequested; }
|
||||
void setCloseRequested() { addFlags(Flag_CloseRequested); }
|
||||
bool isTeeBranch1() const { return flags() & Flag_TeeBranch1; }
|
||||
void setTeeBranch1() {
|
||||
MOZ_ASSERT(!isTeeBranch2());
|
||||
addFlags(Flag_TeeBranch1);
|
||||
}
|
||||
bool isTeeBranch2() const { return flags() & Flag_TeeBranch2; }
|
||||
void setTeeBranch2() {
|
||||
MOZ_ASSERT(!isTeeBranch1());
|
||||
addFlags(Flag_TeeBranch2);
|
||||
}
|
||||
bool hasExternalSource() const { return flags() & Flag_ExternalSource; }
|
||||
bool sourceLocked() const { return flags() & Flag_SourceLocked; }
|
||||
void setSourceLocked() { addFlags(Flag_SourceLocked); }
|
||||
void clearSourceLocked() { removeFlags(Flag_SourceLocked); }
|
||||
};
|
||||
|
||||
class ReadableStreamDefaultController : public ReadableStreamController {
|
||||
private:
|
||||
/**
|
||||
* Memory layout for ReadableStreamDefaultControllers, starting after the
|
||||
* slots shared among all types of controllers.
|
||||
*
|
||||
* StrategySize is treated as an opaque value when stored. The only use site
|
||||
* ensures that it's wrapped into the current cx compartment.
|
||||
*/
|
||||
enum Slots {
|
||||
Slot_StrategySize = ReadableStreamController::SlotCount,
|
||||
SlotCount
|
||||
};
|
||||
|
||||
public:
|
||||
JS::Value strategySize() const { return getFixedSlot(Slot_StrategySize); }
|
||||
void setStrategySize(const JS::Value& size) {
|
||||
setFixedSlot(Slot_StrategySize, size);
|
||||
}
|
||||
|
||||
static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp);
|
||||
static const ClassSpec classSpec_;
|
||||
static const JSClass class_;
|
||||
static const ClassSpec protoClassSpec_;
|
||||
static const JSClass protoClass_;
|
||||
};
|
||||
|
||||
class ReadableByteStreamController : public ReadableStreamController {
|
||||
public:
|
||||
/**
|
||||
* Memory layout for ReadableByteStreamControllers, starting after the
|
||||
* slots shared among all types of controllers.
|
||||
*
|
||||
* PendingPullIntos is guaranteed to be in the same compartment as the
|
||||
* controller, but might contain wrappers for objects from other
|
||||
* compartments.
|
||||
*
|
||||
* AutoAllocateSize is a primitive (numeric) value.
|
||||
*/
|
||||
enum Slots {
|
||||
Slot_BYOBRequest = ReadableStreamController::SlotCount,
|
||||
Slot_PendingPullIntos,
|
||||
Slot_AutoAllocateSize,
|
||||
SlotCount
|
||||
};
|
||||
|
||||
JS::Value byobRequest() const { return getFixedSlot(Slot_BYOBRequest); }
|
||||
void clearBYOBRequest() {
|
||||
setFixedSlot(Slot_BYOBRequest, JS::UndefinedValue());
|
||||
}
|
||||
ListObject* pendingPullIntos() const {
|
||||
return &getFixedSlot(Slot_PendingPullIntos).toObject().as<ListObject>();
|
||||
}
|
||||
JS::Value autoAllocateChunkSize() const {
|
||||
return getFixedSlot(Slot_AutoAllocateSize);
|
||||
}
|
||||
void setAutoAllocateChunkSize(const JS::Value& size) {
|
||||
setFixedSlot(Slot_AutoAllocateSize, size);
|
||||
}
|
||||
|
||||
static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp);
|
||||
static const ClassSpec classSpec_;
|
||||
static const JSClass class_;
|
||||
static const ClassSpec protoClassSpec_;
|
||||
static const JSClass protoClass_;
|
||||
};
|
||||
|
||||
[[nodiscard]] extern bool CheckReadableStreamControllerCanCloseOrEnqueue(
|
||||
JSContext* cx, JS::Handle<ReadableStreamController*> unwrappedController,
|
||||
const char* action);
|
||||
|
||||
[[nodiscard]] extern JSObject* ReadableStreamControllerCancelSteps(
|
||||
JSContext* cx, JS::Handle<ReadableStreamController*> unwrappedController,
|
||||
JS::Handle<JS::Value> reason);
|
||||
|
||||
extern PromiseObject* ReadableStreamDefaultControllerPullSteps(
|
||||
JSContext* cx,
|
||||
JS::Handle<ReadableStreamDefaultController*> unwrappedController);
|
||||
|
||||
extern bool ReadableStreamControllerStartHandler(JSContext* cx, unsigned argc,
|
||||
JS::Value* vp);
|
||||
|
||||
extern bool ReadableStreamControllerStartFailedHandler(JSContext* cx,
|
||||
unsigned argc,
|
||||
JS::Value* vp);
|
||||
|
||||
} // namespace js
|
||||
|
||||
template <>
|
||||
inline bool JSObject::is<js::ReadableStreamController>() const {
|
||||
return is<js::ReadableStreamDefaultController>() ||
|
||||
is<js::ReadableByteStreamController>();
|
||||
}
|
||||
|
||||
namespace js {
|
||||
|
||||
inline ReadableStreamController* ReadableStream::controller() const {
|
||||
return &getFixedSlot(Slot_Controller)
|
||||
.toObject()
|
||||
.as<ReadableStreamController>();
|
||||
}
|
||||
|
||||
inline void ReadableStream::setController(
|
||||
ReadableStreamController* controller) {
|
||||
setFixedSlot(Slot_Controller, JS::ObjectValue(*controller));
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_ReadableStreamController_h
|
|
@ -1,513 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Class ReadableStreamDefaultController. */
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
|
||||
|
||||
#include "jsfriendapi.h" // js::AssertSameCompartment
|
||||
|
||||
#include "builtin/streams/ClassSpecMacro.h" // JS_STREAMS_CLASS_SPEC
|
||||
#include "builtin/streams/MiscellaneousOperations.h" // js::IsMaybeWrapped
|
||||
#include "builtin/streams/PullIntoDescriptor.h" // js::PullIntoDescriptor
|
||||
#include "builtin/streams/QueueWithSizes.h" // js::{DequeueValue,ResetQueue}
|
||||
#include "builtin/streams/ReadableStream.h" // js::ReadableStream, js::SetUpExternalReadableByteStreamController
|
||||
#include "builtin/streams/ReadableStreamController.h" // js::ReadableStream{,Default}Controller, js::ReadableByteStreamController, js::CheckReadableStreamControllerCanCloseOrEnqueue, js::ReadableStreamControllerCancelSteps, js::ReadableStreamDefaultControllerPullSteps, js::ReadableStreamControllerStart{,Failed}Handler
|
||||
#include "builtin/streams/ReadableStreamDefaultControllerOperations.h" // js::ReadableStreamController{CallPullIfNeeded,ClearAlgorithms,Error,GetDesiredSizeUnchecked}, js::ReadableStreamDefaultController{Close,Enqueue}
|
||||
#include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStream{AddReadOrReadIntoRequest,CloseInternal,CreateReadResult}
|
||||
#include "builtin/streams/ReadableStreamOperations.h" // js::ReadableStreamTee_Cancel
|
||||
#include "builtin/streams/ReadableStreamReader.h" // js::ReadableStream{,Default}Reader
|
||||
#include "builtin/streams/StreamController.h" // js::StreamController
|
||||
#include "builtin/streams/TeeState.h" // js::TeeState
|
||||
#include "js/ArrayBuffer.h" // JS::NewArrayBuffer
|
||||
#include "js/Class.h" // js::ClassSpec
|
||||
#include "js/ErrorReport.h" // JS_ReportErrorNumberASCII
|
||||
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
|
||||
#include "js/PropertySpec.h"
|
||||
#include "vm/Interpreter.h"
|
||||
#include "vm/JSContext.h"
|
||||
#include "vm/PlainObject.h" // js::PlainObject
|
||||
#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
|
||||
#include "vm/SelfHosting.h"
|
||||
|
||||
#include "builtin/HandlerFunction-inl.h" // js::TargetFromHandler
|
||||
#include "builtin/streams/MiscellaneousOperations-inl.h" // js::PromiseCall
|
||||
#include "builtin/streams/ReadableStreamReader-inl.h" // js::UnwrapReaderFromStream
|
||||
#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::UnwrapAnd{DowncastObject,TypeCheckThis}
|
||||
#include "vm/JSContext-inl.h" // JSContext::check
|
||||
#include "vm/Realm-inl.h" // js::AutoRealm
|
||||
|
||||
using js::ClassSpec;
|
||||
using js::PromiseObject;
|
||||
using js::ReadableStream;
|
||||
using js::ReadableStreamController;
|
||||
using js::ReadableStreamControllerCallPullIfNeeded;
|
||||
using js::ReadableStreamControllerClearAlgorithms;
|
||||
using js::ReadableStreamControllerError;
|
||||
using js::ReadableStreamControllerGetDesiredSizeUnchecked;
|
||||
using js::ReadableStreamDefaultController;
|
||||
using js::ReadableStreamDefaultControllerClose;
|
||||
using js::ReadableStreamDefaultControllerEnqueue;
|
||||
using js::TargetFromHandler;
|
||||
using js::UnwrapAndTypeCheckThis;
|
||||
|
||||
using JS::CallArgs;
|
||||
using JS::CallArgsFromVp;
|
||||
using JS::Handle;
|
||||
using JS::ObjectValue;
|
||||
using JS::Rooted;
|
||||
using JS::Value;
|
||||
|
||||
/*** 3.9. Class ReadableStreamDefaultController *****************************/
|
||||
|
||||
/**
|
||||
* Streams spec, 3.10.11. SetUpReadableStreamDefaultController, step 11
|
||||
* and
|
||||
* Streams spec, 3.13.26. SetUpReadableByteStreamController, step 16:
|
||||
* Upon fulfillment of startPromise, [...]
|
||||
*/
|
||||
bool js::ReadableStreamControllerStartHandler(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
Rooted<ReadableStreamController*> controller(
|
||||
cx, TargetFromHandler<ReadableStreamController>(args));
|
||||
|
||||
// Step a: Set controller.[[started]] to true.
|
||||
controller->setStarted();
|
||||
|
||||
// Step b: Assert: controller.[[pulling]] is false.
|
||||
MOZ_ASSERT(!controller->pulling());
|
||||
|
||||
// Step c: Assert: controller.[[pullAgain]] is false.
|
||||
MOZ_ASSERT(!controller->pullAgain());
|
||||
|
||||
// Step d: Perform
|
||||
// ! ReadableStreamDefaultControllerCallPullIfNeeded(controller)
|
||||
// (or ReadableByteStreamControllerCallPullIfNeeded(controller)).
|
||||
if (!ReadableStreamControllerCallPullIfNeeded(cx, controller)) {
|
||||
return false;
|
||||
}
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.10.11. SetUpReadableStreamDefaultController, step 12
|
||||
* and
|
||||
* Streams spec, 3.13.26. SetUpReadableByteStreamController, step 17:
|
||||
* Upon rejection of startPromise with reason r, [...]
|
||||
*/
|
||||
bool js::ReadableStreamControllerStartFailedHandler(JSContext* cx,
|
||||
unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
Rooted<ReadableStreamController*> controller(
|
||||
cx, TargetFromHandler<ReadableStreamController>(args));
|
||||
|
||||
// Step a: Perform
|
||||
// ! ReadableStreamDefaultControllerError(controller, r)
|
||||
// (or ReadableByteStreamControllerError(controller, r)).
|
||||
if (!ReadableStreamControllerError(cx, controller, args.get(0))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.9.3.
|
||||
* new ReadableStreamDefaultController( stream, underlyingSource, size,
|
||||
* highWaterMark )
|
||||
*/
|
||||
bool ReadableStreamDefaultController::constructor(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
// Step 1: Throw a TypeError.
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_BOGUS_CONSTRUCTOR,
|
||||
"ReadableStreamDefaultController");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.9.4.1. get desiredSize
|
||||
*/
|
||||
static bool ReadableStreamDefaultController_desiredSize(JSContext* cx,
|
||||
unsigned argc,
|
||||
Value* vp) {
|
||||
// Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
|
||||
// TypeError exception.
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
Rooted<ReadableStreamController*> unwrappedController(
|
||||
cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(
|
||||
cx, args, "get desiredSize"));
|
||||
if (!unwrappedController) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3.10.8. ReadableStreamDefaultControllerGetDesiredSize, steps 1-4.
|
||||
// 3.10.8. Step 1: Let stream be controller.[[controlledReadableStream]].
|
||||
ReadableStream* unwrappedStream = unwrappedController->stream();
|
||||
|
||||
// 3.10.8. Step 2: Let state be stream.[[state]].
|
||||
// 3.10.8. Step 3: If state is "errored", return null.
|
||||
if (unwrappedStream->errored()) {
|
||||
args.rval().setNull();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3.10.8. Step 4: If state is "closed", return 0.
|
||||
if (unwrappedStream->closed()) {
|
||||
args.rval().setInt32(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 2: Return ! ReadableStreamDefaultControllerGetDesiredSize(this).
|
||||
args.rval().setNumber(
|
||||
ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unified implementation of step 2 of 3.9.4.2 and 3.9.4.3,
|
||||
* and steps 2-3 of 3.11.4.3.
|
||||
*/
|
||||
[[nodiscard]] bool js::CheckReadableStreamControllerCanCloseOrEnqueue(
|
||||
JSContext* cx, Handle<ReadableStreamController*> unwrappedController,
|
||||
const char* action) {
|
||||
// 3.9.4.2. close(), step 2, and
|
||||
// 3.9.4.3. enqueue(chunk), step 2:
|
||||
// If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is false,
|
||||
// throw a TypeError exception.
|
||||
// RSDCCanCloseOrEnqueue returns false in two cases: (1)
|
||||
// controller.[[closeRequested]] is true; (2) the stream is not readable,
|
||||
// i.e. already closed or errored. This amounts to exactly the same thing as
|
||||
// 3.11.4.3 steps 2-3 below, and we want different error messages for the two
|
||||
// cases anyway.
|
||||
|
||||
// 3.11.4.3. Step 2: If this.[[closeRequested]] is true, throw a TypeError
|
||||
// exception.
|
||||
if (unwrappedController->closeRequested()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAMCONTROLLER_CLOSED, action);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3.11.4.3. Step 3: If this.[[controlledReadableByteStream]].[[state]] is
|
||||
// not "readable", throw a TypeError exception.
|
||||
ReadableStream* unwrappedStream = unwrappedController->stream();
|
||||
if (!unwrappedStream->readable()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE,
|
||||
action);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.9.4.2 close()
|
||||
*/
|
||||
static bool ReadableStreamDefaultController_close(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
// Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
|
||||
// TypeError exception.
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
Rooted<ReadableStreamDefaultController*> unwrappedController(
|
||||
cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(cx, args,
|
||||
"close"));
|
||||
if (!unwrappedController) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is
|
||||
// false, throw a TypeError exception.
|
||||
if (!CheckReadableStreamControllerCanCloseOrEnqueue(cx, unwrappedController,
|
||||
"close")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Perform ! ReadableStreamDefaultControllerClose(this).
|
||||
if (!ReadableStreamDefaultControllerClose(cx, unwrappedController)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.9.4.3. enqueue ( chunk )
|
||||
*/
|
||||
static bool ReadableStreamDefaultController_enqueue(JSContext* cx,
|
||||
unsigned argc, Value* vp) {
|
||||
// Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
|
||||
// TypeError exception.
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
Rooted<ReadableStreamDefaultController*> unwrappedController(
|
||||
cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(cx, args,
|
||||
"enqueue"));
|
||||
if (!unwrappedController) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is
|
||||
// false, throw a TypeError exception.
|
||||
if (!CheckReadableStreamControllerCanCloseOrEnqueue(cx, unwrappedController,
|
||||
"enqueue")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Return ! ReadableStreamDefaultControllerEnqueue(this, chunk).
|
||||
if (!ReadableStreamDefaultControllerEnqueue(cx, unwrappedController,
|
||||
args.get(0))) {
|
||||
return false;
|
||||
}
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.9.4.4. error ( e )
|
||||
*/
|
||||
static bool ReadableStreamDefaultController_error(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
// Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
|
||||
// TypeError exception.
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
Rooted<ReadableStreamDefaultController*> unwrappedController(
|
||||
cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(cx, args,
|
||||
"enqueue"));
|
||||
if (!unwrappedController) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: Perform ! ReadableStreamDefaultControllerError(this, e).
|
||||
if (!ReadableStreamControllerError(cx, unwrappedController, args.get(0))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
static const JSPropertySpec ReadableStreamDefaultController_properties[] = {
|
||||
JS_PSG("desiredSize", ReadableStreamDefaultController_desiredSize, 0),
|
||||
JS_PS_END};
|
||||
|
||||
static const JSFunctionSpec ReadableStreamDefaultController_methods[] = {
|
||||
JS_FN("close", ReadableStreamDefaultController_close, 0, 0),
|
||||
JS_FN("enqueue", ReadableStreamDefaultController_enqueue, 1, 0),
|
||||
JS_FN("error", ReadableStreamDefaultController_error, 1, 0), JS_FS_END};
|
||||
|
||||
JS_STREAMS_CLASS_SPEC(ReadableStreamDefaultController, 0, SlotCount,
|
||||
ClassSpec::DontDefineConstructor, 0, JS_NULL_CLASS_OPS);
|
||||
|
||||
/**
|
||||
* Unified implementation of ReadableStream controllers' [[CancelSteps]]
|
||||
* internal methods.
|
||||
* Streams spec, 3.9.5.1. [[CancelSteps]] ( reason )
|
||||
* and
|
||||
* Streams spec, 3.11.5.1. [[CancelSteps]] ( reason )
|
||||
*/
|
||||
[[nodiscard]] JSObject* js::ReadableStreamControllerCancelSteps(
|
||||
JSContext* cx, Handle<ReadableStreamController*> unwrappedController,
|
||||
Handle<Value> reason) {
|
||||
AssertSameCompartment(cx, reason);
|
||||
|
||||
// Step 1 of 3.11.5.1: If this.[[pendingPullIntos]] is not empty,
|
||||
if (!unwrappedController->is<ReadableStreamDefaultController>()) {
|
||||
Rooted<ListObject*> unwrappedPendingPullIntos(
|
||||
cx, unwrappedController->as<ReadableByteStreamController>()
|
||||
.pendingPullIntos());
|
||||
|
||||
if (unwrappedPendingPullIntos->length() != 0) {
|
||||
// Step a: Let firstDescriptor be the first element of
|
||||
// this.[[pendingPullIntos]].
|
||||
PullIntoDescriptor* unwrappedDescriptor =
|
||||
UnwrapAndDowncastObject<PullIntoDescriptor>(
|
||||
cx, &unwrappedPendingPullIntos->get(0).toObject());
|
||||
if (!unwrappedDescriptor) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step b: Set firstDescriptor.[[bytesFilled]] to 0.
|
||||
unwrappedDescriptor->setBytesFilled(0);
|
||||
}
|
||||
}
|
||||
|
||||
Rooted<Value> unwrappedUnderlyingSource(
|
||||
cx, unwrappedController->underlyingSource());
|
||||
|
||||
// Step 1 of 3.9.5.1, step 2 of 3.11.5.1: Perform ! ResetQueue(this).
|
||||
if (!ResetQueue(cx, unwrappedController)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 2 of 3.9.5.1, step 3 of 3.11.5.1: Let result be the result of
|
||||
// performing this.[[cancelAlgorithm]], passing reason.
|
||||
//
|
||||
// Our representation of cancel algorithms is a bit awkward, for
|
||||
// performance, so we must figure out which algorithm is being invoked.
|
||||
Rooted<JSObject*> result(cx);
|
||||
if (IsMaybeWrapped<TeeState>(unwrappedUnderlyingSource)) {
|
||||
// The cancel algorithm given in ReadableStreamTee step 13 or 14.
|
||||
MOZ_ASSERT(unwrappedUnderlyingSource.toObject().is<TeeState>(),
|
||||
"tee streams and controllers are always same-compartment with "
|
||||
"the TeeState object");
|
||||
Rooted<TeeState*> unwrappedTeeState(
|
||||
cx, &unwrappedUnderlyingSource.toObject().as<TeeState>());
|
||||
Rooted<ReadableStreamDefaultController*> unwrappedDefaultController(
|
||||
cx, &unwrappedController->as<ReadableStreamDefaultController>());
|
||||
result = ReadableStreamTee_Cancel(cx, unwrappedTeeState,
|
||||
unwrappedDefaultController, reason);
|
||||
} else if (unwrappedController->hasExternalSource()) {
|
||||
// An embedding-provided cancel algorithm.
|
||||
Rooted<Value> rval(cx);
|
||||
{
|
||||
AutoRealm ar(cx, unwrappedController);
|
||||
JS::ReadableStreamUnderlyingSource* source =
|
||||
unwrappedController->externalSource();
|
||||
Rooted<ReadableStream*> stream(cx, unwrappedController->stream());
|
||||
Rooted<Value> wrappedReason(cx, reason);
|
||||
if (!cx->compartment()->wrap(cx, &wrappedReason)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cx->check(stream, wrappedReason);
|
||||
rval = source->cancel(cx, stream, wrappedReason);
|
||||
}
|
||||
|
||||
// Make sure the ReadableStreamControllerClearAlgorithms call below is
|
||||
// reached, even on error.
|
||||
if (!cx->compartment()->wrap(cx, &rval)) {
|
||||
result = nullptr;
|
||||
} else {
|
||||
result = PromiseObject::unforgeableResolve(cx, rval);
|
||||
}
|
||||
} else {
|
||||
// The algorithm created in
|
||||
// SetUpReadableByteStreamControllerFromUnderlyingSource step 5.
|
||||
Rooted<Value> unwrappedCancelMethod(cx,
|
||||
unwrappedController->cancelMethod());
|
||||
if (unwrappedCancelMethod.isUndefined()) {
|
||||
// CreateAlgorithmFromUnderlyingMethod step 7.
|
||||
result = PromiseResolvedWithUndefined(cx);
|
||||
} else {
|
||||
// CreateAlgorithmFromUnderlyingMethod steps 6.c.i-ii.
|
||||
{
|
||||
AutoRealm ar(cx, unwrappedController);
|
||||
|
||||
// |unwrappedCancelMethod| and |unwrappedUnderlyingSource| come directly
|
||||
// from |unwrappedController| slots so must be same-compartment with it.
|
||||
cx->check(unwrappedCancelMethod);
|
||||
cx->check(unwrappedUnderlyingSource);
|
||||
|
||||
Rooted<Value> wrappedReason(cx, reason);
|
||||
if (!cx->compartment()->wrap(cx, &wrappedReason)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If PromiseCall fails, don't bail out until after the
|
||||
// ReadableStreamControllerClearAlgorithms call below.
|
||||
result = PromiseCall(cx, unwrappedCancelMethod,
|
||||
unwrappedUnderlyingSource, wrappedReason);
|
||||
}
|
||||
if (!cx->compartment()->wrap(cx, &result)) {
|
||||
result = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3 (or 4): Perform
|
||||
// ! ReadableStreamDefaultControllerClearAlgorithms(this)
|
||||
// (or ReadableByteStreamControllerClearAlgorithms(this)).
|
||||
ReadableStreamControllerClearAlgorithms(unwrappedController);
|
||||
|
||||
// Step 4 (or 5): Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.9.5.2.
|
||||
* ReadableStreamDefaultController [[PullSteps]]( forAuthorCode )
|
||||
*/
|
||||
PromiseObject* js::ReadableStreamDefaultControllerPullSteps(
|
||||
JSContext* cx,
|
||||
Handle<ReadableStreamDefaultController*> unwrappedController) {
|
||||
// Step 1: Let stream be this.[[controlledReadableStream]].
|
||||
Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
|
||||
|
||||
// Step 2: If this.[[queue]] is not empty,
|
||||
Rooted<ListObject*> unwrappedQueue(cx);
|
||||
Rooted<Value> val(
|
||||
cx, unwrappedController->getFixedSlot(StreamController::Slot_Queue));
|
||||
if (val.isObject()) {
|
||||
unwrappedQueue = &val.toObject().as<ListObject>();
|
||||
}
|
||||
|
||||
if (unwrappedQueue && unwrappedQueue->length() != 0) {
|
||||
// Step a: Let chunk be ! DequeueValue(this).
|
||||
Rooted<Value> chunk(cx);
|
||||
if (!DequeueValue(cx, unwrappedController, &chunk)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step b: If this.[[closeRequested]] is true and this.[[queue]] is empty,
|
||||
if (unwrappedController->closeRequested() &&
|
||||
unwrappedQueue->length() == 0) {
|
||||
// Step i: Perform ! ReadableStreamDefaultControllerClearAlgorithms(this).
|
||||
ReadableStreamControllerClearAlgorithms(unwrappedController);
|
||||
|
||||
// Step ii: Perform ! ReadableStreamClose(stream).
|
||||
if (!ReadableStreamCloseInternal(cx, unwrappedStream)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Step c: Otherwise, perform
|
||||
// ! ReadableStreamDefaultControllerCallPullIfNeeded(this).
|
||||
else {
|
||||
if (!ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Step d: Return a promise resolved with
|
||||
// ! ReadableStreamCreateReadResult(chunk, false, forAuthorCode).
|
||||
cx->check(chunk);
|
||||
ReadableStreamReader* unwrappedReader =
|
||||
UnwrapReaderFromStream(cx, unwrappedStream);
|
||||
if (!unwrappedReader) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PlainObject* readResultObj = ReadableStreamCreateReadResult(
|
||||
cx, chunk, false, unwrappedReader->forAuthorCode());
|
||||
if (!readResultObj) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Rooted<Value> readResult(cx, ObjectValue(*readResultObj));
|
||||
return PromiseObject::unforgeableResolveWithNonPromise(cx, readResult);
|
||||
}
|
||||
|
||||
// Step 3: Let pendingPromise be
|
||||
// ! ReadableStreamAddReadRequest(stream, forAuthorCode).
|
||||
Rooted<PromiseObject*> pendingPromise(
|
||||
cx, ReadableStreamAddReadOrReadIntoRequest(cx, unwrappedStream));
|
||||
if (!pendingPromise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 4: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this).
|
||||
if (!ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 5: Return pendingPromise.
|
||||
return pendingPromise;
|
||||
}
|
|
@ -1,682 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Readable stream default controller abstract operations. */
|
||||
|
||||
#include "builtin/streams/ReadableStreamDefaultControllerOperations.h"
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
|
||||
|
||||
#include "jsfriendapi.h" // js::AssertSameCompartment
|
||||
|
||||
#include "builtin/Stream.h" // js::ReadableByteStreamControllerClearPendingPullIntos
|
||||
#include "builtin/streams/MiscellaneousOperations.h" // js::CreateAlgorithmFromUnderlyingMethod, js::InvokeOrNoop, js::IsMaybeWrapped
|
||||
#include "builtin/streams/QueueWithSizes.h" // js::EnqueueValueWithSize, js::ResetQueue
|
||||
#include "builtin/streams/ReadableStreamController.h" // js::ReadableStream{,Default}Controller, js::ReadableByteStreamController, js::ReadableStreamControllerStart{,Failed}Handler
|
||||
#include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStream{CloseInternal,ErrorInternal,FulfillReadOrReadIntoRequest,GetNumReadRequests}
|
||||
#include "builtin/streams/ReadableStreamOperations.h" // js::ReadableStreamTee_Pull, js::SetUpReadableStreamDefaultController
|
||||
#include "builtin/streams/TeeState.h" // js::TeeState
|
||||
#include "js/CallAndConstruct.h" // JS::IsCallable
|
||||
#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
|
||||
#include "js/Promise.h" // JS::AddPromiseReactions
|
||||
#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
|
||||
#include "js/Stream.h" // JS::ReadableStreamUnderlyingSource
|
||||
#include "js/Value.h" // JS::{,Int32,Object}Value, JS::UndefinedHandleValue
|
||||
#include "vm/Compartment.h" // JS::Compartment
|
||||
#include "vm/Interpreter.h" // js::Call, js::GetAndClearExceptionAndStack
|
||||
#include "vm/JSContext.h" // JSContext
|
||||
#include "vm/JSObject.h" // JSObject
|
||||
#include "vm/List.h" // js::ListObject
|
||||
#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
|
||||
#include "vm/Runtime.h" // JSAtomState
|
||||
#include "vm/SavedFrame.h" // js::SavedFrame
|
||||
|
||||
#include "builtin/HandlerFunction-inl.h" // js::NewHandler
|
||||
#include "builtin/streams/MiscellaneousOperations-inl.h" // js::PromiseCall
|
||||
#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::UnwrapCalleeSlot
|
||||
#include "vm/JSContext-inl.h" // JSContext::check
|
||||
#include "vm/JSObject-inl.h" // js::IsCallable, js::NewBuiltinClassInstance
|
||||
#include "vm/Realm-inl.h" // js::AutoRealm
|
||||
|
||||
using js::ReadableByteStreamController;
|
||||
using js::ReadableStream;
|
||||
using js::ReadableStreamController;
|
||||
using js::ReadableStreamControllerCallPullIfNeeded;
|
||||
using js::ReadableStreamControllerError;
|
||||
using js::ReadableStreamGetNumReadRequests;
|
||||
using js::UnwrapCalleeSlot;
|
||||
|
||||
using JS::CallArgs;
|
||||
using JS::CallArgsFromVp;
|
||||
using JS::Handle;
|
||||
using JS::Rooted;
|
||||
using JS::UndefinedHandleValue;
|
||||
using JS::Value;
|
||||
|
||||
/*** 3.10. Readable stream default controller abstract operations ***********/
|
||||
|
||||
// Streams spec, 3.10.1. IsReadableStreamDefaultController ( x )
|
||||
// Implemented via is<ReadableStreamDefaultController>()
|
||||
|
||||
/**
|
||||
* Streams spec, 3.10.2 and 3.13.3. step 7:
|
||||
* Upon fulfillment of pullPromise, [...]
|
||||
*/
|
||||
static bool ControllerPullHandler(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
Rooted<ReadableStreamController*> unwrappedController(
|
||||
cx, UnwrapCalleeSlot<ReadableStreamController>(cx, args, 0));
|
||||
if (!unwrappedController) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool pullAgain = unwrappedController->pullAgain();
|
||||
|
||||
// Step a: Set controller.[[pulling]] to false.
|
||||
// Step b.i: Set controller.[[pullAgain]] to false.
|
||||
unwrappedController->clearPullFlags();
|
||||
|
||||
// Step b: If controller.[[pullAgain]] is true,
|
||||
if (pullAgain) {
|
||||
// Step ii: Perform
|
||||
// ! ReadableStreamDefaultControllerCallPullIfNeeded(controller)
|
||||
// (or ReadableByteStreamControllerCallPullIfNeeded(controller)).
|
||||
if (!ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.10.2 and 3.13.3. step 8:
|
||||
* Upon rejection of pullPromise with reason e,
|
||||
*/
|
||||
static bool ControllerPullFailedHandler(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
Handle<Value> e = args.get(0);
|
||||
|
||||
Rooted<ReadableStreamController*> controller(
|
||||
cx, UnwrapCalleeSlot<ReadableStreamController>(cx, args, 0));
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step a: Perform ! ReadableStreamDefaultControllerError(controller, e).
|
||||
// (ReadableByteStreamControllerError in 3.12.3.)
|
||||
if (!ReadableStreamControllerError(cx, controller, e)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ReadableStreamControllerShouldCallPull(
|
||||
ReadableStreamController* unwrappedController);
|
||||
|
||||
/**
|
||||
* Streams spec, 3.10.2
|
||||
* ReadableStreamDefaultControllerCallPullIfNeeded ( controller )
|
||||
* Streams spec, 3.13.3.
|
||||
* ReadableByteStreamControllerCallPullIfNeeded ( controller )
|
||||
*/
|
||||
[[nodiscard]] bool js::ReadableStreamControllerCallPullIfNeeded(
|
||||
JSContext* cx, Handle<ReadableStreamController*> unwrappedController) {
|
||||
// Step 1: Let shouldPull be
|
||||
// ! ReadableStreamDefaultControllerShouldCallPull(controller).
|
||||
// (ReadableByteStreamDefaultControllerShouldCallPull in 3.13.3.)
|
||||
bool shouldPull = ReadableStreamControllerShouldCallPull(unwrappedController);
|
||||
|
||||
// Step 2: If shouldPull is false, return.
|
||||
if (!shouldPull) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 3: If controller.[[pulling]] is true,
|
||||
if (unwrappedController->pulling()) {
|
||||
// Step a: Set controller.[[pullAgain]] to true.
|
||||
unwrappedController->setPullAgain();
|
||||
|
||||
// Step b: Return.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 4: Assert: controller.[[pullAgain]] is false.
|
||||
MOZ_ASSERT(!unwrappedController->pullAgain());
|
||||
|
||||
// Step 5: Set controller.[[pulling]] to true.
|
||||
unwrappedController->setPulling();
|
||||
|
||||
// We use this variable in step 7. For ease of error-handling, we wrap it
|
||||
// early.
|
||||
Rooted<JSObject*> wrappedController(cx, unwrappedController);
|
||||
if (!cx->compartment()->wrap(cx, &wrappedController)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 6: Let pullPromise be the result of performing
|
||||
// controller.[[pullAlgorithm]].
|
||||
// Our representation of pull algorithms is a bit awkward, for performance,
|
||||
// so we must figure out which algorithm is being invoked.
|
||||
Rooted<JSObject*> pullPromise(cx);
|
||||
Rooted<Value> unwrappedUnderlyingSource(
|
||||
cx, unwrappedController->underlyingSource());
|
||||
|
||||
if (IsMaybeWrapped<TeeState>(unwrappedUnderlyingSource)) {
|
||||
// The pull algorithm given in ReadableStreamTee step 12.
|
||||
MOZ_ASSERT(unwrappedUnderlyingSource.toObject().is<TeeState>(),
|
||||
"tee streams and controllers are always same-compartment with "
|
||||
"the TeeState object");
|
||||
Rooted<TeeState*> unwrappedTeeState(
|
||||
cx, &unwrappedUnderlyingSource.toObject().as<TeeState>());
|
||||
pullPromise = ReadableStreamTee_Pull(cx, unwrappedTeeState);
|
||||
} else if (unwrappedController->hasExternalSource()) {
|
||||
// An embedding-provided pull algorithm.
|
||||
{
|
||||
AutoRealm ar(cx, unwrappedController);
|
||||
JS::ReadableStreamUnderlyingSource* source =
|
||||
unwrappedController->externalSource();
|
||||
Rooted<ReadableStream*> stream(cx, unwrappedController->stream());
|
||||
double desiredSize =
|
||||
ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController);
|
||||
source->requestData(cx, stream, desiredSize);
|
||||
}
|
||||
pullPromise = PromiseResolvedWithUndefined(cx);
|
||||
} else {
|
||||
// The pull algorithm created in
|
||||
// SetUpReadableStreamDefaultControllerFromUnderlyingSource step 4.
|
||||
Rooted<Value> unwrappedPullMethod(cx, unwrappedController->pullMethod());
|
||||
if (unwrappedPullMethod.isUndefined()) {
|
||||
// CreateAlgorithmFromUnderlyingMethod step 7.
|
||||
pullPromise = PromiseResolvedWithUndefined(cx);
|
||||
} else {
|
||||
// CreateAlgorithmFromUnderlyingMethod step 6.b.i.
|
||||
{
|
||||
AutoRealm ar(cx, unwrappedController);
|
||||
|
||||
// |unwrappedPullMethod| and |unwrappedUnderlyingSource| come directly
|
||||
// from |unwrappedController| slots so must be same-compartment with it.
|
||||
cx->check(unwrappedPullMethod);
|
||||
cx->check(unwrappedUnderlyingSource);
|
||||
|
||||
Rooted<Value> controller(cx, ObjectValue(*unwrappedController));
|
||||
cx->check(controller);
|
||||
|
||||
pullPromise = PromiseCall(cx, unwrappedPullMethod,
|
||||
unwrappedUnderlyingSource, controller);
|
||||
if (!pullPromise) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!cx->compartment()->wrap(cx, &pullPromise)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!pullPromise) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 7: Upon fulfillment of pullPromise, [...]
|
||||
// Step 8. Upon rejection of pullPromise with reason e, [...]
|
||||
Rooted<JSObject*> onPullFulfilled(
|
||||
cx, NewHandler(cx, ControllerPullHandler, wrappedController));
|
||||
if (!onPullFulfilled) {
|
||||
return false;
|
||||
}
|
||||
Rooted<JSObject*> onPullRejected(
|
||||
cx, NewHandler(cx, ControllerPullFailedHandler, wrappedController));
|
||||
if (!onPullRejected) {
|
||||
return false;
|
||||
}
|
||||
return JS::AddPromiseReactions(cx, pullPromise, onPullFulfilled,
|
||||
onPullRejected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.10.3.
|
||||
* ReadableStreamDefaultControllerShouldCallPull ( controller )
|
||||
* Streams spec, 3.13.25.
|
||||
* ReadableByteStreamControllerShouldCallPull ( controller )
|
||||
*/
|
||||
static bool ReadableStreamControllerShouldCallPull(
|
||||
ReadableStreamController* unwrappedController) {
|
||||
// Step 1: Let stream be controller.[[controlledReadableStream]]
|
||||
// (or [[controlledReadableByteStream]]).
|
||||
ReadableStream* unwrappedStream = unwrappedController->stream();
|
||||
|
||||
// 3.10.3. Step 2:
|
||||
// If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller)
|
||||
// is false, return false.
|
||||
// This turns out to be the same as 3.13.25 steps 2-3.
|
||||
|
||||
// 3.13.25 Step 2: If stream.[[state]] is not "readable", return false.
|
||||
if (!unwrappedStream->readable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3.13.25 Step 3: If controller.[[closeRequested]] is true, return false.
|
||||
if (unwrappedController->closeRequested()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3 (or 4):
|
||||
// If controller.[[started]] is false, return false.
|
||||
if (!unwrappedController->started()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3.10.3.
|
||||
// Step 4: If ! IsReadableStreamLocked(stream) is true and
|
||||
// ! ReadableStreamGetNumReadRequests(stream) > 0, return true.
|
||||
//
|
||||
// 3.13.25.
|
||||
// Step 5: If ! ReadableStreamHasDefaultReader(stream) is true and
|
||||
// ! ReadableStreamGetNumReadRequests(stream) > 0, return true.
|
||||
// Step 6: If ! ReadableStreamHasBYOBReader(stream) is true and
|
||||
// ! ReadableStreamGetNumReadIntoRequests(stream) > 0, return true.
|
||||
//
|
||||
// All of these amount to the same thing in this implementation:
|
||||
if (unwrappedStream->locked() &&
|
||||
ReadableStreamGetNumReadRequests(unwrappedStream) > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 5 (or 7):
|
||||
// Let desiredSize be
|
||||
// ! ReadableStreamDefaultControllerGetDesiredSize(controller).
|
||||
// (ReadableByteStreamControllerGetDesiredSize in 3.13.25.)
|
||||
double desiredSize =
|
||||
ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController);
|
||||
|
||||
// Step 6 (or 8): Assert: desiredSize is not null (implicit).
|
||||
// Step 7 (or 9): If desiredSize > 0, return true.
|
||||
// Step 8 (or 10): Return false.
|
||||
return desiredSize > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.10.4.
|
||||
* ReadableStreamDefaultControllerClearAlgorithms ( controller )
|
||||
* and 3.13.4.
|
||||
* ReadableByteStreamControllerClearAlgorithms ( controller )
|
||||
*/
|
||||
void js::ReadableStreamControllerClearAlgorithms(
|
||||
Handle<ReadableStreamController*> controller) {
|
||||
// Step 1: Set controller.[[pullAlgorithm]] to undefined.
|
||||
// Step 2: Set controller.[[cancelAlgorithm]] to undefined.
|
||||
// (In this implementation, the UnderlyingSource slot is part of the
|
||||
// representation of these algorithms.)
|
||||
controller->setPullMethod(UndefinedHandleValue);
|
||||
controller->setCancelMethod(UndefinedHandleValue);
|
||||
ReadableStreamController::clearUnderlyingSource(controller);
|
||||
|
||||
// Step 3 (of 3.10.4 only) : Set controller.[[strategySizeAlgorithm]] to
|
||||
// undefined.
|
||||
if (controller->is<ReadableStreamDefaultController>()) {
|
||||
controller->as<ReadableStreamDefaultController>().setStrategySize(
|
||||
UndefinedHandleValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.10.5. ReadableStreamDefaultControllerClose ( controller )
|
||||
*/
|
||||
[[nodiscard]] bool js::ReadableStreamDefaultControllerClose(
|
||||
JSContext* cx,
|
||||
Handle<ReadableStreamDefaultController*> unwrappedController) {
|
||||
// Step 1: Let stream be controller.[[controlledReadableStream]].
|
||||
Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
|
||||
|
||||
// Step 2: Assert:
|
||||
// ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller)
|
||||
// is true.
|
||||
MOZ_ASSERT(!unwrappedController->closeRequested());
|
||||
MOZ_ASSERT(unwrappedStream->readable());
|
||||
|
||||
// Step 3: Set controller.[[closeRequested]] to true.
|
||||
unwrappedController->setCloseRequested();
|
||||
|
||||
// Step 4: If controller.[[queue]] is empty,
|
||||
Rooted<ListObject*> unwrappedQueue(cx, unwrappedController->queue());
|
||||
if (unwrappedQueue->length() == 0) {
|
||||
// Step a: Perform
|
||||
// ! ReadableStreamDefaultControllerClearAlgorithms(controller).
|
||||
ReadableStreamControllerClearAlgorithms(unwrappedController);
|
||||
|
||||
// Step b: Perform ! ReadableStreamClose(stream).
|
||||
return ReadableStreamCloseInternal(cx, unwrappedStream);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.10.6.
|
||||
* ReadableStreamDefaultControllerEnqueue ( controller, chunk )
|
||||
*/
|
||||
[[nodiscard]] bool js::ReadableStreamDefaultControllerEnqueue(
|
||||
JSContext* cx, Handle<ReadableStreamDefaultController*> unwrappedController,
|
||||
Handle<Value> chunk) {
|
||||
AssertSameCompartment(cx, chunk);
|
||||
|
||||
// Step 1: Let stream be controller.[[controlledReadableStream]].
|
||||
Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
|
||||
|
||||
// Step 2: Assert:
|
||||
// ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller) is
|
||||
// true.
|
||||
MOZ_ASSERT(!unwrappedController->closeRequested());
|
||||
MOZ_ASSERT(unwrappedStream->readable());
|
||||
|
||||
// Step 3: If ! IsReadableStreamLocked(stream) is true and
|
||||
// ! ReadableStreamGetNumReadRequests(stream) > 0, perform
|
||||
// ! ReadableStreamFulfillReadRequest(stream, chunk, false).
|
||||
if (unwrappedStream->locked() &&
|
||||
ReadableStreamGetNumReadRequests(unwrappedStream) > 0) {
|
||||
if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, unwrappedStream, chunk,
|
||||
false)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Step 4: Otherwise,
|
||||
// Step a: Let result be the result of performing
|
||||
// controller.[[strategySizeAlgorithm]], passing in chunk, and
|
||||
// interpreting the result as an ECMAScript completion value.
|
||||
// Step c: (on success) Let chunkSize be result.[[Value]].
|
||||
Rooted<Value> chunkSize(cx, Int32Value(1));
|
||||
bool success = true;
|
||||
Rooted<Value> strategySize(cx, unwrappedController->strategySize());
|
||||
if (!strategySize.isUndefined()) {
|
||||
if (!cx->compartment()->wrap(cx, &strategySize)) {
|
||||
return false;
|
||||
}
|
||||
success = Call(cx, strategySize, UndefinedHandleValue, chunk, &chunkSize);
|
||||
}
|
||||
|
||||
// Step d: Let enqueueResult be
|
||||
// EnqueueValueWithSize(controller, chunk, chunkSize).
|
||||
if (success) {
|
||||
success = EnqueueValueWithSize(cx, unwrappedController, chunk, chunkSize);
|
||||
}
|
||||
|
||||
// Step b: If result is an abrupt completion,
|
||||
// and
|
||||
// Step e: If enqueueResult is an abrupt completion,
|
||||
if (!success) {
|
||||
Rooted<Value> exn(cx);
|
||||
Rooted<SavedFrame*> stack(cx);
|
||||
if (!cx->isExceptionPending() ||
|
||||
!GetAndClearExceptionAndStack(cx, &exn, &stack)) {
|
||||
// Uncatchable error. Die immediately without erroring the
|
||||
// stream.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step b.i: Perform ! ReadableStreamDefaultControllerError(
|
||||
// controller, result.[[Value]]).
|
||||
// Step e.i: Perform ! ReadableStreamDefaultControllerError(
|
||||
// controller, enqueueResult.[[Value]]).
|
||||
if (!ReadableStreamControllerError(cx, unwrappedController, exn)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step b.ii: Return result.
|
||||
// Step e.ii: Return enqueueResult.
|
||||
// (I.e., propagate the exception.)
|
||||
cx->setPendingException(exn, stack);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Perform
|
||||
// ! ReadableStreamDefaultControllerCallPullIfNeeded(controller).
|
||||
return ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.10.7. ReadableStreamDefaultControllerError ( controller, e )
|
||||
* Streams spec, 3.13.11. ReadableByteStreamControllerError ( controller, e )
|
||||
*/
|
||||
[[nodiscard]] bool js::ReadableStreamControllerError(
|
||||
JSContext* cx, Handle<ReadableStreamController*> unwrappedController,
|
||||
Handle<Value> e) {
|
||||
MOZ_ASSERT(!cx->isExceptionPending());
|
||||
AssertSameCompartment(cx, e);
|
||||
|
||||
// Step 1: Let stream be controller.[[controlledReadableStream]]
|
||||
// (or controller.[[controlledReadableByteStream]]).
|
||||
Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
|
||||
|
||||
// Step 2: If stream.[[state]] is not "readable", return.
|
||||
if (!unwrappedStream->readable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 3 of 3.13.10:
|
||||
// Perform ! ReadableByteStreamControllerClearPendingPullIntos(controller).
|
||||
if (unwrappedController->is<ReadableByteStreamController>()) {
|
||||
Rooted<ReadableByteStreamController*> unwrappedByteStreamController(
|
||||
cx, &unwrappedController->as<ReadableByteStreamController>());
|
||||
if (!ReadableByteStreamControllerClearPendingPullIntos(
|
||||
cx, unwrappedByteStreamController)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3 (or 4): Perform ! ResetQueue(controller).
|
||||
if (!ResetQueue(cx, unwrappedController)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 4 (or 5):
|
||||
// Perform ! ReadableStreamDefaultControllerClearAlgorithms(controller)
|
||||
// (or ReadableByteStreamControllerClearAlgorithms(controller)).
|
||||
ReadableStreamControllerClearAlgorithms(unwrappedController);
|
||||
|
||||
// Step 5 (or 6): Perform ! ReadableStreamError(stream, e).
|
||||
return ReadableStreamErrorInternal(cx, unwrappedStream, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.10.8.
|
||||
* ReadableStreamDefaultControllerGetDesiredSize ( controller )
|
||||
* Streams spec 3.13.14.
|
||||
* ReadableByteStreamControllerGetDesiredSize ( controller )
|
||||
*/
|
||||
[[nodiscard]] double js::ReadableStreamControllerGetDesiredSizeUnchecked(
|
||||
ReadableStreamController* controller) {
|
||||
// Steps 1-4 done at callsites, so only assert that they have been done.
|
||||
#if DEBUG
|
||||
ReadableStream* stream = controller->stream();
|
||||
MOZ_ASSERT(!(stream->errored() || stream->closed()));
|
||||
#endif // DEBUG
|
||||
|
||||
// Step 5: Return controller.[[strategyHWM]] − controller.[[queueTotalSize]].
|
||||
return controller->strategyHWM() - controller->queueTotalSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.10.11.
|
||||
* SetUpReadableStreamDefaultController(stream, controller,
|
||||
* startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark,
|
||||
* sizeAlgorithm )
|
||||
*
|
||||
* The standard algorithm takes a `controller` argument which must be a new,
|
||||
* blank object. This implementation creates a new controller instead.
|
||||
*
|
||||
* In the spec, three algorithms (startAlgorithm, pullAlgorithm,
|
||||
* cancelAlgorithm) are passed as arguments to this routine. This
|
||||
* implementation passes these "algorithms" as data, using four arguments:
|
||||
* sourceAlgorithms, underlyingSource, pullMethod, and cancelMethod. The
|
||||
* sourceAlgorithms argument tells how to interpret the other three:
|
||||
*
|
||||
* - SourceAlgorithms::Script - We're creating a stream from a JS source.
|
||||
* The caller is `new ReadableStream(underlyingSource)` or
|
||||
* `JS::NewReadableDefaultStreamObject`. `underlyingSource` is the
|
||||
* source; `pullMethod` and `cancelMethod` are its .pull and
|
||||
* .cancel methods, which the caller has already extracted and
|
||||
* type-checked: each one must be either a callable JS object or undefined.
|
||||
*
|
||||
* Script streams use the start/pull/cancel algorithms defined in
|
||||
* 3.10.12. SetUpReadableStreamDefaultControllerFromUnderlyingSource, which
|
||||
* call JS methods of the underlyingSource.
|
||||
*
|
||||
* - SourceAlgorithms::Tee - We're creating a tee stream. `underlyingSource`
|
||||
* is a TeeState object. `pullMethod` and `cancelMethod` are undefined.
|
||||
*
|
||||
* Tee streams use the start/pull/cancel algorithms given in
|
||||
* 3.4.10. ReadableStreamTee.
|
||||
*
|
||||
* Note: All arguments must be same-compartment with cx. ReadableStream
|
||||
* controllers are always created in the same compartment as the stream.
|
||||
*/
|
||||
[[nodiscard]] bool js::SetUpReadableStreamDefaultController(
|
||||
JSContext* cx, Handle<ReadableStream*> stream,
|
||||
SourceAlgorithms sourceAlgorithms, Handle<Value> underlyingSource,
|
||||
Handle<Value> pullMethod, Handle<Value> cancelMethod, double highWaterMark,
|
||||
Handle<Value> size) {
|
||||
cx->check(stream, underlyingSource, size);
|
||||
MOZ_ASSERT(pullMethod.isUndefined() || IsCallable(pullMethod));
|
||||
MOZ_ASSERT(cancelMethod.isUndefined() || IsCallable(cancelMethod));
|
||||
MOZ_ASSERT_IF(sourceAlgorithms != SourceAlgorithms::Script,
|
||||
pullMethod.isUndefined());
|
||||
MOZ_ASSERT_IF(sourceAlgorithms != SourceAlgorithms::Script,
|
||||
cancelMethod.isUndefined());
|
||||
MOZ_ASSERT(highWaterMark >= 0);
|
||||
MOZ_ASSERT(size.isUndefined() || IsCallable(size));
|
||||
|
||||
// Done elsewhere in the standard: Create the new controller.
|
||||
Rooted<ReadableStreamDefaultController*> controller(
|
||||
cx, NewBuiltinClassInstance<ReadableStreamDefaultController>(cx));
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 1: Assert: stream.[[readableStreamController]] is undefined.
|
||||
MOZ_ASSERT(!stream->hasController());
|
||||
|
||||
// Step 2: Set controller.[[controlledReadableStream]] to stream.
|
||||
controller->setStream(stream);
|
||||
|
||||
// Step 3: Set controller.[[queue]] and controller.[[queueTotalSize]] to
|
||||
// undefined (implicit), then perform ! ResetQueue(controller).
|
||||
if (!ResetQueue(cx, controller)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 4: Set controller.[[started]], controller.[[closeRequested]],
|
||||
// controller.[[pullAgain]], and controller.[[pulling]] to false.
|
||||
controller->setFlags(0);
|
||||
|
||||
// Step 5: Set controller.[[strategySizeAlgorithm]] to sizeAlgorithm
|
||||
// and controller.[[strategyHWM]] to highWaterMark.
|
||||
controller->setStrategySize(size);
|
||||
controller->setStrategyHWM(highWaterMark);
|
||||
|
||||
// Step 6: Set controller.[[pullAlgorithm]] to pullAlgorithm.
|
||||
// (In this implementation, the pullAlgorithm is determined by the
|
||||
// underlyingSource in combination with the pullMethod field.)
|
||||
controller->setUnderlyingSource(underlyingSource);
|
||||
controller->setPullMethod(pullMethod);
|
||||
|
||||
// Step 7: Set controller.[[cancelAlgorithm]] to cancelAlgorithm.
|
||||
controller->setCancelMethod(cancelMethod);
|
||||
|
||||
// Step 8: Set stream.[[readableStreamController]] to controller.
|
||||
stream->setController(controller);
|
||||
|
||||
// Step 9: Let startResult be the result of performing startAlgorithm.
|
||||
Rooted<Value> startResult(cx);
|
||||
if (sourceAlgorithms == SourceAlgorithms::Script) {
|
||||
Rooted<Value> controllerVal(cx, ObjectValue(*controller));
|
||||
if (!InvokeOrNoop(cx, underlyingSource, cx->names().start, controllerVal,
|
||||
&startResult)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 10: Let startPromise be a promise resolved with startResult.
|
||||
Rooted<JSObject*> startPromise(
|
||||
cx, PromiseObject::unforgeableResolve(cx, startResult));
|
||||
if (!startPromise) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 11: Upon fulfillment of startPromise, [...]
|
||||
// Step 12: Upon rejection of startPromise with reason r, [...]
|
||||
Rooted<JSObject*> onStartFulfilled(
|
||||
cx, NewHandler(cx, ReadableStreamControllerStartHandler, controller));
|
||||
if (!onStartFulfilled) {
|
||||
return false;
|
||||
}
|
||||
Rooted<JSObject*> onStartRejected(
|
||||
cx,
|
||||
NewHandler(cx, ReadableStreamControllerStartFailedHandler, controller));
|
||||
if (!onStartRejected) {
|
||||
return false;
|
||||
}
|
||||
if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled,
|
||||
onStartRejected)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.10.12.
|
||||
* SetUpReadableStreamDefaultControllerFromUnderlyingSource( stream,
|
||||
* underlyingSource, highWaterMark, sizeAlgorithm )
|
||||
*/
|
||||
[[nodiscard]] bool js::SetUpReadableStreamDefaultControllerFromUnderlyingSource(
|
||||
JSContext* cx, Handle<ReadableStream*> stream,
|
||||
Handle<Value> underlyingSource, double highWaterMark,
|
||||
Handle<Value> sizeAlgorithm) {
|
||||
// Step 1: Assert: underlyingSource is not undefined.
|
||||
MOZ_ASSERT(!underlyingSource.isUndefined());
|
||||
|
||||
// Step 2: Let controller be ObjectCreate(the original value of
|
||||
// ReadableStreamDefaultController's prototype property).
|
||||
// (Deferred to SetUpReadableStreamDefaultController.)
|
||||
|
||||
// Step 3: Let startAlgorithm be the following steps:
|
||||
// a. Return ? InvokeOrNoop(underlyingSource, "start",
|
||||
// « controller »).
|
||||
SourceAlgorithms sourceAlgorithms = SourceAlgorithms::Script;
|
||||
|
||||
// Step 4: Let pullAlgorithm be
|
||||
// ? CreateAlgorithmFromUnderlyingMethod(underlyingSource, "pull",
|
||||
// 0, « controller »).
|
||||
Rooted<Value> pullMethod(cx);
|
||||
if (!CreateAlgorithmFromUnderlyingMethod(cx, underlyingSource,
|
||||
"ReadableStream source.pull method",
|
||||
cx->names().pull, &pullMethod)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 5. Let cancelAlgorithm be
|
||||
// ? CreateAlgorithmFromUnderlyingMethod(underlyingSource,
|
||||
// "cancel", 1, « »).
|
||||
Rooted<Value> cancelMethod(cx);
|
||||
if (!CreateAlgorithmFromUnderlyingMethod(
|
||||
cx, underlyingSource, "ReadableStream source.cancel method",
|
||||
cx->names().cancel, &cancelMethod)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 6. Perform ? SetUpReadableStreamDefaultController(stream,
|
||||
// controller, startAlgorithm, pullAlgorithm, cancelAlgorithm,
|
||||
// highWaterMark, sizeAlgorithm).
|
||||
return SetUpReadableStreamDefaultController(
|
||||
cx, stream, sourceAlgorithms, underlyingSource, pullMethod, cancelMethod,
|
||||
highWaterMark, sizeAlgorithm);
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Readable stream default controller abstract operations. */
|
||||
|
||||
#ifndef builtin_streams_ReadableStreamDefaultControllerOperations_h
|
||||
#define builtin_streams_ReadableStreamDefaultControllerOperations_h
|
||||
|
||||
#include "jstypes.h" // JS_PUBLIC_API
|
||||
#include "js/RootingAPI.h" // JS::Handle
|
||||
#include "js/Value.h" // JS::Value
|
||||
|
||||
struct JS_PUBLIC_API JSContext;
|
||||
|
||||
namespace js {
|
||||
|
||||
class ReadableStream;
|
||||
class ReadableStreamController;
|
||||
class ReadableStreamDefaultController;
|
||||
|
||||
[[nodiscard]] extern bool ReadableStreamDefaultControllerEnqueue(
|
||||
JSContext* cx,
|
||||
JS::Handle<ReadableStreamDefaultController*> unwrappedController,
|
||||
JS::Handle<JS::Value> chunk);
|
||||
|
||||
[[nodiscard]] extern bool ReadableStreamControllerError(
|
||||
JSContext* cx, JS::Handle<ReadableStreamController*> unwrappedController,
|
||||
JS::Handle<JS::Value> e);
|
||||
|
||||
[[nodiscard]] extern bool ReadableStreamDefaultControllerClose(
|
||||
JSContext* cx,
|
||||
JS::Handle<ReadableStreamDefaultController*> unwrappedController);
|
||||
|
||||
[[nodiscard]] extern double ReadableStreamControllerGetDesiredSizeUnchecked(
|
||||
ReadableStreamController* controller);
|
||||
|
||||
[[nodiscard]] extern bool ReadableStreamControllerCallPullIfNeeded(
|
||||
JSContext* cx, JS::Handle<ReadableStreamController*> unwrappedController);
|
||||
|
||||
extern void ReadableStreamControllerClearAlgorithms(
|
||||
JS::Handle<ReadableStreamController*> controller);
|
||||
|
||||
/**
|
||||
* Characterizes the family of algorithms, (startAlgorithm, pullAlgorithm,
|
||||
* cancelAlgorithm), associated with a readable stream.
|
||||
*
|
||||
* See the comment on SetUpReadableStreamDefaultController().
|
||||
*/
|
||||
enum class SourceAlgorithms {
|
||||
Script,
|
||||
Tee,
|
||||
};
|
||||
|
||||
[[nodiscard]] extern bool SetUpReadableStreamDefaultController(
|
||||
JSContext* cx, JS::Handle<ReadableStream*> stream,
|
||||
SourceAlgorithms sourceAlgorithms, JS::Handle<JS::Value> underlyingSource,
|
||||
JS::Handle<JS::Value> pullMethod, JS::Handle<JS::Value> cancelMethod,
|
||||
double highWaterMark, JS::Handle<JS::Value> size);
|
||||
|
||||
[[nodiscard]] extern bool
|
||||
SetUpReadableStreamDefaultControllerFromUnderlyingSource(
|
||||
JSContext* cx, JS::Handle<ReadableStream*> stream,
|
||||
JS::Handle<JS::Value> underlyingSource, double highWaterMark,
|
||||
JS::Handle<JS::Value> sizeAlgorithm);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_ReadableStreamDefaultControllerOperations_h
|
|
@ -1,264 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Class ReadableStreamDefaultReader. */
|
||||
|
||||
#include "builtin/streams/ClassSpecMacro.h" // JS_STREAMS_CLASS_SPEC
|
||||
#include "builtin/streams/MiscellaneousOperations.h" // js::ReturnPromiseRejectedWithPendingError
|
||||
#include "builtin/streams/ReadableStream.h" // js::ReadableStream
|
||||
#include "builtin/streams/ReadableStreamReader.h" // js::ForAuthorCodeBool, js::ReadableStream{,Default}Reader
|
||||
#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
|
||||
#include "js/Class.h" // JSClass, JS_NULL_CLASS_OPS
|
||||
#include "js/ErrorReport.h" // JS_ReportErrorNumberASCII
|
||||
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
|
||||
#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
|
||||
#include "vm/PromiseObject.h" // js::PromiseObject
|
||||
|
||||
#include "vm/Compartment-inl.h" // js::UnwrapAndTypeCheckThis
|
||||
#include "vm/JSObject-inl.h" // js::NewObjectWithClassProto
|
||||
#include "vm/NativeObject-inl.h" // js::ThrowIfNotConstructing
|
||||
|
||||
using JS::CallArgs;
|
||||
using JS::CallArgsFromVp;
|
||||
using JS::Handle;
|
||||
using JS::Rooted;
|
||||
using JS::Value;
|
||||
|
||||
using js::ForAuthorCodeBool;
|
||||
using js::GetErrorMessage;
|
||||
using js::ListObject;
|
||||
using js::NewObjectWithClassProto;
|
||||
using js::PromiseObject;
|
||||
using js::ReadableStream;
|
||||
using js::ReadableStreamDefaultReader;
|
||||
using js::ReadableStreamReader;
|
||||
using js::UnwrapAndTypeCheckThis;
|
||||
|
||||
/*** 3.6. Class ReadableStreamDefaultReader *********************************/
|
||||
|
||||
/**
|
||||
* Stream spec, 3.6.3. new ReadableStreamDefaultReader ( stream )
|
||||
* Steps 2-4.
|
||||
*/
|
||||
[[nodiscard]] ReadableStreamDefaultReader*
|
||||
js::CreateReadableStreamDefaultReader(JSContext* cx,
|
||||
Handle<ReadableStream*> unwrappedStream,
|
||||
ForAuthorCodeBool forAuthorCode,
|
||||
Handle<JSObject*> proto /* = nullptr */) {
|
||||
Rooted<ReadableStreamDefaultReader*> reader(
|
||||
cx, NewObjectWithClassProto<ReadableStreamDefaultReader>(cx, proto));
|
||||
if (!reader) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 2: If ! IsReadableStreamLocked(stream) is true, throw a TypeError
|
||||
// exception.
|
||||
if (unwrappedStream->locked()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAM_LOCKED);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 3: Perform ! ReadableStreamReaderGenericInitialize(this, stream).
|
||||
// Step 4: Set this.[[readRequests]] to a new empty List.
|
||||
if (!ReadableStreamReaderGenericInitialize(cx, reader, unwrappedStream,
|
||||
forAuthorCode)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream spec, 3.6.3. new ReadableStreamDefaultReader ( stream )
|
||||
*/
|
||||
bool ReadableStreamDefaultReader::constructor(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
if (!ThrowIfNotConstructing(cx, args, "ReadableStreamDefaultReader")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Implicit in the spec: Find the prototype object to use.
|
||||
Rooted<JSObject*> proto(cx);
|
||||
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Null, &proto)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 1: If ! IsReadableStream(stream) is false, throw a TypeError
|
||||
// exception.
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, UnwrapAndTypeCheckArgument<ReadableStream>(
|
||||
cx, args, "ReadableStreamDefaultReader constructor", 0));
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<JSObject*> reader(
|
||||
cx, CreateReadableStreamDefaultReader(cx, unwrappedStream,
|
||||
ForAuthorCodeBool::Yes, proto));
|
||||
if (!reader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*reader);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.6.4.1 get closed
|
||||
*/
|
||||
[[nodiscard]] static bool ReadableStreamDefaultReader_closed(JSContext* cx,
|
||||
unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
|
||||
// rejected with a TypeError exception.
|
||||
Rooted<ReadableStreamDefaultReader*> unwrappedReader(
|
||||
cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultReader>(cx, args,
|
||||
"get closed"));
|
||||
if (!unwrappedReader) {
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 2: Return this.[[closedPromise]].
|
||||
Rooted<JSObject*> closedPromise(cx, unwrappedReader->closedPromise());
|
||||
if (!cx->compartment()->wrap(cx, &closedPromise)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*closedPromise);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.6.4.2. cancel ( reason )
|
||||
*/
|
||||
[[nodiscard]] static bool ReadableStreamDefaultReader_cancel(JSContext* cx,
|
||||
unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
|
||||
// rejected with a TypeError exception.
|
||||
Rooted<ReadableStreamDefaultReader*> unwrappedReader(
|
||||
cx,
|
||||
UnwrapAndTypeCheckThis<ReadableStreamDefaultReader>(cx, args, "cancel"));
|
||||
if (!unwrappedReader) {
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
|
||||
// rejected with a TypeError exception.
|
||||
if (!unwrappedReader->hasStream()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAMREADER_NOT_OWNED, "cancel");
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 3: Return ! ReadableStreamReaderGenericCancel(this, reason).
|
||||
JSObject* cancelPromise =
|
||||
ReadableStreamReaderGenericCancel(cx, unwrappedReader, args.get(0));
|
||||
if (!cancelPromise) {
|
||||
return false;
|
||||
}
|
||||
args.rval().setObject(*cancelPromise);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.6.4.3 read ( )
|
||||
*/
|
||||
[[nodiscard]] static bool ReadableStreamDefaultReader_read(JSContext* cx,
|
||||
unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
|
||||
// rejected with a TypeError exception.
|
||||
Rooted<ReadableStreamDefaultReader*> unwrappedReader(
|
||||
cx,
|
||||
UnwrapAndTypeCheckThis<ReadableStreamDefaultReader>(cx, args, "read"));
|
||||
if (!unwrappedReader) {
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
|
||||
// rejected with a TypeError exception.
|
||||
if (!unwrappedReader->hasStream()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAMREADER_NOT_OWNED, "read");
|
||||
return ReturnPromiseRejectedWithPendingError(cx, args);
|
||||
}
|
||||
|
||||
// Step 3: Return ! ReadableStreamDefaultReaderRead(this, true).
|
||||
PromiseObject* readPromise =
|
||||
js::ReadableStreamDefaultReaderRead(cx, unwrappedReader);
|
||||
if (!readPromise) {
|
||||
return false;
|
||||
}
|
||||
args.rval().setObject(*readPromise);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.6.4.4. releaseLock ( )
|
||||
*/
|
||||
static bool ReadableStreamDefaultReader_releaseLock(JSContext* cx,
|
||||
unsigned argc, Value* vp) {
|
||||
// Step 1: If ! IsReadableStreamDefaultReader(this) is false,
|
||||
// throw a TypeError exception.
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
Rooted<ReadableStreamDefaultReader*> reader(
|
||||
cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultReader>(cx, args,
|
||||
"releaseLock"));
|
||||
if (!reader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: If this.[[ownerReadableStream]] is undefined, return.
|
||||
if (!reader->hasStream()) {
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 3: If this.[[readRequests]] is not empty, throw a TypeError exception.
|
||||
Value val = reader->getFixedSlot(ReadableStreamReader::Slot_Requests);
|
||||
if (!val.isUndefined()) {
|
||||
ListObject* readRequests = &val.toObject().as<ListObject>();
|
||||
if (readRequests->length() != 0) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAMREADER_NOT_EMPTY,
|
||||
"releaseLock");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Perform ! ReadableStreamReaderGenericRelease(this).
|
||||
if (!js::ReadableStreamReaderGenericRelease(cx, reader)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
static const JSFunctionSpec ReadableStreamDefaultReader_methods[] = {
|
||||
JS_FN("cancel", ReadableStreamDefaultReader_cancel, 1, 0),
|
||||
JS_FN("read", ReadableStreamDefaultReader_read, 0, 0),
|
||||
JS_FN("releaseLock", ReadableStreamDefaultReader_releaseLock, 0, 0),
|
||||
JS_FS_END};
|
||||
|
||||
static const JSPropertySpec ReadableStreamDefaultReader_properties[] = {
|
||||
JS_PSG("closed", ReadableStreamDefaultReader_closed, 0), JS_PS_END};
|
||||
|
||||
const JSClass ReadableStreamReader::class_ = {"ReadableStreamReader"};
|
||||
|
||||
JS_STREAMS_CLASS_SPEC(ReadableStreamDefaultReader, 1, SlotCount,
|
||||
js::ClassSpec::DontDefineConstructor, 0,
|
||||
JS_NULL_CLASS_OPS);
|
|
@ -1,473 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* The interface between readable streams and controllers. */
|
||||
|
||||
#include "builtin/streams/ReadableStreamInternals.h"
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
|
||||
|
||||
#include <stdint.h> // uint32_t
|
||||
|
||||
#include "jsfriendapi.h" // js::AssertSameCompartment
|
||||
|
||||
#include "builtin/streams/ReadableStreamController.h" // js::ReadableStreamController{,CancelSteps}
|
||||
#include "builtin/streams/ReadableStreamReader.h" // js::ReadableStream{,Default}Reader, js::ForAuthorCodeBool
|
||||
#include "gc/AllocKind.h" // js::gc::AllocKind
|
||||
#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
|
||||
#include "js/GCAPI.h" // JS::AutoSuppressGCAnalysis
|
||||
#include "js/Promise.h" // JS::CallOriginalPromiseThen, JS::ResolvePromise
|
||||
#include "js/Result.h" // JS_TRY_VAR_OR_RETURN_NULL
|
||||
#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
|
||||
#include "js/Stream.h" // JS::ReadableStreamUnderlyingSource, JS::ReadableStreamMode
|
||||
#include "js/Value.h" // JS::Value, JS::{Boolean,Object}Value, JS::UndefinedHandleValue
|
||||
#include "vm/JSContext.h" // JSContext
|
||||
#include "vm/JSFunction.h" // JSFunction, js::NewNativeFunction
|
||||
#include "vm/JSObject.h" // js::GenericObject
|
||||
#include "vm/NativeObject.h" // js::NativeObject, js::PlainObject
|
||||
#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
|
||||
#include "vm/Realm.h" // JS::Realm
|
||||
#include "vm/StringType.h" // js::PropertyName
|
||||
|
||||
#include "builtin/Promise-inl.h" // js::SetSettledPromiseIsHandled
|
||||
#include "builtin/streams/MiscellaneousOperations-inl.h" // js::{Reject,Resolve}UnwrappedPromiseWithUndefined
|
||||
#include "builtin/streams/ReadableStreamReader-inl.h" // js::js::UnwrapReaderFromStream{,NoThrow}
|
||||
#include "vm/Compartment-inl.h" // JS::Compartment::wrap
|
||||
#include "vm/JSContext-inl.h" // JSContext::check
|
||||
#include "vm/List-inl.h" // js::ListObject, js::AppendToListInFixedSlot, js::StoreNewListInFixedSlot
|
||||
#include "vm/PlainObject-inl.h" // js::PlainObject::createWithTemplate
|
||||
#include "vm/Realm-inl.h" // JS::Realm
|
||||
|
||||
using JS::BooleanValue;
|
||||
using JS::CallArgs;
|
||||
using JS::CallArgsFromVp;
|
||||
using JS::Handle;
|
||||
using JS::ObjectValue;
|
||||
using JS::ResolvePromise;
|
||||
using JS::Rooted;
|
||||
using JS::UndefinedHandleValue;
|
||||
using JS::Value;
|
||||
|
||||
using js::PlainObject;
|
||||
using js::ReadableStream;
|
||||
|
||||
/*** 3.5. The interface between readable streams and controllers ************/
|
||||
|
||||
/**
|
||||
* Streams spec, 3.5.1.
|
||||
* ReadableStreamAddReadIntoRequest ( stream, forAuthorCode )
|
||||
* Streams spec, 3.5.2.
|
||||
* ReadableStreamAddReadRequest ( stream, forAuthorCode )
|
||||
*
|
||||
* Our implementation does not pass around forAuthorCode parameters in the same
|
||||
* places as the standard, but the effect is the same. See the comment on
|
||||
* `ReadableStreamReader::forAuthorCode()`.
|
||||
*/
|
||||
[[nodiscard]] js::PromiseObject* js::ReadableStreamAddReadOrReadIntoRequest(
|
||||
JSContext* cx, Handle<ReadableStream*> unwrappedStream) {
|
||||
// Step 1: Assert: ! IsReadableStream{BYOB,Default}Reader(stream.[[reader]])
|
||||
// is true.
|
||||
// (Only default readers exist so far.)
|
||||
Rooted<ReadableStreamReader*> unwrappedReader(
|
||||
cx, UnwrapReaderFromStream(cx, unwrappedStream));
|
||||
if (!unwrappedReader) {
|
||||
return nullptr;
|
||||
}
|
||||
MOZ_ASSERT(unwrappedReader->is<ReadableStreamDefaultReader>());
|
||||
|
||||
// Step 2 of 3.5.1: Assert: stream.[[state]] is "readable" or "closed".
|
||||
// Step 2 of 3.5.2: Assert: stream.[[state]] is "readable".
|
||||
MOZ_ASSERT(unwrappedStream->readable() || unwrappedStream->closed());
|
||||
MOZ_ASSERT_IF(unwrappedReader->is<ReadableStreamDefaultReader>(),
|
||||
unwrappedStream->readable());
|
||||
|
||||
// Step 3: Let promise be a new promise.
|
||||
Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
|
||||
if (!promise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 4: Let read{Into}Request be
|
||||
// Record {[[promise]]: promise, [[forAuthorCode]]: forAuthorCode}.
|
||||
// Step 5: Append read{Into}Request as the last element of
|
||||
// stream.[[reader]].[[read{Into}Requests]].
|
||||
// Since we don't need the [[forAuthorCode]] field (see the comment on
|
||||
// `ReadableStreamReader::forAuthorCode()`), we elide the Record and store
|
||||
// only the promise.
|
||||
if (!AppendToListInFixedSlot(cx, unwrappedReader,
|
||||
ReadableStreamReader::Slot_Requests, promise)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 6: Return promise.
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for transforming the result of promise fulfillment/rejection.
|
||||
*/
|
||||
static bool ReturnUndefined(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.5.3. ReadableStreamCancel ( stream, reason )
|
||||
*/
|
||||
[[nodiscard]] JSObject* js::ReadableStreamCancel(
|
||||
JSContext* cx, Handle<ReadableStream*> unwrappedStream,
|
||||
Handle<Value> reason) {
|
||||
AssertSameCompartment(cx, reason);
|
||||
|
||||
// Step 1: Set stream.[[disturbed]] to true.
|
||||
unwrappedStream->setDisturbed();
|
||||
|
||||
// Step 2: If stream.[[state]] is "closed", return a promise resolved with
|
||||
// undefined.
|
||||
if (unwrappedStream->closed()) {
|
||||
return PromiseResolvedWithUndefined(cx);
|
||||
}
|
||||
|
||||
// Step 3: If stream.[[state]] is "errored", return a promise rejected with
|
||||
// stream.[[storedError]].
|
||||
if (unwrappedStream->errored()) {
|
||||
Rooted<Value> storedError(cx, unwrappedStream->storedError());
|
||||
if (!cx->compartment()->wrap(cx, &storedError)) {
|
||||
return nullptr;
|
||||
}
|
||||
return PromiseObject::unforgeableReject(cx, storedError);
|
||||
}
|
||||
|
||||
// Step 4: Perform ! ReadableStreamClose(stream).
|
||||
if (!ReadableStreamCloseInternal(cx, unwrappedStream)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 5: Let sourceCancelPromise be
|
||||
// ! stream.[[readableStreamController]].[[CancelSteps]](reason).
|
||||
Rooted<ReadableStreamController*> unwrappedController(
|
||||
cx, unwrappedStream->controller());
|
||||
Rooted<JSObject*> sourceCancelPromise(
|
||||
cx, ReadableStreamControllerCancelSteps(cx, unwrappedController, reason));
|
||||
if (!sourceCancelPromise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 6: Return the result of reacting to sourceCancelPromise with a
|
||||
// fulfillment step that returns undefined.
|
||||
Handle<PropertyName*> funName = cx->names().empty;
|
||||
Rooted<JSFunction*> returnUndefined(
|
||||
cx, NewNativeFunction(cx, ReturnUndefined, 0, funName,
|
||||
gc::AllocKind::FUNCTION, GenericObject));
|
||||
if (!returnUndefined) {
|
||||
return nullptr;
|
||||
}
|
||||
return JS::CallOriginalPromiseThen(cx, sourceCancelPromise, returnUndefined,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.5.4. ReadableStreamClose ( stream )
|
||||
*/
|
||||
[[nodiscard]] bool js::ReadableStreamCloseInternal(
|
||||
JSContext* cx, Handle<ReadableStream*> unwrappedStream) {
|
||||
// Step 1: Assert: stream.[[state]] is "readable".
|
||||
MOZ_ASSERT(unwrappedStream->readable());
|
||||
|
||||
// Step 2: Set stream.[[state]] to "closed".
|
||||
unwrappedStream->setClosed();
|
||||
|
||||
// Step 4: If reader is undefined, return (reordered).
|
||||
if (!unwrappedStream->hasReader()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 3: Let reader be stream.[[reader]].
|
||||
Rooted<ReadableStreamReader*> unwrappedReader(
|
||||
cx, UnwrapReaderFromStream(cx, unwrappedStream));
|
||||
if (!unwrappedReader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 5: If ! IsReadableStreamDefaultReader(reader) is true,
|
||||
if (unwrappedReader->is<ReadableStreamDefaultReader>()) {
|
||||
ForAuthorCodeBool forAuthorCode = unwrappedReader->forAuthorCode();
|
||||
|
||||
// Step a: Repeat for each readRequest that is an element of
|
||||
// reader.[[readRequests]],
|
||||
Rooted<ListObject*> unwrappedReadRequests(cx, unwrappedReader->requests());
|
||||
uint32_t len = unwrappedReadRequests->length();
|
||||
Rooted<JSObject*> readRequest(cx);
|
||||
Rooted<JSObject*> resultObj(cx);
|
||||
Rooted<Value> resultVal(cx);
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
// Step i: Resolve readRequest.[[promise]] with
|
||||
// ! ReadableStreamCreateReadResult(undefined, true,
|
||||
// readRequest.[[forAuthorCode]]).
|
||||
readRequest = &unwrappedReadRequests->getAs<JSObject>(i);
|
||||
if (!cx->compartment()->wrap(cx, &readRequest)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
resultObj = js::ReadableStreamCreateReadResult(cx, UndefinedHandleValue,
|
||||
true, forAuthorCode);
|
||||
if (!resultObj) {
|
||||
return false;
|
||||
}
|
||||
resultVal = ObjectValue(*resultObj);
|
||||
if (!ResolvePromise(cx, readRequest, resultVal)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step b: Set reader.[[readRequests]] to an empty List.
|
||||
unwrappedReader->clearRequests();
|
||||
}
|
||||
|
||||
// Step 6: Resolve reader.[[closedPromise]] with undefined.
|
||||
if (!ResolveUnwrappedPromiseWithUndefined(cx,
|
||||
unwrappedReader->closedPromise())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource) {
|
||||
// Make sure we're in the stream's compartment.
|
||||
AutoRealm ar(cx, unwrappedStream);
|
||||
JS::ReadableStreamUnderlyingSource* source =
|
||||
unwrappedStream->controller()->externalSource();
|
||||
source->onClosed(cx, unwrappedStream);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.5.5. ReadableStreamCreateReadResult ( value, done,
|
||||
* forAuthorCode )
|
||||
*/
|
||||
[[nodiscard]] PlainObject* js::ReadableStreamCreateReadResult(
|
||||
JSContext* cx, Handle<Value> value, bool done,
|
||||
ForAuthorCodeBool forAuthorCode) {
|
||||
// Step 1: Let prototype be null.
|
||||
// Step 2: If forAuthorCode is true, set prototype to %ObjectPrototype%.
|
||||
Rooted<PlainObject*> templateObject(
|
||||
cx,
|
||||
forAuthorCode == ForAuthorCodeBool::Yes
|
||||
? GlobalObject::getOrCreateIterResultTemplateObject(cx)
|
||||
: GlobalObject::getOrCreateIterResultWithoutPrototypeTemplateObject(
|
||||
cx));
|
||||
if (!templateObject) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 3: Assert: Type(done) is Boolean (implicit).
|
||||
|
||||
// Step 4: Let obj be ObjectCreate(prototype).
|
||||
PlainObject* obj = PlainObject::createWithTemplate(cx, templateObject);
|
||||
if (!obj) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 5: Perform CreateDataProperty(obj, "value", value).
|
||||
obj->setSlot(GlobalObject::IterResultObjectValueSlot, value);
|
||||
|
||||
// Step 6: Perform CreateDataProperty(obj, "done", done).
|
||||
obj->setSlot(GlobalObject::IterResultObjectDoneSlot, BooleanValue(done));
|
||||
|
||||
// Step 7: Return obj.
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.5.6. ReadableStreamError ( stream, e )
|
||||
*/
|
||||
[[nodiscard]] bool js::ReadableStreamErrorInternal(
|
||||
JSContext* cx, Handle<ReadableStream*> unwrappedStream, Handle<Value> e) {
|
||||
// Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
|
||||
|
||||
// Step 2: Assert: stream.[[state]] is "readable".
|
||||
MOZ_ASSERT(unwrappedStream->readable());
|
||||
|
||||
// Step 3: Set stream.[[state]] to "errored".
|
||||
unwrappedStream->setErrored();
|
||||
|
||||
// Step 4: Set stream.[[storedError]] to e.
|
||||
{
|
||||
AutoRealm ar(cx, unwrappedStream);
|
||||
Rooted<Value> wrappedError(cx, e);
|
||||
if (!cx->compartment()->wrap(cx, &wrappedError)) {
|
||||
return false;
|
||||
}
|
||||
unwrappedStream->setStoredError(wrappedError);
|
||||
}
|
||||
|
||||
// Step 6: If reader is undefined, return (reordered).
|
||||
if (!unwrappedStream->hasReader()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 5: Let reader be stream.[[reader]].
|
||||
Rooted<ReadableStreamReader*> unwrappedReader(
|
||||
cx, UnwrapReaderFromStream(cx, unwrappedStream));
|
||||
if (!unwrappedReader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Steps 7-8: (Identical in our implementation.)
|
||||
// Step 7.a/8.b: Repeat for each read{Into}Request that is an element of
|
||||
// reader.[[read{Into}Requests]],
|
||||
{
|
||||
Rooted<ListObject*> unwrappedReadRequests(cx, unwrappedReader->requests());
|
||||
Rooted<JSObject*> readRequest(cx);
|
||||
uint32_t len = unwrappedReadRequests->length();
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
// Step i: Reject read{Into}Request.[[promise]] with e.
|
||||
// Responses have to be created in the compartment from which the error
|
||||
// was triggered, which might not be the same as the one the request was
|
||||
// created in, so we have to wrap requests here.
|
||||
readRequest = &unwrappedReadRequests->get(i).toObject();
|
||||
if (!RejectUnwrappedPromiseWithError(cx, &readRequest, e)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 7.b/8.c: Set reader.[[read{Into}Requests]] to a new empty List.
|
||||
if (!StoreNewListInFixedSlot(cx, unwrappedReader,
|
||||
ReadableStreamReader::Slot_Requests)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 9: Reject reader.[[closedPromise]] with e.
|
||||
if (!RejectUnwrappedPromiseWithError(cx, unwrappedReader->closedPromise(),
|
||||
e)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 10: Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
|
||||
//
|
||||
// `closedPromise` can return a CCW, but that case is filtered out by step 6,
|
||||
// given the only place that can set [[closedPromise]] to a CCW is
|
||||
// 3.8.5 ReadableStreamReaderGenericRelease step 4, and
|
||||
// 3.8.5 ReadableStreamReaderGenericRelease step 6 sets
|
||||
// stream.[[reader]] to undefined.
|
||||
Rooted<JSObject*> closedPromise(cx, unwrappedReader->closedPromise());
|
||||
js::SetSettledPromiseIsHandled(cx, closedPromise.as<PromiseObject>());
|
||||
|
||||
if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource) {
|
||||
// Make sure we're in the stream's compartment.
|
||||
AutoRealm ar(cx, unwrappedStream);
|
||||
JS::ReadableStreamUnderlyingSource* source =
|
||||
unwrappedStream->controller()->externalSource();
|
||||
|
||||
// Ensure that the embedding doesn't have to deal with
|
||||
// mixed-compartment arguments to the callback.
|
||||
Rooted<Value> error(cx, e);
|
||||
if (!cx->compartment()->wrap(cx, &error)) {
|
||||
return false;
|
||||
}
|
||||
source->onErrored(cx, unwrappedStream, error);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.5.7.
|
||||
* ReadableStreamFulfillReadIntoRequest( stream, chunk, done )
|
||||
* Streams spec, 3.5.8.
|
||||
* ReadableStreamFulfillReadRequest ( stream, chunk, done )
|
||||
* These two spec functions are identical in our implementation.
|
||||
*/
|
||||
[[nodiscard]] bool js::ReadableStreamFulfillReadOrReadIntoRequest(
|
||||
JSContext* cx, Handle<ReadableStream*> unwrappedStream, Handle<Value> chunk,
|
||||
bool done) {
|
||||
cx->check(chunk);
|
||||
|
||||
// Step 1: Let reader be stream.[[reader]].
|
||||
Rooted<ReadableStreamReader*> unwrappedReader(
|
||||
cx, UnwrapReaderFromStream(cx, unwrappedStream));
|
||||
if (!unwrappedReader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: Let read{Into}Request be the first element of
|
||||
// reader.[[read{Into}Requests]].
|
||||
// Step 3: Remove read{Into}Request from reader.[[read{Into}Requests]],
|
||||
// shifting all other elements downward (so that the second becomes
|
||||
// the first, and so on).
|
||||
Rooted<ListObject*> unwrappedReadIntoRequests(cx,
|
||||
unwrappedReader->requests());
|
||||
Rooted<JSObject*> readIntoRequest(
|
||||
cx, &unwrappedReadIntoRequests->popFirstAs<JSObject>(cx));
|
||||
MOZ_ASSERT(readIntoRequest);
|
||||
if (!cx->compartment()->wrap(cx, &readIntoRequest)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 4: Resolve read{Into}Request.[[promise]] with
|
||||
// ! ReadableStreamCreateReadResult(chunk, done,
|
||||
// readIntoRequest.[[forAuthorCode]]).
|
||||
PlainObject* iterResult = ReadableStreamCreateReadResult(
|
||||
cx, chunk, done, unwrappedReader->forAuthorCode());
|
||||
if (!iterResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<Value> val(cx, ObjectValue(*iterResult));
|
||||
return ResolvePromise(cx, readIntoRequest, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.5.9. ReadableStreamGetNumReadIntoRequests ( stream )
|
||||
* Streams spec, 3.5.10. ReadableStreamGetNumReadRequests ( stream )
|
||||
* (Identical implementation.)
|
||||
*/
|
||||
uint32_t js::ReadableStreamGetNumReadRequests(ReadableStream* stream) {
|
||||
// Step 1: Return the number of elements in
|
||||
// stream.[[reader]].[[read{Into}Requests]].
|
||||
if (!stream->hasReader()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
JS::AutoSuppressGCAnalysis nogc;
|
||||
ReadableStreamReader* reader = UnwrapReaderFromStreamNoThrow(stream);
|
||||
|
||||
// Reader is a dead wrapper, treat it as non-existent.
|
||||
if (!reader) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return reader->requests()->length();
|
||||
}
|
||||
|
||||
// Streams spec, 3.5.11. ReadableStreamHasBYOBReader ( stream )
|
||||
//
|
||||
// Not implemented.
|
||||
|
||||
/**
|
||||
* Streams spec 3.5.12. ReadableStreamHasDefaultReader ( stream )
|
||||
*/
|
||||
[[nodiscard]] bool js::ReadableStreamHasDefaultReader(
|
||||
JSContext* cx, Handle<ReadableStream*> unwrappedStream, bool* result) {
|
||||
// Step 1: Let reader be stream.[[reader]].
|
||||
// Step 2: If reader is undefined, return false.
|
||||
if (!unwrappedStream->hasReader()) {
|
||||
*result = false;
|
||||
return true;
|
||||
}
|
||||
Rooted<ReadableStreamReader*> unwrappedReader(
|
||||
cx, UnwrapReaderFromStream(cx, unwrappedStream));
|
||||
if (!unwrappedReader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: If ! ReadableStreamDefaultReader(reader) is false, return false.
|
||||
// Step 4: Return true.
|
||||
*result = unwrappedReader->is<ReadableStreamDefaultReader>();
|
||||
return true;
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* The interface between readable streams and controllers. */
|
||||
|
||||
#ifndef builtin_streams_ReadableStreamInternals_h
|
||||
#define builtin_streams_ReadableStreamInternals_h
|
||||
|
||||
#include "jstypes.h" // JS_PUBLIC_API
|
||||
#include "builtin/streams/ReadableStreamReader.h" // js::ForAuthorCodeBool
|
||||
#include "js/RootingAPI.h" // JS::Handle
|
||||
#include "js/Value.h" // JS::Value
|
||||
|
||||
struct JS_PUBLIC_API JSContext;
|
||||
class JS_PUBLIC_API JSObject;
|
||||
|
||||
namespace js {
|
||||
|
||||
class PlainObject;
|
||||
class PromiseObject;
|
||||
class ReadableStream;
|
||||
|
||||
[[nodiscard]] extern PromiseObject* ReadableStreamAddReadOrReadIntoRequest(
|
||||
JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream);
|
||||
|
||||
[[nodiscard]] extern JSObject* ReadableStreamCancel(
|
||||
JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream,
|
||||
JS::Handle<JS::Value> reason);
|
||||
|
||||
[[nodiscard]] extern bool ReadableStreamCloseInternal(
|
||||
JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream);
|
||||
|
||||
[[nodiscard]] extern PlainObject* ReadableStreamCreateReadResult(
|
||||
JSContext* cx, JS::Handle<JS::Value> value, bool done,
|
||||
ForAuthorCodeBool forAuthorCode);
|
||||
|
||||
[[nodiscard]] extern bool ReadableStreamErrorInternal(
|
||||
JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream,
|
||||
JS::Handle<JS::Value> e);
|
||||
|
||||
[[nodiscard]] extern bool ReadableStreamFulfillReadOrReadIntoRequest(
|
||||
JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream,
|
||||
JS::Handle<JS::Value> chunk, bool done);
|
||||
|
||||
extern uint32_t ReadableStreamGetNumReadRequests(ReadableStream* stream);
|
||||
|
||||
[[nodiscard]] extern bool ReadableStreamHasDefaultReader(
|
||||
JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream, bool* result);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_ReadableStreamInternals_h
|
|
@ -1,633 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* General readable stream abstract operations. */
|
||||
|
||||
#include "builtin/streams/ReadableStreamOperations.h"
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
|
||||
|
||||
#include "builtin/Array.h" // js::NewDenseFullyAllocatedArray
|
||||
#include "builtin/Promise.h" // js::RejectPromiseWithPendingError
|
||||
#include "builtin/streams/ReadableStream.h" // js::ReadableStream
|
||||
#include "builtin/streams/ReadableStreamController.h" // js::ReadableStream{,Default}Controller
|
||||
#include "builtin/streams/ReadableStreamDefaultControllerOperations.h" // js::ReadableStreamDefaultController{Close,Enqueue}, js::ReadableStreamControllerError, js::SourceAlgorithms
|
||||
#include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStreamCancel
|
||||
#include "builtin/streams/ReadableStreamReader.h" // js::CreateReadableStreamDefaultReader, js::ForAuthorCodeBool, js::ReadableStream{,Default}Reader, js::ReadableStreamDefaultReaderRead
|
||||
#include "builtin/streams/TeeState.h" // js::TeeState
|
||||
#include "js/CallAndConstruct.h" // JS::IsCallable
|
||||
#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
|
||||
#include "js/Promise.h" // JS::CallOriginalPromiseThen, JS::AddPromiseReactions
|
||||
#include "js/RootingAPI.h" // JS::{,Mutable}Handle, JS::Rooted
|
||||
#include "js/Value.h" // JS::Value, JS::UndefinedHandleValue
|
||||
#include "vm/JSContext.h" // JSContext
|
||||
#include "vm/NativeObject.h" // js::NativeObject
|
||||
#include "vm/ObjectOperations.h" // js::GetProperty
|
||||
#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
|
||||
|
||||
#include "builtin/HandlerFunction-inl.h" // js::NewHandler, js::TargetFromHandler
|
||||
#include "builtin/streams/MiscellaneousOperations-inl.h" // js::ResolveUnwrappedPromiseWithValue
|
||||
#include "builtin/streams/ReadableStreamReader-inl.h" // js::UnwrapReaderFromStream
|
||||
#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::Unwrap{Callee,Internal}Slot
|
||||
#include "vm/JSContext-inl.h" // JSContext::check
|
||||
#include "vm/JSObject-inl.h" // js::IsCallable, js::NewObjectWithClassProto
|
||||
#include "vm/Realm-inl.h" // js::AutoRealm
|
||||
|
||||
using js::IsCallable;
|
||||
using js::NewHandler;
|
||||
using js::NewObjectWithClassProto;
|
||||
using js::PromiseObject;
|
||||
using js::ReadableStream;
|
||||
using js::ReadableStreamDefaultController;
|
||||
using js::ReadableStreamDefaultControllerEnqueue;
|
||||
using js::ReadableStreamDefaultReader;
|
||||
using js::ReadableStreamReader;
|
||||
using js::SourceAlgorithms;
|
||||
using js::TargetFromHandler;
|
||||
using js::TeeState;
|
||||
using js::UnwrapCalleeSlot;
|
||||
|
||||
using JS::CallArgs;
|
||||
using JS::CallArgsFromVp;
|
||||
using JS::Handle;
|
||||
using JS::MutableHandle;
|
||||
using JS::ObjectValue;
|
||||
using JS::Rooted;
|
||||
using JS::UndefinedHandleValue;
|
||||
using JS::Value;
|
||||
|
||||
/*** 3.4. General readable stream abstract operations ***********************/
|
||||
|
||||
// Streams spec, 3.4.1. AcquireReadableStreamBYOBReader ( stream )
|
||||
// Always inlined.
|
||||
|
||||
// Streams spec, 3.4.2. AcquireReadableStreamDefaultReader ( stream )
|
||||
// Always inlined. See CreateReadableStreamDefaultReader.
|
||||
|
||||
/**
|
||||
* Streams spec, 3.4.3. CreateReadableStream (
|
||||
* startAlgorithm, pullAlgorithm, cancelAlgorithm
|
||||
* [, highWaterMark [, sizeAlgorithm ] ] )
|
||||
*
|
||||
* The start/pull/cancelAlgorithm arguments are represented instead as four
|
||||
* arguments: sourceAlgorithms, underlyingSource, pullMethod, cancelMethod.
|
||||
* See the comment on SetUpReadableStreamDefaultController.
|
||||
*/
|
||||
[[nodiscard]] static ReadableStream* CreateReadableStream(
|
||||
JSContext* cx, SourceAlgorithms sourceAlgorithms,
|
||||
Handle<Value> underlyingSource,
|
||||
Handle<Value> pullMethod = UndefinedHandleValue,
|
||||
Handle<Value> cancelMethod = UndefinedHandleValue, double highWaterMark = 1,
|
||||
Handle<Value> sizeAlgorithm = UndefinedHandleValue,
|
||||
Handle<JSObject*> proto = nullptr) {
|
||||
cx->check(underlyingSource, sizeAlgorithm, proto);
|
||||
MOZ_ASSERT(sizeAlgorithm.isUndefined() || IsCallable(sizeAlgorithm));
|
||||
|
||||
// Step 1: If highWaterMark was not passed, set it to 1 (implicit).
|
||||
// Step 2: If sizeAlgorithm was not passed, set it to an algorithm that
|
||||
// returns 1 (implicit).
|
||||
// Step 3: Assert: ! IsNonNegativeNumber(highWaterMark) is true.
|
||||
MOZ_ASSERT(highWaterMark >= 0);
|
||||
|
||||
// Step 4: Let stream be ObjectCreate(the original value of ReadableStream's
|
||||
// prototype property).
|
||||
// Step 5: Perform ! InitializeReadableStream(stream).
|
||||
Rooted<ReadableStream*> stream(cx,
|
||||
ReadableStream::create(cx, nullptr, proto));
|
||||
if (!stream) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 6: Let controller be ObjectCreate(the original value of
|
||||
// ReadableStreamDefaultController's prototype property).
|
||||
// Step 7: Perform ? SetUpReadableStreamDefaultController(stream,
|
||||
// controller, startAlgorithm, pullAlgorithm, cancelAlgorithm,
|
||||
// highWaterMark, sizeAlgorithm).
|
||||
if (!SetUpReadableStreamDefaultController(
|
||||
cx, stream, sourceAlgorithms, underlyingSource, pullMethod,
|
||||
cancelMethod, highWaterMark, sizeAlgorithm)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 8: Return stream.
|
||||
return stream;
|
||||
}
|
||||
|
||||
// Streams spec, 3.4.4. CreateReadableByteStream (
|
||||
// startAlgorithm, pullAlgorithm, cancelAlgorithm
|
||||
// [, highWaterMark [, autoAllocateChunkSize ] ] )
|
||||
// Not implemented.
|
||||
|
||||
/**
|
||||
* Streams spec, 3.4.5. InitializeReadableStream ( stream )
|
||||
*/
|
||||
/* static */ [[nodiscard]] ReadableStream* ReadableStream::create(
|
||||
JSContext* cx, void* nsISupportsObject_alreadyAddreffed /* = nullptr */,
|
||||
Handle<JSObject*> proto /* = nullptr */) {
|
||||
// In the spec, InitializeReadableStream is always passed a newly created
|
||||
// ReadableStream object. We instead create it here and return it below.
|
||||
Rooted<ReadableStream*> stream(
|
||||
cx, NewObjectWithClassProto<ReadableStream>(cx, proto));
|
||||
if (!stream) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static_assert(Slot_ISupports == 0,
|
||||
"Must use right slot for JSCLASS_SLOT0_IS_NSISUPPORTS");
|
||||
JS::SetObjectISupports(stream, nsISupportsObject_alreadyAddreffed);
|
||||
|
||||
// Step 1: Set stream.[[state]] to "readable".
|
||||
stream->initStateBits(Readable);
|
||||
MOZ_ASSERT(stream->readable());
|
||||
|
||||
// Step 2: Set stream.[[reader]] and stream.[[storedError]] to
|
||||
// undefined (implicit).
|
||||
MOZ_ASSERT(!stream->hasReader());
|
||||
MOZ_ASSERT(stream->storedError().isUndefined());
|
||||
|
||||
// Step 3: Set stream.[[disturbed]] to false (done in step 1).
|
||||
MOZ_ASSERT(!stream->disturbed());
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
// Streams spec, 3.4.6. IsReadableStream ( x )
|
||||
// Using UnwrapAndTypeCheck templates instead.
|
||||
|
||||
// Streams spec, 3.4.7. IsReadableStreamDisturbed ( stream )
|
||||
// Using stream->disturbed() instead.
|
||||
|
||||
/**
|
||||
* Streams spec, 3.4.8. IsReadableStreamLocked ( stream )
|
||||
*/
|
||||
bool ReadableStream::locked() const {
|
||||
// Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
|
||||
// Step 2: If stream.[[reader]] is undefined, return false.
|
||||
// Step 3: Return true.
|
||||
// Special-casing for streams with external sources. Those can be locked
|
||||
// explicitly via JSAPI, which is indicated by a controller flag.
|
||||
// IsReadableStreamLocked is called from the controller's constructor, at
|
||||
// which point we can't yet call stream->controller(), but the source also
|
||||
// can't be locked yet.
|
||||
if (hasController() && controller()->sourceLocked()) {
|
||||
return true;
|
||||
}
|
||||
return hasReader();
|
||||
}
|
||||
|
||||
// Streams spec, 3.4.9. IsReadableStreamAsyncIterator ( x )
|
||||
//
|
||||
// Not implemented.
|
||||
|
||||
/**
|
||||
* Streams spec, 3.4.10. ReadableStreamTee steps 12.c.i-x.
|
||||
*/
|
||||
static bool TeeReaderReadHandler(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
Rooted<TeeState*> unwrappedTeeState(cx,
|
||||
UnwrapCalleeSlot<TeeState>(cx, args, 0));
|
||||
if (!unwrappedTeeState) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Handle<Value> resultVal = args.get(0);
|
||||
|
||||
// Step 12.c.i: Set reading to false.
|
||||
unwrappedTeeState->unsetReading();
|
||||
|
||||
// Step 12.c.ii: Assert: Type(result) is Object.
|
||||
Rooted<JSObject*> result(cx, &resultVal.toObject());
|
||||
|
||||
bool done;
|
||||
{
|
||||
// Step 12.c.iii: Let done be ? Get(result, "done").
|
||||
// (This can fail only if `result` was nuked.)
|
||||
Rooted<Value> doneVal(cx);
|
||||
if (!GetProperty(cx, result, result, cx->names().done, &doneVal)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 12.c.iv: Assert: Type(done) is Boolean.
|
||||
done = doneVal.toBoolean();
|
||||
}
|
||||
|
||||
if (done) {
|
||||
// Step 12.3 close steps
|
||||
|
||||
// Step 1: Set reading to false (done unconditionally above).
|
||||
// Step 2: If canceled1 is false, perform
|
||||
// ! ReadableStreamDefaultControllerClose(branch1.[[controller]]).
|
||||
if (!unwrappedTeeState->canceled1()) {
|
||||
Rooted<ReadableStreamDefaultController*> unwrappedBranch1(
|
||||
cx, unwrappedTeeState->branch1());
|
||||
if (!ReadableStreamDefaultControllerClose(cx, unwrappedBranch1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: If canceled2 is false, perform
|
||||
// ! ReadableStreamDefaultControllerClose(branch2.[[controller]]).
|
||||
if (!unwrappedTeeState->canceled2()) {
|
||||
Rooted<ReadableStreamDefaultController*> unwrappedBranch2(
|
||||
cx, unwrappedTeeState->branch2());
|
||||
if (!ReadableStreamDefaultControllerClose(cx, unwrappedBranch2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: If canceled1 is false or canceled2 is false,
|
||||
// resolve cancelPromise with undefined.
|
||||
if (!unwrappedTeeState->canceled1() || !unwrappedTeeState->canceled2()) {
|
||||
Rooted<PromiseObject*> unwrappedCancelPromise(
|
||||
cx, unwrappedTeeState->cancelPromise());
|
||||
MOZ_ASSERT(unwrappedCancelPromise != nullptr);
|
||||
|
||||
if (!ResolveUnwrappedPromiseWithUndefined(cx, unwrappedCancelPromise)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 12.c.vi: Let value be ! Get(result, "value").
|
||||
// (This can fail only if `result` was nuked.)
|
||||
Rooted<Value> value(cx);
|
||||
if (!GetProperty(cx, result, result, cx->names().value, &value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 12.c.vii: Let value1 and value2 be value.
|
||||
// Step 12.c.viii: If canceled2 is false and cloneForBranch2 is true, set
|
||||
// value2 to
|
||||
// ? StructuredDeserialize(? StructuredSerialize(value2),
|
||||
// the current Realm Record).
|
||||
// We don't yet support any specifications that use cloneForBranch2, and
|
||||
// the Streams spec doesn't offer any way for author code to enable it,
|
||||
// so it's always false here.
|
||||
auto& value1 = value;
|
||||
MOZ_ASSERT(!unwrappedTeeState->cloneForBranch2(),
|
||||
"support for cloneForBranch2=true is not yet implemented");
|
||||
auto& value2 = value;
|
||||
|
||||
Rooted<ReadableStreamDefaultController*> unwrappedController(cx);
|
||||
|
||||
// Step 12.c.ix: If canceled1 is false, perform
|
||||
// ? ReadableStreamDefaultControllerEnqueue(
|
||||
// branch1.[[readableStreamController]], value1).
|
||||
if (!unwrappedTeeState->canceled1()) {
|
||||
unwrappedController = unwrappedTeeState->branch1();
|
||||
if (!ReadableStreamDefaultControllerEnqueue(cx, unwrappedController,
|
||||
value1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 12.c.x: If canceled2 is false, perform
|
||||
// ? ReadableStreamDefaultControllerEnqueue(
|
||||
// branch2.[[readableStreamController]], value2).
|
||||
if (!unwrappedTeeState->canceled2()) {
|
||||
unwrappedController = unwrappedTeeState->branch2();
|
||||
if (!ReadableStreamDefaultControllerEnqueue(cx, unwrappedController,
|
||||
value2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.4.10. ReadableStreamTee step 12, "Let pullAlgorithm be the
|
||||
* following steps:"
|
||||
*/
|
||||
[[nodiscard]] PromiseObject* js::ReadableStreamTee_Pull(
|
||||
JSContext* cx, JS::Handle<TeeState*> unwrappedTeeState) {
|
||||
// Combine step 12.a/12.e far below, and handle steps 12.b-12.d after
|
||||
// inverting step 12.a's "If reading is true" condition.
|
||||
if (!unwrappedTeeState->reading()) {
|
||||
// Step 12.b: Set reading to true.
|
||||
unwrappedTeeState->setReading();
|
||||
|
||||
// Implicit in the spec: Unpack `reader` from the TeeState (by way of the
|
||||
// stream stored in one of its slots).
|
||||
Rooted<ReadableStreamDefaultReader*> unwrappedReader(cx);
|
||||
{
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, UnwrapInternalSlot<ReadableStream>(cx, unwrappedTeeState,
|
||||
TeeState::Slot_Stream));
|
||||
if (!unwrappedStream) {
|
||||
return nullptr;
|
||||
}
|
||||
ReadableStreamReader* unwrappedReaderObj =
|
||||
UnwrapReaderFromStream(cx, unwrappedStream);
|
||||
if (!unwrappedReaderObj) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
unwrappedReader = &unwrappedReaderObj->as<ReadableStreamDefaultReader>();
|
||||
}
|
||||
|
||||
// Step 12.c: Let readPromise be the result of reacting to
|
||||
// ! ReadableStreamDefaultReaderRead(reader) with the following
|
||||
// fulfillment steps given the argument result: [...]
|
||||
// Step 12.d: Set readPromise.[[PromiseIsHandled]] to true.
|
||||
|
||||
// First, perform |ReadableStreamDefaultReaderRead(reader)|.
|
||||
Rooted<PromiseObject*> readerReadResultPromise(
|
||||
cx, js::ReadableStreamDefaultReaderRead(cx, unwrappedReader));
|
||||
if (!readerReadResultPromise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Next, create a function to perform the fulfillment steps under step 12.c
|
||||
// (implemented in the |TeeReaderReadHandler| C++ function).
|
||||
Rooted<JSObject*> teeState(cx, unwrappedTeeState);
|
||||
if (!cx->compartment()->wrap(cx, &teeState)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Rooted<JSObject*> onFulfilled(
|
||||
cx, NewHandler(cx, TeeReaderReadHandler, teeState));
|
||||
if (!onFulfilled) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Finally, perform those fulfillment steps when |readerReadResultPromise|
|
||||
// fulfills. (Step 12.c doesn't provide rejection steps, so don't handle
|
||||
// rejection.)
|
||||
//
|
||||
// The spec's |readPromise| promise is unobservable, so implement this using
|
||||
// a JSAPI function that acts as if it created |readPromise| but doesn't
|
||||
// actually do so.
|
||||
//
|
||||
// Step 12.d causes |readPromise| to be treated as handled, even if it
|
||||
// rejects. Use |JS::AddPromiseReactionsIgnoringUnhandledRejection|, not
|
||||
// |JS::AddPromiseReactions|, to avoid reporting a freshly-consed-up promise
|
||||
// as rejected if |readerReadResultPromise| rejects.
|
||||
if (!JS::AddPromiseReactionsIgnoringUnhandledRejection(
|
||||
cx, readerReadResultPromise, onFulfilled, nullptr)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 12.a: (If reading is true,) return a promise resolved with undefined.
|
||||
// Step 12.e: Return a promise resolved with undefined.
|
||||
return PromiseResolvedWithUndefined(cx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel one branch of a tee'd stream with the given |reason_|.
|
||||
*
|
||||
* Streams spec, 3.4.10. ReadableStreamTee steps 13 and 14: "Let
|
||||
* cancel1Algorithm/cancel2Algorithm be the following steps, taking a reason
|
||||
* argument:"
|
||||
*/
|
||||
[[nodiscard]] JSObject* js::ReadableStreamTee_Cancel(
|
||||
JSContext* cx, JS::Handle<TeeState*> unwrappedTeeState,
|
||||
JS::Handle<ReadableStreamDefaultController*> unwrappedBranch,
|
||||
JS::Handle<Value> reason) {
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, UnwrapInternalSlot<ReadableStream>(cx, unwrappedTeeState,
|
||||
TeeState::Slot_Stream));
|
||||
if (!unwrappedStream) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool bothBranchesCanceled = false;
|
||||
|
||||
// Step 13/14.a: Set canceled1/canceled2 to true.
|
||||
// Step 13/14.b: Set reason1/reason2 to reason.
|
||||
{
|
||||
AutoRealm ar(cx, unwrappedTeeState);
|
||||
|
||||
Rooted<Value> unwrappedReason(cx, reason);
|
||||
if (!cx->compartment()->wrap(cx, &unwrappedReason)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (unwrappedBranch->isTeeBranch1()) {
|
||||
unwrappedTeeState->setCanceled1(unwrappedReason);
|
||||
bothBranchesCanceled = unwrappedTeeState->canceled2();
|
||||
} else {
|
||||
MOZ_ASSERT(unwrappedBranch->isTeeBranch2());
|
||||
unwrappedTeeState->setCanceled2(unwrappedReason);
|
||||
bothBranchesCanceled = unwrappedTeeState->canceled1();
|
||||
}
|
||||
}
|
||||
|
||||
Rooted<PromiseObject*> unwrappedCancelPromise(
|
||||
cx, unwrappedTeeState->cancelPromise());
|
||||
MOZ_ASSERT(unwrappedCancelPromise != nullptr);
|
||||
|
||||
// Step 13/14.c: If canceled2/canceled1 is true,
|
||||
if (bothBranchesCanceled) {
|
||||
// Step 13/14.c.i: Let compositeReason be
|
||||
// ! CreateArrayFromList(« reason1, reason2 »).
|
||||
Rooted<Value> compositeReason(cx);
|
||||
{
|
||||
Rooted<Value> reason1(cx, unwrappedTeeState->reason1());
|
||||
Rooted<Value> reason2(cx, unwrappedTeeState->reason2());
|
||||
if (!cx->compartment()->wrap(cx, &reason1) ||
|
||||
!cx->compartment()->wrap(cx, &reason2)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ArrayObject* reasonArray = NewDenseFullyAllocatedArray(cx, 2);
|
||||
if (!reasonArray) {
|
||||
return nullptr;
|
||||
}
|
||||
reasonArray->setDenseInitializedLength(2);
|
||||
reasonArray->initDenseElement(0, reason1);
|
||||
reasonArray->initDenseElement(1, reason2);
|
||||
|
||||
compositeReason = ObjectValue(*reasonArray);
|
||||
}
|
||||
|
||||
// Step 13/14.c.ii: Let cancelResult be
|
||||
// ! ReadableStreamCancel(stream, compositeReason).
|
||||
// In our implementation, this can fail with OOM. The best course then
|
||||
// is to reject cancelPromise with an OOM error.
|
||||
Rooted<JSObject*> cancelResult(
|
||||
cx, js::ReadableStreamCancel(cx, unwrappedStream, compositeReason));
|
||||
if (!cancelResult) {
|
||||
// Handle the OOM case mentioned above.
|
||||
AutoRealm ar(cx, unwrappedCancelPromise);
|
||||
if (!RejectPromiseWithPendingError(cx, unwrappedCancelPromise)) {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
// Step 13/14.c.iii: Resolve cancelPromise with cancelResult.
|
||||
Rooted<Value> cancelResultVal(cx, ObjectValue(*cancelResult));
|
||||
if (!ResolveUnwrappedPromiseWithValue(cx, unwrappedCancelPromise,
|
||||
cancelResultVal)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 13/14.d: Return cancelPromise.
|
||||
Rooted<JSObject*> cancelPromise(cx, unwrappedCancelPromise);
|
||||
if (!cx->compartment()->wrap(cx, &cancelPromise)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return cancelPromise;
|
||||
}
|
||||
|
||||
/*
|
||||
* https://streams.spec.whatwg.org/#readable-stream-tee
|
||||
* ReadableStreamTee(stream, cloneForBranch2)
|
||||
*
|
||||
* Step 18: Upon rejection of reader.[[closedPromise]] with reason r,
|
||||
*/
|
||||
static bool TeeReaderErroredHandler(JSContext* cx, unsigned argc,
|
||||
JS::Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
Rooted<TeeState*> teeState(cx, TargetFromHandler<TeeState>(args));
|
||||
Handle<Value> reason = args.get(0);
|
||||
|
||||
Rooted<ReadableStreamDefaultController*> unwrappedBranchController(cx);
|
||||
|
||||
// Step 18.1: Perform
|
||||
// ! ReadableStreamDefaultControllerError(
|
||||
// branch1.[[controller]], r).
|
||||
unwrappedBranchController = teeState->branch1();
|
||||
if (!ReadableStreamControllerError(cx, unwrappedBranchController, reason)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 18.2: Perform
|
||||
// ! ReadableStreamDefaultControllerError(
|
||||
// branch2.[[controller]], r).
|
||||
unwrappedBranchController = teeState->branch2();
|
||||
if (!ReadableStreamControllerError(cx, unwrappedBranchController, reason)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 18.3: If canceled1 is false or canceled2 is false,
|
||||
// resolve cancelPromise with undefined.
|
||||
if (!teeState->canceled1() || !teeState->canceled2()) {
|
||||
Rooted<PromiseObject*> unwrappedCancelPromise(cx,
|
||||
teeState->cancelPromise());
|
||||
MOZ_ASSERT(unwrappedCancelPromise != nullptr);
|
||||
|
||||
if (!ResolveUnwrappedPromiseWithUndefined(cx, unwrappedCancelPromise)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.4.10. ReadableStreamTee ( stream, cloneForBranch2 )
|
||||
*/
|
||||
[[nodiscard]] bool js::ReadableStreamTee(
|
||||
JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream,
|
||||
bool cloneForBranch2, JS::MutableHandle<ReadableStream*> branch1Stream,
|
||||
JS::MutableHandle<ReadableStream*> branch2Stream) {
|
||||
// Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
|
||||
|
||||
// Step 2: Assert: Type(cloneForBranch2) is Boolean (implicit).
|
||||
//
|
||||
// The streams spec only ever passes |cloneForBranch2 = false|. It's expected
|
||||
// that external specs that pass |cloneForBranch2 = true| will at some point
|
||||
// come into existence, but we don't presently implement any such specs.
|
||||
MOZ_ASSERT(!cloneForBranch2,
|
||||
"support for cloneForBranch2=true is not yet implemented");
|
||||
|
||||
// Step 3: Let reader be ? AcquireReadableStreamDefaultReader(stream).
|
||||
Rooted<ReadableStreamDefaultReader*> reader(
|
||||
cx, CreateReadableStreamDefaultReader(cx, unwrappedStream,
|
||||
ForAuthorCodeBool::No));
|
||||
if (!reader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Several algorithms close over the variables initialized in the next few
|
||||
// steps, so we allocate them in an object, the TeeState. The algorithms
|
||||
// also close over `stream` and `reader`, so TeeState gets a reference to
|
||||
// the stream.
|
||||
//
|
||||
// Step 4: Let reading be false.
|
||||
// Step 5: Let canceled1 be false.
|
||||
// Step 6: Let canceled2 be false.
|
||||
// Step 7: Let reason1 be undefined.
|
||||
// Step 8: Let reason2 be undefined.
|
||||
// Step 9: Let branch1 be undefined.
|
||||
// Step 10: Let branch2 be undefined.
|
||||
// Step 11: Let cancelPromise be a new promise.
|
||||
Rooted<TeeState*> teeState(cx, TeeState::create(cx, unwrappedStream));
|
||||
if (!teeState) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!teeState->reading());
|
||||
MOZ_ASSERT(!teeState->canceled1());
|
||||
MOZ_ASSERT(!teeState->canceled2());
|
||||
|
||||
// Step 12: Let pullAlgorithm be the following steps: [...]
|
||||
// Step 13: Let cancel1Algorithm be the following steps: [...]
|
||||
// Step 14: Let cancel2Algorithm be the following steps: [...]
|
||||
// Step 15: Let startAlgorithm be an algorithm that returns undefined.
|
||||
//
|
||||
// Implicit. Our implementation does not use objects to represent
|
||||
// [[pullAlgorithm]], [[cancelAlgorithm]], and so on. Instead, we decide
|
||||
// which one to perform based on class checks. For example, our
|
||||
// implementation of ReadableStreamControllerCallPullIfNeeded checks
|
||||
// whether the stream's underlyingSource is a TeeState object.
|
||||
|
||||
// Step 16: Set branch1 to
|
||||
// ! CreateReadableStream(startAlgorithm, pullAlgorithm,
|
||||
// cancel1Algorithm).
|
||||
Rooted<Value> underlyingSource(cx, ObjectValue(*teeState));
|
||||
branch1Stream.set(
|
||||
CreateReadableStream(cx, SourceAlgorithms::Tee, underlyingSource));
|
||||
if (!branch1Stream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<ReadableStreamDefaultController*> branch1(cx);
|
||||
branch1 = &branch1Stream->controller()->as<ReadableStreamDefaultController>();
|
||||
branch1->setTeeBranch1();
|
||||
teeState->setBranch1(branch1);
|
||||
|
||||
// Step 17: Set branch2 to
|
||||
// ! CreateReadableStream(startAlgorithm, pullAlgorithm,
|
||||
// cancel2Algorithm).
|
||||
branch2Stream.set(
|
||||
CreateReadableStream(cx, SourceAlgorithms::Tee, underlyingSource));
|
||||
if (!branch2Stream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<ReadableStreamDefaultController*> branch2(cx);
|
||||
branch2 = &branch2Stream->controller()->as<ReadableStreamDefaultController>();
|
||||
branch2->setTeeBranch2();
|
||||
teeState->setBranch2(branch2);
|
||||
|
||||
// Step 18: Upon rejection of reader.[[closedPromise]] with reason r, [...]
|
||||
Rooted<JSObject*> closedPromise(cx, reader->closedPromise());
|
||||
|
||||
Rooted<JSObject*> onRejected(
|
||||
cx, NewHandler(cx, TeeReaderErroredHandler, teeState));
|
||||
if (!onRejected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!JS::AddPromiseReactions(cx, closedPromise, nullptr, onRejected)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 19: Return « branch1, branch2 ».
|
||||
return true;
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* General readable stream abstract operations. */
|
||||
|
||||
#ifndef builtin_streams_ReadableStreamOperations_h
|
||||
#define builtin_streams_ReadableStreamOperations_h
|
||||
|
||||
#include "js/RootingAPI.h" // JS::Handle
|
||||
#include "js/Value.h" // JS::Value
|
||||
|
||||
class JS_PUBLIC_API JSObject;
|
||||
|
||||
namespace js {
|
||||
|
||||
class PromiseObject;
|
||||
class ReadableStream;
|
||||
class ReadableStreamDefaultController;
|
||||
class TeeState;
|
||||
|
||||
[[nodiscard]] extern PromiseObject* ReadableStreamTee_Pull(
|
||||
JSContext* cx, JS::Handle<TeeState*> unwrappedTeeState);
|
||||
|
||||
[[nodiscard]] extern JSObject* ReadableStreamTee_Cancel(
|
||||
JSContext* cx, JS::Handle<TeeState*> unwrappedTeeState,
|
||||
JS::Handle<ReadableStreamDefaultController*> unwrappedBranch,
|
||||
JS::Handle<JS::Value> reason);
|
||||
|
||||
[[nodiscard]] extern bool ReadableStreamTee(
|
||||
JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream,
|
||||
bool cloneForBranch2, JS::MutableHandle<ReadableStream*> branch1Stream,
|
||||
JS::MutableHandle<ReadableStream*> branch2Stream);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_ReadableStreamOperations_h
|
|
@ -1,70 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef builtin_streams_ReadableStreamReader_inl_h
|
||||
#define builtin_streams_ReadableStreamReader_inl_h
|
||||
|
||||
#include "builtin/streams/ReadableStreamReader.h"
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
|
||||
#include "jsfriendapi.h" // JS_IsDeadWrapper
|
||||
|
||||
#include "builtin/streams/ReadableStream.h" // js::ReadableStream
|
||||
#include "js/Proxy.h" // js::IsProxy
|
||||
#include "js/RootingAPI.h" // JS::Handle
|
||||
#include "vm/NativeObject.h" // js::NativeObject::getFixedSlot
|
||||
|
||||
#include "vm/Compartment-inl.h" // js::UnwrapInternalSlot
|
||||
|
||||
namespace js {
|
||||
|
||||
/**
|
||||
* Returns the stream associated with the given reader.
|
||||
*/
|
||||
[[nodiscard]] inline ReadableStream* UnwrapStreamFromReader(
|
||||
JSContext* cx, JS::Handle<ReadableStreamReader*> reader) {
|
||||
MOZ_ASSERT(reader->hasStream());
|
||||
return UnwrapInternalSlot<ReadableStream>(cx, reader,
|
||||
ReadableStreamReader::Slot_Stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reader associated with the given stream.
|
||||
*
|
||||
* Must only be called on ReadableStreams that already have a reader
|
||||
* associated with them.
|
||||
*
|
||||
* If the reader is a wrapper, it will be unwrapped, so the result might not be
|
||||
* an object from the currently active compartment.
|
||||
*/
|
||||
[[nodiscard]] inline ReadableStreamReader* UnwrapReaderFromStream(
|
||||
JSContext* cx, JS::Handle<ReadableStream*> stream) {
|
||||
return UnwrapInternalSlot<ReadableStreamReader>(cx, stream,
|
||||
ReadableStream::Slot_Reader);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline ReadableStreamReader* UnwrapReaderFromStreamNoThrow(
|
||||
ReadableStream* stream) {
|
||||
JSObject* readerObj =
|
||||
&stream->getFixedSlot(ReadableStream::Slot_Reader).toObject();
|
||||
if (IsProxy(readerObj)) {
|
||||
if (JS_IsDeadWrapper(readerObj)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
readerObj = readerObj->maybeUnwrapAs<ReadableStreamReader>();
|
||||
if (!readerObj) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return &readerObj->as<ReadableStreamReader>();
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_ReadableStreamReader_inl_h
|
|
@ -1,275 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* ReadableStream reader abstract operations. */
|
||||
|
||||
#include "builtin/streams/ReadableStreamReader-inl.h"
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
|
||||
|
||||
#include "jsfriendapi.h" // JS_ReportErrorNumberASCII
|
||||
|
||||
#include "builtin/Stream.h" // js::ReadableStreamController, js::ReadableStreamControllerPullSteps
|
||||
#include "builtin/streams/ReadableStream.h" // js::ReadableStream
|
||||
#include "builtin/streams/ReadableStreamController.h" // js::ReadableStreamController
|
||||
#include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStream{Cancel,CreateReadResult}
|
||||
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
|
||||
#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
|
||||
#include "js/Value.h" // JS::Value, JS::UndefinedHandleValue
|
||||
#include "vm/Interpreter.h" // js::GetAndClearException
|
||||
#include "vm/JSContext.h" // JSContext
|
||||
#include "vm/PlainObject.h" // js::PlainObject
|
||||
#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
|
||||
#include "vm/Runtime.h" // JSRuntime
|
||||
|
||||
#include "builtin/Promise-inl.h" // js::SetSettledPromiseIsHandled
|
||||
#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::UnwrapInternalSlot
|
||||
#include "vm/List-inl.h" // js::StoreNewListInFixedSlot
|
||||
#include "vm/Realm-inl.h" // js::AutoRealm
|
||||
|
||||
using JS::Handle;
|
||||
using JS::Rooted;
|
||||
using JS::Value;
|
||||
|
||||
using js::PromiseObject;
|
||||
using js::ReadableStreamController;
|
||||
using js::UnwrapStreamFromReader;
|
||||
|
||||
/*** 3.8. Readable stream reader abstract operations ************************/
|
||||
|
||||
// Streams spec, 3.8.1. IsReadableStreamDefaultReader ( x )
|
||||
// Implemented via is<ReadableStreamDefaultReader>()
|
||||
|
||||
// Streams spec, 3.8.2. IsReadableStreamBYOBReader ( x )
|
||||
// Implemented via is<ReadableStreamBYOBReader>()
|
||||
|
||||
/**
|
||||
* Streams spec, 3.8.3. ReadableStreamReaderGenericCancel ( reader, reason )
|
||||
*/
|
||||
[[nodiscard]] JSObject* js::ReadableStreamReaderGenericCancel(
|
||||
JSContext* cx, Handle<ReadableStreamReader*> unwrappedReader,
|
||||
Handle<Value> reason) {
|
||||
// Step 1: Let stream be reader.[[ownerReadableStream]].
|
||||
// Step 2: Assert: stream is not undefined (implicit).
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, UnwrapStreamFromReader(cx, unwrappedReader));
|
||||
if (!unwrappedStream) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 3: Return ! ReadableStreamCancel(stream, reason).
|
||||
return js::ReadableStreamCancel(cx, unwrappedStream, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.8.4.
|
||||
* ReadableStreamReaderGenericInitialize ( reader, stream )
|
||||
*/
|
||||
[[nodiscard]] bool js::ReadableStreamReaderGenericInitialize(
|
||||
JSContext* cx, Handle<ReadableStreamReader*> reader,
|
||||
Handle<ReadableStream*> unwrappedStream, ForAuthorCodeBool forAuthorCode) {
|
||||
cx->check(reader);
|
||||
|
||||
// Step 1: Set reader.[[forAuthorCode]] to true.
|
||||
reader->setForAuthorCode(forAuthorCode);
|
||||
|
||||
// Step 2: Set reader.[[ownerReadableStream]] to stream.
|
||||
{
|
||||
Rooted<JSObject*> readerCompartmentStream(cx, unwrappedStream);
|
||||
if (!cx->compartment()->wrap(cx, &readerCompartmentStream)) {
|
||||
return false;
|
||||
}
|
||||
reader->setStream(readerCompartmentStream);
|
||||
}
|
||||
|
||||
// Step 3 is moved to the end.
|
||||
|
||||
// Step 4: If stream.[[state]] is "readable",
|
||||
Rooted<PromiseObject*> promise(cx);
|
||||
if (unwrappedStream->readable()) {
|
||||
// Step a: Set reader.[[closedPromise]] to a new promise.
|
||||
promise = PromiseObject::createSkippingExecutor(cx);
|
||||
} else if (unwrappedStream->closed()) {
|
||||
// Step 5: Otherwise, if stream.[[state]] is "closed",
|
||||
// Step a: Set reader.[[closedPromise]] to a promise resolved with
|
||||
// undefined.
|
||||
promise = PromiseResolvedWithUndefined(cx);
|
||||
} else {
|
||||
// Step 6: Otherwise,
|
||||
// Step a: Assert: stream.[[state]] is "errored".
|
||||
MOZ_ASSERT(unwrappedStream->errored());
|
||||
|
||||
// Step b: Set reader.[[closedPromise]] to a promise rejected with
|
||||
// stream.[[storedError]].
|
||||
Rooted<Value> storedError(cx, unwrappedStream->storedError());
|
||||
if (!cx->compartment()->wrap(cx, &storedError)) {
|
||||
return false;
|
||||
}
|
||||
promise = PromiseObject::unforgeableReject(cx, storedError);
|
||||
if (!promise) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step c. Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
|
||||
js::SetSettledPromiseIsHandled(cx, promise);
|
||||
}
|
||||
|
||||
if (!promise) {
|
||||
return false;
|
||||
}
|
||||
|
||||
reader->setClosedPromise(promise);
|
||||
|
||||
// Step 4 of caller 3.6.3. new ReadableStreamDefaultReader(stream):
|
||||
// Step 5 of caller 3.7.3. new ReadableStreamBYOBReader(stream):
|
||||
// Set this.[[read{Into}Requests]] to a new empty List.
|
||||
if (!StoreNewListInFixedSlot(cx, reader,
|
||||
ReadableStreamReader::Slot_Requests)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Set stream.[[reader]] to reader.
|
||||
// Doing this last prevents a partially-initialized reader from being
|
||||
// attached to the stream (and possibly left there on OOM).
|
||||
{
|
||||
AutoRealm ar(cx, unwrappedStream);
|
||||
Rooted<JSObject*> streamCompartmentReader(cx, reader);
|
||||
if (!cx->compartment()->wrap(cx, &streamCompartmentReader)) {
|
||||
return false;
|
||||
}
|
||||
unwrappedStream->setReader(streamCompartmentReader);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.8.5. ReadableStreamReaderGenericRelease ( reader )
|
||||
*/
|
||||
[[nodiscard]] bool js::ReadableStreamReaderGenericRelease(
|
||||
JSContext* cx, Handle<ReadableStreamReader*> unwrappedReader) {
|
||||
// Step 1: Assert: reader.[[ownerReadableStream]] is not undefined.
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, UnwrapStreamFromReader(cx, unwrappedReader));
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: Assert: reader.[[ownerReadableStream]].[[reader]] is reader.
|
||||
#ifdef DEBUG
|
||||
// The assertion is weakened a bit to allow for nuked wrappers.
|
||||
ReadableStreamReader* unwrappedReader2 =
|
||||
UnwrapReaderFromStreamNoThrow(unwrappedStream);
|
||||
MOZ_ASSERT_IF(unwrappedReader2, unwrappedReader2 == unwrappedReader);
|
||||
#endif
|
||||
|
||||
// Create an exception to reject promises with below. We don't have a
|
||||
// clean way to do this, unfortunately.
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAMREADER_RELEASED);
|
||||
Rooted<Value> exn(cx);
|
||||
if (!cx->isExceptionPending() || !GetAndClearException(cx, &exn)) {
|
||||
// Uncatchable error. Die immediately without resolving
|
||||
// reader.[[closedPromise]].
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: If reader.[[ownerReadableStream]].[[state]] is "readable", reject
|
||||
// reader.[[closedPromise]] with a TypeError exception.
|
||||
Rooted<PromiseObject*> unwrappedClosedPromise(cx);
|
||||
if (unwrappedStream->readable()) {
|
||||
unwrappedClosedPromise = UnwrapInternalSlot<PromiseObject>(
|
||||
cx, unwrappedReader, ReadableStreamReader::Slot_ClosedPromise);
|
||||
if (!unwrappedClosedPromise) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AutoRealm ar(cx, unwrappedClosedPromise);
|
||||
if (!cx->compartment()->wrap(cx, &exn)) {
|
||||
return false;
|
||||
}
|
||||
if (!PromiseObject::reject(cx, unwrappedClosedPromise, exn)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Step 4: Otherwise, set reader.[[closedPromise]] to a new promise
|
||||
// rejected with a TypeError exception.
|
||||
Rooted<JSObject*> closedPromise(cx,
|
||||
PromiseObject::unforgeableReject(cx, exn));
|
||||
if (!closedPromise) {
|
||||
return false;
|
||||
}
|
||||
unwrappedClosedPromise = &closedPromise->as<PromiseObject>();
|
||||
|
||||
AutoRealm ar(cx, unwrappedReader);
|
||||
if (!cx->compartment()->wrap(cx, &closedPromise)) {
|
||||
return false;
|
||||
}
|
||||
unwrappedReader->setClosedPromise(closedPromise);
|
||||
}
|
||||
|
||||
// Step 5: Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
|
||||
js::SetSettledPromiseIsHandled(cx, unwrappedClosedPromise);
|
||||
|
||||
// Step 6: Set reader.[[ownerReadableStream]].[[reader]] to undefined.
|
||||
unwrappedStream->clearReader();
|
||||
|
||||
// Step 7: Set reader.[[ownerReadableStream]] to undefined.
|
||||
unwrappedReader->clearStream();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams spec, 3.8.7.
|
||||
* ReadableStreamDefaultReaderRead ( reader [, forAuthorCode ] )
|
||||
*/
|
||||
[[nodiscard]] PromiseObject* js::ReadableStreamDefaultReaderRead(
|
||||
JSContext* cx, Handle<ReadableStreamDefaultReader*> unwrappedReader) {
|
||||
// Step 1: If forAuthorCode was not passed, set it to false (implicit).
|
||||
|
||||
// Step 2: Let stream be reader.[[ownerReadableStream]].
|
||||
// Step 3: Assert: stream is not undefined.
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, UnwrapStreamFromReader(cx, unwrappedReader));
|
||||
if (!unwrappedStream) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 4: Set stream.[[disturbed]] to true.
|
||||
unwrappedStream->setDisturbed();
|
||||
|
||||
// Step 5: If stream.[[state]] is "closed", return a promise resolved with
|
||||
// ! ReadableStreamCreateReadResult(undefined, true, forAuthorCode).
|
||||
if (unwrappedStream->closed()) {
|
||||
PlainObject* iterResult = ReadableStreamCreateReadResult(
|
||||
cx, UndefinedHandleValue, true, unwrappedReader->forAuthorCode());
|
||||
if (!iterResult) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Rooted<Value> iterResultVal(cx, JS::ObjectValue(*iterResult));
|
||||
return PromiseObject::unforgeableResolveWithNonPromise(cx, iterResultVal);
|
||||
}
|
||||
|
||||
// Step 6: If stream.[[state]] is "errored", return a promise rejected
|
||||
// with stream.[[storedError]].
|
||||
if (unwrappedStream->errored()) {
|
||||
Rooted<Value> storedError(cx, unwrappedStream->storedError());
|
||||
if (!cx->compartment()->wrap(cx, &storedError)) {
|
||||
return nullptr;
|
||||
}
|
||||
return PromiseObject::unforgeableReject(cx, storedError);
|
||||
}
|
||||
|
||||
// Step 7: Assert: stream.[[state]] is "readable".
|
||||
MOZ_ASSERT(unwrappedStream->readable());
|
||||
|
||||
// Step 8: Return ! stream.[[readableStreamController]].[[PullSteps]]().
|
||||
Rooted<ReadableStreamController*> unwrappedController(
|
||||
cx, unwrappedStream->controller());
|
||||
return ReadableStreamControllerPullSteps(cx, unwrappedController);
|
||||
}
|
|
@ -1,154 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* ReadableStream readers and generic reader operations. */
|
||||
|
||||
#ifndef builtin_streams_ReadableStreamReader_h
|
||||
#define builtin_streams_ReadableStreamReader_h
|
||||
|
||||
#include "jstypes.h" // JS_PUBLIC_API
|
||||
#include "js/Class.h" // JSClass, js::ClassSpec
|
||||
#include "js/RootingAPI.h" // JS::Handle
|
||||
#include "js/Value.h" // JS::{,Boolean,Object,Undefined}Value
|
||||
#include "vm/JSObject.h" // JSObject::is
|
||||
#include "vm/List.h" // js::ListObject
|
||||
#include "vm/NativeObject.h" // js::NativeObject
|
||||
|
||||
struct JS_PUBLIC_API JSContext;
|
||||
|
||||
namespace js {
|
||||
|
||||
class PromiseObject;
|
||||
class ReadableStream;
|
||||
|
||||
/**
|
||||
* Tells whether or not read() result objects inherit from Object.prototype.
|
||||
* Generally, they should do so only if the reader was created by author code.
|
||||
* See <https://streams.spec.whatwg.org/#readable-stream-create-read-result>.
|
||||
*/
|
||||
enum class ForAuthorCodeBool { No, Yes };
|
||||
|
||||
class ReadableStreamReader : public NativeObject {
|
||||
public:
|
||||
/**
|
||||
* Memory layout of Stream Reader instances.
|
||||
*
|
||||
* See https://streams.spec.whatwg.org/#default-reader-internal-slots and
|
||||
* https://streams.spec.whatwg.org/#byob-reader-internal-slots for details.
|
||||
*
|
||||
* Note that [[readRequests]] and [[readIntoRequests]] are treated the same
|
||||
* in our implementation.
|
||||
*
|
||||
* Of the stored values, Stream and ClosedPromise might be
|
||||
* cross-compartment wrapper wrappers.
|
||||
*
|
||||
* For Stream, this can happen if the Reader was created by applying a
|
||||
* different compartment's ReadableStream.prototype.getReader method.
|
||||
*
|
||||
* For ClosedPromise, it can be caused by applying a different
|
||||
* compartment's ReadableStream*Reader.prototype.releaseLock method.
|
||||
*
|
||||
* Requests is guaranteed to be in the same compartment as the Reader, but
|
||||
* can contain wrapped request objects from other globals.
|
||||
*/
|
||||
enum Slots {
|
||||
Slot_Stream,
|
||||
Slot_Requests,
|
||||
Slot_ClosedPromise,
|
||||
Slot_ForAuthorCode,
|
||||
SlotCount,
|
||||
};
|
||||
|
||||
bool hasStream() const { return !getFixedSlot(Slot_Stream).isUndefined(); }
|
||||
void setStream(JSObject* stream) {
|
||||
setFixedSlot(Slot_Stream, JS::ObjectValue(*stream));
|
||||
}
|
||||
void clearStream() { setFixedSlot(Slot_Stream, JS::UndefinedValue()); }
|
||||
bool isClosed() { return !hasStream(); }
|
||||
|
||||
/**
|
||||
* Tells whether this reader was created by author code.
|
||||
*
|
||||
* This returns Yes for readers created using `stream.getReader()`, and No
|
||||
* for readers created for the internal use of algorithms like
|
||||
* `stream.tee()` and `new Response(stream)`.
|
||||
*
|
||||
* The standard does not have this field. Instead, eight algorithms take a
|
||||
* forAuthorCode parameter, and a [[forAuthorCode]] field is part of each
|
||||
* read request. But the behavior is always equivalent to treating readers
|
||||
* created by author code as having a bit set on them. We implement it that
|
||||
* way for simplicity.
|
||||
*/
|
||||
ForAuthorCodeBool forAuthorCode() const {
|
||||
return getFixedSlot(Slot_ForAuthorCode).toBoolean() ? ForAuthorCodeBool::Yes
|
||||
: ForAuthorCodeBool::No;
|
||||
}
|
||||
void setForAuthorCode(ForAuthorCodeBool value) {
|
||||
setFixedSlot(Slot_ForAuthorCode,
|
||||
JS::BooleanValue(value == ForAuthorCodeBool::Yes));
|
||||
}
|
||||
|
||||
ListObject* requests() const {
|
||||
return &getFixedSlot(Slot_Requests).toObject().as<ListObject>();
|
||||
}
|
||||
void clearRequests() { setFixedSlot(Slot_Requests, JS::UndefinedValue()); }
|
||||
|
||||
JSObject* closedPromise() const {
|
||||
return &getFixedSlot(Slot_ClosedPromise).toObject();
|
||||
}
|
||||
void setClosedPromise(JSObject* wrappedPromise) {
|
||||
setFixedSlot(Slot_ClosedPromise, JS::ObjectValue(*wrappedPromise));
|
||||
}
|
||||
|
||||
static const JSClass class_;
|
||||
};
|
||||
|
||||
class ReadableStreamDefaultReader : public ReadableStreamReader {
|
||||
public:
|
||||
static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp);
|
||||
static const ClassSpec classSpec_;
|
||||
static const JSClass class_;
|
||||
static const ClassSpec protoClassSpec_;
|
||||
static const JSClass protoClass_;
|
||||
};
|
||||
|
||||
[[nodiscard]] extern ReadableStreamDefaultReader*
|
||||
CreateReadableStreamDefaultReader(JSContext* cx,
|
||||
JS::Handle<ReadableStream*> unwrappedStream,
|
||||
ForAuthorCodeBool forAuthorCode,
|
||||
JS::Handle<JSObject*> proto = nullptr);
|
||||
|
||||
[[nodiscard]] extern JSObject* ReadableStreamReaderGenericCancel(
|
||||
JSContext* cx, JS::Handle<ReadableStreamReader*> unwrappedReader,
|
||||
JS::Handle<JS::Value> reason);
|
||||
|
||||
[[nodiscard]] extern bool ReadableStreamReaderGenericInitialize(
|
||||
JSContext* cx, JS::Handle<ReadableStreamReader*> reader,
|
||||
JS::Handle<ReadableStream*> unwrappedStream,
|
||||
ForAuthorCodeBool forAuthorCode);
|
||||
|
||||
[[nodiscard]] extern bool ReadableStreamReaderGenericRelease(
|
||||
JSContext* cx, JS::Handle<ReadableStreamReader*> unwrappedReader);
|
||||
|
||||
[[nodiscard]] extern PromiseObject* ReadableStreamDefaultReaderRead(
|
||||
JSContext* cx, JS::Handle<ReadableStreamDefaultReader*> unwrappedReader);
|
||||
|
||||
} // namespace js
|
||||
|
||||
template <>
|
||||
inline bool JSObject::is<js::ReadableStreamReader>() const {
|
||||
return is<js::ReadableStreamDefaultReader>();
|
||||
}
|
||||
|
||||
namespace js {
|
||||
|
||||
[[nodiscard]] extern JSObject* CreateReadableStreamBYOBReader(
|
||||
JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream,
|
||||
ForAuthorCodeBool forAuthorCode, JS::Handle<JSObject*> proto = nullptr);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_ReadableStreamReader_h
|
|
@ -1,603 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Public and friend stream APIs for external use. */
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
|
||||
|
||||
#include <stdint.h> // uint32_t, uintptr_t
|
||||
|
||||
#include "jsfriendapi.h" // js::IsObjectInContextCompartment
|
||||
#include "jstypes.h" // JS_{FRIEND,PUBLIC}_API
|
||||
|
||||
#include "builtin/Stream.h" // js::ReadableByteStreamController{,Close}, js::ReadableStreamDefaultController{,Close}, js::StreamController
|
||||
#include "builtin/streams/ReadableStream.h" // js::ReadableStream
|
||||
#include "builtin/streams/ReadableStreamController.h" // js::CheckReadableStreamControllerCanCloseOrEnqueue
|
||||
#include "builtin/streams/ReadableStreamDefaultControllerOperations.h" // js::ReadableStreamController{Error,GetDesiredSizeUnchecked}, js::SetUpReadableStreamDefaultControllerFromUnderlyingSource
|
||||
#include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStream{Cancel,FulfillReadOrReadIntoRequest,GetNumReadRequests,HasDefaultReader}
|
||||
#include "builtin/streams/ReadableStreamOperations.h" // js::ReadableStreamTee
|
||||
#include "builtin/streams/ReadableStreamReader.h" // js::ReadableStream{,Default}Reader, js::ForAuthorCodeBool
|
||||
#include "builtin/streams/StreamController.h" // js::StreamController
|
||||
#include "gc/Zone.h" // JS::Zone
|
||||
#include "js/Context.h" // js::AssertHeapIsIdle
|
||||
#include "js/ErrorReport.h" // JS_ReportErrorNumberASCII
|
||||
#include "js/experimental/TypedData.h" // JS_GetArrayBufferViewData, JS_NewUint8Array
|
||||
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
|
||||
#include "js/GCAPI.h" // JS::AutoCheckCannotGC, JS::AutoSuppressGCAnalysis
|
||||
#include "js/Object.h" // JS::SetObjectISupports
|
||||
#include "js/RootingAPI.h" // JS::{,Mutable}Handle, JS::Rooted
|
||||
#include "js/Stream.h" // JS::ReadableStreamUnderlyingSource
|
||||
#include "js/Value.h" // JS::{,Object,Undefined}Value
|
||||
#include "vm/ArrayBufferViewObject.h" // js::ArrayBufferViewObject
|
||||
#include "vm/JSContext.h" // JSContext, CHECK_THREAD
|
||||
#include "vm/JSObject.h" // JSObject
|
||||
#include "vm/PlainObject.h" // js::PlainObject
|
||||
#include "vm/PromiseObject.h" // js::PromiseObject
|
||||
|
||||
#include "builtin/streams/ReadableStreamReader-inl.h" // js::UnwrapStreamFromReader
|
||||
#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::UnwrapAndDowncastObject
|
||||
#include "vm/JSObject-inl.h" // js::NewBuiltinClassInstance
|
||||
#include "vm/Realm-inl.h" // js::AutoRealm
|
||||
|
||||
using js::ArrayBufferViewObject;
|
||||
using js::AssertHeapIsIdle;
|
||||
using js::AutoRealm;
|
||||
using js::CheckReadableStreamControllerCanCloseOrEnqueue;
|
||||
using js::ForAuthorCodeBool;
|
||||
using js::GetErrorMessage;
|
||||
using js::IsObjectInContextCompartment;
|
||||
using js::NewBuiltinClassInstance;
|
||||
using js::PlainObject;
|
||||
using js::ReadableByteStreamController;
|
||||
using js::ReadableByteStreamControllerClose;
|
||||
using js::ReadableStream;
|
||||
using js::ReadableStreamController;
|
||||
using js::ReadableStreamControllerError;
|
||||
using js::ReadableStreamControllerGetDesiredSizeUnchecked;
|
||||
using js::ReadableStreamDefaultController;
|
||||
using js::ReadableStreamDefaultControllerClose;
|
||||
using js::ReadableStreamDefaultReader;
|
||||
using js::ReadableStreamFulfillReadOrReadIntoRequest;
|
||||
using js::ReadableStreamGetNumReadRequests;
|
||||
using js::ReadableStreamHasDefaultReader;
|
||||
using js::ReadableStreamReader;
|
||||
using js::ReadableStreamTee;
|
||||
using js::SetUpReadableStreamDefaultControllerFromUnderlyingSource;
|
||||
using js::StreamController;
|
||||
using js::UnwrapAndDowncastObject;
|
||||
using js::UnwrapStreamFromReader;
|
||||
|
||||
JS_PUBLIC_API JSObject* js::UnwrapReadableStream(JSObject* obj) {
|
||||
return obj->maybeUnwrapIf<ReadableStream>();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API JSObject* JS::NewReadableDefaultStreamObject(
|
||||
JSContext* cx, JS::Handle<JSObject*> underlyingSource /* = nullptr */,
|
||||
JS::Handle<JSFunction*> size /* = nullptr */,
|
||||
double highWaterMark /* = 1 */,
|
||||
JS::Handle<JSObject*> proto /* = nullptr */) {
|
||||
MOZ_ASSERT(!cx->zone()->isAtomsZone());
|
||||
AssertHeapIsIdle();
|
||||
CHECK_THREAD(cx);
|
||||
cx->check(underlyingSource, size, proto);
|
||||
MOZ_ASSERT(highWaterMark >= 0);
|
||||
|
||||
// A copy of ReadableStream::constructor, with most of the
|
||||
// argument-checking done implicitly by C++ type checking.
|
||||
Rooted<ReadableStream*> stream(cx, ReadableStream::create(cx));
|
||||
if (!stream) {
|
||||
return nullptr;
|
||||
}
|
||||
Rooted<Value> sourceVal(cx);
|
||||
if (underlyingSource) {
|
||||
sourceVal.setObject(*underlyingSource);
|
||||
} else {
|
||||
JSObject* source = NewPlainObject(cx);
|
||||
if (!source) {
|
||||
return nullptr;
|
||||
}
|
||||
sourceVal.setObject(*source);
|
||||
}
|
||||
Rooted<Value> sizeVal(cx, size ? ObjectValue(*size) : UndefinedValue());
|
||||
|
||||
if (!SetUpReadableStreamDefaultControllerFromUnderlyingSource(
|
||||
cx, stream, sourceVal, highWaterMark, sizeVal)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API JSObject* JS::NewReadableExternalSourceStreamObject(
|
||||
JSContext* cx, JS::ReadableStreamUnderlyingSource* underlyingSource,
|
||||
void* nsISupportsObject_alreadyAddreffed /* = nullptr */,
|
||||
Handle<JSObject*> proto /* = nullptr */) {
|
||||
MOZ_ASSERT(!cx->zone()->isAtomsZone());
|
||||
AssertHeapIsIdle();
|
||||
CHECK_THREAD(cx);
|
||||
MOZ_ASSERT(underlyingSource);
|
||||
MOZ_ASSERT((uintptr_t(underlyingSource) & 1) == 0,
|
||||
"external underlying source pointers must be aligned");
|
||||
cx->check(proto);
|
||||
|
||||
return ReadableStream::createExternalSourceStream(
|
||||
cx, underlyingSource, nsISupportsObject_alreadyAddreffed, proto);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::IsReadableStream(JSObject* obj) {
|
||||
return obj->canUnwrapAs<ReadableStream>();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::IsReadableStreamReader(JSObject* obj) {
|
||||
return obj->canUnwrapAs<ReadableStreamDefaultReader>();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::IsReadableStreamDefaultReader(JSObject* obj) {
|
||||
return obj->canUnwrapAs<ReadableStreamDefaultReader>();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
[[nodiscard]] static T* APIUnwrapAndDowncast(JSContext* cx, JSObject* obj) {
|
||||
cx->check(obj);
|
||||
return UnwrapAndDowncastObject<T>(cx, obj);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::ReadableStreamIsReadable(JSContext* cx,
|
||||
Handle<JSObject*> streamObj,
|
||||
bool* result) {
|
||||
ReadableStream* unwrappedStream =
|
||||
APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = unwrappedStream->readable();
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::ReadableStreamIsLocked(JSContext* cx,
|
||||
Handle<JSObject*> streamObj,
|
||||
bool* result) {
|
||||
ReadableStream* unwrappedStream =
|
||||
APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = unwrappedStream->locked();
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::ReadableStreamIsDisturbed(JSContext* cx,
|
||||
Handle<JSObject*> streamObj,
|
||||
bool* result) {
|
||||
ReadableStream* unwrappedStream =
|
||||
APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = unwrappedStream->disturbed();
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API JSObject* JS::ReadableStreamCancel(JSContext* cx,
|
||||
Handle<JSObject*> streamObj,
|
||||
Handle<Value> reason) {
|
||||
AssertHeapIsIdle();
|
||||
CHECK_THREAD(cx);
|
||||
cx->check(reason);
|
||||
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
|
||||
if (!unwrappedStream) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return js::ReadableStreamCancel(cx, unwrappedStream, reason);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::ReadableStreamGetMode(JSContext* cx,
|
||||
Handle<JSObject*> streamObj,
|
||||
JS::ReadableStreamMode* mode) {
|
||||
ReadableStream* unwrappedStream =
|
||||
APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*mode = unwrappedStream->mode();
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API JSObject* JS::ReadableStreamGetReader(
|
||||
JSContext* cx, Handle<JSObject*> streamObj, ReadableStreamReaderMode mode) {
|
||||
AssertHeapIsIdle();
|
||||
CHECK_THREAD(cx);
|
||||
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
|
||||
if (!unwrappedStream) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JSObject* result = CreateReadableStreamDefaultReader(cx, unwrappedStream,
|
||||
ForAuthorCodeBool::No);
|
||||
MOZ_ASSERT_IF(result, IsObjectInContextCompartment(result, cx));
|
||||
return result;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::ReadableStreamGetExternalUnderlyingSource(
|
||||
JSContext* cx, Handle<JSObject*> streamObj,
|
||||
JS::ReadableStreamUnderlyingSource** source) {
|
||||
AssertHeapIsIdle();
|
||||
CHECK_THREAD(cx);
|
||||
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource);
|
||||
if (unwrappedStream->locked()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAM_LOCKED);
|
||||
return false;
|
||||
}
|
||||
if (!unwrappedStream->readable()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE,
|
||||
"ReadableStreamGetExternalUnderlyingSource");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto unwrappedController =
|
||||
&unwrappedStream->controller()->as<ReadableByteStreamController>();
|
||||
unwrappedController->setSourceLocked();
|
||||
*source = unwrappedController->externalSource();
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::ReadableStreamReleaseExternalUnderlyingSource(
|
||||
JSContext* cx, Handle<JSObject*> streamObj) {
|
||||
ReadableStream* unwrappedStream =
|
||||
APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource);
|
||||
MOZ_ASSERT(unwrappedStream->locked());
|
||||
MOZ_ASSERT(unwrappedStream->controller()->sourceLocked());
|
||||
unwrappedStream->controller()->clearSourceLocked();
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::ReadableStreamUpdateDataAvailableFromSource(
|
||||
JSContext* cx, JS::Handle<JSObject*> streamObj, uint32_t availableData) {
|
||||
AssertHeapIsIdle();
|
||||
CHECK_THREAD(cx);
|
||||
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is based on Streams spec 3.11.4.4. enqueue(chunk) steps 1-3 and
|
||||
// 3.13.9. ReadableByteStreamControllerEnqueue(controller, chunk) 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.
|
||||
|
||||
Rooted<ReadableByteStreamController*> unwrappedController(
|
||||
cx, &unwrappedStream->controller()->as<ReadableByteStreamController>());
|
||||
|
||||
// Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
|
||||
if (unwrappedController->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 (!unwrappedController->stream()->readable()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE,
|
||||
"enqueue");
|
||||
return false;
|
||||
}
|
||||
|
||||
unwrappedController->clearPullFlags();
|
||||
|
||||
#if DEBUG
|
||||
uint32_t oldAvailableData =
|
||||
unwrappedController->getFixedSlot(StreamController::Slot_TotalSize)
|
||||
.toInt32();
|
||||
#endif // DEBUG
|
||||
unwrappedController->setQueueTotalSize(availableData);
|
||||
|
||||
// 3.139. ReadableByteStreamControllerEnqueue
|
||||
// Step 8.a: If ! ReadableStreamGetNumReadRequests(stream) is 0,
|
||||
// Reordered because for externally-sourced streams it applies regardless
|
||||
// of reader type.
|
||||
if (ReadableStreamGetNumReadRequests(unwrappedStream) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 8: If ! ReadableStreamHasDefaultReader(stream) is true
|
||||
bool hasDefaultReader;
|
||||
if (!ReadableStreamHasDefaultReader(cx, unwrappedStream, &hasDefaultReader)) {
|
||||
return false;
|
||||
}
|
||||
if (hasDefaultReader) {
|
||||
// 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);
|
||||
if (!viewObj) {
|
||||
return false;
|
||||
}
|
||||
Rooted<ArrayBufferViewObject*> transferredView(
|
||||
cx, &viewObj->as<ArrayBufferViewObject>());
|
||||
if (!transferredView) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JS::ReadableStreamUnderlyingSource* source =
|
||||
unwrappedController->externalSource();
|
||||
|
||||
size_t bytesWritten;
|
||||
{
|
||||
AutoRealm ar(cx, unwrappedStream);
|
||||
source->writeIntoReadRequestBuffer(cx, unwrappedStream, transferredView,
|
||||
availableData, &bytesWritten);
|
||||
}
|
||||
|
||||
// Step iii: Perform ! ReadableStreamFulfillReadRequest(stream,
|
||||
// transferredView,
|
||||
// false).
|
||||
Rooted<Value> chunk(cx, ObjectValue(*transferredView));
|
||||
if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, unwrappedStream, chunk,
|
||||
false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unwrappedController->setQueueTotalSize(availableData - bytesWritten);
|
||||
} else {
|
||||
// Step 9: Otherwise, if ! ReadableStreamHasBYOBReader(stream) is true,
|
||||
// [...]
|
||||
// (Omitted. BYOB readers are not implemented.)
|
||||
|
||||
// Step 10: Otherwise,
|
||||
// Step a: Assert: ! IsReadableStreamLocked(stream) is false.
|
||||
MOZ_ASSERT(!unwrappedStream->locked());
|
||||
|
||||
// Step b: Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(
|
||||
// controller, transferredBuffer, byteOffset, byteLength).
|
||||
// (Not needed for external underlying sources.)
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API void JS::ReadableStreamReleaseCCObject(JSObject* streamObj) {
|
||||
MOZ_ASSERT(JS::IsReadableStream(streamObj));
|
||||
JS::SetObjectISupports(streamObj, nullptr);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::ReadableStreamTee(JSContext* cx,
|
||||
Handle<JSObject*> streamObj,
|
||||
MutableHandle<JSObject*> branch1Obj,
|
||||
MutableHandle<JSObject*> branch2Obj) {
|
||||
AssertHeapIsIdle();
|
||||
CHECK_THREAD(cx);
|
||||
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<ReadableStream*> branch1Stream(cx);
|
||||
Rooted<ReadableStream*> branch2Stream(cx);
|
||||
if (!ReadableStreamTee(cx, unwrappedStream, false, &branch1Stream,
|
||||
&branch2Stream)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
branch1Obj.set(branch1Stream);
|
||||
branch2Obj.set(branch2Stream);
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::ReadableStreamGetDesiredSize(JSContext* cx,
|
||||
JSObject* streamObj,
|
||||
bool* hasValue,
|
||||
double* value) {
|
||||
ReadableStream* unwrappedStream =
|
||||
APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (unwrappedStream->errored()) {
|
||||
*hasValue = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
*hasValue = true;
|
||||
|
||||
if (unwrappedStream->closed()) {
|
||||
*value = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
*value = ReadableStreamControllerGetDesiredSizeUnchecked(
|
||||
unwrappedStream->controller());
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::ReadableStreamClose(JSContext* cx,
|
||||
Handle<JSObject*> streamObj) {
|
||||
AssertHeapIsIdle();
|
||||
CHECK_THREAD(cx);
|
||||
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<ReadableStreamController*> unwrappedControllerObj(
|
||||
cx, unwrappedStream->controller());
|
||||
if (!CheckReadableStreamControllerCanCloseOrEnqueue(
|
||||
cx, unwrappedControllerObj, "close")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (unwrappedControllerObj->is<ReadableStreamDefaultController>()) {
|
||||
Rooted<ReadableStreamDefaultController*> unwrappedController(cx);
|
||||
unwrappedController =
|
||||
&unwrappedControllerObj->as<ReadableStreamDefaultController>();
|
||||
return ReadableStreamDefaultControllerClose(cx, unwrappedController);
|
||||
}
|
||||
|
||||
Rooted<ReadableByteStreamController*> unwrappedController(cx);
|
||||
unwrappedController =
|
||||
&unwrappedControllerObj->as<ReadableByteStreamController>();
|
||||
return ReadableByteStreamControllerClose(cx, unwrappedController);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::ReadableStreamEnqueue(JSContext* cx,
|
||||
Handle<JSObject*> streamObj,
|
||||
Handle<Value> chunk) {
|
||||
AssertHeapIsIdle();
|
||||
CHECK_THREAD(cx);
|
||||
cx->check(chunk);
|
||||
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (unwrappedStream->mode() != JS::ReadableStreamMode::Default) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_READABLESTREAM_NOT_DEFAULT_CONTROLLER,
|
||||
"JS::ReadableStreamEnqueue");
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<ReadableStreamDefaultController*> unwrappedController(cx);
|
||||
unwrappedController =
|
||||
&unwrappedStream->controller()->as<ReadableStreamDefaultController>();
|
||||
|
||||
MOZ_ASSERT(!unwrappedController->closeRequested());
|
||||
MOZ_ASSERT(unwrappedStream->readable());
|
||||
|
||||
return ReadableStreamDefaultControllerEnqueue(cx, unwrappedController, chunk);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::ReadableStreamError(JSContext* cx,
|
||||
Handle<JSObject*> streamObj,
|
||||
Handle<Value> error) {
|
||||
AssertHeapIsIdle();
|
||||
CHECK_THREAD(cx);
|
||||
cx->check(error);
|
||||
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<ReadableStreamController*> unwrappedController(
|
||||
cx, unwrappedStream->controller());
|
||||
return ReadableStreamControllerError(cx, unwrappedController, error);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::ReadableStreamReaderIsClosed(JSContext* cx,
|
||||
Handle<JSObject*> readerObj,
|
||||
bool* result) {
|
||||
Rooted<ReadableStreamReader*> unwrappedReader(
|
||||
cx, APIUnwrapAndDowncast<ReadableStreamReader>(cx, readerObj));
|
||||
if (!unwrappedReader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = unwrappedReader->isClosed();
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::ReadableStreamReaderCancel(JSContext* cx,
|
||||
Handle<JSObject*> readerObj,
|
||||
Handle<Value> reason) {
|
||||
AssertHeapIsIdle();
|
||||
CHECK_THREAD(cx);
|
||||
cx->check(reason);
|
||||
|
||||
Rooted<ReadableStreamReader*> unwrappedReader(
|
||||
cx, APIUnwrapAndDowncast<ReadableStreamReader>(cx, readerObj));
|
||||
if (!unwrappedReader) {
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT(unwrappedReader->forAuthorCode() == ForAuthorCodeBool::No,
|
||||
"C++ code should not touch readers created by scripts");
|
||||
|
||||
return ReadableStreamReaderGenericCancel(cx, unwrappedReader, reason);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API bool JS::ReadableStreamReaderReleaseLock(
|
||||
JSContext* cx, Handle<JSObject*> readerObj) {
|
||||
AssertHeapIsIdle();
|
||||
CHECK_THREAD(cx);
|
||||
|
||||
Rooted<ReadableStreamReader*> unwrappedReader(
|
||||
cx, APIUnwrapAndDowncast<ReadableStreamReader>(cx, readerObj));
|
||||
if (!unwrappedReader) {
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT(unwrappedReader->forAuthorCode() == ForAuthorCodeBool::No,
|
||||
"C++ code should not touch readers created by scripts");
|
||||
|
||||
#ifdef DEBUG
|
||||
Rooted<ReadableStream*> unwrappedStream(
|
||||
cx, UnwrapStreamFromReader(cx, unwrappedReader));
|
||||
if (!unwrappedStream) {
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT(ReadableStreamGetNumReadRequests(unwrappedStream) == 0);
|
||||
#endif // DEBUG
|
||||
|
||||
return ReadableStreamReaderGenericRelease(cx, unwrappedReader);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API JSObject* JS::ReadableStreamDefaultReaderRead(
|
||||
JSContext* cx, Handle<JSObject*> readerObj) {
|
||||
AssertHeapIsIdle();
|
||||
CHECK_THREAD(cx);
|
||||
|
||||
Rooted<ReadableStreamDefaultReader*> unwrappedReader(
|
||||
cx, APIUnwrapAndDowncast<ReadableStreamDefaultReader>(cx, readerObj));
|
||||
if (!unwrappedReader) {
|
||||
return nullptr;
|
||||
}
|
||||
MOZ_ASSERT(unwrappedReader->forAuthorCode() == ForAuthorCodeBool::No,
|
||||
"C++ code should not touch readers created by scripts");
|
||||
|
||||
return js::ReadableStreamDefaultReaderRead(cx, unwrappedReader);
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Base stream controller inlines. */
|
||||
|
||||
#ifndef builtin_streams_StreamController_inl_h
|
||||
#define builtin_streams_StreamController_inl_h
|
||||
|
||||
#include "builtin/streams/StreamController.h" // js::StreamController
|
||||
#include "builtin/streams/ReadableStreamController.h" // js::Readable{ByteStream,StreamDefault}Controller
|
||||
#include "vm/JSObject.h" // JSObject
|
||||
|
||||
template <>
|
||||
inline bool JSObject::is<js::StreamController>() const {
|
||||
return is<js::ReadableStreamDefaultController>() ||
|
||||
is<js::ReadableByteStreamController>();
|
||||
}
|
||||
|
||||
#endif // builtin_streams_ReadableStreamController_inl_h
|
|
@ -1,51 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Base class for readable and writable stream controllers. */
|
||||
|
||||
#ifndef builtin_streams_StreamController_h
|
||||
#define builtin_streams_StreamController_h
|
||||
|
||||
#include "js/Value.h" // JS::Value, JS::NumberValue
|
||||
#include "vm/JSObject.h" // JSObject
|
||||
#include "vm/List.h" // js::ListObject
|
||||
#include "vm/NativeObject.h" // js::NativeObject
|
||||
|
||||
namespace js {
|
||||
|
||||
/**
|
||||
* Common base class of both readable and writable stream controllers.
|
||||
*/
|
||||
class StreamController : public NativeObject {
|
||||
public:
|
||||
/**
|
||||
* Memory layout for stream controllers.
|
||||
*
|
||||
* Both ReadableStreamDefaultController and ReadableByteStreamController
|
||||
* are queue containers and must have these slots at identical offsets.
|
||||
*
|
||||
* The queue is guaranteed to be in the same compartment as the container,
|
||||
* but might contain wrappers for objects from other compartments.
|
||||
*/
|
||||
enum Slots { Slot_Queue, Slot_TotalSize, SlotCount };
|
||||
|
||||
ListObject* queue() const {
|
||||
return &getFixedSlot(Slot_Queue).toObject().as<ListObject>();
|
||||
}
|
||||
double queueTotalSize() const {
|
||||
return getFixedSlot(Slot_TotalSize).toNumber();
|
||||
}
|
||||
void setQueueTotalSize(double size) {
|
||||
setFixedSlot(Slot_TotalSize, JS::NumberValue(size));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace js
|
||||
|
||||
template <>
|
||||
inline bool JSObject::is<js::StreamController>() const;
|
||||
|
||||
#endif // builtin_streams_StreamController_h
|
|
@ -1,53 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Stream teeing state. */
|
||||
|
||||
#include "builtin/streams/TeeState.h"
|
||||
|
||||
#include "builtin/streams/ReadableStream.h" // js::ReadableStream
|
||||
#include "js/Class.h" // JSClass, JSCLASS_HAS_RESERVED_SLOTS
|
||||
#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
|
||||
#include "vm/Compartment.h" // JS::Compartment
|
||||
#include "vm/JSContext.h" // JSContext
|
||||
#include "vm/PromiseObject.h" // js::PromiseObject
|
||||
|
||||
#include "vm/JSObject-inl.h" // js::NewBuiltinClassInstance
|
||||
|
||||
using js::ReadableStream;
|
||||
using js::TeeState;
|
||||
|
||||
using JS::Handle;
|
||||
using JS::Int32Value;
|
||||
using JS::ObjectValue;
|
||||
using JS::Rooted;
|
||||
|
||||
/* static */ TeeState* TeeState::create(
|
||||
JSContext* cx, Handle<ReadableStream*> unwrappedStream) {
|
||||
Rooted<TeeState*> state(cx, NewBuiltinClassInstance<TeeState>(cx));
|
||||
if (!state) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Rooted<PromiseObject*> cancelPromise(
|
||||
cx, PromiseObject::createSkippingExecutor(cx));
|
||||
if (!cancelPromise) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
state->setFixedSlot(Slot_Flags, Int32Value(0));
|
||||
state->setFixedSlot(Slot_CancelPromise, ObjectValue(*cancelPromise));
|
||||
Rooted<JSObject*> wrappedStream(cx, unwrappedStream);
|
||||
if (!cx->compartment()->wrap(cx, &wrappedStream)) {
|
||||
return nullptr;
|
||||
}
|
||||
state->setFixedSlot(Slot_Stream, JS::ObjectValue(*wrappedStream));
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
const JSClass TeeState::class_ = {"TeeState",
|
||||
JSCLASS_HAS_RESERVED_SLOTS(SlotCount)};
|
|
@ -1,155 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Stream teeing state. */
|
||||
|
||||
#ifndef builtin_streams_TeeState_h
|
||||
#define builtin_streams_TeeState_h
|
||||
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
|
||||
#include <stdint.h> // uint32_t
|
||||
|
||||
#include "builtin/streams/ReadableStreamController.h" // js::ReadableStreamDefaultController
|
||||
#include "js/Class.h" // JSClass
|
||||
#include "js/Value.h" // JS::{Int32,Object}Value
|
||||
#include "vm/NativeObject.h" // js::NativeObject
|
||||
#include "vm/PromiseObject.h" // js::PromiseObject
|
||||
|
||||
namespace js {
|
||||
|
||||
/**
|
||||
* TeeState objects implement the local variables in Streams spec 3.3.9
|
||||
* ReadableStreamTee, which are accessed by several algorithms.
|
||||
*/
|
||||
class TeeState : public NativeObject {
|
||||
public:
|
||||
/**
|
||||
* Memory layout for TeeState instances.
|
||||
*
|
||||
* The Reason1 and Reason2 slots store opaque values, which might be
|
||||
* wrapped objects from other compartments. Since we don't treat them as
|
||||
* objects in Streams-specific code, we don't have to worry about that
|
||||
* apart from ensuring that the values are properly wrapped before storing
|
||||
* them.
|
||||
*
|
||||
* CancelPromise is always created in TeeState::create below, so is
|
||||
* guaranteed to be in the same compartment as the TeeState instance
|
||||
* itself.
|
||||
*
|
||||
* Stream can be from another compartment. It is automatically wrapped
|
||||
* before storing it and unwrapped upon retrieval. That means that
|
||||
* TeeState consumers need to be able to deal with unwrapped
|
||||
* ReadableStream instances from non-current compartments.
|
||||
*
|
||||
* Branch1 and Branch2 are always created in the same compartment as the
|
||||
* TeeState instance, so cannot be from another compartment.
|
||||
*/
|
||||
enum Slots {
|
||||
Slot_Flags = 0,
|
||||
Slot_Reason1,
|
||||
Slot_Reason2,
|
||||
Slot_CancelPromise,
|
||||
Slot_Stream,
|
||||
Slot_Branch1,
|
||||
Slot_Branch2,
|
||||
SlotCount
|
||||
};
|
||||
|
||||
private:
|
||||
enum Flags {
|
||||
Flag_Reading = 1 << 0,
|
||||
Flag_Canceled1 = 1 << 1,
|
||||
Flag_Canceled2 = 1 << 2,
|
||||
|
||||
// No internal user ever sets the cloneForBranch2 flag to true, and the
|
||||
// streams spec doesn't expose a way to set the flag to true. So for the
|
||||
// moment, don't even reserve flag-space to store it.
|
||||
// Flag_CloneForBranch2 = 1 << 3,
|
||||
};
|
||||
uint32_t flags() const { return getFixedSlot(Slot_Flags).toInt32(); }
|
||||
void setFlags(uint32_t flags) {
|
||||
setFixedSlot(Slot_Flags, JS::Int32Value(flags));
|
||||
}
|
||||
|
||||
public:
|
||||
static const JSClass class_;
|
||||
|
||||
// Consistent with not even storing this always-false flag, expose it as
|
||||
// compile-time constant false.
|
||||
static constexpr bool cloneForBranch2() { return false; }
|
||||
|
||||
bool reading() const { return flags() & Flag_Reading; }
|
||||
void setReading() {
|
||||
MOZ_ASSERT(!(flags() & Flag_Reading));
|
||||
setFlags(flags() | Flag_Reading);
|
||||
}
|
||||
void unsetReading() {
|
||||
MOZ_ASSERT(flags() & Flag_Reading);
|
||||
setFlags(flags() & ~Flag_Reading);
|
||||
}
|
||||
|
||||
bool canceled1() const { return flags() & Flag_Canceled1; }
|
||||
void setCanceled1(HandleValue reason) {
|
||||
MOZ_ASSERT(!(flags() & Flag_Canceled1));
|
||||
setFlags(flags() | Flag_Canceled1);
|
||||
setFixedSlot(Slot_Reason1, reason);
|
||||
}
|
||||
|
||||
bool canceled2() const { return flags() & Flag_Canceled2; }
|
||||
void setCanceled2(HandleValue reason) {
|
||||
MOZ_ASSERT(!(flags() & Flag_Canceled2));
|
||||
setFlags(flags() | Flag_Canceled2);
|
||||
setFixedSlot(Slot_Reason2, reason);
|
||||
}
|
||||
|
||||
JS::Value reason1() const {
|
||||
MOZ_ASSERT(canceled1());
|
||||
return getFixedSlot(Slot_Reason1);
|
||||
}
|
||||
|
||||
JS::Value reason2() const {
|
||||
MOZ_ASSERT(canceled2());
|
||||
return getFixedSlot(Slot_Reason2);
|
||||
}
|
||||
|
||||
PromiseObject* cancelPromise() {
|
||||
return &getFixedSlot(Slot_CancelPromise).toObject().as<PromiseObject>();
|
||||
}
|
||||
|
||||
ReadableStreamDefaultController* branch1() {
|
||||
ReadableStreamDefaultController* controller =
|
||||
&getFixedSlot(Slot_Branch1)
|
||||
.toObject()
|
||||
.as<ReadableStreamDefaultController>();
|
||||
MOZ_ASSERT(controller->isTeeBranch1());
|
||||
return controller;
|
||||
}
|
||||
void setBranch1(ReadableStreamDefaultController* controller) {
|
||||
MOZ_ASSERT(controller->isTeeBranch1());
|
||||
setFixedSlot(Slot_Branch1, JS::ObjectValue(*controller));
|
||||
}
|
||||
|
||||
ReadableStreamDefaultController* branch2() {
|
||||
ReadableStreamDefaultController* controller =
|
||||
&getFixedSlot(Slot_Branch2)
|
||||
.toObject()
|
||||
.as<ReadableStreamDefaultController>();
|
||||
MOZ_ASSERT(controller->isTeeBranch2());
|
||||
return controller;
|
||||
}
|
||||
void setBranch2(ReadableStreamDefaultController* controller) {
|
||||
MOZ_ASSERT(controller->isTeeBranch2());
|
||||
setFixedSlot(Slot_Branch2, JS::ObjectValue(*controller));
|
||||
}
|
||||
|
||||
static TeeState* create(JSContext* cx,
|
||||
Handle<ReadableStream*> unwrappedStream);
|
||||
};
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // builtin_streams_TeeState_h
|
|
@ -33,7 +33,7 @@ static const JSClass* getGlobalClass() {
|
|||
static JSObject* jsfuzz_createGlobal(JSContext* cx, JSPrincipals* principals) {
|
||||
/* Create the global object. */
|
||||
JS::RealmOptions options;
|
||||
options.creationOptions().setStreamsEnabled(true).setWeakRefsEnabled(
|
||||
options.creationOptions().setWeakRefsEnabled(
|
||||
JS::WeakRefSpecifier::EnabledWithCleanupSome);
|
||||
return JS_NewGlobalObject(cx, getGlobalClass(), principals,
|
||||
JS::FireOnNewGlobalHook, options);
|
||||
|
|
|
@ -162,11 +162,6 @@ if not CONFIG["JS_CODEGEN_NONE"]:
|
|||
"testsJit.cpp",
|
||||
]
|
||||
|
||||
if CONFIG["MOZ_JS_STREAMS"]:
|
||||
UNIFIED_SOURCES += [
|
||||
"testReadableStream.cpp",
|
||||
]
|
||||
|
||||
if CONFIG["NIGHTLY_BUILD"]:
|
||||
# The Error interceptor only exists on Nightly.
|
||||
UNIFIED_SOURCES += [
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -116,7 +116,6 @@ JSObject* JSAPITest::createGlobal(JSPrincipals* principals) {
|
|||
JS::RootedObject newGlobal(cx);
|
||||
JS::RealmOptions options;
|
||||
options.creationOptions()
|
||||
.setStreamsEnabled(true)
|
||||
.setWeakRefsEnabled(JS::WeakRefSpecifier::EnabledWithCleanupSome)
|
||||
.setSharedMemoryAndAtomicsEnabled(true);
|
||||
newGlobal = JS_NewGlobalObject(cx, getGlobalClass(), principals,
|
||||
|
|
|
@ -200,7 +200,6 @@ EXPORTS.js += [
|
|||
"../public/SourceText.h",
|
||||
"../public/StableStringChars.h",
|
||||
"../public/Stack.h",
|
||||
"../public/Stream.h",
|
||||
"../public/StreamConsumer.h",
|
||||
"../public/String.h",
|
||||
"../public/StructuredClone.h",
|
||||
|
|
|
@ -604,7 +604,6 @@ bool shell::enableTestWasmAwaitTier2 = false;
|
|||
bool shell::enableSourcePragmas = true;
|
||||
bool shell::enableAsyncStacks = false;
|
||||
bool shell::enableAsyncStackCaptureDebuggeeOnly = false;
|
||||
bool shell::enableStreams = false;
|
||||
bool shell::enableWeakRefs = false;
|
||||
bool shell::enableToSource = false;
|
||||
bool shell::enablePropertyErrorMessageFix = false;
|
||||
|
@ -3841,7 +3840,6 @@ static void SetStandardRealmOptions(JS::RealmOptions& options) {
|
|||
options.creationOptions()
|
||||
.setSharedMemoryAndAtomicsEnabled(enableSharedMemory)
|
||||
.setCoopAndCoepEnabled(false)
|
||||
.setStreamsEnabled(enableStreams)
|
||||
.setWeakRefsEnabled(enableWeakRefs
|
||||
? JS::WeakRefSpecifier::EnabledWithCleanupSome
|
||||
: JS::WeakRefSpecifier::Disabled)
|
||||
|
@ -10601,7 +10599,6 @@ static bool SetContextOptions(JSContext* cx, const OptionParser& op) {
|
|||
enableAsyncStacks = !op.getBoolOption("no-async-stacks");
|
||||
enableAsyncStackCaptureDebuggeeOnly =
|
||||
op.getBoolOption("async-stacks-capture-debuggee-only");
|
||||
enableStreams = !op.getBoolOption("no-streams");
|
||||
enableWeakRefs = !op.getBoolOption("disable-weak-refs");
|
||||
enableToSource = !op.getBoolOption("disable-tosource");
|
||||
enablePropertyErrorMessageFix =
|
||||
|
@ -11575,9 +11572,6 @@ int main(int argc, char** argv) {
|
|||
!op.addBoolOption('\0', "less-debug-code",
|
||||
"Emit less machine code for "
|
||||
"checking assertions under DEBUG.") ||
|
||||
!op.addBoolOption('\0', "enable-streams",
|
||||
"Enable WHATWG Streams (default)") ||
|
||||
!op.addBoolOption('\0', "no-streams", "Disable WHATWG Streams") ||
|
||||
!op.addBoolOption('\0', "disable-weak-refs", "Disable weak references") ||
|
||||
!op.addBoolOption('\0', "disable-tosource", "Disable toSource/uneval") ||
|
||||
!op.addBoolOption('\0', "disable-property-error-message-fix",
|
||||
|
|
|
@ -122,11 +122,6 @@ extern bool enableTestWasmAwaitTier2;
|
|||
extern bool enableSourcePragmas;
|
||||
extern bool enableAsyncStacks;
|
||||
extern bool enableAsyncStackCaptureDebuggeeOnly;
|
||||
extern bool enableStreams;
|
||||
extern bool enableReadableByteStreams;
|
||||
extern bool enableBYOBStreamReaders;
|
||||
|
||||
extern bool enableReadableStreamPipeTo;
|
||||
extern bool enableWeakRefs;
|
||||
extern bool enableToSource;
|
||||
extern bool enablePropertyErrorMessageFix;
|
||||
|
|
|
@ -25,11 +25,6 @@
|
|||
#include "builtin/FinalizationRegistryObject.h"
|
||||
#include "builtin/MapObject.h"
|
||||
#include "builtin/ShadowRealm.h"
|
||||
#include "builtin/Stream.h"
|
||||
#include "builtin/streams/QueueingStrategies.h" // js::{ByteLength,Count}QueueingStrategy
|
||||
#include "builtin/streams/ReadableStream.h" // js::ReadableStream
|
||||
#include "builtin/streams/ReadableStreamController.h" // js::Readable{StreamDefault,ByteStream}Controller
|
||||
#include "builtin/streams/ReadableStreamReader.h" // js::ReadableStreamDefaultReader
|
||||
#include "builtin/Symbol.h"
|
||||
#include "builtin/WeakMapObject.h"
|
||||
#include "builtin/WeakRefObject.h"
|
||||
|
@ -180,15 +175,6 @@ bool GlobalObject::skipDeselectedConstructor(JSContext* cx, JSProtoKey key) {
|
|||
case JSProto_RelativeTimeFormat:
|
||||
return false;
|
||||
#endif
|
||||
#ifdef MOZ_JS_STREAMS
|
||||
case JSProto_ReadableStream:
|
||||
case JSProto_ReadableStreamDefaultReader:
|
||||
case JSProto_ReadableStreamDefaultController:
|
||||
case JSProto_ReadableByteStreamController:
|
||||
case JSProto_ByteLengthQueuingStrategy:
|
||||
case JSProto_CountQueuingStrategy:
|
||||
return !cx->realm()->creationOptions().getStreamsEnabled();
|
||||
#endif
|
||||
|
||||
// Return true if the given constructor has been disabled at run-time.
|
||||
case JSProto_Atomics:
|
||||
|
|
|
@ -53,7 +53,6 @@
|
|||
#include "js/MemoryMetrics.h"
|
||||
#include "js/Object.h" // JS::GetClass
|
||||
#include "js/RealmIterators.h"
|
||||
#include "js/Stream.h" // JS::AbortSignalIsAborted, JS::InitPipeToHandling
|
||||
#include "js/SliceBudget.h"
|
||||
#include "js/UbiNode.h"
|
||||
#include "js/UbiNodeUtils.h"
|
||||
|
|
Загрузка…
Ссылка в новой задаче