├── .glitch-assets ├── README.md ├── package.json ├── private └── streams │ ├── byte-length-queuing-strategy.js │ ├── count-queuing-strategy.js │ ├── helpers.js │ ├── queue-with-sizes.js │ ├── readable-stream.js │ ├── transform-stream.js │ ├── utils.js │ └── writable-stream.js ├── public ├── assets │ └── templates │ │ ├── body.html │ │ ├── foot.html │ │ ├── head.html │ │ └── item.html ├── data │ └── columns.json ├── manifest.json ├── scripts │ ├── DOMParser.js │ ├── dot.js │ ├── platform │ │ ├── common.js │ │ ├── node.js │ │ └── web.js │ ├── router.js │ └── routes │ │ ├── index.js │ │ ├── proxy.js │ │ └── root.js └── sw.js └── server.js /.glitch-assets: -------------------------------------------------------------------------------- 1 | {"name":"drag-in-files.svg","date":"2016-10-22T16:17:49.954Z","url":"https://cdn.hyperdev.com/drag-in-files.svg","type":"image/svg","size":7646,"imageWidth":276,"imageHeight":276,"thumbnail":"https://cdn.hyperdev.com/drag-in-files.svg","thumbnailWidth":276,"thumbnailHeight":276,"dominantColor":"rgb(102, 153, 205)","uuid":"adSBq97hhhpFNUna"} 2 | {"name":"click-me.svg","date":"2016-10-23T16:17:49.954Z","url":"https://cdn.hyperdev.com/click-me.svg","type":"image/svg","size":7116,"imageWidth":276,"imageHeight":276,"thumbnail":"https://cdn.hyperdev.com/click-me.svg","thumbnailWidth":276,"thumbnailHeight":276,"dominantColor":"rgb(243, 185, 186)","uuid":"adSBq97hhhpFNUnb"} 3 | {"name":"paste-me.svg","date":"2016-10-24T16:17:49.954Z","url":"https://cdn.hyperdev.com/paste-me.svg","type":"image/svg","size":7242,"imageWidth":276,"imageHeight":276,"thumbnail":"https://cdn.hyperdev.com/paste-me.svg","thumbnailWidth":276,"thumbnailHeight":276,"dominantColor":"rgb(42, 179, 185)","uuid":"adSBq97hhhpFNUnc"} 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Feeddeck 💯 2 | ========================= 3 | 4 | Inspired by River of news, but HTTPs only and a PWA. 5 | 6 | Attempt to create a PWA that is a news reader that renders on the server and on the client using all the same templating and logic. 7 | 8 | 9 | Progress 10 | -------- 11 | 12 | * Client logic in SW is now shared with the Server logic [done] 13 | * Templating is done via streaming templates [done] 14 | * Use WhatWG streams in node. [done] 15 | * Need to clean up a lot. [errrr.....] 16 | * Configuration file should really be an OPML file. [todo] 17 | * Cache fetched data on the client and server [done] 18 | * To render the client it still needs JS turned on. Investigate server 19 | load of RSS feeds. 20 | * This is done in the SW, will use on Server soon. 21 | 22 | 23 | Thoughts 24 | -------- 25 | 26 | If possible the server logic and the client logic should be near exactly the same. 27 | 28 | Ideally configured via a OPML file that defines the columns, that links to other OPML files that contain the feeds that should be aggregrated in this. 29 | 30 | Thoughts: 31 | 32 | * The UI should render the structure without JS. Ideally after this has been 33 | rendered, I can stream in the first batch of aggreagated and merged feeds. 34 | * The UI should then take over and update the feeds in the client as much as 35 | possible. 36 | * On reload, the server should not be hit at all, save for any updates to the 37 | OPML file. In an ideal world, the SW would be doing the work. 38 | 39 | 40 | Technical hitches: 41 | We are going to have to proxy the RSS feed requests. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "feeddeck", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "start": "node server.js" 6 | }, 7 | "dependencies": { 8 | "express": "^4.15.2", 9 | "node-fetch": "*", 10 | "text-encoding": "*", 11 | "xmldom-alpha": "*" 12 | }, 13 | "engines": { 14 | "node": "6.9.x" 15 | }, 16 | "license": "Apache-2.0", 17 | "keywords": [ 18 | "node", 19 | "glitch", 20 | "express" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /private/streams/byte-length-queuing-strategy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { createDataProperty } = require('./helpers.js'); 3 | 4 | module.exports = class ByteLengthQueuingStrategy { 5 | constructor({ highWaterMark }) { 6 | createDataProperty(this, 'highWaterMark', highWaterMark); 7 | } 8 | 9 | size(chunk) { 10 | return chunk.byteLength; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /private/streams/count-queuing-strategy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { createDataProperty } = require('./helpers.js'); 3 | 4 | module.exports = class CountQueuingStrategy { 5 | constructor({ highWaterMark }) { 6 | createDataProperty(this, 'highWaterMark', highWaterMark); 7 | } 8 | 9 | size() { 10 | return 1; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /private/streams/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('assert'); 3 | 4 | function IsPropertyKey(argument) { 5 | return typeof argument === 'string' || typeof argument === 'symbol'; 6 | } 7 | 8 | exports.typeIsObject = x => (typeof x === 'object' && x !== null) || typeof x === 'function'; 9 | 10 | exports.createDataProperty = (o, p, v) => { 11 | assert(exports.typeIsObject(o)); 12 | Object.defineProperty(o, p, { value: v, writable: true, enumerable: true, configurable: true }); 13 | }; 14 | 15 | exports.createArrayFromList = elements => { 16 | // We use arrays to represent lists, so this is basically a no-op. 17 | // Do a slice though just in case we happen to depend on the unique-ness. 18 | return elements.slice(); 19 | }; 20 | 21 | exports.ArrayBufferCopy = (dest, destOffset, src, srcOffset, n) => { 22 | new Uint8Array(dest).set(new Uint8Array(src, srcOffset, n), destOffset); 23 | }; 24 | 25 | exports.CreateIterResultObject = (value, done) => { 26 | assert(typeof done === 'boolean'); 27 | const obj = {}; 28 | Object.defineProperty(obj, 'value', { value, enumerable: true, writable: true, configurable: true }); 29 | Object.defineProperty(obj, 'done', { value: done, enumerable: true, writable: true, configurable: true }); 30 | return obj; 31 | }; 32 | 33 | exports.IsFiniteNonNegativeNumber = v => { 34 | if (Number.isNaN(v)) { 35 | return false; 36 | } 37 | if (v === Infinity) { 38 | return false; 39 | } 40 | if (v < 0) { 41 | return false; 42 | } 43 | 44 | return true; 45 | }; 46 | 47 | function Call(F, V, args) { 48 | if (typeof F !== 'function') { 49 | throw new TypeError('Argument is not a function'); 50 | } 51 | 52 | return Function.prototype.apply.call(F, V, args); 53 | } 54 | 55 | exports.InvokeOrNoop = (O, P, args) => { 56 | assert(O !== undefined); 57 | assert(IsPropertyKey(P)); 58 | assert(Array.isArray(args)); 59 | 60 | const method = O[P]; 61 | if (method === undefined) { 62 | return undefined; 63 | } 64 | 65 | return Call(method, O, args); 66 | }; 67 | 68 | exports.PromiseInvokeOrNoop = (O, P, args) => { 69 | assert(O !== undefined); 70 | assert(IsPropertyKey(P)); 71 | assert(Array.isArray(args)); 72 | try { 73 | return Promise.resolve(exports.InvokeOrNoop(O, P, args)); 74 | } catch (returnValueE) { 75 | return Promise.reject(returnValueE); 76 | } 77 | }; 78 | 79 | exports.PromiseInvokeOrPerformFallback = (O, P, args, F, argsF) => { 80 | assert(O !== undefined); 81 | assert(IsPropertyKey(P)); 82 | assert(Array.isArray(args)); 83 | assert(Array.isArray(argsF)); 84 | 85 | let method; 86 | try { 87 | method = O[P]; 88 | } catch (methodE) { 89 | return Promise.reject(methodE); 90 | } 91 | 92 | if (method === undefined) { 93 | return F(...argsF); 94 | } 95 | 96 | try { 97 | return Promise.resolve(Call(method, O, args)); 98 | } catch (e) { 99 | return Promise.reject(e); 100 | } 101 | }; 102 | 103 | // Not implemented correctly 104 | exports.TransferArrayBuffer = O => O.slice(); 105 | 106 | exports.ValidateAndNormalizeHighWaterMark = highWaterMark => { 107 | highWaterMark = Number(highWaterMark); 108 | if (Number.isNaN(highWaterMark) || highWaterMark < 0) { 109 | throw new RangeError('highWaterMark property of a queuing strategy must be non-negative and non-NaN'); 110 | } 111 | 112 | return highWaterMark; 113 | }; 114 | 115 | exports.ValidateAndNormalizeQueuingStrategy = (size, highWaterMark) => { 116 | if (size !== undefined && typeof size !== 'function') { 117 | throw new TypeError('size property of a queuing strategy must be a function'); 118 | } 119 | 120 | highWaterMark = exports.ValidateAndNormalizeHighWaterMark(highWaterMark); 121 | 122 | return { size, highWaterMark }; 123 | }; 124 | -------------------------------------------------------------------------------- /private/streams/queue-with-sizes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('assert'); 3 | const { IsFiniteNonNegativeNumber } = require('./helpers.js'); 4 | 5 | exports.DequeueValue = container => { 6 | assert('_queue' in container && '_queueTotalSize' in container, 7 | 'Spec-level failure: DequeueValue should only be used on containers with [[queue]] and [[queueTotalSize]].'); 8 | assert(container._queue.length > 0, 'Spec-level failure: should never dequeue from an empty queue.'); 9 | 10 | const pair = container._queue.shift(); 11 | container._queueTotalSize -= pair.size; 12 | if (container._queueTotalSize < 0) { 13 | container._queueTotalSize = 0; 14 | } 15 | 16 | return pair.value; 17 | }; 18 | 19 | exports.EnqueueValueWithSize = (container, value, size) => { 20 | assert('_queue' in container && '_queueTotalSize' in container, 21 | 'Spec-level failure: EnqueueValueWithSize should only be used on containers with [[queue]] and ' + 22 | '[[queueTotalSize]].'); 23 | 24 | size = Number(size); 25 | if (!IsFiniteNonNegativeNumber(size)) { 26 | throw new RangeError('Size must be a finite, non-NaN, non-negative number.'); 27 | } 28 | 29 | container._queue.push({ value, size }); 30 | container._queueTotalSize += size; 31 | }; 32 | 33 | exports.PeekQueueValue = container => { 34 | assert('_queue' in container && '_queueTotalSize' in container, 35 | 'Spec-level failure: PeekQueueValue should only be used on containers with [[queue]] and [[queueTotalSize]].'); 36 | assert(container._queue.length > 0, 'Spec-level failure: should never peek at an empty queue.'); 37 | 38 | const pair = container._queue[0]; 39 | return pair.value; 40 | }; 41 | 42 | exports.ResetQueue = container => { 43 | assert('_queue' in container && '_queueTotalSize' in container, 44 | 'Spec-level failure: ResetQueue should only be used on containers with [[queue]] and [[queueTotalSize]].'); 45 | 46 | container._queue = []; 47 | container._queueTotalSize = 0; 48 | }; 49 | -------------------------------------------------------------------------------- /private/streams/readable-stream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('assert'); 3 | const { ArrayBufferCopy, CreateIterResultObject, IsFiniteNonNegativeNumber, InvokeOrNoop, PromiseInvokeOrNoop, 4 | TransferArrayBuffer, ValidateAndNormalizeQueuingStrategy, ValidateAndNormalizeHighWaterMark } = 5 | require('./helpers.js'); 6 | const { createArrayFromList, createDataProperty, typeIsObject } = require('./helpers.js'); 7 | const { rethrowAssertionErrorRejection } = require('./utils.js'); 8 | const { DequeueValue, EnqueueValueWithSize, ResetQueue } = require('./queue-with-sizes.js'); 9 | const { AcquireWritableStreamDefaultWriter, IsWritableStream, IsWritableStreamLocked, 10 | WritableStreamAbort, WritableStreamDefaultWriterCloseWithErrorPropagation, 11 | WritableStreamDefaultWriterRelease, WritableStreamDefaultWriterWrite, WritableStreamCloseQueuedOrInFlight } = 12 | require('./writable-stream.js'); 13 | 14 | const CancelSteps = Symbol('[[CancelSteps]]'); 15 | const PullSteps = Symbol('[[PullSteps]]'); 16 | 17 | class ReadableStream { 18 | constructor(underlyingSource = {}, { size, highWaterMark } = {}) { 19 | // Exposed to controllers. 20 | this._state = 'readable'; 21 | 22 | this._reader = undefined; 23 | this._storedError = undefined; 24 | 25 | this._disturbed = false; 26 | 27 | // Initialize to undefined first because the constructor of the controller checks this 28 | // variable to validate the caller. 29 | this._readableStreamController = undefined; 30 | const type = underlyingSource.type; 31 | const typeString = String(type); 32 | if (typeString === 'bytes') { 33 | if (highWaterMark === undefined) { 34 | highWaterMark = 0; 35 | } 36 | this._readableStreamController = new ReadableByteStreamController(this, underlyingSource, highWaterMark); 37 | } else if (type === undefined) { 38 | if (highWaterMark === undefined) { 39 | highWaterMark = 1; 40 | } 41 | this._readableStreamController = new ReadableStreamDefaultController(this, underlyingSource, size, highWaterMark); 42 | } else { 43 | throw new RangeError('Invalid type is specified'); 44 | } 45 | } 46 | 47 | get locked() { 48 | if (IsReadableStream(this) === false) { 49 | throw streamBrandCheckException('locked'); 50 | } 51 | 52 | return IsReadableStreamLocked(this); 53 | } 54 | 55 | cancel(reason) { 56 | if (IsReadableStream(this) === false) { 57 | return Promise.reject(streamBrandCheckException('cancel')); 58 | } 59 | 60 | if (IsReadableStreamLocked(this) === true) { 61 | return Promise.reject(new TypeError('Cannot cancel a stream that already has a reader')); 62 | } 63 | 64 | return ReadableStreamCancel(this, reason); 65 | } 66 | 67 | getReader({ mode } = {}) { 68 | if (IsReadableStream(this) === false) { 69 | throw streamBrandCheckException('getReader'); 70 | } 71 | 72 | if (mode === undefined) { 73 | return AcquireReadableStreamDefaultReader(this); 74 | } 75 | 76 | mode = String(mode); 77 | 78 | if (mode === 'byob') { 79 | return AcquireReadableStreamBYOBReader(this); 80 | } 81 | 82 | throw new RangeError('Invalid mode is specified'); 83 | } 84 | 85 | pipeThrough({ writable, readable }, options) { 86 | const promise = this.pipeTo(writable, options); 87 | 88 | ifIsObjectAndHasAPromiseIsHandledInternalSlotSetPromiseIsHandledToTrue(promise); 89 | 90 | return readable; 91 | } 92 | 93 | pipeTo(dest, { preventClose, preventAbort, preventCancel } = {}) { 94 | if (IsReadableStream(this) === false) { 95 | return Promise.reject(streamBrandCheckException('pipeTo')); 96 | } 97 | if (IsWritableStream(dest) === false) { 98 | return Promise.reject( 99 | new TypeError('ReadableStream.prototype.pipeTo\'s first argument must be a WritableStream')); 100 | } 101 | 102 | preventClose = Boolean(preventClose); 103 | preventAbort = Boolean(preventAbort); 104 | preventCancel = Boolean(preventCancel); 105 | 106 | if (IsReadableStreamLocked(this) === true) { 107 | return Promise.reject(new TypeError('ReadableStream.prototype.pipeTo cannot be used on a locked ReadableStream')); 108 | } 109 | if (IsWritableStreamLocked(dest) === true) { 110 | return Promise.reject(new TypeError('ReadableStream.prototype.pipeTo cannot be used on a locked WritableStream')); 111 | } 112 | 113 | const reader = AcquireReadableStreamDefaultReader(this); 114 | const writer = AcquireWritableStreamDefaultWriter(dest); 115 | 116 | let shuttingDown = false; 117 | 118 | // This is used to keep track of the spec's requirement that we wait for ongoing writes during shutdown. 119 | let currentWrite = Promise.resolve(); 120 | 121 | return new Promise((resolve, reject) => { 122 | // Using reader and writer, read all chunks from this and write them to dest 123 | // - Backpressure must be enforced 124 | // - Shutdown must stop all activity 125 | function pipeLoop() { 126 | currentWrite = Promise.resolve(); 127 | 128 | if (shuttingDown === true) { 129 | return Promise.resolve(); 130 | } 131 | 132 | return writer._readyPromise.then(() => { 133 | return ReadableStreamDefaultReaderRead(reader).then(({ value, done }) => { 134 | if (done === true) { 135 | return; 136 | } 137 | 138 | currentWrite = WritableStreamDefaultWriterWrite(writer, value).catch(() => {}); 139 | }); 140 | }) 141 | .then(pipeLoop); 142 | } 143 | 144 | // Errors must be propagated forward 145 | isOrBecomesErrored(this, reader._closedPromise, storedError => { 146 | if (preventAbort === false) { 147 | shutdownWithAction(() => WritableStreamAbort(dest, storedError), true, storedError); 148 | } else { 149 | shutdown(true, storedError); 150 | } 151 | }); 152 | 153 | // Errors must be propagated backward 154 | isOrBecomesErrored(dest, writer._closedPromise, storedError => { 155 | if (preventCancel === false) { 156 | shutdownWithAction(() => ReadableStreamCancel(this, storedError), true, storedError); 157 | } else { 158 | shutdown(true, storedError); 159 | } 160 | }); 161 | 162 | // Closing must be propagated forward 163 | isOrBecomesClosed(this, reader._closedPromise, () => { 164 | if (preventClose === false) { 165 | shutdownWithAction(() => WritableStreamDefaultWriterCloseWithErrorPropagation(writer)); 166 | } else { 167 | shutdown(); 168 | } 169 | }); 170 | 171 | // Closing must be propagated backward 172 | if (WritableStreamCloseQueuedOrInFlight(dest) === true || dest._state === 'closed') { 173 | const destClosed = new TypeError('the destination writable stream closed before all data could be piped to it'); 174 | 175 | if (preventCancel === false) { 176 | shutdownWithAction(() => ReadableStreamCancel(this, destClosed), true, destClosed); 177 | } else { 178 | shutdown(true, destClosed); 179 | } 180 | } 181 | 182 | pipeLoop().catch(err => { 183 | currentWrite = Promise.resolve(); 184 | rethrowAssertionErrorRejection(err); 185 | }); 186 | 187 | function waitForWritesToFinish() { 188 | // Another write may have started while we were waiting on this currentWrite, so we have to be sure to wait 189 | // for that too. 190 | const oldCurrentWrite = currentWrite; 191 | return currentWrite.then(() => oldCurrentWrite !== currentWrite ? waitForWritesToFinish() : undefined); 192 | } 193 | 194 | function isOrBecomesErrored(stream, promise, action) { 195 | if (stream._state === 'errored') { 196 | action(stream._storedError); 197 | } else { 198 | promise.catch(action).catch(rethrowAssertionErrorRejection); 199 | } 200 | } 201 | 202 | function isOrBecomesClosed(stream, promise, action) { 203 | if (stream._state === 'closed') { 204 | action(); 205 | } else { 206 | promise.then(action).catch(rethrowAssertionErrorRejection); 207 | } 208 | } 209 | 210 | function shutdownWithAction(action, originalIsError, originalError) { 211 | if (shuttingDown === true) { 212 | return; 213 | } 214 | shuttingDown = true; 215 | 216 | if (dest._state === 'writable' && WritableStreamCloseQueuedOrInFlight(dest) === false) { 217 | waitForWritesToFinish().then(doTheRest); 218 | } else { 219 | doTheRest(); 220 | } 221 | 222 | function doTheRest() { 223 | action().then( 224 | () => finalize(originalIsError, originalError), 225 | newError => finalize(true, newError) 226 | ) 227 | .catch(rethrowAssertionErrorRejection); 228 | } 229 | } 230 | 231 | function shutdown(isError, error) { 232 | if (shuttingDown === true) { 233 | return; 234 | } 235 | shuttingDown = true; 236 | 237 | if (dest._state === 'writable' && WritableStreamCloseQueuedOrInFlight(dest) === false) { 238 | waitForWritesToFinish().then(() => finalize(isError, error)).catch(rethrowAssertionErrorRejection); 239 | } else { 240 | finalize(isError, error); 241 | } 242 | } 243 | 244 | function finalize(isError, error) { 245 | WritableStreamDefaultWriterRelease(writer); 246 | ReadableStreamReaderGenericRelease(reader); 247 | 248 | if (isError) { 249 | reject(error); 250 | } else { 251 | resolve(undefined); 252 | } 253 | } 254 | }); 255 | } 256 | 257 | tee() { 258 | if (IsReadableStream(this) === false) { 259 | throw streamBrandCheckException('tee'); 260 | } 261 | 262 | const branches = ReadableStreamTee(this, false); 263 | return createArrayFromList(branches); 264 | } 265 | } 266 | 267 | module.exports = { 268 | ReadableStream, 269 | IsReadableStreamDisturbed, 270 | ReadableStreamDefaultControllerClose, 271 | ReadableStreamDefaultControllerEnqueue, 272 | ReadableStreamDefaultControllerError, 273 | ReadableStreamDefaultControllerGetDesiredSize 274 | }; 275 | 276 | // Abstract operations for the ReadableStream. 277 | 278 | function AcquireReadableStreamBYOBReader(stream) { 279 | return new ReadableStreamBYOBReader(stream); 280 | } 281 | 282 | function AcquireReadableStreamDefaultReader(stream) { 283 | return new ReadableStreamDefaultReader(stream); 284 | } 285 | 286 | function IsReadableStream(x) { 287 | if (!typeIsObject(x)) { 288 | return false; 289 | } 290 | 291 | if (!Object.prototype.hasOwnProperty.call(x, '_readableStreamController')) { 292 | return false; 293 | } 294 | 295 | return true; 296 | } 297 | 298 | function IsReadableStreamDisturbed(stream) { 299 | assert(IsReadableStream(stream) === true, 'IsReadableStreamDisturbed should only be used on known readable streams'); 300 | 301 | return stream._disturbed; 302 | } 303 | 304 | function IsReadableStreamLocked(stream) { 305 | assert(IsReadableStream(stream) === true, 'IsReadableStreamLocked should only be used on known readable streams'); 306 | 307 | if (stream._reader === undefined) { 308 | return false; 309 | } 310 | 311 | return true; 312 | } 313 | 314 | function ReadableStreamTee(stream, cloneForBranch2) { 315 | assert(IsReadableStream(stream) === true); 316 | assert(typeof cloneForBranch2 === 'boolean'); 317 | 318 | const reader = AcquireReadableStreamDefaultReader(stream); 319 | 320 | const teeState = { 321 | closedOrErrored: false, 322 | canceled1: false, 323 | canceled2: false, 324 | reason1: undefined, 325 | reason2: undefined 326 | }; 327 | teeState.promise = new Promise(resolve => { 328 | teeState._resolve = resolve; 329 | }); 330 | 331 | const pull = create_ReadableStreamTeePullFunction(); 332 | pull._reader = reader; 333 | pull._teeState = teeState; 334 | pull._cloneForBranch2 = cloneForBranch2; 335 | 336 | const cancel1 = create_ReadableStreamTeeBranch1CancelFunction(); 337 | cancel1._stream = stream; 338 | cancel1._teeState = teeState; 339 | 340 | const cancel2 = create_ReadableStreamTeeBranch2CancelFunction(); 341 | cancel2._stream = stream; 342 | cancel2._teeState = teeState; 343 | 344 | const underlyingSource1 = Object.create(Object.prototype); 345 | createDataProperty(underlyingSource1, 'pull', pull); 346 | createDataProperty(underlyingSource1, 'cancel', cancel1); 347 | const branch1Stream = new ReadableStream(underlyingSource1); 348 | 349 | const underlyingSource2 = Object.create(Object.prototype); 350 | createDataProperty(underlyingSource2, 'pull', pull); 351 | createDataProperty(underlyingSource2, 'cancel', cancel2); 352 | const branch2Stream = new ReadableStream(underlyingSource2); 353 | 354 | pull._branch1 = branch1Stream._readableStreamController; 355 | pull._branch2 = branch2Stream._readableStreamController; 356 | 357 | reader._closedPromise.catch(r => { 358 | if (teeState.closedOrErrored === true) { 359 | return; 360 | } 361 | 362 | ReadableStreamDefaultControllerError(pull._branch1, r); 363 | ReadableStreamDefaultControllerError(pull._branch2, r); 364 | teeState.closedOrErrored = true; 365 | }); 366 | 367 | return [branch1Stream, branch2Stream]; 368 | } 369 | 370 | function create_ReadableStreamTeePullFunction() { 371 | function f() { 372 | const { _reader: reader, _branch1: branch1, _branch2: branch2, _teeState: teeState/* , 373 | _cloneForBranch2: cloneForBranch2*/ } = f; 374 | 375 | return ReadableStreamDefaultReaderRead(reader).then(result => { 376 | assert(typeIsObject(result)); 377 | const value = result.value; 378 | const done = result.done; 379 | assert(typeof done === 'boolean'); 380 | 381 | if (done === true && teeState.closedOrErrored === false) { 382 | if (teeState.canceled1 === false) { 383 | ReadableStreamDefaultControllerClose(branch1); 384 | } 385 | if (teeState.canceled2 === false) { 386 | ReadableStreamDefaultControllerClose(branch2); 387 | } 388 | teeState.closedOrErrored = true; 389 | } 390 | 391 | if (teeState.closedOrErrored === true) { 392 | return; 393 | } 394 | 395 | const value1 = value; 396 | const value2 = value; 397 | 398 | // There is no way to access the cloning code right now in the reference implementation. 399 | // If we add one then we'll need an implementation for serializable objects. 400 | // if (teeState.canceled2 === false && cloneForBranch2 === true) { 401 | // value2 = StructuredDeserialize(StructuredSerialize(value2)); 402 | // } 403 | 404 | if (teeState.canceled1 === false) { 405 | ReadableStreamDefaultControllerEnqueue(branch1, value1); 406 | } 407 | 408 | if (teeState.canceled2 === false) { 409 | ReadableStreamDefaultControllerEnqueue(branch2, value2); 410 | } 411 | }); 412 | } 413 | return f; 414 | } 415 | 416 | function create_ReadableStreamTeeBranch1CancelFunction() { 417 | function f(reason) { 418 | const { _stream: stream, _teeState: teeState } = f; 419 | 420 | teeState.canceled1 = true; 421 | teeState.reason1 = reason; 422 | if (teeState.canceled2 === true) { 423 | const compositeReason = createArrayFromList([teeState.reason1, teeState.reason2]); 424 | const cancelResult = ReadableStreamCancel(stream, compositeReason); 425 | teeState._resolve(cancelResult); 426 | } 427 | return teeState.promise; 428 | } 429 | return f; 430 | } 431 | 432 | function create_ReadableStreamTeeBranch2CancelFunction() { 433 | function f(reason) { 434 | const { _stream: stream, _teeState: teeState } = f; 435 | 436 | teeState.canceled2 = true; 437 | teeState.reason2 = reason; 438 | if (teeState.canceled1 === true) { 439 | const compositeReason = createArrayFromList([teeState.reason1, teeState.reason2]); 440 | const cancelResult = ReadableStreamCancel(stream, compositeReason); 441 | teeState._resolve(cancelResult); 442 | } 443 | return teeState.promise; 444 | } 445 | return f; 446 | } 447 | 448 | // ReadableStream API exposed for controllers. 449 | 450 | function ReadableStreamAddReadIntoRequest(stream) { 451 | assert(IsReadableStreamBYOBReader(stream._reader) === true); 452 | assert(stream._state === 'readable' || stream._state === 'closed'); 453 | 454 | const promise = new Promise((resolve, reject) => { 455 | const readIntoRequest = { 456 | _resolve: resolve, 457 | _reject: reject 458 | }; 459 | 460 | stream._reader._readIntoRequests.push(readIntoRequest); 461 | }); 462 | 463 | return promise; 464 | } 465 | 466 | function ReadableStreamAddReadRequest(stream) { 467 | assert(IsReadableStreamDefaultReader(stream._reader) === true); 468 | assert(stream._state === 'readable'); 469 | 470 | const promise = new Promise((resolve, reject) => { 471 | const readRequest = { 472 | _resolve: resolve, 473 | _reject: reject 474 | }; 475 | 476 | stream._reader._readRequests.push(readRequest); 477 | }); 478 | 479 | return promise; 480 | } 481 | 482 | function ReadableStreamCancel(stream, reason) { 483 | stream._disturbed = true; 484 | 485 | if (stream._state === 'closed') { 486 | return Promise.resolve(undefined); 487 | } 488 | if (stream._state === 'errored') { 489 | return Promise.reject(stream._storedError); 490 | } 491 | 492 | ReadableStreamClose(stream); 493 | 494 | const sourceCancelPromise = stream._readableStreamController[CancelSteps](reason); 495 | return sourceCancelPromise.then(() => undefined); 496 | } 497 | 498 | function ReadableStreamClose(stream) { 499 | assert(stream._state === 'readable'); 500 | 501 | stream._state = 'closed'; 502 | 503 | const reader = stream._reader; 504 | 505 | if (reader === undefined) { 506 | return undefined; 507 | } 508 | 509 | if (IsReadableStreamDefaultReader(reader) === true) { 510 | for (const { _resolve } of reader._readRequests) { 511 | _resolve(CreateIterResultObject(undefined, true)); 512 | } 513 | reader._readRequests = []; 514 | } 515 | 516 | defaultReaderClosedPromiseResolve(reader); 517 | 518 | return undefined; 519 | } 520 | 521 | function ReadableStreamError(stream, e) { 522 | assert(IsReadableStream(stream) === true, 'stream must be ReadableStream'); 523 | assert(stream._state === 'readable', 'state must be readable'); 524 | 525 | stream._state = 'errored'; 526 | stream._storedError = e; 527 | 528 | const reader = stream._reader; 529 | 530 | if (reader === undefined) { 531 | return undefined; 532 | } 533 | 534 | if (IsReadableStreamDefaultReader(reader) === true) { 535 | for (const readRequest of reader._readRequests) { 536 | readRequest._reject(e); 537 | } 538 | 539 | reader._readRequests = []; 540 | } else { 541 | assert(IsReadableStreamBYOBReader(reader), 'reader must be ReadableStreamBYOBReader'); 542 | 543 | for (const readIntoRequest of reader._readIntoRequests) { 544 | readIntoRequest._reject(e); 545 | } 546 | 547 | reader._readIntoRequests = []; 548 | } 549 | 550 | defaultReaderClosedPromiseReject(reader, e); 551 | reader._closedPromise.catch(() => {}); 552 | } 553 | 554 | function ReadableStreamFulfillReadIntoRequest(stream, chunk, done) { 555 | const reader = stream._reader; 556 | 557 | assert(reader._readIntoRequests.length > 0); 558 | 559 | const readIntoRequest = reader._readIntoRequests.shift(); 560 | readIntoRequest._resolve(CreateIterResultObject(chunk, done)); 561 | } 562 | 563 | function ReadableStreamFulfillReadRequest(stream, chunk, done) { 564 | const reader = stream._reader; 565 | 566 | assert(reader._readRequests.length > 0); 567 | 568 | const readRequest = reader._readRequests.shift(); 569 | readRequest._resolve(CreateIterResultObject(chunk, done)); 570 | } 571 | 572 | function ReadableStreamGetNumReadIntoRequests(stream) { 573 | return stream._reader._readIntoRequests.length; 574 | } 575 | 576 | function ReadableStreamGetNumReadRequests(stream) { 577 | return stream._reader._readRequests.length; 578 | } 579 | 580 | function ReadableStreamHasBYOBReader(stream) { 581 | const reader = stream._reader; 582 | 583 | if (reader === undefined) { 584 | return false; 585 | } 586 | 587 | if (IsReadableStreamBYOBReader(reader) === false) { 588 | return false; 589 | } 590 | 591 | return true; 592 | } 593 | 594 | function ReadableStreamHasDefaultReader(stream) { 595 | const reader = stream._reader; 596 | 597 | if (reader === undefined) { 598 | return false; 599 | } 600 | 601 | if (IsReadableStreamDefaultReader(reader) === false) { 602 | return false; 603 | } 604 | 605 | return true; 606 | } 607 | 608 | // Readers 609 | 610 | class ReadableStreamDefaultReader { 611 | constructor(stream) { 612 | if (IsReadableStream(stream) === false) { 613 | throw new TypeError('ReadableStreamDefaultReader can only be constructed with a ReadableStream instance'); 614 | } 615 | if (IsReadableStreamLocked(stream) === true) { 616 | throw new TypeError('This stream has already been locked for exclusive reading by another reader'); 617 | } 618 | 619 | ReadableStreamReaderGenericInitialize(this, stream); 620 | 621 | this._readRequests = []; 622 | } 623 | 624 | get closed() { 625 | if (IsReadableStreamDefaultReader(this) === false) { 626 | return Promise.reject(defaultReaderBrandCheckException('closed')); 627 | } 628 | 629 | return this._closedPromise; 630 | } 631 | 632 | cancel(reason) { 633 | if (IsReadableStreamDefaultReader(this) === false) { 634 | return Promise.reject(defaultReaderBrandCheckException('cancel')); 635 | } 636 | 637 | if (this._ownerReadableStream === undefined) { 638 | return Promise.reject(readerLockException('cancel')); 639 | } 640 | 641 | return ReadableStreamReaderGenericCancel(this, reason); 642 | } 643 | 644 | read() { 645 | if (IsReadableStreamDefaultReader(this) === false) { 646 | return Promise.reject(defaultReaderBrandCheckException('read')); 647 | } 648 | 649 | if (this._ownerReadableStream === undefined) { 650 | return Promise.reject(readerLockException('read from')); 651 | } 652 | 653 | return ReadableStreamDefaultReaderRead(this); 654 | } 655 | 656 | releaseLock() { 657 | if (IsReadableStreamDefaultReader(this) === false) { 658 | throw defaultReaderBrandCheckException('releaseLock'); 659 | } 660 | 661 | if (this._ownerReadableStream === undefined) { 662 | return; 663 | } 664 | 665 | if (this._readRequests.length > 0) { 666 | throw new TypeError('Tried to release a reader lock when that reader has pending read() calls un-settled'); 667 | } 668 | 669 | ReadableStreamReaderGenericRelease(this); 670 | } 671 | } 672 | 673 | class ReadableStreamBYOBReader { 674 | constructor(stream) { 675 | if (!IsReadableStream(stream)) { 676 | throw new TypeError('ReadableStreamBYOBReader can only be constructed with a ReadableStream instance given a ' + 677 | 'byte source'); 678 | } 679 | if (IsReadableByteStreamController(stream._readableStreamController) === false) { 680 | throw new TypeError('Cannot construct a ReadableStreamBYOBReader for a stream not constructed with a byte ' + 681 | 'source'); 682 | } 683 | if (IsReadableStreamLocked(stream)) { 684 | throw new TypeError('This stream has already been locked for exclusive reading by another reader'); 685 | } 686 | 687 | ReadableStreamReaderGenericInitialize(this, stream); 688 | 689 | this._readIntoRequests = []; 690 | } 691 | 692 | get closed() { 693 | if (!IsReadableStreamBYOBReader(this)) { 694 | return Promise.reject(byobReaderBrandCheckException('closed')); 695 | } 696 | 697 | return this._closedPromise; 698 | } 699 | 700 | cancel(reason) { 701 | if (!IsReadableStreamBYOBReader(this)) { 702 | return Promise.reject(byobReaderBrandCheckException('cancel')); 703 | } 704 | 705 | if (this._ownerReadableStream === undefined) { 706 | return Promise.reject(readerLockException('cancel')); 707 | } 708 | 709 | return ReadableStreamReaderGenericCancel(this, reason); 710 | } 711 | 712 | read(view) { 713 | if (!IsReadableStreamBYOBReader(this)) { 714 | return Promise.reject(byobReaderBrandCheckException('read')); 715 | } 716 | 717 | if (this._ownerReadableStream === undefined) { 718 | return Promise.reject(readerLockException('read from')); 719 | } 720 | 721 | if (!ArrayBuffer.isView(view)) { 722 | return Promise.reject(new TypeError('view must be an array buffer view')); 723 | } 724 | 725 | if (view.byteLength === 0) { 726 | return Promise.reject(new TypeError('view must have non-zero byteLength')); 727 | } 728 | 729 | return ReadableStreamBYOBReaderRead(this, view); 730 | } 731 | 732 | releaseLock() { 733 | if (!IsReadableStreamBYOBReader(this)) { 734 | throw byobReaderBrandCheckException('releaseLock'); 735 | } 736 | 737 | if (this._ownerReadableStream === undefined) { 738 | return; 739 | } 740 | 741 | if (this._readIntoRequests.length > 0) { 742 | throw new TypeError('Tried to release a reader lock when that reader has pending read() calls un-settled'); 743 | } 744 | 745 | ReadableStreamReaderGenericRelease(this); 746 | } 747 | } 748 | 749 | // Abstract operations for the readers. 750 | 751 | function IsReadableStreamBYOBReader(x) { 752 | if (!typeIsObject(x)) { 753 | return false; 754 | } 755 | 756 | if (!Object.prototype.hasOwnProperty.call(x, '_readIntoRequests')) { 757 | return false; 758 | } 759 | 760 | return true; 761 | } 762 | 763 | function IsReadableStreamDefaultReader(x) { 764 | if (!typeIsObject(x)) { 765 | return false; 766 | } 767 | 768 | if (!Object.prototype.hasOwnProperty.call(x, '_readRequests')) { 769 | return false; 770 | } 771 | 772 | return true; 773 | } 774 | 775 | function ReadableStreamReaderGenericInitialize(reader, stream) { 776 | reader._ownerReadableStream = stream; 777 | stream._reader = reader; 778 | 779 | if (stream._state === 'readable') { 780 | defaultReaderClosedPromiseInitialize(reader); 781 | } else if (stream._state === 'closed') { 782 | defaultReaderClosedPromiseInitializeAsResolved(reader); 783 | } else { 784 | assert(stream._state === 'errored', 'state must be errored'); 785 | 786 | defaultReaderClosedPromiseInitializeAsRejected(reader, stream._storedError); 787 | reader._closedPromise.catch(() => {}); 788 | } 789 | } 790 | 791 | // A client of ReadableStreamDefaultReader and ReadableStreamBYOBReader may use these functions directly to bypass state 792 | // check. 793 | 794 | function ReadableStreamReaderGenericCancel(reader, reason) { 795 | const stream = reader._ownerReadableStream; 796 | assert(stream !== undefined); 797 | return ReadableStreamCancel(stream, reason); 798 | } 799 | 800 | function ReadableStreamReaderGenericRelease(reader) { 801 | assert(reader._ownerReadableStream !== undefined); 802 | assert(reader._ownerReadableStream._reader === reader); 803 | 804 | if (reader._ownerReadableStream._state === 'readable') { 805 | defaultReaderClosedPromiseReject( 806 | reader, 807 | new TypeError('Reader was released and can no longer be used to monitor the stream\'s closedness')); 808 | } else { 809 | defaultReaderClosedPromiseResetToRejected( 810 | reader, 811 | new TypeError('Reader was released and can no longer be used to monitor the stream\'s closedness')); 812 | } 813 | reader._closedPromise.catch(() => {}); 814 | 815 | reader._ownerReadableStream._reader = undefined; 816 | reader._ownerReadableStream = undefined; 817 | } 818 | 819 | function ReadableStreamBYOBReaderRead(reader, view) { 820 | const stream = reader._ownerReadableStream; 821 | 822 | assert(stream !== undefined); 823 | 824 | stream._disturbed = true; 825 | 826 | if (stream._state === 'errored') { 827 | return Promise.reject(stream._storedError); 828 | } 829 | 830 | // Controllers must implement this. 831 | return ReadableByteStreamControllerPullInto(stream._readableStreamController, view); 832 | } 833 | 834 | function ReadableStreamDefaultReaderRead(reader) { 835 | const stream = reader._ownerReadableStream; 836 | 837 | assert(stream !== undefined); 838 | 839 | stream._disturbed = true; 840 | 841 | if (stream._state === 'closed') { 842 | return Promise.resolve(CreateIterResultObject(undefined, true)); 843 | } 844 | 845 | if (stream._state === 'errored') { 846 | return Promise.reject(stream._storedError); 847 | } 848 | 849 | assert(stream._state === 'readable'); 850 | 851 | return stream._readableStreamController[PullSteps](); 852 | } 853 | 854 | // Controllers 855 | 856 | class ReadableStreamDefaultController { 857 | constructor(stream, underlyingSource, size, highWaterMark) { 858 | if (IsReadableStream(stream) === false) { 859 | throw new TypeError('ReadableStreamDefaultController can only be constructed with a ReadableStream instance'); 860 | } 861 | 862 | if (stream._readableStreamController !== undefined) { 863 | throw new TypeError( 864 | 'ReadableStreamDefaultController instances can only be created by the ReadableStream constructor'); 865 | } 866 | 867 | this._controlledReadableStream = stream; 868 | 869 | this._underlyingSource = underlyingSource; 870 | 871 | // Need to set the slots so that the assert doesn't fire. In the spec the slots already exist implicitly. 872 | this._queue = undefined; 873 | this._queueTotalSize = undefined; 874 | ResetQueue(this); 875 | 876 | this._started = false; 877 | this._closeRequested = false; 878 | this._pullAgain = false; 879 | this._pulling = false; 880 | 881 | const normalizedStrategy = ValidateAndNormalizeQueuingStrategy(size, highWaterMark); 882 | this._strategySize = normalizedStrategy.size; 883 | this._strategyHWM = normalizedStrategy.highWaterMark; 884 | 885 | const controller = this; 886 | 887 | const startResult = InvokeOrNoop(underlyingSource, 'start', [this]); 888 | Promise.resolve(startResult).then( 889 | () => { 890 | controller._started = true; 891 | 892 | assert(controller._pulling === false); 893 | assert(controller._pullAgain === false); 894 | 895 | ReadableStreamDefaultControllerCallPullIfNeeded(controller); 896 | }, 897 | r => { 898 | ReadableStreamDefaultControllerErrorIfNeeded(controller, r); 899 | } 900 | ) 901 | .catch(rethrowAssertionErrorRejection); 902 | } 903 | 904 | get desiredSize() { 905 | if (IsReadableStreamDefaultController(this) === false) { 906 | throw defaultControllerBrandCheckException('desiredSize'); 907 | } 908 | 909 | return ReadableStreamDefaultControllerGetDesiredSize(this); 910 | } 911 | 912 | close() { 913 | if (IsReadableStreamDefaultController(this) === false) { 914 | throw defaultControllerBrandCheckException('close'); 915 | } 916 | 917 | if (this._closeRequested === true) { 918 | throw new TypeError('The stream has already been closed; do not close it again!'); 919 | } 920 | 921 | const state = this._controlledReadableStream._state; 922 | if (state !== 'readable') { 923 | throw new TypeError(`The stream (in ${state} state) is not in the readable state and cannot be closed`); 924 | } 925 | 926 | ReadableStreamDefaultControllerClose(this); 927 | } 928 | 929 | enqueue(chunk) { 930 | if (IsReadableStreamDefaultController(this) === false) { 931 | throw defaultControllerBrandCheckException('enqueue'); 932 | } 933 | 934 | if (this._closeRequested === true) { 935 | throw new TypeError('stream is closed or draining'); 936 | } 937 | 938 | const state = this._controlledReadableStream._state; 939 | if (state !== 'readable') { 940 | throw new TypeError(`The stream (in ${state} state) is not in the readable state and cannot be enqueued to`); 941 | } 942 | 943 | return ReadableStreamDefaultControllerEnqueue(this, chunk); 944 | } 945 | 946 | error(e) { 947 | if (IsReadableStreamDefaultController(this) === false) { 948 | throw defaultControllerBrandCheckException('error'); 949 | } 950 | 951 | const stream = this._controlledReadableStream; 952 | if (stream._state !== 'readable') { 953 | throw new TypeError(`The stream is ${stream._state} and so cannot be errored`); 954 | } 955 | 956 | ReadableStreamDefaultControllerError(this, e); 957 | } 958 | 959 | [CancelSteps](reason) { 960 | ResetQueue(this); 961 | return PromiseInvokeOrNoop(this._underlyingSource, 'cancel', [reason]); 962 | } 963 | 964 | [PullSteps]() { 965 | const stream = this._controlledReadableStream; 966 | 967 | if (this._queue.length > 0) { 968 | const chunk = DequeueValue(this); 969 | 970 | if (this._closeRequested === true && this._queue.length === 0) { 971 | ReadableStreamClose(stream); 972 | } else { 973 | ReadableStreamDefaultControllerCallPullIfNeeded(this); 974 | } 975 | 976 | return Promise.resolve(CreateIterResultObject(chunk, false)); 977 | } 978 | 979 | const pendingPromise = ReadableStreamAddReadRequest(stream); 980 | ReadableStreamDefaultControllerCallPullIfNeeded(this); 981 | return pendingPromise; 982 | } 983 | } 984 | 985 | // Abstract operations for the ReadableStreamDefaultController. 986 | 987 | function IsReadableStreamDefaultController(x) { 988 | if (!typeIsObject(x)) { 989 | return false; 990 | } 991 | 992 | if (!Object.prototype.hasOwnProperty.call(x, '_underlyingSource')) { 993 | return false; 994 | } 995 | 996 | return true; 997 | } 998 | 999 | function ReadableStreamDefaultControllerCallPullIfNeeded(controller) { 1000 | const shouldPull = ReadableStreamDefaultControllerShouldCallPull(controller); 1001 | if (shouldPull === false) { 1002 | return undefined; 1003 | } 1004 | 1005 | if (controller._pulling === true) { 1006 | controller._pullAgain = true; 1007 | return undefined; 1008 | } 1009 | 1010 | assert(controller._pullAgain === false); 1011 | 1012 | controller._pulling = true; 1013 | 1014 | const pullPromise = PromiseInvokeOrNoop(controller._underlyingSource, 'pull', [controller]); 1015 | pullPromise.then( 1016 | () => { 1017 | controller._pulling = false; 1018 | 1019 | if (controller._pullAgain === true) { 1020 | controller._pullAgain = false; 1021 | return ReadableStreamDefaultControllerCallPullIfNeeded(controller); 1022 | } 1023 | return undefined; 1024 | }, 1025 | e => { 1026 | ReadableStreamDefaultControllerErrorIfNeeded(controller, e); 1027 | } 1028 | ) 1029 | .catch(rethrowAssertionErrorRejection); 1030 | 1031 | return undefined; 1032 | } 1033 | 1034 | function ReadableStreamDefaultControllerShouldCallPull(controller) { 1035 | const stream = controller._controlledReadableStream; 1036 | 1037 | if (stream._state === 'closed' || stream._state === 'errored') { 1038 | return false; 1039 | } 1040 | 1041 | if (controller._closeRequested === true) { 1042 | return false; 1043 | } 1044 | 1045 | if (controller._started === false) { 1046 | return false; 1047 | } 1048 | 1049 | if (IsReadableStreamLocked(stream) === true && ReadableStreamGetNumReadRequests(stream) > 0) { 1050 | return true; 1051 | } 1052 | 1053 | const desiredSize = ReadableStreamDefaultControllerGetDesiredSize(controller); 1054 | if (desiredSize > 0) { 1055 | return true; 1056 | } 1057 | 1058 | return false; 1059 | } 1060 | 1061 | // A client of ReadableStreamDefaultController may use these functions directly to bypass state check. 1062 | 1063 | function ReadableStreamDefaultControllerClose(controller) { 1064 | const stream = controller._controlledReadableStream; 1065 | 1066 | assert(controller._closeRequested === false); 1067 | assert(stream._state === 'readable'); 1068 | 1069 | controller._closeRequested = true; 1070 | 1071 | if (controller._queue.length === 0) { 1072 | ReadableStreamClose(stream); 1073 | } 1074 | } 1075 | 1076 | function ReadableStreamDefaultControllerEnqueue(controller, chunk) { 1077 | const stream = controller._controlledReadableStream; 1078 | 1079 | assert(controller._closeRequested === false); 1080 | assert(stream._state === 'readable'); 1081 | 1082 | if (IsReadableStreamLocked(stream) === true && ReadableStreamGetNumReadRequests(stream) > 0) { 1083 | ReadableStreamFulfillReadRequest(stream, chunk, false); 1084 | } else { 1085 | let chunkSize = 1; 1086 | 1087 | if (controller._strategySize !== undefined) { 1088 | const strategySize = controller._strategySize; 1089 | try { 1090 | chunkSize = strategySize(chunk); 1091 | } catch (chunkSizeE) { 1092 | ReadableStreamDefaultControllerErrorIfNeeded(controller, chunkSizeE); 1093 | throw chunkSizeE; 1094 | } 1095 | } 1096 | 1097 | try { 1098 | EnqueueValueWithSize(controller, chunk, chunkSize); 1099 | } catch (enqueueE) { 1100 | ReadableStreamDefaultControllerErrorIfNeeded(controller, enqueueE); 1101 | throw enqueueE; 1102 | } 1103 | } 1104 | 1105 | ReadableStreamDefaultControllerCallPullIfNeeded(controller); 1106 | 1107 | return undefined; 1108 | } 1109 | 1110 | function ReadableStreamDefaultControllerError(controller, e) { 1111 | const stream = controller._controlledReadableStream; 1112 | 1113 | assert(stream._state === 'readable'); 1114 | 1115 | ResetQueue(controller); 1116 | 1117 | ReadableStreamError(stream, e); 1118 | } 1119 | 1120 | function ReadableStreamDefaultControllerErrorIfNeeded(controller, e) { 1121 | if (controller._controlledReadableStream._state === 'readable') { 1122 | ReadableStreamDefaultControllerError(controller, e); 1123 | } 1124 | } 1125 | 1126 | function ReadableStreamDefaultControllerGetDesiredSize(controller) { 1127 | const stream = controller._controlledReadableStream; 1128 | const state = stream._state; 1129 | 1130 | if (state === 'errored') { 1131 | return null; 1132 | } 1133 | if (state === 'closed') { 1134 | return 0; 1135 | } 1136 | 1137 | return controller._strategyHWM - controller._queueTotalSize; 1138 | } 1139 | 1140 | class ReadableStreamBYOBRequest { 1141 | constructor(controller, view) { 1142 | this._associatedReadableByteStreamController = controller; 1143 | this._view = view; 1144 | } 1145 | 1146 | get view() { 1147 | return this._view; 1148 | } 1149 | 1150 | respond(bytesWritten) { 1151 | if (IsReadableStreamBYOBRequest(this) === false) { 1152 | throw byobRequestBrandCheckException('respond'); 1153 | } 1154 | 1155 | if (this._associatedReadableByteStreamController === undefined) { 1156 | throw new TypeError('This BYOB request has been invalidated'); 1157 | } 1158 | 1159 | ReadableByteStreamControllerRespond(this._associatedReadableByteStreamController, bytesWritten); 1160 | } 1161 | 1162 | respondWithNewView(view) { 1163 | if (IsReadableStreamBYOBRequest(this) === false) { 1164 | throw byobRequestBrandCheckException('respond'); 1165 | } 1166 | 1167 | if (this._associatedReadableByteStreamController === undefined) { 1168 | throw new TypeError('This BYOB request has been invalidated'); 1169 | } 1170 | 1171 | if (!ArrayBuffer.isView(view)) { 1172 | throw new TypeError('You can only respond with array buffer views'); 1173 | } 1174 | 1175 | ReadableByteStreamControllerRespondWithNewView(this._associatedReadableByteStreamController, view); 1176 | } 1177 | } 1178 | 1179 | class ReadableByteStreamController { 1180 | constructor(stream, underlyingByteSource, highWaterMark) { 1181 | if (IsReadableStream(stream) === false) { 1182 | throw new TypeError('ReadableByteStreamController can only be constructed with a ReadableStream instance given ' + 1183 | 'a byte source'); 1184 | } 1185 | 1186 | if (stream._readableStreamController !== undefined) { 1187 | throw new TypeError( 1188 | 'ReadableByteStreamController instances can only be created by the ReadableStream constructor given a byte ' + 1189 | 'source'); 1190 | } 1191 | 1192 | this._controlledReadableStream = stream; 1193 | 1194 | this._underlyingByteSource = underlyingByteSource; 1195 | 1196 | this._pullAgain = false; 1197 | this._pulling = false; 1198 | 1199 | ReadableByteStreamControllerClearPendingPullIntos(this); 1200 | 1201 | // Need to set the slots so that the assert doesn't fire. In the spec the slots already exist implicitly. 1202 | this._queue = this._queueTotalSize = undefined; 1203 | ResetQueue(this); 1204 | 1205 | this._closeRequested = false; 1206 | this._started = false; 1207 | 1208 | this._strategyHWM = ValidateAndNormalizeHighWaterMark(highWaterMark); 1209 | 1210 | const autoAllocateChunkSize = underlyingByteSource.autoAllocateChunkSize; 1211 | if (autoAllocateChunkSize !== undefined) { 1212 | if (Number.isInteger(autoAllocateChunkSize) === false || autoAllocateChunkSize <= 0) { 1213 | throw new RangeError('autoAllocateChunkSize must be a positive integer'); 1214 | } 1215 | } 1216 | this._autoAllocateChunkSize = autoAllocateChunkSize; 1217 | 1218 | this._pendingPullIntos = []; 1219 | 1220 | const controller = this; 1221 | 1222 | const startResult = InvokeOrNoop(underlyingByteSource, 'start', [this]); 1223 | Promise.resolve(startResult).then( 1224 | () => { 1225 | controller._started = true; 1226 | 1227 | assert(controller._pulling === false); 1228 | assert(controller._pullAgain === false); 1229 | 1230 | ReadableByteStreamControllerCallPullIfNeeded(controller); 1231 | }, 1232 | r => { 1233 | if (stream._state === 'readable') { 1234 | ReadableByteStreamControllerError(controller, r); 1235 | } 1236 | } 1237 | ) 1238 | .catch(rethrowAssertionErrorRejection); 1239 | } 1240 | 1241 | get byobRequest() { 1242 | if (IsReadableByteStreamController(this) === false) { 1243 | throw byteStreamControllerBrandCheckException('byobRequest'); 1244 | } 1245 | 1246 | if (this._byobRequest === undefined && this._pendingPullIntos.length > 0) { 1247 | const firstDescriptor = this._pendingPullIntos[0]; 1248 | const view = new Uint8Array(firstDescriptor.buffer, 1249 | firstDescriptor.byteOffset + firstDescriptor.bytesFilled, 1250 | firstDescriptor.byteLength - firstDescriptor.bytesFilled); 1251 | 1252 | this._byobRequest = new ReadableStreamBYOBRequest(this, view); 1253 | } 1254 | 1255 | return this._byobRequest; 1256 | } 1257 | 1258 | get desiredSize() { 1259 | if (IsReadableByteStreamController(this) === false) { 1260 | throw byteStreamControllerBrandCheckException('desiredSize'); 1261 | } 1262 | 1263 | return ReadableByteStreamControllerGetDesiredSize(this); 1264 | } 1265 | 1266 | close() { 1267 | if (IsReadableByteStreamController(this) === false) { 1268 | throw byteStreamControllerBrandCheckException('close'); 1269 | } 1270 | 1271 | if (this._closeRequested === true) { 1272 | throw new TypeError('The stream has already been closed; do not close it again!'); 1273 | } 1274 | 1275 | const state = this._controlledReadableStream._state; 1276 | if (state !== 'readable') { 1277 | throw new TypeError(`The stream (in ${state} state) is not in the readable state and cannot be closed`); 1278 | } 1279 | 1280 | ReadableByteStreamControllerClose(this); 1281 | } 1282 | 1283 | enqueue(chunk) { 1284 | if (IsReadableByteStreamController(this) === false) { 1285 | throw byteStreamControllerBrandCheckException('enqueue'); 1286 | } 1287 | 1288 | if (this._closeRequested === true) { 1289 | throw new TypeError('stream is closed or draining'); 1290 | } 1291 | 1292 | const state = this._controlledReadableStream._state; 1293 | if (state !== 'readable') { 1294 | throw new TypeError(`The stream (in ${state} state) is not in the readable state and cannot be enqueued to`); 1295 | } 1296 | 1297 | if (!ArrayBuffer.isView(chunk)) { 1298 | throw new TypeError('You can only enqueue array buffer views when using a ReadableByteStreamController'); 1299 | } 1300 | 1301 | ReadableByteStreamControllerEnqueue(this, chunk); 1302 | } 1303 | 1304 | error(e) { 1305 | if (IsReadableByteStreamController(this) === false) { 1306 | throw byteStreamControllerBrandCheckException('error'); 1307 | } 1308 | 1309 | const stream = this._controlledReadableStream; 1310 | if (stream._state !== 'readable') { 1311 | throw new TypeError(`The stream is ${stream._state} and so cannot be errored`); 1312 | } 1313 | 1314 | ReadableByteStreamControllerError(this, e); 1315 | } 1316 | 1317 | [CancelSteps](reason) { 1318 | if (this._pendingPullIntos.length > 0) { 1319 | const firstDescriptor = this._pendingPullIntos[0]; 1320 | firstDescriptor.bytesFilled = 0; 1321 | } 1322 | 1323 | ResetQueue(this); 1324 | 1325 | return PromiseInvokeOrNoop(this._underlyingByteSource, 'cancel', [reason]); 1326 | } 1327 | 1328 | [PullSteps]() { 1329 | const stream = this._controlledReadableStream; 1330 | assert(ReadableStreamHasDefaultReader(stream) === true); 1331 | 1332 | if (this._queueTotalSize > 0) { 1333 | assert(ReadableStreamGetNumReadRequests(stream) === 0); 1334 | 1335 | const entry = this._queue.shift(); 1336 | this._queueTotalSize -= entry.byteLength; 1337 | 1338 | ReadableByteStreamControllerHandleQueueDrain(this); 1339 | 1340 | let view; 1341 | try { 1342 | view = new Uint8Array(entry.buffer, entry.byteOffset, entry.byteLength); 1343 | } catch (viewE) { 1344 | return Promise.reject(viewE); 1345 | } 1346 | 1347 | return Promise.resolve(CreateIterResultObject(view, false)); 1348 | } 1349 | 1350 | const autoAllocateChunkSize = this._autoAllocateChunkSize; 1351 | if (autoAllocateChunkSize !== undefined) { 1352 | let buffer; 1353 | try { 1354 | buffer = new ArrayBuffer(autoAllocateChunkSize); 1355 | } catch (bufferE) { 1356 | return Promise.reject(bufferE); 1357 | } 1358 | 1359 | const pullIntoDescriptor = { 1360 | buffer, 1361 | byteOffset: 0, 1362 | byteLength: autoAllocateChunkSize, 1363 | bytesFilled: 0, 1364 | elementSize: 1, 1365 | ctor: Uint8Array, 1366 | readerType: 'default' 1367 | }; 1368 | 1369 | this._pendingPullIntos.push(pullIntoDescriptor); 1370 | } 1371 | 1372 | const promise = ReadableStreamAddReadRequest(stream); 1373 | 1374 | ReadableByteStreamControllerCallPullIfNeeded(this); 1375 | 1376 | return promise; 1377 | } 1378 | } 1379 | 1380 | // Abstract operations for the ReadableByteStreamController. 1381 | 1382 | function IsReadableByteStreamController(x) { 1383 | if (!typeIsObject(x)) { 1384 | return false; 1385 | } 1386 | 1387 | if (!Object.prototype.hasOwnProperty.call(x, '_underlyingByteSource')) { 1388 | return false; 1389 | } 1390 | 1391 | return true; 1392 | } 1393 | 1394 | function IsReadableStreamBYOBRequest(x) { 1395 | if (!typeIsObject(x)) { 1396 | return false; 1397 | } 1398 | 1399 | if (!Object.prototype.hasOwnProperty.call(x, '_associatedReadableByteStreamController')) { 1400 | return false; 1401 | } 1402 | 1403 | return true; 1404 | } 1405 | 1406 | function ReadableByteStreamControllerCallPullIfNeeded(controller) { 1407 | const shouldPull = ReadableByteStreamControllerShouldCallPull(controller); 1408 | if (shouldPull === false) { 1409 | return undefined; 1410 | } 1411 | 1412 | if (controller._pulling === true) { 1413 | controller._pullAgain = true; 1414 | return undefined; 1415 | } 1416 | 1417 | assert(controller._pullAgain === false); 1418 | 1419 | controller._pulling = true; 1420 | 1421 | // TODO: Test controller argument 1422 | const pullPromise = PromiseInvokeOrNoop(controller._underlyingByteSource, 'pull', [controller]); 1423 | pullPromise.then( 1424 | () => { 1425 | controller._pulling = false; 1426 | 1427 | if (controller._pullAgain === true) { 1428 | controller._pullAgain = false; 1429 | ReadableByteStreamControllerCallPullIfNeeded(controller); 1430 | } 1431 | }, 1432 | e => { 1433 | if (controller._controlledReadableStream._state === 'readable') { 1434 | ReadableByteStreamControllerError(controller, e); 1435 | } 1436 | } 1437 | ) 1438 | .catch(rethrowAssertionErrorRejection); 1439 | 1440 | return undefined; 1441 | } 1442 | 1443 | function ReadableByteStreamControllerClearPendingPullIntos(controller) { 1444 | ReadableByteStreamControllerInvalidateBYOBRequest(controller); 1445 | controller._pendingPullIntos = []; 1446 | } 1447 | 1448 | function ReadableByteStreamControllerCommitPullIntoDescriptor(stream, pullIntoDescriptor) { 1449 | assert(stream._state !== 'errored', 'state must not be errored'); 1450 | 1451 | let done = false; 1452 | if (stream._state === 'closed') { 1453 | assert(pullIntoDescriptor.bytesFilled === 0); 1454 | done = true; 1455 | } 1456 | 1457 | const filledView = ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor); 1458 | if (pullIntoDescriptor.readerType === 'default') { 1459 | ReadableStreamFulfillReadRequest(stream, filledView, done); 1460 | } else { 1461 | assert(pullIntoDescriptor.readerType === 'byob'); 1462 | ReadableStreamFulfillReadIntoRequest(stream, filledView, done); 1463 | } 1464 | } 1465 | 1466 | function ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor) { 1467 | const bytesFilled = pullIntoDescriptor.bytesFilled; 1468 | const elementSize = pullIntoDescriptor.elementSize; 1469 | 1470 | assert(bytesFilled <= pullIntoDescriptor.byteLength); 1471 | assert(bytesFilled % elementSize === 0); 1472 | 1473 | return new pullIntoDescriptor.ctor( 1474 | pullIntoDescriptor.buffer, pullIntoDescriptor.byteOffset, bytesFilled / elementSize); 1475 | } 1476 | 1477 | function ReadableByteStreamControllerEnqueueChunkToQueue(controller, buffer, byteOffset, byteLength) { 1478 | controller._queue.push({ buffer, byteOffset, byteLength }); 1479 | controller._queueTotalSize += byteLength; 1480 | } 1481 | 1482 | function ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, pullIntoDescriptor) { 1483 | const elementSize = pullIntoDescriptor.elementSize; 1484 | 1485 | const currentAlignedBytes = pullIntoDescriptor.bytesFilled - pullIntoDescriptor.bytesFilled % elementSize; 1486 | 1487 | const maxBytesToCopy = Math.min(controller._queueTotalSize, 1488 | pullIntoDescriptor.byteLength - pullIntoDescriptor.bytesFilled); 1489 | const maxBytesFilled = pullIntoDescriptor.bytesFilled + maxBytesToCopy; 1490 | const maxAlignedBytes = maxBytesFilled - maxBytesFilled % elementSize; 1491 | 1492 | let totalBytesToCopyRemaining = maxBytesToCopy; 1493 | let ready = false; 1494 | if (maxAlignedBytes > currentAlignedBytes) { 1495 | totalBytesToCopyRemaining = maxAlignedBytes - pullIntoDescriptor.bytesFilled; 1496 | ready = true; 1497 | } 1498 | 1499 | const queue = controller._queue; 1500 | 1501 | while (totalBytesToCopyRemaining > 0) { 1502 | const headOfQueue = queue[0]; 1503 | 1504 | const bytesToCopy = Math.min(totalBytesToCopyRemaining, headOfQueue.byteLength); 1505 | 1506 | const destStart = pullIntoDescriptor.byteOffset + pullIntoDescriptor.bytesFilled; 1507 | ArrayBufferCopy(pullIntoDescriptor.buffer, destStart, headOfQueue.buffer, headOfQueue.byteOffset, bytesToCopy); 1508 | 1509 | if (headOfQueue.byteLength === bytesToCopy) { 1510 | queue.shift(); 1511 | } else { 1512 | headOfQueue.byteOffset += bytesToCopy; 1513 | headOfQueue.byteLength -= bytesToCopy; 1514 | } 1515 | controller._queueTotalSize -= bytesToCopy; 1516 | 1517 | ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, bytesToCopy, pullIntoDescriptor); 1518 | 1519 | totalBytesToCopyRemaining -= bytesToCopy; 1520 | } 1521 | 1522 | if (ready === false) { 1523 | assert(controller._queueTotalSize === 0, 'queue must be empty'); 1524 | assert(pullIntoDescriptor.bytesFilled > 0); 1525 | assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize); 1526 | } 1527 | 1528 | return ready; 1529 | } 1530 | 1531 | function ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, size, pullIntoDescriptor) { 1532 | assert(controller._pendingPullIntos.length === 0 || controller._pendingPullIntos[0] === pullIntoDescriptor); 1533 | 1534 | ReadableByteStreamControllerInvalidateBYOBRequest(controller); 1535 | pullIntoDescriptor.bytesFilled += size; 1536 | } 1537 | 1538 | function ReadableByteStreamControllerHandleQueueDrain(controller) { 1539 | assert(controller._controlledReadableStream._state === 'readable'); 1540 | 1541 | if (controller._queueTotalSize === 0 && controller._closeRequested === true) { 1542 | ReadableStreamClose(controller._controlledReadableStream); 1543 | } else { 1544 | ReadableByteStreamControllerCallPullIfNeeded(controller); 1545 | } 1546 | } 1547 | 1548 | function ReadableByteStreamControllerInvalidateBYOBRequest(controller) { 1549 | if (controller._byobRequest === undefined) { 1550 | return; 1551 | } 1552 | 1553 | controller._byobRequest._associatedReadableByteStreamController = undefined; 1554 | controller._byobRequest._view = undefined; 1555 | controller._byobRequest = undefined; 1556 | } 1557 | 1558 | function ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller) { 1559 | assert(controller._closeRequested === false); 1560 | 1561 | while (controller._pendingPullIntos.length > 0) { 1562 | if (controller._queueTotalSize === 0) { 1563 | return; 1564 | } 1565 | 1566 | const pullIntoDescriptor = controller._pendingPullIntos[0]; 1567 | 1568 | if (ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, pullIntoDescriptor) === true) { 1569 | ReadableByteStreamControllerShiftPendingPullInto(controller); 1570 | 1571 | ReadableByteStreamControllerCommitPullIntoDescriptor(controller._controlledReadableStream, pullIntoDescriptor); 1572 | } 1573 | } 1574 | } 1575 | 1576 | function ReadableByteStreamControllerPullInto(controller, view) { 1577 | const stream = controller._controlledReadableStream; 1578 | 1579 | let elementSize = 1; 1580 | if (view.constructor !== DataView) { 1581 | elementSize = view.constructor.BYTES_PER_ELEMENT; 1582 | } 1583 | 1584 | const ctor = view.constructor; 1585 | 1586 | const pullIntoDescriptor = { 1587 | buffer: view.buffer, 1588 | byteOffset: view.byteOffset, 1589 | byteLength: view.byteLength, 1590 | bytesFilled: 0, 1591 | elementSize, 1592 | ctor, 1593 | readerType: 'byob' 1594 | }; 1595 | 1596 | if (controller._pendingPullIntos.length > 0) { 1597 | pullIntoDescriptor.buffer = TransferArrayBuffer(pullIntoDescriptor.buffer); 1598 | controller._pendingPullIntos.push(pullIntoDescriptor); 1599 | 1600 | // No ReadableByteStreamControllerCallPullIfNeeded() call since: 1601 | // - No change happens on desiredSize 1602 | // - The source has already been notified of that there's at least 1 pending read(view) 1603 | 1604 | return ReadableStreamAddReadIntoRequest(stream); 1605 | } 1606 | 1607 | if (stream._state === 'closed') { 1608 | const emptyView = new view.constructor(pullIntoDescriptor.buffer, pullIntoDescriptor.byteOffset, 0); 1609 | return Promise.resolve(CreateIterResultObject(emptyView, true)); 1610 | } 1611 | 1612 | if (controller._queueTotalSize > 0) { 1613 | if (ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, pullIntoDescriptor) === true) { 1614 | const filledView = ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor); 1615 | 1616 | ReadableByteStreamControllerHandleQueueDrain(controller); 1617 | 1618 | return Promise.resolve(CreateIterResultObject(filledView, false)); 1619 | } 1620 | 1621 | if (controller._closeRequested === true) { 1622 | const e = new TypeError('Insufficient bytes to fill elements in the given buffer'); 1623 | ReadableByteStreamControllerError(controller, e); 1624 | 1625 | return Promise.reject(e); 1626 | } 1627 | } 1628 | 1629 | pullIntoDescriptor.buffer = TransferArrayBuffer(pullIntoDescriptor.buffer); 1630 | controller._pendingPullIntos.push(pullIntoDescriptor); 1631 | 1632 | const promise = ReadableStreamAddReadIntoRequest(stream); 1633 | 1634 | ReadableByteStreamControllerCallPullIfNeeded(controller); 1635 | 1636 | return promise; 1637 | } 1638 | 1639 | function ReadableByteStreamControllerRespondInClosedState(controller, firstDescriptor) { 1640 | firstDescriptor.buffer = TransferArrayBuffer(firstDescriptor.buffer); 1641 | 1642 | assert(firstDescriptor.bytesFilled === 0, 'bytesFilled must be 0'); 1643 | 1644 | const stream = controller._controlledReadableStream; 1645 | if (ReadableStreamHasBYOBReader(stream) === true) { 1646 | while (ReadableStreamGetNumReadIntoRequests(stream) > 0) { 1647 | const pullIntoDescriptor = ReadableByteStreamControllerShiftPendingPullInto(controller); 1648 | ReadableByteStreamControllerCommitPullIntoDescriptor(stream, pullIntoDescriptor); 1649 | } 1650 | } 1651 | } 1652 | 1653 | function ReadableByteStreamControllerRespondInReadableState(controller, bytesWritten, pullIntoDescriptor) { 1654 | if (pullIntoDescriptor.bytesFilled + bytesWritten > pullIntoDescriptor.byteLength) { 1655 | throw new RangeError('bytesWritten out of range'); 1656 | } 1657 | 1658 | ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, bytesWritten, pullIntoDescriptor); 1659 | 1660 | if (pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize) { 1661 | // TODO: Figure out whether we should detach the buffer or not here. 1662 | return; 1663 | } 1664 | 1665 | ReadableByteStreamControllerShiftPendingPullInto(controller); 1666 | 1667 | const remainderSize = pullIntoDescriptor.bytesFilled % pullIntoDescriptor.elementSize; 1668 | if (remainderSize > 0) { 1669 | const end = pullIntoDescriptor.byteOffset + pullIntoDescriptor.bytesFilled; 1670 | const remainder = pullIntoDescriptor.buffer.slice(end - remainderSize, end); 1671 | ReadableByteStreamControllerEnqueueChunkToQueue(controller, remainder, 0, remainder.byteLength); 1672 | } 1673 | 1674 | pullIntoDescriptor.buffer = TransferArrayBuffer(pullIntoDescriptor.buffer); 1675 | pullIntoDescriptor.bytesFilled -= remainderSize; 1676 | ReadableByteStreamControllerCommitPullIntoDescriptor(controller._controlledReadableStream, pullIntoDescriptor); 1677 | 1678 | ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller); 1679 | } 1680 | 1681 | function ReadableByteStreamControllerRespondInternal(controller, bytesWritten) { 1682 | const firstDescriptor = controller._pendingPullIntos[0]; 1683 | 1684 | const stream = controller._controlledReadableStream; 1685 | 1686 | if (stream._state === 'closed') { 1687 | if (bytesWritten !== 0) { 1688 | throw new TypeError('bytesWritten must be 0 when calling respond() on a closed stream'); 1689 | } 1690 | 1691 | ReadableByteStreamControllerRespondInClosedState(controller, firstDescriptor); 1692 | } else { 1693 | assert(stream._state === 'readable'); 1694 | 1695 | ReadableByteStreamControllerRespondInReadableState(controller, bytesWritten, firstDescriptor); 1696 | } 1697 | } 1698 | 1699 | function ReadableByteStreamControllerShiftPendingPullInto(controller) { 1700 | const descriptor = controller._pendingPullIntos.shift(); 1701 | ReadableByteStreamControllerInvalidateBYOBRequest(controller); 1702 | return descriptor; 1703 | } 1704 | 1705 | function ReadableByteStreamControllerShouldCallPull(controller) { 1706 | const stream = controller._controlledReadableStream; 1707 | 1708 | if (stream._state !== 'readable') { 1709 | return false; 1710 | } 1711 | 1712 | if (controller._closeRequested === true) { 1713 | return false; 1714 | } 1715 | 1716 | if (controller._started === false) { 1717 | return false; 1718 | } 1719 | 1720 | if (ReadableStreamHasDefaultReader(stream) === true && ReadableStreamGetNumReadRequests(stream) > 0) { 1721 | return true; 1722 | } 1723 | 1724 | if (ReadableStreamHasBYOBReader(stream) === true && ReadableStreamGetNumReadIntoRequests(stream) > 0) { 1725 | return true; 1726 | } 1727 | 1728 | if (ReadableByteStreamControllerGetDesiredSize(controller) > 0) { 1729 | return true; 1730 | } 1731 | 1732 | return false; 1733 | } 1734 | 1735 | // A client of ReadableByteStreamController may use these functions directly to bypass state check. 1736 | 1737 | function ReadableByteStreamControllerClose(controller) { 1738 | const stream = controller._controlledReadableStream; 1739 | 1740 | assert(controller._closeRequested === false); 1741 | assert(stream._state === 'readable'); 1742 | 1743 | if (controller._queueTotalSize > 0) { 1744 | controller._closeRequested = true; 1745 | 1746 | return; 1747 | } 1748 | 1749 | if (controller._pendingPullIntos.length > 0) { 1750 | const firstPendingPullInto = controller._pendingPullIntos[0]; 1751 | if (firstPendingPullInto.bytesFilled > 0) { 1752 | const e = new TypeError('Insufficient bytes to fill elements in the given buffer'); 1753 | ReadableByteStreamControllerError(controller, e); 1754 | 1755 | throw e; 1756 | } 1757 | } 1758 | 1759 | ReadableStreamClose(stream); 1760 | } 1761 | 1762 | function ReadableByteStreamControllerEnqueue(controller, chunk) { 1763 | const stream = controller._controlledReadableStream; 1764 | 1765 | assert(controller._closeRequested === false); 1766 | assert(stream._state === 'readable'); 1767 | 1768 | const buffer = chunk.buffer; 1769 | const byteOffset = chunk.byteOffset; 1770 | const byteLength = chunk.byteLength; 1771 | const transferredBuffer = TransferArrayBuffer(buffer); 1772 | 1773 | if (ReadableStreamHasDefaultReader(stream) === true) { 1774 | if (ReadableStreamGetNumReadRequests(stream) === 0) { 1775 | ReadableByteStreamControllerEnqueueChunkToQueue(controller, transferredBuffer, byteOffset, byteLength); 1776 | } else { 1777 | assert(controller._queue.length === 0); 1778 | 1779 | const transferredView = new Uint8Array(transferredBuffer, byteOffset, byteLength); 1780 | ReadableStreamFulfillReadRequest(stream, transferredView, false); 1781 | } 1782 | } else if (ReadableStreamHasBYOBReader(stream) === true) { 1783 | // TODO: Ideally in this branch detaching should happen only if the buffer is not consumed fully. 1784 | ReadableByteStreamControllerEnqueueChunkToQueue(controller, transferredBuffer, byteOffset, byteLength); 1785 | ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller); 1786 | } else { 1787 | assert(IsReadableStreamLocked(stream) === false, 'stream must not be locked'); 1788 | ReadableByteStreamControllerEnqueueChunkToQueue(controller, transferredBuffer, byteOffset, byteLength); 1789 | } 1790 | } 1791 | 1792 | function ReadableByteStreamControllerError(controller, e) { 1793 | const stream = controller._controlledReadableStream; 1794 | 1795 | assert(stream._state === 'readable'); 1796 | 1797 | ReadableByteStreamControllerClearPendingPullIntos(controller); 1798 | 1799 | ResetQueue(controller); 1800 | ReadableStreamError(stream, e); 1801 | } 1802 | 1803 | function ReadableByteStreamControllerGetDesiredSize(controller) { 1804 | const stream = controller._controlledReadableStream; 1805 | const state = stream._state; 1806 | 1807 | if (state === 'errored') { 1808 | return null; 1809 | } 1810 | if (state === 'closed') { 1811 | return 0; 1812 | } 1813 | 1814 | return controller._strategyHWM - controller._queueTotalSize; 1815 | } 1816 | 1817 | function ReadableByteStreamControllerRespond(controller, bytesWritten) { 1818 | bytesWritten = Number(bytesWritten); 1819 | if (IsFiniteNonNegativeNumber(bytesWritten) === false) { 1820 | throw new RangeError('bytesWritten must be a finite'); 1821 | } 1822 | 1823 | assert(controller._pendingPullIntos.length > 0); 1824 | 1825 | ReadableByteStreamControllerRespondInternal(controller, bytesWritten); 1826 | } 1827 | 1828 | function ReadableByteStreamControllerRespondWithNewView(controller, view) { 1829 | assert(controller._pendingPullIntos.length > 0); 1830 | 1831 | const firstDescriptor = controller._pendingPullIntos[0]; 1832 | 1833 | if (firstDescriptor.byteOffset + firstDescriptor.bytesFilled !== view.byteOffset) { 1834 | throw new RangeError('The region specified by view does not match byobRequest'); 1835 | } 1836 | if (firstDescriptor.byteLength !== view.byteLength) { 1837 | throw new RangeError('The buffer of view has different capacity than byobRequest'); 1838 | } 1839 | 1840 | firstDescriptor.buffer = view.buffer; 1841 | 1842 | ReadableByteStreamControllerRespondInternal(controller, view.byteLength); 1843 | } 1844 | 1845 | // Helper functions for the ReadableStream. 1846 | 1847 | function streamBrandCheckException(name) { 1848 | return new TypeError(`ReadableStream.prototype.${name} can only be used on a ReadableStream`); 1849 | } 1850 | 1851 | // Helper functions for the readers. 1852 | 1853 | function readerLockException(name) { 1854 | return new TypeError('Cannot ' + name + ' a stream using a released reader'); 1855 | } 1856 | 1857 | // Helper functions for the ReadableStreamDefaultReader. 1858 | 1859 | function defaultReaderBrandCheckException(name) { 1860 | return new TypeError( 1861 | `ReadableStreamDefaultReader.prototype.${name} can only be used on a ReadableStreamDefaultReader`); 1862 | } 1863 | 1864 | function defaultReaderClosedPromiseInitialize(reader) { 1865 | reader._closedPromise = new Promise((resolve, reject) => { 1866 | reader._closedPromise_resolve = resolve; 1867 | reader._closedPromise_reject = reject; 1868 | }); 1869 | } 1870 | 1871 | function defaultReaderClosedPromiseInitializeAsRejected(reader, reason) { 1872 | reader._closedPromise = Promise.reject(reason); 1873 | reader._closedPromise_resolve = undefined; 1874 | reader._closedPromise_reject = undefined; 1875 | } 1876 | 1877 | function defaultReaderClosedPromiseInitializeAsResolved(reader) { 1878 | reader._closedPromise = Promise.resolve(undefined); 1879 | reader._closedPromise_resolve = undefined; 1880 | reader._closedPromise_reject = undefined; 1881 | } 1882 | 1883 | function defaultReaderClosedPromiseReject(reader, reason) { 1884 | assert(reader._closedPromise_resolve !== undefined); 1885 | assert(reader._closedPromise_reject !== undefined); 1886 | 1887 | reader._closedPromise_reject(reason); 1888 | reader._closedPromise_resolve = undefined; 1889 | reader._closedPromise_reject = undefined; 1890 | } 1891 | 1892 | function defaultReaderClosedPromiseResetToRejected(reader, reason) { 1893 | assert(reader._closedPromise_resolve === undefined); 1894 | assert(reader._closedPromise_reject === undefined); 1895 | 1896 | reader._closedPromise = Promise.reject(reason); 1897 | } 1898 | 1899 | function defaultReaderClosedPromiseResolve(reader) { 1900 | assert(reader._closedPromise_resolve !== undefined); 1901 | assert(reader._closedPromise_reject !== undefined); 1902 | 1903 | reader._closedPromise_resolve(undefined); 1904 | reader._closedPromise_resolve = undefined; 1905 | reader._closedPromise_reject = undefined; 1906 | } 1907 | 1908 | // Helper functions for the ReadableStreamDefaultReader. 1909 | 1910 | function byobReaderBrandCheckException(name) { 1911 | return new TypeError( 1912 | `ReadableStreamBYOBReader.prototype.${name} can only be used on a ReadableStreamBYOBReader`); 1913 | } 1914 | 1915 | // Helper functions for the ReadableStreamDefaultController. 1916 | 1917 | function defaultControllerBrandCheckException(name) { 1918 | return new TypeError( 1919 | `ReadableStreamDefaultController.prototype.${name} can only be used on a ReadableStreamDefaultController`); 1920 | } 1921 | 1922 | // Helper functions for the ReadableStreamBYOBRequest. 1923 | 1924 | function byobRequestBrandCheckException(name) { 1925 | return new TypeError( 1926 | `ReadableStreamBYOBRequest.prototype.${name} can only be used on a ReadableStreamBYOBRequest`); 1927 | } 1928 | 1929 | // Helper functions for the ReadableByteStreamController. 1930 | 1931 | function byteStreamControllerBrandCheckException(name) { 1932 | return new TypeError( 1933 | `ReadableByteStreamController.prototype.${name} can only be used on a ReadableByteStreamController`); 1934 | } 1935 | 1936 | // Helper function for ReadableStream pipeThrough 1937 | 1938 | function ifIsObjectAndHasAPromiseIsHandledInternalSlotSetPromiseIsHandledToTrue(promise) { 1939 | try { 1940 | // This relies on the brand-check that is enforced by Promise.prototype.then(). As with the rest of the reference 1941 | // implementation, it doesn't attempt to do the right thing if someone has modified the global environment. 1942 | Promise.prototype.then.call(promise, undefined, () => {}); 1943 | } catch (e) { 1944 | // The brand check failed, therefore the internal slot is not present and there's nothing further to do. 1945 | } 1946 | } 1947 | -------------------------------------------------------------------------------- /private/streams/transform-stream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('assert'); 3 | const { InvokeOrNoop, PromiseInvokeOrPerformFallback, PromiseInvokeOrNoop, typeIsObject } = require('./helpers.js'); 4 | const { ReadableStream, ReadableStreamDefaultControllerClose, 5 | ReadableStreamDefaultControllerEnqueue, ReadableStreamDefaultControllerError, 6 | ReadableStreamDefaultControllerGetDesiredSize } = require('./readable-stream.js'); 7 | const { WritableStream, WritableStreamDefaultControllerError } = require('./writable-stream.js'); 8 | 9 | // Methods on the transform stream controller object 10 | 11 | function TransformStreamCloseReadable(transformStream) { 12 | // console.log('TransformStreamCloseReadable()'); 13 | 14 | if (transformStream._errored === true) { 15 | throw new TypeError('TransformStream is already errored'); 16 | } 17 | 18 | if (transformStream._readableClosed === true) { 19 | throw new TypeError('Readable side is already closed'); 20 | } 21 | 22 | TransformStreamCloseReadableInternal(transformStream); 23 | } 24 | 25 | function TransformStreamEnqueueToReadable(transformStream, chunk) { 26 | // console.log('TransformStreamEnqueueToReadable()'); 27 | 28 | if (transformStream._errored === true) { 29 | throw new TypeError('TransformStream is already errored'); 30 | } 31 | 32 | if (transformStream._readableClosed === true) { 33 | throw new TypeError('Readable side is already closed'); 34 | } 35 | 36 | // We throttle transformer.transform invocation based on the backpressure of the ReadableStream, but we still 37 | // accept TransformStreamEnqueueToReadable() calls. 38 | 39 | const controller = transformStream._readableController; 40 | 41 | try { 42 | ReadableStreamDefaultControllerEnqueue(controller, chunk); 43 | } catch (e) { 44 | // This happens when readableStrategy.size() throws. 45 | // The ReadableStream has already errored itself. 46 | transformStream._readableClosed = true; 47 | TransformStreamErrorIfNeeded(transformStream, e); 48 | 49 | throw transformStream._storedError; 50 | } 51 | 52 | const desiredSize = ReadableStreamDefaultControllerGetDesiredSize(controller); 53 | const maybeBackpressure = desiredSize <= 0; 54 | 55 | if (maybeBackpressure === true && transformStream._backpressure === false) { 56 | // This allows pull() again. When desiredSize is 0, it's possible that a pull() will happen immediately (but 57 | // asynchronously) after this because of pending read()s and set _backpressure back to false. 58 | // 59 | // If pull() could be called from inside enqueue(), then this logic would be wrong. This cannot happen 60 | // because there is always a promise pending from start() or pull() when _backpressure is false. 61 | TransformStreamSetBackpressure(transformStream, true); 62 | } 63 | } 64 | 65 | function TransformStreamError(transformStream, e) { 66 | if (transformStream._errored === true) { 67 | throw new TypeError('TransformStream is already errored'); 68 | } 69 | 70 | TransformStreamErrorInternal(transformStream, e); 71 | } 72 | 73 | // Abstract operations. 74 | 75 | function TransformStreamCloseReadableInternal(transformStream) { 76 | assert(transformStream._errored === false); 77 | assert(transformStream._readableClosed === false); 78 | 79 | try { 80 | ReadableStreamDefaultControllerClose(transformStream._readableController); 81 | } catch (e) { 82 | assert(false); 83 | } 84 | 85 | transformStream._readableClosed = true; 86 | } 87 | 88 | function TransformStreamErrorIfNeeded(transformStream, e) { 89 | if (transformStream._errored === false) { 90 | TransformStreamErrorInternal(transformStream, e); 91 | } 92 | } 93 | 94 | function TransformStreamErrorInternal(transformStream, e) { 95 | // console.log('TransformStreamErrorInternal()'); 96 | 97 | assert(transformStream._errored === false); 98 | 99 | transformStream._errored = true; 100 | transformStream._storedError = e; 101 | 102 | if (transformStream._writableDone === false) { 103 | WritableStreamDefaultControllerError(transformStream._writableController, e); 104 | } 105 | if (transformStream._readableClosed === false) { 106 | ReadableStreamDefaultControllerError(transformStream._readableController, e); 107 | } 108 | } 109 | 110 | // Used for preventing the next write() call on TransformStreamSink until there 111 | // is no longer backpressure. 112 | function TransformStreamReadableReadyPromise(transformStream) { 113 | assert(transformStream._backpressureChangePromise !== undefined, 114 | '_backpressureChangePromise should have been initialized'); 115 | 116 | if (transformStream._backpressure === false) { 117 | return Promise.resolve(); 118 | } 119 | 120 | assert(transformStream._backpressure === true, '_backpressure should have been initialized'); 121 | 122 | return transformStream._backpressureChangePromise; 123 | } 124 | 125 | function TransformStreamSetBackpressure(transformStream, backpressure) { 126 | // console.log(`TransformStreamSetBackpressure(${backpressure})`); 127 | 128 | // Passes also when called during construction. 129 | assert(transformStream._backpressure !== backpressure, 130 | 'TransformStreamSetBackpressure() should be called only when backpressure is changed'); 131 | 132 | if (transformStream._backpressureChangePromise !== undefined) { 133 | // The fulfillment value is just for a sanity check. 134 | transformStream._backpressureChangePromise_resolve(backpressure); 135 | } 136 | 137 | transformStream._backpressureChangePromise = new Promise(resolve => { 138 | transformStream._backpressureChangePromise_resolve = resolve; 139 | }); 140 | 141 | transformStream._backpressureChangePromise.then(resolution => { 142 | assert(resolution !== backpressure, 143 | '_backpressureChangePromise should be fulfilled only when backpressure is changed'); 144 | }); 145 | 146 | transformStream._backpressure = backpressure; 147 | } 148 | 149 | function TransformStreamDefaultTransform(chunk, transformStreamController) { 150 | const transformStream = transformStreamController._controlledTransformStream; 151 | TransformStreamEnqueueToReadable(transformStream, chunk); 152 | return Promise.resolve(); 153 | } 154 | 155 | function TransformStreamTransform(transformStream, chunk) { 156 | // console.log('TransformStreamTransform()'); 157 | 158 | assert(transformStream._errored === false); 159 | assert(transformStream._transforming === false); 160 | assert(transformStream._backpressure === false); 161 | 162 | transformStream._transforming = true; 163 | 164 | const transformer = transformStream._transformer; 165 | const controller = transformStream._transformStreamController; 166 | 167 | const transformPromise = PromiseInvokeOrPerformFallback(transformer, 'transform', [chunk, controller], 168 | TransformStreamDefaultTransform, [chunk, controller]); 169 | 170 | return transformPromise.then( 171 | () => { 172 | transformStream._transforming = false; 173 | 174 | return TransformStreamReadableReadyPromise(transformStream); 175 | }, 176 | e => { 177 | TransformStreamErrorIfNeeded(transformStream, e); 178 | return Promise.reject(e); 179 | }); 180 | } 181 | 182 | function IsTransformStreamDefaultController(x) { 183 | if (!typeIsObject(x)) { 184 | return false; 185 | } 186 | 187 | if (!Object.prototype.hasOwnProperty.call(x, '_controlledTransformStream')) { 188 | return false; 189 | } 190 | 191 | return true; 192 | } 193 | 194 | function IsTransformStream(x) { 195 | if (!typeIsObject(x)) { 196 | return false; 197 | } 198 | 199 | if (!Object.prototype.hasOwnProperty.call(x, '_transformStreamController')) { 200 | return false; 201 | } 202 | 203 | return true; 204 | } 205 | 206 | class TransformStreamSink { 207 | constructor(transformStream, startPromise) { 208 | this._transformStream = transformStream; 209 | this._startPromise = startPromise; 210 | } 211 | 212 | start(c) { 213 | const transformStream = this._transformStream; 214 | 215 | transformStream._writableController = c; 216 | 217 | return this._startPromise.then(() => TransformStreamReadableReadyPromise(transformStream)); 218 | } 219 | 220 | write(chunk) { 221 | // console.log('TransformStreamSink.write()'); 222 | 223 | const transformStream = this._transformStream; 224 | 225 | return TransformStreamTransform(transformStream, chunk); 226 | } 227 | 228 | abort() { 229 | const transformStream = this._transformStream; 230 | transformStream._writableDone = true; 231 | TransformStreamErrorInternal(transformStream, new TypeError('Writable side aborted')); 232 | } 233 | 234 | close() { 235 | // console.log('TransformStreamSink.close()'); 236 | 237 | const transformStream = this._transformStream; 238 | 239 | assert(transformStream._transforming === false); 240 | 241 | transformStream._writableDone = true; 242 | 243 | const flushPromise = PromiseInvokeOrNoop(transformStream._transformer, 244 | 'flush', [transformStream._transformStreamController]); 245 | // Return a promise that is fulfilled with undefined on success. 246 | return flushPromise.then(() => { 247 | if (transformStream._errored === true) { 248 | return Promise.reject(transformStream._storedError); 249 | } 250 | if (transformStream._readableClosed === false) { 251 | TransformStreamCloseReadableInternal(transformStream); 252 | } 253 | return Promise.resolve(); 254 | }).catch(r => { 255 | TransformStreamErrorIfNeeded(transformStream, r); 256 | return Promise.reject(transformStream._storedError); 257 | }); 258 | } 259 | } 260 | 261 | class TransformStreamSource { 262 | constructor(transformStream, startPromise) { 263 | this._transformStream = transformStream; 264 | this._startPromise = startPromise; 265 | } 266 | 267 | start(c) { 268 | const transformStream = this._transformStream; 269 | 270 | transformStream._readableController = c; 271 | 272 | return this._startPromise.then(() => { 273 | // Prevent the first pull() call until there is backpressure. 274 | 275 | assert(transformStream._backpressureChangePromise !== undefined, 276 | '_backpressureChangePromise should have been initialized'); 277 | 278 | if (transformStream._backpressure === true) { 279 | return Promise.resolve(); 280 | } 281 | 282 | assert(transformStream._backpressure === false, '_backpressure should have been initialized'); 283 | 284 | return transformStream._backpressureChangePromise; 285 | }); 286 | } 287 | 288 | pull() { 289 | // console.log('TransformStreamSource.pull()'); 290 | 291 | const transformStream = this._transformStream; 292 | 293 | // Invariant. Enforced by the promises returned by start() and pull(). 294 | assert(transformStream._backpressure === true, 'pull() should be never called while _backpressure is false'); 295 | 296 | assert(transformStream._backpressureChangePromise !== undefined, 297 | '_backpressureChangePromise should have been initialized'); 298 | 299 | TransformStreamSetBackpressure(transformStream, false); 300 | 301 | // Prevent the next pull() call until there is backpressure. 302 | return transformStream._backpressureChangePromise; 303 | } 304 | 305 | cancel() { 306 | const transformStream = this._transformStream; 307 | transformStream._readableClosed = true; 308 | TransformStreamErrorInternal(transformStream, new TypeError('Readable side canceled')); 309 | } 310 | } 311 | 312 | class TransformStreamDefaultController { 313 | constructor(transformStream) { 314 | if (IsTransformStream(transformStream) === false) { 315 | throw new TypeError('TransformStreamDefaultController can only be ' + 316 | 'constructed with a TransformStream instance'); 317 | } 318 | 319 | if (transformStream._transformStreamController !== undefined) { 320 | throw new TypeError('TransformStreamDefaultController instances can ' + 321 | 'only be created by the TransformStream constructor'); 322 | } 323 | 324 | this._controlledTransformStream = transformStream; 325 | } 326 | 327 | get desiredSize() { 328 | if (IsTransformStreamDefaultController(this) === false) { 329 | throw defaultControllerBrandCheckException('desiredSize'); 330 | } 331 | 332 | const transformStream = this._controlledTransformStream; 333 | const readableController = transformStream._readableController; 334 | 335 | return ReadableStreamDefaultControllerGetDesiredSize(readableController); 336 | } 337 | 338 | enqueue(chunk) { 339 | if (IsTransformStreamDefaultController(this) === false) { 340 | throw defaultControllerBrandCheckException('enqueue'); 341 | } 342 | 343 | TransformStreamEnqueueToReadable(this._controlledTransformStream, chunk); 344 | } 345 | 346 | close() { 347 | if (IsTransformStreamDefaultController(this) === false) { 348 | throw defaultControllerBrandCheckException('close'); 349 | } 350 | 351 | TransformStreamCloseReadable(this._controlledTransformStream); 352 | } 353 | 354 | error(reason) { 355 | if (IsTransformStreamDefaultController(this) === false) { 356 | throw defaultControllerBrandCheckException('error'); 357 | } 358 | 359 | TransformStreamError(this._controlledTransformStream, reason); 360 | } 361 | } 362 | 363 | class TransformStream { 364 | constructor(transformer = {}) { 365 | this._transformer = transformer; 366 | const { readableStrategy, writableStrategy } = transformer; 367 | 368 | this._transforming = false; 369 | this._errored = false; 370 | this._storedError = undefined; 371 | 372 | this._writableController = undefined; 373 | this._readableController = undefined; 374 | this._transformStreamController = undefined; 375 | 376 | this._writableDone = false; 377 | this._readableClosed = false; 378 | 379 | this._backpressure = undefined; 380 | this._backpressureChangePromise = undefined; 381 | this._backpressureChangePromise_resolve = undefined; 382 | 383 | this._transformStreamController = new TransformStreamDefaultController(this); 384 | 385 | let startPromise_resolve; 386 | const startPromise = new Promise(resolve => { 387 | startPromise_resolve = resolve; 388 | }); 389 | 390 | const source = new TransformStreamSource(this, startPromise); 391 | 392 | this._readable = new ReadableStream(source, readableStrategy); 393 | 394 | const sink = new TransformStreamSink(this, startPromise); 395 | 396 | this._writable = new WritableStream(sink, writableStrategy); 397 | 398 | assert(this._writableController !== undefined); 399 | assert(this._readableController !== undefined); 400 | 401 | const desiredSize = ReadableStreamDefaultControllerGetDesiredSize(this._readableController); 402 | // Set _backpressure based on desiredSize. As there is no read() at this point, we can just interpret 403 | // desiredSize being non-positive as backpressure. 404 | TransformStreamSetBackpressure(this, desiredSize <= 0); 405 | 406 | const transformStream = this; 407 | const startResult = InvokeOrNoop(transformer, 'start', 408 | [transformStream._transformStreamController]); 409 | startPromise_resolve(startResult); 410 | startPromise.catch(e => { 411 | // The underlyingSink and underlyingSource will error the readable and writable ends on their own. 412 | if (transformStream._errored === false) { 413 | transformStream._errored = true; 414 | transformStream._storedError = e; 415 | } 416 | }); 417 | } 418 | 419 | get readable() { 420 | if (IsTransformStream(this) === false) { 421 | throw streamBrandCheckException('readable'); 422 | } 423 | 424 | return this._readable; 425 | } 426 | 427 | get writable() { 428 | if (IsTransformStream(this) === false) { 429 | throw streamBrandCheckException('writable'); 430 | } 431 | 432 | return this._writable; 433 | } 434 | } 435 | 436 | module.exports = { TransformStream }; 437 | 438 | // Helper functions for the TransformStreamDefaultController. 439 | 440 | function defaultControllerBrandCheckException(name) { 441 | return new TypeError( 442 | `TransformStreamDefaultController.prototype.${name} can only be used on a TransformStreamDefaultController`); 443 | } 444 | 445 | // Helper functions for the TransformStream. 446 | 447 | function streamBrandCheckException(name) { 448 | return new TypeError( 449 | `TransformStream.prototype.${name} can only be used on a TransformStream`); 450 | } 451 | -------------------------------------------------------------------------------- /private/streams/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('assert'); 3 | 4 | exports.rethrowAssertionErrorRejection = e => { 5 | // Used throughout the reference implementation, as `.catch(rethrowAssertionErrorRejection)`, to ensure any errors 6 | // get shown. There are places in the spec where we do promise transformations and purposefully ignore or don't 7 | // expect any errors, but assertion errors are always problematic. 8 | if (e && e.constructor === assert.AssertionError) { 9 | setTimeout(() => { 10 | throw e; 11 | }, 0); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /private/streams/writable-stream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('assert'); 3 | const { InvokeOrNoop, PromiseInvokeOrNoop, ValidateAndNormalizeQueuingStrategy, typeIsObject } = 4 | require('./helpers.js'); 5 | const { rethrowAssertionErrorRejection } = require('./utils.js'); 6 | const { DequeueValue, EnqueueValueWithSize, PeekQueueValue, ResetQueue } = require('./queue-with-sizes.js'); 7 | 8 | const StartSteps = Symbol('[[StartSteps]]'); 9 | const AbortSteps = Symbol('[[AbortSteps]]'); 10 | const ErrorSteps = Symbol('[[ErrorSteps]]'); 11 | 12 | class WritableStream { 13 | constructor(underlyingSink = {}, { size, highWaterMark = 1 } = {}) { 14 | this._state = 'writable'; 15 | 16 | // The error that will be reported by new method calls once the state becomes errored. Only set when [[state]] is 17 | // 'erroring' or 'errored'. May be set to an undefined value. 18 | this._storedError = undefined; 19 | 20 | this._writer = undefined; 21 | 22 | // Initialize to undefined first because the constructor of the controller checks this 23 | // variable to validate the caller. 24 | this._writableStreamController = undefined; 25 | 26 | // This queue is placed here instead of the writer class in order to allow for passing a writer to the next data 27 | // producer without waiting for the queued writes to finish. 28 | this._writeRequests = []; 29 | 30 | // Write requests are removed from _writeRequests when write() is called on the underlying sink. This prevents 31 | // them from being erroneously rejected on error. If a write() call is in-flight, the request is stored here. 32 | this._inFlightWriteRequest = undefined; 33 | 34 | // The promise that was returned from writer.close(). Stored here because it may be fulfilled after the writer 35 | // has been detached. 36 | this._closeRequest = undefined; 37 | 38 | // Close request is removed from _closeRequest when close() is called on the underlying sink. This prevents it 39 | // from being erroneously rejected on error. If a close() call is in-flight, the request is stored here. 40 | this._inFlightCloseRequest = undefined; 41 | 42 | // The promise that was returned from writer.abort(). This may also be fulfilled after the writer has detached. 43 | this._pendingAbortRequest = undefined; 44 | 45 | // The backpressure signal set by the controller. 46 | this._backpressure = false; 47 | 48 | const type = underlyingSink.type; 49 | 50 | if (type !== undefined) { 51 | throw new RangeError('Invalid type is specified'); 52 | } 53 | 54 | this._writableStreamController = new WritableStreamDefaultController(this, underlyingSink, size, highWaterMark); 55 | this._writableStreamController[StartSteps](); 56 | } 57 | 58 | get locked() { 59 | if (IsWritableStream(this) === false) { 60 | throw streamBrandCheckException('locked'); 61 | } 62 | 63 | return IsWritableStreamLocked(this); 64 | } 65 | 66 | abort(reason) { 67 | if (IsWritableStream(this) === false) { 68 | return Promise.reject(streamBrandCheckException('abort')); 69 | } 70 | 71 | if (IsWritableStreamLocked(this) === true) { 72 | return Promise.reject(new TypeError('Cannot abort a stream that already has a writer')); 73 | } 74 | 75 | return WritableStreamAbort(this, reason); 76 | } 77 | 78 | getWriter() { 79 | if (IsWritableStream(this) === false) { 80 | throw streamBrandCheckException('getWriter'); 81 | } 82 | 83 | return AcquireWritableStreamDefaultWriter(this); 84 | } 85 | } 86 | 87 | module.exports = { 88 | AcquireWritableStreamDefaultWriter, 89 | IsWritableStream, 90 | IsWritableStreamLocked, 91 | WritableStream, 92 | WritableStreamAbort, 93 | WritableStreamDefaultControllerError, 94 | WritableStreamDefaultWriterCloseWithErrorPropagation, 95 | WritableStreamDefaultWriterRelease, 96 | WritableStreamDefaultWriterWrite, 97 | WritableStreamCloseQueuedOrInFlight 98 | }; 99 | 100 | // Abstract operations for the WritableStream. 101 | 102 | function AcquireWritableStreamDefaultWriter(stream) { 103 | return new WritableStreamDefaultWriter(stream); 104 | } 105 | 106 | function IsWritableStream(x) { 107 | if (!typeIsObject(x)) { 108 | return false; 109 | } 110 | 111 | if (!Object.prototype.hasOwnProperty.call(x, '_writableStreamController')) { 112 | return false; 113 | } 114 | 115 | return true; 116 | } 117 | 118 | function IsWritableStreamLocked(stream) { 119 | assert(IsWritableStream(stream) === true, 'IsWritableStreamLocked should only be used on known writable streams'); 120 | 121 | if (stream._writer === undefined) { 122 | return false; 123 | } 124 | 125 | return true; 126 | } 127 | 128 | function WritableStreamAbort(stream, reason) { 129 | const state = stream._state; 130 | if (state === 'closed') { 131 | return Promise.resolve(undefined); 132 | } 133 | if (state === 'errored') { 134 | return Promise.reject(stream._storedError); 135 | } 136 | const error = new TypeError('Requested to abort'); 137 | if (stream._pendingAbortRequest !== undefined) { 138 | return Promise.reject(error); 139 | } 140 | 141 | assert(state === 'writable' || state === 'erroring', 'state must be writable or erroring'); 142 | 143 | let wasAlreadyErroring = false; 144 | if (state === 'erroring') { 145 | wasAlreadyErroring = true; 146 | // reason will not be used, so don't keep a reference to it. 147 | reason = undefined; 148 | } 149 | 150 | const promise = new Promise((resolve, reject) => { 151 | stream._pendingAbortRequest = { 152 | _resolve: resolve, 153 | _reject: reject, 154 | _reason: reason, 155 | _wasAlreadyErroring: wasAlreadyErroring 156 | }; 157 | }); 158 | 159 | if (wasAlreadyErroring === false) { 160 | WritableStreamStartErroring(stream, error); 161 | } 162 | 163 | return promise; 164 | } 165 | 166 | // WritableStream API exposed for controllers. 167 | 168 | function WritableStreamAddWriteRequest(stream) { 169 | assert(IsWritableStreamLocked(stream) === true); 170 | assert(stream._state === 'writable'); 171 | 172 | const promise = new Promise((resolve, reject) => { 173 | const writeRequest = { 174 | _resolve: resolve, 175 | _reject: reject 176 | }; 177 | 178 | stream._writeRequests.push(writeRequest); 179 | }); 180 | 181 | return promise; 182 | } 183 | 184 | function WritableStreamDealWithRejection(stream, error) { 185 | const state = stream._state; 186 | 187 | if (state === 'writable') { 188 | WritableStreamStartErroring(stream, error); 189 | return; 190 | } 191 | 192 | assert(state === 'erroring'); 193 | WritableStreamFinishErroring(stream); 194 | } 195 | 196 | function WritableStreamStartErroring(stream, reason) { 197 | assert(stream._storedError === undefined, 'stream._storedError === undefined'); 198 | assert(stream._state === 'writable', 'state must be writable'); 199 | 200 | const controller = stream._writableStreamController; 201 | assert(controller !== undefined, 'controller must not be undefined'); 202 | 203 | stream._state = 'erroring'; 204 | stream._storedError = reason; 205 | const writer = stream._writer; 206 | if (writer !== undefined) { 207 | WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason); 208 | } 209 | 210 | if (WritableStreamHasOperationMarkedInFlight(stream) === false && controller._started === true) { 211 | WritableStreamFinishErroring(stream); 212 | } 213 | } 214 | 215 | function WritableStreamFinishErroring(stream) { 216 | assert(stream._state === 'erroring', 'stream._state === erroring'); 217 | assert(WritableStreamHasOperationMarkedInFlight(stream) === false, 218 | 'WritableStreamHasOperationMarkedInFlight(stream) === false'); 219 | stream._state = 'errored'; 220 | stream._writableStreamController[ErrorSteps](); 221 | 222 | const storedError = stream._storedError; 223 | for (const writeRequest of stream._writeRequests) { 224 | writeRequest._reject(storedError); 225 | } 226 | stream._writeRequests = []; 227 | 228 | if (stream._pendingAbortRequest === undefined) { 229 | WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream); 230 | return; 231 | } 232 | 233 | const abortRequest = stream._pendingAbortRequest; 234 | stream._pendingAbortRequest = undefined; 235 | 236 | if (abortRequest._wasAlreadyErroring === true) { 237 | abortRequest._reject(storedError); 238 | WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream); 239 | return; 240 | } 241 | 242 | const promise = stream._writableStreamController[AbortSteps](abortRequest._reason); 243 | promise.then( 244 | () => { 245 | abortRequest._resolve(); 246 | WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream); 247 | }, 248 | reason => { 249 | abortRequest._reject(reason); 250 | WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream); 251 | }); 252 | } 253 | 254 | function WritableStreamFinishInFlightWrite(stream) { 255 | assert(stream._inFlightWriteRequest !== undefined); 256 | stream._inFlightWriteRequest._resolve(undefined); 257 | stream._inFlightWriteRequest = undefined; 258 | } 259 | 260 | function WritableStreamFinishInFlightWriteWithError(stream, error) { 261 | assert(stream._inFlightWriteRequest !== undefined); 262 | stream._inFlightWriteRequest._reject(error); 263 | stream._inFlightWriteRequest = undefined; 264 | 265 | assert(stream._state === 'writable' || stream._state === 'erroring'); 266 | 267 | WritableStreamDealWithRejection(stream, error); 268 | } 269 | 270 | function WritableStreamFinishInFlightClose(stream) { 271 | assert(stream._inFlightCloseRequest !== undefined); 272 | stream._inFlightCloseRequest._resolve(undefined); 273 | stream._inFlightCloseRequest = undefined; 274 | 275 | const state = stream._state; 276 | 277 | assert(state === 'writable' || state === 'erroring'); 278 | 279 | if (state === 'erroring') { 280 | // The error was too late to do anything, so it is ignored. 281 | stream._storedError = undefined; 282 | if (stream._pendingAbortRequest !== undefined) { 283 | stream._pendingAbortRequest._resolve(); 284 | stream._pendingAbortRequest = undefined; 285 | } 286 | } 287 | 288 | stream._state = 'closed'; 289 | 290 | const writer = stream._writer; 291 | if (writer !== undefined) { 292 | defaultWriterClosedPromiseResolve(writer); 293 | } 294 | 295 | assert(stream._pendingAbortRequest === undefined, 'stream._pendingAbortRequest === undefined'); 296 | assert(stream._storedError === undefined, 'stream._storedError === undefined'); 297 | } 298 | 299 | function WritableStreamFinishInFlightCloseWithError(stream, error) { 300 | assert(stream._inFlightCloseRequest !== undefined); 301 | stream._inFlightCloseRequest._reject(error); 302 | stream._inFlightCloseRequest = undefined; 303 | 304 | assert(stream._state === 'writable' || stream._state === 'erroring'); 305 | 306 | // Never execute sink abort() after sink close(). 307 | if (stream._pendingAbortRequest !== undefined) { 308 | stream._pendingAbortRequest._reject(error); 309 | stream._pendingAbortRequest = undefined; 310 | } 311 | WritableStreamDealWithRejection(stream, error); 312 | } 313 | 314 | // TODO(ricea): Fix alphabetical order. 315 | function WritableStreamCloseQueuedOrInFlight(stream) { 316 | if (stream._closeRequest === undefined && stream._inFlightCloseRequest === undefined) { 317 | return false; 318 | } 319 | 320 | return true; 321 | } 322 | 323 | function WritableStreamHasOperationMarkedInFlight(stream) { 324 | if (stream._inFlightWriteRequest === undefined && stream._inFlightCloseRequest === undefined) { 325 | return false; 326 | } 327 | 328 | return true; 329 | } 330 | 331 | function WritableStreamMarkCloseRequestInFlight(stream) { 332 | assert(stream._inFlightCloseRequest === undefined); 333 | assert(stream._closeRequest !== undefined); 334 | stream._inFlightCloseRequest = stream._closeRequest; 335 | stream._closeRequest = undefined; 336 | } 337 | 338 | function WritableStreamMarkFirstWriteRequestInFlight(stream) { 339 | assert(stream._inFlightWriteRequest === undefined, 'there must be no pending write request'); 340 | assert(stream._writeRequests.length !== 0, 'writeRequests must not be empty'); 341 | stream._inFlightWriteRequest = stream._writeRequests.shift(); 342 | } 343 | 344 | function WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream) { 345 | assert(stream._state === 'errored', '_stream_.[[state]] is `"errored"`'); 346 | if (stream._closeRequest !== undefined) { 347 | assert(stream._inFlightCloseRequest === undefined); 348 | 349 | stream._closeRequest._reject(stream._storedError); 350 | stream._closeRequest = undefined; 351 | } 352 | const writer = stream._writer; 353 | if (writer !== undefined) { 354 | defaultWriterClosedPromiseReject(writer, stream._storedError); 355 | writer._closedPromise.catch(() => {}); 356 | } 357 | } 358 | 359 | function WritableStreamUpdateBackpressure(stream, backpressure) { 360 | assert(stream._state === 'writable'); 361 | assert(WritableStreamCloseQueuedOrInFlight(stream) === false); 362 | 363 | const writer = stream._writer; 364 | if (writer !== undefined && backpressure !== stream._backpressure) { 365 | if (backpressure === true) { 366 | defaultWriterReadyPromiseReset(writer); 367 | } else { 368 | assert(backpressure === false); 369 | 370 | defaultWriterReadyPromiseResolve(writer); 371 | } 372 | } 373 | 374 | stream._backpressure = backpressure; 375 | } 376 | 377 | class WritableStreamDefaultWriter { 378 | constructor(stream) { 379 | if (IsWritableStream(stream) === false) { 380 | throw new TypeError('WritableStreamDefaultWriter can only be constructed with a WritableStream instance'); 381 | } 382 | if (IsWritableStreamLocked(stream) === true) { 383 | throw new TypeError('This stream has already been locked for exclusive writing by another writer'); 384 | } 385 | 386 | this._ownerWritableStream = stream; 387 | stream._writer = this; 388 | 389 | const state = stream._state; 390 | 391 | if (state === 'writable') { 392 | if (WritableStreamCloseQueuedOrInFlight(stream) === false && stream._backpressure === true) { 393 | defaultWriterReadyPromiseInitialize(this); 394 | } else { 395 | defaultWriterReadyPromiseInitializeAsResolved(this); 396 | } 397 | 398 | defaultWriterClosedPromiseInitialize(this); 399 | } else if (state === 'erroring') { 400 | defaultWriterReadyPromiseInitializeAsRejected(this, stream._storedError); 401 | this._readyPromise.catch(() => {}); 402 | defaultWriterClosedPromiseInitialize(this); 403 | } else if (state === 'closed') { 404 | defaultWriterReadyPromiseInitializeAsResolved(this); 405 | defaultWriterClosedPromiseInitializeAsResolved(this); 406 | } else { 407 | assert(state === 'errored', 'state must be errored'); 408 | 409 | const storedError = stream._storedError; 410 | defaultWriterReadyPromiseInitializeAsRejected(this, storedError); 411 | this._readyPromise.catch(() => {}); 412 | defaultWriterClosedPromiseInitializeAsRejected(this, storedError); 413 | this._closedPromise.catch(() => {}); 414 | } 415 | } 416 | 417 | get closed() { 418 | if (IsWritableStreamDefaultWriter(this) === false) { 419 | return Promise.reject(defaultWriterBrandCheckException('closed')); 420 | } 421 | 422 | return this._closedPromise; 423 | } 424 | 425 | get desiredSize() { 426 | if (IsWritableStreamDefaultWriter(this) === false) { 427 | throw defaultWriterBrandCheckException('desiredSize'); 428 | } 429 | 430 | if (this._ownerWritableStream === undefined) { 431 | throw defaultWriterLockException('desiredSize'); 432 | } 433 | 434 | return WritableStreamDefaultWriterGetDesiredSize(this); 435 | } 436 | 437 | get ready() { 438 | if (IsWritableStreamDefaultWriter(this) === false) { 439 | return Promise.reject(defaultWriterBrandCheckException('ready')); 440 | } 441 | 442 | return this._readyPromise; 443 | } 444 | 445 | abort(reason) { 446 | if (IsWritableStreamDefaultWriter(this) === false) { 447 | return Promise.reject(defaultWriterBrandCheckException('abort')); 448 | } 449 | 450 | if (this._ownerWritableStream === undefined) { 451 | return Promise.reject(defaultWriterLockException('abort')); 452 | } 453 | 454 | return WritableStreamDefaultWriterAbort(this, reason); 455 | } 456 | 457 | close() { 458 | if (IsWritableStreamDefaultWriter(this) === false) { 459 | return Promise.reject(defaultWriterBrandCheckException('close')); 460 | } 461 | 462 | const stream = this._ownerWritableStream; 463 | 464 | if (stream === undefined) { 465 | return Promise.reject(defaultWriterLockException('close')); 466 | } 467 | 468 | if (WritableStreamCloseQueuedOrInFlight(stream) === true) { 469 | return Promise.reject(new TypeError('cannot close an already-closing stream')); 470 | } 471 | 472 | return WritableStreamDefaultWriterClose(this); 473 | } 474 | 475 | releaseLock() { 476 | if (IsWritableStreamDefaultWriter(this) === false) { 477 | throw defaultWriterBrandCheckException('releaseLock'); 478 | } 479 | 480 | const stream = this._ownerWritableStream; 481 | 482 | if (stream === undefined) { 483 | return; 484 | } 485 | 486 | assert(stream._writer !== undefined); 487 | 488 | WritableStreamDefaultWriterRelease(this); 489 | } 490 | 491 | write(chunk) { 492 | if (IsWritableStreamDefaultWriter(this) === false) { 493 | return Promise.reject(defaultWriterBrandCheckException('write')); 494 | } 495 | 496 | if (this._ownerWritableStream === undefined) { 497 | return Promise.reject(defaultWriterLockException('write to')); 498 | } 499 | 500 | return WritableStreamDefaultWriterWrite(this, chunk); 501 | } 502 | } 503 | 504 | // Abstract operations for the WritableStreamDefaultWriter. 505 | 506 | function IsWritableStreamDefaultWriter(x) { 507 | if (!typeIsObject(x)) { 508 | return false; 509 | } 510 | 511 | if (!Object.prototype.hasOwnProperty.call(x, '_ownerWritableStream')) { 512 | return false; 513 | } 514 | 515 | return true; 516 | } 517 | 518 | // A client of WritableStreamDefaultWriter may use these functions directly to bypass state check. 519 | 520 | function WritableStreamDefaultWriterAbort(writer, reason) { 521 | const stream = writer._ownerWritableStream; 522 | 523 | assert(stream !== undefined); 524 | 525 | return WritableStreamAbort(stream, reason); 526 | } 527 | 528 | function WritableStreamDefaultWriterClose(writer) { 529 | const stream = writer._ownerWritableStream; 530 | 531 | assert(stream !== undefined); 532 | 533 | const state = stream._state; 534 | if (state === 'closed' || state === 'errored') { 535 | return Promise.reject(new TypeError( 536 | `The stream (in ${state} state) is not in the writable state and cannot be closed`)); 537 | } 538 | 539 | assert(state === 'writable' || state === 'erroring'); 540 | assert(WritableStreamCloseQueuedOrInFlight(stream) === false); 541 | 542 | const promise = new Promise((resolve, reject) => { 543 | const closeRequest = { 544 | _resolve: resolve, 545 | _reject: reject 546 | }; 547 | 548 | stream._closeRequest = closeRequest; 549 | }); 550 | 551 | if (stream._backpressure === true && state === 'writable') { 552 | defaultWriterReadyPromiseResolve(writer); 553 | } 554 | 555 | WritableStreamDefaultControllerClose(stream._writableStreamController); 556 | 557 | return promise; 558 | } 559 | 560 | 561 | function WritableStreamDefaultWriterCloseWithErrorPropagation(writer) { 562 | const stream = writer._ownerWritableStream; 563 | 564 | assert(stream !== undefined); 565 | 566 | const state = stream._state; 567 | if (WritableStreamCloseQueuedOrInFlight(stream) === true || state === 'closed') { 568 | return Promise.resolve(); 569 | } 570 | 571 | if (state === 'errored') { 572 | return Promise.reject(stream._storedError); 573 | } 574 | 575 | assert(state === 'writable' || state === 'erroring'); 576 | 577 | return WritableStreamDefaultWriterClose(writer); 578 | } 579 | 580 | function WritableStreamDefaultWriterEnsureClosedPromiseRejected(writer, error) { 581 | if (writer._closedPromiseState === 'pending') { 582 | defaultWriterClosedPromiseReject(writer, error); 583 | } else { 584 | defaultWriterClosedPromiseResetToRejected(writer, error); 585 | } 586 | writer._closedPromise.catch(() => {}); 587 | } 588 | 589 | function WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, error) { 590 | if (writer._readyPromiseState === 'pending') { 591 | defaultWriterReadyPromiseReject(writer, error); 592 | } else { 593 | defaultWriterReadyPromiseResetToRejected(writer, error); 594 | } 595 | writer._readyPromise.catch(() => {}); 596 | } 597 | 598 | function WritableStreamDefaultWriterGetDesiredSize(writer) { 599 | const stream = writer._ownerWritableStream; 600 | const state = stream._state; 601 | 602 | if (state === 'errored' || state === 'erroring') { 603 | return null; 604 | } 605 | 606 | if (state === 'closed') { 607 | return 0; 608 | } 609 | 610 | return WritableStreamDefaultControllerGetDesiredSize(stream._writableStreamController); 611 | } 612 | 613 | function WritableStreamDefaultWriterRelease(writer) { 614 | const stream = writer._ownerWritableStream; 615 | assert(stream !== undefined); 616 | assert(stream._writer === writer); 617 | 618 | const releasedError = new TypeError( 619 | 'Writer was released and can no longer be used to monitor the stream\'s closedness'); 620 | 621 | WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, releasedError); 622 | 623 | // The state transitions to "errored" before the sink abort() method runs, but the writer.closed promise is not 624 | // rejected until afterwards. This means that simply testing state will not work. 625 | WritableStreamDefaultWriterEnsureClosedPromiseRejected(writer, releasedError); 626 | 627 | stream._writer = undefined; 628 | writer._ownerWritableStream = undefined; 629 | } 630 | 631 | function WritableStreamDefaultWriterWrite(writer, chunk) { 632 | const stream = writer._ownerWritableStream; 633 | 634 | assert(stream !== undefined); 635 | 636 | const controller = stream._writableStreamController; 637 | 638 | const chunkSize = WritableStreamDefaultControllerGetChunkSize(controller, chunk); 639 | 640 | if (stream !== writer._ownerWritableStream) { 641 | return Promise.reject(defaultWriterLockException('write to')); 642 | } 643 | 644 | const state = stream._state; 645 | if (state === 'errored') { 646 | return Promise.reject(stream._storedError); 647 | } 648 | if (WritableStreamCloseQueuedOrInFlight(stream) === true || state === 'closed') { 649 | return Promise.reject(new TypeError('The stream is closing or closed and cannot be written to')); 650 | } 651 | if (state === 'erroring') { 652 | return Promise.reject(stream._storedError); 653 | } 654 | 655 | assert(state === 'writable'); 656 | 657 | const promise = WritableStreamAddWriteRequest(stream); 658 | 659 | WritableStreamDefaultControllerWrite(controller, chunk, chunkSize); 660 | 661 | return promise; 662 | } 663 | 664 | class WritableStreamDefaultController { 665 | constructor(stream, underlyingSink, size, highWaterMark) { 666 | if (IsWritableStream(stream) === false) { 667 | throw new TypeError('WritableStreamDefaultController can only be constructed with a WritableStream instance'); 668 | } 669 | 670 | if (stream._writableStreamController !== undefined) { 671 | throw new TypeError( 672 | 'WritableStreamDefaultController instances can only be created by the WritableStream constructor'); 673 | } 674 | 675 | this._controlledWritableStream = stream; 676 | 677 | this._underlyingSink = underlyingSink; 678 | 679 | // Need to set the slots so that the assert doesn't fire. In the spec the slots already exist implicitly. 680 | this._queue = undefined; 681 | this._queueTotalSize = undefined; 682 | ResetQueue(this); 683 | 684 | this._started = false; 685 | 686 | const normalizedStrategy = ValidateAndNormalizeQueuingStrategy(size, highWaterMark); 687 | this._strategySize = normalizedStrategy.size; 688 | this._strategyHWM = normalizedStrategy.highWaterMark; 689 | 690 | const backpressure = WritableStreamDefaultControllerGetBackpressure(this); 691 | WritableStreamUpdateBackpressure(stream, backpressure); 692 | } 693 | 694 | error(e) { 695 | if (IsWritableStreamDefaultController(this) === false) { 696 | throw new TypeError( 697 | 'WritableStreamDefaultController.prototype.error can only be used on a WritableStreamDefaultController'); 698 | } 699 | const state = this._controlledWritableStream._state; 700 | if (state !== 'writable') { 701 | // The stream is closed, errored or will be soon. The sink can't do anything useful if it gets an error here, so 702 | // just treat it as a no-op. 703 | return; 704 | } 705 | 706 | WritableStreamDefaultControllerError(this, e); 707 | } 708 | 709 | [AbortSteps](reason) { 710 | return PromiseInvokeOrNoop(this._underlyingSink, 'abort', [reason]); 711 | } 712 | 713 | [ErrorSteps]() { 714 | ResetQueue(this); 715 | } 716 | 717 | [StartSteps]() { 718 | const startResult = InvokeOrNoop(this._underlyingSink, 'start', [this]); 719 | const stream = this._controlledWritableStream; 720 | 721 | Promise.resolve(startResult).then( 722 | () => { 723 | assert(stream._state === 'writable' || stream._state === 'erroring'); 724 | this._started = true; 725 | WritableStreamDefaultControllerAdvanceQueueIfNeeded(this); 726 | }, 727 | r => { 728 | assert(stream._state === 'writable' || stream._state === 'erroring'); 729 | this._started = true; 730 | WritableStreamDealWithRejection(stream, r); 731 | } 732 | ) 733 | .catch(rethrowAssertionErrorRejection); 734 | } 735 | } 736 | 737 | // Abstract operations implementing interface required by the WritableStream. 738 | 739 | function WritableStreamDefaultControllerClose(controller) { 740 | EnqueueValueWithSize(controller, 'close', 0); 741 | WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller); 742 | } 743 | 744 | function WritableStreamDefaultControllerGetChunkSize(controller, chunk) { 745 | const strategySize = controller._strategySize; 746 | 747 | if (strategySize === undefined) { 748 | return 1; 749 | } 750 | 751 | try { 752 | return strategySize(chunk); 753 | } catch (chunkSizeE) { 754 | WritableStreamDefaultControllerErrorIfNeeded(controller, chunkSizeE); 755 | return 1; 756 | } 757 | } 758 | 759 | function WritableStreamDefaultControllerGetDesiredSize(controller) { 760 | return controller._strategyHWM - controller._queueTotalSize; 761 | } 762 | 763 | function WritableStreamDefaultControllerWrite(controller, chunk, chunkSize) { 764 | const writeRecord = { chunk }; 765 | 766 | try { 767 | EnqueueValueWithSize(controller, writeRecord, chunkSize); 768 | } catch (enqueueE) { 769 | WritableStreamDefaultControllerErrorIfNeeded(controller, enqueueE); 770 | return; 771 | } 772 | 773 | const stream = controller._controlledWritableStream; 774 | if (WritableStreamCloseQueuedOrInFlight(stream) === false && stream._state === 'writable') { 775 | const backpressure = WritableStreamDefaultControllerGetBackpressure(controller); 776 | WritableStreamUpdateBackpressure(stream, backpressure); 777 | } 778 | 779 | WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller); 780 | } 781 | 782 | // Abstract operations for the WritableStreamDefaultController. 783 | 784 | function IsWritableStreamDefaultController(x) { 785 | if (!typeIsObject(x)) { 786 | return false; 787 | } 788 | 789 | if (!Object.prototype.hasOwnProperty.call(x, '_underlyingSink')) { 790 | return false; 791 | } 792 | 793 | return true; 794 | } 795 | 796 | function WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller) { 797 | const stream = controller._controlledWritableStream; 798 | 799 | if (controller._started === false) { 800 | return; 801 | } 802 | 803 | if (stream._inFlightWriteRequest !== undefined) { 804 | return; 805 | } 806 | 807 | const state = stream._state; 808 | if (state === 'closed' || state === 'errored') { 809 | return; 810 | } 811 | if (state === 'erroring') { 812 | WritableStreamFinishErroring(stream); 813 | return; 814 | } 815 | 816 | if (controller._queue.length === 0) { 817 | return; 818 | } 819 | 820 | const writeRecord = PeekQueueValue(controller); 821 | if (writeRecord === 'close') { 822 | WritableStreamDefaultControllerProcessClose(controller); 823 | } else { 824 | WritableStreamDefaultControllerProcessWrite(controller, writeRecord.chunk); 825 | } 826 | } 827 | 828 | function WritableStreamDefaultControllerErrorIfNeeded(controller, error) { 829 | if (controller._controlledWritableStream._state === 'writable') { 830 | WritableStreamDefaultControllerError(controller, error); 831 | } 832 | } 833 | 834 | function WritableStreamDefaultControllerProcessClose(controller) { 835 | const stream = controller._controlledWritableStream; 836 | 837 | WritableStreamMarkCloseRequestInFlight(stream); 838 | 839 | DequeueValue(controller); 840 | assert(controller._queue.length === 0, 'queue must be empty once the final write record is dequeued'); 841 | 842 | const sinkClosePromise = PromiseInvokeOrNoop(controller._underlyingSink, 'close', []); 843 | sinkClosePromise.then( 844 | () => { 845 | WritableStreamFinishInFlightClose(stream); 846 | }, 847 | reason => { 848 | WritableStreamFinishInFlightCloseWithError(stream, reason); 849 | } 850 | ) 851 | .catch(rethrowAssertionErrorRejection); 852 | } 853 | 854 | function WritableStreamDefaultControllerProcessWrite(controller, chunk) { 855 | const stream = controller._controlledWritableStream; 856 | 857 | WritableStreamMarkFirstWriteRequestInFlight(stream); 858 | 859 | const sinkWritePromise = PromiseInvokeOrNoop(controller._underlyingSink, 'write', [chunk, controller]); 860 | sinkWritePromise.then( 861 | () => { 862 | WritableStreamFinishInFlightWrite(stream); 863 | 864 | const state = stream._state; 865 | assert(state === 'writable' || state === 'erroring'); 866 | 867 | DequeueValue(controller); 868 | 869 | if (WritableStreamCloseQueuedOrInFlight(stream) === false && state === 'writable') { 870 | const backpressure = WritableStreamDefaultControllerGetBackpressure(controller); 871 | WritableStreamUpdateBackpressure(stream, backpressure); 872 | } 873 | 874 | WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller); 875 | }, 876 | reason => { 877 | WritableStreamFinishInFlightWriteWithError(stream, reason); 878 | } 879 | ) 880 | .catch(rethrowAssertionErrorRejection); 881 | } 882 | 883 | function WritableStreamDefaultControllerGetBackpressure(controller) { 884 | const desiredSize = WritableStreamDefaultControllerGetDesiredSize(controller); 885 | return desiredSize <= 0; 886 | } 887 | 888 | // A client of WritableStreamDefaultController may use these functions directly to bypass state check. 889 | 890 | function WritableStreamDefaultControllerError(controller, error) { 891 | const stream = controller._controlledWritableStream; 892 | 893 | assert(stream._state === 'writable'); 894 | 895 | WritableStreamStartErroring(stream, error); 896 | } 897 | 898 | // Helper functions for the WritableStream. 899 | 900 | function streamBrandCheckException(name) { 901 | return new TypeError(`WritableStream.prototype.${name} can only be used on a WritableStream`); 902 | } 903 | 904 | // Helper functions for the WritableStreamDefaultWriter. 905 | 906 | function defaultWriterBrandCheckException(name) { 907 | return new TypeError( 908 | `WritableStreamDefaultWriter.prototype.${name} can only be used on a WritableStreamDefaultWriter`); 909 | } 910 | 911 | function defaultWriterLockException(name) { 912 | return new TypeError('Cannot ' + name + ' a stream using a released writer'); 913 | } 914 | 915 | function defaultWriterClosedPromiseInitialize(writer) { 916 | writer._closedPromise = new Promise((resolve, reject) => { 917 | writer._closedPromise_resolve = resolve; 918 | writer._closedPromise_reject = reject; 919 | writer._closedPromiseState = 'pending'; 920 | }); 921 | } 922 | 923 | function defaultWriterClosedPromiseInitializeAsRejected(writer, reason) { 924 | writer._closedPromise = Promise.reject(reason); 925 | writer._closedPromise_resolve = undefined; 926 | writer._closedPromise_reject = undefined; 927 | writer._closedPromiseState = 'rejected'; 928 | } 929 | 930 | function defaultWriterClosedPromiseInitializeAsResolved(writer) { 931 | writer._closedPromise = Promise.resolve(undefined); 932 | writer._closedPromise_resolve = undefined; 933 | writer._closedPromise_reject = undefined; 934 | writer._closedPromiseState = 'resolved'; 935 | } 936 | 937 | function defaultWriterClosedPromiseReject(writer, reason) { 938 | assert(writer._closedPromise_resolve !== undefined, 'writer._closedPromise_resolve !== undefined'); 939 | assert(writer._closedPromise_reject !== undefined, 'writer._closedPromise_reject !== undefined'); 940 | assert(writer._closedPromiseState === 'pending', 'writer._closedPromiseState is pending'); 941 | 942 | writer._closedPromise_reject(reason); 943 | writer._closedPromise_resolve = undefined; 944 | writer._closedPromise_reject = undefined; 945 | writer._closedPromiseState = 'rejected'; 946 | } 947 | 948 | function defaultWriterClosedPromiseResetToRejected(writer, reason) { 949 | assert(writer._closedPromise_resolve === undefined, 'writer._closedPromise_resolve === undefined'); 950 | assert(writer._closedPromise_reject === undefined, 'writer._closedPromise_reject === undefined'); 951 | assert(writer._closedPromiseState !== 'pending', 'writer._closedPromiseState is not pending'); 952 | 953 | writer._closedPromise = Promise.reject(reason); 954 | writer._closedPromiseState = 'rejected'; 955 | } 956 | 957 | function defaultWriterClosedPromiseResolve(writer) { 958 | assert(writer._closedPromise_resolve !== undefined, 'writer._closedPromise_resolve !== undefined'); 959 | assert(writer._closedPromise_reject !== undefined, 'writer._closedPromise_reject !== undefined'); 960 | assert(writer._closedPromiseState === 'pending', 'writer._closedPromiseState is pending'); 961 | 962 | writer._closedPromise_resolve(undefined); 963 | writer._closedPromise_resolve = undefined; 964 | writer._closedPromise_reject = undefined; 965 | writer._closedPromiseState = 'resolved'; 966 | } 967 | 968 | function defaultWriterReadyPromiseInitialize(writer) { 969 | writer._readyPromise = new Promise((resolve, reject) => { 970 | writer._readyPromise_resolve = resolve; 971 | writer._readyPromise_reject = reject; 972 | }); 973 | writer._readyPromiseState = 'pending'; 974 | } 975 | 976 | function defaultWriterReadyPromiseInitializeAsRejected(writer, reason) { 977 | writer._readyPromise = Promise.reject(reason); 978 | writer._readyPromise_resolve = undefined; 979 | writer._readyPromise_reject = undefined; 980 | writer._readyPromiseState = 'rejected'; 981 | } 982 | 983 | function defaultWriterReadyPromiseInitializeAsResolved(writer) { 984 | writer._readyPromise = Promise.resolve(undefined); 985 | writer._readyPromise_resolve = undefined; 986 | writer._readyPromise_reject = undefined; 987 | writer._readyPromiseState = 'fulfilled'; 988 | } 989 | 990 | function defaultWriterReadyPromiseReject(writer, reason) { 991 | assert(writer._readyPromise_resolve !== undefined, 'writer._readyPromise_resolve !== undefined'); 992 | assert(writer._readyPromise_reject !== undefined, 'writer._readyPromise_reject !== undefined'); 993 | 994 | writer._readyPromise_reject(reason); 995 | writer._readyPromise_resolve = undefined; 996 | writer._readyPromise_reject = undefined; 997 | writer._readyPromiseState = 'rejected'; 998 | } 999 | 1000 | function defaultWriterReadyPromiseReset(writer) { 1001 | assert(writer._readyPromise_resolve === undefined, 'writer._readyPromise_resolve === undefined'); 1002 | assert(writer._readyPromise_reject === undefined, 'writer._readyPromise_reject === undefined'); 1003 | 1004 | writer._readyPromise = new Promise((resolve, reject) => { 1005 | writer._readyPromise_resolve = resolve; 1006 | writer._readyPromise_reject = reject; 1007 | }); 1008 | writer._readyPromiseState = 'pending'; 1009 | } 1010 | 1011 | function defaultWriterReadyPromiseResetToRejected(writer, reason) { 1012 | assert(writer._readyPromise_resolve === undefined, 'writer._readyPromise_resolve === undefined'); 1013 | assert(writer._readyPromise_reject === undefined, 'writer._readyPromise_reject === undefined'); 1014 | 1015 | writer._readyPromise = Promise.reject(reason); 1016 | writer._readyPromiseState = 'rejected'; 1017 | } 1018 | 1019 | function defaultWriterReadyPromiseResolve(writer) { 1020 | assert(writer._readyPromise_resolve !== undefined, 'writer._readyPromise_resolve !== undefined'); 1021 | assert(writer._readyPromise_reject !== undefined, 'writer._readyPromise_reject !== undefined'); 1022 | 1023 | writer._readyPromise_resolve(undefined); 1024 | writer._readyPromise_resolve = undefined; 1025 | writer._readyPromise_reject = undefined; 1026 | writer._readyPromiseState = 'fulfilled'; 1027 | } 1028 | -------------------------------------------------------------------------------- /public/assets/templates/body.html: -------------------------------------------------------------------------------- 1 |
2 |