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