tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

reentrant-strategies.any.js (7378B)


      1 // META: global=window,worker,shadowrealm
      2 // META: script=../resources/recording-streams.js
      3 // META: script=../resources/rs-utils.js
      4 // META: script=../resources/test-utils.js
      5 'use strict';
      6 
      7 // The size() function of the readable strategy can re-entrantly call back into the ReadableStream implementation. This
      8 // makes it risky to cache state across the call to ReadableStreamDefaultControllerEnqueue. These tests attempt to catch
      9 // such errors. They are separated from the other strategy tests because no real user code should ever do anything like
     10 // this.
     11 
     12 const error1 = new Error('error1');
     13 error1.name = 'error1';
     14 
     15 promise_test(() => {
     16  let controller;
     17  let calls = 0;
     18  const rs = new ReadableStream({
     19    start(c) {
     20      controller = c;
     21    }
     22  }, {
     23    size() {
     24      ++calls;
     25      if (calls < 2) {
     26        controller.enqueue('b');
     27      }
     28      return 1;
     29    }
     30  });
     31  controller.enqueue('a');
     32  controller.close();
     33  return readableStreamToArray(rs)
     34      .then(array => assert_array_equals(array, ['b', 'a'], 'array should contain two chunks'));
     35 }, 'enqueue() inside size() should work');
     36 
     37 promise_test(() => {
     38  let controller;
     39  const rs = new ReadableStream({
     40    start(c) {
     41      controller = c;
     42    }
     43  }, {
     44    size() {
     45      // The queue is empty.
     46      controller.close();
     47      // The state has gone from "readable" to "closed".
     48      return 1;
     49      // This chunk will be enqueued, but will be impossible to read because the state is already "closed".
     50    }
     51  });
     52  controller.enqueue('a');
     53  return readableStreamToArray(rs)
     54      .then(array => assert_array_equals(array, [], 'array should contain no chunks'));
     55  // The chunk 'a' is still in rs's queue. It is closed so 'a' cannot be read.
     56 }, 'close() inside size() should not crash');
     57 
     58 promise_test(() => {
     59  let controller;
     60  let calls = 0;
     61  const rs = new ReadableStream({
     62    start(c) {
     63      controller = c;
     64    }
     65  }, {
     66    size() {
     67      ++calls;
     68      if (calls === 2) {
     69        // The queue contains one chunk.
     70        controller.close();
     71        // The state is still "readable", but closeRequest is now true.
     72      }
     73      return 1;
     74    }
     75  });
     76  controller.enqueue('a');
     77  controller.enqueue('b');
     78  return readableStreamToArray(rs)
     79      .then(array => assert_array_equals(array, ['a', 'b'], 'array should contain two chunks'));
     80 }, 'close request inside size() should work');
     81 
     82 promise_test(t => {
     83  let controller;
     84  const rs = new ReadableStream({
     85    start(c) {
     86      controller = c;
     87    }
     88  }, {
     89    size() {
     90      controller.error(error1);
     91      return 1;
     92    }
     93  });
     94  controller.enqueue('a');
     95  return promise_rejects_exactly(t, error1, rs.getReader().read(), 'read() should reject');
     96 }, 'error() inside size() should work');
     97 
     98 promise_test(() => {
     99  let controller;
    100  const rs = new ReadableStream({
    101    start(c) {
    102      controller = c;
    103    }
    104  }, {
    105    size() {
    106      assert_equals(controller.desiredSize, 1, 'desiredSize should be 1');
    107      return 1;
    108    },
    109    highWaterMark: 1
    110  });
    111  controller.enqueue('a');
    112  controller.close();
    113  return readableStreamToArray(rs)
    114      .then(array => assert_array_equals(array, ['a'], 'array should contain one chunk'));
    115 }, 'desiredSize inside size() should work');
    116 
    117 promise_test(t => {
    118  let cancelPromise;
    119  let controller;
    120  const rs = new ReadableStream({
    121    start(c) {
    122      controller = c;
    123    },
    124    cancel: t.step_func(reason => {
    125      assert_equals(reason, error1, 'reason should be error1');
    126      assert_throws_js(TypeError, () => controller.enqueue(), 'enqueue() should throw');
    127    })
    128  }, {
    129    size() {
    130      cancelPromise = rs.cancel(error1);
    131      return 1;
    132    },
    133    highWaterMark: Infinity
    134  });
    135  controller.enqueue('a');
    136  const reader = rs.getReader();
    137  return Promise.all([
    138    reader.closed,
    139    cancelPromise
    140  ]);
    141 }, 'cancel() inside size() should work');
    142 
    143 promise_test(() => {
    144  let controller;
    145  let pipeToPromise;
    146  const ws = recordingWritableStream();
    147  const rs = new ReadableStream({
    148    start(c) {
    149      controller = c;
    150    }
    151  }, {
    152    size() {
    153      if (!pipeToPromise) {
    154        pipeToPromise = rs.pipeTo(ws);
    155      }
    156      return 1;
    157    },
    158    highWaterMark: 1
    159  });
    160  controller.enqueue('a');
    161  assert_not_equals(pipeToPromise, undefined);
    162 
    163  // Some pipeTo() implementations need an additional chunk enqueued in order for the first one to be processed. See
    164  // https://github.com/whatwg/streams/issues/794 for background.
    165  controller.enqueue('a');
    166 
    167  // Give pipeTo() a chance to process the queued chunks.
    168  return delay(0).then(() => {
    169    assert_array_equals(ws.events, ['write', 'a', 'write', 'a'], 'ws should contain two chunks');
    170    controller.close();
    171    return pipeToPromise;
    172  }).then(() => {
    173    assert_array_equals(ws.events, ['write', 'a', 'write', 'a', 'close'], 'target should have been closed');
    174  });
    175 }, 'pipeTo() inside size() should behave as expected');
    176 
    177 promise_test(() => {
    178  let controller;
    179  let readPromise;
    180  let calls = 0;
    181  let readResolved = false;
    182  let reader;
    183  const rs = new ReadableStream({
    184    start(c) {
    185      controller = c;
    186    }
    187  }, {
    188    size() {
    189      // This is triggered by controller.enqueue(). The queue is empty and there are no pending reads. This read is
    190      // added to the list of pending reads.
    191      readPromise = reader.read();
    192      ++calls;
    193      return 1;
    194    },
    195    highWaterMark: 0
    196  });
    197  reader = rs.getReader();
    198  controller.enqueue('a');
    199  readPromise.then(() => {
    200    readResolved = true;
    201  });
    202  return flushAsyncEvents().then(() => {
    203    assert_false(readResolved);
    204    controller.enqueue('b');
    205    assert_equals(calls, 1, 'size() should have been called once');
    206    return delay(0);
    207  }).then(() => {
    208    assert_true(readResolved);
    209    assert_equals(calls, 1, 'size() should only be called once');
    210    return readPromise;
    211  }).then(({ value, done }) => {
    212    assert_false(done, 'done should be false');
    213    // See https://github.com/whatwg/streams/issues/794 for why this chunk is not 'a'.
    214    assert_equals(value, 'b', 'chunk should have been read');
    215    assert_equals(calls, 1, 'calls should still be 1');
    216    return reader.read();
    217  }).then(({ value, done }) => {
    218    assert_false(done, 'done should be false again');
    219    assert_equals(value, 'a', 'chunk a should come after b');
    220  });
    221 }, 'read() inside of size() should behave as expected');
    222 
    223 promise_test(() => {
    224  let controller;
    225  let reader;
    226  const rs = new ReadableStream({
    227    start(c) {
    228      controller = c;
    229    }
    230  }, {
    231    size() {
    232      reader = rs.getReader();
    233      return 1;
    234    }
    235  });
    236  controller.enqueue('a');
    237  return reader.read().then(({ value, done }) => {
    238    assert_false(done, 'done should be false');
    239    assert_equals(value, 'a', 'value should be a');
    240  });
    241 }, 'getReader() inside size() should work');
    242 
    243 promise_test(() => {
    244  let controller;
    245  let branch1;
    246  let branch2;
    247  const rs = new ReadableStream({
    248    start(c) {
    249      controller = c;
    250    }
    251  }, {
    252    size() {
    253      [branch1, branch2] = rs.tee();
    254      return 1;
    255    }
    256  });
    257  controller.enqueue('a');
    258  assert_true(rs.locked, 'rs should be locked');
    259  controller.close();
    260  return Promise.all([
    261    readableStreamToArray(branch1).then(array => assert_array_equals(array, ['a'], 'branch1 should have one chunk')),
    262    readableStreamToArray(branch2).then(array => assert_array_equals(array, ['a'], 'branch2 should have one chunk'))
    263  ]);
    264 }, 'tee() inside size() should work');