• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// META: global=window,worker
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
12const error1 = new Error('error1');
13error1.name = 'error1';
14
15promise_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
37promise_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
58promise_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
82promise_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
98promise_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
117promise_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
143promise_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
177promise_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
223promise_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
243promise_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');
265