tor-browser

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

cancel.any.js (7840B)


      1 // META: global=window,worker,shadowrealm
      2 // META: script=../resources/test-utils.js
      3 // META: script=../resources/rs-utils.js
      4 'use strict';
      5 
      6 promise_test(t => {
      7 
      8  const randomSource = new RandomPushSource();
      9 
     10  let cancellationFinished = false;
     11  const rs = new ReadableStream({
     12    start(c) {
     13      randomSource.ondata = c.enqueue.bind(c);
     14      randomSource.onend = c.close.bind(c);
     15      randomSource.onerror = c.error.bind(c);
     16    },
     17 
     18    pull() {
     19      randomSource.readStart();
     20    },
     21 
     22    cancel() {
     23      randomSource.readStop();
     24 
     25      return new Promise(resolve => {
     26        t.step_timeout(() => {
     27          cancellationFinished = true;
     28          resolve();
     29        }, 1);
     30      });
     31    }
     32  });
     33 
     34  const reader = rs.getReader();
     35 
     36  // We call delay multiple times to avoid cancelling too early for the
     37  // source to enqueue at least one chunk.
     38  const cancel = delay(5).then(() => delay(5)).then(() => delay(5)).then(() => {
     39    const cancelPromise = reader.cancel();
     40    assert_false(cancellationFinished, 'cancellation in source should happen later');
     41    return cancelPromise;
     42  });
     43 
     44  return readableStreamToArray(rs, reader).then(chunks => {
     45    assert_greater_than(chunks.length, 0, 'at least one chunk should be read');
     46    for (let i = 0; i < chunks.length; i++) {
     47      assert_equals(chunks[i].length, 128, 'chunk ' + i + ' should have 128 bytes');
     48    }
     49    return cancel;
     50  }).then(() => {
     51    assert_true(cancellationFinished, 'it returns a promise that is fulfilled when the cancellation finishes');
     52  });
     53 
     54 }, 'ReadableStream cancellation: integration test on an infinite stream derived from a random push source');
     55 
     56 test(() => {
     57 
     58  let recordedReason;
     59  const rs = new ReadableStream({
     60    cancel(reason) {
     61      recordedReason = reason;
     62    }
     63  });
     64 
     65  const passedReason = new Error('Sorry, it just wasn\'t meant to be.');
     66  rs.cancel(passedReason);
     67 
     68  assert_equals(recordedReason, passedReason,
     69    'the error passed to the underlying source\'s cancel method should equal the one passed to the stream\'s cancel');
     70 
     71 }, 'ReadableStream cancellation: cancel(reason) should pass through the given reason to the underlying source');
     72 
     73 promise_test(() => {
     74 
     75  const rs = new ReadableStream({
     76    start(c) {
     77      c.enqueue('a');
     78      c.close();
     79    },
     80    cancel() {
     81      assert_unreached('underlying source cancel() should not have been called');
     82    }
     83  });
     84 
     85  const reader = rs.getReader();
     86 
     87  return rs.cancel().then(() => {
     88    assert_unreached('cancel() should be rejected');
     89  }, e => {
     90    assert_equals(e.name, 'TypeError', 'cancel() should be rejected with a TypeError');
     91  }).then(() => {
     92    return reader.read();
     93  }).then(result => {
     94    assert_object_equals(result, { value: 'a', done: false }, 'read() should still work after the attempted cancel');
     95    return reader.closed;
     96  });
     97 
     98 }, 'ReadableStream cancellation: cancel() on a locked stream should fail and not call the underlying source cancel');
     99 
    100 promise_test(() => {
    101 
    102  let cancelReceived = false;
    103  const cancelReason = new Error('I am tired of this stream, I prefer to cancel it');
    104  const rs = new ReadableStream({
    105    cancel(reason) {
    106      cancelReceived = true;
    107      assert_equals(reason, cancelReason, 'cancellation reason given to the underlying source should be equal to the one passed');
    108    }
    109  });
    110 
    111  return rs.cancel(cancelReason).then(() => {
    112    assert_true(cancelReceived);
    113  });
    114 
    115 }, 'ReadableStream cancellation: should fulfill promise when cancel callback went fine');
    116 
    117 promise_test(() => {
    118 
    119  const rs = new ReadableStream({
    120    cancel() {
    121      return 'Hello';
    122    }
    123  });
    124 
    125  return rs.cancel().then(v => {
    126    assert_equals(v, undefined, 'cancel() return value should be fulfilled with undefined');
    127  });
    128 
    129 }, 'ReadableStream cancellation: returning a value from the underlying source\'s cancel should not affect the fulfillment value of the promise returned by the stream\'s cancel');
    130 
    131 promise_test(() => {
    132 
    133  const thrownError = new Error('test');
    134  let cancelCalled = false;
    135 
    136  const rs = new ReadableStream({
    137    cancel() {
    138      cancelCalled = true;
    139      throw thrownError;
    140    }
    141  });
    142 
    143  return rs.cancel('test').then(() => {
    144    assert_unreached('cancel should reject');
    145  }, e => {
    146    assert_true(cancelCalled);
    147    assert_equals(e, thrownError);
    148  });
    149 
    150 }, 'ReadableStream cancellation: should reject promise when cancel callback raises an exception');
    151 
    152 promise_test(() => {
    153 
    154  const cancelReason = new Error('test');
    155 
    156  const rs = new ReadableStream({
    157    cancel(error) {
    158      assert_equals(error, cancelReason);
    159      return delay(1);
    160    }
    161  });
    162 
    163  return rs.cancel(cancelReason);
    164 
    165 }, 'ReadableStream cancellation: if the underlying source\'s cancel method returns a promise, the promise returned by the stream\'s cancel should fulfill when that one does (1)');
    166 
    167 promise_test(t => {
    168 
    169  let resolveSourceCancelPromise;
    170  let sourceCancelPromiseHasFulfilled = false;
    171 
    172  const rs = new ReadableStream({
    173    cancel() {
    174      const sourceCancelPromise = new Promise(resolve => resolveSourceCancelPromise = resolve);
    175 
    176      sourceCancelPromise.then(() => {
    177        sourceCancelPromiseHasFulfilled = true;
    178      });
    179 
    180      return sourceCancelPromise;
    181    }
    182  });
    183 
    184  t.step_timeout(() => resolveSourceCancelPromise('Hello'), 1);
    185 
    186  return rs.cancel().then(value => {
    187    assert_true(sourceCancelPromiseHasFulfilled, 'cancel() return value should be fulfilled only after the promise returned by the underlying source\'s cancel');
    188    assert_equals(value, undefined, 'cancel() return value should be fulfilled with undefined');
    189  });
    190 
    191 }, 'ReadableStream cancellation: if the underlying source\'s cancel method returns a promise, the promise returned by the stream\'s cancel should fulfill when that one does (2)');
    192 
    193 promise_test(t => {
    194 
    195  let rejectSourceCancelPromise;
    196  let sourceCancelPromiseHasRejected = false;
    197 
    198  const rs = new ReadableStream({
    199    cancel() {
    200      const sourceCancelPromise = new Promise((resolve, reject) => rejectSourceCancelPromise = reject);
    201 
    202      sourceCancelPromise.catch(() => {
    203        sourceCancelPromiseHasRejected = true;
    204      });
    205 
    206      return sourceCancelPromise;
    207    }
    208  });
    209 
    210  const errorInCancel = new Error('Sorry, it just wasn\'t meant to be.');
    211 
    212  t.step_timeout(() => rejectSourceCancelPromise(errorInCancel), 1);
    213 
    214  return rs.cancel().then(() => {
    215    assert_unreached('cancel() return value should be rejected');
    216  }, r => {
    217    assert_true(sourceCancelPromiseHasRejected, 'cancel() return value should be rejected only after the promise returned by the underlying source\'s cancel');
    218    assert_equals(r, errorInCancel, 'cancel() return value should be rejected with the underlying source\'s rejection reason');
    219  });
    220 
    221 }, 'ReadableStream cancellation: if the underlying source\'s cancel method returns a promise, the promise returned by the stream\'s cancel should reject when that one does');
    222 
    223 promise_test(() => {
    224 
    225  const rs = new ReadableStream({
    226    start() {
    227      return new Promise(() => {});
    228    },
    229    pull() {
    230      assert_unreached('pull should not have been called');
    231    }
    232  });
    233 
    234  return Promise.all([rs.cancel(), rs.getReader().closed]);
    235 
    236 }, 'ReadableStream cancellation: cancelling before start finishes should prevent pull() from being called');
    237 
    238 promise_test(async () => {
    239 
    240  const events = [];
    241 
    242  const pendingPromise = new Promise(() => {});
    243 
    244  const rs = new ReadableStream({
    245    pull() {
    246      events.push('pull');
    247      return pendingPromise;
    248    },
    249    cancel() {
    250      events.push('cancel');
    251    }
    252  });
    253 
    254  const reader = rs.getReader();
    255  reader.read().catch(() => {}); // No await.
    256  await delay(0);
    257  await Promise.all([reader.cancel(), reader.closed]);
    258 
    259  assert_array_equals(events, ['pull', 'cancel'], 'cancel should have been called');
    260 
    261 }, 'ReadableStream cancellation: underlyingSource.cancel() should called, even with pending pull');