From e62fabe9d2cfed5d87f94eb6962de550a1e25f0b Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Tue, 21 Jul 2026 21:55:02 -0700 Subject: [PATCH 1/4] Add optional WHATWG Streams polyfill Vendor the ES5 web-streams-polyfill 4.3.0 ponyfill and expose an idempotent Streams initializer that preserves constructors supplied by the selected JavaScript engine. Cover readable, writable, transform, BYOB, error, tee, subclassing, and host-constructor behavior with focused ports from WPT plus Firefox and Chromium regression tests. Validate the implementation on JavaScriptCore under ASan/UBSan and QuickJS Release. --- CMakeLists.txt | 1 + Polyfills/CMakeLists.txt | 6 +- Polyfills/Streams/CMakeLists.txt | 30 ++++ .../Include/Babylon/Polyfills/Streams.h | 11 ++ Polyfills/Streams/README.md | 14 ++ Polyfills/Streams/Source/Streams.cpp | 55 ++++++ Polyfills/Streams/Source/StreamsScripts.h.in | 36 ++++ .../ThirdParty/web-streams-polyfill/LICENSE | 22 +++ .../web-streams-polyfill/ponyfill.es5.js | 8 + Tests/UnitTests/CMakeLists.txt | 1 + Tests/UnitTests/Scripts/tests.ts | 156 ++++++++++++++++++ Tests/UnitTests/Shared/Shared.cpp | 26 +++ 12 files changed, 365 insertions(+), 1 deletion(-) create mode 100644 Polyfills/Streams/CMakeLists.txt create mode 100644 Polyfills/Streams/Include/Babylon/Polyfills/Streams.h create mode 100644 Polyfills/Streams/README.md create mode 100644 Polyfills/Streams/Source/Streams.cpp create mode 100644 Polyfills/Streams/Source/StreamsScripts.h.in create mode 100644 Polyfills/Streams/ThirdParty/web-streams-polyfill/LICENSE create mode 100644 Polyfills/Streams/ThirdParty/web-streams-polyfill/ponyfill.es5.js diff --git a/CMakeLists.txt b/CMakeLists.txt index 481e848f..0d27daf7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,7 @@ option(JSRUNTIMEHOST_POLYFILL_FILE "Include JsRuntimeHost Polyfill File and File option(JSRUNTIMEHOST_POLYFILL_PERFORMANCE "Include JsRuntimeHost Polyfill Performance." ON) option(JSRUNTIMEHOST_POLYFILL_TEXTDECODER "Include JsRuntimeHost Polyfill TextDecoder." ON) option(JSRUNTIMEHOST_POLYFILL_TEXTENCODER "Include JsRuntimeHost Polyfill TextEncoder." ON) +option(JSRUNTIMEHOST_POLYFILL_STREAMS "Include JsRuntimeHost Polyfill Web Streams." ON) # Sanitizers option(ENABLE_SANITIZERS "Enable AddressSanitizer and UBSan" OFF) diff --git a/Polyfills/CMakeLists.txt b/Polyfills/CMakeLists.txt index a44765fb..c952cf27 100644 --- a/Polyfills/CMakeLists.txt +++ b/Polyfills/CMakeLists.txt @@ -44,4 +44,8 @@ endif() if(JSRUNTIMEHOST_POLYFILL_TEXTENCODER) add_subdirectory(TextEncoder) -endif() \ No newline at end of file +endif() + +if(JSRUNTIMEHOST_POLYFILL_STREAMS) + add_subdirectory(Streams) +endif() diff --git a/Polyfills/Streams/CMakeLists.txt b/Polyfills/Streams/CMakeLists.txt new file mode 100644 index 00000000..6c00ed81 --- /dev/null +++ b/Polyfills/Streams/CMakeLists.txt @@ -0,0 +1,30 @@ +set(WEB_STREAMS_POLYFILL_FILE + "${CMAKE_CURRENT_SOURCE_DIR}/ThirdParty/web-streams-polyfill/ponyfill.es5.js") +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS + "${WEB_STREAMS_POLYFILL_FILE}") +file(READ "${WEB_STREAMS_POLYFILL_FILE}" WEB_STREAMS_POLYFILL_SOURCE) +string(SUBSTRING "${WEB_STREAMS_POLYFILL_SOURCE}" 0 60000 WEB_STREAMS_POLYFILL_SOURCE_1) +string(SUBSTRING "${WEB_STREAMS_POLYFILL_SOURCE}" 60000 -1 WEB_STREAMS_POLYFILL_SOURCE_2) +configure_file( + "Source/StreamsScripts.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/Generated/StreamsScripts.h" + @ONLY) + +set(SOURCES + "Include/Babylon/Polyfills/Streams.h" + "Source/Streams.cpp" + "Source/StreamsScripts.h.in" + "ThirdParty/web-streams-polyfill/LICENSE" + "ThirdParty/web-streams-polyfill/ponyfill.es5.js") + +add_library(Streams ${SOURCES}) +warnings_as_errors(Streams) + +target_include_directories(Streams + PUBLIC "Include" + PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/Generated") + +target_link_libraries(Streams PUBLIC JsRuntime) + +set_property(TARGET Streams PROPERTY FOLDER Polyfills) +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES}) diff --git a/Polyfills/Streams/Include/Babylon/Polyfills/Streams.h b/Polyfills/Streams/Include/Babylon/Polyfills/Streams.h new file mode 100644 index 00000000..01e5a15c --- /dev/null +++ b/Polyfills/Streams/Include/Babylon/Polyfills/Streams.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +namespace Babylon::Polyfills::Streams +{ + // Installs the WHATWG Streams constructors that are not already supplied + // by the JavaScript engine. + void BABYLON_API Initialize(Napi::Env env); +} diff --git a/Polyfills/Streams/README.md b/Polyfills/Streams/README.md new file mode 100644 index 00000000..c84a38e3 --- /dev/null +++ b/Polyfills/Streams/README.md @@ -0,0 +1,14 @@ +# Web Streams + +Installs the standard `ReadableStream`, `WritableStream`, `TransformStream`, +reader, writer, controller, and queuing-strategy globals when the selected +JavaScript engine does not provide them. + +The implementation is the ES5 ponyfill bundle from +[`web-streams-polyfill` 4.3.0](https://github.com/MattiasBuelens/web-streams-polyfill/tree/add69059ed08eae6a18559aba49575a280d1529e), +which is based on the WHATWG reference implementation. The vendored project +tests this release against the Streams portion of WPT at revision +[`c05b4473`](https://github.com/web-platform-tests/wpt/tree/c05b447326585237713013c66341eab2cdf967b6/streams). + +`Initialize` preserves any Streams constructors already supplied by the host +engine and fills only missing globals. diff --git a/Polyfills/Streams/Source/Streams.cpp b/Polyfills/Streams/Source/Streams.cpp new file mode 100644 index 00000000..4552ed07 --- /dev/null +++ b/Polyfills/Streams/Source/Streams.cpp @@ -0,0 +1,55 @@ +#include + +#include "StreamsScripts.h" + +#include +#include + +namespace Babylon::Polyfills::Streams +{ + void BABYLON_API Initialize(Napi::Env env) + { + Napi::HandleScope scope{env}; + auto global = env.Global(); + + static constexpr std::array constructorNames{ + "ReadableStream", + "ReadableStreamDefaultController", + "ReadableByteStreamController", + "ReadableStreamBYOBRequest", + "ReadableStreamDefaultReader", + "ReadableStreamBYOBReader", + "WritableStream", + "WritableStreamDefaultController", + "WritableStreamDefaultWriter", + "ByteLengthQueuingStrategy", + "CountQueuingStrategy", + "TransformStream", + "TransformStreamDefaultController", + }; + + bool needsPonyfill{}; + for (const auto name : constructorNames) + { + if (global.Get(name.data()).IsUndefined()) + { + needsPonyfill = true; + break; + } + } + + if (!needsPonyfill) + { + return; + } + + const auto exports = Napi::Eval(env, Internal::StreamsScripts::Ponyfill.data(), "jsruntimehost://web-streams-polyfill.js").As(); + for (const auto name : constructorNames) + { + if (global.Get(name.data()).IsUndefined()) + { + global.Set(name.data(), exports.Get(name.data())); + } + } + } +} diff --git a/Polyfills/Streams/Source/StreamsScripts.h.in b/Polyfills/Streams/Source/StreamsScripts.h.in new file mode 100644 index 00000000..9bf7f5bf --- /dev/null +++ b/Polyfills/Streams/Source/StreamsScripts.h.in @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +namespace Babylon::Polyfills::Internal::StreamsScripts +{ + inline constexpr char PonyfillPart1[] = R"JSRHSTREAM( +(function() { + var exports = {}; + var module = { exports: exports }; +@WEB_STREAMS_POLYFILL_SOURCE_1@ +)JSRHSTREAM"; + + inline constexpr char PonyfillPart2[] = R"JSRHSTREAM(@WEB_STREAMS_POLYFILL_SOURCE_2@ + return module.exports; +})() +)JSRHSTREAM"; + + template + consteval auto Join(const char (&part1)[Part1Size], const char (&part2)[Part2Size]) + { + std::array result{}; + for (std::size_t index{}; index < Part1Size - 1; ++index) + { + result[index] = part1[index]; + } + for (std::size_t index{}; index < Part2Size; ++index) + { + result[Part1Size - 1 + index] = part2[index]; + } + return result; + } + + inline constexpr auto Ponyfill = Join(PonyfillPart1, PonyfillPart2); +} diff --git a/Polyfills/Streams/ThirdParty/web-streams-polyfill/LICENSE b/Polyfills/Streams/ThirdParty/web-streams-polyfill/LICENSE new file mode 100644 index 00000000..7adbcf57 --- /dev/null +++ b/Polyfills/Streams/ThirdParty/web-streams-polyfill/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2026 Mattias Buelens +Copyright (c) 2016 Diwank Singh Tomer + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. diff --git a/Polyfills/Streams/ThirdParty/web-streams-polyfill/ponyfill.es5.js b/Polyfills/Streams/ThirdParty/web-streams-polyfill/ponyfill.es5.js new file mode 100644 index 00000000..18d63683 --- /dev/null +++ b/Polyfills/Streams/ThirdParty/web-streams-polyfill/ponyfill.es5.js @@ -0,0 +1,8 @@ +/** + * @license + * web-streams-polyfill v4.3.0 + * Copyright 2026 Mattias Buelens, Diwank Singh Tomer and other contributors. + * This code is released under the MIT license. + * SPDX-License-Identifier: MIT + */ +!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports):"function"==typeof define&&define.amd?define(["exports"],r):r((e="undefined"!=typeof globalThis?globalThis:e||self).WebStreamsPolyfill={})}(this,function(e){"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?Symbol:function(e){return"Symbol(".concat(e,")")};function t(){}function o(e){return"object"==typeof e&&null!==e||"function"==typeof e}"function"==typeof SuppressedError&&SuppressedError;var n=t;function i(e,r){try{Object.defineProperty(e,"name",{value:r,configurable:!0})}catch(e){}}var a=Promise,u=Promise.resolve.bind(a),l=Promise.prototype.then,s=Promise.reject.bind(a),c=u;function f(e){return new a(e)}function d(e){return f(function(r){return r(e)})}function p(e){return s(e)}function b(e,r,t){return l.call(e,r,t)}function h(e,r,t){b(b(e,r,t),void 0,n)}function _(e,r){h(e,r)}function m(e,r){h(e,void 0,r)}function v(e,r,t){return b(e,r,t)}function y(e){b(e,void 0,n)}var g=function(e){if("function"==typeof queueMicrotask)g=queueMicrotask;else{var r=d(void 0);g=function(e){return b(r,e)}}return g(e)};function S(e,r,t){if("function"!=typeof e)throw new TypeError("Argument is not a function");return Function.prototype.apply.call(e,r,t)}function w(e,r,t){try{return d(S(e,r,t))}catch(e){return p(e)}}var R=function(){function e(){this._cursor=0,this._size=0,this._front={_elements:[],_next:void 0},this._back=this._front,this._cursor=0,this._size=0}return Object.defineProperty(e.prototype,"length",{get:function(){return this._size},enumerable:!1,configurable:!0}),e.prototype.push=function(e){var r=this._back,t=r;16383===r._elements.length&&(t={_elements:[],_next:void 0}),r._elements.push(e),t!==r&&(this._back=t,r._next=t),++this._size},e.prototype.shift=function(){var e=this._front,r=e,t=this._cursor,o=t+1,n=e._elements,i=n[t];return 16384===o&&(r=e._next,o=0),--this._size,this._cursor=o,e!==r&&(this._front=r),n[t]=void 0,i},e.prototype.forEach=function(e){for(var r=this._cursor,t=this._front,o=t._elements;!(r===o.length&&void 0===t._next||r===o.length&&(r=0,0===(o=(t=t._next)._elements).length));)e(o[r]),++r},e.prototype.peek=function(){var e=this._front,r=this._cursor;return e._elements[r]},e}(),T=r("[[AbortSteps]]"),P=r("[[ErrorSteps]]"),C=r("[[CancelSteps]]"),q=r("[[PullSteps]]"),E=r("[[CanPullSyncSteps]]"),W=r("[[ReleaseSteps]]");function O(e,r){e._ownerReadableStream=r,r._reader=e,"readable"===r._state?A(e):"closed"===r._state?function(e){A(e),F(e)}(e):z(e,r._storedError)}function j(e,r){return eo(e._ownerReadableStream,r)}function B(e){var r=e._ownerReadableStream;"readable"===r._state?D(e,new TypeError("Reader was released and can no longer be used to monitor the stream's closedness")):function(e,r){z(e,r)}(e,new TypeError("Reader was released and can no longer be used to monitor the stream's closedness")),r._readableStreamController[W](),r._reader=void 0,e._ownerReadableStream=void 0}function k(e){return new TypeError("Cannot "+e+" a stream using a released reader")}function A(e){e._closedPromise=f(function(r,t){e._closedPromise_resolve=r,e._closedPromise_reject=t})}function z(e,r){A(e),D(e,r)}function D(e,r){void 0!==e._closedPromise_reject&&(y(e._closedPromise),e._closedPromise_reject(r),e._closedPromise_resolve=void 0,e._closedPromise_reject=void 0)}function F(e){void 0!==e._closedPromise_resolve&&(e._closedPromise_resolve(void 0),e._closedPromise_resolve=void 0,e._closedPromise_reject=void 0)}var L=Number.isFinite||function(e){return"number"==typeof e&&isFinite(e)},I=Math.trunc||function(e){return e<0?Math.ceil(e):Math.floor(e)};function M(e,r){if(void 0!==e&&("object"!=typeof(t=e)&&"function"!=typeof t))throw new TypeError("".concat(r," is not an object."));var t}function x(e,r){if("function"!=typeof e)throw new TypeError("".concat(r," is not a function."))}function Y(e,r){if(!function(e){return"object"==typeof e&&null!==e||"function"==typeof e}(e))throw new TypeError("".concat(r," is not an object."))}function Q(e,r,t){if(void 0===e)throw new TypeError("Parameter ".concat(r," is required in '").concat(t,"'."))}function N(e,r,t){if(void 0===e)throw new TypeError("".concat(r," is required in '").concat(t,"'."))}function H(e){return Number(e)}function V(e){return 0===e?0:e}function U(e,r){var t=Number.MAX_SAFE_INTEGER,o=Number(e);if(o=V(o),!L(o))throw new TypeError("".concat(r," is not a finite number"));if((o=function(e){return V(I(e))}(o))<0||o>t)throw new TypeError("".concat(r," is outside the accepted range of ").concat(0," to ").concat(t,", inclusive"));return L(o)&&0!==o?o:0}function G(e,r){if(!Zt(e))throw new TypeError("".concat(r," is not a ReadableStream."))}function X(e){return new ee(e)}function J(e,r){e._reader._readRequests.push(r)}function K(e,r,t){var o=e._reader._readRequests.shift();t?o._closeSteps():o._chunkSteps(r)}function Z(e){return e._reader._readRequests.length}function $(e){var r=e._reader;return void 0!==r&&!!ae(r)}var ee=function(){function ReadableStreamDefaultReader(e){if(Q(e,1,"ReadableStreamDefaultReader"),G(e,"First parameter"),$t(e))throw new TypeError("This stream has already been locked for exclusive reading by another reader");O(this,e),this._readRequests=new R}return Object.defineProperty(ReadableStreamDefaultReader.prototype,"closed",{get:function(){return ae(this)?this._closedPromise:p(ce("closed"))},enumerable:!1,configurable:!0}),ReadableStreamDefaultReader.prototype.cancel=function(e){return void 0===e&&(e=void 0),ae(this)?void 0===this._ownerReadableStream?p(k("cancel")):j(this,e):p(ce("cancel"))},ReadableStreamDefaultReader.prototype.read=function(){if(!ae(this))return p(ce("read"));if(void 0===this._ownerReadableStream)return p(k("read from"));var e=le(this)?new ie:new ne;return ue(this,e),e._promise},ReadableStreamDefaultReader.prototype.releaseLock=function(){if(!ae(this))throw ce("releaseLock");void 0!==this._ownerReadableStream&&function(e){B(e);var r=new TypeError("Reader was released");se(e,r)}(this)},ReadableStreamDefaultReader}();Object.defineProperties(ee.prototype,{cancel:{enumerable:!0},read:{enumerable:!0},releaseLock:{enumerable:!0},closed:{enumerable:!0}}),i(ee.prototype.cancel,"cancel"),i(ee.prototype.read,"read"),i(ee.prototype.releaseLock,"releaseLock"),"symbol"==typeof r.toStringTag&&Object.defineProperty(ee.prototype,r.toStringTag,{value:"ReadableStreamDefaultReader",configurable:!0});var re,te,oe,ne=function(){function e(){var e=this;this._promise=f(function(r,t){e._resolvePromise=r,e._rejectPromise=t})}return e.prototype._chunkSteps=function(e){this._resolvePromise({value:e,done:!1})},e.prototype._closeSteps=function(){this._resolvePromise({value:void 0,done:!0})},e.prototype._errorSteps=function(e){this._rejectPromise(e)},e}(),ie=function(){function e(){this._promise=void 0}return e.prototype._chunkSteps=function(e){this._promise=c({value:e,done:!1})},e.prototype._closeSteps=function(){this._promise=c({value:void 0,done:!0})},e.prototype._errorSteps=function(e){this._promise=p(e)},e}();function ae(e){return!!o(e)&&(!!Object.prototype.hasOwnProperty.call(e,"_readRequests")&&e instanceof ee)}function ue(e,r){var t=e._ownerReadableStream;t._disturbed=!0,"closed"===t._state?r._closeSteps():"errored"===t._state?r._errorSteps(t._storedError):t._readableStreamController[q](r)}function le(e){var r=e._ownerReadableStream;return"closed"===r._state||("errored"===r._state||r._readableStreamController[E]())}function se(e,r){var t=e._readRequests;e._readRequests=new R,t.forEach(function(e){e._errorSteps(r)})}function ce(e){return new TypeError("ReadableStreamDefaultReader.prototype.".concat(e," can only be used on a ReadableStreamDefaultReader"))}function fe(e){return e.slice()}function de(e,r,t,o,n){new Uint8Array(e).set(new Uint8Array(t,o,n),r)}var pe=function(e){return(pe="function"==typeof e.transfer?function(e){return e.transfer()}:"function"==typeof structuredClone?function(e){return structuredClone(e,{transfer:[e]})}:function(e){return e})(e)},be=function(e){return(be="boolean"==typeof e.detached?function(e){return e.detached}:function(e){return 0===e.byteLength})(e)};function he(e,r,t){if(e.slice)return e.slice(r,t);var o=t-r,n=new ArrayBuffer(o);return de(n,0,e,r,o),n}function _e(e,r){var t=e[r];if(null!=t){if("function"!=typeof t)throw new TypeError("".concat(String(r)," is not a function"));return t}}function me(e){try{var r=e.done,t=e.value;return b(c(t),function(e){return{done:r,value:e}})}catch(e){return p(e)}}var ve,ye=null!==(oe=null!==(re=r.asyncIterator)&&void 0!==re?re:null===(te=r.for)||void 0===te?void 0:te.call(r,"Symbol.asyncIterator"))&&void 0!==oe?oe:"@@asyncIterator";function ge(e,t,n){if(void 0===t&&(t="sync"),void 0===n)if("async"===t){if(void 0===(n=_e(e,ye)))return function(e){var r={next:function(){var r;try{r=Se(e)}catch(e){return p(e)}return me(r)},return:function(r){var t;try{var n=_e(e.iterator,"return");if(void 0===n)return d({done:!0,value:r});t=S(n,e.iterator,[r])}catch(e){return p(e)}return o(t)?me(t):p(new TypeError("The iterator.return() method must return an object"))}};return{iterator:r,nextMethod:r.next,done:!1}}(ge(e,"sync",_e(e,r.iterator)))}else n=_e(e,r.iterator);if(void 0===n)throw new TypeError("The object is not iterable");var i=S(n,e,[]);if(!o(i))throw new TypeError("The iterator method must return an object");return{iterator:i,nextMethod:i.next,done:!1}}function Se(e){var r=S(e.nextMethod,e.iterator,[]);if(!o(r))throw new TypeError("The iterator.next() method must return an object");return r}var we=function(){function e(e,r){this._ongoingPromise=void 0,this._isFinished=!1,this._reader=e,this._preventCancel=r}return e.prototype.next=function(){var e=this,r=function(){return e._nextSteps()};return this._ongoingPromise=this._ongoingPromise?v(this._ongoingPromise,r,r):r(),this._ongoingPromise},e.prototype.return=function(e){var r=this,t=function(){return r._returnSteps(e)};return this._ongoingPromise=this._ongoingPromise?v(this._ongoingPromise,t,t):t(),this._ongoingPromise},e.prototype._nextSteps=function(){if(this._isFinished)return Promise.resolve({value:void 0,done:!0});var e=this._reader,r=new Re(this);return ue(e,r),r._promise},e.prototype._returnSteps=function(e){if(this._isFinished)return Promise.resolve({value:e,done:!0});this._isFinished=!0;var r=this._reader;if(!this._preventCancel){var t=j(r,e);return B(r),v(t,function(){return{value:e,done:!0}})}return B(r),d({value:e,done:!0})},e}(),Re=function(){function e(e){var r=this;this._iterator=e,this._promise=f(function(e,t){r._resolvePromise=e,r._rejectPromise=t})}return e.prototype._chunkSteps=function(e){var r=this;this._iterator._ongoingPromise=void 0,g(function(){return r._resolvePromise({value:e,done:!1})})},e.prototype._closeSteps=function(){var e=this._iterator;e._ongoingPromise=void 0,e._isFinished=!0,B(e._reader),this._resolvePromise({value:void 0,done:!0})},e.prototype._errorSteps=function(e){var r=this._iterator;r._ongoingPromise=void 0,r._isFinished=!0,B(r._reader),this._rejectPromise(e)},e}(),Te=((ve={next:function(){return Pe(this)?this._asyncIteratorImpl.next():p(Ce("next"))},return:function(e){return Pe(this)?this._asyncIteratorImpl.return(e):p(Ce("return"))}})[ye]=function(){return this},ve);function Pe(e){if(!o(e))return!1;if(!Object.prototype.hasOwnProperty.call(e,"_asyncIteratorImpl"))return!1;try{return e._asyncIteratorImpl instanceof we}catch(e){return!1}}function Ce(e){return new TypeError("ReadableStreamAsyncIterator.".concat(e," can only be used on a ReadableSteamAsyncIterator"))}Object.defineProperty(Te,ye,{enumerable:!1});var qe=Number.isNaN||function(e){return e!=e};function Ee(e){var r=he(e.buffer,e.byteOffset,e.byteOffset+e.byteLength);return new Uint8Array(r)}function We(e){var r=e._queue.shift();return e._queueTotalSize-=r.size,e._queueTotalSize<0&&(e._queueTotalSize=0),r.value}function Oe(e,r,t){if("number"!=typeof(o=t)||qe(o)||o<0||t===1/0)throw new RangeError("Size must be a finite, non-NaN, non-negative number.");var o;e._queue.push({value:r,size:t}),e._queueTotalSize+=t}function je(e){e._queue=new R,e._queueTotalSize=0}function Be(e){return e===DataView}function ke(e){return Be(e)?1:e.BYTES_PER_ELEMENT}var Ae=function(){function ReadableStreamBYOBRequest(){throw new TypeError("Illegal constructor")}return Object.defineProperty(ReadableStreamBYOBRequest.prototype,"view",{get:function(){if(!Fe(this))throw sr("view");return this._view},enumerable:!1,configurable:!0}),ReadableStreamBYOBRequest.prototype.respond=function(e){if(!Fe(this))throw sr("respond");if(Q(e,1,"respond"),e=U(e,"First parameter"),void 0===this._associatedReadableByteStreamController)throw new TypeError("This BYOB request has been invalidated");if(be(this._view.buffer))throw new TypeError("The BYOB request's buffer has been detached and so cannot be used as a response");ar(this._associatedReadableByteStreamController,e)},ReadableStreamBYOBRequest.prototype.respondWithNewView=function(e){if(!Fe(this))throw sr("respondWithNewView");if(Q(e,1,"respondWithNewView"),!ArrayBuffer.isView(e))throw new TypeError("You can only respond with array buffer views");if(void 0===this._associatedReadableByteStreamController)throw new TypeError("This BYOB request has been invalidated");if(be(e.buffer))throw new TypeError("The given view's buffer has been detached and so cannot be used as a response");ur(this._associatedReadableByteStreamController,e)},ReadableStreamBYOBRequest}();Object.defineProperties(Ae.prototype,{respond:{enumerable:!0},respondWithNewView:{enumerable:!0},view:{enumerable:!0}}),i(Ae.prototype.respond,"respond"),i(Ae.prototype.respondWithNewView,"respondWithNewView"),"symbol"==typeof r.toStringTag&&Object.defineProperty(Ae.prototype,r.toStringTag,{value:"ReadableStreamBYOBRequest",configurable:!0});var ze=function(){function ReadableByteStreamController(){throw new TypeError("Illegal constructor")}return Object.defineProperty(ReadableByteStreamController.prototype,"byobRequest",{get:function(){if(!De(this))throw cr("byobRequest");return nr(this)},enumerable:!1,configurable:!0}),Object.defineProperty(ReadableByteStreamController.prototype,"desiredSize",{get:function(){if(!De(this))throw cr("desiredSize");return ir(this)},enumerable:!1,configurable:!0}),ReadableByteStreamController.prototype.close=function(){if(!De(this))throw cr("close");if(this._closeRequested)throw new TypeError("The stream has already been closed; do not close it again!");var e=this._controlledReadableByteStream._state;if("readable"!==e)throw new TypeError("The stream (in ".concat(e," state) is not in the readable state and cannot be closed"));er(this)},ReadableByteStreamController.prototype.enqueue=function(e){if(!De(this))throw cr("enqueue");if(Q(e,1,"enqueue"),!ArrayBuffer.isView(e))throw new TypeError("chunk must be an array buffer view");if(0===e.byteLength)throw new TypeError("chunk must have non-zero byteLength");if(0===e.buffer.byteLength)throw new TypeError("chunk's buffer must have non-zero byteLength");if(this._closeRequested)throw new TypeError("stream is closed or draining");var r=this._controlledReadableByteStream._state;if("readable"!==r)throw new TypeError("The stream (in ".concat(r," state) is not in the readable state and cannot be enqueued to"));rr(this,e)},ReadableByteStreamController.prototype.error=function(e){if(void 0===e&&(e=void 0),!De(this))throw cr("error");tr(this,e)},ReadableByteStreamController.prototype[C]=function(e){Ie(this),je(this);var r=this._cancelAlgorithm(e);return $e(this),r},ReadableByteStreamController.prototype[q]=function(e){var r=this._controlledReadableByteStream;if(this._queueTotalSize>0)or(this,e);else{var t=this._autoAllocateChunkSize;if(void 0!==t){var o=void 0;try{o=new ArrayBuffer(t)}catch(r){return void e._errorSteps(r)}var n={buffer:o,bufferByteLength:t,byteOffset:0,byteLength:t,bytesFilled:0,minimumFill:1,elementSize:1,viewConstructor:Uint8Array,readerType:"default"};this._pendingPullIntos.push(n)}J(r,e),Le(this)}},ReadableByteStreamController.prototype[E]=function(){return this._queueTotalSize>0},ReadableByteStreamController.prototype[W]=function(){if(this._pendingPullIntos.length>0){var e=this._pendingPullIntos.peek();e.readerType="none",this._pendingPullIntos=new R,this._pendingPullIntos.push(e)}},ReadableByteStreamController}();function De(e){return!!o(e)&&(!!Object.prototype.hasOwnProperty.call(e,"_controlledReadableByteStream")&&e instanceof ze)}function Fe(e){return!!o(e)&&(!!Object.prototype.hasOwnProperty.call(e,"_associatedReadableByteStreamController")&&e instanceof Ae)}function Le(e){var r=function(e){var r=e._controlledReadableByteStream;if("readable"!==r._state)return!1;if(e._closeRequested)return!1;if(!e._started)return!1;if($(r)&&Z(r)>0)return!0;if(hr(r)&&br(r)>0)return!0;var t=ir(e);if(t>0)return!0;return!1}(e);r&&(e._pulling?e._pullAgain=!0:(e._pulling=!0,h(e._pullAlgorithm(),function(){return e._pulling=!1,e._pullAgain&&(e._pullAgain=!1,Le(e)),null},function(r){return tr(e,r),null})))}function Ie(e){Xe(e),e._pendingPullIntos=new R}function Me(e,r){var t=!1;"closed"===e._state&&(t=!0);var o=Ye(r);"default"===r.readerType?K(e,o,t):function(e,r,t){var o=e._reader,n=o._readIntoRequests.shift();t?n._closeSteps(r):n._chunkSteps(r)}(e,o,t)}function xe(e,r){for(var t=0;t0&&Ne(e,r.buffer,r.byteOffset,r.bytesFilled),Ze(e)}function Ve(e,r){var t=Math.min(e._queueTotalSize,r.byteLength-r.bytesFilled),o=r.bytesFilled+t,n=t,i=!1,a=o-o%r.elementSize;a>=r.minimumFill&&(n=a-r.bytesFilled,i=!0);for(var u=e._queue;n>0;){var l=u.peek(),s=Math.min(n,l.byteLength),c=r.byteOffset+r.bytesFilled;de(r.buffer,c,l.buffer,l.byteOffset,s),l.byteLength===s?u.shift():(l.byteOffset+=s,l.byteLength-=s),e._queueTotalSize-=s,Ue(e,s,r),n-=s}return i}function Ue(e,r,t){t.bytesFilled+=r}function Ge(e){0===e._queueTotalSize&&e._closeRequested?($e(e),ro(e._controlledReadableByteStream)):Le(e)}function Xe(e){null!==e._byobRequest&&(e._byobRequest._associatedReadableByteStreamController=void 0,e._byobRequest._view=null,e._byobRequest=null)}function Je(e){for(var r=[];e._pendingPullIntos.length>0&&0!==e._queueTotalSize;){var t=e._pendingPullIntos.peek();Ve(e,t)&&(Ze(e),r.push(t))}return r}function Ke(e,r){var t=e._pendingPullIntos.peek();Xe(e),"closed"===e._controlledReadableByteStream._state?function(e,r){"none"===r.readerType&&Ze(e);var t=e._controlledReadableByteStream;if(hr(t)){for(var o=[];o.length0){var n=t.byteOffset+t.bytesFilled;Ne(e,t.buffer,n-o,o)}t.bytesFilled-=o;var i=Je(e);Me(e._controlledReadableByteStream,t),xe(e._controlledReadableByteStream,i)}}else{He(e,t);var a=Je(e);xe(e._controlledReadableByteStream,a)}}(e,r,t),Le(e)}function Ze(e){return e._pendingPullIntos.shift()}function $e(e){e._pullAlgorithm=void 0,e._cancelAlgorithm=void 0}function er(e){var r=e._controlledReadableByteStream;if(!e._closeRequested&&"readable"===r._state)if(e._queueTotalSize>0)e._closeRequested=!0;else{if(e._pendingPullIntos.length>0){var t=e._pendingPullIntos.peek();if(t.bytesFilled%t.elementSize!==0){var o=new TypeError("Insufficient bytes to fill elements in the given buffer");throw tr(e,o),o}}$e(e),ro(r)}}function rr(e,r){var t=e._controlledReadableByteStream;if(!e._closeRequested&&"readable"===t._state){var o=r.buffer,n=r.byteOffset,i=r.byteLength;if(be(o))throw new TypeError("chunk's buffer is detached and so cannot be enqueued");var a=pe(o);if(e._pendingPullIntos.length>0){var u=e._pendingPullIntos.peek();if(be(u.buffer))throw new TypeError("The BYOB request's buffer has been detached and so cannot be filled with an enqueued chunk");Xe(e),u.buffer=pe(u.buffer),"none"===u.readerType&&He(e,u)}if($(t))if(function(e){for(var r=e._controlledReadableByteStream._reader;r._readRequests.length>0;){if(0===e._queueTotalSize)return;or(e,r._readRequests.shift())}}(e),0===Z(t))Qe(e,a,n,i);else e._pendingPullIntos.length>0&&Ze(e),K(t,new Uint8Array(a,n,i),!1);else if(hr(t)){Qe(e,a,n,i),xe(t,Je(e))}else Qe(e,a,n,i);Le(e)}}function tr(e,r){var t=e._controlledReadableByteStream;"readable"===t._state&&(Ie(e),je(e),$e(e),to(t,r))}function or(e,r){var t=e._queue.shift();e._queueTotalSize-=t.byteLength,Ge(e);var o=new Uint8Array(t.buffer,t.byteOffset,t.byteLength);r._chunkSteps(o)}function nr(e){if(null===e._byobRequest&&e._pendingPullIntos.length>0){var r=e._pendingPullIntos.peek(),t=new Uint8Array(r.buffer,r.byteOffset+r.bytesFilled,r.byteLength-r.bytesFilled),o=Object.create(Ae.prototype);!function(e,r,t){e._associatedReadableByteStreamController=r,e._view=t}(o,e,t),e._byobRequest=o}return e._byobRequest}function ir(e){var r=e._controlledReadableByteStream._state;return"errored"===r?null:"closed"===r?0:e._strategyHWM-e._queueTotalSize}function ar(e,r){var t=e._pendingPullIntos.peek();if("closed"===e._controlledReadableByteStream._state){if(0!==r)throw new TypeError("bytesWritten must be 0 when calling respond() on a closed stream")}else{if(0===r)throw new TypeError("bytesWritten must be greater than 0 when calling respond() on a readable stream");if(t.bytesFilled+r>t.byteLength)throw new RangeError("bytesWritten out of range")}t.buffer=pe(t.buffer),Ke(e,r)}function ur(e,r){var t=e._pendingPullIntos.peek();if("closed"===e._controlledReadableByteStream._state){if(0!==r.byteLength)throw new TypeError("The view's length must be 0 when calling respondWithNewView() on a closed stream")}else if(0===r.byteLength)throw new TypeError("The view's length must be greater than 0 when calling respondWithNewView() on a readable stream");if(t.byteOffset+t.bytesFilled!==r.byteOffset)throw new RangeError("The region specified by view does not match byobRequest");if(t.bufferByteLength!==r.buffer.byteLength)throw new RangeError("The buffer of view has different capacity than byobRequest");if(t.bytesFilled+r.byteLength>t.byteLength)throw new RangeError("The region specified by view is larger than byobRequest");var o=r.byteLength;t.buffer=pe(r.buffer),Ke(e,o)}function lr(e,r,t,o,n,i,a){r._controlledReadableByteStream=e,r._pullAgain=!1,r._pulling=!1,r._byobRequest=null,r._queue=r._queueTotalSize=void 0,je(r),r._closeRequested=!1,r._started=!1,r._strategyHWM=i,r._pullAlgorithm=o,r._cancelAlgorithm=n,r._autoAllocateChunkSize=a,r._pendingPullIntos=new R,e._readableStreamController=r,h(d(t()),function(){return r._started=!0,Le(r),null},function(e){return tr(r,e),null})}function sr(e){return new TypeError("ReadableStreamBYOBRequest.prototype.".concat(e," can only be used on a ReadableStreamBYOBRequest"))}function cr(e){return new TypeError("ReadableByteStreamController.prototype.".concat(e," can only be used on a ReadableByteStreamController"))}function fr(e,r){if("byob"!==(e="".concat(e)))throw new TypeError("".concat(r," '").concat(e,"' is not a valid enumeration value for ReadableStreamReaderMode"));return e}function dr(e){return new _r(e)}function pr(e,r){e._reader._readIntoRequests.push(r)}function br(e){return e._reader._readIntoRequests.length}function hr(e){var r=e._reader;return void 0!==r&&!!yr(r)}Object.defineProperties(ze.prototype,{close:{enumerable:!0},enqueue:{enumerable:!0},error:{enumerable:!0},byobRequest:{enumerable:!0},desiredSize:{enumerable:!0}}),i(ze.prototype.close,"close"),i(ze.prototype.enqueue,"enqueue"),i(ze.prototype.error,"error"),"symbol"==typeof r.toStringTag&&Object.defineProperty(ze.prototype,r.toStringTag,{value:"ReadableByteStreamController",configurable:!0});var _r=function(){function ReadableStreamBYOBReader(e){if(Q(e,1,"ReadableStreamBYOBReader"),G(e,"First parameter"),$t(e))throw new TypeError("This stream has already been locked for exclusive reading by another reader");if(!De(e._readableStreamController))throw new TypeError("Cannot construct a ReadableStreamBYOBReader for a stream not constructed with a byte source");O(this,e),this._readIntoRequests=new R}return Object.defineProperty(ReadableStreamBYOBReader.prototype,"closed",{get:function(){return yr(this)?this._closedPromise:p(wr("closed"))},enumerable:!1,configurable:!0}),ReadableStreamBYOBReader.prototype.cancel=function(e){return void 0===e&&(e=void 0),yr(this)?void 0===this._ownerReadableStream?p(k("cancel")):j(this,e):p(wr("cancel"))},ReadableStreamBYOBReader.prototype.read=function(e,r){if(void 0===r&&(r={}),!yr(this))return p(wr("read"));if(!ArrayBuffer.isView(e))return p(new TypeError("view must be an array buffer view"));if(0===e.byteLength)return p(new TypeError("view must have non-zero byteLength"));if(0===e.buffer.byteLength)return p(new TypeError("view's buffer must have non-zero byteLength"));if(be(e.buffer))return p(new TypeError("view's buffer has been detached"));var t;try{t=function(e,r){var t;return M(e,r),{min:U(null!==(t=null==e?void 0:e.min)&&void 0!==t?t:1,"".concat(r," has member 'min' that"))}}(r,"options")}catch(e){return p(e)}var o=t.min;if(0===o)return p(new TypeError("options.min must be greater than 0"));if(function(e){return Be(e.constructor)}(e)){if(o>e.byteLength)return p(new RangeError("options.min must be less than or equal to view's byteLength"))}else if(o>e.length)return p(new RangeError("options.min must be less than or equal to view's length"));if(void 0===this._ownerReadableStream)return p(k("read from"));var n=function(e,r,t){var o=e._ownerReadableStream;return"errored"===o._state||function(e,r,t){var o=e._controlledReadableByteStream,n=ke(r.constructor);r.byteLength;var i=t*n;return!(e._pendingPullIntos.length>0)&&("closed"===o._state||e._queueTotalSize>=i)}(o._readableStreamController,r,t)}(this,e,o)?new vr:new mr;return gr(this,e,o,n),n._promise},ReadableStreamBYOBReader.prototype.releaseLock=function(){if(!yr(this))throw wr("releaseLock");void 0!==this._ownerReadableStream&&function(e){B(e);var r=new TypeError("Reader was released");Sr(e,r)}(this)},ReadableStreamBYOBReader}();Object.defineProperties(_r.prototype,{cancel:{enumerable:!0},read:{enumerable:!0},releaseLock:{enumerable:!0},closed:{enumerable:!0}}),i(_r.prototype.cancel,"cancel"),i(_r.prototype.read,"read"),i(_r.prototype.releaseLock,"releaseLock"),"symbol"==typeof r.toStringTag&&Object.defineProperty(_r.prototype,r.toStringTag,{value:"ReadableStreamBYOBReader",configurable:!0});var mr=function(){function e(){var e=this;this._promise=f(function(r,t){e._resolvePromise=r,e._rejectPromise=t})}return e.prototype._chunkSteps=function(e){this._resolvePromise({value:e,done:!1})},e.prototype._closeSteps=function(e){this._resolvePromise({value:e,done:!0})},e.prototype._errorSteps=function(e){this._rejectPromise(e)},e}(),vr=function(){function e(){this._promise=void 0}return e.prototype._chunkSteps=function(e){this._promise=c({value:e,done:!1})},e.prototype._closeSteps=function(e){this._promise=c({value:e,done:!0})},e.prototype._errorSteps=function(e){this._promise=p(e)},e}();function yr(e){return!!o(e)&&(!!Object.prototype.hasOwnProperty.call(e,"_readIntoRequests")&&e instanceof _r)}function gr(e,r,t,o){var n=e._ownerReadableStream;n._disturbed=!0,"errored"===n._state?o._errorSteps(n._storedError):function(e,r,t,o){var n,i=e._controlledReadableByteStream,a=r.constructor,u=ke(a),l=r.byteOffset,s=r.byteLength,c=t*u;try{n=pe(r.buffer)}catch(p){return void o._errorSteps(p)}var f={buffer:n,bufferByteLength:n.byteLength,byteOffset:l,byteLength:s,bytesFilled:0,minimumFill:c,elementSize:u,viewConstructor:a,readerType:"byob"};if(e._pendingPullIntos.length>0)return e._pendingPullIntos.push(f),void pr(i,o);if("closed"!==i._state){if(e._queueTotalSize>0){if(Ve(e,f)){var d=Ye(f);return Ge(e),void o._chunkSteps(d)}if(e._closeRequested){var p=new TypeError("Insufficient bytes to fill elements in the given buffer");return tr(e,p),void o._errorSteps(p)}}e._pendingPullIntos.push(f),pr(i,o),Le(e)}else{var b=new a(f.buffer,f.byteOffset,0);o._closeSteps(b)}}(n._readableStreamController,r,t,o)}function Sr(e,r){var t=e._readIntoRequests;e._readIntoRequests=new R,t.forEach(function(e){e._errorSteps(r)})}function wr(e){return new TypeError("ReadableStreamBYOBReader.prototype.".concat(e," can only be used on a ReadableStreamBYOBReader"))}function Rr(e,r){var t=e.highWaterMark;if(void 0===t)return r;if(qe(t)||t<0)throw new RangeError("Invalid highWaterMark");return t}function Tr(e){var r=e.size;return r||function(){return 1}}function Pr(e,r){M(e,r);var t=null==e?void 0:e.highWaterMark,o=null==e?void 0:e.size;return{highWaterMark:void 0===t?void 0:H(t),size:void 0===o?void 0:Cr(o,"".concat(r," has member 'size' that"))}}function Cr(e,r){return x(e,r),function(r){return H(e(r))}}function qr(e,r,t){return x(e,t),function(t){return w(e,r,[t])}}function Er(e,r,t){return x(e,t),function(){return w(e,r,[])}}function Wr(e,r,t){return x(e,t),function(t){return S(e,r,[t])}}function Or(e,r,t){return x(e,t),function(t,o){return w(e,r,[t,o])}}function jr(e,r){if(!zr(e))throw new TypeError("".concat(r," is not a WritableStream."))}var Br=function(){function WritableStream(e,r){void 0===e&&(e={}),void 0===r&&(r={}),void 0===e?e=null:Y(e,"First parameter");var t=Pr(r,"Second parameter"),o=function(e,r){M(e,r);var t=null==e?void 0:e.abort,o=null==e?void 0:e.close,n=null==e?void 0:e.start,i=null==e?void 0:e.type,a=null==e?void 0:e.write;return{abort:void 0===t?void 0:qr(t,e,"".concat(r," has member 'abort' that")),close:void 0===o?void 0:Er(o,e,"".concat(r," has member 'close' that")),start:void 0===n?void 0:Wr(n,e,"".concat(r," has member 'start' that")),write:void 0===a?void 0:Or(a,e,"".concat(r," has member 'write' that")),type:i}}(e,"First parameter");if(Ar(this),void 0!==o.type)throw new RangeError("Invalid type is specified");var n=Tr(t);!function(e,r,t,o){var n,i,a,u,l=Object.create($r.prototype);n=void 0!==r.start?function(){return r.start(l)}:function(){};i=void 0!==r.write?function(e){return r.write(e,l)}:function(){return d(void 0)};a=void 0!==r.close?function(){return r.close()}:function(){return d(void 0)};u=void 0!==r.abort?function(e){return r.abort(e)}:function(){return d(void 0)};rt(e,l,n,i,a,u,t,o)}(this,o,Rr(t,1),n)}return Object.defineProperty(WritableStream.prototype,"locked",{get:function(){if(!zr(this))throw lt("locked");return Dr(this)},enumerable:!1,configurable:!0}),WritableStream.prototype.abort=function(e){return void 0===e&&(e=void 0),zr(this)?Dr(this)?p(new TypeError("Cannot abort a stream that already has a writer")):Fr(this,e):p(lt("abort"))},WritableStream.prototype.close=function(){return zr(this)?Dr(this)?p(new TypeError("Cannot close a stream that already has a writer")):Yr(this)?p(new TypeError("Cannot close an already-closing stream")):Lr(this):p(lt("close"))},WritableStream.prototype.getWriter=function(){if(!zr(this))throw lt("getWriter");return kr(this)},WritableStream}();function kr(e){return new Hr(e)}function Ar(e){e._state="writable",e._storedError=void 0,e._writer=void 0,e._writableStreamController=void 0,e._writeRequests=new R,e._inFlightWriteRequest=void 0,e._closeRequest=void 0,e._inFlightCloseRequest=void 0,e._pendingAbortRequest=void 0,e._backpressure=!1}function zr(e){return!!o(e)&&(!!Object.prototype.hasOwnProperty.call(e,"_writableStreamController")&&e instanceof Br)}function Dr(e){return void 0!==e._writer}function Fr(e,r){var t;if("closed"===e._state||"errored"===e._state)return d(void 0);e._writableStreamController._abortReason=r,null===(t=e._writableStreamController._abortController)||void 0===t||t.abort(r);var o=e._state;if("closed"===o||"errored"===o)return d(void 0);if(void 0!==e._pendingAbortRequest)return e._pendingAbortRequest._promise;var n=!1;"erroring"===o&&(n=!0,r=void 0);var i=f(function(t,o){e._pendingAbortRequest={_promise:void 0,_resolve:t,_reject:o,_reason:r,_wasAlreadyErroring:n}});return e._pendingAbortRequest._promise=i,n||Mr(e,r),i}function Lr(e){var r=e._state;if("closed"===r||"errored"===r)return p(new TypeError("The stream (in ".concat(r," state) is not in the writable state and cannot be closed")));var t,o=f(function(r,t){var o={_resolve:r,_reject:t};e._closeRequest=o}),n=e._writer;return void 0!==n&&e._backpressure&&"writable"===r&>(n),Oe(t=e._writableStreamController,Zr,0),nt(t),o}function Ir(e,r){"writable"!==e._state?xr(e):Mr(e,r)}function Mr(e,r){var t=e._writableStreamController;e._state="erroring",e._storedError=r;var o=e._writer;void 0!==o&&Xr(o,r),!function(e){if(void 0===e._inFlightWriteRequest&&void 0===e._inFlightCloseRequest)return!1;return!0}(e)&&t._started&&xr(e)}function xr(e){e._state="errored",e._writableStreamController[P]();var r=e._storedError;if(e._writeRequests.forEach(function(e){e._reject(r)}),e._writeRequests=new R,void 0!==e._pendingAbortRequest){var t=e._pendingAbortRequest;if(e._pendingAbortRequest=void 0,t._wasAlreadyErroring)return t._reject(r),void Qr(e);h(e._writableStreamController[T](t._reason),function(){return t._resolve(),Qr(e),null},function(r){return t._reject(r),Qr(e),null})}else Qr(e)}function Yr(e){return void 0!==e._closeRequest||void 0!==e._inFlightCloseRequest}function Qr(e){void 0!==e._closeRequest&&(e._closeRequest._reject(e._storedError),e._closeRequest=void 0);var r=e._writer;void 0!==r&&bt(r,e._storedError)}function Nr(e,r){var t=e._writer;void 0!==t&&r!==e._backpressure&&(r?function(e){_t(e)}(t):gt(t)),e._backpressure=r}Object.defineProperties(Br.prototype,{abort:{enumerable:!0},close:{enumerable:!0},getWriter:{enumerable:!0},locked:{enumerable:!0}}),i(Br.prototype.abort,"abort"),i(Br.prototype.close,"close"),i(Br.prototype.getWriter,"getWriter"),"symbol"==typeof r.toStringTag&&Object.defineProperty(Br.prototype,r.toStringTag,{value:"WritableStream",configurable:!0});var Hr=function(){function WritableStreamDefaultWriter(e){if(Q(e,1,"WritableStreamDefaultWriter"),jr(e,"First parameter"),Dr(e))throw new TypeError("This stream has already been locked for exclusive writing by another writer");this._ownerWritableStream=e,e._writer=this;var r,t=e._state;if("writable"===t)!Yr(e)&&e._backpressure?_t(this):vt(this),dt(this);else if("erroring"===t)mt(this,e._storedError),dt(this);else if("closed"===t)vt(this),dt(r=this),ht(r);else{var o=e._storedError;mt(this,o),pt(this,o)}}return Object.defineProperty(WritableStreamDefaultWriter.prototype,"closed",{get:function(){return Vr(this)?this._closedPromise:p(ct("closed"))},enumerable:!1,configurable:!0}),Object.defineProperty(WritableStreamDefaultWriter.prototype,"desiredSize",{get:function(){if(!Vr(this))throw ct("desiredSize");if(void 0===this._ownerWritableStream)throw ft("desiredSize");return function(e){var r=e._ownerWritableStream,t=r._state;if("errored"===t||"erroring"===t)return null;if("closed"===t)return 0;return ot(r._writableStreamController)}(this)},enumerable:!1,configurable:!0}),Object.defineProperty(WritableStreamDefaultWriter.prototype,"ready",{get:function(){return Vr(this)?this._readyPromise:p(ct("ready"))},enumerable:!1,configurable:!0}),WritableStreamDefaultWriter.prototype.abort=function(e){return void 0===e&&(e=void 0),Vr(this)?void 0===this._ownerWritableStream?p(ft("abort")):function(e,r){return Fr(e._ownerWritableStream,r)}(this,e):p(ct("abort"))},WritableStreamDefaultWriter.prototype.close=function(){if(!Vr(this))return p(ct("close"));var e=this._ownerWritableStream;return void 0===e?p(ft("close")):Yr(e)?p(new TypeError("Cannot close an already-closing stream")):Ur(this)},WritableStreamDefaultWriter.prototype.releaseLock=function(){if(!Vr(this))throw ct("releaseLock");void 0!==this._ownerWritableStream&&Jr(this)},WritableStreamDefaultWriter.prototype.write=function(e){return void 0===e&&(e=void 0),Vr(this)?void 0===this._ownerWritableStream?p(ft("write to")):Kr(this,e):p(ct("write"))},WritableStreamDefaultWriter}();function Vr(e){return!!o(e)&&(!!Object.prototype.hasOwnProperty.call(e,"_ownerWritableStream")&&e instanceof Hr)}function Ur(e){return Lr(e._ownerWritableStream)}function Gr(e,r){"pending"===e._closedPromiseState?bt(e,r):function(e,r){pt(e,r)}(e,r)}function Xr(e,r){"pending"===e._readyPromiseState?yt(e,r):function(e,r){mt(e,r)}(e,r)}function Jr(e){var r=e._ownerWritableStream,t=new TypeError("Writer was released and can no longer be used to monitor the stream's closedness");Xr(e,t),Gr(e,t),r._writer=void 0,e._ownerWritableStream=void 0}function Kr(e,r){var t=e._ownerWritableStream,o=t._writableStreamController,n=function(e,r){if(void 0===e._strategySizeAlgorithm)return 1;try{return e._strategySizeAlgorithm(r)}catch(r){return it(e,r),1}}(o,r);if(t!==e._ownerWritableStream)return p(ft("write to"));var i=t._state;if("errored"===i)return p(t._storedError);if(Yr(t)||"closed"===i)return p(new TypeError("The stream is closing or closed and cannot be written to"));if("erroring"===i)return p(t._storedError);var a=function(e){return f(function(r,t){var o={_resolve:r,_reject:t};e._writeRequests.push(o)})}(t);return function(e,r,t){try{Oe(e,r,t)}catch(r){return void it(e,r)}var o=e._controlledWritableStream;if(!Yr(o)&&"writable"===o._state){Nr(o,at(e))}nt(e)}(o,r,n),a}Object.defineProperties(Hr.prototype,{abort:{enumerable:!0},close:{enumerable:!0},releaseLock:{enumerable:!0},write:{enumerable:!0},closed:{enumerable:!0},desiredSize:{enumerable:!0},ready:{enumerable:!0}}),i(Hr.prototype.abort,"abort"),i(Hr.prototype.close,"close"),i(Hr.prototype.releaseLock,"releaseLock"),i(Hr.prototype.write,"write"),"symbol"==typeof r.toStringTag&&Object.defineProperty(Hr.prototype,r.toStringTag,{value:"WritableStreamDefaultWriter",configurable:!0});var Zr={},$r=function(){function WritableStreamDefaultController(){throw new TypeError("Illegal constructor")}return Object.defineProperty(WritableStreamDefaultController.prototype,"abortReason",{get:function(){if(!et(this))throw st("abortReason");return this._abortReason},enumerable:!1,configurable:!0}),Object.defineProperty(WritableStreamDefaultController.prototype,"signal",{get:function(){if(!et(this))throw st("signal");if(void 0===this._abortController)throw new TypeError("WritableStreamDefaultController.prototype.signal is not supported");return this._abortController.signal},enumerable:!1,configurable:!0}),WritableStreamDefaultController.prototype.error=function(e){if(void 0===e&&(e=void 0),!et(this))throw st("error");"writable"===this._controlledWritableStream._state&&ut(this,e)},WritableStreamDefaultController.prototype[T]=function(e){var r=this._abortAlgorithm(e);return tt(this),r},WritableStreamDefaultController.prototype[P]=function(){je(this)},WritableStreamDefaultController}();function et(e){return!!o(e)&&(!!Object.prototype.hasOwnProperty.call(e,"_controlledWritableStream")&&e instanceof $r)}function rt(e,r,t,o,n,i,a,u){r._controlledWritableStream=e,e._writableStreamController=r,r._queue=void 0,r._queueTotalSize=void 0,je(r),r._abortReason=void 0,r._abortController=function(){if("function"==typeof AbortController)return new AbortController}(),r._started=!1,r._strategySizeAlgorithm=u,r._strategyHWM=a,r._writeAlgorithm=o,r._closeAlgorithm=n,r._abortAlgorithm=i;var l=at(r);Nr(e,l),h(d(t()),function(){return r._started=!0,nt(r),null},function(t){return r._started=!0,Ir(e,t),null})}function tt(e){e._writeAlgorithm=void 0,e._closeAlgorithm=void 0,e._abortAlgorithm=void 0,e._strategySizeAlgorithm=void 0}function ot(e){return e._strategyHWM-e._queueTotalSize}function nt(e){var r=e._controlledWritableStream;if(e._started&&void 0===r._inFlightWriteRequest)if("erroring"!==r._state){if(0!==e._queue.length){var t=e._queue.peek().value;t===Zr?function(e){var r=e._controlledWritableStream;(function(e){e._inFlightCloseRequest=e._closeRequest,e._closeRequest=void 0})(r),We(e);var t=e._closeAlgorithm();tt(e),h(t,function(){return function(e){e._inFlightCloseRequest._resolve(void 0),e._inFlightCloseRequest=void 0,"erroring"===e._state&&(e._storedError=void 0,void 0!==e._pendingAbortRequest&&(e._pendingAbortRequest._resolve(),e._pendingAbortRequest=void 0)),e._state="closed";var r=e._writer;void 0!==r&&ht(r)}(r),null},function(e){return function(e,r){e._inFlightCloseRequest._reject(r),e._inFlightCloseRequest=void 0,void 0!==e._pendingAbortRequest&&(e._pendingAbortRequest._reject(r),e._pendingAbortRequest=void 0),Ir(e,r)}(r,e),null})}(e):function(e,r){var t=e._controlledWritableStream;!function(e){e._inFlightWriteRequest=e._writeRequests.shift()}(t);var o=e._writeAlgorithm(r);h(o,function(){!function(e){e._inFlightWriteRequest._resolve(void 0),e._inFlightWriteRequest=void 0}(t);var r=t._state;if(We(e),!Yr(t)&&"writable"===r){var o=at(e);Nr(t,o)}return nt(e),null},function(r){return"writable"===t._state&&tt(e),function(e,r){e._inFlightWriteRequest._reject(r),e._inFlightWriteRequest=void 0,Ir(e,r)}(t,r),null})}(e,t)}}else xr(r)}function it(e,r){"writable"===e._controlledWritableStream._state&&ut(e,r)}function at(e){return ot(e)<=0}function ut(e,r){var t=e._controlledWritableStream;tt(e),Mr(t,r)}function lt(e){return new TypeError("WritableStream.prototype.".concat(e," can only be used on a WritableStream"))}function st(e){return new TypeError("WritableStreamDefaultController.prototype.".concat(e," can only be used on a WritableStreamDefaultController"))}function ct(e){return new TypeError("WritableStreamDefaultWriter.prototype.".concat(e," can only be used on a WritableStreamDefaultWriter"))}function ft(e){return new TypeError("Cannot "+e+" a stream using a released writer")}function dt(e){e._closedPromise=f(function(r,t){e._closedPromise_resolve=r,e._closedPromise_reject=t,e._closedPromiseState="pending"})}function pt(e,r){dt(e),bt(e,r)}function bt(e,r){void 0!==e._closedPromise_reject&&(y(e._closedPromise),e._closedPromise_reject(r),e._closedPromise_resolve=void 0,e._closedPromise_reject=void 0,e._closedPromiseState="rejected")}function ht(e){void 0!==e._closedPromise_resolve&&(e._closedPromise_resolve(void 0),e._closedPromise_resolve=void 0,e._closedPromise_reject=void 0,e._closedPromiseState="resolved")}function _t(e){e._readyPromise=f(function(r,t){e._readyPromise_resolve=r,e._readyPromise_reject=t}),e._readyPromiseState="pending"}function mt(e,r){_t(e),yt(e,r)}function vt(e){_t(e),gt(e)}function yt(e,r){void 0!==e._readyPromise_reject&&(y(e._readyPromise),e._readyPromise_reject(r),e._readyPromise_resolve=void 0,e._readyPromise_reject=void 0,e._readyPromiseState="rejected")}function gt(e){void 0!==e._readyPromise_resolve&&(e._readyPromise_resolve(void 0),e._readyPromise_resolve=void 0,e._readyPromise_reject=void 0,e._readyPromiseState="fulfilled")}Object.defineProperties($r.prototype,{abortReason:{enumerable:!0},signal:{enumerable:!0},error:{enumerable:!0}}),"symbol"==typeof r.toStringTag&&Object.defineProperty($r.prototype,r.toStringTag,{value:"WritableStreamDefaultController",configurable:!0});var St="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof global?global:void 0;var wt,Rt=(function(e){if("function"!=typeof e&&"object"!=typeof e)return!1;if("DOMException"!==e.name)return!1;try{return new e,!0}catch(e){return!1}}(wt=null==St?void 0:St.DOMException)?wt:void 0)||function(){var e=function(e,r){this.message=e||"",this.name=r||"Error",Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)};return i(e,"DOMException"),e.prototype=Object.create(Error.prototype),Object.defineProperty(e.prototype,"constructor",{value:e,writable:!0,configurable:!0}),e}();function Tt(e,r,t,o,n,i){var a=X(e),u=kr(r);e._disturbed=!0;var l=new Pt(u),s=new qt(l);return f(function(c,m){var v,g,S,w;if(void 0!==i){if(v=function(){var t=void 0!==i.reason?i.reason:new Rt("Aborted","AbortError"),a=[];o||a.push(function(){return"writable"===r._state?Fr(r,t):d(void 0)}),n||a.push(function(){return"readable"===e._state?eo(e,t):d(void 0)}),P(function(){return Promise.all(a.map(function(e){return e()}))},!0,t)},i.aborted)return void v();i.addEventListener("abort",v)}function R(){for(;!l._shuttingDown&&!r._backpressure&&"writable"===r._state&&!Yr(r)&&"readable"===e._state&&le(a);)ue(a,s);if(l._shuttingDown)return d(!0);if(r._backpressure)return b(u._readyPromise,R);var t=new Ct(l);return ue(a,t),t._promise}if(Et(e,a._closedPromise,function(e){return o?C(!0,e):P(function(){return Fr(r,e)},!0,e),null}),Et(r,u._closedPromise,function(r){return n?C(!0,r):P(function(){return eo(e,r)},!0,r),null}),g=e,S=a._closedPromise,w=function(){return t?C():P(function(){return function(e){var r=e._ownerWritableStream,t=r._state;return Yr(r)||"closed"===t?d(void 0):"errored"===t?p(r._storedError):Ur(e)}(u)}),null},"closed"===g._state?w():_(S,w),Yr(r)||"closed"===r._state){var T=new TypeError("the destination writable stream closed before all data could be piped to it");n?C(!0,T):P(function(){return eo(e,T)},!0,T)}function P(e,t,o){function n(){return h(e(),function(){return q(t,o)},function(e){return q(!0,e)}),null}l._shuttingDown||(l._shuttingDown=!0,"writable"!==r._state||Yr(r)?n():_(l._waitForWritesToFinish(),n))}function C(e,t){l._shuttingDown||(l._shuttingDown=!0,"writable"!==r._state||Yr(r)?q(e,t):_(l._waitForWritesToFinish(),function(){return q(e,t)}))}function q(e,r){return Jr(u),B(a),void 0!==i&&i.removeEventListener("abort",v),e?m(r):c(void 0),null}y(f(function(e,r){!function t(o){o?e():b(R(),t,r)}(!1)}))})}var Pt=function(){function e(e){this._writer=e,this._shuttingDown=!1,this._currentWrite=d(void 0)}return e.prototype._waitForWritesToFinish=function(){var e=this,r=this._currentWrite;return b(this._currentWrite,function(){return r!==e._currentWrite?e._waitForWritesToFinish():void 0})},e}(),Ct=function(){function e(e){var r=this;this._state=e,this._promise=f(function(e,t){r._resolvePromise=e,r._rejectPromise=t})}return e.prototype._chunkSteps=function(e){this._state._currentWrite=b(Kr(this._state._writer,e),void 0,t),this._resolvePromise(!1)},e.prototype._closeSteps=function(){this._resolvePromise(!0)},e.prototype._errorSteps=function(e){this._rejectPromise(e)},e}(),qt=function(){function e(e){this._state=e}return e.prototype._chunkSteps=function(e){this._state._currentWrite=b(Kr(this._state._writer,e),void 0,t)},e.prototype._closeSteps=function(){},e.prototype._errorSteps=function(e){},e}();function Et(e,r,t){"errored"===e._state?t(e._storedError):m(r,t)}var Wt=function(){function ReadableStreamDefaultController(){throw new TypeError("Illegal constructor")}return Object.defineProperty(ReadableStreamDefaultController.prototype,"desiredSize",{get:function(){if(!Ot(this))throw Mt("desiredSize");return Ft(this)},enumerable:!1,configurable:!0}),ReadableStreamDefaultController.prototype.close=function(){if(!Ot(this))throw Mt("close");if(!Lt(this))throw new TypeError("The stream is not in a state that permits close");At(this)},ReadableStreamDefaultController.prototype.enqueue=function(e){if(void 0===e&&(e=void 0),!Ot(this))throw Mt("enqueue");if(!Lt(this))throw new TypeError("The stream is not in a state that permits enqueue");return zt(this,e)},ReadableStreamDefaultController.prototype.error=function(e){if(void 0===e&&(e=void 0),!Ot(this))throw Mt("error");Dt(this,e)},ReadableStreamDefaultController.prototype[C]=function(e){je(this);var r=this._cancelAlgorithm(e);return kt(this),r},ReadableStreamDefaultController.prototype[q]=function(e){var r=this._controlledReadableStream;if(this._queue.length>0){var t=We(this);this._closeRequested&&0===this._queue.length?(kt(this),ro(r)):jt(this),e._chunkSteps(t)}else J(r,e),jt(this)},ReadableStreamDefaultController.prototype[E]=function(){return this._queue.length>0},ReadableStreamDefaultController.prototype[W]=function(){},ReadableStreamDefaultController}();function Ot(e){return!!o(e)&&(!!Object.prototype.hasOwnProperty.call(e,"_controlledReadableStream")&&e instanceof Wt)}function jt(e){Bt(e)&&(e._pulling?e._pullAgain=!0:(e._pulling=!0,h(e._pullAlgorithm(),function(){return e._pulling=!1,e._pullAgain&&(e._pullAgain=!1,jt(e)),null},function(r){return Dt(e,r),null})))}function Bt(e){var r=e._controlledReadableStream;return!!Lt(e)&&(!!e._started&&(!!($t(r)&&Z(r)>0)||Ft(e)>0))}function kt(e){e._pullAlgorithm=void 0,e._cancelAlgorithm=void 0,e._strategySizeAlgorithm=void 0}function At(e){if(Lt(e)){var r=e._controlledReadableStream;e._closeRequested=!0,0===e._queue.length&&(kt(e),ro(r))}}function zt(e,r){if(Lt(e)){var t=e._controlledReadableStream;if($t(t)&&Z(t)>0)K(t,r,!1);else{var o=void 0;try{o=e._strategySizeAlgorithm(r)}catch(r){throw Dt(e,r),r}try{Oe(e,r,o)}catch(r){throw Dt(e,r),r}}jt(e)}}function Dt(e,r){var t=e._controlledReadableStream;"readable"===t._state&&(je(e),kt(e),to(t,r))}function Ft(e){var r=e._controlledReadableStream._state;return"errored"===r?null:"closed"===r?0:e._strategyHWM-e._queueTotalSize}function Lt(e){var r=e._controlledReadableStream._state;return!e._closeRequested&&"readable"===r}function It(e,r,t,o,n,i,a){r._controlledReadableStream=e,r._queue=void 0,r._queueTotalSize=void 0,je(r),r._started=!1,r._closeRequested=!1,r._pullAgain=!1,r._pulling=!1,r._strategySizeAlgorithm=a,r._strategyHWM=i,r._pullAlgorithm=o,r._cancelAlgorithm=n,e._readableStreamController=r,h(d(t()),function(){return r._started=!0,jt(r),null},function(e){return Dt(r,e),null})}function Mt(e){return new TypeError("ReadableStreamDefaultController.prototype.".concat(e," can only be used on a ReadableStreamDefaultController"))}function xt(e,r){return De(e._readableStreamController)?function(e){var r,t,o,n,i,a=X(e),u=!1,l=!1,s=!1,c=!1,p=!1,b=f(function(e){i=e});function h(e){m(e._closedPromise,function(r){return e!==a||(tr(o._readableStreamController,r),tr(n._readableStreamController,r),c&&p||i(void 0)),null})}function _(){yr(a)&&(B(a),h(a=X(e))),ue(a,{_chunkSteps:function(r){g(function(){l=!1,s=!1;var t=r,a=r;if(!c&&!p)try{a=Ee(r)}catch(r){return tr(o._readableStreamController,r),tr(n._readableStreamController,r),void i(eo(e,r))}c||rr(o._readableStreamController,t),p||rr(n._readableStreamController,a),u=!1,l?y():s&&S()})},_closeSteps:function(){u=!1,c||er(o._readableStreamController),p||er(n._readableStreamController),o._readableStreamController._pendingPullIntos.length>0&&ar(o._readableStreamController,0),n._readableStreamController._pendingPullIntos.length>0&&ar(n._readableStreamController,0),c&&p||i(void 0)},_errorSteps:function(){u=!1}})}function v(r,t){ae(a)&&(B(a),h(a=dr(e)));var f=t?n:o,d=t?o:n;gr(a,r,1,{_chunkSteps:function(r){g(function(){l=!1,s=!1;var o=t?p:c;if(t?c:p)o||ur(f._readableStreamController,r);else{var n=void 0;try{n=Ee(r)}catch(r){return tr(f._readableStreamController,r),tr(d._readableStreamController,r),void i(eo(e,r))}o||ur(f._readableStreamController,r),rr(d._readableStreamController,n)}u=!1,l?y():s&&S()})},_closeSteps:function(e){u=!1;var r=t?p:c,o=t?c:p;r||er(f._readableStreamController),o||er(d._readableStreamController),void 0!==e&&(r||ur(f._readableStreamController,e),!o&&d._readableStreamController._pendingPullIntos.length>0&&ar(d._readableStreamController,0)),r&&o||i(void 0)},_errorSteps:function(){u=!1}})}function y(){if(u)return l=!0,d(void 0);u=!0;var e=nr(o._readableStreamController);return null===e?_():v(e._view,!1),d(void 0)}function S(){if(u)return s=!0,d(void 0);u=!0;var e=nr(n._readableStreamController);return null===e?_():v(e._view,!0),d(void 0)}function w(o){if(c=!0,r=o,p){var n=fe([r,t]),a=eo(e,n);i(a)}return b}function R(o){if(p=!0,t=o,c){var n=fe([r,t]),a=eo(e,n);i(a)}return b}function T(){}return o=Jt(T,y,w),n=Jt(T,S,R),h(a),[o,n]}(e):function(e){var r,t,o,n,i,a=X(e),u=!1,l=!1,s=!1,c=!1,p=f(function(e){i=e});function b(){return u?(l=!0,d(void 0)):(u=!0,ue(a,{_chunkSteps:function(e){g(function(){l=!1;var r=e,t=e;s||zt(o._readableStreamController,r),c||zt(n._readableStreamController,t),u=!1,l&&b()})},_closeSteps:function(){u=!1,s||At(o._readableStreamController),c||At(n._readableStreamController),s&&c||i(void 0)},_errorSteps:function(){u=!1}}),d(void 0))}function h(o){if(s=!0,r=o,c){var n=fe([r,t]),a=eo(e,n);i(a)}return p}function _(o){if(c=!0,t=o,s){var n=fe([r,t]),a=eo(e,n);i(a)}return p}function v(){}return o=Xt(v,b,h),n=Xt(v,b,_),m(a._closedPromise,function(e){return Dt(o._readableStreamController,e),Dt(n._readableStreamController,e),s&&c||i(void 0),null}),[o,n]}(e)}function Yt(e){return o(r=e)&&void 0!==r.getReader?function(e){var r;function n(){var t;try{t=e.read()}catch(e){return p(e)}return v(t,function(e){if(!o(e))throw new TypeError("The promise returned by the reader.read() method must fulfill with an object");if(e.done)At(r._readableStreamController);else{var t=e.value;zt(r._readableStreamController,t)}})}function i(r){try{return d(e.cancel(r))}catch(e){return p(e)}}return r=Xt(t,n,i,0),r}(e.getReader()):function(e){var r,n=ge(e,"async");function i(){var e;try{e=Se(n)}catch(e){return p(e)}return v(d(e),function(e){if(!o(e))throw new TypeError("The promise returned by the iterator.next() method must fulfill with an object");if(e.done)At(r._readableStreamController);else{var t=e.value;zt(r._readableStreamController,t)}})}function a(e){var r,t=n.iterator;try{r=_e(t,"return")}catch(e){return p(e)}return void 0===r?d(void 0):v(w(r,t,[e]),function(e){if(!o(e))throw new TypeError("The promise returned by the iterator.return() method must fulfill with an object")})}return r=Xt(t,i,a,0),r}(e);var r}function Qt(e,r,t){return x(e,t),function(t){return w(e,r,[t])}}function Nt(e,r,t){return x(e,t),function(t){return w(e,r,[t])}}function Ht(e,r,t){return x(e,t),function(t){return S(e,r,[t])}}function Vt(e,r){if("bytes"!==(e="".concat(e)))throw new TypeError("".concat(r," '").concat(e,"' is not a valid enumeration value for ReadableStreamType"));return e}function Ut(e,r){M(e,r);var t=null==e?void 0:e.preventAbort,o=null==e?void 0:e.preventCancel,n=null==e?void 0:e.preventClose,i=null==e?void 0:e.signal;return void 0!==i&&function(e,r){if(!function(e){if("object"!=typeof e||null===e)return!1;try{return"boolean"==typeof e.aborted}catch(e){return!1}}(e))throw new TypeError("".concat(r," is not an AbortSignal."))}(i,"".concat(r," has member 'signal' that")),{preventAbort:Boolean(t),preventCancel:Boolean(o),preventClose:Boolean(n),signal:i}}Object.defineProperties(Wt.prototype,{close:{enumerable:!0},enqueue:{enumerable:!0},error:{enumerable:!0},desiredSize:{enumerable:!0}}),i(Wt.prototype.close,"close"),i(Wt.prototype.enqueue,"enqueue"),i(Wt.prototype.error,"error"),"symbol"==typeof r.toStringTag&&Object.defineProperty(Wt.prototype,r.toStringTag,{value:"ReadableStreamDefaultController",configurable:!0});var Gt=function(){function ReadableStream(e,r){void 0===e&&(e={}),void 0===r&&(r={}),void 0===e?e=null:Y(e,"First parameter");var t=Pr(r,"Second parameter"),o=function(e,r){M(e,r);var t=e,o=null==t?void 0:t.autoAllocateChunkSize,n=null==t?void 0:t.cancel,i=null==t?void 0:t.pull,a=null==t?void 0:t.start,u=null==t?void 0:t.type;return{autoAllocateChunkSize:void 0===o?void 0:U(o,"".concat(r," has member 'autoAllocateChunkSize' that")),cancel:void 0===n?void 0:Qt(n,t,"".concat(r," has member 'cancel' that")),pull:void 0===i?void 0:Nt(i,t,"".concat(r," has member 'pull' that")),start:void 0===a?void 0:Ht(a,t,"".concat(r," has member 'start' that")),type:void 0===u?void 0:Vt(u,"".concat(r," has member 'type' that"))}}(e,"First parameter");if(Kt(this),"bytes"===o.type){if(void 0!==t.size)throw new RangeError("The strategy for a byte stream cannot have a size function");!function(e,r,t){var o,n,i,a=Object.create(ze.prototype);o=void 0!==r.start?function(){return r.start(a)}:function(){},n=void 0!==r.pull?function(){return r.pull(a)}:function(){return d(void 0)},i=void 0!==r.cancel?function(e){return r.cancel(e)}:function(){return d(void 0)};var u=r.autoAllocateChunkSize;if(0===u)throw new TypeError("autoAllocateChunkSize must be greater than 0");lr(e,a,o,n,i,t,u)}(this,o,Rr(t,0))}else{var n=Tr(t);!function(e,r,t,o){var n,i,a,u=Object.create(Wt.prototype);n=void 0!==r.start?function(){return r.start(u)}:function(){},i=void 0!==r.pull?function(){return r.pull(u)}:function(){return d(void 0)},a=void 0!==r.cancel?function(e){return r.cancel(e)}:function(){return d(void 0)},It(e,u,n,i,a,t,o)}(this,o,Rr(t,1),n)}}return Object.defineProperty(ReadableStream.prototype,"locked",{get:function(){if(!Zt(this))throw oo("locked");return $t(this)},enumerable:!1,configurable:!0}),ReadableStream.prototype.cancel=function(e){return void 0===e&&(e=void 0),Zt(this)?$t(this)?p(new TypeError("Cannot cancel a stream that already has a reader")):eo(this,e):p(oo("cancel"))},ReadableStream.prototype.getReader=function(e){if(void 0===e&&(e=void 0),!Zt(this))throw oo("getReader");return void 0===function(e,r){M(e,r);var t=null==e?void 0:e.mode;return{mode:void 0===t?void 0:fr(t,"".concat(r," has member 'mode' that"))}}(e,"First parameter").mode?X(this):dr(this)},ReadableStream.prototype.pipeThrough=function(e,r){if(void 0===r&&(r={}),!Zt(this))throw oo("pipeThrough");Q(e,1,"pipeThrough");var t=function(e,r){M(e,r);var t=null==e?void 0:e.readable;N(t,"readable","ReadableWritablePair"),G(t,"".concat(r," has member 'readable' that"));var o=null==e?void 0:e.writable;return N(o,"writable","ReadableWritablePair"),jr(o,"".concat(r," has member 'writable' that")),{readable:t,writable:o}}(e,"First parameter"),o=Ut(r,"Second parameter");if($t(this))throw new TypeError("ReadableStream.prototype.pipeThrough cannot be used on a locked ReadableStream");if(Dr(t.writable))throw new TypeError("ReadableStream.prototype.pipeThrough cannot be used on a locked WritableStream");return y(Tt(this,t.writable,o.preventClose,o.preventAbort,o.preventCancel,o.signal)),t.readable},ReadableStream.prototype.pipeTo=function(e,r){if(void 0===r&&(r={}),!Zt(this))return p(oo("pipeTo"));if(void 0===e)return p("Parameter 1 is required in 'pipeTo'.");if(!zr(e))return p(new TypeError("ReadableStream.prototype.pipeTo's first argument must be a WritableStream"));var t;try{t=Ut(r,"Second parameter")}catch(e){return p(e)}return $t(this)?p(new TypeError("ReadableStream.prototype.pipeTo cannot be used on a locked ReadableStream")):Dr(e)?p(new TypeError("ReadableStream.prototype.pipeTo cannot be used on a locked WritableStream")):Tt(this,e,t.preventClose,t.preventAbort,t.preventCancel,t.signal)},ReadableStream.prototype.tee=function(){if(!Zt(this))throw oo("tee");return fe(xt(this))},ReadableStream.prototype.values=function(e){if(void 0===e&&(e=void 0),!Zt(this))throw oo("values");var r,t,o,n,i,a=function(e,r){M(e,r);var t=null==e?void 0:e.preventCancel;return{preventCancel:Boolean(t)}}(e,"First parameter");return r=this,t=a.preventCancel,o=X(r),n=new we(o,t),(i=Object.create(Te))._asyncIteratorImpl=n,i},ReadableStream.prototype[ye]=function(e){return this.values(e)},ReadableStream.from=function(e){return Yt(e)},ReadableStream}();function Xt(e,r,t,o,n){void 0===o&&(o=1),void 0===n&&(n=function(){return 1});var i=Object.create(Gt.prototype);return Kt(i),It(i,Object.create(Wt.prototype),e,r,t,o,n),i}function Jt(e,r,t){var o=Object.create(Gt.prototype);return Kt(o),lr(o,Object.create(ze.prototype),e,r,t,0,void 0),o}function Kt(e){e._state="readable",e._reader=void 0,e._storedError=void 0,e._disturbed=!1}function Zt(e){return!!o(e)&&(!!Object.prototype.hasOwnProperty.call(e,"_readableStreamController")&&e instanceof Gt)}function $t(e){return void 0!==e._reader}function eo(e,r){if(e._disturbed=!0,"closed"===e._state)return d(void 0);if("errored"===e._state)return p(e._storedError);ro(e);var o=e._reader;if(void 0!==o&&yr(o)){var n=o._readIntoRequests;o._readIntoRequests=new R,n.forEach(function(e){e._closeSteps(void 0)})}return v(e._readableStreamController[C](r),t)}function ro(e){e._state="closed";var r=e._reader;if(void 0!==r&&(F(r),ae(r))){var t=r._readRequests;r._readRequests=new R,t.forEach(function(e){e._closeSteps()})}}function to(e,r){e._state="errored",e._storedError=r;var t=e._reader;void 0!==t&&(D(t,r),ae(t)?se(t,r):Sr(t,r))}function oo(e){return new TypeError("ReadableStream.prototype.".concat(e," can only be used on a ReadableStream"))}function no(e,r){M(e,r);var t=null==e?void 0:e.highWaterMark;return N(t,"highWaterMark","QueuingStrategyInit"),{highWaterMark:H(t)}}Object.defineProperties(Gt,{from:{enumerable:!0}}),Object.defineProperties(Gt.prototype,{cancel:{enumerable:!0},getReader:{enumerable:!0},pipeThrough:{enumerable:!0},pipeTo:{enumerable:!0},tee:{enumerable:!0},values:{enumerable:!0},locked:{enumerable:!0}}),i(Gt.from,"from"),i(Gt.prototype.cancel,"cancel"),i(Gt.prototype.getReader,"getReader"),i(Gt.prototype.pipeThrough,"pipeThrough"),i(Gt.prototype.pipeTo,"pipeTo"),i(Gt.prototype.tee,"tee"),i(Gt.prototype.values,"values"),"symbol"==typeof r.toStringTag&&Object.defineProperty(Gt.prototype,r.toStringTag,{value:"ReadableStream",configurable:!0}),Object.defineProperty(Gt.prototype,ye,{value:Gt.prototype.values,writable:!0,configurable:!0});var io=function(e){return e.byteLength};i(io,"size");var ao=function(){function ByteLengthQueuingStrategy(e){Q(e,1,"ByteLengthQueuingStrategy"),e=no(e,"First parameter"),this._byteLengthQueuingStrategyHighWaterMark=e.highWaterMark}return Object.defineProperty(ByteLengthQueuingStrategy.prototype,"highWaterMark",{get:function(){if(!lo(this))throw uo("highWaterMark");return this._byteLengthQueuingStrategyHighWaterMark},enumerable:!1,configurable:!0}),Object.defineProperty(ByteLengthQueuingStrategy.prototype,"size",{get:function(){if(!lo(this))throw uo("size");return io},enumerable:!1,configurable:!0}),ByteLengthQueuingStrategy}();function uo(e){return new TypeError("ByteLengthQueuingStrategy.prototype.".concat(e," can only be used on a ByteLengthQueuingStrategy"))}function lo(e){return!!o(e)&&(!!Object.prototype.hasOwnProperty.call(e,"_byteLengthQueuingStrategyHighWaterMark")&&e instanceof ao)}Object.defineProperties(ao.prototype,{highWaterMark:{enumerable:!0},size:{enumerable:!0}}),"symbol"==typeof r.toStringTag&&Object.defineProperty(ao.prototype,r.toStringTag,{value:"ByteLengthQueuingStrategy",configurable:!0});var so=function(){return 1};i(so,"size");var co=function(){function CountQueuingStrategy(e){Q(e,1,"CountQueuingStrategy"),e=no(e,"First parameter"),this._countQueuingStrategyHighWaterMark=e.highWaterMark}return Object.defineProperty(CountQueuingStrategy.prototype,"highWaterMark",{get:function(){if(!po(this))throw fo("highWaterMark");return this._countQueuingStrategyHighWaterMark},enumerable:!1,configurable:!0}),Object.defineProperty(CountQueuingStrategy.prototype,"size",{get:function(){if(!po(this))throw fo("size");return so},enumerable:!1,configurable:!0}),CountQueuingStrategy}();function fo(e){return new TypeError("CountQueuingStrategy.prototype.".concat(e," can only be used on a CountQueuingStrategy"))}function po(e){return!!o(e)&&(!!Object.prototype.hasOwnProperty.call(e,"_countQueuingStrategyHighWaterMark")&&e instanceof co)}function bo(e,r,t){return x(e,t),function(t){return w(e,r,[t])}}function ho(e,r,t){return x(e,t),function(t){return S(e,r,[t])}}function _o(e,r,t){return x(e,t),function(t,o){return w(e,r,[t,o])}}function mo(e,r,t){return x(e,t),function(t){return w(e,r,[t])}}Object.defineProperties(co.prototype,{highWaterMark:{enumerable:!0},size:{enumerable:!0}}),"symbol"==typeof r.toStringTag&&Object.defineProperty(co.prototype,r.toStringTag,{value:"CountQueuingStrategy",configurable:!0});var vo=function(){function TransformStream(e,r,t){void 0===e&&(e={}),void 0===r&&(r={}),void 0===t&&(t={}),void 0===e&&(e=null);var o=Pr(r,"Second parameter"),n=Pr(t,"Third parameter"),i=function(e,r){M(e,r);var t=null==e?void 0:e.cancel,o=null==e?void 0:e.flush,n=null==e?void 0:e.readableType,i=null==e?void 0:e.start,a=null==e?void 0:e.transform,u=null==e?void 0:e.writableType;return{cancel:void 0===t?void 0:mo(t,e,"".concat(r," has member 'cancel' that")),flush:void 0===o?void 0:bo(o,e,"".concat(r," has member 'flush' that")),readableType:n,start:void 0===i?void 0:ho(i,e,"".concat(r," has member 'start' that")),transform:void 0===a?void 0:_o(a,e,"".concat(r," has member 'transform' that")),writableType:u}}(e,"First parameter");if(void 0!==i.readableType)throw new RangeError("Invalid readableType specified");if(void 0!==i.writableType)throw new RangeError("Invalid writableType specified");var a,u=Rr(n,0),l=Tr(n),s=Rr(o,1),c=Tr(o);!function(e,r,t,o,n,i){function a(){return r}function u(r){return function(e,r){var t=e._transformStreamController;if(e._backpressure){return v(e._backpressureChangePromise,function(){var o=e._writable;if("erroring"===o._state)throw o._storedError;return Eo(t,r)})}return Eo(t,r)}(e,r)}function l(r){return function(e,r){var t=e._transformStreamController;if(void 0!==t._finishPromise)return t._finishPromise;var o=e._readable;t._finishPromise=f(function(e,r){t._finishPromise_resolve=e,t._finishPromise_reject=r});var n=t._cancelAlgorithm(r);return Co(t),h(n,function(){return"errored"===o._state?jo(t,o._storedError):(Dt(o._readableStreamController,r),Oo(t)),null},function(e){return Dt(o._readableStreamController,e),jo(t,e),null}),t._finishPromise}(e,r)}function s(){return function(e){var r=e._transformStreamController;if(void 0!==r._finishPromise)return r._finishPromise;var t=e._readable;r._finishPromise=f(function(e,t){r._finishPromise_resolve=e,r._finishPromise_reject=t});var o=r._flushAlgorithm();return Co(r),h(o,function(){return"errored"===t._state?jo(r,t._storedError):(At(t._readableStreamController),Oo(r)),null},function(e){return Dt(t._readableStreamController,e),jo(r,e),null}),r._finishPromise}(e)}function c(){return function(e){return Ro(e,!1),e._backpressureChangePromise}(e)}function d(r){return function(e,r){var t=e._transformStreamController;if(void 0!==t._finishPromise)return t._finishPromise;var o=e._writable;t._finishPromise=f(function(e,r){t._finishPromise_resolve=e,t._finishPromise_reject=r});var n=t._cancelAlgorithm(r);return Co(t),h(n,function(){return"errored"===o._state?jo(t,o._storedError):(it(o._writableStreamController,r),wo(e),Oo(t)),null},function(r){return it(o._writableStreamController,r),wo(e),jo(t,r),null}),t._finishPromise}(e,r)}e._writable=function(e,r,t,o,n,i){void 0===n&&(n=1),void 0===i&&(i=function(){return 1});var a=Object.create(Br.prototype);return Ar(a),rt(a,Object.create($r.prototype),e,r,t,o,n,i),a}(a,u,s,l,t,o),e._readable=Xt(a,c,d,n,i),e._backpressure=void 0,e._backpressureChangePromise=void 0,e._backpressureChangePromise_resolve=void 0,Ro(e,!0),e._transformStreamController=void 0}(this,f(function(e){a=e}),s,c,u,l),function(e,r){var t,o,n,i=Object.create(To.prototype);t=void 0!==r.transform?function(e){return r.transform(e,i)}:function(e){try{return qo(i,e),d(void 0)}catch(e){return p(e)}};o=void 0!==r.flush?function(){return r.flush(i)}:function(){return d(void 0)};n=void 0!==r.cancel?function(e){return r.cancel(e)}:function(){return d(void 0)};!function(e,r,t,o,n){r._controlledTransformStream=e,e._transformStreamController=r,r._transformAlgorithm=t,r._flushAlgorithm=o,r._cancelAlgorithm=n,r._finishPromise=void 0,r._finishPromise_resolve=void 0,r._finishPromise_reject=void 0}(e,i,t,o,n)}(this,i),void 0!==i.start?a(i.start(this._transformStreamController)):a(void 0)}return Object.defineProperty(TransformStream.prototype,"readable",{get:function(){if(!yo(this))throw Bo("readable");return this._readable},enumerable:!1,configurable:!0}),Object.defineProperty(TransformStream.prototype,"writable",{get:function(){if(!yo(this))throw Bo("writable");return this._writable},enumerable:!1,configurable:!0}),TransformStream}();function yo(e){return!!o(e)&&(!!Object.prototype.hasOwnProperty.call(e,"_transformStreamController")&&e instanceof vo)}function go(e,r){Dt(e._readable._readableStreamController,r),So(e,r)}function So(e,r){Co(e._transformStreamController),it(e._writable._writableStreamController,r),wo(e)}function wo(e){e._backpressure&&Ro(e,!1)}function Ro(e,r){void 0!==e._backpressureChangePromise&&e._backpressureChangePromise_resolve(),e._backpressureChangePromise=f(function(r){e._backpressureChangePromise_resolve=r}),e._backpressure=r}Object.defineProperties(vo.prototype,{readable:{enumerable:!0},writable:{enumerable:!0}}),"symbol"==typeof r.toStringTag&&Object.defineProperty(vo.prototype,r.toStringTag,{value:"TransformStream",configurable:!0});var To=function(){function TransformStreamDefaultController(){throw new TypeError("Illegal constructor")}return Object.defineProperty(TransformStreamDefaultController.prototype,"desiredSize",{get:function(){if(!Po(this))throw Wo("desiredSize");return Ft(this._controlledTransformStream._readable._readableStreamController)},enumerable:!1,configurable:!0}),TransformStreamDefaultController.prototype.enqueue=function(e){if(void 0===e&&(e=void 0),!Po(this))throw Wo("enqueue");qo(this,e)},TransformStreamDefaultController.prototype.error=function(e){if(void 0===e&&(e=void 0),!Po(this))throw Wo("error");var r;r=e,go(this._controlledTransformStream,r)},TransformStreamDefaultController.prototype.terminate=function(){if(!Po(this))throw Wo("terminate");!function(e){var r=e._controlledTransformStream;At(r._readable._readableStreamController);var t=new TypeError("TransformStream terminated");So(r,t)}(this)},TransformStreamDefaultController}();function Po(e){return!!o(e)&&(!!Object.prototype.hasOwnProperty.call(e,"_controlledTransformStream")&&e instanceof To)}function Co(e){e._transformAlgorithm=void 0,e._flushAlgorithm=void 0,e._cancelAlgorithm=void 0}function qo(e,r){var t=e._controlledTransformStream,o=t._readable._readableStreamController;if(!Lt(o))throw new TypeError("Readable side is not in a state that permits enqueue");try{zt(o,r)}catch(e){throw So(t,e),t._readable._storedError}var n=function(e){return!Bt(e)}(o);n!==t._backpressure&&Ro(t,!0)}function Eo(e,r){return v(e._transformAlgorithm(r),void 0,function(r){throw go(e._controlledTransformStream,r),r})}function Wo(e){return new TypeError("TransformStreamDefaultController.prototype.".concat(e," can only be used on a TransformStreamDefaultController"))}function Oo(e){void 0!==e._finishPromise_resolve&&(e._finishPromise_resolve(),e._finishPromise_resolve=void 0,e._finishPromise_reject=void 0)}function jo(e,r){void 0!==e._finishPromise_reject&&(y(e._finishPromise),e._finishPromise_reject(r),e._finishPromise_resolve=void 0,e._finishPromise_reject=void 0)}function Bo(e){return new TypeError("TransformStream.prototype.".concat(e," can only be used on a TransformStream"))}Object.defineProperties(To.prototype,{enqueue:{enumerable:!0},error:{enumerable:!0},terminate:{enumerable:!0},desiredSize:{enumerable:!0}}),i(To.prototype.enqueue,"enqueue"),i(To.prototype.error,"error"),i(To.prototype.terminate,"terminate"),"symbol"==typeof r.toStringTag&&Object.defineProperty(To.prototype,r.toStringTag,{value:"TransformStreamDefaultController",configurable:!0}),e.ByteLengthQueuingStrategy=ao,e.CountQueuingStrategy=co,e.ReadableByteStreamController=ze,e.ReadableStream=Gt,e.ReadableStreamBYOBReader=_r,e.ReadableStreamBYOBRequest=Ae,e.ReadableStreamDefaultController=Wt,e.ReadableStreamDefaultReader=ee,e.TransformStream=vo,e.TransformStreamDefaultController=To,e.WritableStream=Br,e.WritableStreamDefaultController=$r,e.WritableStreamDefaultWriter=Hr}); diff --git a/Tests/UnitTests/CMakeLists.txt b/Tests/UnitTests/CMakeLists.txt index 2dbc7619..686b79a5 100644 --- a/Tests/UnitTests/CMakeLists.txt +++ b/Tests/UnitTests/CMakeLists.txt @@ -70,6 +70,7 @@ target_link_libraries(UnitTests PRIVATE Performance PRIVATE TextDecoder PRIVATE TextEncoder + PRIVATE Streams ${ADDITIONAL_LIBRARIES}) # See https://gitlab.kitware.com/cmake/cmake/-/issues/23543 diff --git a/Tests/UnitTests/Scripts/tests.ts b/Tests/UnitTests/Scripts/tests.ts index b286edf8..973c6420 100644 --- a/Tests/UnitTests/Scripts/tests.ts +++ b/Tests/UnitTests/Scripts/tests.ts @@ -1371,6 +1371,162 @@ describe("Console", function () { }); }); +describe("Web Streams", function () { + // Focused ports from the WHATWG Streams WPT suites at c05b4473: + // readable-streams/general.any.js and tee.any.js, + // writable-streams/write.any.js, and transform-streams/general.any.js. + it("installs the standard stream constructors", function () { + expect(ReadableStream).to.be.a("function"); + expect(WritableStream).to.be.a("function"); + expect(TransformStream).to.be.a("function"); + expect(ByteLengthQueuingStrategy).to.be.a("function"); + expect(CountQueuingStrategy).to.be.a("function"); + }); + + it("delivers queued chunks in order and closes the reader", async function () { + const stream = new ReadableStream({ + start(controller) { + controller.enqueue("a"); + controller.enqueue("b"); + controller.close(); + } + }); + const reader = stream.getReader(); + + expect(await reader.read()).to.deep.equal({ value: "a", done: false }); + expect(await reader.read()).to.deep.equal({ value: "b", done: false }); + expect(await reader.read()).to.deep.equal({ value: undefined, done: true }); + await reader.closed; + }); + + it("propagates a rejected pull to read and closed", async function () { + const failure = new Error("pull failed"); + const reader = new ReadableStream({ + pull() { + return Promise.reject(failure); + } + }).getReader(); + + let readFailure: unknown; + let closedFailure: unknown; + try { await reader.read(); } catch (error) { readFailure = error; } + try { await reader.closed; } catch (error) { closedFailure = error; } + expect(readFailure).to.equal(failure); + expect(closedFailure).to.equal(failure); + }); + + it("tees without one branch consuming the other", async function () { + const [first, second] = new ReadableStream({ + start(controller) { + controller.enqueue("a"); + controller.enqueue("b"); + controller.close(); + } + }).tee(); + const firstReader = first.getReader(); + const secondReader = second.getReader(); + + expect(await firstReader.read()).to.deep.equal({ value: "a", done: false }); + expect(await firstReader.read()).to.deep.equal({ value: "b", done: false }); + expect(await firstReader.read()).to.deep.equal({ value: undefined, done: true }); + expect(await secondReader.read()).to.deep.equal({ value: "a", done: false }); + }); + + it("waits for asynchronous writes before closing", async function () { + const stored: number[] = []; + const writable = new WritableStream({ + write(chunk) { + return Promise.resolve().then(() => stored.push(chunk)); + } + }); + const writer = writable.getWriter(); + + writer.write(1); + writer.write(2); + await writer.close(); + expect(stored).to.deep.equal([1, 2]); + }); + + it("applies transform output and backpressure", async function () { + const transform = new TransformStream({ + transform(chunk, controller) { + controller.enqueue(chunk.toUpperCase()); + } + }); + const writer = transform.writable.getWriter(); + const reader = transform.readable.getReader(); + const write = writer.write("native"); + + expect(await reader.read()).to.deep.equal({ value: "NATIVE", done: false }); + await write; + await writer.close(); + expect(await reader.read()).to.deep.equal({ value: undefined, done: true }); + }); + + it("supports BYOB reads from byte streams", async function () { + let sent = false; + const stream = new ReadableStream({ + type: "bytes", + pull(controller) { + if (!sent) { + sent = true; + controller.enqueue(new Uint8Array([8, 241, 48, 123, 151])); + controller.close(); + } + } + } as any); + const reader = stream.getReader({ mode: "byob" }); + const result = await reader.read(new Uint8Array(8)); + + expect(result.done).to.equal(false); + expect(Array.from(result.value!)).to.deep.equal([8, 241, 48, 123, 151]); + expect((await reader.read(new Uint8Array(8))).done).to.equal(true); + }); + + // Ported from Firefox's dom/streams/test/xpcshell/subclassing.js. + it("supports subclassed streams, readers, and queuing strategies", async function () { + class SubclassedStream extends ReadableStream {} + class SubclassedStrategy extends CountQueuingStrategy {} + const stream = new SubclassedStream({ + start(controller) { + controller.enqueue("first"); + controller.close(); + } + }); + const Reader = stream.getReader().constructor as typeof ReadableStreamDefaultReader; + class SubclassedReader extends Reader {} + + expect(stream).to.be.instanceOf(ReadableStream); + expect(new SubclassedStrategy({ highWaterMark: 4 }).highWaterMark).to.equal(4); + + const secondStream = new ReadableStream({ + start(controller) { + controller.enqueue("second"); + controller.close(); + } + }); + const reader = new SubclassedReader(secondStream); + expect(await reader.read()).to.deep.equal({ value: "second", done: false }); + }); + + // Ported from Chromium's http/tests/streams/chromium/transform-stream-enqueue.html. + it("rejects enqueues after a transform is terminated or errored", function () { + expect(() => new TransformStream({ + start(controller) { + controller.terminate(); + controller.enqueue("late"); + } + })).to.throw(TypeError); + + expect(() => new TransformStream({ + start(controller) { + controller.error(new Error("failed")); + controller.enqueue("late"); + } + })).to.throw(TypeError); + }); +}); + describe("Blob", function () { let emptyBlobs: Blob[], helloBlobs: Blob[], stringBlob: Blob, typedArrayBlob: Blob, arrayBufferBlob: Blob, blobBlob: Blob; diff --git a/Tests/UnitTests/Shared/Shared.cpp b/Tests/UnitTests/Shared/Shared.cpp index a920fa1f..e08d1f4d 100644 --- a/Tests/UnitTests/Shared/Shared.cpp +++ b/Tests/UnitTests/Shared/Shared.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +91,7 @@ TEST(JavaScript, All) Babylon::Polyfills::File::Initialize(env); Babylon::Polyfills::TextDecoder::Initialize(env); Babylon::Polyfills::TextEncoder::Initialize(env); + Babylon::Polyfills::Streams::Initialize(env); auto setExitCodeCallback = Napi::Function::New( env, [&exitCodePromise](const Napi::CallbackInfo& info) { @@ -111,6 +113,30 @@ TEST(JavaScript, All) EXPECT_EQ(exitCode, 0); } +TEST(Streams, PreservesHostConstructorsAndIsIdempotent) +{ + Babylon::AppRuntime runtime{}; + std::promise done; + + runtime.Dispatch([&done](Napi::Env env) { + auto global = env.Global(); + const auto hostReadableStream = Napi::Function::New(env, [](const Napi::CallbackInfo&) {}, "HostReadableStream"); + global.Set("ReadableStream", hostReadableStream); + + Babylon::Polyfills::Streams::Initialize(env); + EXPECT_TRUE(global.Get("ReadableStream").StrictEquals(hostReadableStream)); + EXPECT_TRUE(global.Get("TransformStream").IsFunction()); + + const auto installedTransformStream = global.Get("TransformStream"); + Babylon::Polyfills::Streams::Initialize(env); + EXPECT_TRUE(global.Get("ReadableStream").StrictEquals(hostReadableStream)); + EXPECT_TRUE(global.Get("TransformStream").StrictEquals(installedTransformStream)); + done.set_value(); + }); + + done.get_future().get(); +} + TEST(Console, Log) { Babylon::AppRuntime runtime{}; From 3bcad239c99e1376f0e6901b32ba933f0c1865dc Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Tue, 21 Jul 2026 23:10:42 -0700 Subject: [PATCH 2/4] Add browser-compatible compression streams Add optional CompressionStream and DecompressionStream polyfills for gzip, deflate, and raw deflate on top of WHATWG Streams. Reuse one native output buffer per active codec, borrow input views only synchronously, and defer JavaScript enqueue callbacks until zlib has finished consuming each input. Cover constructor and BufferSource behavior, split and empty chunks, large flushes, corrupt/truncated/trailing input, reentrant input mutation, and repeated stream teardown with focused WPT and browser-engine regression tests. Preserve host constructors and use platform zlib where available with a pinned fallback. --- CMakeLists.txt | 1 + Polyfills/CMakeLists.txt | 7 + Polyfills/Compression/CMakeLists.txt | 59 +++ .../Include/Babylon/Polyfills/Compression.h | 11 + Polyfills/Compression/README.md | 16 + Polyfills/Compression/Source/Compression.cpp | 407 ++++++++++++++++++ .../Compression/Source/CompressionPolyfill.js | 137 ++++++ .../Source/CompressionScripts.h.in | 8 + Polyfills/Compression/ThirdParty/zlib/LICENSE | 22 + Tests/UnitTests/CMakeLists.txt | 1 + Tests/UnitTests/Scripts/tests.ts | 272 ++++++++++++ Tests/UnitTests/Shared/Shared.cpp | 25 ++ 12 files changed, 966 insertions(+) create mode 100644 Polyfills/Compression/CMakeLists.txt create mode 100644 Polyfills/Compression/Include/Babylon/Polyfills/Compression.h create mode 100644 Polyfills/Compression/README.md create mode 100644 Polyfills/Compression/Source/Compression.cpp create mode 100644 Polyfills/Compression/Source/CompressionPolyfill.js create mode 100644 Polyfills/Compression/Source/CompressionScripts.h.in create mode 100644 Polyfills/Compression/ThirdParty/zlib/LICENSE diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d27daf7..85dbc47e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,7 @@ option(JSRUNTIMEHOST_POLYFILL_PERFORMANCE "Include JsRuntimeHost Polyfill Perfor option(JSRUNTIMEHOST_POLYFILL_TEXTDECODER "Include JsRuntimeHost Polyfill TextDecoder." ON) option(JSRUNTIMEHOST_POLYFILL_TEXTENCODER "Include JsRuntimeHost Polyfill TextEncoder." ON) option(JSRUNTIMEHOST_POLYFILL_STREAMS "Include JsRuntimeHost Polyfill Web Streams." ON) +option(JSRUNTIMEHOST_POLYFILL_COMPRESSION "Include JsRuntimeHost Polyfills CompressionStream and DecompressionStream." ON) # Sanitizers option(ENABLE_SANITIZERS "Enable AddressSanitizer and UBSan" OFF) diff --git a/Polyfills/CMakeLists.txt b/Polyfills/CMakeLists.txt index c952cf27..3368375f 100644 --- a/Polyfills/CMakeLists.txt +++ b/Polyfills/CMakeLists.txt @@ -49,3 +49,10 @@ endif() if(JSRUNTIMEHOST_POLYFILL_STREAMS) add_subdirectory(Streams) endif() + +if(JSRUNTIMEHOST_POLYFILL_COMPRESSION) + if(NOT JSRUNTIMEHOST_POLYFILL_STREAMS) + message(FATAL_ERROR "The Compression polyfill requires the Streams polyfill.") + endif() + add_subdirectory(Compression) +endif() diff --git a/Polyfills/Compression/CMakeLists.txt b/Polyfills/Compression/CMakeLists.txt new file mode 100644 index 00000000..d049f155 --- /dev/null +++ b/Polyfills/Compression/CMakeLists.txt @@ -0,0 +1,59 @@ +if(APPLE) + # FindZLIB exposes the SDK's usr/include as an explicit system include on + # recent Xcode versions. That breaks libc++'s include_next ordering. The + # header and library are both SDK-provided, so only add the link library. + find_library(JSRUNTIMEHOST_COMPRESSION_ZLIB_LIBRARY NAMES z REQUIRED) + set(JSRUNTIMEHOST_COMPRESSION_ZLIB_TARGET "${JSRUNTIMEHOST_COMPRESSION_ZLIB_LIBRARY}") +elseif(TARGET ZLIB::ZLIB) + set(JSRUNTIMEHOST_COMPRESSION_ZLIB_TARGET ZLIB::ZLIB) +elseif(TARGET zlibstatic) + set(JSRUNTIMEHOST_COMPRESSION_ZLIB_TARGET zlibstatic) +elseif(TARGET zlib) + set(JSRUNTIMEHOST_COMPRESSION_ZLIB_TARGET zlib) +else() + find_package(ZLIB QUIET) + if(ZLIB_FOUND) + set(JSRUNTIMEHOST_COMPRESSION_ZLIB_TARGET ZLIB::ZLIB) + else() + set(ZLIB_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + set(SKIP_INSTALL_ALL ON CACHE BOOL "" FORCE) + FetchContent_Declare(jsruntimehost_zlib + GIT_REPOSITORY https://github.com/madler/zlib.git + GIT_TAG 51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf + EXCLUDE_FROM_ALL) + FetchContent_MakeAvailable_With_Message(jsruntimehost_zlib) + set(JSRUNTIMEHOST_COMPRESSION_ZLIB_TARGET zlibstatic) + set_property(TARGET zlibstatic PROPERTY FOLDER Dependencies) + endif() +endif() + +set(COMPRESSION_POLYFILL_FILE "${CMAKE_CURRENT_SOURCE_DIR}/Source/CompressionPolyfill.js") +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${COMPRESSION_POLYFILL_FILE}") +file(READ "${COMPRESSION_POLYFILL_FILE}" COMPRESSION_POLYFILL_SOURCE) +configure_file( + "Source/CompressionScripts.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/Generated/CompressionScripts.h" + @ONLY) + +set(SOURCES + "Include/Babylon/Polyfills/Compression.h" + "README.md" + "Source/Compression.cpp" + "Source/CompressionPolyfill.js" + "Source/CompressionScripts.h.in" + "ThirdParty/zlib/LICENSE") + +add_library(Compression ${SOURCES}) +warnings_as_errors(Compression) + +target_include_directories(Compression + PUBLIC "Include" + PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/Generated") + +target_link_libraries(Compression + PUBLIC JsRuntime + PUBLIC Streams + PRIVATE ${JSRUNTIMEHOST_COMPRESSION_ZLIB_TARGET}) + +set_property(TARGET Compression PROPERTY FOLDER Polyfills) +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES}) diff --git a/Polyfills/Compression/Include/Babylon/Polyfills/Compression.h b/Polyfills/Compression/Include/Babylon/Polyfills/Compression.h new file mode 100644 index 00000000..44cb9693 --- /dev/null +++ b/Polyfills/Compression/Include/Babylon/Polyfills/Compression.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +namespace Babylon::Polyfills::Compression +{ + // Installs CompressionStream and DecompressionStream when the host does + // not already provide them. The Streams polyfill is initialized first. + void BABYLON_API Initialize(Napi::Env env); +} diff --git a/Polyfills/Compression/README.md b/Polyfills/Compression/README.md new file mode 100644 index 00000000..78a0d424 --- /dev/null +++ b/Polyfills/Compression/README.md @@ -0,0 +1,16 @@ +# Compression streams + +This optional polyfill installs the browser `CompressionStream` and +`DecompressionStream` interfaces when the JavaScript host does not already +provide them. It supports the interoperable `deflate`, `deflate-raw`, and +`gzip` formats using zlib and the WHATWG Streams API. + +Input `BufferSource` memory is borrowed only for the duration of a synchronous +codec call. Output is accumulated before invoking JavaScript so an enqueue +callback cannot invalidate an input buffer still in use. A native 64 KiB +scratch buffer is reused for the lifetime of each active stream; each emitted +`Uint8Array` receives one exact-size copy into JavaScript-owned memory. + +The module uses an existing CMake zlib target or the platform zlib package when +available. It fetches pinned zlib 1.3.1 only when no zlib package is present. +The zlib license is included under `ThirdParty/zlib`. diff --git a/Polyfills/Compression/Source/Compression.cpp b/Polyfills/Compression/Source/Compression.cpp new file mode 100644 index 00000000..aab2ab10 --- /dev/null +++ b/Polyfills/Compression/Source/Compression.cpp @@ -0,0 +1,407 @@ +#include "CompressionScripts.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Babylon::Polyfills::Internal +{ + namespace + { + constexpr size_t OUTPUT_BUFFER_SIZE = 64 * 1024; + + int WindowBits(const std::string& format) + { + if (format == "deflate") + { + return 15; + } + if (format == "deflate-raw") + { + return -15; + } + if (format == "gzip") + { + return 15 + 16; + } + return 0; + } + + class CompressionCodec final : public Napi::ObjectWrap + { + public: + static Napi::Function CreateConstructor(Napi::Env env) + { + return DefineClass( + env, + "NativeCompressionCodec", + { + InstanceMethod("transform", &CompressionCodec::Transform), + InstanceMethod("finish", &CompressionCodec::Finish), + InstanceMethod("close", &CompressionCodec::Close), + }); + } + + explicit CompressionCodec(const Napi::CallbackInfo& info) + : Napi::ObjectWrap{info} + { + auto env = info.Env(); + if (info.Length() < 2 || !info[0].IsString()) + { + throw Napi::TypeError::New(env, "Invalid compression codec arguments"); + } + + const auto format = info[0].As().Utf8Value(); + const auto windowBits = WindowBits(format); + if (windowBits == 0) + { + throw Napi::TypeError::New(env, "Unsupported compression format"); + } + + m_compressing = info[1].ToBoolean().Value(); + const auto result = m_compressing + ? deflateInit2(&m_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, windowBits, 8, Z_DEFAULT_STRATEGY) + : inflateInit2(&m_stream, windowBits); + if (result != Z_OK) + { + throw Napi::Error::New(env, "Unable to initialize the compression codec"); + } + m_initialized = true; + } + + ~CompressionCodec() override + { + EndStream(); + } + + private: + Napi::Value Transform(const Napi::CallbackInfo& info) + { + auto env = info.Env(); + if (m_closed || m_finished) + { + throw Napi::TypeError::New(env, "The compression stream is no longer writable"); + } + if (info.Length() < 2 || !info[0].IsTypedArray() || !info[1].IsFunction()) + { + throw Napi::TypeError::New(env, "Invalid compression transform arguments"); + } + + const auto input = info[0].As(); + if (input.TypedArrayType() != napi_uint8_array) + { + throw Napi::TypeError::New(env, "Compression input must be a Uint8Array"); + } + + const auto bytes = info[0].As(); + if (bytes.ByteLength() > 0) + { + if (!Process(bytes.Data(), bytes.ByteLength(), false, info[1].As())) + { + return {}; + } + } + return env.Undefined(); + } + + Napi::Value Finish(const Napi::CallbackInfo& info) + { + auto env = info.Env(); + if (!m_closed && !m_finished) + { + if (info.Length() < 1 || !info[0].IsFunction()) + { + throw Napi::TypeError::New(env, "Invalid compression finish arguments"); + } + if (!Process(nullptr, 0, true, info[0].As())) + { + return {}; + } + } + return env.Undefined(); + } + + Napi::Value Close(const Napi::CallbackInfo& info) + { + m_closed = true; + EndStream(); + ReleasePendingStorage(); + return info.Env().Undefined(); + } + + void EnsureOutputBuffer() + { + if (!m_outputBuffer) + { + m_outputBuffer = std::make_unique(OUTPUT_BUFFER_SIZE); + } + } + + void CaptureOutput(Napi::Env env, size_t byteLength) + { + if (byteLength == 0) + { + return; + } + + auto output = Napi::Uint8Array::New(env, byteLength); + std::memcpy(output.Data(), m_outputBuffer.get(), byteLength); + m_pendingOutput.emplace_back(std::move(output)); + } + + void EnqueuePending(const Napi::Function& enqueue) + { + for (const auto& output : m_pendingOutput) + { + enqueue.Call({output}); + } + m_pendingOutput.clear(); + } + + bool Process(const uint8_t* input, size_t inputLength, bool finishing, const Napi::Function& enqueue) + { + auto env = enqueue.Env(); + if (!m_initialized) + { + throw Napi::TypeError::New(env, "The compression stream is no longer writable"); + } + + EnsureOutputBuffer(); + m_pendingOutput.clear(); + + const uint8_t* inputCursor = input; + size_t inputRemaining = inputLength; + bool reachedEnd{}; + bool closeAfterEnqueue{}; + std::string failure; + + try + { + while (true) + { + if (m_stream.avail_in == 0 && inputRemaining > 0) + { + const auto nextLength = std::min( + inputRemaining, + static_cast(std::numeric_limits::max())); + m_stream.next_in = const_cast(reinterpret_cast(inputCursor)); + m_stream.avail_in = static_cast(nextLength); + inputCursor += nextLength; + inputRemaining -= nextLength; + } + + m_stream.next_out = reinterpret_cast(m_outputBuffer.get()); + m_stream.avail_out = static_cast(OUTPUT_BUFFER_SIZE); + + const auto inputBefore = m_stream.avail_in; + const auto result = m_compressing + ? deflate(&m_stream, finishing ? Z_FINISH : Z_NO_FLUSH) + : inflate(&m_stream, finishing ? Z_FINISH : Z_NO_FLUSH); + const auto produced = OUTPUT_BUFFER_SIZE - m_stream.avail_out; + CaptureOutput(env, produced); + + if (result == Z_STREAM_END) + { + reachedEnd = true; + closeAfterEnqueue = true; + if (!m_compressing && (m_stream.avail_in > 0 || inputRemaining > 0)) + { + failure = "Unexpected input after the end of the compressed stream"; + } + break; + } + + if (result == Z_DATA_ERROR || result == Z_NEED_DICT) + { + failure = m_stream.msg != nullptr + ? std::string{"The compressed data is invalid: "} + m_stream.msg + : "The compressed data is invalid"; + closeAfterEnqueue = true; + break; + } + if (result == Z_MEM_ERROR) + { + failure = "The compression codec ran out of memory"; + closeAfterEnqueue = true; + break; + } + if (result != Z_OK && result != Z_BUF_ERROR) + { + failure = "The compression codec entered an invalid state"; + closeAfterEnqueue = true; + break; + } + + const bool madeProgress = produced > 0 || m_stream.avail_in < inputBefore; + const bool hasInput = m_stream.avail_in > 0 || inputRemaining > 0; + if (!finishing && !hasInput && m_stream.avail_out > 0) + { + break; + } + if (!madeProgress && result == Z_BUF_ERROR) + { + if (finishing && !m_compressing) + { + failure = "The compressed input ended before the end of the stream"; + } + else + { + failure = "The compression codec could not make progress"; + } + closeAfterEnqueue = true; + break; + } + if (!madeProgress && finishing) + { + failure = m_compressing + ? "The compression codec could not finish" + : "The compressed input ended before the end of the stream"; + closeAfterEnqueue = true; + break; + } + } + } + catch (...) + { + ResetZlibPointers(); + m_pendingOutput.clear(); + m_closed = true; + EndStream(); + ReleasePendingStorage(); + throw; + } + + ResetZlibPointers(); + if (closeAfterEnqueue) + { + EndStream(); + } + + try + { + // Complete zlib's access to the borrowed input before this + // callback can run JavaScript and mutate or detach it. + EnqueuePending(enqueue); + } + catch (...) + { + m_closed = true; + EndStream(); + ReleasePendingStorage(); + throw; + } + + if (!failure.empty()) + { + m_closed = true; + ReleasePendingStorage(); + // This error is reported after EnqueuePending has called + // back into JavaScript. Setting the pending exception + // directly avoids a second C++ exception conversion in + // Node-API backends with reentrant callback contexts. + static_cast(napi_throw_type_error(env, nullptr, failure.c_str())); + return false; + } + + if (reachedEnd || finishing) + { + m_finished = true; + EndStream(); + ReleasePendingStorage(); + } + return true; + } + + void ResetZlibPointers() noexcept + { + m_stream.next_in = Z_NULL; + m_stream.avail_in = 0; + m_stream.next_out = Z_NULL; + m_stream.avail_out = 0; + } + + void EndStream() noexcept + { + if (m_initialized) + { + if (m_compressing) + { + deflateEnd(&m_stream); + } + else + { + inflateEnd(&m_stream); + } + m_initialized = false; + } + m_outputBuffer.reset(); + } + + void ReleasePendingStorage() + { + m_pendingOutput.clear(); + std::vector{}.swap(m_pendingOutput); + } + + z_stream m_stream{}; + std::unique_ptr m_outputBuffer; + std::vector m_pendingOutput; + bool m_compressing{}; + bool m_initialized{}; + bool m_finished{}; + bool m_closed{}; + }; + } +} + +namespace Babylon::Polyfills::Compression +{ + void BABYLON_API Initialize(Napi::Env env) + { + Streams::Initialize(env); + + Napi::HandleScope scope{env}; + auto global = env.Global(); + const auto compressionStream = global.Get("CompressionStream"); + const auto decompressionStream = global.Get("DecompressionStream"); + if (!compressionStream.IsUndefined() && !decompressionStream.IsUndefined()) + { + return; + } + + if (!global.Get("TransformStream").IsFunction()) + { + throw Napi::Error::New(env, "Compression streams require TransformStream"); + } + + const auto nativeConstructor = Internal::CompressionCodec::CreateConstructor(env); + const auto factory = Napi::Eval( + env, + Internal::CompressionScripts::Polyfill, + "jsruntimehost://compression-polyfill.js") + .As(); + const auto exports = factory.Call({nativeConstructor}).As(); + + if (compressionStream.IsUndefined()) + { + global.Set("CompressionStream", exports.Get("CompressionStream")); + } + if (decompressionStream.IsUndefined()) + { + global.Set("DecompressionStream", exports.Get("DecompressionStream")); + } + } +} diff --git a/Polyfills/Compression/Source/CompressionPolyfill.js b/Polyfills/Compression/Source/CompressionPolyfill.js new file mode 100644 index 00000000..92c7c468 --- /dev/null +++ b/Polyfills/Compression/Source/CompressionPolyfill.js @@ -0,0 +1,137 @@ +(function() { + "use strict"; + + return function createCompressionPolyfill(NativeCompressionCodec) { + var compressionStates = new WeakMap(); + var decompressionStates = new WeakMap(); + + function normalizeFormat(value) { + // String concatenation follows Web IDL's DOMString conversion, + // including throwing for Symbol values and propagating user + // conversion exceptions. + var format = value + ""; + if (format !== "deflate" && format !== "deflate-raw" && format !== "gzip") { + throw new TypeError("Unsupported compression format: '" + format + "'"); + } + return format; + } + + function toUint8Array(chunk) { + if (chunk instanceof ArrayBuffer) { + return new Uint8Array(chunk); + } + if (ArrayBuffer.isView(chunk) && chunk.buffer instanceof ArrayBuffer) { + return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength); + } + throw new TypeError("Compression stream chunks must be BufferSource values"); + } + + function initialize(instance, states, format, compressing) { + var codec = new NativeCompressionCodec(format, compressing); + var enqueue; + var transform = new TransformStream({ + start: function(controller) { + enqueue = controller.enqueue.bind(controller); + }, + transform: function(chunk) { + var bytes = toUint8Array(chunk); + if (bytes.byteLength === 0) { + return; + } + + try { + codec.transform(bytes, enqueue); + } catch (error) { + codec.close(); + codec = null; + enqueue = null; + throw error; + } + }, + flush: function() { + try { + codec.finish(enqueue); + } finally { + codec.close(); + codec = null; + enqueue = null; + } + } + }); + + states.set(instance, { + readable: transform.readable, + writable: transform.writable + }); + } + + function getState(states, value) { + var state = states.get(value); + if (!state) { + throw new TypeError("Illegal invocation"); + } + return state; + } + + function CompressionStream(format) { + if (!(this instanceof CompressionStream)) { + throw new TypeError("CompressionStream must be constructed with new"); + } + if (arguments.length === 0) { + throw new TypeError("CompressionStream requires a format"); + } + initialize(this, compressionStates, normalizeFormat(format), true); + } + + function DecompressionStream(format) { + if (!(this instanceof DecompressionStream)) { + throw new TypeError("DecompressionStream must be constructed with new"); + } + if (arguments.length === 0) { + throw new TypeError("DecompressionStream requires a format"); + } + initialize(this, decompressionStates, normalizeFormat(format), false); + } + + Object.defineProperties(CompressionStream.prototype, { + readable: { + configurable: true, + enumerable: true, + get: function() { return getState(compressionStates, this).readable; } + }, + writable: { + configurable: true, + enumerable: true, + get: function() { return getState(compressionStates, this).writable; } + } + }); + Object.defineProperties(DecompressionStream.prototype, { + readable: { + configurable: true, + enumerable: true, + get: function() { return getState(decompressionStates, this).readable; } + }, + writable: { + configurable: true, + enumerable: true, + get: function() { return getState(decompressionStates, this).writable; } + } + }); + + if (typeof Symbol === "function" && Symbol.toStringTag) { + Object.defineProperty(CompressionStream.prototype, Symbol.toStringTag, { + configurable: true, + value: "CompressionStream" + }); + Object.defineProperty(DecompressionStream.prototype, Symbol.toStringTag, { + configurable: true, + value: "DecompressionStream" + }); + } + + return { + CompressionStream: CompressionStream, + DecompressionStream: DecompressionStream + }; + }; +})() diff --git a/Polyfills/Compression/Source/CompressionScripts.h.in b/Polyfills/Compression/Source/CompressionScripts.h.in new file mode 100644 index 00000000..abee928f --- /dev/null +++ b/Polyfills/Compression/Source/CompressionScripts.h.in @@ -0,0 +1,8 @@ +#pragma once + +namespace Babylon::Polyfills::Internal::CompressionScripts +{ + inline constexpr char Polyfill[] = R"JSRHCOMPRESSION( +@COMPRESSION_POLYFILL_SOURCE@ +)JSRHCOMPRESSION"; +} diff --git a/Polyfills/Compression/ThirdParty/zlib/LICENSE b/Polyfills/Compression/ThirdParty/zlib/LICENSE new file mode 100644 index 00000000..ab8ee6f7 --- /dev/null +++ b/Polyfills/Compression/ThirdParty/zlib/LICENSE @@ -0,0 +1,22 @@ +Copyright notice: + + (C) 1995-2022 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu diff --git a/Tests/UnitTests/CMakeLists.txt b/Tests/UnitTests/CMakeLists.txt index 686b79a5..816b8ee2 100644 --- a/Tests/UnitTests/CMakeLists.txt +++ b/Tests/UnitTests/CMakeLists.txt @@ -71,6 +71,7 @@ target_link_libraries(UnitTests PRIVATE TextDecoder PRIVATE TextEncoder PRIVATE Streams + PRIVATE Compression ${ADDITIONAL_LIBRARIES}) # See https://gitlab.kitware.com/cmake/cmake/-/issues/23543 diff --git a/Tests/UnitTests/Scripts/tests.ts b/Tests/UnitTests/Scripts/tests.ts index 973c6420..14ecb5e9 100644 --- a/Tests/UnitTests/Scripts/tests.ts +++ b/Tests/UnitTests/Scripts/tests.ts @@ -1527,6 +1527,278 @@ describe("Web Streams", function () { }); }); +describe("Compression streams", function () { + this.timeout(20000); + + type SupportedCompressionFormat = "deflate" | "deflate-raw" | "gzip"; + type CompressionTransform = { + readable: ReadableStream; + writable: WritableStream; + }; + + const formats: SupportedCompressionFormat[] = ["deflate", "deflate-raw", "gzip"]; + const expectedOutput = new TextEncoder().encode("expected output"); + const compressedFixtures: Array<[SupportedCompressionFormat, Uint8Array]> = [ + ["deflate", new Uint8Array([120, 156, 75, 173, 40, 72, 77, 46, 73, 77, 81, 200, 47, 45, 41, 40, 45, 1, 0, 48, 173, 6, 36])], + ["gzip", new Uint8Array([31, 139, 8, 0, 0, 0, 0, 0, 0, 3, 75, 173, 40, 72, 77, 46, 73, 77, 81, 200, 47, 45, 41, 40, 45, 1, 0, 176, 1, 57, 179, 15, 0, 0, 0])], + ["deflate-raw", new Uint8Array([75, 173, 40, 72, 77, 46, 73, 77, 81, 200, 47, 45, 41, 40, 45, 1, 0])], + ]; + + async function concatenateStream(readable: ReadableStream): Promise { + const reader = readable.getReader(); + const chunks: Uint8Array[] = []; + let byteLength = 0; + while (true) { + const result = await reader.read(); + if (result.done) { + break; + } + expect(result.value).to.be.instanceOf(Uint8Array); + chunks.push(result.value); + byteLength += result.value.byteLength; + } + + const output = new Uint8Array(byteLength); + let offset = 0; + for (const chunk of chunks) { + output.set(chunk, offset); + offset += chunk.byteLength; + } + return output; + } + + async function transformChunks(transform: CompressionTransform, chunks: BufferSource[]): Promise { + const output = concatenateStream(transform.readable); + const writer = transform.writable.getWriter(); + for (const chunk of chunks) { + await writer.write(chunk); + } + await writer.close(); + return output; + } + + async function roundTrip(input: Uint8Array, format: SupportedCompressionFormat): Promise { + const compressed = await transformChunks(new CompressionStream(format), [input]); + return transformChunks(new DecompressionStream(format), [compressed]); + } + + it("exposes browser-shaped constructors, properties, and brand checks", function () { + const compression = new CompressionStream("gzip"); + const decompression = new DecompressionStream("gzip"); + expect(compression.readable).to.be.instanceOf(ReadableStream); + expect(compression.writable).to.be.instanceOf(WritableStream); + expect(decompression.readable).to.be.instanceOf(ReadableStream); + expect(decompression.writable).to.be.instanceOf(WritableStream); + expect(String(compression)).to.equal("[object CompressionStream]"); + expect(String(decompression)).to.equal("[object DecompressionStream]"); + + const compressionReadable = Object.getOwnPropertyDescriptor(CompressionStream.prototype, "readable")!.get!; + const decompressionWritable = Object.getOwnPropertyDescriptor(DecompressionStream.prototype, "writable")!.get!; + expect(() => compressionReadable.call({})).to.throw(TypeError); + expect(() => decompressionWritable.call({})).to.throw(TypeError); + }); + + // Focused ports from WPT compression/*-constructor-error.any.js. + it("validates and converts constructor formats", function () { + expect(() => new CompressionStream()).to.throw(TypeError); + expect(() => new DecompressionStream()).to.throw(TypeError); + expect(() => new CompressionStream("invalid" as any)).to.throw(TypeError); + expect(() => new DecompressionStream("GZIP" as any)).to.throw(TypeError); + expect(() => new CompressionStream(Symbol("gzip") as any)).to.throw(TypeError); + + const failure = new Error("format conversion failed"); + let thrown: unknown; + try { + new DecompressionStream({ toString() { throw failure; } } as any); + } catch (error) { + thrown = error; + } + expect(thrown).to.equal(failure); + }); + + // Focused ports from WPT compression-stream.any.js, + // compression-multiple-chunks.any.js, and including-empty-chunk.any.js. + it("round-trips all supported formats across multiple and empty chunks", async function () { + const middleBacking = new Uint8Array(new TextEncoder().encode("!from browser-shaped streams!")); + const inputChunks = [ + new TextEncoder().encode("Hello "), + new Uint8Array(), + middleBacking.subarray(1, middleBacking.byteLength - 1), + ]; + const expected = new TextEncoder().encode("Hello from browser-shaped streams"); + + for (const format of formats) { + const compressed = await transformChunks(new CompressionStream(format), inputChunks); + const output = await transformChunks(new DecompressionStream(format), [compressed]); + expect(Array.from(output)).to.deep.equal(Array.from(expected)); + } + }); + + // Ported from WPT decompression-buffersource.any.js. + it("accepts ArrayBuffer, typed-array, and DataView input", async function () { + const fixtures: Array<[SupportedCompressionFormat, number[], string]> = [ + ["deflate", [120, 156, 75, 52, 48, 52, 50, 54, 49, 53, 3, 0, 8, 136, 1, 199], "a0123456"], + ["gzip", [31, 139, 8, 0, 0, 0, 0, 0, 0, 3, 75, 52, 48, 52, 2, 0, 216, 252, 63, 136, 4, 0, 0, 0], "a012"], + ["deflate-raw", [0, 6, 0, 249, 255, 65, 66, 67, 68, 69, 70, 1, 0, 0, 255, 255], "ABCDEF"], + ]; + + for (const [format, values, expected] of fixtures) { + const expectedBytes = Array.from(new TextEncoder().encode(expected)); + const makeBuffer = () => new Uint8Array(values).buffer as ArrayBuffer; + const padded = new Uint8Array(values.length + 2); + padded.set(values, 1); + const inputs: BufferSource[] = [ + makeBuffer(), + new Int8Array(makeBuffer()), + new Uint16Array(makeBuffer()), + new DataView(makeBuffer()), + new DataView(padded.buffer, 1, values.length), + ]; + for (const input of inputs) { + const output = await transformChunks(new DecompressionStream(format), [input]); + expect(Array.from(output)).to.deep.equal(expectedBytes); + } + } + }); + + // Focused ports from WPT decompression-split-chunk.any.js and + // decompression-uint8array-output.any.js. + it("decompresses input split at every small chunk boundary", async function () { + for (const [format, fixture] of compressedFixtures) { + for (let chunkSize = 1; chunkSize < 16; ++chunkSize) { + const chunks: Uint8Array[] = []; + for (let offset = 0; offset < fixture.byteLength; offset += chunkSize) { + chunks.push(fixture.slice(offset, offset + chunkSize)); + } + const output = await transformChunks(new DecompressionStream(format), chunks); + expect(Array.from(output)).to.deep.equal(Array.from(expectedOutput)); + } + } + }); + + // Ported from WPT compression-large-flush-output.any.js. + it("does not truncate output produced while closing", async function () { + const encoded = new TextEncoder().encode(JSON.stringify(Array.from({ length: 10000 }, (_, index) => index))); + const input = encoded.subarray(0, 35579); + for (const format of formats) { + expect(Array.from(await roundTrip(input, format))).to.deep.equal(Array.from(input)); + } + }); + + // WPT decompression-extra-input.any.js requires already-produced output + // to be observable before the stream reports the trailing-data error. + it("emits valid output before rejecting extra compressed input", async function () { + for (const [format, fixture] of compressedFixtures) { + const stream = new DecompressionStream(format); + const reader = stream.readable.getReader(); + const writer = stream.writable.getWriter(); + const firstRead = reader.read(); + const write = writer.write(new Uint8Array([...fixture, 0])).catch(error => error); + const first = await firstRead; + expect(first.done).to.equal(false); + expect(Array.from(first.value!)).to.deep.equal(Array.from(expectedOutput)); + + let readFailure: unknown; + try { await reader.read(); } catch (error) { readFailure = error; } + expect(readFailure).to.be.instanceOf(TypeError); + expect(await write).to.be.instanceOf(TypeError); + } + }); + + // Focused ports from WPT compression-bad-chunks.any.js and + // decompression-bad-chunks.any.js. + it("errors both sides of the transform for non-BufferSource chunks", async function () { + for (const create of [ + () => new CompressionStream("gzip"), + () => new DecompressionStream("gzip"), + ]) { + const stream = create(); + const reader = stream.readable.getReader(); + const writer = stream.writable.getWriter(); + const read = reader.read().catch(error => error); + const write = writer.write({} as any).catch(error => error); + expect(await write).to.be.instanceOf(TypeError); + expect(await read).to.be.instanceOf(TypeError); + } + }); + + // Focused ports from WPT decompression-corrupt-input.any.js and the + // truncated-input check in Chromium's InflateTransformer. + it("rejects corrupt and truncated compressed input", async function () { + for (const [format, fixture] of compressedFixtures) { + const inputs = [ + fixture.subarray(0, fixture.byteLength - 1), + new Uint8Array(fixture.map((value, index) => index === 0 ? value ^ 0xff : value)), + ]; + for (const input of inputs) { + const stream = new DecompressionStream(format); + const output = concatenateStream(stream.readable).catch(error => error); + const writer = stream.writable.getWriter(); + await writer.write(input).catch(() => undefined); + const close = writer.close().catch(error => error); + expect(await close).to.be.instanceOf(TypeError); + expect(await output).to.be.instanceOf(TypeError); + } + } + }); + + // Chromium buffers output before enqueue because enqueue may execute + // JavaScript that mutates or detaches the input still being consumed. + // This regression test forces that reentrancy without needing postMessage. + it("finishes consuming input before an enqueue callback can mutate it", async function () { + const input = new Uint8Array(256 * 1024); + let state = 0x12345678; + for (let index = 0; index < input.length; ++index) { + state ^= state << 13; + state ^= state >>> 17; + state ^= state << 5; + input[index] = state & 0xff; + } + const expected = input.slice(); + + const prototype = TransformStreamDefaultController.prototype as any; + const originalEnqueue = prototype.enqueue; + let mutated = false; + prototype.enqueue = function(chunk: unknown) { + if (!mutated) { + mutated = true; + input.fill(0); + } + return originalEnqueue.call(this, chunk); + }; + + let compressed: Uint8Array; + try { + compressed = await transformChunks(new CompressionStream("deflate"), [input]); + } finally { + prototype.enqueue = originalEnqueue; + } + + expect(mutated).to.equal(true); + const output = await transformChunks(new DecompressionStream("deflate"), [compressed!]); + expect(Array.from(output)).to.deep.equal(Array.from(expected)); + }); + + // Empty chunks are common in WebKit and WPT stream regressions. They must + // not allocate codec output or accumulate retained per-write state. + it("handles a long sequence of empty writes without retained output", async function () { + const chunks = Array.from({ length: 1024 }, () => new Uint8Array()); + const compressed = await transformChunks(new CompressionStream("gzip"), chunks); + const output = await transformChunks(new DecompressionStream("gzip"), [compressed]); + expect(output.byteLength).to.equal(0); + }); + + // Repeated short-lived streams model asset-heavy native applications. A + // completed stream must release zlib and scratch storage before JS GC. + it("releases completed codec state across repeated streams", async function () { + const fixture = compressedFixtures.find(([format]) => format === "gzip")![1]; + for (let iteration = 0; iteration < 128; ++iteration) { + const output = await transformChunks(new DecompressionStream("gzip"), [fixture]); + expect(Array.from(output)).to.deep.equal(Array.from(expectedOutput)); + } + }); +}); + describe("Blob", function () { let emptyBlobs: Blob[], helloBlobs: Blob[], stringBlob: Blob, typedArrayBlob: Blob, arrayBufferBlob: Blob, blobBlob: Blob; diff --git a/Tests/UnitTests/Shared/Shared.cpp b/Tests/UnitTests/Shared/Shared.cpp index e08d1f4d..8af57d3b 100644 --- a/Tests/UnitTests/Shared/Shared.cpp +++ b/Tests/UnitTests/Shared/Shared.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -92,6 +93,7 @@ TEST(JavaScript, All) Babylon::Polyfills::TextDecoder::Initialize(env); Babylon::Polyfills::TextEncoder::Initialize(env); Babylon::Polyfills::Streams::Initialize(env); + Babylon::Polyfills::Compression::Initialize(env); auto setExitCodeCallback = Napi::Function::New( env, [&exitCodePromise](const Napi::CallbackInfo& info) { @@ -137,6 +139,29 @@ TEST(Streams, PreservesHostConstructorsAndIsIdempotent) done.get_future().get(); } +TEST(Compression, PreservesHostConstructorsAndIsIdempotent) +{ + Babylon::AppRuntime runtime{}; + std::promise done; + + runtime.Dispatch([&done](Napi::Env env) { + auto global = env.Global(); + const auto hostCompressionStream = Napi::Function::New(env, [](const Napi::CallbackInfo&) {}, "HostCompressionStream"); + global.Set("CompressionStream", hostCompressionStream); + + Babylon::Polyfills::Compression::Initialize(env); + EXPECT_TRUE(global.Get("CompressionStream").StrictEquals(hostCompressionStream)); + EXPECT_TRUE(global.Get("DecompressionStream").IsFunction()); + + const auto installedDecompressionStream = global.Get("DecompressionStream"); + Babylon::Polyfills::Compression::Initialize(env); + EXPECT_TRUE(global.Get("CompressionStream").StrictEquals(hostCompressionStream)); + EXPECT_TRUE(global.Get("DecompressionStream").StrictEquals(installedDecompressionStream)); + done.set_value(); + }); + + done.get_future().get(); +} TEST(Console, Log) { Babylon::AppRuntime runtime{}; From 2802fe05ebe875699e2bbc46c66a5ab9297f5275 Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Wed, 22 Jul 2026 00:26:50 -0700 Subject: [PATCH 3/4] Keep Streams implementations internally consistent Replace a partial or null host Streams surface as a complete constructor suite so stream products retain compatible instanceof relationships. Preserve a complete suite on repeated initialization.\n\nHarden the native initialization test so N-API failures complete the test promise instead of hanging, and cover partial host replacement plus null handling and cross-constructor compatibility. --- Polyfills/Streams/Source/Streams.cpp | 8 ++---- Tests/UnitTests/Shared/Shared.cpp | 42 +++++++++++++++++++++------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/Polyfills/Streams/Source/Streams.cpp b/Polyfills/Streams/Source/Streams.cpp index 4552ed07..e742344f 100644 --- a/Polyfills/Streams/Source/Streams.cpp +++ b/Polyfills/Streams/Source/Streams.cpp @@ -31,7 +31,8 @@ namespace Babylon::Polyfills::Streams bool needsPonyfill{}; for (const auto name : constructorNames) { - if (global.Get(name.data()).IsUndefined()) + const auto constructor = global.Get(name.data()); + if (constructor.IsUndefined() || constructor.IsNull()) { needsPonyfill = true; break; @@ -46,10 +47,7 @@ namespace Babylon::Polyfills::Streams const auto exports = Napi::Eval(env, Internal::StreamsScripts::Ponyfill.data(), "jsruntimehost://web-streams-polyfill.js").As(); for (const auto name : constructorNames) { - if (global.Get(name.data()).IsUndefined()) - { - global.Set(name.data(), exports.Get(name.data())); - } + global.Set(name.data(), exports.Get(name.data())); } } } diff --git a/Tests/UnitTests/Shared/Shared.cpp b/Tests/UnitTests/Shared/Shared.cpp index 8af57d3b..6c05cfea 100644 --- a/Tests/UnitTests/Shared/Shared.cpp +++ b/Tests/UnitTests/Shared/Shared.cpp @@ -115,28 +115,50 @@ TEST(JavaScript, All) EXPECT_EQ(exitCode, 0); } -TEST(Streams, PreservesHostConstructorsAndIsIdempotent) +TEST(Streams, ReplacesPartialHostSuiteAndIsIdempotent) { - Babylon::AppRuntime runtime{}; - std::promise done; + std::promise done; + std::atomic_bool completed{}; + const auto complete = [&done, &completed](std::string result) { + if (!completed.exchange(true)) + { + done.set_value(std::move(result)); + } + }; - runtime.Dispatch([&done](Napi::Env env) { + Babylon::AppRuntime::Options options{}; + options.UnhandledExceptionHandler = [&complete](const Napi::Error& error) { + complete(Napi::GetErrorString(error)); + }; + Babylon::AppRuntime runtime{options}; + + runtime.Dispatch([&complete](Napi::Env env) { auto global = env.Global(); const auto hostReadableStream = Napi::Function::New(env, [](const Napi::CallbackInfo&) {}, "HostReadableStream"); global.Set("ReadableStream", hostReadableStream); + global.Set("TransformStream", env.Null()); Babylon::Polyfills::Streams::Initialize(env); - EXPECT_TRUE(global.Get("ReadableStream").StrictEquals(hostReadableStream)); - EXPECT_TRUE(global.Get("TransformStream").IsFunction()); - + const auto installedReadableStream = global.Get("ReadableStream"); const auto installedTransformStream = global.Get("TransformStream"); + EXPECT_FALSE(installedReadableStream.StrictEquals(hostReadableStream)); + if (!installedReadableStream.IsFunction() || !installedTransformStream.IsFunction()) + { + complete("Streams::Initialize did not install a complete constructor suite."); + return; + } + + const auto transform = installedTransformStream.As().New({}); + EXPECT_TRUE(transform.Get("readable").As().InstanceOf(installedReadableStream.As())); + Babylon::Polyfills::Streams::Initialize(env); - EXPECT_TRUE(global.Get("ReadableStream").StrictEquals(hostReadableStream)); + EXPECT_TRUE(global.Get("ReadableStream").StrictEquals(installedReadableStream)); EXPECT_TRUE(global.Get("TransformStream").StrictEquals(installedTransformStream)); - done.set_value(); + complete({}); }); - done.get_future().get(); + const auto error = done.get_future().get(); + EXPECT_TRUE(error.empty()) << error; } TEST(Compression, PreservesHostConstructorsAndIsIdempotent) From 557fddb489e712fbea812dfac2d4e04fa66e3631 Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Sun, 26 Jul 2026 02:14:59 -0700 Subject: [PATCH 4/4] Preserve Streams bundle bytes across source splits --- Polyfills/Streams/Source/StreamsScripts.h.in | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Polyfills/Streams/Source/StreamsScripts.h.in b/Polyfills/Streams/Source/StreamsScripts.h.in index 9bf7f5bf..126a23f7 100644 --- a/Polyfills/Streams/Source/StreamsScripts.h.in +++ b/Polyfills/Streams/Source/StreamsScripts.h.in @@ -9,9 +9,10 @@ namespace Babylon::Polyfills::Internal::StreamsScripts (function() { var exports = {}; var module = { exports: exports }; -@WEB_STREAMS_POLYFILL_SOURCE_1@ -)JSRHSTREAM"; +@WEB_STREAMS_POLYFILL_SOURCE_1@)JSRHSTREAM"; + // Keep the split transparent: inserting whitespace here can divide an + // identifier or string literal when the vendored bundle is regenerated. inline constexpr char PonyfillPart2[] = R"JSRHSTREAM(@WEB_STREAMS_POLYFILL_SOURCE_2@ return module.exports; })()