js/public/Stream.h
author Jan de Mooij <jdemooij@mozilla.com>
Mon, 27 Sep 2021 11:15:49 +0000
changeset 593265 5144ad09cfb7011b98c5422b78b82936b1343541
parent 555298 2a2548dc3dedce27f612367d1214a315adb8296f
permissions -rw-r--r--
Bug 1732281 part 2 - Transpile ValueToIteratorResult in Warp. r=iain Depends on D126514 Differential Revision: https://phabricator.services.mozilla.com/D126515

/* -*- 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.
   *
   * Note: This method *must not cause GC*, because that could potentially
   * invalidate the `buffer` pointer.
   */
  virtual void writeIntoReadRequestBuffer(JSContext* cx, HandleObject stream,
                                          void* buffer, 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 };

/**
 * 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);

class JS_PUBLIC_API WritableStreamUnderlyingSink {
 public:
  virtual ~WritableStreamUnderlyingSink() = default;

  /**
   * Invoked when the associated WritableStream 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;
};

// ReadableStream.prototype.pipeTo SUPPORT

/**
 * The signature of a function that, when passed an |AbortSignal| instance, will
 * return the value of its "aborted" flag.
 *
 * This function will be called while |signal|'s realm has been entered.
 */
using AbortSignalIsAborted = bool (*)(JSObject* signal);

/**
 * Dictate embedder-specific details necessary to implement certain aspects of
 * the |ReadableStream.prototype.pipeTo| function.  This should be performed
 * exactly once, for a single context associated with a |JSRuntime|.
 *
 * The |ReadableStream.prototype.pipeTo| function accepts a |signal| argument
 * that may be used to abort the piping operation.  This argument must be either
 * |undefined| (in other words, the piping operation can't be aborted) or an
 * |AbortSignal| instance (that may be aborted using the signal's associated
 * |AbortController|).  |AbortSignal| is defined by WebIDL and the DOM in the
 * web embedding.  Therefore, embedders must use this function to specify how
 * such objects can be recognized and how to perform various essential actions
 * upon them.
 *
 * The provided |isAborted| function will be called with an unwrapped
 * |AbortSignal| instance, while that instance's realm has been entered.
 *
 * If this function isn't called, and a situation arises where an "is this an
 * |AbortSignal|?" question must be asked, that question will simply be answered
 * "no".
 */
extern JS_PUBLIC_API void InitPipeToHandling(const JSClass* abortSignalClass,
                                             AbortSignalIsAborted isAborted,
                                             JSContext* cx);

}  // namespace JS

#endif  // js_Stream_h