├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── 0-new-issue.yml │ ├── 1-new-feature.yml │ └── config.yml └── workflows │ ├── build.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── .pr-preview.json ├── CONTRIBUTING.md ├── FAQ.md ├── LICENSE ├── Makefile ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── Requirements.md ├── byte-streams-explainer.md ├── demos ├── append-child.html ├── append-child.mp4 ├── data │ ├── commits.include │ └── commits.json ├── index.html ├── resources │ ├── commits.css │ ├── common.css │ ├── highlight-default.css │ ├── highlight.pack.js │ ├── jank-meter.css │ ├── web-animations.min.js │ └── web-animations.min.js.map ├── streaming-element-backpressure.html ├── streaming-element-backpressure.mp4 ├── streaming-element.html ├── streaming-element.mp4 ├── tags │ ├── streaming-element-backpressure.js │ ├── streaming-element.js │ └── view-source.js └── transforms │ ├── parse-json.js │ ├── split-stream.js │ ├── text-encode-transform.js │ └── transform-stream-polyfill.js ├── index.bs ├── readable-stream-async-iteration-explainer.md ├── reference-implementation ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── COPYING.txt ├── LICENSE.md ├── README.md ├── compile-idl.js ├── lib │ ├── ByteLengthQueuingStrategy-impl.js │ ├── ByteLengthQueuingStrategy.webidl │ ├── CountQueuingStrategy-impl.js │ ├── CountQueuingStrategy.webidl │ ├── QueuingStrategy.webidl │ ├── QueuingStrategyInit.webidl │ ├── ReadableByteStreamController-impl.js │ ├── ReadableByteStreamController.webidl │ ├── ReadableStream-impl.js │ ├── ReadableStream.webidl │ ├── ReadableStreamBYOBReader-impl.js │ ├── ReadableStreamBYOBReader.webidl │ ├── ReadableStreamBYOBRequest-impl.js │ ├── ReadableStreamBYOBRequest.webidl │ ├── ReadableStreamDefaultController-impl.js │ ├── ReadableStreamDefaultController.webidl │ ├── ReadableStreamDefaultReader-impl.js │ ├── ReadableStreamDefaultReader.webidl │ ├── ReadableStreamGenericReader-impl.js │ ├── ReadableStreamGenericReader.webidl │ ├── ReadableStreamReadResult.webidl │ ├── TransformStream-impl.js │ ├── TransformStream.webidl │ ├── TransformStreamDefaultController-impl.js │ ├── TransformStreamDefaultController.webidl │ ├── Transformer.webidl │ ├── UnderlyingSink.webidl │ ├── UnderlyingSource.webidl │ ├── WritableStream-impl.js │ ├── WritableStream.webidl │ ├── WritableStreamDefaultController-impl.js │ ├── WritableStreamDefaultController.webidl │ ├── WritableStreamDefaultWriter-impl.js │ ├── WritableStreamDefaultWriter.webidl │ ├── abstract-ops │ │ ├── ecmascript.js │ │ ├── internal-methods.js │ │ ├── miscellaneous.js │ │ ├── queue-with-sizes.js │ │ ├── queuing-strategy.js │ │ ├── readable-streams.js │ │ ├── transform-streams.js │ │ └── writable-streams.js │ ├── helpers │ │ ├── miscellaneous.js │ │ └── webidl.js │ └── index.js ├── package.json └── run-web-platform-tests.js ├── review-drafts ├── 2018-08.bs ├── 2019-02.bs ├── 2019-08.bs ├── 2020-02.bs ├── 2020-08.bs ├── 2021-02.bs ├── 2021-08.bs ├── 2022-02.bs ├── 2022-08.bs ├── 2023-02.bs ├── 2023-08.bs ├── 2024-02.bs ├── 2024-08.bs └── 2025-02.bs ├── streams-for-raw-video-explainer.md ├── transferable-streams-explainer.md └── writable-stream-abort-signal-explainer.md /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_size = 2 8 | indent_style = space 9 | trim_trailing_whitespace = true 10 | max_line_length = 100 11 | 12 | [Makefile] 13 | indent_style = tab 14 | 15 | [*.bs] 16 | indent_size = 1 17 | 18 | [*.py] 19 | indent_size = 4 20 | 21 | [*.js] 22 | max_line_length = 120 23 | 24 | [.gitmodules] 25 | indent_style = tab 26 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.bs diff=html linguist-language=HTML 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/0-new-issue.yml: -------------------------------------------------------------------------------- 1 | name: New issue 2 | description: File a new issue against the Streams Standard. 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Before filling out this form, please familiarize yourself with the [Code of Conduct](https://whatwg.org/code-of-conduct). You might also find the [FAQ](https://whatwg.org/faq) and [Working Mode](https://whatwg.org/working-mode) useful. 8 | 9 | If at any point you have questions, please reach out to us on [Chat](https://whatwg.org/chat). 10 | - type: textarea 11 | attributes: 12 | label: "What is the issue with the Streams Standard?" 13 | validations: 14 | required: true 15 | - type: markdown 16 | attributes: 17 | value: "Thank you for taking the time to improve the Streams Standard!" 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-new-feature.yml: -------------------------------------------------------------------------------- 1 | name: New feature 2 | description: Request a new feature in the Streams Standard. 3 | labels: ["addition/proposal", "needs implementer interest"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Before filling out this form, please familiarize yourself with the [Code of Conduct](https://whatwg.org/code-of-conduct), [FAQ](https://whatwg.org/faq), and [Working Mode](https://whatwg.org/working-mode). They help with setting expectations and making sure you know what is required. The FAQ ["How should I go about proposing new features to WHATWG standards?"](https://whatwg.org/faq#adding-new-features) is especially relevant. 9 | 10 | If at any point you have questions, please reach out to us on [Chat](https://whatwg.org/chat). 11 | - type: textarea 12 | attributes: 13 | label: "What problem are you trying to solve?" 14 | validations: 15 | required: true 16 | - type: textarea 17 | attributes: 18 | label: "What solutions exist today?" 19 | - type: textarea 20 | attributes: 21 | label: "How would you solve it?" 22 | - type: textarea 23 | attributes: 24 | label: "Anything else?" 25 | - type: markdown 26 | attributes: 27 | value: "Thank you for taking the time to improve the Streams Standard!" 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Chat 4 | url: https://whatwg.org/chat 5 | about: Please do reach out with questions and feedback! 6 | - name: Stack Overflow 7 | url: https://stackoverflow.com/ 8 | about: If you're having trouble building a web page, this is not the right repository. Consider asking your question on Stack Overflow instead. 9 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build: 14 | name: Build 15 | runs-on: ubuntu-22.04 16 | steps: 17 | - uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 2 20 | - uses: actions/setup-python@v4 21 | with: 22 | python-version: "3.11" 23 | - run: pip install bikeshed && bikeshed update 24 | # Note: `make deploy` will do a deploy dry run on PRs. 25 | - run: make deploy 26 | env: 27 | SERVER: ${{ secrets.MARQUEE_SERVER }} 28 | SERVER_PUBLIC_KEY: ${{ secrets.MARQUEE_PUBLIC_KEY }} 29 | SERVER_DEPLOY_KEY: ${{ secrets.MARQUEE_DEPLOY_KEY }} 30 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | push: 7 | branches: 8 | - main 9 | jobs: 10 | test: 11 | name: Test 12 | runs-on: ubuntu-latest 13 | defaults: 14 | run: 15 | working-directory: reference-implementation 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | submodules: true 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: 22 23 | - run: npm install 24 | - run: npm test 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /streams.spec.whatwg.org/ 2 | /deploy.sh 3 | /index.html 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "reference-implementation/web-platform-tests"] 2 | path = reference-implementation/web-platform-tests 3 | url = https://github.com/web-platform-tests/wpt.git 4 | -------------------------------------------------------------------------------- /.pr-preview.json: -------------------------------------------------------------------------------- 1 | { 2 | "src_file": "index.bs", 3 | "type": "bikeshed", 4 | "params": { 5 | "force": 1, 6 | "md-status": "LS-PR", 7 | "md-Text-Macro": "PR-NUMBER {{ pull_request.number }}" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Streams Standard contributor guidelines 2 | 3 | These are the guidelines for contributing to the Streams Standard. First see the [WHATWG contributor guidelines](https://github.com/whatwg/meta/blob/main/CONTRIBUTING.md). 4 | 5 | We label [good first issues](https://github.com/whatwg/streams/labels/good%20first%20issue) that you could help us fix, to get a taste for how to submit pull requests, how the build process works, and so on. 6 | 7 | ## Spec editorial conventions 8 | 9 | In general, follow existing practice. 10 | 11 | Wrap lines to 100 columns. 12 | 13 | Use single-space indents. 14 | 15 | Do not introduce explicit `

` tags. (Bikeshed does this automatically.) 16 | 17 | Mark up definitions [appropriately](https://speced.github.io/bikeshed/#definitions). This is mostly applicable when defining new classes or methods; follow the existing examples in the spec. 18 | 19 | Use cross-reference [autolinking](https://speced.github.io/bikeshed/#autolinking) liberally. This generally amounts to writing references to "definitions" as `[=term=]`, and writing references to classes or methods as `{{ClassName}}` or `{{ClassName/methodName()}}`. 20 | 21 | When writing examples or notes, JavaScript variables and values are enclosed in `` tags, not in `` tags. 22 | 23 | Use abstract operations to factor out shared logic used by multiple public APIs, or by multiple other abstract operations. 24 | 25 | ## Reference implementation style 26 | 27 | Wrap lines to 120 columns. 28 | 29 | Use two-space indents. 30 | 31 | Alphabetize imports. 32 | 33 | Use single quotes. 34 | 35 | Pass ESLint. 36 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # Streams API FAQ 2 | 3 | This is a work in progress, documenting some design decisions that were made and that were non-obvious enough that we feel the need to explain them. We'll probably be adding to it as we go. If there's something you feel belongs here, please file an issue or pull request and we'll add it! 4 | 5 | ## Are readable streams appropriate for my use case? 6 | 7 | Readable streams fit best in situations where: 8 | 9 | - You are trying to represent some underlying I/O source of data, especially one for which backpressure is meaningful. 10 | - Consumers care about the logical concatenation of all chunks of the stream, such that every value produced is important. 11 | - Your usual use case is a single consumer, with allowances for multi-consumer via teeing. 12 | - Errors are either final and non-recoverable (socket prematurely closed), or just a specialized type of data (404). 13 | 14 | Notably, readable streams are _not_ a good fit for: 15 | 16 | - Continuous "snapshots" of some varying quantity, where consumers tune in and out as appropriate. 17 | - Broadcasts to a large number of independent consumers. 18 | 19 | In these types of scenarios, the buffering and backpressure built in to readable streams is unnecessary and problematic, and the reader design becomes awkward to work with. 20 | 21 | ## Are writable streams appropriate for my use case? 22 | 23 | Writable streams shine in situations where: 24 | 25 | - You are trying to represent some underlying I/O sink for data, especially one which cannot accept that data instantly. 26 | - Your sink must receive the sequence of chunks written in the order provided. 27 | - Your sink has a way of acknowledging successful writes, or at least receipt of the data. 28 | - Your sink requires a "close" signal to release resources or similar. 29 | 30 | ## Are streams only for byte data? 31 | 32 | No! Although streams are designed around I/O use cases, the APIs in the Streams Standard accommodate other types of chunks flowing through the streams just as well. Although usually the original source of or ultimate sink for I/O deals in bytes, there are many steps in between where streaming APIs are important, and they are meant to play well there too. 33 | 34 | For example, your program could easily have readable stream of JavaScript objects representing users. This stream was probably originally derived from a readable stream of bytes coming from a HTTP request; maybe there was even an intermediate stream of decoded text. But the same APIs work for all of these streams: it is only the chunk type that differs. 35 | 36 | A key part of allowing this flexibility in the API is the concept of a _queuing strategy_. A stream's queuing strategy is responsible for knowing what type of chunks it can process, and crucially, for measuring their size. This is what allows backpressure to work, without us coding knowledge of every possible chunk type into the Streams Standard. 37 | 38 | ## What's the deal with byte streams then? 39 | 40 | That is: given that the stream APIs work great for any type of data, why are there special APIs, like BYOB readers, for dealing specifically with byte streams? 41 | 42 | The answer is that there are certain operations, mostly around memory management, that only make sense when dealing directly with bytes. So the reader returned by `getReader({ mode: "byob" })`, or the controller interface provided when you give the constructor the `type: "bytes"` option, provide _extra_ APIs on top of the normal ones, which can be used by consumers and producers that are specifically interested in working at that level. But generally, your code can ignore the difference—a readable byte stream will act exactly like a normal readable stream, if you simply use `.getReader()` with no `mode` option. 43 | 44 | ## How do readable streams relate to [observables](https://github.com/zenparsing/es-observable) or EventTarget? 45 | 46 | Readable streams and observables/EventTarget are not terribly related. Observables/EventTarget are specifically designed for multi-consumer scenarios, and have no support for backpressure. They are generally a bad fit for anything related to I/O, whereas streams excel there. 47 | 48 | Observables and readable streams both share the semantic of "zero or more chunks, followed by either an error or done signal". But beyond that, they are not very comparable. 49 | 50 | ## How do readable streams relate to [async iterables](https://github.com/zenparsing/async-iteration/)? 51 | 52 | Readable streams are conceptually a special case of async iterables, with a focus on I/O. The best analogy is something like "readable streams is to async iterable as array is to sync iterable." That is, just like arrays are a specialized type of iterable optimized for memory locality and random access, readable streams are a specialized type of async iterable optimized for things like off-main-thread data transfer and precise backpressure signaling. 53 | 54 | As the async iterable proposal progresses, we anticipate making readable streams into async iterables proper, so that you could use the proposed `for async (const chunk of rs)` syntax. 55 | 56 | ## What are the main differences between these streams and Node.js streams? 57 | 58 | The Streams Standard specifically started with the Node.js streams model as the basis, vowing to learn the lessons from Node's iterative streams1/2/3 process and incorporate them. Over time it's diverged in a few more ways, often via urging from the Node.js team. At this point, the main differences are: 59 | 60 | - Readable streams: 61 | - An asynchronous `.read()` method, instead of async `.on("readable", ...)` plus synchronous `.read()` 62 | - Addition of the exclusive reader and locking concepts, to better support off-main-thread piping 63 | - Addition of cancelation semantics 64 | - Addition of more precise flow control via the `desiredSize` signal 65 | - Built-in teeing support 66 | - Removal of the `"data"` event, which competes conceptually with other ways of reading 67 | - Removal of pause/resume for managing backpressure 68 | - Removal of the `unshift` method for putting chunks back into the queue after reading them 69 | - No "binary/string mode" vs. "object mode" switch; instead, queueing strategies allow custom chunk types 70 | - No optional and only sometimes-working size parameter while reading; instead use BYOB readers 71 | - Writable streams: 72 | - No cork/uncork support, yet (this is [#27](https://github.com/whatwg/streams/issues/27)) 73 | - Transform streams: 74 | - No longer a mash of the readable and writable stream APIs, but instead simple `{ readable, writable }` objects where the component streams are connected behind the scenes 75 | - General: 76 | - Modernized API to use promises instead of callbacks and one-time events 77 | - No built-in text encoding/decoding 78 | - Split the `pipe` method into `pipeTo(writable)` and `pipeThrough(transform)` 79 | 80 | ## Why don't errors that occur while cancelling put the readable stream in an error state? 81 | 82 | The idea of cancelling a readable stream is that it should behave as a "loss of interest": the consumer cancelling the stream expects nobody will read from it further, and that the stream should be treated in the same way as if it had closed naturally. Thus, cancellation _immediately_ moves the stream into a `"closed"` state, which has the same properties as if the stream had closed itself. This gives the most consistent view of the stream to the outside world. 83 | 84 | On the other hand, it may be important for the consumer _performing_ the cancellation to be notified whether the cancellation succeeds or fails. To handle this, you can simply use the promise returned from `.cancel()`: 85 | 86 | ```js 87 | readableStream.cancel().then( 88 | () => console.log("Cancellation successful!"), 89 | err => console.error("Cancellation failed!", err) 90 | ); 91 | ``` 92 | 93 | ## What's with `pipeTo` vs `pipeThrough`? 94 | 95 | There are only two types of streams: readable and writable streams. `pipeTo` is for piping between them. 96 | 97 | For the concept of something with a writable end and a readable end, we have "duplex streams." Duplex streams are really just containers for a pair of streams, one writable and one readable, stored in the properties `writable` and `readable` respectively. 98 | 99 | Some duplex streams will be transform streams, wherein the two streams are entangled, so that writing to the writable side affects what can be read from the readable side. This could be a very direct entanglement, of the sort produced by the `TransformStream` class, or something more indirect, such as the relationship between `stdin` and `stdout`. 100 | 101 | `pipeThrough` is for piping into the writable half of the entangled streams and out the readable side. That is, 102 | 103 | ```js 104 | src.pipeThrough(through).pipeTo(dest); 105 | ``` 106 | 107 | is really just sugar for: 108 | 109 | ```js 110 | src.pipeTo(through.writable); 111 | through.readable.pipeTo(dest); 112 | ``` 113 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL=/bin/bash -o pipefail 2 | .PHONY: local remote deploy 3 | 4 | remote: index.bs 5 | @ (HTTP_STATUS=$$(curl https://api.csswg.org/bikeshed/ \ 6 | --output index.html \ 7 | --write-out "%{http_code}" \ 8 | --header "Accept: text/plain, text/html" \ 9 | -F die-on=warning \ 10 | -F md-Text-Macro="COMMIT-SHA LOCAL COPY" \ 11 | -F file=@index.bs) && \ 12 | [[ "$$HTTP_STATUS" -eq "200" ]]) || ( \ 13 | echo ""; cat index.html; echo ""; \ 14 | rm -f index.html; \ 15 | exit 22 \ 16 | ); 17 | 18 | local: index.bs 19 | bikeshed spec index.bs index.html --md-Text-Macro="COMMIT-SHA LOCAL-COPY" 20 | 21 | deploy: index.bs 22 | curl --remote-name --fail https://resources.whatwg.org/build/deploy.sh 23 | EXTRA_FILES="demos/* demos/**/*" \ 24 | bash ./deploy.sh 25 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | - [ ] At least two implementers are interested (and none opposed): 8 | * … 9 | * … 10 | - [ ] [Tests](https://github.com/web-platform-tests/wpt) are written and can be reviewed and commented upon at: 11 | * … 12 | - [ ] [Implementation bugs](https://github.com/whatwg/meta/blob/main/MAINTAINERS.md#handling-pull-requests) are filed: 13 | * Chromium: … 14 | * Gecko: … 15 | * WebKit: … 16 | * Deno: … 17 | * Node.js: … 18 | - [ ] [MDN issue](https://github.com/whatwg/meta/blob/main/MAINTAINERS.md#handling-pull-requests) is filed: … 19 | - [ ] The top of this comment includes a [clear commit message](https://github.com/whatwg/meta/blob/main/COMMITTING.md) to use. 20 | 21 | (See [WHATWG Working Mode: Changes](https://whatwg.org/working-mode#changes) for more details.) 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Streams Standard 2 | 3 | This repository hosts the [Streams Standard](https://streams.spec.whatwg.org/). 4 | 5 | ## Contribution opportunities 6 | 7 | Folks notice minor and larger issues with the Streams Standard all the time and we'd love your 8 | help fixing those. Pull requests for typographical and grammar errors are also most welcome. 9 | 10 | We'd be happy to mentor you through this process. If you're interested and need help getting 11 | started, leave a comment on the issue or ask around 12 | [in the Matrix chat room](https://whatwg.org/chat). 13 | 14 | ## Pull requests 15 | 16 | In short, change `index.bs` and submit your patch, with a 17 | [good commit message](https://github.com/whatwg/meta/blob/main/COMMITTING.md). Consider 18 | reading through the [WHATWG FAQ](https://whatwg.org/faq) if you are new here. 19 | 20 | Please add your name to the Acknowledgments section in your first pull request, even for trivial 21 | fixes. The names are sorted lexicographically. 22 | 23 | For formatting specific to this standard, see [CONTRIBUTING.md](CONTRIBUTING.md). Additionally, for 24 | any normative changes, we'll also want to change the tests and reference implementation: 25 | 26 | ## Tests 27 | 28 | Tests can be found in the `streams/` directory of 29 | [`web-platform-tests/wpt`](https://github.com/web-platform-tests/wpt). 30 | 31 | ## Reference implementation 32 | 33 | This repository also includes a reference implementation, written in JavaScript, under 34 | `reference-implementation/`. See the README under that directory for more details. 35 | 36 | We strive for every commit that changes the spec to also add tests, and to change the reference 37 | implementation in order to pass those tests. 38 | 39 | ## Building "locally" 40 | 41 | For quick local iteration, run `make`. To verify your changes locally, run `make deploy`. See more 42 | in the 43 | [WHATWG Contributor Guidelines](https://github.com/whatwg/meta/blob/main/CONTRIBUTING.md#building). 44 | 45 | ## Merge policy 46 | 47 | If you can commit to this repository, see the 48 | [WHATWG Maintainer Guidelines](https://github.com/whatwg/meta/blob/main/MAINTAINERS.md). 49 | 50 | ## Code of conduct 51 | 52 | We are committed to providing a friendly, safe, and welcoming environment for all. Please read and 53 | respect the [WHATWG Code of Conduct](https://whatwg.org/code-of-conduct). 54 | -------------------------------------------------------------------------------- /byte-streams-explainer.md: -------------------------------------------------------------------------------- 1 | # Byte Streams Explainer 2 | 3 | 4 | ## Introduction 5 | 6 | The streams APIs provide ubiquitous, interoperable primitives for creating, composing, and consuming streams of data. 7 | For streams representing bytes, readable byte streams are an extended version of readable streams which are provided to 8 | handle bytes efficiently. 9 | 10 | Byte streams allow for BYOB (bring-your-own-buffer) readers to be acquired. The default stream type can give a range of 11 | different outputs, for example strings or array buffers in the case of WebSockets, whereas byte streams guarantee byte 12 | output. Furthermore, being able to have BYOB readers has benefits in terms of stability. This is because if a buffer 13 | detaches, it can guarantee that one does not write into the same buffer twice, hence avoiding race conditions. BYOB 14 | readers can reduce the number of times we run garbage collection, because we can reuse buffers. 15 | 16 | ## API Proposed 17 | 18 | * Constructing a Readable Byte Stream 19 | * new [ReadableStream](https://streams.spec.whatwg.org/#rs-constructor)({ type: "bytes" }) 20 | * Getting a BYOB reader 21 | * [getReader](https://streams.spec.whatwg.org/#rs-get-reader)({ mode: "byob" }) 22 | * As part of the implementation, there are 3 main classes that will be added to the Streams API: 23 | * The [ReadableStreamBYOBReader](https://streams.spec.whatwg.org/#byob-reader-class) class 24 | * This class represents a BYOB reader designed to be vended by a `ReadableStream` instance. 25 | * The [ReadableByteStreamController](https://streams.spec.whatwg.org/#rbs-controller-class) class 26 | * This class has methods that allow control of a `ReadableStream`’s state and internal queue. When 27 | constructing a `ReadableStream` that is a readable byte stream, the underlying source is given a corresponding 28 | `ReadableByteStreamController` instance to manipulate. 29 | * The [ReadableStreamBYOBRequest](https://streams.spec.whatwg.org/#rs-byob-request-class) class 30 | * This class represents a pull-into request in a `ReadableByteStreamController`. 31 | 32 | 33 | ## Examples 34 | 35 | These are a few examples of Javascript which can be used for byte streams once this is implemented: 36 | 37 | 38 | ### Reading bytes from the stream into a single memory buffer 39 | 40 | The code reads the first 1024 bytes from the stream into a single memory buffer. This is due to the fact that if a 41 | stream is a readable byte stream, you can also acquire a BYOB reader for it, which allows more precise control over 42 | buffer allocation in order to avoid copies. 43 | 44 | 45 | ```javascript 46 | const reader = readableStream.getReader({ mode: "byob" }); 47 | 48 | let startingAB = new ArrayBuffer(1024); 49 | const buffer = await readInto(startingAB); 50 | console.log("The first 1024 bytes: ", buffer); 51 | 52 | async function readInto(buffer) { 53 | let offset = 0; 54 | 55 | while (offset < buffer.byteLength) { 56 | const {value: view, done} = 57 | await reader.read(new Uint8Array(buffer, offset, buffer.byteLength - offset)); 58 | buffer = view.buffer; 59 | if (done) { 60 | break; 61 | } 62 | offset += view.byteLength; 63 | } 64 | 65 | return buffer; 66 | } 67 | ``` 68 | 69 | 70 | Note that after this code has run, `startingAB` is detached and can no longer be used to access the data, but `buffer` 71 | points to the same region of memory. 72 | 73 | 74 | ### A readable byte stream with an underlying pull source 75 | 76 | The following function returns readable byte streams that allow efficient zero-copy reading of a randomly generated 77 | array. Instead of using a predetermined chunk size of 1024, it attempts to fill the developer-supplied buffer, 78 | allowing full control. 79 | 80 | 81 | ```javascript 82 | const DEFAULT_CHUNK_SIZE = 1024; 83 | 84 | function makeReadableByteStream() { 85 | return new ReadableStream({ 86 | type: "bytes", 87 | 88 | async pull(controller) { 89 | // Even when the consumer is using the default reader, the auto-allocation 90 | // feature allocates a buffer and passes it to us via byobRequest. 91 | const v = controller.byobRequest.view; 92 | v = crypto.getRandomValues(v); 93 | controller.byobRequest.respond(v.byteLength); 94 | }, 95 | 96 | autoAllocateChunkSize: DEFAULT_CHUNK_SIZE 97 | }); 98 | } 99 | ``` 100 | 101 | 102 | With this in hand, we can create and use BYOB readers for the returned `ReadableStream`. The adaptation between the 103 | low-level byte tracking of the underlying byte source shown here, and the higher-level chunk-based consumption of 104 | a default reader, is all taken care of automatically by the streams implementation. 105 | 106 | 107 | ## Goals 108 | 109 | * Provide a way to represent a [ReadableStream](https://streams.spec.whatwg.org/#rs-class) for bytes efficiently. 110 | * Avoid races caused by multiple access for the same buffer. 111 | * Permit buffer re-use to reduce GC churn. 112 | 113 | ## Non-Goals 114 | 115 | * Non-binary chunk types will not be supported. They can still use the default type of readable stream. 116 | * Shared array buffers will not be supported. Currently, we always detach buffers, but shared array buffers 117 | cannot be detached. 118 | 119 | ## User Benefits 120 | 121 | * By enabling developers to use the Readable Byte Streams API, this will increase speed and lower memory usage 122 | for sites that take advantage of it. Specifically, sites that handle streaming binary data will see improved 123 | performance. 124 | 125 | ## Alternatives 126 | 127 | * Some of the early versions of the standard had a specific ReadableByteStream constructor which would keep the two 128 | types of streams completely separate. However, this was unnecessary and we decided to just use separate controllers 129 | to support byte streams and non-byte streams with the same ReadableStream API to make it simpler. 130 | -------------------------------------------------------------------------------- /demos/append-child.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Append child writable stream demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

Append child writable stream demo

18 | Back to demo index 19 | 20 | 21 |
22 | 23 | 24 |
25 | 26 |
27 | 28 | 100 | -------------------------------------------------------------------------------- /demos/append-child.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whatwg/streams/080852ccd709e063cc6af239ae07fc040e365179/demos/append-child.mp4 -------------------------------------------------------------------------------- /demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Streams Demos 5 | 6 | 7 |

Streams Demos

8 | 9 |
10 |
Append child WritableStream 11 |
Piping through a series of transforms to a custom writable stream that appends children to 12 | the DOM. 13 | 14 |
Streaming element 15 |
Using custom element to stream into a given DOM location. 16 | 17 |
Streaming element with backpressure 18 |
A variation on the streaming element demo that adds backpressure to the element's writable 19 | stream, decreasing jank. 20 | 21 |
Streaming fetch() call with a progress bar | (Source Code) 22 |
A call to the fetch() api that uses the ReadableStream on the response body to fill a progress bar. 23 | 24 |
Streaming response from a service worker that instantly populates the DOM | (Source Code) 25 |
A book search app that caches responses from an API and streams them to the DOM, allowing for offline streaming responses and making use of the browser's streaming HTML parser. 26 | 27 |
Progress indicators with Service Workers and Fetch | (Source Code) 28 |
Streams intercept Service Worker FetchEvent and fetch() statements to show progress indicators. See browser support notes. 29 | 30 |
Grayscale a PNG | (Source Code) 31 |
A PNG is retrieved via fetch(), and the response body is piped through a TransformStream to convert the PNG data 32 | to black and white. 33 | 34 |
Unpack chunks of a PNG | (Source Code) 35 |
A PNG is fetched as a ReadableStream, then pipeThrough() is used to transform the stream of 36 | Uint8Arrays to a stream of PNG chunks. 37 |
38 | 39 |

Feel free to submit more demos by sending a pull request to the 40 | whatwg/streams repository!

41 | -------------------------------------------------------------------------------- /demos/resources/commits.css: -------------------------------------------------------------------------------- 1 | #commits { 2 | font-size: 16pt; 3 | } 4 | 5 | #commits th { 6 | text-align: left; 7 | padding: 2pt 4pt 2pt 4pt; 8 | } 9 | 10 | #commits tr:nth-child(even) { 11 | background-color: rgb(248, 248, 248); 12 | } 13 | 14 | #commits td { 15 | padding: 2pt 4pt 2pt 4pt; 16 | } 17 | 18 | /* Display hashes in monospace font */ 19 | #commits td:first-child { 20 | font-family: Consolas, monospace; 21 | font-size: 0.9em; 22 | } 23 | 24 | /* Don't wrap dates onto multiple lines */ 25 | #commits td:nth-child(2) { 26 | white-space: nowrap; 27 | } 28 | -------------------------------------------------------------------------------- /demos/resources/common.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 14pt; 3 | font-family: sans-serif; 4 | } 5 | 6 | button { 7 | font-size: 14pt; 8 | } 9 | 10 | #buttons { 11 | margin: 14pt 0; 12 | } 13 | 14 | #back-to-index { 15 | position: absolute; 16 | right: 10px; 17 | top: 10px; 18 | font-size: smaller; 19 | } 20 | 21 | h1 { 22 | font-weight: normal; 23 | font-family: Rockwell, Georgia, serif; 24 | font-size: 1.333em; 25 | color: #3c790a; 26 | margin-top: 10px; 27 | } 28 | 29 | dd { 30 | margin-bottom: 1em; 31 | } 32 | 33 | table { 34 | border-collapse: collapse; 35 | } 36 | 37 | .highlight { 38 | border: 1px solid #ddd; 39 | padding: 14pt 28pt 14pt 28pt; 40 | font-family: Consolas, monospace; 41 | background-color: #f8f8f8; 42 | } 43 | -------------------------------------------------------------------------------- /demos/resources/highlight-default.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Original highlight.js style (c) Ivan Sagalaev 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | background: #F0F0F0; 12 | } 13 | 14 | 15 | /* Base color: saturation 0; */ 16 | 17 | .hljs, 18 | .hljs-subst { 19 | color: #444; 20 | } 21 | 22 | .hljs-comment { 23 | color: #888888; 24 | } 25 | 26 | .hljs-keyword, 27 | .hljs-attribute, 28 | .hljs-selector-tag, 29 | .hljs-meta-keyword, 30 | .hljs-doctag, 31 | .hljs-name { 32 | font-weight: bold; 33 | } 34 | 35 | 36 | /* User color: hue: 0 */ 37 | 38 | .hljs-type, 39 | .hljs-string, 40 | .hljs-number, 41 | .hljs-selector-id, 42 | .hljs-selector-class, 43 | .hljs-quote, 44 | .hljs-template-tag, 45 | .hljs-deletion { 46 | color: #880000; 47 | } 48 | 49 | .hljs-title, 50 | .hljs-section { 51 | color: #880000; 52 | font-weight: bold; 53 | } 54 | 55 | .hljs-regexp, 56 | .hljs-symbol, 57 | .hljs-variable, 58 | .hljs-template-variable, 59 | .hljs-link, 60 | .hljs-selector-attr, 61 | .hljs-selector-pseudo { 62 | color: #BC6060; 63 | } 64 | 65 | 66 | /* Language color: hue: 90; */ 67 | 68 | .hljs-literal { 69 | color: #78A960; 70 | } 71 | 72 | .hljs-built_in, 73 | .hljs-bullet, 74 | .hljs-code, 75 | .hljs-addition { 76 | color: #397300; 77 | } 78 | 79 | 80 | /* Meta color: hue: 200 */ 81 | 82 | .hljs-meta { 83 | color: #1f7199; 84 | } 85 | 86 | .hljs-meta-string { 87 | color: #4d99bf; 88 | } 89 | 90 | 91 | /* Misc effects */ 92 | 93 | .hljs-emphasis { 94 | font-style: italic; 95 | } 96 | 97 | .hljs-strong { 98 | font-weight: bold; 99 | } 100 | -------------------------------------------------------------------------------- /demos/resources/highlight.pack.js: -------------------------------------------------------------------------------- 1 | /*! highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */ 2 | !function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){s+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"
":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("javascript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},c={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,c,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:t,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,c,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:r+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:r,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+r+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:r},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:s}]}]},{b://,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:r}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}}); -------------------------------------------------------------------------------- /demos/resources/jank-meter.css: -------------------------------------------------------------------------------- 1 | #jank-meter { 2 | text-align: center; 3 | animation-direction: alternate; 4 | animation-duration: 1s; 5 | animation-iteration-count: infinite; 6 | animation-timing-function: linear; 7 | animation-name: spacing; 8 | } 9 | 10 | @keyframes spacing { 11 | 0% { letter-spacing: 0em; } 12 | 100% { letter-spacing: 2em; } 13 | } 14 | -------------------------------------------------------------------------------- /demos/resources/web-animations.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["src/scope.js","src/timing-utilities.js","src/normalize-keyframes.js","src/deprecation.js","src/web-animations-bonus-cancel-events.js","src/web-animations-bonus-object-form-keyframes.js"],"names":["webAnimationsShared","webAnimations1","webAnimationsNext","webAnimationsTesting","shared","testing","cloneTimingInput","timingInput","clone","m","AnimationEffectTiming","this","_delay","_endDelay","_fill","_iterationStart","_iterations","_duration","_playbackRate","_direction","_easing","_easingFunction","linear","isInvalidTimingDeprecated","isDeprecated","makeTiming","forGroup","effect","timing","fill","duration","isNaN","Object","getOwnPropertyNames","forEach","property","fills","indexOf","directions","numericTimingToObject","normalizeTimingInput","cubic","a","b","c","d","x","f","start_gradient","end_gradient","start","end","mid","xEst","Math","abs","step","count","pos","stepSize","normalizeEasing","easing","styleForCleaning","document","createElement","style","animationTimingFunction","normalizedEasing","TypeError","parseEasingFunction","cubicData","cubicBezierRe","exec","apply","slice","map","Number","stepData","stepRe","Start","middle","Middle","End","presets","calculateActiveDuration","repeatedDuration","playbackRate","iterations","calculatePhase","activeDuration","localTime","PhaseNone","endTime","delay","endDelay","min","PhaseBefore","PhaseAfter","PhaseActive","calculateActiveTime","fillMode","phase","calculateOverallProgress","iterationDuration","activeTime","iterationStart","overallProgress","calculateSimpleIterationProgress","simpleIterationProgress","Infinity","calculateCurrentIteration","floor","calculateDirectedProgress","playbackDirection","currentIteration","currentDirection","calculateIterationProgress","directedProgress","direction","split","prototype","_setMember","member","value","_effect","_timingInput","_timing","_animation","_rebuildUnderlyingAnimation","ease","ease-in","ease-out","ease-in-out","step-start","step-middle","step-end","numberString","RegExp","antiAlias","aliases","isNotAnimatable","lastIndexOf","expandShorthandAndAntiAlias","result","longProperties","shorthandToLonghand","shorthandExpanderElem","i","longProperty","longhandValue","convertToArrayForm","effectInput","normalizedEffectInput","values","Array","isArray","keyframe","numKeyframes","length","offset","composite","push","sort","normalizeKeyframes","spaceKeyframes","keyframes","previousIndex","previousOffset","j","window","Symbol","iterator","from","originalKeyframe","memberValue","isFinite","type","DOMException","NOT_SUPPORTED_ERR","name","message","everyFrameHasOffset","filter","background","border","borderBottom","borderColor","borderLeft","borderRadius","borderRight","borderTop","borderWidth","flex","font","margin","outline","padding","createElementNS","borderWidthAliases","thin","medium","thick","borderBottomWidth","borderLeftWidth","borderRightWidth","borderTopWidth","fontSize","xx-small","x-small","small","large","x-large","xx-large","fontWeight","normal","bold","outlineWidth","textShadow","none","boxShadow","silenced","feature","date","advice","plural","auxVerb","today","Date","expiry","setMonth","getMonth","console","warn","toDateString","deprecated","Error","animate","oncancel","now","performance","AnimationCancelEvent","target","currentTime","timelineTime","bubbles","cancelable","currentTarget","defaultPrevented","eventPhase","Event","AT_TARGET","timeStamp","originalElementAnimate","Element","options","animation","call","_cancelHandlers","originalCancel","cancel","event","handlers","concat","setTimeout","handler","originalAddEventListener","addEventListener","originalRemoveEventListener","removeEventListener","index","splice","element","documentElement","animated","originalOpacity","getComputedStyle","getPropertyValue","testOpacity","opacity","error","exports"],"mappings":";;;;;;;;;;;;;;CAcA,SAAIA,EAAAA,GAAJ,GAAIA,MACAC,KACAC,KAGEC,EAAuB,MCL7B,SAAUC,EAAQC,GAMhB,QAASC,GAAiBC,GACxB,GAA0B,gBAAfA,GACT,MAAOA,EAET,IAAIC,KACJ,KAAK,GAAIC,KAAKF,GACZC,EAAMC,GAAKF,EAAYE,EAEzB,OAAOD,GAGT,QAASE,KACPC,KAAKC,OAAS,EACdD,KAAKE,UAAY,EACjBF,KAAKG,MAAQ,OACbH,KAAKI,gBAAkB,EACvBJ,KAAKK,YAAc,EACnBL,KAAKM,UAAY,EACjBN,KAAKO,cAAgB,EACrBP,KAAKQ,WAAa,SAClBR,KAAKS,QAAU,SACfT,KAAKU,gBAAkBC,EAGzB,QAASC,KACP,MAAOnB,GAAOoB,aAAa,wBAAyB,aAAc,gDAAA,GA8EpE,QAASC,GAAWlB,EAAamB,EAAUC,GACzC,GAAIC,GAAS,GAAIlB,EA4BjB,OA3BIgB,KACFE,EAAOC,KAAO,OACdD,EAAOE,SAAW,QAEM,gBAAfvB,IAA4BwB,MAAMxB,OAAAA,KAElCA,GACTyB,OAAOC,oBAAoB1B,GAAa2B,QAAQ,SAASC,GACvD,GAA6B,QAAzB5B,EAAY4B,GAAqB,CACnC,IAA+B,gBAApBP,GAAOO,IAAqC,YAAZA,KACL,gBAAzB5B,GAAY4B,IAAyBJ,MAAMxB,EAAY4B,KAChE,MAGJ,IAAiB,QAAZA,IAAiE,GAAzCC,EAAMC,QAAQ9B,EAAY4B,IACrD,MAEF,IAAiB,aAAZA,IAA2E,GAA9CG,EAAWD,QAAQ9B,EAAY4B,IAC/D,MAEF,IAAgB,gBAAZA,GAAwD,IAA1B5B,EAAY4B,IAAmB/B,EAAOoB,aAAa,qCAAsC,aAAc,uCACvI,MAEFI,GAAOO,GAAY5B,EAAY4B,MAlBnCP,EAAOE,SAAWvB,EAsBbqB,EAGT,QAASW,GAAsBhC,GAQ7B,MAP0B,gBAAfA,KAEPA,EADEwB,MAAMxB,IACQuB,SAAU,IAEVA,SAAUvB,IAGvBA,EAGT,QAASiC,GAAqBjC,EAAamB,GAEzC,MADAnB,GAAcH,EAAOmC,sBAAsBhC,GACpCkB,EAAWlB,EAAamB,GAGjC,QAASe,GAAMC,EAAGC,EAAGC,EAAGC,GACtB,MAAIH,GAAI,GAAKA,EAAI,GAAKE,EAAI,GAAKA,EAAI,EAC1BtB,EAEF,SAASwB,GAqBZ,QAASC,GAAEL,EAAGC,EAAGlC,GAAK,MAAO,GAAIiC,GAAK,EAAIjC,IAAM,EAAIA,GAAKA,EAAI,EAAIkC,GAAK,EAAIlC,GAAKA,EAAIA,EAAIA,EAAIA,EAAIA,EApBjG,GAAIqC,GAAK,EAAG,CACV,GAAIE,GAAiB,CAKrB,OAJIN,GAAI,EACNM,EAAiBL,EAAID,GACbC,GAAKC,EAAI,IACjBI,EAAiBH,EAAID,GAChBI,EAAiBF,EAE1B,GAAIA,GAAK,EAAG,CACV,GAAIG,GAAe,CAKnB,OAJIL,GAAI,EACNK,GAAgBJ,EAAI,IAAMD,EAAI,GAClB,GAALA,GAAUF,EAAI,IACrBO,GAAgBN,EAAI,IAAMD,EAAI,IACzB,EAAIO,GAAgBH,EAAI,GAIjC,IADA,GAAII,GAAQ,EAAGC,EAAM,EACdD,EAAQC,GAAK,CAClB,GAAIC,IAAOF,EAAQC,GAAO,EAEtBE,EAAON,EAAEL,EAAGE,EAAGQ,EACnB,IAAIE,KAAKC,IAAIT,EAAIO,GAAQ,KACvB,MAAON,GAAEJ,EAAGE,EAAGO,EAEbC,GAAOP,EACTI,EAAQE,EAERD,EAAMC,EAGV,MAAOL,GAAEJ,EAAGE,EAAGO,IAQnB,QAASI,GAAKC,EAAOC,GACnB,MAAO,UAASZ,GACd,GAAIA,GAAK,EACP,MAAO,EAET,IAAIa,GAAW,EAAIF,CAEnB,QADAX,GAAKY,EAAMC,GACAb,EAAIa,GAmBnB,QAASC,GAAgBC,GAClBC,IACHA,EAAmBC,SAASC,cAAc,OAAOC,OAEnDH,EAAiBI,wBAA0B,GAC3CJ,EAAiBI,wBAA0BL,CAC3C,IAAIM,GAAmBL,EAAiBI,uBACxC,IAAwB,IAApBC,GAA0B5C,IAC5B,KAAM,IAAI6C,WAAUP,EAAS,mCAE/B,OAAOM,GAGT,QAASE,GAAoBF,GAC3B,GAAwB,UAApBA,EACF,MAAO7C,EAET,IAAIgD,GAAYC,EAAcC,KAAKL,EACnC,IAAIG,EACF,MAAO7B,GAAMgC,MAAM9D,KAAM2D,EAAUI,MAAM,GAAGC,IAAIC,QAElD,IAAIC,GAAWC,EAAON,KAAKL,EAC3B,OAAIU,GACKrB,EAAKoB,OAAOC,EAAS,KAAM3B,MAAS6B,EAAOC,OAAUC,EAAQ9B,IAAO+B,GAAKL,EAAS,KAE9EM,EAAQhB,IAMd7C,EAGT,QAAS8D,GAAwBxD,GAC/B,MAAO0B,MAAKC,IAAI8B,EAAiBzD,GAAUA,EAAO0D,cAGpD,QAASD,GAAiBzD,GAExB,MAAwB,KAApBA,EAAOE,UAAwC,IAAtBF,EAAO2D,WAC3B,EAEF3D,EAAOE,SAAWF,EAAO2D,WAQlC,QAASC,GAAeC,EAAgBC,EAAW9D,GAEjD,GAAiB,MAAb8D,EACF,MAAOC,EAGT,IAAIC,GAAUhE,EAAOiE,MAAQJ,EAAiB7D,EAAOkE,QACrD,OAAIJ,GAAYpC,KAAKyC,IAAInE,EAAOiE,MAAOD,GAC9BI,EAELN,GAAapC,KAAKyC,IAAInE,EAAOiE,MAAQJ,EAAgBG,GAChDK,EAGFC,EAGT,QAASC,GAAoBV,EAAgBW,EAAUV,EAAWW,EAAOR,GAEvE,OAAQQ,GACN,IAAKL,GACH,MAAgB,aAAZI,GAAuC,QAAZA,EACtB,EACF,IACT,KAAKF,GACH,MAAOR,GAAYG,CACrB,KAAKI,GACH,MAAgB,YAAZG,GAAsC,QAAZA,EACrBX,EACF,IACT,KAAKE,GACH,MAAO,OAIb,QAASW,GAAyBC,EAAmBF,EAAOd,EAAYiB,EAAYC,GAElF,GAAIC,GAAkBD,CAQtB,OAP0B,KAAtBF,EACEF,IAAUL,IACZU,GAAmBnB,GAGrBmB,GAAmBF,EAAaD,EAE3BG,EAGT,QAASC,GAAiCD,EAAiBD,EAAgBJ,EAAOd,EAAYiB,EAAYD,GAGxG,GAAIK,GAA2BF,IAAoBG,EAAAA,EAAYJ,EAAiB,EAAIC,EAAkB,CAKtG,OAJgC,KAA5BE,GAAiCP,IAAUJ,GAA6B,IAAfV,GACzC,IAAfiB,GAA0C,IAAtBD,IACvBK,EAA0B,GAErBA,EAGT,QAASE,GAA0BT,EAAOd,EAAYqB,EAAyBF,GAE7E,MAAIL,KAAUJ,GAAcV,IAAesB,EAAAA,EAClCA,EAAAA,EAEuB,IAA5BD,EACKtD,KAAKyD,MAAML,GAAmB,EAEhCpD,KAAKyD,MAAML,GAGpB,QAASM,GAA0BC,EAAmBC,EAAkBN,GAEtE,GAAIO,GAAmBF,CACvB,IAA0B,WAAtBA,GAAwD,YAAtBA,EAAiC,CACrE,GAAIpE,GAAIqE,CACkB,uBAAtBD,IACFpE,GAAK,GAEPsE,EAAmB,SACftE,IAAMgE,EAAAA,GAAYhE,EAAI,GAAM,IAC9BsE,EAAmB,WAGvB,MAAyB,WAArBA,EACKP,EAEF,EAAIA,EAGb,QAASQ,GAA2B3B,EAAgBC,EAAW9D,GAC7D,GAAIyE,GAAQb,EAAeC,EAAgBC,EAAW9D,GAClD4E,EAAaL,EAAoBV,EAAgB7D,EAAOC,KAAM6D,EAAWW,EAAOzE,EAAOiE,MAC3F,IAAmB,OAAfW,EACF,MAAO,KAET,IAAIE,GAAkBJ,EAAyB1E,EAAOE,SAAUuE,EAAOzE,EAAO2D,WAAYiB,EAAY5E,EAAO6E,gBACzGG,EAA0BD,EAAiCD,EAAiB9E,EAAO6E,eAAgBJ,EAAOzE,EAAO2D,WAAYiB,EAAY5E,EAAOE,UAChJoF,EAAmBJ,EAA0BT,EAAOzE,EAAO2D,WAAYqB,EAAyBF,GAChGW,EAAmBL,EAA0BpF,EAAO0F,UAAWJ,EAAkBN,EAIrF,OAAOhF,GAAOP,gBAAgBgG,GA1XhC,GAAIjF,GAAQ,+BAA+BmF,MAAM,KAC7CjF,EAAa,sCAAsCiF,MAAM,KACzDjG,EAAS,SAASwB,GAAK,MAAOA,GA8BlCpC,GAAsB8G,WACpBC,WAAY,SAASC,EAAQC,GAC3BhH,KAAK,IAAM+G,GAAUC,EACjBhH,KAAKiH,UACPjH,KAAKiH,QAAQC,aAAaH,GAAUC,EACpChH,KAAKiH,QAAQE,QAAU1H,EAAOoC,qBAAqB7B,KAAKiH,QAAQC,cAChElH,KAAKiH,QAAQnC,eAAiBrF,EAAOgF,wBAAwBzE,KAAKiH,QAAQE,SACtEnH,KAAKiH,QAAQG,YACfpH,KAAKiH,QAAQG,WAAWC,gCAI9B1C,GAAIA,gBACF,MAAO3E,MAAKO,eAEd2E,GAAIA,OAAM8B,GACRhH,KAAK8G,WAAW,QAASE,IAE3B9B,GAAIA,SACF,MAAOlF,MAAKC,QAEdkF,GAAIA,UAAS6B,GACXhH,KAAK8G,WAAW,WAAYE,IAE9B7B,GAAIA,YACF,MAAOnF,MAAKE,WAEdgB,GAAIA,MAAK8F,GACPhH,KAAK8G,WAAW,OAAQE,IAE1B9F,GAAIA,QACF,MAAOlB,MAAKG,OAEd2F,GAAIA,gBAAekB,GACjB,IAAK5F,MAAM4F,IAAUA,EAAQ,IAAMpG,IACjC,KAAM,IAAI6C,WAAU,2DAA6DxC,OAAO6E,eAE1F9F,MAAK8G,WAAW,iBAAkBE,IAEpClB,GAAIA,kBACF,MAAO9F,MAAKI,iBAEde,GAAIA,UAAS6F,GACX,GAAa,QAATA,IAAoB5F,MAAM4F,IAAUA,EAAQ,IAAMpG,IACpD,KAAM,IAAI6C,WAAU,oDAAsDuD,EAE5EhH,MAAK8G,WAAW,WAAYE,IAE9B7F,GAAIA,YACF,MAAOnB,MAAKM,WAEdqG,GAAIA,WAAUK,GACZhH,KAAK8G,WAAW,YAAaE,IAE/BL,GAAIA,aACF,MAAO3G,MAAKQ,YAEd0C,GAAIA,QAAO8D,GACThH,KAAKU,gBAAkBgD,EAAoBT,EAAgB+D,IAC3DhH,KAAK8G,WAAW,SAAUE,IAE5B9D,GAAIA,UACF,MAAOlD,MAAKS,SAEdmE,GAAIA,YAAWoC,GACb,IAAK5F,MAAM4F,IAAUA,EAAQ,IAAMpG,IACjC,KAAM,IAAI6C,WAAU,8CAAgDuD,EAEtEhH,MAAK8G,WAAW,aAAcE,IAEhCpC,GAAIA,cACF,MAAO5E,MAAKK,aA4FhB,IAAI+D,GAAQ,EACRE,EAAS,GACTC,EAAM,EAaNC,GACF8C,KAAQxF,EAAM,IAAM,GAAK,IAAM,GAC/ByF,UAAWzF,EAAM,IAAM,EAAG,EAAG,GAC7B0F,WAAY1F,EAAM,EAAG,EAAG,IAAM,GAC9B2F,cAAe3F,EAAM,IAAM,EAAG,IAAM,GACpC4F,aAAc7E,EAAK,EAAGuB,GACtBuD,cAAe9E,EAAK,EAAGyB,GACvBsD,WAAY/E,EAAK,EAAG0B,IAGlBpB,EAAmB,KACnB0E,EAAe,qCACfjE,EAAgB,GAAIkE,QAAO,kBAAoBD,EAAe,IAAMA,EAAe,IAAMA,EAAe,IAAMA,EAAe,OAC7H1D,EAAS,gDAgDTa,EAAY,EACZK,EAAc,EACdC,EAAa,EACbC,EAAc,CA2GlB9F,GAAOE,iBAAmBA,EAC1BF,EAAOqB,WAAaA,EACpBrB,EAAOmC,sBAAwBA,EAC/BnC,EAAOoC,qBAAuBA,EAC9BpC,EAAOgF,wBAA0BA,EACjChF,EAAOgH,2BAA6BA,EACpChH,EAAOoF,eAAiBA,EACxBpF,EAAOwD,gBAAkBA,EACzBxD,EAAOiE,oBAAsBA,GAc5BrE,GCrZH,SAAUI,EAAQC,GAmIhB,QAASqI,GAAUvG,EAAUwF,GAC3B,MAAIxF,KAAYwG,GACPA,EAAQxG,GAAUwF,IAAUA,EAE9BA,EAGT,QAASiB,GAAgBzG,GAEvB,MAAoB,YAAbA,GAAmE,IAAzCA,EAAS0G,YAAY,YAAa,IAAsD,IAA1C1G,EAAS0G,YAAY,aAAc,GAIpH,QAASC,GAA4B3G,EAAUwF,EAAOoB,GACpD,IAAIH,EAAgBzG,GAApB,CAGA,GAAI6G,GAAiBC,EAAoB9G,EACzC,IAAI6G,EAAgB,CAClBE,EAAsBjF,MAAM9B,GAAYwF,CACxC,KAAK,GAAIwB,KAAKH,GAAgB,CAC5B,GAAII,GAAeJ,EAAeG,GAC9BE,EAAgBH,EAAsBjF,MAAMmF,EAChDL,GAAOK,GAAgBV,EAAUU,EAAcC,QAGjDN,GAAO5G,GAAYuG,EAAUvG,EAAUwF,IAI3C,QAAS2B,GAAmBC,GAC1B,GAAIC,KAEJ,KAAK,GAAIrH,KAAYoH,GACnB,KAAIpH,KAAa,SAAU,SAAU,cAArC,CAIA,GAAIsH,GAASF,EAAYpH,EACpBuH,OAAMC,QAAQF,KACjBA,GAAUA,GAKZ,KAAK,GAFDG,GACAC,EAAeJ,EAAOK,OACjBX,EAAI,EAAGA,EAAIU,EAAcV,IAChCS,KAGEA,EAASG,OADP,UAAYR,GACIA,EAAYQ,OACL,GAAhBF,EACS,EAEAV,GAAKU,EAAe,GAGpC,UAAYN,KACdK,EAAS/F,OAAS0F,EAAY1F,QAG5B,aAAe0F,KACjBK,EAASI,UAAYT,EAAYS,WAGnCJ,EAASzH,GAAYsH,EAAON,GAE5BK,EAAsBS,KAAKL,GAK/B,MADAJ,GAAsBU,KAAK,SAASxH,EAAGC,GAAK,MAAOD,GAAEqH,OAASpH,EAAEoH,SACzDP,EAGT,QAASW,GAAmBZ,GAqE1B,QAASa,KACP,GAAIN,GAASO,EAAUP,MACa,OAAhCO,EAAUP,EAAS,GAAGC,SACxBM,EAAUP,EAAS,GAAGC,OAAS,GAC7BD,EAAS,GAA4B,MAAvBO,EAAU,GAAGN,SAC7BM,EAAU,GAAGN,OAAS,EAIxB,KAAK,GAFDO,GAAgB,EAChBC,EAAiBF,EAAU,GAAGN,OACzBZ,EAAI,EAAGA,EAAIW,EAAQX,IAAK,CAC/B,GAAIY,GAASM,EAAUlB,GAAGY,MAC1B,IAAc,MAAVA,EAAgB,CAClB,IAAK,GAAIS,GAAI,EAAGA,EAAIrB,EAAImB,EAAeE,IACrCH,EAAUC,EAAgBE,GAAGT,OAASQ,GAAkBR,EAASQ,GAAkBC,GAAKrB,EAAImB,EAC9FA,GAAgBnB,EAChBoB,EAAiBR,IAnFvB,GAAmB,MAAfR,EACF,QAGEkB,QAAOC,QAAUA,OAAOC,UAAYjB,MAAMlC,UAAUoD,MAAQrB,EAAYmB,OAAOC,YAEjFpB,EAAcG,MAAMkB,KAAKrB,IAGtBG,MAAMC,QAAQJ,KACjBA,EAAcD,EAAmBC,GA0CnC,KAAK,GAvCDc,GAAYd,EAAY5E,IAAI,SAASkG,GACvC,GAAIjB,KACJ,KAAK,GAAIlC,KAAUmD,GAAkB,CACnC,GAAIC,GAAcD,EAAiBnD,EACnC,IAAc,UAAVA,GACF,GAAmB,MAAfoD,EAAqB,CAEvB,GADAA,EAAclG,OAAOkG,IAChBC,SAASD,GACZ,KAAM,IAAI1G,WAAU,oCACtB,IAAI0G,EAAc,GAAKA,EAAc,EACnC,KAAM,IAAI1G,WAAU,kDAEnB,IAAc,aAAVsD,EAAuB,CAChC,GAAmB,OAAfoD,GAAuC,cAAfA,EAC1B,MACEE,KAAMC,aAAaC,kBACnBC,KAAM,oBACNC,QAAS,mCAEN,IAAmB,WAAfN,EACT,KAAM,IAAI1G,WAAU,0BAA4B0G,EAAc,SAGhEA,GADmB,UAAVpD,EACKtH,EAAOwD,gBAAgBkH,GAEvB,GAAKA,CAErBhC,GAA4BpB,EAAQoD,EAAalB,GAMnD,WAAA,IAJIA,EAASG,SACXH,EAASG,OAAS,UAAA,IAChBH,EAAS/F,SACX+F,EAAS/F,OAAS,UACb+F,IAGLyB,GAAAA,EAEAd,GAAAA,EAAAA,EACKpB,EAAI,EAAGA,EAAIkB,EAAUP,OAAQX,IAAK,CACzC,GAAIY,GAASM,EAAUlB,GAAGY,MAC1B,IAAc,MAAVA,EAAgB,CAClB,GAAIA,EAASQ,EACX,KAAM,IAAInG,WAAU,uEAEtBmG,GAAiBR,MAEjBsB,IAAAA,EA8BJ,MA1BAhB,GAAYA,EAAUiB,OAAO,SAAS1B,GACpC,MAAOA,GAASG,QAAU,GAAKH,EAASG,QAAU,IAsB/CsB,GACHjB,IAEKC,EAvST,GAAIpB,IACFsC,YACE,kBACA,qBACA,iBACA,mBACA,uBACA,mBACA,iBACA,mBAEFC,QACE,iBACA,iBACA,iBACA,mBACA,mBACA,mBACA,oBACA,oBACA,oBACA,kBACA,kBACA,mBAEFC,cACE,oBACA,oBACA,qBAEFC,aACE,iBACA,mBACA,oBACA,mBAEFC,YACE,kBACA,kBACA,mBAEFC,cACE,sBACA,uBACA,0BACA,0BAEFC,aACE,mBACA,mBACA,oBAEFC,WACE,iBACA,iBACA,kBAEFC,aACE,iBACA,mBACA,oBACA,mBAEFC,MACE,WACA,aACA,aAEFC,MACE,aACA,WACA,YACA,cACA,aACA,cAEFC,QACE,YACA,cACA,eACA,cAEFC,SACE,eACA,eACA,gBAEFC,SACE,aACA,eACA,gBACA,gBAIAlD,EAAwBnF,SAASsI,gBAAgB,+BAAgC,OAEjFC,GACFC,KAAM,MACNC,OAAQ,MACRC,MAAO,OAGL9D,GACF+D,kBAAmBJ,EACnBK,gBAAiBL,EACjBM,iBAAkBN,EAClBO,eAAgBP,EAChBQ,UACEC,WAAY,MACZC,UAAW,MACXC,MAAS,MACTT,OAAU,OACVU,MAAS,OACTC,UAAW,OACXC,WAAY,QAEdC,YACEC,OAAQ,MACRC,KAAM,OAERC,aAAclB,EACdmB,YACEC,KAAM,2BAERC,WACED,KAAM,+BA4KVtN,GAAOkJ,mBAAqBA,EAC5BlJ,EAAO+J,mBAAqBA,GAM3BnK,GClTH,SAAUI,GAER,GAAIwN,KAEJxN,GAAOoB,aAAe,SAASqM,EAASC,EAAMC,EAAQC,GAKpD,GAAIC,GAAUD,EAAS,MAAQ,KAC3BE,EAAQ,GAAIC,MACZC,EAAS,GAAID,MAAKL,EAGtB,OAFAM,GAAOC,SAASD,EAAOE,WAAa,KAEhCJ,EAAQE,IACJP,IAAWD,IACfW,QAAQC,KAAK,mBAAqBX,EAAU,IAAMI,EAAU,wCAA0CG,EAAOK,eAAiB,KAAOV,GAEvIH,EAASC,IAAAA,EAAW,KAOxBzN,EAAOsO,WAAa,SAASb,EAASC,EAAMC,EAAQC,GAClD,GAAIC,GAAUD,EAAS,MAAQ,IAC/B,IAAI5N,EAAOoB,aAAaqM,EAASC,EAAMC,EAAQC,GAC7C,KAAM,IAAIW,OAAMd,EAAU,IAAMI,EAAU,yBAA2BF,KAIxE/N,i8jCChCH,WAEE,OAAA,KAAI+D,SAASC,cAAc,OAAO4K,YAAYC,SAA9C,CAKE,GAAIC,EACC,IAAIrE,OAAOsE,aAAeA,YAAYD,IAC3C,GAAIA,GAAM,WAAa,MAAOC,aAAYD,WAE1C,IAAIA,GAAM,WAAa,MAAOX,MAAKW,MAGrC,IAAIE,GAAuB,SAASC,EAAQC,EAAaC,GACvDxO,KAAKsO,OAASA,EACdtO,KAAKuO,YAAcA,EACnBvO,KAAKwO,aAAeA,EAEpBxO,KAAKqK,KAAO,SACZrK,KAAKyO,SAAAA,EACLzO,KAAK0O,YAAAA,EACL1O,KAAK2O,cAAgBL,EACrBtO,KAAK4O,kBAAAA,EACL5O,KAAK6O,WAAaC,MAAMC,UACxB/O,KAAKgP,UAAYxB,KAAKW,OAGpBc,EAAyBnF,OAAOoF,QAAQrI,UAAUoH,OACtDnE,QAAOoF,QAAQrI,UAAUoH,QAAU,SAASrF,EAAauG,GACvD,GAAIC,GAAYH,EAAuBI,KAAKrP,KAAM4I,EAAauG,EAE/DC,GAAUE,mBACVF,EAAUlB,SAAW,IAErB,IAAIqB,GAAiBH,EAAUI,MAC/BJ,GAAUI,OAAS,WACjBD,EAAeF,KAAKrP,KACpB,IAAIyP,GAAQ,GAAIpB,GAAqBrO,KAAM,KAAMmO,KAC7CuB,EAAW1P,KAAKsP,gBAAgBK,OAAO3P,KAAKkO,UAAYlO,KAAKkO,aACjE0B,YAAW,WACTF,EAASnO,QAAQ,SAASsO,GACxBA,EAAQR,KAAKI,EAAMnB,OAAQmB,MAE5B,GAGL,IAAIK,GAA2BV,EAAUW,gBACzCX,GAAUW,iBAAmB,SAAS1F,EAAMwF,GACpB,kBAAXA,IAAiC,UAARxF,EAClCrK,KAAKsP,gBAAgBhG,KAAKuG,GAE1BC,EAAyBT,KAAKrP,KAAMqK,EAAMwF,GAG9C,IAAIG,GAA8BZ,EAAUa,mBAW5C,OAVAb,GAAUa,oBAAsB,SAAS5F,EAAMwF,GAC7C,GAAY,UAARxF,EAAkB,CACpB,GAAI6F,GAAQlQ,KAAKsP,gBAAgB5N,QAAQmO,EACrCK,IAAS,GACXlQ,KAAKsP,gBAAgBa,OAAOD,EAAO,OAErCF,GAA4BX,KAAKrP,KAAMqK,EAAMwF,IAI1CT,OClEX,SAAU3P,GAgBR,GAAI2Q,GAAUhN,SAASiN,gBACnBjB,EAAY,KACZkB,GAAAA,CACJ,KACE,GAAIC,GAAkBC,iBAAiBJ,GAASK,iBAAiB,WAC7DC,EAAiC,KAAnBH,EAAyB,IAAM,GACjDnB,GAAYgB,EAAQnC,SAAS0C,SAAYD,EAAaA,KACjDvP,SAAU,IACfiO,EAAUb,YAAc,EACxB+B,EAAWE,iBAAiBJ,GAASK,iBAAiB,YAAcC,EACpE,MAAOE,IACP,QACIxB,GACFA,EAAUI,SAEd,IAAIc,EAAJ,CAIA,GAAIrB,GAAyBnF,OAAOoF,QAAQrI,UAAUoH,OACtDnE,QAAOoF,QAAQrI,UAAUoH,QAAU,SAASrF,EAAauG,GAUvD,MATIrF,QAAOC,QAAUA,OAAOC,UAAYjB,MAAMlC,UAAUoD,MAAQrB,EAAYmB,OAAOC,YAEjFpB,EAAcG,MAAMkB,KAAKrB,IAGtBG,MAAMC,QAAQJ,IAAgC,OAAhBA,IACjCA,EAAcnJ,EAAOkJ,mBAAmBC,IAGnCqG,EAAuBI,KAAKrP,KAAM4I,EAAauG,MAEvD9P,GL9CCE,EAAAA,KAEJsR,MACMrR,WAAAA,MAAuBQ","file":"web-animations.min.js"} -------------------------------------------------------------------------------- /demos/streaming-element-backpressure.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Streaming element with backpressure demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

Streaming element with backpressure demo

18 | Back to demo index 19 | 20 | 21 |
22 | 23 | 24 |
25 | 26 |
JANK METER
27 | 28 | 29 | 30 | 62 | -------------------------------------------------------------------------------- /demos/streaming-element-backpressure.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whatwg/streams/080852ccd709e063cc6af239ae07fc040e365179/demos/streaming-element-backpressure.mp4 -------------------------------------------------------------------------------- /demos/streaming-element.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Streaming element demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

Streaming element demo

18 | Back to demo index 19 | 20 | 21 |
22 | 23 | 24 |
25 | 26 |
JANK METER
27 | 28 | 29 | 30 | 62 | -------------------------------------------------------------------------------- /demos/streaming-element.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whatwg/streams/080852ccd709e063cc6af239ae07fc040e365179/demos/streaming-element.mp4 -------------------------------------------------------------------------------- /demos/tags/streaming-element-backpressure.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 'use strict'; 15 | 16 | customElements.define('streaming-element-backpressure', 17 | class StreamingElementBackPressure extends HTMLElement { 18 | constructor() { 19 | super(); 20 | this.reset(); 21 | } 22 | 23 | reset() { 24 | this.innerHTML = ''; 25 | 26 | const iframeReady = new Promise(resolve => { 27 | const iframe = document.createElement('iframe'); 28 | iframe.style.display = 'none'; 29 | document.body.appendChild(iframe); 30 | 31 | iframe.onload = () => { 32 | iframe.onload = null; 33 | iframe.contentDocument.write(''); 34 | this.appendChild(iframe.contentDocument.querySelector('streaming-element-inner')); 35 | resolve(iframe); 36 | }; 37 | iframe.src = ''; 38 | }); 39 | 40 | async function end() { 41 | let iframe = await iframeReady; 42 | iframe.contentDocument.write(''); 43 | iframe.contentDocument.close(); 44 | iframe.remove(); 45 | } 46 | 47 | let idlePromise; 48 | let charactersWrittenInThisChunk = 0; 49 | // Sometimes the browser will decide to target 30fps and nothing we do will 50 | // make it change its mind. To avoid bad behaviour in this case, 51 | // always target 30fps. 52 | const MS_PER_FRAME = 1000/30; 53 | // This is dynamically adjusted according to the measured wait time. 54 | let charactersPerChunk = 4096; 55 | // Smooth over several frames to avoid overcorrection for outliers. 56 | let lastFewFrames = []; 57 | const FRAMES_TO_SMOOTH_OVER = 3; 58 | 59 | function startNewChunk() { 60 | idlePromise = new Promise(resolve => { 61 | window.requestAnimationFrame(resolve); 62 | }); 63 | charactersWrittenInThisChunk = 0; 64 | } 65 | 66 | this.writable = new WritableStream({ 67 | async write(chunk) { 68 | if (idlePromise === undefined) { 69 | startNewChunk(); 70 | await idlePromise; 71 | startNewChunk(); 72 | } 73 | let iframe = await iframeReady; 74 | let cursor = 0; 75 | while (cursor < chunk.length) { 76 | const writeCharacters = Math.min(chunk.length - cursor, 77 | charactersPerChunk - charactersWrittenInThisChunk); 78 | iframe.contentDocument.write(chunk.substr(cursor, writeCharacters)); 79 | cursor += writeCharacters; 80 | charactersWrittenInThisChunk += writeCharacters; 81 | if (charactersWrittenInThisChunk === charactersPerChunk) { 82 | const timeBeforeWait = performance.now(); 83 | await idlePromise; 84 | const timeElapsed = performance.now() - timeBeforeWait; 85 | lastFewFrames.push(timeElapsed); 86 | if (lastFewFrames.length > FRAMES_TO_SMOOTH_OVER) { 87 | lastFewFrames.shift(); 88 | } 89 | const averageTimeElapsed = lastFewFrames.reduce((acc, val) => acc + val) / lastFewFrames.length; 90 | charactersPerChunk = Math.max(256, Math.ceil(charactersPerChunk * MS_PER_FRAME / averageTimeElapsed)); 91 | console.log(`timeElapsed = ${timeElapsed}, averageTimeElapsed = ${averageTimeElapsed}, charactersPerChunk = ${charactersPerChunk}`); 92 | startNewChunk(); 93 | } 94 | } 95 | }, 96 | close: end, 97 | abort: end 98 | }); 99 | } 100 | }); 101 | -------------------------------------------------------------------------------- /demos/tags/streaming-element.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 'use strict'; 15 | 16 | customElements.define('streaming-element', class StreamingElement extends HTMLElement { 17 | constructor() { 18 | super(); 19 | this.reset(); 20 | } 21 | 22 | reset() { 23 | this.innerHTML = ''; 24 | 25 | const iframeReady = new Promise(resolve => { 26 | const iframe = document.createElement('iframe'); 27 | iframe.style.display = 'none'; 28 | document.body.appendChild(iframe); 29 | 30 | iframe.onload = () => { 31 | iframe.onload = null; 32 | iframe.contentDocument.write(''); 33 | this.appendChild(iframe.contentDocument.querySelector('streaming-element-inner')); 34 | resolve(iframe); 35 | }; 36 | iframe.src = ''; 37 | }); 38 | 39 | async function end() { 40 | const iframe = await iframeReady; 41 | iframe.contentDocument.write(''); 42 | iframe.contentDocument.close(); 43 | iframe.remove(); 44 | } 45 | 46 | this.writable = new WritableStream({ 47 | async write(chunk) { 48 | const iframe = await iframeReady; 49 | iframe.contentDocument.write(chunk); 50 | }, 51 | close: end, 52 | abort: end 53 | }); 54 | } 55 | }); 56 | -------------------------------------------------------------------------------- /demos/tags/view-source.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 'use strict'; 15 | 16 | const animateOptions = { 17 | duration: 100, 18 | fill: 'forwards', 19 | easing: 'ease' 20 | }; 21 | 22 | customElements.define('view-source', class ViewSource extends HTMLElement { 23 | constructor() { 24 | super(); 25 | this._built = false; 26 | 27 | const shadowRoot = this.attachShadow({mode: 'open'}); 28 | 29 | shadowRoot.innerHTML = ` 30 | 31 | 45 | 49 |

 50 |     `;
 51 | 
 52 | 
 53 |     this._spinnyArrow = shadowRoot.querySelector('#spinny-arrow');
 54 |     this._label = shadowRoot.querySelector('#label');
 55 |     this._source = shadowRoot.querySelector('#source');
 56 | 
 57 |     const button = shadowRoot.querySelector('button');
 58 |     let shown = false;
 59 |     button.onclick = () => {
 60 |       if (shown) {
 61 |         this.hide();
 62 |         shown = false;
 63 |         return;
 64 |       }
 65 | 
 66 |       this.show();
 67 |       shown = true;
 68 |     };
 69 |   }
 70 | 
 71 |   hide() {
 72 |     this._spinnyArrow.animate({
 73 |       transform: ['rotate(90deg)', 'rotate(0deg)']
 74 |     }, animateOptions);
 75 | 
 76 |     const currentHeight = this._source.clientHeight;
 77 |     this._source.animate({
 78 |       height: [`${currentHeight}px`, '0px'],
 79 |       paddingTop: ['0.5em', '0'],
 80 |       paddingBottom: ['0.5em', '0']
 81 |     }, animateOptions);
 82 |   }
 83 | 
 84 |   show() {
 85 |     this._spinnyArrow.animate({
 86 |       transform: ['rotate(0deg)', 'rotate(90deg)']
 87 |     }, animateOptions);
 88 | 
 89 |     const currentHeight = this._source.clientHeight;
 90 |     this._source.style.overflow = 'hidden';
 91 |     this._source.animate({
 92 |       height: ['0', 'auto'],
 93 |       paddingTop: ['0', '0.5em'],
 94 |       paddingBottom: ['0', '0.5em']
 95 |     }, animateOptions);
 96 |   }
 97 | 
 98 |   async connectedCallback() {
 99 |     if (this._built) {
100 |       return;
101 |     }
102 | 
103 |     const res = await fetch(location.href);
104 |     const text = await res.text();
105 | 
106 |     const [,snippedText] = /\/\/ BEGIN SOURCE TO VIEW([\s\S]+)\/\/ END SOURCE TO VIEW/.exec(text);
107 | 
108 |     this._source.textContent = snippedText.trim();
109 |     hljs.highlightBlock(this._source);
110 |     this._built = true;
111 |   }
112 | });
113 | 


--------------------------------------------------------------------------------
/demos/transforms/parse-json.js:
--------------------------------------------------------------------------------
1 | function parseJSON() {
2 |   return new TransformStream({
3 |     transform(chunk, controller) {
4 |       controller.enqueue(JSON.parse(chunk));
5 |     }
6 |   });
7 | }
8 | 


--------------------------------------------------------------------------------
/demos/transforms/split-stream.js:
--------------------------------------------------------------------------------
 1 | function splitStream(splitOn) {
 2 |   let buffer = '';
 3 | 
 4 |   return new TransformStream({
 5 |     transform(chunk, controller) {
 6 |       buffer += chunk;
 7 |       const parts = buffer.split(splitOn);
 8 |       parts.slice(0, -1).forEach(part => controller.enqueue(part));
 9 |       buffer = parts[parts.length - 1];
10 |     },
11 |     flush(controller) {
12 |       if (buffer) controller.enqueue(buffer);
13 |     }
14 |   });
15 | }
16 | 


--------------------------------------------------------------------------------
/demos/transforms/text-encode-transform.js:
--------------------------------------------------------------------------------
  1 | // Copyright 2016 Google Inc.
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | // Polyfill for TextEncoderStream and TextDecoderStream
 16 | 
 17 | (function() {
 18 |   'use strict';
 19 | 
 20 |   if (typeof self.TextEncoderStream === 'function' &&
 21 |       typeof self.TextDecoderStream === 'function') {
 22 |     // The constructors exist. Assume that they work and don't replace them.
 23 |     return;
 24 |   }
 25 | 
 26 |   if (typeof self.TextEncoder !== 'function') {
 27 |     throw new ReferenceError('TextEncoder implementation required');
 28 |   }
 29 | 
 30 |   if (typeof self.TextDecoder !== 'function') {
 31 |     throw new ReferenceError('TextDecoder implementation required');
 32 |   }
 33 | 
 34 |   // These symbols end up being different for every realm, so mixing objects
 35 |   // created in one realm with methods created in another fails.
 36 |   const codec = Symbol('codec');
 37 |   const transform = Symbol('transform');
 38 | 
 39 |   class TextEncoderStream {
 40 |     constructor() {
 41 |       this[codec] = new TextEncoder();
 42 |       this[transform] =
 43 |           new TransformStream(new TextEncodeTransformer(this[codec]));
 44 |     }
 45 |   }
 46 | 
 47 |   class TextDecoderStream {
 48 |     constructor(label = undefined, options = undefined) {
 49 |       this[codec] = new TextDecoder(label, options);
 50 |       this[transform] = new TransformStream(
 51 |           new TextDecodeTransformer(this[codec]));
 52 |     }
 53 |   }
 54 | 
 55 |   // ECMAScript class syntax will create getters that are non-enumerable, but we
 56 |   // need them to be enumerable in WebIDL-style, so we add them manually.
 57 |   // "readable" and "writable" are always delegated to the TransformStream
 58 |   // object. Properties specified in |properties| are delegated to the
 59 |   // underlying TextEncoder or TextDecoder.
 60 |   function addDelegatingProperties(prototype, properties) {
 61 |     for (const transformProperty of ['readable', 'writable']) {
 62 |       addGetter(prototype, transformProperty, function() {
 63 |         return this[transform][transformProperty];
 64 |       });
 65 |     }
 66 | 
 67 |     for (const codecProperty of properties) {
 68 |       addGetter(prototype, codecProperty, function() {
 69 |         return this[codec][codecProperty];
 70 |       });
 71 |     }
 72 |   }
 73 | 
 74 |   function addGetter(prototype, property, getter) {
 75 |     Object.defineProperty(prototype, property,
 76 |                           {
 77 |                             configurable: true,
 78 |                             enumerable: true,
 79 |                             get: getter
 80 |                           });
 81 |   }
 82 | 
 83 |   addDelegatingProperties(TextEncoderStream.prototype, ['encoding']);
 84 |   addDelegatingProperties(TextDecoderStream.prototype,
 85 |                           ['encoding', 'fatal', 'ignoreBOM']);
 86 | 
 87 |   class TextEncodeTransformer {
 88 |     constructor() {
 89 |       this._encoder = new TextEncoder();
 90 |       this._carry = undefined;
 91 |     }
 92 | 
 93 |     transform(chunk, controller) {
 94 |       chunk = String(chunk);
 95 |       if (this._carry !== undefined) {
 96 |         chunk = this._carry + chunk;
 97 |         this._carry = undefined;
 98 |       }
 99 |       const terminalCodeUnit = chunk.charCodeAt(chunk.length - 1);
100 |       if (terminalCodeUnit >= 0xD800 && terminalCodeUnit < 0xDC00) {
101 |         this._carry = chunk.substring(chunk.length - 1);
102 |         chunk = chunk.substring(0, chunk.length - 1);
103 |       }
104 |       const encoded = this._encoder.encode(chunk);
105 |       if (encoded.length > 0) {
106 |         controller.enqueue(encoded);
107 |       }
108 |     }
109 | 
110 |     flush(controller) {
111 |       if (this._carry !== undefined) {
112 |         controller.enqueue(this._encoder.encode(this._carry));
113 |         this._carry = undefined;
114 |       }
115 |     }
116 |   }
117 | 
118 |   class TextDecodeTransformer {
119 |     constructor(decoder) {
120 |       this._decoder = new TextDecoder(decoder.encoding, {
121 |         fatal: decoder.fatal,
122 |         ignoreBOM: decoder.ignoreBOM
123 |       });
124 |     }
125 | 
126 |     transform(chunk, controller) {
127 |       const decoded = this._decoder.decode(chunk, {stream: true});
128 |       if (decoded != '') {
129 |         controller.enqueue(decoded);
130 |       }
131 |     }
132 | 
133 |     flush(controller) {
134 |       // If {fatal: false} is in options (the default), then the final call to
135 |       // decode() can produce extra output (usually the unicode replacement
136 |       // character 0xFFFD). When fatal is true, this call is just used for its
137 |       // side-effect of throwing a TypeError exception if the input is
138 |       // incomplete.
139 |       var output = this._decoder.decode();
140 |       if (output !== '') {
141 |         controller.enqueue(output);
142 |       }
143 |     }
144 |   }
145 | 
146 |   function exportAs(name, value) {
147 |     // Make it stringify as [object ] rather than [object Object].
148 |     value.prototype[Symbol.toStringTag] = name;
149 |     Object.defineProperty(self, name,
150 |                           {
151 |                             configurable: true,
152 |                             enumerable: false,
153 |                             writable: true,
154 |                             value
155 |                           });
156 |   }
157 | 
158 |   exportAs('TextEncoderStream', TextEncoderStream);
159 |   exportAs('TextDecoderStream', TextDecoderStream);
160 | })();
161 | 


--------------------------------------------------------------------------------
/demos/transforms/transform-stream-polyfill.js:
--------------------------------------------------------------------------------
  1 | // Quick and dirty polyfill for TransformStream.
  2 | // Based on the reference implementation:
  3 | // https://github.com/whatwg/streams/blob/main/reference-implementation/lib/transform-stream.js
  4 | 
  5 | (function() {
  6 | 'use strict';
  7 | 
  8 | if ('TransformStream' in self) {
  9 |   return;
 10 | }
 11 | 
 12 | // Methods on the transform stream controller object
 13 | 
 14 | function TransformStreamCloseReadable(transformStream) {
 15 |   if (transformStream._errored === true) {
 16 |     throw new TypeError('TransformStream is already errored');
 17 |   }
 18 | 
 19 |   if (transformStream._readableClosed === true) {
 20 |     throw new TypeError('Readable side is already closed');
 21 |   }
 22 | 
 23 |   TransformStreamCloseReadableInternal(transformStream);
 24 | }
 25 | 
 26 | function TransformStreamEnqueueToReadable(transformStream, chunk) {
 27 |   if (transformStream._errored === true) {
 28 |     throw new TypeError('TransformStream is already errored');
 29 |   }
 30 | 
 31 |   if (transformStream._readableClosed === true) {
 32 |     throw new TypeError('Readable side is already closed');
 33 |   }
 34 | 
 35 |   // We throttle transformer.transform invocation based on the backpressure of the ReadableStream, but we still
 36 |   // accept TransformStreamEnqueueToReadable() calls.
 37 | 
 38 |   const controller = transformStream._readableController;
 39 | 
 40 |   try {
 41 |     ReadableStreamDefaultControllerEnqueue(controller, chunk);
 42 |   } catch (e) {
 43 |     // This happens when readableStrategy.size() throws.
 44 |     // The ReadableStream has already errored itself.
 45 |     transformStream._readableClosed = true;
 46 |     TransformStreamErrorIfNeeded(transformStream, e);
 47 | 
 48 |     throw transformStream._storedError;
 49 |   }
 50 | 
 51 |   const desiredSize = ReadableStreamDefaultControllerGetDesiredSize(controller);
 52 |   const maybeBackpressure = desiredSize <= 0;
 53 | 
 54 |   if (maybeBackpressure === true && transformStream._backpressure === false) {
 55 |     // This allows pull() again. When desiredSize is 0, it's possible that a pull() will happen immediately (but
 56 |     // asynchronously) after this because of pending read()s and set _backpressure back to false.
 57 |     //
 58 |     // If pull() could be called from inside enqueue(), then this logic would be wrong. This cannot happen
 59 |     // because there is always a promise pending from start() or pull() when _backpressure is false.
 60 |     TransformStreamSetBackpressure(transformStream, true);
 61 |   }
 62 | }
 63 | 
 64 | function TransformStreamError(transformStream, e) {
 65 |   if (transformStream._errored === true) {
 66 |     throw new TypeError('TransformStream is already errored');
 67 |   }
 68 | 
 69 |   TransformStreamErrorInternal(transformStream, e);
 70 | }
 71 | 
 72 | // Abstract operations.
 73 | 
 74 | function TransformStreamCloseReadableInternal(transformStream) {
 75 |   ReadableStreamDefaultControllerClose(transformStream._readableController);
 76 | 
 77 |   transformStream._readableClosed = true;
 78 | }
 79 | 
 80 | function TransformStreamErrorIfNeeded(transformStream, e) {
 81 |   if (transformStream._errored === false) {
 82 |     TransformStreamErrorInternal(transformStream, e);
 83 |   }
 84 | }
 85 | 
 86 | function TransformStreamErrorInternal(transformStream, e) {
 87 |   transformStream._errored = true;
 88 |   transformStream._storedError = e;
 89 | 
 90 |   if (transformStream._writableDone === false) {
 91 |     WritableStreamDefaultControllerError(transformStream._writableController, e);
 92 |   }
 93 |   if (transformStream._readableClosed === false) {
 94 |     ReadableStreamDefaultControllerError(transformStream._readableController, e);
 95 |   }
 96 | }
 97 | 
 98 | // Used for preventing the next write() call on TransformStreamSink until there
 99 | // is no longer backpressure.
100 | function TransformStreamReadableReadyPromise(transformStream) {
101 |   if (transformStream._backpressure === false) {
102 |     return Promise.resolve();
103 |   }
104 | 
105 |   return transformStream._backpressureChangePromise;
106 | }
107 | 
108 | function TransformStreamSetBackpressure(transformStream, backpressure) {
109 |   // Passes also when called during construction.
110 | 
111 |   if (transformStream._backpressureChangePromise !== undefined) {
112 |     // The fulfillment value is just for a sanity check.
113 |     transformStream._backpressureChangePromise_resolve(backpressure);
114 |   }
115 | 
116 |   transformStream._backpressureChangePromise = new Promise(resolve => {
117 |     transformStream._backpressureChangePromise_resolve = resolve;
118 |   });
119 | 
120 |   transformStream._backpressureChangePromise.then(resolution => {
121 |   });
122 | 
123 |   transformStream._backpressure = backpressure;
124 | }
125 | 
126 | function TransformStreamDefaultTransform(chunk, transformStreamController) {
127 |   const transformStream = transformStreamController._controlledTransformStream;
128 |   TransformStreamEnqueueToReadable(transformStream, chunk);
129 |   return Promise.resolve();
130 | }
131 | 
132 | function TransformStreamTransform(transformStream, chunk) {
133 |   transformStream._transforming = true;
134 | 
135 |   const transformer = transformStream._transformer;
136 |   const controller = transformStream._transformStreamController;
137 | 
138 |   const transformPromise = PromiseInvokeOrPerformFallback(transformer, 'transform', [chunk, controller],
139 |                              TransformStreamDefaultTransform, [chunk, controller]);
140 | 
141 |   return transformPromise.then(
142 |     () => {
143 |       transformStream._transforming = false;
144 | 
145 |       return TransformStreamReadableReadyPromise(transformStream);
146 |     },
147 |     e => {
148 |       TransformStreamErrorIfNeeded(transformStream, e);
149 |       return Promise.reject(e);
150 |     });
151 | }
152 | 
153 | function IsTransformStreamDefaultController(x) {
154 |   if (!typeIsObject(x)) {
155 |     return false;
156 |   }
157 | 
158 |   if (!Object.prototype.hasOwnProperty.call(x, '_controlledTransformStream')) {
159 |     return false;
160 |   }
161 | 
162 |   return true;
163 | }
164 | 
165 | function IsTransformStream(x) {
166 |   if (!typeIsObject(x)) {
167 |     return false;
168 |   }
169 | 
170 |   if (!Object.prototype.hasOwnProperty.call(x, '_transformStreamController')) {
171 |     return false;
172 |   }
173 | 
174 |   return true;
175 | }
176 | 
177 | class TransformStreamSink {
178 |   constructor(transformStream, startPromise) {
179 |     this._transformStream = transformStream;
180 |     this._startPromise = startPromise;
181 |   }
182 | 
183 |   start(c) {
184 |     const transformStream = this._transformStream;
185 | 
186 |     transformStream._writableController = c;
187 | 
188 |     return this._startPromise.then(() => TransformStreamReadableReadyPromise(transformStream));
189 |   }
190 | 
191 |   write(chunk) {
192 |     const transformStream = this._transformStream;
193 | 
194 |     return TransformStreamTransform(transformStream, chunk);
195 |   }
196 | 
197 |   abort() {
198 |     const transformStream = this._transformStream;
199 |     transformStream._writableDone = true;
200 |     TransformStreamErrorInternal(transformStream, new TypeError('Writable side aborted'));
201 |   }
202 | 
203 |   close() {
204 |     const transformStream = this._transformStream;
205 | 
206 |     transformStream._writableDone = true;
207 | 
208 |     const flushPromise = PromiseInvokeOrNoop(transformStream._transformer,
209 |                          'flush', [transformStream._transformStreamController]);
210 |     // Return a promise that is fulfilled with undefined on success.
211 |     return flushPromise.then(() => {
212 |       if (transformStream._errored === true) {
213 |         return Promise.reject(transformStream._storedError);
214 |       }
215 |       if (transformStream._readableClosed === false) {
216 |         TransformStreamCloseReadableInternal(transformStream);
217 |       }
218 |       return Promise.resolve();
219 |     }).catch(r => {
220 |       TransformStreamErrorIfNeeded(transformStream, r);
221 |       return Promise.reject(transformStream._storedError);
222 |     });
223 |   }
224 | }
225 | 
226 | class TransformStreamSource {
227 |   constructor(transformStream, startPromise) {
228 |     this._transformStream = transformStream;
229 |     this._startPromise = startPromise;
230 |   }
231 | 
232 |   start(c) {
233 |     const transformStream = this._transformStream;
234 | 
235 |     transformStream._readableController = c;
236 | 
237 |     return this._startPromise.then(() => {
238 |       // Prevent the first pull() call until there is backpressure.
239 | 
240 |       if (transformStream._backpressure === true) {
241 |         return Promise.resolve();
242 |       }
243 | 
244 |       return transformStream._backpressureChangePromise;
245 |     });
246 |   }
247 | 
248 |   pull() {
249 |     const transformStream = this._transformStream;
250 | 
251 |     // Invariant. Enforced by the promises returned by start() and pull().
252 | 
253 |     TransformStreamSetBackpressure(transformStream, false);
254 | 
255 |     // Prevent the next pull() call until there is backpressure.
256 |     return transformStream._backpressureChangePromise;
257 |   }
258 | 
259 |   cancel() {
260 |     const transformStream = this._transformStream;
261 |     transformStream._readableClosed = true;
262 |     TransformStreamErrorInternal(transformStream, new TypeError('Readable side canceled'));
263 |   }
264 | }
265 | 
266 | class TransformStreamDefaultController {
267 |   constructor(transformStream) {
268 |     if (IsTransformStream(transformStream) === false) {
269 |       throw new TypeError('TransformStreamDefaultController can only be ' +
270 |                           'constructed with a TransformStream instance');
271 |     }
272 | 
273 |     if (transformStream._transformStreamController !== undefined) {
274 |       throw new TypeError('TransformStreamDefaultController instances can ' +
275 |                           'only be created by the TransformStream constructor');
276 |     }
277 | 
278 |     this._controlledTransformStream = transformStream;
279 |   }
280 | 
281 |   get desiredSize() {
282 |     if (IsTransformStreamDefaultController(this) === false) {
283 |       throw defaultControllerBrandCheckException('desiredSize');
284 |     }
285 | 
286 |     const transformStream = this._controlledTransformStream;
287 |     const readableController = transformStream._readableController;
288 | 
289 |     return ReadableStreamDefaultControllerGetDesiredSize(readableController);
290 |   }
291 | 
292 |   enqueue(chunk) {
293 |     if (IsTransformStreamDefaultController(this) === false) {
294 |       throw defaultControllerBrandCheckException('enqueue');
295 |     }
296 | 
297 |     TransformStreamEnqueueToReadable(this._controlledTransformStream, chunk);
298 |   }
299 | 
300 |   close() {
301 |     if (IsTransformStreamDefaultController(this) === false) {
302 |       throw defaultControllerBrandCheckException('close');
303 |     }
304 | 
305 |     TransformStreamCloseReadable(this._controlledTransformStream);
306 |   }
307 | 
308 |   error(reason) {
309 |     if (IsTransformStreamDefaultController(this) === false) {
310 |       throw defaultControllerBrandCheckException('error');
311 |     }
312 | 
313 |     TransformStreamError(this._controlledTransformStream, reason);
314 |   }
315 | }
316 | 
317 | class TransformStream {
318 |   constructor(transformer = {}) {
319 |     this._transformer = transformer;
320 |     const { readableStrategy, writableStrategy } = transformer;
321 | 
322 |     this._transforming = false;
323 |     this._errored = false;
324 |     this._storedError = undefined;
325 | 
326 |     this._writableController = undefined;
327 |     this._readableController = undefined;
328 |     this._transformStreamController = undefined;
329 | 
330 |     this._writableDone = false;
331 |     this._readableClosed = false;
332 | 
333 |     this._backpressure = undefined;
334 |     this._backpressureChangePromise = undefined;
335 |     this._backpressureChangePromise_resolve = undefined;
336 | 
337 |     this._transformStreamController = new TransformStreamDefaultController(this);
338 | 
339 |     let startPromise_resolve;
340 |     const startPromise = new Promise(resolve => {
341 |       startPromise_resolve = resolve;
342 |     });
343 | 
344 |     const source = new TransformStreamSource(this, startPromise);
345 | 
346 |     this._readable = new ReadableStream(source, readableStrategy);
347 | 
348 |     const sink = new TransformStreamSink(this, startPromise);
349 | 
350 |     this._writable = new WritableStream(sink, writableStrategy);
351 | 
352 |     const desiredSize = ReadableStreamDefaultControllerGetDesiredSize(this._readableController);
353 |     // Set _backpressure based on desiredSize. As there is no read() at this point, we can just interpret
354 |     // desiredSize being non-positive as backpressure.
355 |     TransformStreamSetBackpressure(this, desiredSize <= 0);
356 | 
357 |     const transformStream = this;
358 |     const startResult = InvokeOrNoop(transformer, 'start',
359 |                           [transformStream._transformStreamController]);
360 |     startPromise_resolve(startResult);
361 |     startPromise.catch(e => {
362 |       // The underlyingSink and underlyingSource will error the readable and writable ends on their own.
363 |       if (transformStream._errored === false) {
364 |         transformStream._errored = true;
365 |         transformStream._storedError = e;
366 |       }
367 |     });
368 |   }
369 | 
370 |   get readable() {
371 |     if (IsTransformStream(this) === false) {
372 |       throw streamBrandCheckException('readable');
373 |     }
374 | 
375 |     return this._readable;
376 |   }
377 | 
378 |   get writable() {
379 |     if (IsTransformStream(this) === false) {
380 |       throw streamBrandCheckException('writable');
381 |     }
382 | 
383 |     return this._writable;
384 |   }
385 | }
386 | 
387 | // Helper functions for the TransformStreamDefaultController.
388 | 
389 | function defaultControllerBrandCheckException(name) {
390 |   return new TypeError(
391 |     `TransformStreamDefaultController.prototype.${name} can only be used on a TransformStreamDefaultController`);
392 | }
393 | 
394 | // Stubs for abstract operations used from ReadableStream and WritableStream.
395 | 
396 | function ReadableStreamDefaultControllerEnqueue(controller, chunk) {
397 |   controller.enqueue(chunk);
398 | }
399 | 
400 | function ReadableStreamDefaultControllerGetDesiredSize(controller) {
401 |   return controller.desiredSize;
402 | }
403 | 
404 | function ReadableStreamDefaultControllerClose(controller) {
405 |   controller.close();
406 | }
407 | 
408 | function ReadableStreamDefaultControllerError(controller, e) {
409 |   controller.error(e);
410 | }
411 | 
412 | function WritableStreamDefaultControllerError(controller, e) {
413 |   controller.error(e);
414 | }
415 | 
416 | // Helper functions for the TransformStream.
417 | 
418 | function streamBrandCheckException(name) {
419 |   return new TypeError(
420 |     `TransformStream.prototype.${name} can only be used on a TransformStream`);
421 | }
422 | 
423 | // Copied from helpers.js.
424 | function IsPropertyKey(argument) {
425 |   return typeof argument === 'string' || typeof argument === 'symbol';
426 | }
427 | 
428 | function typeIsObject(x) {
429 |   return (typeof x === 'object' && x !== null) || typeof x === 'function';
430 | }
431 | 
432 | function Call(F, V, args) {
433 |   if (typeof F !== 'function') {
434 |     throw new TypeError('Argument is not a function');
435 |   }
436 | 
437 |   return Function.prototype.apply.call(F, V, args);
438 | }
439 | 
440 | function InvokeOrNoop(O, P, args) {
441 |   const method = O[P];
442 |   if (method === undefined) {
443 |     return undefined;
444 |   }
445 | 
446 |   return Call(method, O, args);
447 | };
448 | 
449 | function PromiseInvokeOrNoop(O, P, args) {
450 |   try {
451 |     return Promise.resolve(InvokeOrNoop(O, P, args));
452 |   } catch (returnValueE) {
453 |     return Promise.reject(returnValueE);
454 |   }
455 | };
456 | 
457 | function PromiseInvokeOrPerformFallback(O, P, args, F, argsF) {
458 |   let method;
459 |   try {
460 |     method = O[P];
461 |   } catch (methodE) {
462 |     return Promise.reject(methodE);
463 |   }
464 | 
465 |   if (method === undefined) {
466 |     return F(...argsF);
467 |   }
468 | 
469 |   try {
470 |     return Promise.resolve(Call(method, O, args));
471 |   } catch (e) {
472 |     return Promise.reject(e);
473 |   }
474 | };
475 | 
476 | // DIY assert() implementation
477 | function assert(predicate, s) {
478 |   class TransformStreamInternalLogicError extends Error {
479 |     constructor(s) {
480 |       super(s);
481 |     }
482 |   }
483 |   if (!predicate) {
484 |     console.log(`TransformStream internal logic error: assertion failed: s`);
485 |     throw new TransformStreamInternalLogicError(s);
486 |   }
487 | }
488 | 
489 | self['TransformStream'] = TransformStream;
490 | })();
491 | 


--------------------------------------------------------------------------------
/readable-stream-async-iteration-explainer.md:
--------------------------------------------------------------------------------
  1 | # `ReadableStream` Async Iteration Explained
  2 | 
  3 | 
  4 | ## Introduction
  5 | 
  6 | The streams APIs provide ubiquitous, interoperable primitives for creating, composing, and consuming streams of data.
  7 | 
  8 | This change adds support for the [async iterable protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols)
  9 | to the `ReadableStream` API, enabling readable streams to be used as the source of `for await...of` loops.
 10 | 
 11 | To consume a `ReadableStream`, developers currently acquire a reader and repeatedly call `read()`:
 12 | ```javascript
 13 | async function getResponseSize(url) {
 14 |   const response = await fetch(url);
 15 |   const reader = response.body.getReader();
 16 |   let total = 0;
 17 | 
 18 |   while (true) {
 19 |     const {done, value} = await reader.read();
 20 |     if (done) return total;
 21 |     total += value.length;
 22 |   }
 23 | }
 24 | ```
 25 | 
 26 | By adding support for the async iterable protocol, web developers will be able to use the much simpler
 27 | `for await...of` syntax to loop over all chunks of a `ReadableStream`.
 28 | 
 29 | ## API
 30 | 
 31 | The [ReadableStream definition](https://streams.spec.whatwg.org/#rs-class-definition) is extended
 32 | with a [Web IDL `async iterable` declaration](https://webidl.spec.whatwg.org/#idl-async-iterable):
 33 | ```
 34 | interface ReadableStream {
 35 |   async iterable(optional ReadableStreamIteratorOptions options = {});
 36 | };
 37 | 
 38 | dictionary ReadableStreamIteratorOptions {
 39 |   boolean preventCancel = false;
 40 | };
 41 | ```
 42 | 
 43 | This results in the following methods being added to the JavaScript binding:
 44 | 
 45 | *   `ReadableStream.prototype.values({ preventCancel = false } = {})`: returns an [AsyncIterator](https://tc39.es/ecma262/#sec-asynciterator-interface)
 46 |     object which locks the stream.
 47 |     *  `iterator.next()` reads the next chunk from the stream, like `reader.read()`.
 48 |         If the stream becomes closed or errored, this automatically releases the lock.
 49 |     *  `iterator.return(arg)` releases the lock, like `reader.releaseLock()`.
 50 |         If `preventCancel` is unset or false, then this also cancels the stream
 51 |         with the optional `arg` as cancel reason.
 52 | *   `ReadableStream.prototype[Symbol.asyncIterator]()`: same as `values()`.
 53 |      This method makes `ReadableStream` adhere to the [ECMAScript AsyncIterable protocol](https://tc39.es/ecma262/#sec-asynciterable-interface),
 54 |      and enables `for await...of` to work.
 55 | 
 56 | ## Examples
 57 | 
 58 | The original example can be written more succinctly using `for await...of`:
 59 | ```javascript
 60 | async function getResponseSize(url) {
 61 |   const response = await fetch(url);
 62 |   let total = 0;
 63 |   for await (const chunk of response) {
 64 |     total += chunk.length;
 65 |   }
 66 |   return total;
 67 | }
 68 | ```
 69 | 
 70 | Finding a specific chunk or byte in a stream also becomes easier (adapted from
 71 | [Jake Archibald's blog post](https://jakearchibald.com/2017/async-iterators-and-generators/#making-streams-iterate)):
 72 | ```javascript
 73 | async function example() {
 74 |   const find = 'J';
 75 |   const findCode = find.codePointAt(0);
 76 |   const response = await fetch('https://html.spec.whatwg.org');
 77 |   let bytes = 0;
 78 | 
 79 |   for await (const chunk of response.body) {
 80 |     const index = chunk.indexOf(findCode);
 81 | 
 82 |     if (index != -1) {
 83 |       bytes += index;
 84 |       console.log(`Found ${find} at byte ${bytes}.`);
 85 |       break;
 86 |     }
 87 | 
 88 |     bytes += chunk.length;
 89 |   }
 90 | }
 91 | ```
 92 | Note that the stream is automatically cancelled when we `break` out of the loop.
 93 | To prevent this, for example if you want to consume the remainder of the stream differently,
 94 | you can instead use `response.body.values({ preventCancel: true })`.
 95 | 
 96 | 
 97 | ## Goals
 98 | 
 99 | *   Permit `ReadableStream` to be used as the source of a `for await...of` loop.
100 | 
101 | 
102 | ## Non-goals
103 | 
104 | N/A.
105 | 
106 | 
107 | ## End-user benefit
108 | 
109 | *   Reduces boilerplate for developers when manually consuming a `ReadableStream`.
110 | *   Allows integration with future ECMAScript proposals, such as [Async Iterator Helpers](https://github.com/tc39/proposal-async-iterator-helpers).
111 | *   Allows interoperability with other APIs that can "adapt" async iterables, such as
112 |     Node.js [Readable.from](https://nodejs.org/docs/latest-v20.x/api/stream.html#streamreadablefromiterable-options).
113 | 
114 | 
115 | ## Alternatives
116 | 
117 | *   It was [initially suggested](https://github.com/whatwg/streams/issues/778#issuecomment-371711899)
118 |     that we could use a `ReadableStreamDefaultReader` as an `AsyncIterator`, by adding `next()`
119 |     and `return()` methods directly to the reader. However, the return values of `return()` and
120 |     `releaseLock()` are different, so the choice went to adding a separate async iterator object.
121 | 


--------------------------------------------------------------------------------
/reference-implementation/.eslintignore:
--------------------------------------------------------------------------------
1 | web-platform-tests/
2 | coverage/
3 | generated/
4 | bundle.js
5 | 


--------------------------------------------------------------------------------
/reference-implementation/.eslintrc.json:
--------------------------------------------------------------------------------
  1 | {
  2 |   "root": true,
  3 |   "env": {
  4 |     "node": true,
  5 |     "browser": true,
  6 |     "es2020": true
  7 |   },
  8 |   "parserOptions": {
  9 |     "ecmaVersion": 2020
 10 |   },
 11 |   "globals": {
 12 |     "GCController": false,
 13 |     "gc": false,
 14 |     "globalThis": false
 15 |   },
 16 |   "rules": {
 17 |     // Possible errors
 18 |     "comma-dangle": ["error", "never"],
 19 |     "no-cond-assign": ["error", "except-parens"],
 20 |     "no-console": "error",
 21 |     "no-constant-condition": "error",
 22 |     "no-control-regex": "error",
 23 |     "no-debugger": "error",
 24 |     "no-dupe-args": "error",
 25 |     "no-dupe-keys": "error",
 26 |     "no-duplicate-case": "error",
 27 |     "no-empty": "error",
 28 |     "no-empty-character-class": "error",
 29 |     "no-ex-assign": "error",
 30 |     "no-extra-boolean-cast": "error",
 31 |     "no-extra-parens": ["error", "all", { "conditionalAssign": false, "nestedBinaryExpressions": false, "returnAssign": false }],
 32 |     "no-extra-semi": "error",
 33 |     "no-func-assign": "error",
 34 |     "no-inner-declarations": "off",
 35 |     "no-invalid-regexp": "error",
 36 |     "no-irregular-whitespace": "error",
 37 |     "no-negated-in-lhs": "error",
 38 |     "no-obj-calls": "error",
 39 |     "no-regex-spaces": "error",
 40 |     "no-sparse-arrays": "error",
 41 |     "no-unexpected-multiline": "error",
 42 |     "no-unreachable": "error",
 43 |     "no-unsafe-finally": "off",
 44 |     "use-isnan": "error",
 45 |     "valid-jsdoc": "off",
 46 |     "valid-typeof": "error",
 47 | 
 48 |     // Best practices
 49 |     "accessor-pairs": "error",
 50 |     "array-callback-return": "error",
 51 |     "block-scoped-var": "off",
 52 |     "complexity": "off",
 53 |     "consistent-return": "error",
 54 |     "curly": ["error", "all"],
 55 |     "default-case": "off",
 56 |     "dot-location": ["error", "property"],
 57 |     "dot-notation": "error",
 58 |     "eqeqeq": "error",
 59 |     "guard-for-in": "off",
 60 |     "no-alert": "error",
 61 |     "no-caller": "error",
 62 |     "no-case-declarations": "error",
 63 |     "no-div-regex": "off",
 64 |     "no-else-return": "error",
 65 |     "no-empty-function": "off",
 66 |     "no-empty-pattern": "error",
 67 |     "no-eq-null": "error",
 68 |     "no-eval": "error",
 69 |     "no-extend-native": "error",
 70 |     "no-extra-bind": "error",
 71 |     "no-extra-label": "error",
 72 |     "no-fallthrough": "error",
 73 |     "no-floating-decimal": "error",
 74 |     "no-implicit-coercion": "error",
 75 |     "no-implicit-globals": "error",
 76 |     "no-implied-eval": "off",
 77 |     "no-invalid-this": "off", // meh
 78 |     "no-iterator": "error",
 79 |     "no-labels": ["error", { "allowLoop": true }],
 80 |     "no-lone-blocks": "error",
 81 |     "no-loop-func": "off",
 82 |     "no-magic-numbers": "off",
 83 |     "no-multi-spaces": "error",
 84 |     "no-multi-str": "error",
 85 |     "no-native-reassign": "error",
 86 |     "no-new": "off",
 87 |     "no-new-func": "error",
 88 |     "no-new-wrappers": "error",
 89 |     "no-octal": "error",
 90 |     "no-octal-escape": "error",
 91 |     "no-param-reassign": "off",
 92 |     "no-process-env": "error",
 93 |     "no-proto": "error",
 94 |     "no-redeclare": "error",
 95 |     "no-return-assign": ["error", "except-parens"],
 96 |     "no-script-url": "off",
 97 |     "no-self-assign": "error",
 98 |     "no-self-compare": "error",
 99 |     "no-sequences": "error",
100 |     "no-throw-literal": "error",
101 |     "no-unmodified-loop-condition": "error",
102 |     "no-unused-expressions": "error",
103 |     "no-unused-labels": "error",
104 |     "no-useless-call": "error",
105 |     "no-useless-concat": "error",
106 |     "no-useless-escape": "error",
107 |     "no-void": "error",
108 |     "no-warning-comments": "off",
109 |     "no-with": "error",
110 |     "radix": ["error", "as-needed"],
111 |     "vars-on-top": "off",
112 |     "wrap-iife": ["error", "outside"],
113 |     "yoda": ["error", "never"],
114 | 
115 |     // Strict Mode
116 |     "strict": ["error", "global"],
117 | 
118 |     // Variables
119 |     "init-declarations": "off",
120 |     "no-catch-shadow": "error",
121 |     "no-delete-var": "error",
122 |     "no-label-var": "error",
123 |     "no-restricted-globals": "off",
124 |     "no-shadow": "error",
125 |     "no-shadow-restricted-names": "error",
126 |     "no-undef": "error",
127 |     "no-undef-init": "error",
128 |     "no-undefined": "off",
129 |     "no-unused-vars": "error",
130 |     "no-use-before-define": "off",
131 | 
132 |     // Node.js and CommonJS
133 |     "callback-return": "off",
134 |     "global-require": "error",
135 |     "handle-callback-err": "error",
136 |     "no-mixed-requires": ["error", true],
137 |     "no-new-require": "error",
138 |     "no-path-concat": "error",
139 |     "no-process-exit": "error",
140 |     "no-restricted-imports": "off",
141 |     "no-restricted-modules": "off",
142 |     "no-sync": "off",
143 | 
144 |     // Stylistic Issues
145 |     "array-bracket-spacing": ["error", "never"],
146 |     "block-spacing": ["error", "always"],
147 |     "brace-style": ["error", "1tbs", { "allowSingleLine": true }],
148 |     "camelcase": "off",
149 |     "comma-spacing": ["error", { "before": false, "after": true }],
150 |     "comma-style": ["error", "last"],
151 |     "computed-property-spacing": ["error", "never"],
152 |     "consistent-this": "off",
153 |     "eol-last": "error",
154 |     "func-names": "off",
155 |     "func-style": "off",
156 |     "id-blacklist": "off",
157 |     "id-length": "off",
158 |     "id-match": "off",
159 |     "indent": ["error", 2, {
160 |       "SwitchCase": 1,
161 |       "FunctionDeclaration": { "parameters": "first" },
162 |       "FunctionExpression": { "parameters": "first" },
163 |       "CallExpression": { "arguments": "first" },
164 |       "ArrayExpression": "first",
165 |       "ObjectExpression": "first",
166 |       "ImportDeclaration": "first"
167 |     }],
168 |     "jsx-quotes": "off",
169 |     "key-spacing": ["error", { "beforeColon": false, "afterColon": true, "mode": "strict" }],
170 |     "keyword-spacing": ["error", { "before": true, "after": true }],
171 |     "linebreak-style": ["error", "unix"],
172 |     "lines-around-comment": "off",
173 |     "max-depth": "off",
174 |     "max-len": ["error", 120, { "ignoreUrls": true }],
175 |     "max-nested-callbacks": "off",
176 |     "max-params": "off",
177 |     "max-statements": "off",
178 |     "max-statements-per-line": ["error", { "max": 1 }],
179 |     "new-cap": "off",
180 |     "new-parens": "error",
181 |     "newline-after-var": "off",
182 |     "newline-before-return": "off",
183 |     "newline-per-chained-call": "off",
184 |     "no-array-constructor": "error",
185 |     "no-bitwise": "off",
186 |     "no-continue": "off",
187 |     "no-inline-comments": "off",
188 |     "no-lonely-if": "error",
189 |     "no-mixed-spaces-and-tabs": "error",
190 |     "no-multiple-empty-lines": "error",
191 |     "no-negated-condition": "off",
192 |     "no-nested-ternary": "error",
193 |     "no-new-object": "error",
194 |     "no-plusplus": "off",
195 |     "no-restricted-syntax": "off",
196 |     "no-spaced-func": "error",
197 |     "no-ternary": "off",
198 |     "no-trailing-spaces": "error",
199 |     "no-underscore-dangle": "off",
200 |     "no-unneeded-ternary": "error",
201 |     "no-whitespace-before-property": "error",
202 |     "object-curly-spacing": ["error", "always"],
203 |     "object-property-newline": "off",
204 |     "one-var": ["error", "never"],
205 |     "one-var-declaration-per-line": ["error", "initializations"],
206 |     "operator-assignment": ["error", "always"],
207 |     "operator-linebreak": ["error", "after"],
208 |     "padded-blocks": ["error", "never"],
209 |     "quote-props": ["error", "as-needed"],
210 |     "quotes": ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": true }],
211 |     "require-jsdoc": "off",
212 |     "semi": ["error", "always"],
213 |     "semi-spacing": "error",
214 |     "sort-imports": "off",
215 |     "sort-vars": "off",
216 |     "space-before-blocks": ["error", "always"],
217 |     "space-before-function-paren": ["error", { "anonymous": "always", "named": "never" }],
218 |     "space-in-parens": ["error", "never"],
219 |     "space-infix-ops": "error",
220 |     "space-unary-ops": ["error", { "words": true, "nonwords": false }],
221 |     "spaced-comment": ["error", "always", { "markers": ["///"] }],
222 |     "wrap-regex": "off",
223 | 
224 |     // ECMAScript 6
225 |     "arrow-body-style": "off", // meh
226 |     "arrow-parens": ["error", "as-needed"],
227 |     "arrow-spacing": "error",
228 |     "constructor-super": "error",
229 |     "generator-star-spacing": ["error", "after"],
230 |     "no-class-assign": "error",
231 |     "no-confusing-arrow": "off",
232 |     "no-const-assign": "error",
233 |     "no-dupe-class-members": "error",
234 |     "no-duplicate-imports": "error",
235 |     "no-new-symbol": "error",
236 |     "no-this-before-super": "error",
237 |     "no-useless-computed-key": "error",
238 |     "no-useless-constructor": "error",
239 |     "no-var": "error",
240 |     "object-shorthand": "error",
241 |     "prefer-arrow-callback": "error",
242 |     "prefer-const": ["error", { "ignoreReadBeforeAssign": true }],
243 |     "prefer-reflect": "off",
244 |     "prefer-rest-params": "error",
245 |     "prefer-spread": "error",
246 |     "prefer-template": "off",
247 |     "require-yield": "error",
248 |     "template-curly-spacing": ["error", "never"],
249 |     "yield-star-spacing": ["error", "after"]
250 |   }
251 | }
252 | 


--------------------------------------------------------------------------------
/reference-implementation/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log
3 | package-lock.json
4 | 
5 | .nyc_output/
6 | coverage/
7 | generated/
8 | bundle.js
9 | 


--------------------------------------------------------------------------------
/reference-implementation/COPYING.txt:
--------------------------------------------------------------------------------
  1 | Creative Commons Legal Code
  2 | 
  3 | CC0 1.0 Universal
  4 | 
  5 |     CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
  6 |     LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
  7 |     ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
  8 |     INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
  9 |     REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
 10 |     PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
 11 |     THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
 12 |     HEREUNDER.
 13 | 
 14 | Statement of Purpose
 15 | 
 16 | The laws of most jurisdictions throughout the world automatically confer
 17 | exclusive Copyright and Related Rights (defined below) upon the creator
 18 | and subsequent owner(s) (each and all, an "owner") of an original work of
 19 | authorship and/or a database (each, a "Work").
 20 | 
 21 | Certain owners wish to permanently relinquish those rights to a Work for
 22 | the purpose of contributing to a commons of creative, cultural and
 23 | scientific works ("Commons") that the public can reliably and without fear
 24 | of later claims of infringement build upon, modify, incorporate in other
 25 | works, reuse and redistribute as freely as possible in any form whatsoever
 26 | and for any purposes, including without limitation commercial purposes.
 27 | These owners may contribute to the Commons to promote the ideal of a free
 28 | culture and the further production of creative, cultural and scientific
 29 | works, or to gain reputation or greater distribution for their Work in
 30 | part through the use and efforts of others.
 31 | 
 32 | For these and/or other purposes and motivations, and without any
 33 | expectation of additional consideration or compensation, the person
 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she
 35 | is an owner of Copyright and Related Rights in the Work, voluntarily
 36 | elects to apply CC0 to the Work and publicly distribute the Work under its
 37 | terms, with knowledge of his or her Copyright and Related Rights in the
 38 | Work and the meaning and intended legal effect of CC0 on those rights.
 39 | 
 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be
 41 | protected by copyright and related or neighboring rights ("Copyright and
 42 | Related Rights"). Copyright and Related Rights include, but are not
 43 | limited to, the following:
 44 | 
 45 |   i. the right to reproduce, adapt, distribute, perform, display,
 46 |      communicate, and translate a Work;
 47 |  ii. moral rights retained by the original author(s) and/or performer(s);
 48 | iii. publicity and privacy rights pertaining to a person's image or
 49 |      likeness depicted in a Work;
 50 |  iv. rights protecting against unfair competition in regards to a Work,
 51 |      subject to the limitations in paragraph 4(a), below;
 52 |   v. rights protecting the extraction, dissemination, use and reuse of data
 53 |      in a Work;
 54 |  vi. database rights (such as those arising under Directive 96/9/EC of the
 55 |      European Parliament and of the Council of 11 March 1996 on the legal
 56 |      protection of databases, and under any national implementation
 57 |      thereof, including any amended or successor version of such
 58 |      directive); and
 59 | vii. other similar, equivalent or corresponding rights throughout the
 60 |      world based on applicable law or treaty, and any national
 61 |      implementations thereof.
 62 | 
 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention
 64 | of, applicable law, Affirmer hereby overtly, fully, permanently,
 65 | irrevocably and unconditionally waives, abandons, and surrenders all of
 66 | Affirmer's Copyright and Related Rights and associated claims and causes
 67 | of action, whether now known or unknown (including existing as well as
 68 | future claims and causes of action), in the Work (i) in all territories
 69 | worldwide, (ii) for the maximum duration provided by applicable law or
 70 | treaty (including future time extensions), (iii) in any current or future
 71 | medium and for any number of copies, and (iv) for any purpose whatsoever,
 72 | including without limitation commercial, advertising or promotional
 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
 74 | member of the public at large and to the detriment of Affirmer's heirs and
 75 | successors, fully intending that such Waiver shall not be subject to
 76 | revocation, rescission, cancellation, termination, or any other legal or
 77 | equitable action to disrupt the quiet enjoyment of the Work by the public
 78 | as contemplated by Affirmer's express Statement of Purpose.
 79 | 
 80 | 3. Public License Fallback. Should any part of the Waiver for any reason
 81 | be judged legally invalid or ineffective under applicable law, then the
 82 | Waiver shall be preserved to the maximum extent permitted taking into
 83 | account Affirmer's express Statement of Purpose. In addition, to the
 84 | extent the Waiver is so judged Affirmer hereby grants to each affected
 85 | person a royalty-free, non transferable, non sublicensable, non exclusive,
 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and
 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the
 88 | maximum duration provided by applicable law or treaty (including future
 89 | time extensions), (iii) in any current or future medium and for any number
 90 | of copies, and (iv) for any purpose whatsoever, including without
 91 | limitation commercial, advertising or promotional purposes (the
 92 | "License"). The License shall be deemed effective as of the date CC0 was
 93 | applied by Affirmer to the Work. Should any part of the License for any
 94 | reason be judged legally invalid or ineffective under applicable law, such
 95 | partial invalidity or ineffectiveness shall not invalidate the remainder
 96 | of the License, and in such case Affirmer hereby affirms that he or she
 97 | will not (i) exercise any of his or her remaining Copyright and Related
 98 | Rights in the Work or (ii) assert any associated claims and causes of
 99 | action with respect to the Work, in either case contrary to Affirmer's
100 | express Statement of Purpose.
101 | 
102 | 4. Limitations and Disclaimers.
103 | 
104 |  a. No trademark or patent rights held by Affirmer are waived, abandoned,
105 |     surrendered, licensed or otherwise affected by this document.
106 |  b. Affirmer offers the Work as-is and makes no representations or
107 |     warranties of any kind concerning the Work, express, implied,
108 |     statutory or otherwise, including without limitation warranties of
109 |     title, merchantability, fitness for a particular purpose, non
110 |     infringement, or the absence of latent or other defects, accuracy, or
111 |     the present or absence of errors, whether or not discoverable, all to
112 |     the greatest extent permissible under applicable law.
113 |  c. Affirmer disclaims responsibility for clearing rights of other persons
114 |     that may apply to the Work or any use thereof, including without
115 |     limitation any person's Copyright and Related Rights in the Work.
116 |     Further, Affirmer disclaims responsibility for obtaining any necessary
117 |     consents, permissions or other rights required for any use of the
118 |     Work.
119 |  d. Affirmer understands and acknowledges that Creative Commons is not a
120 |     party to this document and has no duty or obligation with respect to
121 |     this CC0 or use of the Work.
122 | 


--------------------------------------------------------------------------------
/reference-implementation/LICENSE.md:
--------------------------------------------------------------------------------
 1 | # WHATWG Streams Reference Implementation Licensing
 2 | 
 3 | For the reference implementation and tests for the WHATWG Streams Standard.
 4 | 
 5 | This code is dual-licensed under CC0 and the MIT license. You can choose which one you want to use.
 6 | 
 7 | ## CC0
 8 | 
 9 | To the extent possible under law, the authors have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty.
10 | 
11 | You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see https://creativecommons.org/publicdomain/zero/1.0/.
12 | 
13 | ## MIT
14 | 
15 | Copyright (c) 2013–2015 Streams Standard Reference Implementation Authors
16 | 
17 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
18 | 
19 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
20 | 
21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/reference-implementation/README.md:
--------------------------------------------------------------------------------
 1 | # Reference Implementation and Tests
 2 | 
 3 | This folder contains a reference implementation of the streams standard, inside `lib/`. It also contains infrastructure to run the web platform tests against the reference implementation, described below.
 4 | 
 5 | ## Reference implementation
 6 | 
 7 | The reference implementation is meant to be a fairly close transcription of the spec into JavaScript. It is written in modern JavaScript and primarily tested in [Node.js](https://nodejs.org/en/); at least version 6.0.0 is required.
 8 | 
 9 | ## Tests
10 | 
11 | Test coverage is not complete, but we do aim for it to be. Adding tests would be a great way to contribute to this project.
12 | 
13 | You can check the test coverage at any time by running `npm run coverage` in this folder.
14 | 
15 | To run all tests (and the lint step), run `npm test` in this folder.
16 | 
17 | ### Web platform tests
18 | 
19 | The test suite for this standard is written in [web platform tests](https://github.com/web-platform-tests/wpt) format.
20 | 
21 | - To run the web platform tests, type `npm run wpt` in this folder.
22 | - To run specific test files, you can use a glob pattern, rooted at the streams directory: `npm run wpt -- "writable-streams/**"`
23 | 
24 | The test runner here is a Node.js emulated-DOM environment, with the reference implementation loaded into it.
25 | 
26 | **To sync your local web-platform-tests checkout with the one tracked by this repository**, type `npm run sync-wpt`. However, note that this will override any local modifications you've made, e.g. in the process of working on a spec change. It's thus good to do this before you start working on such a change.
27 | 
28 | #### Upstream web platform tests
29 | 
30 | The web platform tests for streams are found in the [streams directory](https://github.com/web-platform-tests/wpt/tree/master/streams) of the web platform tests repository, and maintained via pull requests to that repository. They are then pulled into this repository via a [Git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules).
31 | 
32 | This means that in order to land a test change for these tests, you'll need to make a pull request to the web platform tests repository, and then update the submodule pointer in this repository (probably in the same pull request as your spec change). That can be done via the command
33 | 
34 | ```
35 | git submodule update --remote web-platform-tests
36 | ```
37 | 
38 | and then staging and commiting the submodule update.
39 | 
40 | If you are working on a spec change and need to modify or add to these tests, what you can do is work directly in the `web-platform-tests` subdirectory. Create a branch there, where you modify the tests. You can then modify the spec and reference implementation to match your branch. Finally, you can use that branch to send a pull request to the web-platform-tests project.
41 | 
42 | ## Diagnostics
43 | 
44 | Diagnostic output is provided using the [debug](https://www.npmjs.com/package/debug) package. It is useful to understand the behaviour of the reference implementation, particularly in tests that exercise asynchronous behaviour. To enable, set the DEBUG environment variable. For example, in Bash,
45 | 
46 | ```bash
47 | DEBUG=streams:* npm test
48 | ```
49 | 
50 | See [lib/transform-stream.js](lib/transform-stream.js) for examples of how debug statements are used. Diagnostic coverage is sparse at the moment; we expect to add more diagnostics in an ad-hoc manner as they are needed.
51 | 


--------------------------------------------------------------------------------
/reference-implementation/compile-idl.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | /* eslint-disable no-console, no-process-exit */
 3 | const { mkdirSync } = require('fs');
 4 | const path = require('path');
 5 | const Transformer = require('webidl2js');
 6 | 
 7 | const input = path.resolve(__dirname, './lib');
 8 | const output = path.resolve(__dirname, './generated');
 9 | 
10 | mkdirSync(output, { recursive: true });
11 | 
12 | const transformer = new Transformer({
13 |   implSuffix: '-impl',
14 |   suppressErrors: true // until https://github.com/jsdom/webidl2js/pull/123 lands
15 | });
16 | 
17 | transformer.addSource(input, input);
18 | transformer.generate(output)
19 |   .catch(err => {
20 |     console.error(err.stack);
21 |     process.exit(1);
22 |   });
23 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/ByteLengthQueuingStrategy-impl.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | exports.implementation = class ByteLengthQueuingStrategyImpl {
 4 |   constructor(globalObject, [{ highWaterMark }]) {
 5 |     this._globalObject = globalObject;
 6 |     this.highWaterMark = highWaterMark;
 7 |   }
 8 | 
 9 |   get size() {
10 |     initializeSizeFunction(this._globalObject);
11 |     return sizeFunctionWeakMap.get(this._globalObject);
12 |   }
13 | };
14 | 
15 | const sizeFunctionWeakMap = new WeakMap();
16 | function initializeSizeFunction(globalObject) {
17 |   if (sizeFunctionWeakMap.has(globalObject)) {
18 |     return;
19 |   }
20 | 
21 |   // We need to set the 'name' property:
22 |   // The size function must not have a prototype property nor be a constructor
23 |   const size = chunk => chunk.byteLength;
24 |   sizeFunctionWeakMap.set(globalObject, size);
25 | }
26 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/ByteLengthQueuingStrategy.webidl:
--------------------------------------------------------------------------------
1 | [Exposed=(Window,Worker,Worklet)]
2 | interface ByteLengthQueuingStrategy {
3 |   constructor(QueuingStrategyInit init);
4 | 
5 |   readonly attribute unrestricted double highWaterMark;
6 |   readonly attribute Function size;
7 | };
8 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/CountQueuingStrategy-impl.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | exports.implementation = class CountQueuingStrategyImpl {
 4 |   constructor(globalObject, [{ highWaterMark }]) {
 5 |     this._globalObject = globalObject;
 6 |     this.highWaterMark = highWaterMark;
 7 |   }
 8 | 
 9 |   get size() {
10 |     initializeSizeFunction(this._globalObject);
11 |     return sizeFunctionWeakMap.get(this._globalObject);
12 |   }
13 | };
14 | 
15 | const sizeFunctionWeakMap = new WeakMap();
16 | function initializeSizeFunction(globalObject) {
17 |   if (sizeFunctionWeakMap.has(globalObject)) {
18 |     return;
19 |   }
20 | 
21 |   // We need to set the 'name' property:
22 |   // The size function must not have a prototype property nor be a constructor
23 |   const size = () => 1;
24 |   sizeFunctionWeakMap.set(globalObject, size);
25 | }
26 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/CountQueuingStrategy.webidl:
--------------------------------------------------------------------------------
1 | [Exposed=(Window,Worker,Worklet)]
2 | interface CountQueuingStrategy {
3 |   constructor(QueuingStrategyInit init);
4 | 
5 |   readonly attribute unrestricted double highWaterMark;
6 |   readonly attribute Function size;
7 | };
8 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/QueuingStrategy.webidl:
--------------------------------------------------------------------------------
1 | dictionary QueuingStrategy {
2 |   unrestricted double highWaterMark;
3 |   QueuingStrategySize size;
4 | };
5 | 
6 | callback QueuingStrategySize = unrestricted double (any chunk);
7 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/QueuingStrategyInit.webidl:
--------------------------------------------------------------------------------
1 | dictionary QueuingStrategyInit {
2 |   required unrestricted double highWaterMark;
3 | };
4 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/ReadableByteStreamController-impl.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | const assert = require('assert');
  3 | 
  4 | const { CancelSteps, PullSteps, ReleaseSteps } = require('./abstract-ops/internal-methods.js');
  5 | const { ResetQueue } = require('./abstract-ops/queue-with-sizes.js');
  6 | const aos = require('./abstract-ops/readable-streams.js');
  7 | 
  8 | exports.implementation = class ReadableByteStreamControllerImpl {
  9 |   get byobRequest() {
 10 |     return aos.ReadableByteStreamControllerGetBYOBRequest(this);
 11 |   }
 12 | 
 13 |   get desiredSize() {
 14 |     return aos.ReadableByteStreamControllerGetDesiredSize(this);
 15 |   }
 16 | 
 17 |   close() {
 18 |     if (this._closeRequested === true) {
 19 |       throw new TypeError('The stream has already been closed; do not close it again!');
 20 |     }
 21 | 
 22 |     const state = this._stream._state;
 23 |     if (state !== 'readable') {
 24 |       throw new TypeError(`The stream (in ${state} state) is not in the readable state and cannot be closed`);
 25 |     }
 26 | 
 27 |     aos.ReadableByteStreamControllerClose(this);
 28 |   }
 29 | 
 30 |   enqueue(chunk) {
 31 |     if (chunk.byteLength === 0) {
 32 |       throw new TypeError('chunk must have non-zero byteLength');
 33 |     }
 34 |     if (chunk.buffer.byteLength === 0) {
 35 |       throw new TypeError('chunk\'s buffer must have non-zero byteLength');
 36 |     }
 37 | 
 38 |     if (this._closeRequested === true) {
 39 |       throw new TypeError('stream is closed or draining');
 40 |     }
 41 | 
 42 |     const state = this._stream._state;
 43 |     if (state !== 'readable') {
 44 |       throw new TypeError(`The stream (in ${state} state) is not in the readable state and cannot be enqueued to`);
 45 |     }
 46 | 
 47 |     aos.ReadableByteStreamControllerEnqueue(this, chunk);
 48 |   }
 49 | 
 50 |   error(e) {
 51 |     aos.ReadableByteStreamControllerError(this, e);
 52 |   }
 53 | 
 54 |   [CancelSteps](reason) {
 55 |     aos.ReadableByteStreamControllerClearPendingPullIntos(this);
 56 | 
 57 |     ResetQueue(this);
 58 | 
 59 |     const result = this._cancelAlgorithm(reason);
 60 |     aos.ReadableByteStreamControllerClearAlgorithms(this);
 61 |     return result;
 62 |   }
 63 | 
 64 |   [PullSteps](readRequest) {
 65 |     const stream = this._stream;
 66 |     assert(aos.ReadableStreamHasDefaultReader(stream) === true);
 67 | 
 68 |     if (this._queueTotalSize > 0) {
 69 |       assert(aos.ReadableStreamGetNumReadRequests(stream) === 0);
 70 |       aos.ReadableByteStreamControllerFillReadRequestFromQueue(this, readRequest);
 71 |       return;
 72 |     }
 73 | 
 74 |     const autoAllocateChunkSize = this._autoAllocateChunkSize;
 75 |     if (autoAllocateChunkSize !== undefined) {
 76 |       let buffer;
 77 |       try {
 78 |         buffer = new ArrayBuffer(autoAllocateChunkSize);
 79 |       } catch (bufferE) {
 80 |         readRequest.errorSteps(bufferE);
 81 |         return;
 82 |       }
 83 | 
 84 |       const pullIntoDescriptor = {
 85 |         buffer,
 86 |         bufferByteLength: autoAllocateChunkSize,
 87 |         byteOffset: 0,
 88 |         byteLength: autoAllocateChunkSize,
 89 |         bytesFilled: 0,
 90 |         minimumFill: 1,
 91 |         elementSize: 1,
 92 |         viewConstructor: Uint8Array,
 93 |         readerType: 'default'
 94 |       };
 95 | 
 96 |       this._pendingPullIntos.push(pullIntoDescriptor);
 97 |     }
 98 | 
 99 |     aos.ReadableStreamAddReadRequest(stream, readRequest);
100 |     aos.ReadableByteStreamControllerCallPullIfNeeded(this);
101 |   }
102 | 
103 |   [ReleaseSteps]() {
104 |     if (this._pendingPullIntos.length > 0) {
105 |       const firstPullInto = this._pendingPullIntos[0];
106 |       firstPullInto.readerType = 'none';
107 | 
108 |       this._pendingPullIntos = [firstPullInto];
109 |     }
110 |   }
111 | };
112 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/ReadableByteStreamController.webidl:
--------------------------------------------------------------------------------
 1 | [Exposed=(Window,Worker,Worklet)]
 2 | interface ReadableByteStreamController {
 3 |   readonly attribute ReadableStreamBYOBRequest? byobRequest;
 4 |   readonly attribute unrestricted double? desiredSize;
 5 | 
 6 |   undefined close();
 7 |   undefined enqueue(ArrayBufferView chunk);
 8 |   undefined error(optional any e);
 9 | };
10 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/ReadableStream-impl.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | const assert = require('assert');
  3 | 
  4 | const { newPromise, resolvePromise, rejectPromise, promiseResolvedWith, promiseRejectedWith,
  5 |         setPromiseIsHandledToTrue } = require('./helpers/webidl.js');
  6 | const { ExtractHighWaterMark, ExtractSizeAlgorithm } = require('./abstract-ops/queuing-strategy.js');
  7 | const aos = require('./abstract-ops/readable-streams.js');
  8 | const wsAOs = require('./abstract-ops/writable-streams.js');
  9 | 
 10 | const idlUtils = require('../generated/utils.js');
 11 | const UnderlyingSource = require('../generated/UnderlyingSource.js');
 12 | 
 13 | exports.implementation = class ReadableStreamImpl {
 14 |   constructor(globalObject, [underlyingSource, strategy]) {
 15 |     if (underlyingSource === undefined) {
 16 |       underlyingSource = null;
 17 |     }
 18 |     const underlyingSourceDict = UnderlyingSource.convert(globalObject, underlyingSource);
 19 | 
 20 |     aos.InitializeReadableStream(this);
 21 | 
 22 |     if (underlyingSourceDict.type === 'bytes') {
 23 |       if ('size' in strategy) {
 24 |         throw new RangeError('The strategy for a byte stream cannot have a size function');
 25 |       }
 26 | 
 27 |       const highWaterMark = ExtractHighWaterMark(strategy, 0);
 28 |       aos.SetUpReadableByteStreamControllerFromUnderlyingSource(
 29 |         this, underlyingSource, underlyingSourceDict, highWaterMark
 30 |       );
 31 |     } else {
 32 |       assert(!('type' in underlyingSourceDict));
 33 |       const sizeAlgorithm = ExtractSizeAlgorithm(strategy);
 34 |       const highWaterMark = ExtractHighWaterMark(strategy, 1);
 35 |       aos.SetUpReadableStreamDefaultControllerFromUnderlyingSource(
 36 |         this, underlyingSource, underlyingSourceDict, highWaterMark, sizeAlgorithm
 37 |       );
 38 |     }
 39 |   }
 40 | 
 41 |   get locked() {
 42 |     return aos.IsReadableStreamLocked(this);
 43 |   }
 44 | 
 45 |   cancel(reason) {
 46 |     if (aos.IsReadableStreamLocked(this) === true) {
 47 |       return promiseRejectedWith(new TypeError('Cannot cancel a stream that already has a reader'));
 48 |     }
 49 | 
 50 |     return aos.ReadableStreamCancel(this, reason);
 51 |   }
 52 | 
 53 |   getReader(options) {
 54 |     if (!('mode' in options)) {
 55 |       return aos.AcquireReadableStreamDefaultReader(this);
 56 |     }
 57 | 
 58 |     assert(options.mode === 'byob');
 59 |     return aos.AcquireReadableStreamBYOBReader(this);
 60 |   }
 61 | 
 62 |   pipeThrough(transform, options) {
 63 |     // Type checking here is needed until https://github.com/jsdom/webidl2js/issues/81 is fixed.
 64 |     if ('signal' in options) {
 65 |       if (!isAbortSignal(options.signal)) {
 66 |         throw new TypeError('Invalid signal argument');
 67 |       }
 68 |     }
 69 | 
 70 |     if (aos.IsReadableStreamLocked(this) === true) {
 71 |       throw new TypeError('ReadableStream.prototype.pipeThrough cannot be used on a locked ReadableStream');
 72 |     }
 73 |     if (wsAOs.IsWritableStreamLocked(transform.writable) === true) {
 74 |       throw new TypeError('ReadableStream.prototype.pipeThrough cannot be used on a locked WritableStream');
 75 |     }
 76 | 
 77 |     const promise = aos.ReadableStreamPipeTo(
 78 |       this, transform.writable, options.preventClose, options.preventAbort, options.preventCancel, options.signal
 79 |     );
 80 | 
 81 |     setPromiseIsHandledToTrue(promise);
 82 | 
 83 |     return transform.readable;
 84 |   }
 85 | 
 86 |   pipeTo(destination, options) {
 87 |     // Type checking here is needed until https://github.com/jsdom/webidl2js/issues/81 is fixed.
 88 |     if ('signal' in options) {
 89 |       if (!isAbortSignal(options.signal)) {
 90 |         return promiseRejectedWith(new TypeError('Invalid signal argument'));
 91 |       }
 92 |     }
 93 | 
 94 |     if (aos.IsReadableStreamLocked(this) === true) {
 95 |       return promiseRejectedWith(
 96 |         new TypeError('ReadableStream.prototype.pipeTo cannot be used on a locked ReadableStream')
 97 |       );
 98 |     }
 99 |     if (wsAOs.IsWritableStreamLocked(destination) === true) {
100 |       return promiseRejectedWith(
101 |         new TypeError('ReadableStream.prototype.pipeTo cannot be used on a locked WritableStream')
102 |       );
103 |     }
104 | 
105 |     return aos.ReadableStreamPipeTo(
106 |       this, destination, options.preventClose, options.preventAbort, options.preventCancel, options.signal
107 |     );
108 |   }
109 | 
110 |   tee() {
111 |     // Conversion here is only needed until https://github.com/jsdom/webidl2js/pull/108 gets merged.
112 |     return aos.ReadableStreamTee(this, false).map(idlUtils.wrapperForImpl);
113 |   }
114 | 
115 |   [idlUtils.asyncIteratorInit](iterator, [options]) {
116 |     iterator._reader = aos.AcquireReadableStreamDefaultReader(this);
117 |     iterator._preventCancel = options.preventCancel;
118 |   }
119 | 
120 |   [idlUtils.asyncIteratorNext](iterator) {
121 |     const reader = iterator._reader;
122 |     assert(reader._stream !== undefined);
123 | 
124 |     const promise = newPromise();
125 |     const readRequest = {
126 |       chunkSteps: chunk => resolvePromise(promise, chunk),
127 |       closeSteps: () => {
128 |         aos.ReadableStreamDefaultReaderRelease(reader);
129 |         resolvePromise(promise, idlUtils.asyncIteratorEOI);
130 |       },
131 |       errorSteps: e => {
132 |         aos.ReadableStreamDefaultReaderRelease(reader);
133 |         rejectPromise(promise, e);
134 |       }
135 |     };
136 |     aos.ReadableStreamDefaultReaderRead(reader, readRequest);
137 |     return promise;
138 |   }
139 | 
140 |   [idlUtils.asyncIteratorReturn](iterator, arg) {
141 |     const reader = iterator._reader;
142 |     assert(reader._stream !== undefined);
143 |     assert(reader._readRequests.length === 0);
144 | 
145 |     if (iterator._preventCancel === false) {
146 |       const result = aos.ReadableStreamReaderGenericCancel(reader, arg);
147 |       aos.ReadableStreamDefaultReaderRelease(reader);
148 |       return result;
149 |     }
150 | 
151 |     aos.ReadableStreamDefaultReaderRelease(reader);
152 |     return promiseResolvedWith(undefined);
153 |   }
154 | 
155 |   static from(asyncIterable) {
156 |     return aos.ReadableStreamFromIterable(asyncIterable);
157 |   }
158 | };
159 | 
160 | // See pipeTo()/pipeThrough() for why this is needed.
161 | const abortedGetter = Object.getOwnPropertyDescriptor(AbortSignal.prototype, 'aborted').get;
162 | function isAbortSignal(v) {
163 |   try {
164 |     abortedGetter.call(v);
165 |     return true;
166 |   } catch {
167 |     return false;
168 |   }
169 | }
170 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/ReadableStream.webidl:
--------------------------------------------------------------------------------
 1 | [Exposed=(Window,Worker,Worklet), Transferable]
 2 | interface ReadableStream {
 3 |   constructor(optional object underlyingSource, optional QueuingStrategy strategy = {});
 4 | 
 5 |   static ReadableStream from(any asyncIterable);
 6 | 
 7 |   readonly attribute boolean locked;
 8 | 
 9 |   Promise cancel(optional any reason);
10 |   ReadableStreamReader getReader(optional ReadableStreamGetReaderOptions options = {});
11 |   ReadableStream pipeThrough(ReadableWritablePair transform, optional StreamPipeOptions options = {});
12 |   Promise pipeTo(WritableStream destination, optional StreamPipeOptions options = {});
13 |   sequence tee();
14 | 
15 |   [WebIDL2JSHasReturnSteps] async iterable(optional ReadableStreamIteratorOptions options = {});
16 | };
17 | 
18 | typedef (ReadableStreamDefaultReader or ReadableStreamBYOBReader) ReadableStreamReader;
19 | 
20 | enum ReadableStreamReaderMode { "byob" };
21 | 
22 | dictionary ReadableStreamGetReaderOptions {
23 |   ReadableStreamReaderMode mode;
24 | };
25 | 
26 | dictionary ReadableStreamIteratorOptions {
27 |   boolean preventCancel = false;
28 | };
29 | 
30 | dictionary ReadableWritablePair {
31 |   required ReadableStream readable;
32 |   required WritableStream writable;
33 | };
34 | 
35 | dictionary StreamPipeOptions {
36 |   boolean preventClose = false;
37 |   boolean preventAbort = false;
38 |   boolean preventCancel = false;
39 |   AbortSignal signal;
40 | };
41 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/ReadableStreamBYOBReader-impl.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const { newPromise, resolvePromise, rejectPromise, promiseRejectedWith } = require('./helpers/webidl.js');
 4 | const { IsDetachedBuffer } = require('./abstract-ops/ecmascript.js');
 5 | const aos = require('./abstract-ops/readable-streams.js');
 6 | const { mixin } = require('./helpers/miscellaneous.js');
 7 | const ReadableStreamGenericReaderImpl = require('./ReadableStreamGenericReader-impl.js').implementation;
 8 | 
 9 | class ReadableStreamBYOBReaderImpl {
10 |   constructor(globalObject, [stream]) {
11 |     aos.SetUpReadableStreamBYOBReader(this, stream);
12 |   }
13 | 
14 |   read(view, options) {
15 |     if (view.byteLength === 0) {
16 |       return promiseRejectedWith(new TypeError('view must have non-zero byteLength'));
17 |     }
18 |     if (view.buffer.byteLength === 0) {
19 |       return promiseRejectedWith(new TypeError('view\'s buffer must have non-zero byteLength'));
20 |     }
21 |     if (IsDetachedBuffer(view.buffer) === true) {
22 |       return promiseRejectedWith(new TypeError('view\'s buffer has been detached'));
23 |     }
24 | 
25 |     if (options.min === 0) {
26 |       return promiseRejectedWith(
27 |         new TypeError('options.min must be greater than 0')
28 |       );
29 |     }
30 |     if (view.constructor !== DataView) {
31 |       if (options.min > view.length) {
32 |         return promiseRejectedWith(
33 |           new RangeError('options.min must be less than or equal to view\'s length')
34 |         );
35 |       }
36 |     } else if (options.min > view.byteLength) {
37 |       return promiseRejectedWith(
38 |         new RangeError('options.min must be less than or equal to view\'s byteLength')
39 |       );
40 |     }
41 | 
42 |     if (this._stream === undefined) {
43 |       return promiseRejectedWith(readerLockException('read'));
44 |     }
45 | 
46 |     const promise = newPromise();
47 |     const readIntoRequest = {
48 |       chunkSteps: chunk => resolvePromise(promise, { value: chunk, done: false }),
49 |       closeSteps: chunk => resolvePromise(promise, { value: chunk, done: true }),
50 |       errorSteps: e => rejectPromise(promise, e)
51 |     };
52 |     aos.ReadableStreamBYOBReaderRead(this, view, options.min, readIntoRequest);
53 |     return promise;
54 |   }
55 | 
56 |   releaseLock() {
57 |     if (this._stream === undefined) {
58 |       return;
59 |     }
60 | 
61 |     aos.ReadableStreamBYOBReaderRelease(this);
62 |   }
63 | }
64 | 
65 | mixin(ReadableStreamBYOBReaderImpl.prototype, ReadableStreamGenericReaderImpl.prototype);
66 | 
67 | exports.implementation = ReadableStreamBYOBReaderImpl;
68 | 
69 | function readerLockException(name) {
70 |   return new TypeError('Cannot ' + name + ' a stream using a released reader');
71 | }
72 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/ReadableStreamBYOBReader.webidl:
--------------------------------------------------------------------------------
 1 | [Exposed=(Window,Worker,Worklet)]
 2 | interface ReadableStreamBYOBReader {
 3 |   constructor(ReadableStream stream);
 4 | 
 5 |   Promise read(ArrayBufferView view, optional ReadableStreamBYOBReaderReadOptions options = {});
 6 |   undefined releaseLock();
 7 | };
 8 | ReadableStreamBYOBReader includes ReadableStreamGenericReader;
 9 | 
10 | dictionary ReadableStreamBYOBReaderReadOptions {
11 |   [EnforceRange] unsigned long long min = 1;
12 | };
13 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/ReadableStreamBYOBRequest-impl.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | const assert = require('assert');
 3 | 
 4 | const { IsDetachedBuffer } = require('./abstract-ops/ecmascript.js');
 5 | const aos = require('./abstract-ops/readable-streams.js');
 6 | 
 7 | exports.implementation = class ReadableStreamBYOBRequestImpl {
 8 |   get view() {
 9 |     return this._view;
10 |   }
11 | 
12 |   respond(bytesWritten) {
13 |     if (this._controller === undefined) {
14 |       throw new TypeError('This BYOB request has been invalidated');
15 |     }
16 | 
17 |     if (IsDetachedBuffer(this._view.buffer) === true) {
18 |       throw new TypeError('The BYOB request\'s buffer has been detached and so cannot be used as a response');
19 |     }
20 | 
21 |     assert(this._view.byteLength > 0);
22 |     assert(this._view.buffer.byteLength > 0);
23 | 
24 |     aos.ReadableByteStreamControllerRespond(this._controller, bytesWritten);
25 |   }
26 | 
27 |   respondWithNewView(view) {
28 |     if (this._controller === undefined) {
29 |       throw new TypeError('This BYOB request has been invalidated');
30 |     }
31 | 
32 |     if (IsDetachedBuffer(view.buffer) === true) {
33 |       throw new TypeError('The given view\'s buffer has been detached and so cannot be used as a response');
34 |     }
35 | 
36 |     aos.ReadableByteStreamControllerRespondWithNewView(this._controller, view);
37 |   }
38 | };
39 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/ReadableStreamBYOBRequest.webidl:
--------------------------------------------------------------------------------
1 | [Exposed=(Window,Worker,Worklet)]
2 | interface ReadableStreamBYOBRequest {
3 |   readonly attribute ArrayBufferView? view;
4 | 
5 |   undefined respond([EnforceRange] unsigned long long bytesWritten);
6 |   undefined respondWithNewView(ArrayBufferView view);
7 | };
8 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/ReadableStreamDefaultController-impl.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const { CancelSteps, PullSteps, ReleaseSteps } = require('./abstract-ops/internal-methods.js');
 4 | const { DequeueValue, ResetQueue } = require('./abstract-ops/queue-with-sizes.js');
 5 | const aos = require('./abstract-ops/readable-streams.js');
 6 | 
 7 | exports.implementation = class ReadableStreamDefaultControllerImpl {
 8 |   get desiredSize() {
 9 |     return aos.ReadableStreamDefaultControllerGetDesiredSize(this);
10 |   }
11 | 
12 |   close() {
13 |     if (aos.ReadableStreamDefaultControllerCanCloseOrEnqueue(this) === false) {
14 |       throw new TypeError('The stream is not in a state that permits close');
15 |     }
16 | 
17 |     aos.ReadableStreamDefaultControllerClose(this);
18 |   }
19 | 
20 |   enqueue(chunk) {
21 |     if (aos.ReadableStreamDefaultControllerCanCloseOrEnqueue(this) === false) {
22 |       throw new TypeError('The stream is not in a state that permits enqueue');
23 |     }
24 | 
25 |     return aos.ReadableStreamDefaultControllerEnqueue(this, chunk);
26 |   }
27 | 
28 |   error(e) {
29 |     aos.ReadableStreamDefaultControllerError(this, e);
30 |   }
31 | 
32 |   [CancelSteps](reason) {
33 |     ResetQueue(this);
34 |     const result = this._cancelAlgorithm(reason);
35 |     aos.ReadableStreamDefaultControllerClearAlgorithms(this);
36 |     return result;
37 |   }
38 | 
39 |   [PullSteps](readRequest) {
40 |     const stream = this._stream;
41 | 
42 |     if (this._queue.length > 0) {
43 |       const chunk = DequeueValue(this);
44 | 
45 |       if (this._closeRequested === true && this._queue.length === 0) {
46 |         aos.ReadableStreamDefaultControllerClearAlgorithms(this);
47 |         aos.ReadableStreamClose(stream);
48 |       } else {
49 |         aos.ReadableStreamDefaultControllerCallPullIfNeeded(this);
50 |       }
51 | 
52 |       readRequest.chunkSteps(chunk);
53 |     } else {
54 |       aos.ReadableStreamAddReadRequest(stream, readRequest);
55 |       aos.ReadableStreamDefaultControllerCallPullIfNeeded(this);
56 |     }
57 |   }
58 | 
59 |   [ReleaseSteps]() {}
60 | };
61 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/ReadableStreamDefaultController.webidl:
--------------------------------------------------------------------------------
1 | [Exposed=(Window,Worker,Worklet)]
2 | interface ReadableStreamDefaultController {
3 |   readonly attribute unrestricted double? desiredSize;
4 | 
5 |   undefined close();
6 |   undefined enqueue(optional any chunk);
7 |   undefined error(optional any e);
8 | };
9 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/ReadableStreamDefaultReader-impl.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const { newPromise, resolvePromise, rejectPromise, promiseRejectedWith } = require('./helpers/webidl.js');
 4 | const aos = require('./abstract-ops/readable-streams.js');
 5 | const { mixin } = require('./helpers/miscellaneous.js');
 6 | const ReadableStreamGenericReaderImpl = require('./ReadableStreamGenericReader-impl.js').implementation;
 7 | 
 8 | class ReadableStreamDefaultReaderImpl {
 9 |   constructor(globalObject, [stream]) {
10 |     aos.SetUpReadableStreamDefaultReader(this, stream);
11 |   }
12 | 
13 |   read() {
14 |     if (this._stream === undefined) {
15 |       return promiseRejectedWith(readerLockException('read from'));
16 |     }
17 | 
18 |     const promise = newPromise();
19 |     const readRequest = {
20 |       chunkSteps: chunk => resolvePromise(promise, { value: chunk, done: false }),
21 |       closeSteps: () => resolvePromise(promise, { value: undefined, done: true }),
22 |       errorSteps: e => rejectPromise(promise, e)
23 |     };
24 | 
25 |     aos.ReadableStreamDefaultReaderRead(this, readRequest);
26 |     return promise;
27 |   }
28 | 
29 |   releaseLock() {
30 |     if (this._stream === undefined) {
31 |       return;
32 |     }
33 | 
34 |     aos.ReadableStreamDefaultReaderRelease(this);
35 |   }
36 | }
37 | 
38 | mixin(ReadableStreamDefaultReaderImpl.prototype, ReadableStreamGenericReaderImpl.prototype);
39 | 
40 | exports.implementation = ReadableStreamDefaultReaderImpl;
41 | 
42 | function readerLockException(name) {
43 |   return new TypeError('Cannot ' + name + ' a stream using a released reader');
44 | }
45 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/ReadableStreamDefaultReader.webidl:
--------------------------------------------------------------------------------
1 | [Exposed=(Window,Worker,Worklet)]
2 | interface ReadableStreamDefaultReader {
3 |   constructor(ReadableStream stream);
4 | 
5 |   Promise read();
6 |   undefined releaseLock();
7 | };
8 | ReadableStreamDefaultReader includes ReadableStreamGenericReader;
9 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/ReadableStreamGenericReader-impl.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const { promiseRejectedWith } = require('./helpers/webidl.js');
 4 | const aos = require('./abstract-ops/readable-streams.js');
 5 | 
 6 | exports.implementation = class ReadableStreamGenericReaderImpl {
 7 |   get closed() {
 8 |     return this._closedPromise;
 9 |   }
10 | 
11 |   cancel(reason) {
12 |     if (this._stream === undefined) {
13 |       return promiseRejectedWith(readerLockException('cancel'));
14 |     }
15 | 
16 |     return aos.ReadableStreamReaderGenericCancel(this, reason);
17 |   }
18 | };
19 | 
20 | function readerLockException(name) {
21 |   return new TypeError('Cannot ' + name + ' a stream using a released reader');
22 | }
23 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/ReadableStreamGenericReader.webidl:
--------------------------------------------------------------------------------
1 | interface mixin ReadableStreamGenericReader {
2 |   readonly attribute Promise closed;
3 | 
4 |   Promise cancel(optional any reason);
5 | };
6 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/ReadableStreamReadResult.webidl:
--------------------------------------------------------------------------------
1 | dictionary ReadableStreamReadResult {
2 |   any value;
3 |   boolean done;
4 | };
5 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/TransformStream-impl.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const { newPromise, resolvePromise } = require('./helpers/webidl.js');
 4 | const { ExtractHighWaterMark, ExtractSizeAlgorithm } = require('./abstract-ops/queuing-strategy.js');
 5 | const aos = require('./abstract-ops/transform-streams.js');
 6 | 
 7 | const Transformer = require('../generated/Transformer.js');
 8 | 
 9 | exports.implementation = class TransformStreamImpl {
10 |   constructor(globalObject, [transformer, writableStrategy, readableStrategy]) {
11 |     if (transformer === undefined) {
12 |       transformer = null;
13 |     }
14 |     const transformerDict = Transformer.convert(globalObject, transformer);
15 |     if ('readableType' in transformerDict) {
16 |       throw new RangeError('Invalid readableType specified');
17 |     }
18 |     if ('writableType' in transformerDict) {
19 |       throw new RangeError('Invalid writableType specified');
20 |     }
21 | 
22 |     const readableHighWaterMark = ExtractHighWaterMark(readableStrategy, 0);
23 |     const readableSizeAlgorithm = ExtractSizeAlgorithm(readableStrategy);
24 |     const writableHighWaterMark = ExtractHighWaterMark(writableStrategy, 1);
25 |     const writableSizeAlgorithm = ExtractSizeAlgorithm(writableStrategy);
26 | 
27 |     const startPromise = newPromise();
28 | 
29 |     aos.InitializeTransformStream(
30 |       this, startPromise, writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm
31 |     );
32 |     aos.SetUpTransformStreamDefaultControllerFromTransformer(this, transformer, transformerDict);
33 | 
34 |     if ('start' in transformerDict) {
35 |       resolvePromise(startPromise, transformerDict.start.call(transformer, this._controller));
36 |     } else {
37 |       resolvePromise(startPromise, undefined);
38 |     }
39 |   }
40 | 
41 |   get readable() {
42 |     return this._readable;
43 |   }
44 | 
45 |   get writable() {
46 |     return this._writable;
47 |   }
48 | };
49 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/TransformStream.webidl:
--------------------------------------------------------------------------------
 1 | [Exposed=(Window,Worker,Worklet), Transferable]
 2 | interface TransformStream {
 3 |   constructor(optional object transformer,
 4 |               optional QueuingStrategy writableStrategy = {},
 5 |               optional QueuingStrategy readableStrategy = {});
 6 | 
 7 |   readonly attribute ReadableStream readable;
 8 |   readonly attribute WritableStream writable;
 9 | };
10 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/TransformStreamDefaultController-impl.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const aos = require('./abstract-ops/transform-streams.js');
 4 | const rsAOs = require('./abstract-ops/readable-streams.js');
 5 | 
 6 | exports.implementation = class TransformStreamDefaultController {
 7 |   get desiredSize() {
 8 |     const readableController = this._stream._readable._controller;
 9 |     return rsAOs.ReadableStreamDefaultControllerGetDesiredSize(readableController);
10 |   }
11 | 
12 |   enqueue(chunk) {
13 |     aos.TransformStreamDefaultControllerEnqueue(this, chunk);
14 |   }
15 | 
16 |   error(reason) {
17 |     aos.TransformStreamDefaultControllerError(this, reason);
18 |   }
19 | 
20 |   terminate() {
21 |     aos.TransformStreamDefaultControllerTerminate(this);
22 |   }
23 | };
24 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/TransformStreamDefaultController.webidl:
--------------------------------------------------------------------------------
1 | [Exposed=(Window,Worker,Worklet)]
2 | interface TransformStreamDefaultController {
3 |   readonly attribute unrestricted double? desiredSize;
4 | 
5 |   undefined enqueue(optional any chunk);
6 |   undefined error(optional any reason);
7 |   undefined terminate();
8 | };
9 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/Transformer.webidl:
--------------------------------------------------------------------------------
 1 | dictionary Transformer {
 2 |   TransformerStartCallback start;
 3 |   TransformerTransformCallback transform;
 4 |   TransformerFlushCallback flush;
 5 |   TransformerCancelCallback cancel;
 6 |   any readableType;
 7 |   any writableType;
 8 | };
 9 | 
10 | callback TransformerStartCallback = any (TransformStreamDefaultController controller);
11 | callback TransformerFlushCallback = Promise (TransformStreamDefaultController controller);
12 | callback TransformerTransformCallback = Promise (any chunk, TransformStreamDefaultController controller);
13 | callback TransformerCancelCallback = Promise (any reason);
14 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/UnderlyingSink.webidl:
--------------------------------------------------------------------------------
 1 | dictionary UnderlyingSink {
 2 |   UnderlyingSinkStartCallback start;
 3 |   UnderlyingSinkWriteCallback write;
 4 |   UnderlyingSinkCloseCallback close;
 5 |   UnderlyingSinkAbortCallback abort;
 6 |   any type;
 7 | };
 8 | 
 9 | callback UnderlyingSinkStartCallback = any (WritableStreamDefaultController controller);
10 | callback UnderlyingSinkWriteCallback = Promise (any chunk, WritableStreamDefaultController controller);
11 | callback UnderlyingSinkCloseCallback = Promise ();
12 | callback UnderlyingSinkAbortCallback = Promise (optional any reason);
13 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/UnderlyingSource.webidl:
--------------------------------------------------------------------------------
 1 | dictionary UnderlyingSource {
 2 |   UnderlyingSourceStartCallback start;
 3 |   UnderlyingSourcePullCallback pull;
 4 |   UnderlyingSourceCancelCallback cancel;
 5 |   ReadableStreamType type;
 6 |   [EnforceRange] unsigned long long autoAllocateChunkSize;
 7 | };
 8 | 
 9 | typedef (ReadableStreamDefaultController or ReadableByteStreamController) ReadableStreamController;
10 | 
11 | callback UnderlyingSourceStartCallback = any (ReadableStreamController controller);
12 | callback UnderlyingSourcePullCallback = Promise (ReadableStreamController controller);
13 | callback UnderlyingSourceCancelCallback = Promise (optional any reason);
14 | 
15 | enum ReadableStreamType { "bytes" };
16 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/WritableStream-impl.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const { promiseRejectedWith } = require('./helpers/webidl.js');
 4 | const { ExtractHighWaterMark, ExtractSizeAlgorithm } = require('./abstract-ops/queuing-strategy.js');
 5 | const aos = require('./abstract-ops/writable-streams.js');
 6 | 
 7 | const UnderlyingSink = require('../generated/UnderlyingSink.js');
 8 | 
 9 | exports.implementation = class WritableStreamImpl {
10 |   constructor(globalObject, [underlyingSink, strategy]) {
11 |     if (underlyingSink === undefined) {
12 |       underlyingSink = null;
13 |     }
14 |     const underlyingSinkDict = UnderlyingSink.convert(globalObject, underlyingSink);
15 |     if ('type' in underlyingSinkDict) {
16 |       throw new RangeError('Invalid type is specified');
17 |     }
18 | 
19 |     aos.InitializeWritableStream(this);
20 | 
21 |     const sizeAlgorithm = ExtractSizeAlgorithm(strategy);
22 |     const highWaterMark = ExtractHighWaterMark(strategy, 1);
23 | 
24 |     aos.SetUpWritableStreamDefaultControllerFromUnderlyingSink(
25 |       this, underlyingSink, underlyingSinkDict, highWaterMark, sizeAlgorithm
26 |     );
27 |   }
28 | 
29 |   get locked() {
30 |     return aos.IsWritableStreamLocked(this);
31 |   }
32 | 
33 |   abort(reason) {
34 |     if (aos.IsWritableStreamLocked(this) === true) {
35 |       return promiseRejectedWith(new TypeError('Cannot abort a stream that already has a writer'));
36 |     }
37 | 
38 |     return aos.WritableStreamAbort(this, reason);
39 |   }
40 | 
41 |   close() {
42 |     if (aos.IsWritableStreamLocked(this) === true) {
43 |       return promiseRejectedWith(new TypeError('Cannot close a stream that already has a writer'));
44 |     }
45 | 
46 |     if (aos.WritableStreamCloseQueuedOrInFlight(this) === true) {
47 |       return promiseRejectedWith(new TypeError('Cannot close an already-closing stream'));
48 |     }
49 | 
50 |     return aos.WritableStreamClose(this);
51 |   }
52 | 
53 |   getWriter() {
54 |     return aos.AcquireWritableStreamDefaultWriter(this);
55 |   }
56 | };
57 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/WritableStream.webidl:
--------------------------------------------------------------------------------
 1 | [Exposed=(Window,Worker,Worklet), Transferable]
 2 | interface WritableStream {
 3 |   constructor(optional object underlyingSink, optional QueuingStrategy strategy = {});
 4 | 
 5 |   readonly attribute boolean locked;
 6 | 
 7 |   Promise abort(optional any reason);
 8 |   Promise close();
 9 |   WritableStreamDefaultWriter getWriter();
10 | };
11 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/WritableStreamDefaultController-impl.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const aos = require('./abstract-ops/writable-streams.js');
 4 | const { AbortSteps, ErrorSteps } = require('./abstract-ops/internal-methods.js');
 5 | const { ResetQueue } = require('./abstract-ops/queue-with-sizes.js');
 6 | 
 7 | exports.implementation = class WritableStreamDefaultControllerImpl {
 8 |   get signal() {
 9 |     return this._abortController.signal;
10 |   }
11 | 
12 |   error(e) {
13 |     const state = this._stream._state;
14 | 
15 |     if (state !== 'writable') {
16 |       // The stream is closed, errored or will be soon. The sink can't do anything useful if it gets an error here, so
17 |       // just treat it as a no-op.
18 |       return;
19 |     }
20 | 
21 |     aos.WritableStreamDefaultControllerError(this, e);
22 |   }
23 | 
24 |   [AbortSteps](reason) {
25 |     const result = this._abortAlgorithm(reason);
26 |     aos.WritableStreamDefaultControllerClearAlgorithms(this);
27 |     return result;
28 |   }
29 | 
30 |   [ErrorSteps]() {
31 |     ResetQueue(this);
32 |   }
33 | };
34 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/WritableStreamDefaultController.webidl:
--------------------------------------------------------------------------------
1 | [Exposed=(Window,Worker,Worklet)]
2 | interface WritableStreamDefaultController {
3 |   readonly attribute AbortSignal signal;
4 |   undefined error(optional any e);
5 | };
6 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/WritableStreamDefaultWriter-impl.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | const assert = require('assert');
 3 | 
 4 | const { promiseRejectedWith } = require('./helpers/webidl.js');
 5 | const aos = require('./abstract-ops/writable-streams.js');
 6 | 
 7 | exports.implementation = class WritableStreamDefaultWriterImpl {
 8 |   constructor(globalObject, [stream]) {
 9 |     aos.SetUpWritableStreamDefaultWriter(this, stream);
10 |   }
11 | 
12 |   get closed() {
13 |     return this._closedPromise;
14 |   }
15 | 
16 |   get desiredSize() {
17 |     if (this._stream === undefined) {
18 |       throw defaultWriterLockException('desiredSize');
19 |     }
20 | 
21 |     return aos.WritableStreamDefaultWriterGetDesiredSize(this);
22 |   }
23 | 
24 |   get ready() {
25 |     return this._readyPromise;
26 |   }
27 | 
28 |   abort(reason) {
29 |     if (this._stream === undefined) {
30 |       return promiseRejectedWith(defaultWriterLockException('abort'));
31 |     }
32 | 
33 |     return aos.WritableStreamDefaultWriterAbort(this, reason);
34 |   }
35 | 
36 |   close() {
37 |     const stream = this._stream;
38 | 
39 |     if (stream === undefined) {
40 |       return promiseRejectedWith(defaultWriterLockException('close'));
41 |     }
42 | 
43 |     if (aos.WritableStreamCloseQueuedOrInFlight(stream) === true) {
44 |       return promiseRejectedWith(new TypeError('Cannot close an already-closing stream'));
45 |     }
46 | 
47 |     return aos.WritableStreamDefaultWriterClose(this);
48 |   }
49 | 
50 |   releaseLock() {
51 |     const stream = this._stream;
52 | 
53 |     if (stream === undefined) {
54 |       return;
55 |     }
56 | 
57 |     assert(stream._writer !== undefined);
58 | 
59 |     aos.WritableStreamDefaultWriterRelease(this);
60 |   }
61 | 
62 |   write(chunk) {
63 |     if (this._stream === undefined) {
64 |       return promiseRejectedWith(defaultWriterLockException('write to'));
65 |     }
66 | 
67 |     return aos.WritableStreamDefaultWriterWrite(this, chunk);
68 |   }
69 | };
70 | 
71 | function defaultWriterLockException(name) {
72 |   return new TypeError('Cannot ' + name + ' a stream using a released writer');
73 | }
74 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/WritableStreamDefaultWriter.webidl:
--------------------------------------------------------------------------------
 1 | [Exposed=(Window,Worker,Worklet)]
 2 | interface WritableStreamDefaultWriter {
 3 |   constructor(WritableStream stream);
 4 | 
 5 |   readonly attribute Promise closed;
 6 |   readonly attribute unrestricted double? desiredSize;
 7 |   readonly attribute Promise ready;
 8 | 
 9 |   Promise abort(optional any reason);
10 |   Promise close();
11 |   undefined releaseLock();
12 |   Promise write(optional any chunk);
13 | };
14 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/abstract-ops/ecmascript.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | const assert = require('assert');
  3 | 
  4 | const isFakeDetached = Symbol('is "detached" for our purposes');
  5 | 
  6 | exports.typeIsObject = x => (typeof x === 'object' && x !== null) || typeof x === 'function';
  7 | 
  8 | exports.CreateArrayFromList = elements => {
  9 |   // We use arrays to represent lists, so this is basically a no-op.
 10 |   // Do a slice though just in case we happen to depend on the unique-ness.
 11 |   return elements.slice();
 12 | };
 13 | 
 14 | exports.CopyDataBlockBytes = (dest, destOffset, src, srcOffset, n) => {
 15 |   new Uint8Array(dest).set(new Uint8Array(src, srcOffset, n), destOffset);
 16 | };
 17 | 
 18 | // Not implemented correctly
 19 | exports.TransferArrayBuffer = O => {
 20 |   assert(!exports.IsDetachedBuffer(O));
 21 |   const transferredIshVersion = O.slice();
 22 | 
 23 |   // This is specifically to fool tests that test "is transferred" by taking a non-zero-length
 24 |   // ArrayBuffer and checking if its byteLength starts returning 0.
 25 |   Object.defineProperty(O, 'byteLength', {
 26 |     get() {
 27 |       return 0;
 28 |     }
 29 |   });
 30 |   O[isFakeDetached] = true;
 31 | 
 32 |   return transferredIshVersion;
 33 | };
 34 | 
 35 | // Not implemented correctly
 36 | exports.CanTransferArrayBuffer = O => {
 37 |   return !exports.IsDetachedBuffer(O);
 38 | };
 39 | 
 40 | // Not implemented correctly
 41 | exports.IsDetachedBuffer = O => {
 42 |   return isFakeDetached in O;
 43 | };
 44 | 
 45 | exports.Call = (F, V, args = []) => {
 46 |   if (typeof F !== 'function') {
 47 |     throw new TypeError('Argument is not a function');
 48 |   }
 49 | 
 50 |   return Reflect.apply(F, V, args);
 51 | };
 52 | 
 53 | exports.GetMethod = (V, P) => {
 54 |   const func = V[P];
 55 |   if (func === undefined || func === null) {
 56 |     return undefined;
 57 |   }
 58 |   if (typeof func !== 'function') {
 59 |     throw new TypeError(`${P} is not a function`);
 60 |   }
 61 |   return func;
 62 | };
 63 | 
 64 | exports.CreateAsyncFromSyncIterator = syncIteratorRecord => {
 65 |   // Instead of re-implementing CreateAsyncFromSyncIterator and %AsyncFromSyncIteratorPrototype%,
 66 |   // we use yield* inside an async generator function to achieve the same result.
 67 | 
 68 |   // Wrap the sync iterator inside a sync iterable, so we can use it with yield*.
 69 |   const syncIterable = {
 70 |     [Symbol.iterator]: () => syncIteratorRecord.iterator
 71 |   };
 72 |   // Create an async generator function and immediately invoke it.
 73 |   const asyncIterator = (async function* () {
 74 |     return yield* syncIterable;
 75 |   }());
 76 |   // Return as an async iterator record.
 77 |   const nextMethod = asyncIterator.next;
 78 |   return { iterator: asyncIterator, nextMethod, done: false };
 79 | };
 80 | 
 81 | exports.GetIterator = (obj, hint = 'sync', method) => {
 82 |   assert(hint === 'sync' || hint === 'async');
 83 |   if (method === undefined) {
 84 |     if (hint === 'async') {
 85 |       method = exports.GetMethod(obj, Symbol.asyncIterator);
 86 |       if (method === undefined) {
 87 |         const syncMethod = exports.GetMethod(obj, Symbol.iterator);
 88 |         const syncIteratorRecord = exports.GetIterator(obj, 'sync', syncMethod);
 89 |         return exports.CreateAsyncFromSyncIterator(syncIteratorRecord);
 90 |       }
 91 |     } else {
 92 |       method = exports.GetMethod(obj, Symbol.iterator);
 93 |     }
 94 |   }
 95 |   const iterator = exports.Call(method, obj);
 96 |   if (!exports.typeIsObject(iterator)) {
 97 |     throw new TypeError('The iterator method must return an object');
 98 |   }
 99 |   const nextMethod = iterator.next;
100 |   return { iterator, nextMethod, done: false };
101 | };
102 | 
103 | exports.IteratorNext = (iteratorRecord, value) => {
104 |   let result;
105 |   if (value === undefined) {
106 |     result = exports.Call(iteratorRecord.nextMethod, iteratorRecord.iterator);
107 |   } else {
108 |     result = exports.Call(iteratorRecord.nextMethod, iteratorRecord.iterator, [value]);
109 |   }
110 |   if (!exports.typeIsObject(result)) {
111 |     throw new TypeError('The iterator.next() method must return an object');
112 |   }
113 |   return result;
114 | };
115 | 
116 | exports.IteratorComplete = iterResult => {
117 |   assert(exports.typeIsObject(iterResult));
118 |   return Boolean(iterResult.done);
119 | };
120 | 
121 | exports.IteratorValue = iterResult => {
122 |   assert(exports.typeIsObject(iterResult));
123 |   return iterResult.value;
124 | };
125 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/abstract-ops/internal-methods.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | 
3 | exports.AbortSteps = Symbol('[[AbortSteps]]');
4 | exports.ErrorSteps = Symbol('[[ErrorSteps]]');
5 | exports.CancelSteps = Symbol('[[CancelSteps]]');
6 | exports.PullSteps = Symbol('[[PullSteps]]');
7 | exports.ReleaseSteps = Symbol('[[ReleaseSteps]]');
8 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/abstract-ops/miscellaneous.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | const { IsDetachedBuffer } = require('./ecmascript');
 3 | 
 4 | exports.IsNonNegativeNumber = v => {
 5 |   if (typeof v !== 'number') {
 6 |     return false;
 7 |   }
 8 | 
 9 |   if (Number.isNaN(v)) {
10 |     return false;
11 |   }
12 | 
13 |   if (v < 0) {
14 |     return false;
15 |   }
16 | 
17 |   return true;
18 | };
19 | 
20 | exports.CloneAsUint8Array = O => {
21 |   const buffer = O.buffer.slice(O.byteOffset, O.byteOffset + O.byteLength);
22 |   return new Uint8Array(buffer);
23 | };
24 | 
25 | exports.CanCopyDataBlockBytes = (toBuffer, toIndex, fromBuffer, fromIndex, count) => {
26 |   if (toBuffer === fromBuffer) {
27 |     return false;
28 |   }
29 |   if (IsDetachedBuffer(toBuffer) === true) {
30 |     return false;
31 |   }
32 |   if (IsDetachedBuffer(fromBuffer) === true) {
33 |     return false;
34 |   }
35 |   if (toIndex + count > toBuffer.byteLength) {
36 |     return false;
37 |   }
38 |   if (fromIndex + count > fromBuffer.byteLength) {
39 |     return false;
40 |   }
41 |   return true;
42 | };
43 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/abstract-ops/queue-with-sizes.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | const assert = require('assert');
 3 | const { IsNonNegativeNumber } = require('./miscellaneous.js');
 4 | 
 5 | exports.DequeueValue = container => {
 6 |   assert('_queue' in container && '_queueTotalSize' in container);
 7 |   assert(container._queue.length > 0);
 8 | 
 9 |   const pair = container._queue.shift();
10 |   container._queueTotalSize -= pair.size;
11 |   if (container._queueTotalSize < 0) {
12 |     container._queueTotalSize = 0;
13 |   }
14 | 
15 |   return pair.value;
16 | };
17 | 
18 | exports.EnqueueValueWithSize = (container, value, size) => {
19 |   assert('_queue' in container && '_queueTotalSize' in container);
20 | 
21 |   if (!IsNonNegativeNumber(size)) {
22 |     throw new RangeError('Size must be a finite, non-NaN, non-negative number.');
23 |   }
24 |   if (size === Infinity) {
25 |     throw new RangeError('Size must be a finite, non-NaN, non-negative number.');
26 |   }
27 | 
28 |   container._queue.push({ value, size });
29 |   container._queueTotalSize += size;
30 | };
31 | 
32 | exports.PeekQueueValue = container => {
33 |   assert('_queue' in container && '_queueTotalSize' in container);
34 |   assert(container._queue.length > 0);
35 | 
36 |   const pair = container._queue[0];
37 |   return pair.value;
38 | };
39 | 
40 | exports.ResetQueue = container => {
41 |   assert('_queue' in container && '_queueTotalSize' in container);
42 | 
43 |   container._queue = [];
44 |   container._queueTotalSize = 0;
45 | };
46 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/abstract-ops/queuing-strategy.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | exports.ExtractHighWaterMark = (strategy, defaultHWM) => {
 4 |   if (!('highWaterMark' in strategy)) {
 5 |     return defaultHWM;
 6 |   }
 7 | 
 8 |   const { highWaterMark } = strategy;
 9 |   if (Number.isNaN(highWaterMark) || highWaterMark < 0) {
10 |     throw new RangeError('Invalid highWaterMark');
11 |   }
12 | 
13 |   return highWaterMark;
14 | };
15 | 
16 | exports.ExtractSizeAlgorithm = strategy => {
17 |   const { size } = strategy;
18 | 
19 |   if (!size) {
20 |     return () => 1;
21 |   }
22 | 
23 |   // This is silly, but more obviously matches the spec (which distinguishes between algorithms and JS functions).
24 |   return chunk => size(chunk);
25 | };
26 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/abstract-ops/transform-streams.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | const assert = require('assert');
  3 | const verbose = require('debug')('streams:transform-stream:verbose');
  4 | 
  5 | const { promiseResolvedWith, promiseRejectedWith, newPromise, resolvePromise, rejectPromise, uponPromise,
  6 |         transformPromiseWith } = require('../helpers/webidl.js');
  7 | const { CreateReadableStream, ReadableStreamDefaultControllerClose, ReadableStreamDefaultControllerEnqueue,
  8 |         ReadableStreamDefaultControllerError, ReadableStreamDefaultControllerHasBackpressure,
  9 |         ReadableStreamDefaultControllerCanCloseOrEnqueue } = require('./readable-streams.js');
 10 | const { CreateWritableStream, WritableStreamDefaultControllerErrorIfNeeded } = require('./writable-streams.js');
 11 | 
 12 | const TransformStream = require('../../generated/TransformStream.js');
 13 | const TransformStreamDefaultController = require('../../generated/TransformStreamDefaultController.js');
 14 | 
 15 | Object.assign(exports, {
 16 |   InitializeTransformStream,
 17 |   SetUpTransformStreamDefaultControllerFromTransformer,
 18 |   TransformStreamDefaultControllerEnqueue,
 19 |   TransformStreamDefaultControllerError,
 20 |   TransformStreamDefaultControllerTerminate
 21 | });
 22 | 
 23 | // Working with transform streams
 24 | 
 25 | // CreateTransformStream is not implemented since it is only meant for external specs.
 26 | 
 27 | function InitializeTransformStream(
 28 |   stream, startPromise, writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm) {
 29 |   function startAlgorithm() {
 30 |     return startPromise;
 31 |   }
 32 | 
 33 |   function writeAlgorithm(chunk) {
 34 |     return TransformStreamDefaultSinkWriteAlgorithm(stream, chunk);
 35 |   }
 36 | 
 37 |   function abortAlgorithm(reason) {
 38 |     return TransformStreamDefaultSinkAbortAlgorithm(stream, reason);
 39 |   }
 40 | 
 41 |   function closeAlgorithm() {
 42 |     return TransformStreamDefaultSinkCloseAlgorithm(stream);
 43 |   }
 44 | 
 45 |   stream._writable = CreateWritableStream(
 46 |     startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, writableHighWaterMark, writableSizeAlgorithm
 47 |   );
 48 | 
 49 |   function pullAlgorithm() {
 50 |     return TransformStreamDefaultSourcePullAlgorithm(stream);
 51 |   }
 52 | 
 53 |   function cancelAlgorithm(reason) {
 54 |     return TransformStreamDefaultSourceCancelAlgorithm(stream, reason);
 55 |   }
 56 | 
 57 |   stream._readable = CreateReadableStream(
 58 |     startAlgorithm, pullAlgorithm, cancelAlgorithm, readableHighWaterMark, readableSizeAlgorithm
 59 |   );
 60 | 
 61 |   // The [[backpressure]] slot is set to undefined so that it can be initialised by TransformStreamSetBackpressure.
 62 |   stream._backpressure = undefined;
 63 |   stream._backpressureChangePromise = undefined;
 64 |   TransformStreamSetBackpressure(stream, true);
 65 | 
 66 |   stream._controller = undefined;
 67 | }
 68 | 
 69 | function TransformStreamError(stream, e) {
 70 |   verbose('TransformStreamError()');
 71 | 
 72 |   ReadableStreamDefaultControllerError(stream._readable._controller, e);
 73 |   TransformStreamErrorWritableAndUnblockWrite(stream, e);
 74 | }
 75 | 
 76 | function TransformStreamErrorWritableAndUnblockWrite(stream, e) {
 77 |   TransformStreamDefaultControllerClearAlgorithms(stream._controller);
 78 |   WritableStreamDefaultControllerErrorIfNeeded(stream._writable._controller, e);
 79 |   TransformStreamUnblockWrite(stream);
 80 | }
 81 | 
 82 | function TransformStreamUnblockWrite(stream) {
 83 |   if (stream._backpressure === true) {
 84 |     // Pretend that pull() was called to permit any pending write() calls to complete. TransformStreamSetBackpressure()
 85 |     // cannot be called from enqueue() or pull() once the ReadableStream is errored, so this will will be the final time
 86 |     // _backpressure is set.
 87 |     TransformStreamSetBackpressure(stream, false);
 88 |   }
 89 | }
 90 | 
 91 | function TransformStreamSetBackpressure(stream, backpressure) {
 92 |   verbose(`TransformStreamSetBackpressure() [backpressure = ${backpressure}]`);
 93 | 
 94 |   // Passes also when called during construction.
 95 |   assert(stream._backpressure !== backpressure);
 96 | 
 97 |   if (stream._backpressureChangePromise !== undefined) {
 98 |     resolvePromise(stream._backpressureChangePromise, undefined);
 99 |   }
100 | 
101 |   stream._backpressureChangePromise = newPromise();
102 | 
103 |   stream._backpressure = backpressure;
104 | }
105 | 
106 | // Default controllers
107 | 
108 | function SetUpTransformStreamDefaultController(stream, controller, transformAlgorithm, flushAlgorithm,
109 |                                                cancelAlgorithm) {
110 |   assert(TransformStream.isImpl(stream));
111 |   assert(stream._controller === undefined);
112 | 
113 |   controller._stream = stream;
114 |   stream._controller = controller;
115 | 
116 |   controller._transformAlgorithm = transformAlgorithm;
117 |   controller._flushAlgorithm = flushAlgorithm;
118 |   controller._cancelAlgorithm = cancelAlgorithm;
119 | }
120 | 
121 | function SetUpTransformStreamDefaultControllerFromTransformer(stream, transformer, transformerDict) {
122 |   const controller = TransformStreamDefaultController.new(globalThis);
123 | 
124 |   let transformAlgorithm = chunk => {
125 |     try {
126 |       TransformStreamDefaultControllerEnqueue(controller, chunk);
127 |       return promiseResolvedWith(undefined);
128 |     } catch (transformResultE) {
129 |       return promiseRejectedWith(transformResultE);
130 |     }
131 |   };
132 | 
133 |   let flushAlgorithm = () => promiseResolvedWith(undefined);
134 |   let cancelAlgorithm = () => promiseResolvedWith(undefined);
135 | 
136 |   if ('transform' in transformerDict) {
137 |     transformAlgorithm = chunk => transformerDict.transform.call(transformer, chunk, controller);
138 |   }
139 |   if ('flush' in transformerDict) {
140 |     flushAlgorithm = () => transformerDict.flush.call(transformer, controller);
141 |   }
142 |   if ('cancel' in transformerDict) {
143 |     cancelAlgorithm = reason => transformerDict.cancel.call(transformer, reason);
144 |   }
145 | 
146 |   SetUpTransformStreamDefaultController(stream, controller, transformAlgorithm, flushAlgorithm, cancelAlgorithm);
147 | }
148 | 
149 | function TransformStreamDefaultControllerClearAlgorithms(controller) {
150 |   controller._transformAlgorithm = undefined;
151 |   controller._flushAlgorithm = undefined;
152 |   controller._cancelAlgorithm = undefined;
153 | }
154 | 
155 | function TransformStreamDefaultControllerEnqueue(controller, chunk) {
156 |   verbose('TransformStreamDefaultControllerEnqueue()');
157 | 
158 |   const stream = controller._stream;
159 |   const readableController = stream._readable._controller;
160 |   if (ReadableStreamDefaultControllerCanCloseOrEnqueue(readableController) === false) {
161 |     throw new TypeError('Readable side is not in a state that permits enqueue');
162 |   }
163 | 
164 |   // We throttle transform invocations based on the backpressure of the ReadableStream, but we still
165 |   // accept TransformStreamDefaultControllerEnqueue() calls.
166 | 
167 |   try {
168 |     ReadableStreamDefaultControllerEnqueue(readableController, chunk);
169 |   } catch (e) {
170 |     // This happens when readableStrategy.size() throws.
171 |     TransformStreamErrorWritableAndUnblockWrite(stream, e);
172 | 
173 |     throw stream._readable._storedError;
174 |   }
175 | 
176 |   const backpressure = ReadableStreamDefaultControllerHasBackpressure(readableController);
177 |   if (backpressure !== stream._backpressure) {
178 |     assert(backpressure === true);
179 |     TransformStreamSetBackpressure(stream, true);
180 |   }
181 | }
182 | 
183 | function TransformStreamDefaultControllerError(controller, e) {
184 |   TransformStreamError(controller._stream, e);
185 | }
186 | 
187 | function TransformStreamDefaultControllerPerformTransform(controller, chunk) {
188 |   const transformPromise = controller._transformAlgorithm(chunk);
189 |   return transformPromiseWith(transformPromise, undefined, r => {
190 |     TransformStreamError(controller._stream, r);
191 |     throw r;
192 |   });
193 | }
194 | 
195 | function TransformStreamDefaultControllerTerminate(controller) {
196 |   verbose('TransformStreamDefaultControllerTerminate()');
197 | 
198 |   const stream = controller._stream;
199 |   const readableController = stream._readable._controller;
200 | 
201 |   ReadableStreamDefaultControllerClose(readableController);
202 | 
203 |   const error = new TypeError('TransformStream terminated');
204 |   TransformStreamErrorWritableAndUnblockWrite(stream, error);
205 | }
206 | 
207 | // Default sinks
208 | 
209 | function TransformStreamDefaultSinkWriteAlgorithm(stream, chunk) {
210 |   verbose('TransformStreamDefaultSinkWriteAlgorithm()');
211 | 
212 |   assert(stream._writable._state === 'writable');
213 | 
214 |   const controller = stream._controller;
215 | 
216 |   if (stream._backpressure === true) {
217 |     const backpressureChangePromise = stream._backpressureChangePromise;
218 |     assert(backpressureChangePromise !== undefined);
219 |     return transformPromiseWith(backpressureChangePromise, () => {
220 |       const writable = stream._writable;
221 |       const state = writable._state;
222 |       if (state === 'erroring') {
223 |         throw writable._storedError;
224 |       }
225 |       assert(state === 'writable');
226 |       return TransformStreamDefaultControllerPerformTransform(controller, chunk);
227 |     });
228 |   }
229 | 
230 |   return TransformStreamDefaultControllerPerformTransform(controller, chunk);
231 | }
232 | 
233 | function TransformStreamDefaultSinkAbortAlgorithm(stream, reason) {
234 |   verbose('TransformStreamDefaultSinkAbortAlgorithm()');
235 | 
236 |   const controller = stream._controller;
237 |   if (controller._finishPromise !== undefined) {
238 |     return controller._finishPromise;
239 |   }
240 | 
241 |   // stream._readable cannot change after construction, so caching it across a call to user code is safe.
242 |   const readable = stream._readable;
243 | 
244 |   // Assign the _finishPromise now so that if _cancelAlgorithm calls readable.cancel() internally,
245 |   // we don't run the _cancelAlgorithm again.
246 |   controller._finishPromise = newPromise();
247 | 
248 |   const cancelPromise = controller._cancelAlgorithm(reason);
249 |   TransformStreamDefaultControllerClearAlgorithms(controller);
250 | 
251 |   uponPromise(cancelPromise, () => {
252 |     if (readable._state === 'errored') {
253 |       rejectPromise(controller._finishPromise, readable._storedError);
254 |     } else {
255 |       ReadableStreamDefaultControllerError(readable._controller, reason);
256 |       resolvePromise(controller._finishPromise);
257 |     }
258 |   }, r => {
259 |     ReadableStreamDefaultControllerError(readable._controller, r);
260 |     rejectPromise(controller._finishPromise, r);
261 |   });
262 | 
263 |   return controller._finishPromise;
264 | }
265 | 
266 | function TransformStreamDefaultSinkCloseAlgorithm(stream) {
267 |   verbose('TransformStreamDefaultSinkCloseAlgorithm()');
268 | 
269 |   const controller = stream._controller;
270 |   if (controller._finishPromise !== undefined) {
271 |     return controller._finishPromise;
272 |   }
273 | 
274 |   // stream._readable cannot change after construction, so caching it across a call to user code is safe.
275 |   const readable = stream._readable;
276 | 
277 |   // Assign the _finishPromise now so that if _flushAlgorithm calls readable.cancel() internally,
278 |   // we don't also run the _cancelAlgorithm.
279 |   controller._finishPromise = newPromise();
280 | 
281 |   const flushPromise = controller._flushAlgorithm();
282 |   TransformStreamDefaultControllerClearAlgorithms(controller);
283 | 
284 |   uponPromise(flushPromise, () => {
285 |     if (readable._state === 'errored') {
286 |       rejectPromise(controller._finishPromise, readable._storedError);
287 |     } else {
288 |       ReadableStreamDefaultControllerClose(readable._controller);
289 |       resolvePromise(controller._finishPromise);
290 |     }
291 |   }, r => {
292 |     ReadableStreamDefaultControllerError(readable._controller, r);
293 |     rejectPromise(controller._finishPromise, r);
294 |   });
295 | 
296 |   return controller._finishPromise;
297 | }
298 | 
299 | // Default sources
300 | 
301 | function TransformStreamDefaultSourcePullAlgorithm(stream) {
302 |   verbose('TransformStreamDefaultSourcePullAlgorithm()');
303 | 
304 |   // Invariant. Enforced by the promises returned by start() and pull().
305 |   assert(stream._backpressure === true);
306 | 
307 |   assert(stream._backpressureChangePromise !== undefined);
308 | 
309 |   TransformStreamSetBackpressure(stream, false);
310 | 
311 |   // Prevent the next pull() call until there is backpressure.
312 |   return stream._backpressureChangePromise;
313 | }
314 | 
315 | function TransformStreamDefaultSourceCancelAlgorithm(stream, reason) {
316 |   verbose('TransformStreamDefaultSourceCancelAlgorithm()');
317 | 
318 |   const controller = stream._controller;
319 |   if (controller._finishPromise !== undefined) {
320 |     return controller._finishPromise;
321 |   }
322 | 
323 |   // stream._writable cannot change after construction, so caching it across a call to user code is safe.
324 |   const writable = stream._writable;
325 | 
326 |   // Assign the _finishPromise now so that if _flushAlgorithm calls writable.abort() or
327 |   // writable.cancel() internally, we don't run the _cancelAlgorithm again, or also run the
328 |   // _flushAlgorithm.
329 |   controller._finishPromise = newPromise();
330 | 
331 |   const cancelPromise = controller._cancelAlgorithm(reason);
332 |   TransformStreamDefaultControllerClearAlgorithms(controller);
333 | 
334 |   uponPromise(cancelPromise, () => {
335 |     if (writable._state === 'errored') {
336 |       rejectPromise(controller._finishPromise, writable._storedError);
337 |     } else {
338 |       WritableStreamDefaultControllerErrorIfNeeded(writable._controller, reason);
339 |       TransformStreamUnblockWrite(stream);
340 |       resolvePromise(controller._finishPromise);
341 |     }
342 |   }, r => {
343 |     WritableStreamDefaultControllerErrorIfNeeded(writable._controller, r);
344 |     TransformStreamUnblockWrite(stream);
345 |     rejectPromise(controller._finishPromise, r);
346 |   });
347 | 
348 |   return controller._finishPromise;
349 | }
350 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/helpers/miscellaneous.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | const assert = require('assert');
 3 | 
 4 | exports.rethrowAssertionErrorRejection = e => {
 5 |   // Used throughout the reference implementation, as `.catch(rethrowAssertionErrorRejection)`, to ensure any errors
 6 |   // get shown. There are places in the spec where we do promise transformations and purposefully ignore or don't
 7 |   // expect any errors, but assertion errors are always problematic.
 8 |   if (e && e instanceof assert.AssertionError) {
 9 |     setTimeout(() => {
10 |       throw e;
11 |     }, 0);
12 |   }
13 | };
14 | 
15 | exports.mixin = (target, source) => {
16 |   const keys = Reflect.ownKeys(source);
17 |   for (let i = 0; i < keys.length; ++i) {
18 |     if (keys[i] in target) {
19 |       continue;
20 |     }
21 | 
22 |     Object.defineProperty(target, keys[i], Object.getOwnPropertyDescriptor(source, keys[i]));
23 |   }
24 | };
25 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/helpers/webidl.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | const assert = require('assert');
  3 | const { rethrowAssertionErrorRejection } = require('./miscellaneous.js');
  4 | 
  5 | const originalPromise = Promise;
  6 | const originalPromiseThen = Promise.prototype.then;
  7 | const originalPromiseReject = Promise.reject;
  8 | 
  9 | const promiseSideTable = new WeakMap();
 10 | 
 11 | // https://webidl.spec.whatwg.org/#a-new-promise
 12 | function newPromise() {
 13 |   // The stateIsPending tracking only works if we never resolve the promises with other promises.
 14 |   // In this spec, that happens to be true for the promises in question; they are always resolved with undefined.
 15 |   const sideData = { stateIsPending: true };
 16 |   const promise = new originalPromise((resolve, reject) => {
 17 |     sideData.resolve = resolve;
 18 |     sideData.reject = reject;
 19 |   });
 20 | 
 21 |   promiseSideTable.set(promise, sideData);
 22 |   return promise;
 23 | }
 24 | 
 25 | // https://webidl.spec.whatwg.org/#resolve
 26 | function resolvePromise(p, value) {
 27 |   // We intend to only resolve or reject promises that are still pending.
 28 |   // When this is not the case, it usually means there's a bug in the specification that we want to fix.
 29 |   // This assertion is NOT a normative requirement. It is part of the reference implementation only
 30 |   // to help detect bugs in the specification. Other implementors MUST NOT replicate this assertion.
 31 |   assert(stateIsPending(p) === true);
 32 |   promiseSideTable.get(p).resolve(value);
 33 |   promiseSideTable.get(p).stateIsPending = false;
 34 | }
 35 | 
 36 | // https://webidl.spec.whatwg.org/#reject
 37 | function rejectPromise(p, reason) {
 38 |   assert(stateIsPending(p) === true);
 39 |   promiseSideTable.get(p).reject(reason);
 40 |   promiseSideTable.get(p).stateIsPending = false;
 41 | }
 42 | 
 43 | // https://webidl.spec.whatwg.org/#a-promise-resolved-with
 44 | function promiseResolvedWith(value) {
 45 |   // Cannot use original Promise.resolve since that will return value itself sometimes, unlike Web IDL.
 46 |   const promise = new originalPromise(resolve => resolve(value));
 47 |   promiseSideTable.set(promise, { stateIsPending: false });
 48 |   return promise;
 49 | }
 50 | 
 51 | // https://webidl.spec.whatwg.org/#a-promise-rejected-with
 52 | function promiseRejectedWith(reason) {
 53 |   const promise = originalPromiseReject.call(originalPromise, reason);
 54 |   promiseSideTable.set(promise, { stateIsPending: false });
 55 |   return promise;
 56 | }
 57 | 
 58 | function PerformPromiseThen(promise, onFulfilled, onRejected) {
 59 |   // There doesn't appear to be any way to correctly emulate the behaviour from JavaScript, so this is just an
 60 |   // approximation.
 61 |   return originalPromiseThen.call(promise, onFulfilled, onRejected);
 62 | }
 63 | 
 64 | // https://webidl.spec.whatwg.org/#dfn-perform-steps-once-promise-is-settled
 65 | function uponPromise(promise, onFulfilled, onRejected) {
 66 |   PerformPromiseThen(
 67 |     PerformPromiseThen(promise, onFulfilled, onRejected),
 68 |     undefined,
 69 |     rethrowAssertionErrorRejection
 70 |   );
 71 | }
 72 | 
 73 | function uponFulfillment(promise, onFulfilled) {
 74 |   uponPromise(promise, onFulfilled);
 75 | }
 76 | 
 77 | function uponRejection(promise, onRejected) {
 78 |   uponPromise(promise, undefined, onRejected);
 79 | }
 80 | 
 81 | function transformPromiseWith(promise, fulfillmentHandler, rejectionHandler) {
 82 |   return PerformPromiseThen(promise, fulfillmentHandler, rejectionHandler);
 83 | }
 84 | 
 85 | function setPromiseIsHandledToTrue(promise) {
 86 |   PerformPromiseThen(promise, undefined, rethrowAssertionErrorRejection);
 87 | }
 88 | 
 89 | function stateIsPending(promise) {
 90 |   return promiseSideTable.get(promise).stateIsPending;
 91 | }
 92 | 
 93 | Object.assign(exports, {
 94 |   newPromise,
 95 |   resolvePromise,
 96 |   rejectPromise,
 97 |   promiseResolvedWith,
 98 |   promiseRejectedWith,
 99 |   uponPromise,
100 |   uponFulfillment,
101 |   uponRejection,
102 |   transformPromiseWith,
103 |   setPromiseIsHandledToTrue,
104 |   stateIsPending
105 | });
106 | 
107 | // https://webidl.spec.whatwg.org/#wait-for-all
108 | function waitForAll(promises, successSteps, failureSteps) {
109 |   let fulfilledCount = 0;
110 |   let rejected = false;
111 |   const rejectionHandler = arg => {
112 |     if (rejected === false) {
113 |       rejected = true;
114 |       failureSteps(arg);
115 |     }
116 |   };
117 |   let index = 0;
118 |   const total = promises.length;
119 |   const result = new Array(total);
120 |   if (total === 0) {
121 |     queueMicrotask(() => successSteps(result));
122 |     return;
123 |   }
124 |   for (const promise of promises) {
125 |     const promiseIndex = index;
126 |     const fulfillmentHandler = arg => {
127 |       result[promiseIndex] = arg;
128 |       ++fulfilledCount;
129 |       if (fulfilledCount === total) {
130 |         successSteps(result);
131 |       }
132 |     };
133 |     PerformPromiseThen(promise, fulfillmentHandler, rejectionHandler);
134 |     ++index;
135 |   }
136 | }
137 | 
138 | // https://webidl.spec.whatwg.org/#waiting-for-all-promise
139 | exports.waitForAllPromise = promises => {
140 |   const promise = newPromise();
141 |   const successSteps = results => {
142 |     resolvePromise(promise, results);
143 |   };
144 |   const failureSteps = reason => {
145 |     rejectPromise(promise, reason);
146 |   };
147 |   waitForAll(promises, successSteps, failureSteps);
148 |   return promise;
149 | };
150 | 


--------------------------------------------------------------------------------
/reference-implementation/lib/index.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | // This file is used as the entry point for browserifying the reference implementation to allow it
 3 | // to run inside the wpt-runner "browser like" context.
 4 | 
 5 | window.gc = gc;
 6 | 
 7 | require('../generated/ByteLengthQueuingStrategy.js').install(window, ['Window']);
 8 | require('../generated/CountQueuingStrategy.js').install(window, ['Window']);
 9 | 
10 | require('../generated/ReadableStream.js').install(window, ['Window']);
11 | require('../generated/ReadableStreamDefaultReader.js').install(window, ['Window']);
12 | require('../generated/ReadableStreamBYOBReader.js').install(window, ['Window']);
13 | require('../generated/ReadableStreamDefaultController.js').install(window, ['Window']);
14 | require('../generated/ReadableByteStreamController.js').install(window, ['Window']);
15 | require('../generated/ReadableStreamBYOBRequest.js').install(window, ['Window']);
16 | 
17 | require('../generated/WritableStream.js').install(window, ['Window']);
18 | require('../generated/WritableStreamDefaultWriter.js').install(window, ['Window']);
19 | require('../generated/WritableStreamDefaultController.js').install(window, ['Window']);
20 | 
21 | require('../generated/TransformStream.js').install(window, ['Window']);
22 | require('../generated/TransformStreamDefaultController.js').install(window, ['Window']);
23 | 


--------------------------------------------------------------------------------
/reference-implementation/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "private": true,
 3 |   "description": "Reference implementation and tests for the WHATWG Streams Standard",
 4 |   "scripts": {
 5 |     "pretest": "node compile-idl.js && browserify lib/index.js -d -o bundle.js",
 6 |     "test": "npm run lint && npm run wpt",
 7 |     "wpt": "npm run pretest && node --expose_gc run-web-platform-tests.js",
 8 |     "sync-wpt": "git submodule update --init",
 9 |     "lint": "eslint .",
10 |     "coverage": "c8 --reporter=lcov -- npm run wpt && opener coverage/lcov-report/index.html"
11 |   },
12 |   "author": "Domenic Denicola  (https://domenic.me/)",
13 |   "license": "(CC0-1.0 OR MIT)",
14 |   "devDependencies": {
15 |     "browserify": "^17.0.1",
16 |     "c8": "^10.1.2",
17 |     "debug": "^4.1.1",
18 |     "eslint": "^6.8.0",
19 |     "minimatch": "^10.0.1",
20 |     "opener": "^1.5.2",
21 |     "webidl2js": "^18.0.0",
22 |     "wpt-runner": "^6.0.0"
23 |   }
24 | }
25 | 


--------------------------------------------------------------------------------
/reference-implementation/run-web-platform-tests.js:
--------------------------------------------------------------------------------
 1 | // This runs the web platform tests against the reference implementation, in Node.js using jsdom, for easier rapid
 2 | // development of the reference implementation and the web platform tests.
 3 | /* eslint-disable no-console */
 4 | 'use strict';
 5 | const path = require('path');
 6 | const fs = require('fs');
 7 | const { promisify } = require('util');
 8 | const wptRunner = require('wpt-runner');
 9 | const { minimatch } = require('minimatch');
10 | const readFileAsync = promisify(fs.readFile);
11 | 
12 | // wpt-runner does not yet support unhandled rejection tracking a la
13 | // https://github.com/w3c/testharness.js/commit/7716e2581a86dfd9405a9c00547a7504f0c7fe94
14 | // So we emulate it with Node.js events
15 | const rejections = new Map();
16 | process.on('unhandledRejection', (reason, promise) => {
17 |   rejections.set(promise, reason);
18 | });
19 | 
20 | process.on('rejectionHandled', promise => {
21 |   rejections.delete(promise);
22 | });
23 | 
24 | main().catch(e => {
25 |   console.error(e.stack);
26 |   process.exitCode = 1;
27 | });
28 | 
29 | async function main() {
30 |   const bundlePath = path.resolve(__dirname, 'bundle.js');
31 |   const wptPath = path.resolve(__dirname, 'web-platform-tests');
32 |   const testsPath = path.resolve(wptPath, 'streams');
33 | 
34 |   const filterGlobs = process.argv.length >= 3 ? process.argv.slice(2) : ['**/*.html'];
35 |   const excludeGlobs = [
36 |     'piping/general-addition.any.html', // FIXME: reenable this test as part of https://github.com/whatwg/streams/issues/1243.
37 |     // These tests use ArrayBuffers backed by WebAssembly.Memory objects, which *should* be non-transferable.
38 |     // However, our TransferArrayBuffer implementation cannot detect these, and will incorrectly "transfer" them anyway.
39 |     'readable-byte-streams/non-transferable-buffers.any.html',
40 |     'readable-streams/owning-type-message-port.any.html', // disabled due to MessagePort use.
41 |     'readable-streams/owning-type-video-frame.any.html', // disabled due to VideoFrame use.
42 |     'readable-streams/owning-type.any.html', // FIXME: reenable this test once owning type PR lands.
43 |     'transferable/transform-stream-members.any.html' // FIXME: reenable if structuredClone is aligned.
44 |   ];
45 |   const anyTestPattern = /\.any\.html$/;
46 | 
47 |   let bundledJS = await readFileAsync(bundlePath, { encoding: 'utf8' });
48 |   bundledJS = `${bundledJS}\n//# sourceURL=file://${bundlePath}`;
49 | 
50 |   const failures = await wptRunner(testsPath, {
51 |     rootURL: 'streams/',
52 |     setup(window) {
53 |       window.queueMicrotask = queueMicrotask;
54 |       window.structuredClone = globalThis.structuredClone;
55 |       window.fetch = async function (url) {
56 |         const filePath = path.join(wptPath, url);
57 |         if (!filePath.startsWith(wptPath)) {
58 |           throw new TypeError('Invalid URL');
59 |         }
60 |         return {
61 |           ok: true,
62 |           async text() {
63 |             return await readFileAsync(filePath, { encoding: 'utf8' });
64 |           }
65 |         };
66 |       };
67 |       window.eval(bundledJS);
68 |     },
69 |     filter(testPath) {
70 |       // Ignore the window-specific and worker-specific tests
71 |       if (!anyTestPattern.test(testPath)) {
72 |         return false;
73 |       }
74 | 
75 |       return filterGlobs.some(glob => minimatch(testPath, glob)) &&
76 |         !excludeGlobs.some(glob => minimatch(testPath, glob));
77 |     }
78 |   });
79 | 
80 |   process.exitCode = failures;
81 | 
82 |   if (rejections.size > 0) {
83 |     if (failures === 0) {
84 |       process.exitCode = 1;
85 |     }
86 | 
87 |     for (const reason of rejections.values()) {
88 |       console.error('Unhandled promise rejection: ', reason.stack);
89 |     }
90 |   }
91 | }
92 | 


--------------------------------------------------------------------------------
/streams-for-raw-video-explainer.md:
--------------------------------------------------------------------------------
  1 | # Transferring Ownership Streams Explained
  2 | 
  3 | 
  4 | ## Introduction
  5 | 
  6 | The streams APIs provide a convenient way to build processing pipelines.
  7 | Using streams with `VideoFrame` objects has known shortcomings, as illustrated by https://github.com/whatwg/streams/issues/1155 or https://github.com/whatwg/streams/issues/1185 for instance.
  8 | This proposal addresses these shortcomings by improving streams support for chunks similar but not limited to WebCodec `VideoFrame`.
  9 | It could also be useful for streams making use of `ArrayBuffer` or chunks that own `ArrayBuffer` like `RTCEncodedVideoChunk`.
 10 | 
 11 | Streams APIs can create coupling between the processing units of the pipeline when chunks in the pipe are mutable:
 12 | a processing unit A might pass a chunk O to another unit B through a stream (say a `WritableStream`) W.
 13 | If A keeps a reference to O, it might mutate O while B is doing processing based on O.
 14 | 
 15 | This is especially an issue with chunks like `VideoFrame` objects that need to be closed explicitly.
 16 | Taking the previous example, if A decides to close a `VideoFrame` after passing it to W but before B gets it, B will receive a closed `VideoFrame`, which is probably considered a bug.
 17 | If A does not close `VideoFrame`, it is the responsibility of the remaining of the pipeline to close it.
 18 | There is a need to clearly identify who is owning these chunks and who is responsible to close these chunks at any point in time.
 19 | 
 20 | The proposed solution is to transfer ownership of a chunk to the stream when it gets written or enqueueud to the stream.
 21 | By doing so, the processing unit that enqueues/writes chunks will not be able to mutate chunks manipulated by the stream and is relieved of the lifetime management of these chunks.
 22 | Conversely, processing units take ownership of chunks when they receive them from a stream.
 23 | 
 24 | Transferring ownership should be opt-in. For that purpose, a new streams type, named 'owning' in this document,  would be added.
 25 | 
 26 | ## Example
 27 | 
 28 | Below is an example of JavaScript that shows how this can be used.
 29 | The example creates a processing pipe starting with a VideoFrame stream and applying two transforms, one for doing a processing like logging every 30 frame, and one for doing background blur.
 30 | 
 31 | ```worker.js javascript
 32 | function doBackgroundBlurOnVideoFrames(videoFrameStream, doLogging)
 33 | {
 34 |   // JavaScript custom transform.
 35 |   let frameCount = 0;
 36 |   const frameCountTransform = new TransformStream({
 37 |     transform: async (videoFrame, controller) => {
 38 |       try {
 39 |         // videoFrame is under the responsibility of the script and must be closed when no longer needed.
 40 |         controller.enqueue(videoFrame, { transfer: [videoFrame] });
 41 |         // controller.enqueue was called, videoFrame is transferred.
 42 |         if (!(++frameCount % 30) && doLogging)
 43 |             doLogging(frameCount);
 44 |       } catch (e) {
 45 |         // In case of exception, let's make sure videoFrame is closed. This is a no-op if videoFrame was previously transferred.
 46 |         videoFrame.close();
 47 |         // If exception is unrecoverable, let's error the pipe.
 48 |         controller.error(e);
 49 |       }
 50 |     },
 51 |     readableType: 'owning',
 52 |     writableType: 'owning'
 53 |   });
 54 |   // Native transform is of type 'owning'
 55 |   const backgroundBlurTransform = new BackgroundBlurTransform();
 56 | 
 57 |   return videoFrameStream.pipeThrough(backgroundBlurTransform)
 58 |                          .pipeThrough(frameCountTransform);
 59 | }
 60 | ```
 61 | 
 62 | ## Goals
 63 | 
 64 | *   Permit `ReadableStream`, `WritableStream` and `TransformStream` objects to take ownership of chunks they manipulate.
 65 | *   Permit to build a safe and optimal video pipeline using `ReadableStream`, `WritableStream` and `TransformStream` objects that manipulate `VideoFrame` objects.
 66 | *   Permit both native and JavaScript-based streams of type 'owning'.
 67 | *   Permit to optimize streams pipelines of transferable chunks like `ArrayBuffer`, `RTCEncodedVideoFrame` or `RTCEncodedAudioFrame`.
 68 | *   Permit to tee a `ReadableStream` of `VideoFrame` objects without tight coupling between the teed branches.
 69 | 
 70 | ## Non-goals
 71 | 
 72 | *   Add support for transferring and closing of arbitrary JavaScript chunks.
 73 | 
 74 | ## Use cases
 75 | 
 76 | *   Performing realtime transformations of `VideoFrame` objects, for instance taking a camera `MediaStreamTrack` and applying
 77 |     a background blur effect as a `TransformStream` on each `VideoFrame` of the `MediaStreamTrack`.
 78 | 
 79 | ## End-user benefit
 80 | 
 81 | *   `VideoFrame` needs specific management and be closed as quickly as possible, without relying on garbage collection.
 82 |     This is important to not create hangs/stutters in the processing pipeline. By building support for safe patterns
 83 |     directly in streams, this will allow web developers to optimize `VideoFrame` management, and allow user experience
 84 |     to be more consistent accross devices.
 85 | 
 86 | ## Principles
 87 | 
 88 | The envisioned changes to the streams specification could look like the following:
 89 | *   Add a new 'owning' value that can be passed to `ReadableStream` type, `WritableStream` type and `TransformStream` readableType/writableType.
 90 |     For streams that do not use the 'owning' type, nothing changes.
 91 | *   Streams of the 'transfer' type can only manipulate chunks that are marked both as Transferable and Serializable.
 92 | *   If a chunk that is either not Transferable or not Serializable is enqueued or written, the chunk is ignored as if it was never enqueued/written.
 93 | *   If a Transferable and Serializable chunk is enqueueud/written in a 'owning' type `ReadableStreamDefaultController`, `TransformStreamDefaultController`
 94 |     or `WritableStreamDefaultWriter`, create a transferred version of the chunk using StructuredSerializeWithTransfer/StructuredDeserializeWithTransfer.
 95 |     Proceed with the regular stream algorithm by using the transferred chunk instead of the chunk itself.
 96 | *   Introduce a WhatWG streams 'close-able' concept. A chunk that is 'close-able' defines closing steps.
 97 |     For instance `VideoFrame` closing steps could be defined using https://www.w3.org/TR/webcodecs/#close-videoframe.
 98 |     `ArrayBuffer` closing steps could be defined using https://tc39.es/ecma262/#sec-detacharraybuffer.
 99 |     The 'close-able' steps should be a no-op on a transferred chunk.
100 | *   Execute the closing steps of a 'close-able' chunk for streams with the 'owning' type when resetting the queue of `ReadableStreamDefaultController`
101 |     or emptying `WritableStream`.[[writeRequests]] in case of abort/error.
102 | *   When calling tee() on a `ReadableStream` of the 'owning' type, call ReadableStream with cloneForBranch2 equal to true.
103 | *   To solve https://github.com/whatwg/streams/issues/1186, tee() on a `ReadableStream` of the 'owning' type can take a 'realtime' parameter.
104 |     When the 'realtime' parameter is used, chunks will be dropped on the branch that consumes more slowly to keep buffering limited to one chunk.
105 |     The closing steps should be called for any chunk that gets dropped in that situation.
106 | 
107 | ## Alternatives
108 | 
109 | *   It is difficult to emulate neutering/closing of chunks especially in case of teeing or aborting a stream.
110 | *   As discussed in https://github.com/whatwg/streams/issues/1155, lifetime management of chunks could potentially be done at the source level.
111 |     But this is difficult to make it work without introducing tight coupling between producers and consumers.
112 | *   The main alternative would be to design a VideoFrame specific API outside of WhatWG streams, which is feasible, as examplified by WebCodecs API.
113 | 
114 | ## Future Work
115 | 
116 | *   Evaluate what to do when enqueuing/writing a chunk that is not Transferable or not Serializable. We might want to reject the related promise without erroring the stream.
117 | *   Evaluate the usefulness of supporting Serializable but not Transferable chunks, we might just need to create a copy through serialization steps then explicitly call closeable steps on the chunk.
118 | *   Evaluate the usefulness of supporting Transferable but not Serializable chunks, in particular in how to handle `ReadableStream` tee().
119 |     If `ReadableStream` tee() exposes a parameter to enable structured cloning, it might sometimes fail with such chunks and we could piggy back on this behavior.
120 | *   Evaluate the usefulness of adding a `TransformStream` type to set readableType and writableType to the same value.
121 | *   Envision extending this support for arbitrary JavaScript chunks, for both transferring and explicit closing.
122 | *   Envision to introduce close-able concept in WebIDL.
123 | *   We might want to mention that, if we use detach steps for `ArrayBuffer`, implementations can directly deallocate the corresponding memory.
124 | 


--------------------------------------------------------------------------------
/transferable-streams-explainer.md:
--------------------------------------------------------------------------------
  1 | # Transferable Streams Explained
  2 | 
  3 | 
  4 | ## Introduction
  5 | 
  6 | The streams APIs provide ubiquitous, interoperable primitives for creating, composing, and consuming streams of data. A
  7 | natural thing to want to do with a stream is pass it off to a web worker. This provides a building block that is easy
  8 | and natural to use for offloading work onto another thread. Once you can speak the language of streams, you can use that
  9 | same language to make use of a Worker.
 10 | 
 11 | This work will permit streams to be transferred between workers, frames and anywhere else that `postMessage()` can be
 12 | used. Chunks can be anything which is cloneable by `postMessage()`.
 13 | 
 14 | Initially chunks enqueued in such a stream will always be cloned, ie. all data will be copied. Future work will extend
 15 | the Streams APIs to support transferring objects (ie. zero copy).
 16 | 
 17 | This is an example of JavaScript which will work once this is implemented:
 18 | 
 19 | In the main page:
 20 | 
 21 | ```javascript
 22 | const rs = new ReadableStream({
 23 |   start(controller) {
 24 |     controller.enqueue('hello');
 25 |   }
 26 | });
 27 | const w = new Worker('worker.js');
 28 | w.postMessage(rs, [rs]);
 29 | ```
 30 | 
 31 | In worker.js:
 32 | 
 33 | ```javascript
 34 | onmessage = async (evt) => {
 35 |   const rs = evt.data;
 36 |   const reader = rs.getReader();
 37 |   const {value, done} = await reader.read();
 38 |   console.log(value); // logs 'hello'.
 39 | };
 40 | ```
 41 | 
 42 | Note that `w.postMessage(rs)` would not work. Streams can only be _transferred_, not _cloned_. Attempts to clone a
 43 | stream will throw a DataCloneError.
 44 | 
 45 | Once a stream has been transferred with `postMessage()` the original stream is locked and cannot be read or written.
 46 | This is similar to how ArrayBuffers are neutered after they are transferred. However, the code of the underlying source
 47 | or sink is still running in the original context. The benefits to user experience can be seen in [this demo of streaming
 48 | digits of PI](https://glitch.com/edit/#!/streaming-pi?path=pi.js:1:0) (if you have a browser that supports transferable
 49 | streams, you can see it live at https://streaming-pi.glitch.me/).
 50 | 
 51 | Transferable streams are also useful in constructing responses for a service worker. See
 52 | https://gist.github.com/domenic/ea5ebedffcee27f552e103963cf8585c/ for an example.
 53 | 
 54 | ## Goals
 55 | 
 56 | *   Permit `ReadableStream`, `WritableStream` and `TransformStream` objects to be transferred between workers and other
 57 |     contexts where `postMessage()` can be used.
 58 | *   Provide a primitive that is amenable to future optimisations, for example automatic thread offload to bypass the
 59 |     main thread.
 60 | 
 61 | ## Non-goals
 62 | 
 63 | *   Avoiding cloning the chunks is not a goal at this stage; see "future work".
 64 | *   As such, Transfer-only objects (such as `ImageBitmap`) will not be supported yet; only serializable objects and the
 65 |     built-in types supported by the structured serialization algorithm.
 66 | *   A concise syntax for creating a `TransformStream` and a worker to run it on in one operation is not part of this
 67 |     work.
 68 | 
 69 | ## Use cases
 70 | 
 71 | *   Performing expensive transformations off the main thread. Transcoding, for example.
 72 | *   Synthesizing responses from a service worker. For example, generating a PDF from data in the DOM and streaming it to
 73 |     the service worker where it can then be downloaded as a file.
 74 | *   Processing a stream of data from an input device only accessible on the main thread. For example, you could use
 75 |     `MediaRecorder` to capture the audio of a user's microphone and/or the video of a user's camera, pipe the captured
 76 |     data through a off-thread `TransformStream` to transcode it into a hypothetical new experimental media format and
 77 |     then upload the resulting stream to a server.
 78 | *   Displaying a stream of data that is expensive to generate on a web page. Inverting the previous example: you
 79 |     download a stream of a video file encoded in an experimental media format, you transcode it to a natively supported
 80 |     format in a worker, and finally you transfer the resulting stream to the main thread to play back the video using
 81 |     `