• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// META: global=window,worker
2// META: script=../resources/rs-utils.js
3'use strict';
4
5test(() => {
6
7  assert_throws_js(TypeError, () => new ReadableStreamDefaultReader('potato'));
8  assert_throws_js(TypeError, () => new ReadableStreamDefaultReader({}));
9  assert_throws_js(TypeError, () => new ReadableStreamDefaultReader());
10
11}, 'ReadableStreamDefaultReader constructor should get a ReadableStream object as argument');
12
13test(() => {
14
15  const rsReader = new ReadableStreamDefaultReader(new ReadableStream());
16  assert_equals(rsReader.closed, rsReader.closed, 'closed should return the same promise');
17
18}, 'ReadableStreamDefaultReader closed should always return the same promise object');
19
20test(() => {
21
22  const rs = new ReadableStream();
23  new ReadableStreamDefaultReader(rs); // Constructing directly the first time should be fine.
24  assert_throws_js(TypeError, () => new ReadableStreamDefaultReader(rs),
25                   'constructing directly the second time should fail');
26
27}, 'Constructing a ReadableStreamDefaultReader directly should fail if the stream is already locked (via direct ' +
28   'construction)');
29
30test(() => {
31
32  const rs = new ReadableStream();
33  new ReadableStreamDefaultReader(rs); // Constructing directly should be fine.
34  assert_throws_js(TypeError, () => rs.getReader(), 'getReader() should fail');
35
36}, 'Getting a ReadableStreamDefaultReader via getReader should fail if the stream is already locked (via direct ' +
37   'construction)');
38
39test(() => {
40
41  const rs = new ReadableStream();
42  rs.getReader(); // getReader() should be fine.
43  assert_throws_js(TypeError, () => new ReadableStreamDefaultReader(rs), 'constructing directly should fail');
44
45}, 'Constructing a ReadableStreamDefaultReader directly should fail if the stream is already locked (via getReader)');
46
47test(() => {
48
49  const rs = new ReadableStream();
50  rs.getReader(); // getReader() should be fine.
51  assert_throws_js(TypeError, () => rs.getReader(), 'getReader() should fail');
52
53}, 'Getting a ReadableStreamDefaultReader via getReader should fail if the stream is already locked (via getReader)');
54
55test(() => {
56
57  const rs = new ReadableStream({
58    start(c) {
59      c.close();
60    }
61  });
62
63  new ReadableStreamDefaultReader(rs); // Constructing directly should not throw.
64
65}, 'Constructing a ReadableStreamDefaultReader directly should be OK if the stream is closed');
66
67test(() => {
68
69  const theError = new Error('don\'t say i didn\'t warn ya');
70  const rs = new ReadableStream({
71    start(c) {
72      c.error(theError);
73    }
74  });
75
76  new ReadableStreamDefaultReader(rs); // Constructing directly should not throw.
77
78}, 'Constructing a ReadableStreamDefaultReader directly should be OK if the stream is errored');
79
80promise_test(() => {
81
82  let controller;
83  const rs = new ReadableStream({
84    start(c) {
85      controller = c;
86    }
87  });
88  const reader = rs.getReader();
89
90  const promise = reader.read().then(result => {
91    assert_object_equals(result, { value: 'a', done: false }, 'read() should fulfill with the enqueued chunk');
92  });
93
94  controller.enqueue('a');
95  return promise;
96
97}, 'Reading from a reader for an empty stream will wait until a chunk is available');
98
99promise_test(() => {
100
101  let cancelCalled = false;
102  const passedReason = new Error('it wasn\'t the right time, sorry');
103  const rs = new ReadableStream({
104    cancel(reason) {
105      assert_true(rs.locked, 'the stream should still be locked');
106      assert_throws_js(TypeError, () => rs.getReader(), 'should not be able to get another reader');
107      assert_equals(reason, passedReason, 'the cancellation reason is passed through to the underlying source');
108      cancelCalled = true;
109    }
110  });
111
112  const reader = rs.getReader();
113  return reader.cancel(passedReason).then(() => assert_true(cancelCalled));
114
115}, 'cancel() on a reader does not release the reader');
116
117promise_test(() => {
118
119  let controller;
120  const rs = new ReadableStream({
121    start(c) {
122      controller = c;
123    }
124  });
125
126  const reader = rs.getReader();
127  const promise = reader.closed;
128
129  controller.close();
130  return promise;
131
132}, 'closed should be fulfilled after stream is closed (.closed access before acquiring)');
133
134promise_test(t => {
135
136  let controller;
137  const rs = new ReadableStream({
138    start(c) {
139      controller = c;
140    }
141  });
142
143  const reader1 = rs.getReader();
144
145  reader1.releaseLock();
146
147  const reader2 = rs.getReader();
148  controller.close();
149
150  return Promise.all([
151    promise_rejects_js(t, TypeError, reader1.closed),
152    reader2.closed
153  ]);
154
155}, 'closed should be rejected after reader releases its lock (multiple stream locks)');
156
157promise_test(t => {
158
159  let controller;
160  const rs = new ReadableStream({
161    start(c) {
162      controller = c;
163    }
164  });
165
166  const reader = rs.getReader();
167  const promise1 = reader.closed;
168
169  controller.close();
170
171  reader.releaseLock();
172  const promise2 = reader.closed;
173
174  assert_not_equals(promise1, promise2, '.closed should be replaced');
175  return Promise.all([
176    promise1,
177    promise_rejects_js(t, TypeError, promise2, '.closed after releasing lock'),
178  ]);
179
180}, 'closed is replaced when stream closes and reader releases its lock');
181
182promise_test(t => {
183
184  const theError = { name: 'unique error' };
185  let controller;
186  const rs = new ReadableStream({
187    start(c) {
188      controller = c;
189    }
190  });
191
192  const reader = rs.getReader();
193  const promise1 = reader.closed;
194
195  controller.error(theError);
196
197  reader.releaseLock();
198  const promise2 = reader.closed;
199
200  assert_not_equals(promise1, promise2, '.closed should be replaced');
201  return Promise.all([
202    promise_rejects_exactly(t, theError, promise1, '.closed before releasing lock'),
203    promise_rejects_js(t, TypeError, promise2, '.closed after releasing lock')
204  ]);
205
206}, 'closed is replaced when stream errors and reader releases its lock');
207
208promise_test(() => {
209
210  const rs = new ReadableStream({
211    start(c) {
212      c.enqueue('a');
213      c.enqueue('b');
214      c.close();
215    }
216  });
217
218  const reader1 = rs.getReader();
219  const promise1 = reader1.read().then(r => {
220    assert_object_equals(r, { value: 'a', done: false }, 'reading the first chunk from reader1 works');
221  });
222  reader1.releaseLock();
223
224  const reader2 = rs.getReader();
225  const promise2 = reader2.read().then(r => {
226    assert_object_equals(r, { value: 'b', done: false }, 'reading the second chunk from reader2 works');
227  });
228  reader2.releaseLock();
229
230  return Promise.all([promise1, promise2]);
231
232}, 'Multiple readers can access the stream in sequence');
233
234promise_test(() => {
235  const rs = new ReadableStream({
236    start(c) {
237      c.enqueue('a');
238    }
239  });
240
241  const reader1 = rs.getReader();
242  reader1.releaseLock();
243
244  const reader2 = rs.getReader();
245
246  // Should be a no-op
247  reader1.releaseLock();
248
249  return reader2.read().then(result => {
250    assert_object_equals(result, { value: 'a', done: false },
251                         'read() should still work on reader2 even after reader1 is released');
252  });
253
254}, 'Cannot use an already-released reader to unlock a stream again');
255
256promise_test(t => {
257
258  const rs = new ReadableStream({
259    start(c) {
260      c.enqueue('a');
261    },
262    cancel() {
263      assert_unreached('underlying source cancel should not be called');
264    }
265  });
266
267  const reader = rs.getReader();
268  reader.releaseLock();
269  const cancelPromise = reader.cancel();
270
271  const reader2 = rs.getReader();
272  const readPromise = reader2.read().then(r => {
273    assert_object_equals(r, { value: 'a', done: false }, 'a new reader should be able to read a chunk');
274  });
275
276  return Promise.all([
277    promise_rejects_js(t, TypeError, cancelPromise),
278    readPromise
279  ]);
280
281}, 'cancel() on a released reader is a no-op and does not pass through');
282
283promise_test(t => {
284
285  const promiseAsserts = [];
286
287  let controller;
288  const theError = { name: 'unique error' };
289  const rs = new ReadableStream({
290    start(c) {
291      controller = c;
292    }
293  });
294
295  const reader1 = rs.getReader();
296
297  promiseAsserts.push(
298    promise_rejects_exactly(t, theError, reader1.closed),
299    promise_rejects_exactly(t, theError, reader1.read())
300  );
301
302  assert_throws_js(TypeError, () => rs.getReader(), 'trying to get another reader before erroring should throw');
303
304  controller.error(theError);
305
306  reader1.releaseLock();
307
308  const reader2 = rs.getReader();
309
310  promiseAsserts.push(
311    promise_rejects_exactly(t, theError, reader2.closed),
312    promise_rejects_exactly(t, theError, reader2.read())
313  );
314
315  return Promise.all(promiseAsserts);
316
317}, 'Getting a second reader after erroring the stream and releasing the reader should succeed');
318
319promise_test(t => {
320
321  let controller;
322  const rs = new ReadableStream({
323    start(c) {
324      controller = c;
325    }
326  });
327
328  const promise = rs.getReader().closed.then(
329    t.unreached_func('closed promise should not be fulfilled when stream is errored'),
330    err => {
331      assert_equals(err, undefined, 'passed error should be undefined as it was');
332    }
333  );
334
335  controller.error();
336  return promise;
337
338}, 'ReadableStreamDefaultReader closed promise should be rejected with undefined if that is the error');
339
340
341promise_test(t => {
342
343  const rs = new ReadableStream({
344    start() {
345      return Promise.reject();
346    }
347  });
348
349  return rs.getReader().read().then(
350    t.unreached_func('read promise should not be fulfilled when stream is errored'),
351    err => {
352      assert_equals(err, undefined, 'passed error should be undefined as it was');
353    }
354  );
355
356}, 'ReadableStreamDefaultReader: if start rejects with no parameter, it should error the stream with an undefined ' +
357    'error');
358
359promise_test(t => {
360
361  const theError = { name: 'unique string' };
362  let controller;
363  const rs = new ReadableStream({
364    start(c) {
365      controller = c;
366    }
367  });
368
369  const promise = promise_rejects_exactly(t, theError, rs.getReader().closed);
370
371  controller.error(theError);
372  return promise;
373
374}, 'Erroring a ReadableStream after checking closed should reject ReadableStreamDefaultReader closed promise');
375
376promise_test(t => {
377
378  const theError = { name: 'unique string' };
379  let controller;
380  const rs = new ReadableStream({
381    start(c) {
382      controller = c;
383    }
384  });
385
386  controller.error(theError);
387
388  // Let's call getReader twice for extra test coverage of this code path.
389  rs.getReader().releaseLock();
390
391  return promise_rejects_exactly(t, theError, rs.getReader().closed);
392
393}, 'Erroring a ReadableStream before checking closed should reject ReadableStreamDefaultReader closed promise');
394
395promise_test(() => {
396
397  let controller;
398  const rs = new ReadableStream({
399    start(c) {
400      controller = c;
401    }
402  });
403  const reader = rs.getReader();
404
405  const promise = Promise.all([
406    reader.read().then(result => {
407      assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (1)');
408    }),
409    reader.read().then(result => {
410      assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (2)');
411    }),
412    reader.closed
413  ]);
414
415  controller.close();
416  return promise;
417
418}, 'Reading twice on a stream that gets closed');
419
420promise_test(() => {
421
422  let controller;
423  const rs = new ReadableStream({
424    start(c) {
425      controller = c;
426    }
427  });
428
429  controller.close();
430  const reader = rs.getReader();
431
432  return Promise.all([
433    reader.read().then(result => {
434      assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (1)');
435    }),
436    reader.read().then(result => {
437      assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (2)');
438    }),
439    reader.closed
440  ]);
441
442}, 'Reading twice on a closed stream');
443
444promise_test(t => {
445
446  let controller;
447  const rs = new ReadableStream({
448    start(c) {
449      controller = c;
450    }
451  });
452
453  const myError = { name: 'mashed potatoes' };
454  controller.error(myError);
455
456  const reader = rs.getReader();
457
458  return Promise.all([
459    promise_rejects_exactly(t, myError, reader.read()),
460    promise_rejects_exactly(t, myError, reader.read()),
461    promise_rejects_exactly(t, myError, reader.closed)
462  ]);
463
464}, 'Reading twice on an errored stream');
465
466promise_test(t => {
467
468  let controller;
469  const rs = new ReadableStream({
470    start(c) {
471      controller = c;
472    }
473  });
474
475  const myError = { name: 'mashed potatoes' };
476  const reader = rs.getReader();
477
478  const promise = Promise.all([
479    promise_rejects_exactly(t, myError, reader.read()),
480    promise_rejects_exactly(t, myError, reader.read()),
481    promise_rejects_exactly(t, myError, reader.closed)
482  ]);
483
484  controller.error(myError);
485  return promise;
486
487}, 'Reading twice on a stream that gets errored');
488
489test(() => {
490  const rs = new ReadableStream();
491  let toStringCalled = false;
492  const mode = {
493    toString() {
494      toStringCalled = true;
495      return '';
496    }
497  };
498  assert_throws_js(TypeError, () => rs.getReader({ mode }), 'getReader() should throw');
499  assert_true(toStringCalled, 'toString() should be called');
500}, 'getReader() should call ToString() on mode');
501
502promise_test(() => {
503  const rs = new ReadableStream({
504    pull(controller) {
505      controller.close();
506    }
507  });
508
509  const reader = rs.getReader();
510  return reader.read().then(() => {
511    // The test passes if releaseLock() does not throw.
512    reader.releaseLock();
513  });
514}, 'controller.close() should clear the list of pending read requests');
515
516promise_test(t => {
517
518  let controller;
519  const rs = new ReadableStream({
520    start(c) {
521      controller = c;
522    }
523  });
524
525  const reader1 = rs.getReader();
526  const promise1 = promise_rejects_js(t, TypeError, reader1.read(), 'read() from reader1 should reject when reader1 is released');
527  reader1.releaseLock();
528
529  controller.enqueue('a');
530
531  const reader2 = rs.getReader();
532  const promise2 = reader2.read().then(r => {
533    assert_object_equals(r, { value: 'a', done: false }, 'read() from reader2 should resolve with enqueued chunk');
534  })
535  reader2.releaseLock();
536
537  return Promise.all([promise1, promise2]);
538
539}, 'Second reader can read chunks after first reader was released with pending read requests');
540