11cb0ef41Sopenharmony_ci// META: global=window,worker 21cb0ef41Sopenharmony_ci// META: script=../resources/recording-streams.js 31cb0ef41Sopenharmony_ci// META: script=../resources/rs-utils.js 41cb0ef41Sopenharmony_ci// META: script=../resources/test-utils.js 51cb0ef41Sopenharmony_ci'use strict'; 61cb0ef41Sopenharmony_ci 71cb0ef41Sopenharmony_ci// The size() function of readableStrategy can re-entrantly call back into the TransformStream implementation. This 81cb0ef41Sopenharmony_ci// makes it risky to cache state across the call to ReadableStreamDefaultControllerEnqueue. These tests attempt to catch 91cb0ef41Sopenharmony_ci// such errors. They are separated from the other strategy tests because no real user code should ever do anything like 101cb0ef41Sopenharmony_ci// this. 111cb0ef41Sopenharmony_ci// 121cb0ef41Sopenharmony_ci// There is no such issue with writableStrategy size() because it is never called from within TransformStream 131cb0ef41Sopenharmony_ci// algorithms. 141cb0ef41Sopenharmony_ci 151cb0ef41Sopenharmony_ciconst error1 = new Error('error1'); 161cb0ef41Sopenharmony_cierror1.name = 'error1'; 171cb0ef41Sopenharmony_ci 181cb0ef41Sopenharmony_cipromise_test(() => { 191cb0ef41Sopenharmony_ci let controller; 201cb0ef41Sopenharmony_ci let calls = 0; 211cb0ef41Sopenharmony_ci const ts = new TransformStream({ 221cb0ef41Sopenharmony_ci start(c) { 231cb0ef41Sopenharmony_ci controller = c; 241cb0ef41Sopenharmony_ci } 251cb0ef41Sopenharmony_ci }, undefined, { 261cb0ef41Sopenharmony_ci size() { 271cb0ef41Sopenharmony_ci ++calls; 281cb0ef41Sopenharmony_ci if (calls < 2) { 291cb0ef41Sopenharmony_ci controller.enqueue('b'); 301cb0ef41Sopenharmony_ci } 311cb0ef41Sopenharmony_ci return 1; 321cb0ef41Sopenharmony_ci }, 331cb0ef41Sopenharmony_ci highWaterMark: Infinity 341cb0ef41Sopenharmony_ci }); 351cb0ef41Sopenharmony_ci const writer = ts.writable.getWriter(); 361cb0ef41Sopenharmony_ci return Promise.all([writer.write('a'), writer.close()]) 371cb0ef41Sopenharmony_ci .then(() => readableStreamToArray(ts.readable)) 381cb0ef41Sopenharmony_ci .then(array => assert_array_equals(array, ['b', 'a'], 'array should contain two chunks')); 391cb0ef41Sopenharmony_ci}, 'enqueue() inside size() should work'); 401cb0ef41Sopenharmony_ci 411cb0ef41Sopenharmony_cipromise_test(() => { 421cb0ef41Sopenharmony_ci let controller; 431cb0ef41Sopenharmony_ci const ts = new TransformStream({ 441cb0ef41Sopenharmony_ci start(c) { 451cb0ef41Sopenharmony_ci controller = c; 461cb0ef41Sopenharmony_ci } 471cb0ef41Sopenharmony_ci }, undefined, { 481cb0ef41Sopenharmony_ci size() { 491cb0ef41Sopenharmony_ci // The readable queue is empty. 501cb0ef41Sopenharmony_ci controller.terminate(); 511cb0ef41Sopenharmony_ci // The readable state has gone from "readable" to "closed". 521cb0ef41Sopenharmony_ci return 1; 531cb0ef41Sopenharmony_ci // This chunk will be enqueued, but will be impossible to read because the state is already "closed". 541cb0ef41Sopenharmony_ci }, 551cb0ef41Sopenharmony_ci highWaterMark: Infinity 561cb0ef41Sopenharmony_ci }); 571cb0ef41Sopenharmony_ci const writer = ts.writable.getWriter(); 581cb0ef41Sopenharmony_ci return writer.write('a') 591cb0ef41Sopenharmony_ci .then(() => readableStreamToArray(ts.readable)) 601cb0ef41Sopenharmony_ci .then(array => assert_array_equals(array, [], 'array should contain no chunks')); 611cb0ef41Sopenharmony_ci // The chunk 'a' is still in readable's queue. readable is closed so 'a' cannot be read. writable's queue is empty and 621cb0ef41Sopenharmony_ci // it is still writable. 631cb0ef41Sopenharmony_ci}, 'terminate() inside size() should work'); 641cb0ef41Sopenharmony_ci 651cb0ef41Sopenharmony_cipromise_test(t => { 661cb0ef41Sopenharmony_ci let controller; 671cb0ef41Sopenharmony_ci const ts = new TransformStream({ 681cb0ef41Sopenharmony_ci start(c) { 691cb0ef41Sopenharmony_ci controller = c; 701cb0ef41Sopenharmony_ci } 711cb0ef41Sopenharmony_ci }, undefined, { 721cb0ef41Sopenharmony_ci size() { 731cb0ef41Sopenharmony_ci controller.error(error1); 741cb0ef41Sopenharmony_ci return 1; 751cb0ef41Sopenharmony_ci }, 761cb0ef41Sopenharmony_ci highWaterMark: Infinity 771cb0ef41Sopenharmony_ci }); 781cb0ef41Sopenharmony_ci const writer = ts.writable.getWriter(); 791cb0ef41Sopenharmony_ci return writer.write('a') 801cb0ef41Sopenharmony_ci .then(() => promise_rejects_exactly(t, error1, ts.readable.getReader().read(), 'read() should reject')); 811cb0ef41Sopenharmony_ci}, 'error() inside size() should work'); 821cb0ef41Sopenharmony_ci 831cb0ef41Sopenharmony_cipromise_test(() => { 841cb0ef41Sopenharmony_ci let controller; 851cb0ef41Sopenharmony_ci const ts = new TransformStream({ 861cb0ef41Sopenharmony_ci start(c) { 871cb0ef41Sopenharmony_ci controller = c; 881cb0ef41Sopenharmony_ci } 891cb0ef41Sopenharmony_ci }, undefined, { 901cb0ef41Sopenharmony_ci size() { 911cb0ef41Sopenharmony_ci assert_equals(controller.desiredSize, 1, 'desiredSize should be 1'); 921cb0ef41Sopenharmony_ci return 1; 931cb0ef41Sopenharmony_ci }, 941cb0ef41Sopenharmony_ci highWaterMark: 1 951cb0ef41Sopenharmony_ci }); 961cb0ef41Sopenharmony_ci const writer = ts.writable.getWriter(); 971cb0ef41Sopenharmony_ci return Promise.all([writer.write('a'), writer.close()]) 981cb0ef41Sopenharmony_ci .then(() => readableStreamToArray(ts.readable)) 991cb0ef41Sopenharmony_ci .then(array => assert_array_equals(array, ['a'], 'array should contain one chunk')); 1001cb0ef41Sopenharmony_ci}, 'desiredSize inside size() should work'); 1011cb0ef41Sopenharmony_ci 1021cb0ef41Sopenharmony_cipromise_test(t => { 1031cb0ef41Sopenharmony_ci let cancelPromise; 1041cb0ef41Sopenharmony_ci const ts = new TransformStream({}, undefined, { 1051cb0ef41Sopenharmony_ci size() { 1061cb0ef41Sopenharmony_ci cancelPromise = ts.readable.cancel(error1); 1071cb0ef41Sopenharmony_ci return 1; 1081cb0ef41Sopenharmony_ci }, 1091cb0ef41Sopenharmony_ci highWaterMark: Infinity 1101cb0ef41Sopenharmony_ci }); 1111cb0ef41Sopenharmony_ci const writer = ts.writable.getWriter(); 1121cb0ef41Sopenharmony_ci return writer.write('a') 1131cb0ef41Sopenharmony_ci .then(() => { 1141cb0ef41Sopenharmony_ci promise_rejects_exactly(t, error1, writer.closed, 'writer.closed should reject'); 1151cb0ef41Sopenharmony_ci return cancelPromise; 1161cb0ef41Sopenharmony_ci }); 1171cb0ef41Sopenharmony_ci}, 'readable cancel() inside size() should work'); 1181cb0ef41Sopenharmony_ci 1191cb0ef41Sopenharmony_cipromise_test(() => { 1201cb0ef41Sopenharmony_ci let controller; 1211cb0ef41Sopenharmony_ci let pipeToPromise; 1221cb0ef41Sopenharmony_ci const ws = recordingWritableStream(); 1231cb0ef41Sopenharmony_ci const ts = new TransformStream({ 1241cb0ef41Sopenharmony_ci start(c) { 1251cb0ef41Sopenharmony_ci controller = c; 1261cb0ef41Sopenharmony_ci } 1271cb0ef41Sopenharmony_ci }, undefined, { 1281cb0ef41Sopenharmony_ci size() { 1291cb0ef41Sopenharmony_ci if (!pipeToPromise) { 1301cb0ef41Sopenharmony_ci pipeToPromise = ts.readable.pipeTo(ws); 1311cb0ef41Sopenharmony_ci } 1321cb0ef41Sopenharmony_ci return 1; 1331cb0ef41Sopenharmony_ci }, 1341cb0ef41Sopenharmony_ci highWaterMark: 1 1351cb0ef41Sopenharmony_ci }); 1361cb0ef41Sopenharmony_ci // Allow promise returned by start() to resolve so that enqueue() will happen synchronously. 1371cb0ef41Sopenharmony_ci return delay(0).then(() => { 1381cb0ef41Sopenharmony_ci controller.enqueue('a'); 1391cb0ef41Sopenharmony_ci assert_not_equals(pipeToPromise, undefined); 1401cb0ef41Sopenharmony_ci 1411cb0ef41Sopenharmony_ci // Some pipeTo() implementations need an additional chunk enqueued in order for the first one to be processed. See 1421cb0ef41Sopenharmony_ci // https://github.com/whatwg/streams/issues/794 for background. 1431cb0ef41Sopenharmony_ci controller.enqueue('a'); 1441cb0ef41Sopenharmony_ci 1451cb0ef41Sopenharmony_ci // Give pipeTo() a chance to process the queued chunks. 1461cb0ef41Sopenharmony_ci return delay(0); 1471cb0ef41Sopenharmony_ci }).then(() => { 1481cb0ef41Sopenharmony_ci assert_array_equals(ws.events, ['write', 'a', 'write', 'a'], 'ws should contain two chunks'); 1491cb0ef41Sopenharmony_ci controller.terminate(); 1501cb0ef41Sopenharmony_ci return pipeToPromise; 1511cb0ef41Sopenharmony_ci }).then(() => { 1521cb0ef41Sopenharmony_ci assert_array_equals(ws.events, ['write', 'a', 'write', 'a', 'close'], 'target should have been closed'); 1531cb0ef41Sopenharmony_ci }); 1541cb0ef41Sopenharmony_ci}, 'pipeTo() inside size() should work'); 1551cb0ef41Sopenharmony_ci 1561cb0ef41Sopenharmony_cipromise_test(() => { 1571cb0ef41Sopenharmony_ci let controller; 1581cb0ef41Sopenharmony_ci let readPromise; 1591cb0ef41Sopenharmony_ci let calls = 0; 1601cb0ef41Sopenharmony_ci let reader; 1611cb0ef41Sopenharmony_ci const ts = new TransformStream({ 1621cb0ef41Sopenharmony_ci start(c) { 1631cb0ef41Sopenharmony_ci controller = c; 1641cb0ef41Sopenharmony_ci } 1651cb0ef41Sopenharmony_ci }, undefined, { 1661cb0ef41Sopenharmony_ci size() { 1671cb0ef41Sopenharmony_ci // This is triggered by controller.enqueue(). The queue is empty and there are no pending reads. pull() is called 1681cb0ef41Sopenharmony_ci // synchronously, allowing transform() to proceed asynchronously. This results in a second call to enqueue(), 1691cb0ef41Sopenharmony_ci // which resolves this pending read() without calling size() again. 1701cb0ef41Sopenharmony_ci readPromise = reader.read(); 1711cb0ef41Sopenharmony_ci ++calls; 1721cb0ef41Sopenharmony_ci return 1; 1731cb0ef41Sopenharmony_ci }, 1741cb0ef41Sopenharmony_ci highWaterMark: 0 1751cb0ef41Sopenharmony_ci }); 1761cb0ef41Sopenharmony_ci reader = ts.readable.getReader(); 1771cb0ef41Sopenharmony_ci const writer = ts.writable.getWriter(); 1781cb0ef41Sopenharmony_ci let writeResolved = false; 1791cb0ef41Sopenharmony_ci const writePromise = writer.write('b').then(() => { 1801cb0ef41Sopenharmony_ci writeResolved = true; 1811cb0ef41Sopenharmony_ci }); 1821cb0ef41Sopenharmony_ci return flushAsyncEvents().then(() => { 1831cb0ef41Sopenharmony_ci assert_false(writeResolved); 1841cb0ef41Sopenharmony_ci controller.enqueue('a'); 1851cb0ef41Sopenharmony_ci assert_equals(calls, 1, 'size() should have been called once'); 1861cb0ef41Sopenharmony_ci return delay(0); 1871cb0ef41Sopenharmony_ci }).then(() => { 1881cb0ef41Sopenharmony_ci assert_true(writeResolved); 1891cb0ef41Sopenharmony_ci assert_equals(calls, 1, 'size() should only be called once'); 1901cb0ef41Sopenharmony_ci return readPromise; 1911cb0ef41Sopenharmony_ci }).then(({ value, done }) => { 1921cb0ef41Sopenharmony_ci assert_false(done, 'done should be false'); 1931cb0ef41Sopenharmony_ci // See https://github.com/whatwg/streams/issues/794 for why this chunk is not 'a'. 1941cb0ef41Sopenharmony_ci assert_equals(value, 'b', 'chunk should have been read'); 1951cb0ef41Sopenharmony_ci assert_equals(calls, 1, 'calls should still be 1'); 1961cb0ef41Sopenharmony_ci return writePromise; 1971cb0ef41Sopenharmony_ci }); 1981cb0ef41Sopenharmony_ci}, 'read() inside of size() should work'); 1991cb0ef41Sopenharmony_ci 2001cb0ef41Sopenharmony_cipromise_test(() => { 2011cb0ef41Sopenharmony_ci let writer; 2021cb0ef41Sopenharmony_ci let writePromise1; 2031cb0ef41Sopenharmony_ci let calls = 0; 2041cb0ef41Sopenharmony_ci const ts = new TransformStream({}, undefined, { 2051cb0ef41Sopenharmony_ci size() { 2061cb0ef41Sopenharmony_ci ++calls; 2071cb0ef41Sopenharmony_ci if (calls < 2) { 2081cb0ef41Sopenharmony_ci writePromise1 = writer.write('a'); 2091cb0ef41Sopenharmony_ci } 2101cb0ef41Sopenharmony_ci return 1; 2111cb0ef41Sopenharmony_ci }, 2121cb0ef41Sopenharmony_ci highWaterMark: Infinity 2131cb0ef41Sopenharmony_ci }); 2141cb0ef41Sopenharmony_ci writer = ts.writable.getWriter(); 2151cb0ef41Sopenharmony_ci // Give pull() a chance to be called. 2161cb0ef41Sopenharmony_ci return delay(0).then(() => { 2171cb0ef41Sopenharmony_ci // This write results in a synchronous call to transform(), enqueue(), and size(). 2181cb0ef41Sopenharmony_ci const writePromise2 = writer.write('b'); 2191cb0ef41Sopenharmony_ci assert_equals(calls, 1, 'size() should have been called once'); 2201cb0ef41Sopenharmony_ci return Promise.all([writePromise1, writePromise2, writer.close()]); 2211cb0ef41Sopenharmony_ci }).then(() => { 2221cb0ef41Sopenharmony_ci assert_equals(calls, 2, 'size() should have been called twice'); 2231cb0ef41Sopenharmony_ci return readableStreamToArray(ts.readable); 2241cb0ef41Sopenharmony_ci }).then(array => { 2251cb0ef41Sopenharmony_ci assert_array_equals(array, ['b', 'a'], 'both chunks should have been enqueued'); 2261cb0ef41Sopenharmony_ci assert_equals(calls, 2, 'calls should still be 2'); 2271cb0ef41Sopenharmony_ci }); 2281cb0ef41Sopenharmony_ci}, 'writer.write() inside size() should work'); 2291cb0ef41Sopenharmony_ci 2301cb0ef41Sopenharmony_cipromise_test(() => { 2311cb0ef41Sopenharmony_ci let controller; 2321cb0ef41Sopenharmony_ci let writer; 2331cb0ef41Sopenharmony_ci let writePromise; 2341cb0ef41Sopenharmony_ci let calls = 0; 2351cb0ef41Sopenharmony_ci const ts = new TransformStream({ 2361cb0ef41Sopenharmony_ci start(c) { 2371cb0ef41Sopenharmony_ci controller = c; 2381cb0ef41Sopenharmony_ci } 2391cb0ef41Sopenharmony_ci }, undefined, { 2401cb0ef41Sopenharmony_ci size() { 2411cb0ef41Sopenharmony_ci ++calls; 2421cb0ef41Sopenharmony_ci if (calls < 2) { 2431cb0ef41Sopenharmony_ci writePromise = writer.write('a'); 2441cb0ef41Sopenharmony_ci } 2451cb0ef41Sopenharmony_ci return 1; 2461cb0ef41Sopenharmony_ci }, 2471cb0ef41Sopenharmony_ci highWaterMark: Infinity 2481cb0ef41Sopenharmony_ci }); 2491cb0ef41Sopenharmony_ci writer = ts.writable.getWriter(); 2501cb0ef41Sopenharmony_ci // Give pull() a chance to be called. 2511cb0ef41Sopenharmony_ci return delay(0).then(() => { 2521cb0ef41Sopenharmony_ci // This enqueue results in synchronous calls to size(), write(), transform() and enqueue(). 2531cb0ef41Sopenharmony_ci controller.enqueue('b'); 2541cb0ef41Sopenharmony_ci assert_equals(calls, 2, 'size() should have been called twice'); 2551cb0ef41Sopenharmony_ci return Promise.all([writePromise, writer.close()]); 2561cb0ef41Sopenharmony_ci }).then(() => { 2571cb0ef41Sopenharmony_ci return readableStreamToArray(ts.readable); 2581cb0ef41Sopenharmony_ci }).then(array => { 2591cb0ef41Sopenharmony_ci // Because one call to enqueue() is nested inside the other, they finish in the opposite order that they were 2601cb0ef41Sopenharmony_ci // called, so the chunks end up reverse order. 2611cb0ef41Sopenharmony_ci assert_array_equals(array, ['a', 'b'], 'both chunks should have been enqueued'); 2621cb0ef41Sopenharmony_ci assert_equals(calls, 2, 'calls should still be 2'); 2631cb0ef41Sopenharmony_ci }); 2641cb0ef41Sopenharmony_ci}, 'synchronous writer.write() inside size() should work'); 2651cb0ef41Sopenharmony_ci 2661cb0ef41Sopenharmony_cipromise_test(() => { 2671cb0ef41Sopenharmony_ci let writer; 2681cb0ef41Sopenharmony_ci let closePromise; 2691cb0ef41Sopenharmony_ci let controller; 2701cb0ef41Sopenharmony_ci const ts = new TransformStream({ 2711cb0ef41Sopenharmony_ci start(c) { 2721cb0ef41Sopenharmony_ci controller = c; 2731cb0ef41Sopenharmony_ci } 2741cb0ef41Sopenharmony_ci }, undefined, { 2751cb0ef41Sopenharmony_ci size() { 2761cb0ef41Sopenharmony_ci closePromise = writer.close(); 2771cb0ef41Sopenharmony_ci return 1; 2781cb0ef41Sopenharmony_ci }, 2791cb0ef41Sopenharmony_ci highWaterMark: 1 2801cb0ef41Sopenharmony_ci }); 2811cb0ef41Sopenharmony_ci writer = ts.writable.getWriter(); 2821cb0ef41Sopenharmony_ci const reader = ts.readable.getReader(); 2831cb0ef41Sopenharmony_ci // Wait for the promise returned by start() to be resolved so that the call to close() will result in a synchronous 2841cb0ef41Sopenharmony_ci // call to TransformStreamDefaultSink. 2851cb0ef41Sopenharmony_ci return delay(0).then(() => { 2861cb0ef41Sopenharmony_ci controller.enqueue('a'); 2871cb0ef41Sopenharmony_ci return reader.read(); 2881cb0ef41Sopenharmony_ci }).then(({ value, done }) => { 2891cb0ef41Sopenharmony_ci assert_false(done, 'done should be false'); 2901cb0ef41Sopenharmony_ci assert_equals(value, 'a', 'value should be correct'); 2911cb0ef41Sopenharmony_ci return reader.read(); 2921cb0ef41Sopenharmony_ci }).then(({ done }) => { 2931cb0ef41Sopenharmony_ci assert_true(done, 'done should be true'); 2941cb0ef41Sopenharmony_ci return closePromise; 2951cb0ef41Sopenharmony_ci }); 2961cb0ef41Sopenharmony_ci}, 'writer.close() inside size() should work'); 2971cb0ef41Sopenharmony_ci 2981cb0ef41Sopenharmony_cipromise_test(t => { 2991cb0ef41Sopenharmony_ci let abortPromise; 3001cb0ef41Sopenharmony_ci let controller; 3011cb0ef41Sopenharmony_ci const ts = new TransformStream({ 3021cb0ef41Sopenharmony_ci start(c) { 3031cb0ef41Sopenharmony_ci controller = c; 3041cb0ef41Sopenharmony_ci } 3051cb0ef41Sopenharmony_ci }, undefined, { 3061cb0ef41Sopenharmony_ci size() { 3071cb0ef41Sopenharmony_ci abortPromise = ts.writable.abort(error1); 3081cb0ef41Sopenharmony_ci return 1; 3091cb0ef41Sopenharmony_ci }, 3101cb0ef41Sopenharmony_ci highWaterMark: 1 3111cb0ef41Sopenharmony_ci }); 3121cb0ef41Sopenharmony_ci const reader = ts.readable.getReader(); 3131cb0ef41Sopenharmony_ci // Wait for the promise returned by start() to be resolved so that the call to abort() will result in a synchronous 3141cb0ef41Sopenharmony_ci // call to TransformStreamDefaultSink. 3151cb0ef41Sopenharmony_ci return delay(0).then(() => { 3161cb0ef41Sopenharmony_ci controller.enqueue('a'); 3171cb0ef41Sopenharmony_ci return Promise.all([promise_rejects_exactly(t, error1, reader.read(), 'read() should reject'), abortPromise]); 3181cb0ef41Sopenharmony_ci }); 3191cb0ef41Sopenharmony_ci}, 'writer.abort() inside size() should work'); 320