[whatwg/streams] Allow web authors to tee() with cloned chunks (#1156)

Currently, [`ReadableStream.prototype.tee()`](https://streams.spec.whatwg.org/commit-snapshots/5d86b65e45e0d56fcb8fbbff52fbbf2394918154/#rs-tee) performs [`ReadableStreamTee()`](https://streams.spec.whatwg.org/commit-snapshots/5d86b65e45e0d56fcb8fbbff52fbbf2394918154/#readable-stream-tee) with `cloneForBranch2` always set to `false`. This means that both branches always see identical chunks (`chunk1 === chunk2`).

However, in some cases, web authors may want to *clone* each chunk before it is enqueued to the second branch:
* In w3c/mediacapture-transform#56, each chunk is a `VideoFrame` which has an explicit `.close()` method to clean up it underlying media resource. A web author may want to process each frame with two parallel `TransformStream`s, while still allowing each branch to close their received frames. Therefore, the frames should be cloned first.
* A polyfill for the Fetch API wants to implement `Response.clone()`. This must replicate the behavior of ["tee a `ReadableStream`"](https://streams.spec.whatwg.org/commit-snapshots/5d86b65e45e0d56fcb8fbbff52fbbf2394918154/#readablestream-tee), which sets `cloneForBranch2` to `true`.

While it is *technically* possible to implement this in author code, the solution is not pretty:
* You could re-implement `ReadableStreamTee()` yourself and call `VideoFrame.clone()` (or `structuredClone()` when whatwg/html#3414 ships in browsers).
* You could use a bunch of `TransformStream`s to turn `chunk` into `[chunk, clonedChunk]`, then tee the stream, then pick the first or second element in the two branches. See [this comment](https://github.com/whatwg/streams/issues/1155#issuecomment-892184703) for an example. (Note that this also increases the total queue size by 2, since the `TransformStream`s have a writable HWM of 1 chunk.)

Therefore, we may want to expose the `cloneForBranch2` functionality to web authors. Some suggestions:
* `readable.tee({ structuredClone: true })`: performs `ReadableStreamTee(readable, true)`. Works only with transferable platform objects.
* `readable.tee({ cloneCallback: (chunk) => chunk.clone() })`: calls the given `cloneCallback` where `ReadableStreamDefaultTee` currently calls `StructuredClone(chunk2)`. Works with any object.

Or perhaps we can combine both into one option?
```
callback ReadableStreamTeeCloneCallback = any (any chunk);

dictionary ReadableStreamTeeOptions {
  clone: (boolean or ReadableStreamTeeCloneCallback);
}

interface ReadableStream {
   sequence<ReadableStream> tee(optional ReadableStreamTeeOptions options);
}
```
* If `clone` is a function, then tee calls it whenever it needs to clone a chunk.
* If `clone` is a boolean, then tee calls `StructuredClone()` if it's set to `true`. This acts as a shorthand for `readable.tee({ clone: structuredClone })`.

(This issue was split from [#1155](https://github.com/whatwg/streams/issues/1155#issuecomment-892194167).)

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/streams/issues/1156

Received on Wednesday, 4 August 2021 09:01:57 UTC