• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// META: global=window,worker
2// META: script=../resources/test-utils.js
3// META: script=../resources/rs-utils.js
4'use strict';
5
6promise_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
56test(() => {
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
73promise_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
100promise_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
117promise_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
131promise_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
152promise_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
167promise_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
193promise_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
223promise_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