1<!DOCTYPE html>
2<meta charset="utf-8">
3<script src="/resources/testharness.js"></script>
4<script src="/resources/testharnessreport.js"></script>
5<script src="resources/helpers.js"></script>
6<script src="../resources/recording-streams.js"></script>
7<script src="../resources/test-utils.js"></script>
8<script>
9'use strict';
10
11promise_test(async () => {
12  const rs = await createTransferredReadableStream({
13    start(controller) {
14      controller.enqueue('a');
15      controller.close();
16    }
17  });
18  const reader = rs.getReader();
19  {
20    const {value, done} = await reader.read();
21    assert_false(done, 'should not be done yet');
22    assert_equals(value, 'a', 'first chunk should be a');
23  }
24  {
25    const {done} = await reader.read();
26    assert_true(done, 'should be done now');
27  }
28}, 'sending one chunk through a transferred stream should work');
29
30promise_test(async () => {
31  let controller;
32  const rs = await createTransferredReadableStream({
33    start(c) {
34      controller = c;
35    }
36  });
37  for (let i = 0; i < 10; ++i) {
38    controller.enqueue(i);
39  }
40  controller.close();
41  const reader = rs.getReader();
42  for (let i = 0; i < 10; ++i) {
43    const {value, done} = await reader.read();
44    assert_false(done, 'should not be done yet');
45    assert_equals(value, i, 'chunk content should match index');
46  }
47  const {done} = await reader.read();
48  assert_true(done, 'should be done now');
49}, 'sending ten chunks through a transferred stream should work');
50
51promise_test(async () => {
52  let controller;
53  const rs = await createTransferredReadableStream({
54    start(c) {
55      controller = c;
56    }
57  });
58  const reader = rs.getReader();
59  for (let i = 0; i < 10; ++i) {
60    controller.enqueue(i);
61    const {value, done} = await reader.read();
62    assert_false(done, 'should not be done yet');
63    assert_equals(value, i, 'chunk content should match index');
64  }
65  controller.close();
66  const {done} = await reader.read();
67  assert_true(done, 'should be done now');
68}, 'sending ten chunks one at a time should work');
69
70promise_test(async () => {
71  let controller;
72  const rs = await createTransferredReadableStream({
73    start() {
74      this.counter = 0;
75    },
76    pull(controller) {
77      controller.enqueue(this.counter);
78      ++this.counter;
79      if (this.counter === 10)
80        controller.close();
81    }
82  });
83  const reader = rs.getReader();
84  for (let i = 0; i < 10; ++i) {
85    const {value, done} = await reader.read();
86    assert_false(done, 'should not be done yet');
87    assert_equals(value, i, 'chunk content should match index');
88  }
89  const {done} = await reader.read();
90  assert_true(done, 'should be done now');
91}, 'sending ten chunks on demand should work');
92
93promise_test(async () => {
94  const rs = recordingReadableStream({}, { highWaterMark: 0 });
95  await delay(0);
96  assert_array_equals(rs.events, [], 'pull() should not have been called');
97  // Eat the message so it can't interfere with other tests.
98  addEventListener('message', () => {}, {once: true});
99  // The transfer is done manually to verify that it is posting the stream that
100  // relieves backpressure, not receiving it.
101  postMessage(rs, '*', [rs]);
102  await delay(0);
103  assert_array_equals(rs.events, ['pull'], 'pull() should have been called');
104}, 'transferring a stream should relieve backpressure');
105
106promise_test(async () => {
107  const rs = await recordingTransferredReadableStream({
108    pull(controller) {
109      controller.enqueue('a');
110    }
111  }, { highWaterMark: 2 });
112  await delay(0);
113  assert_array_equals(rs.events, ['pull', 'pull', 'pull'],
114                      'pull() should have been called three times');
115}, 'transferring a stream should add one chunk to the queue size');
116
117promise_test(async () => {
118  const rs = await recordingTransferredReadableStream({
119    start(controller) {
120      controller.enqueue(new Uint8Array(1024));
121      controller.enqueue(new Uint8Array(1024));
122    }
123  }, new ByteLengthQueuingStrategy({highWaterMark: 512}));
124  await delay(0);
125  // At this point the queue contains 1024/512 bytes and 1/1 chunk, so it's full
126  // and pull() is not called.
127  assert_array_equals(rs.events, [], 'pull() should not have been called');
128  const reader = rs.getReader();
129  const {value, done} = await reader.read();
130  assert_false(done, 'we should not be done');
131  assert_equals(value.byteLength, 1024, 'expected chunk should be returned');
132  // Now the queue contains 0/512 bytes and 1/1 chunk, so pull() is called. If
133  // the implementation erroneously counted the extra queue space in bytes, then
134  // the queue would contain 1024/513 bytes and pull() wouldn't be called.
135  assert_array_equals(rs.events, ['pull'], 'pull() should have been called');
136}, 'the extra queue from transferring is counted in chunks');
137
138async function transferredReadableStreamWithCancelPromise() {
139  let resolveCancelCalled;
140  const cancelCalled = new Promise(resolve => {
141    resolveCancelCalled = resolve;
142  });
143  const rs = await recordingTransferredReadableStream({
144    cancel() {
145      resolveCancelCalled();
146    }
147  });
148  return { rs, cancelCalled };
149}
150
151promise_test(async () => {
152  const { rs, cancelCalled } = await transferredReadableStreamWithCancelPromise();
153  rs.cancel('message');
154  await cancelCalled;
155  assert_array_equals(rs.events, ['pull', 'cancel', 'message'],
156                      'cancel() should have been called');
157  const reader = rs.getReader();
158  // Check the stream really got closed.
159  await reader.closed;
160}, 'cancel should be propagated to the original');
161
162promise_test(async () => {
163  const { rs, cancelCalled } = await transferredReadableStreamWithCancelPromise();
164  const reader = rs.getReader();
165  const readPromise = reader.read();
166  reader.cancel('done');
167  const { done } = await readPromise;
168  assert_true(done, 'should be done');
169  await cancelCalled;
170  assert_array_equals(rs.events, ['pull', 'cancel', 'done'],
171                      'events should match');
172}, 'cancel should abort a pending read()');
173
174promise_test(async () => {
175  let cancelComplete = false;
176  const rs = await createTransferredReadableStream({
177    async cancel() {
178      await flushAsyncEvents();
179      cancelComplete = true;
180    }
181  });
182  await rs.cancel();
183  assert_false(cancelComplete,
184               'cancel() on the underlying sink should not have completed');
185}, 'stream cancel should not wait for underlying source cancel');
186
187promise_test(async t => {
188  const rs = await recordingTransferredReadableStream();
189  const reader = rs.getReader();
190  let serializationHappened = false;
191  rs.controller.enqueue({
192    get getter() {
193      serializationHappened = true;
194      return 'a';
195    }
196  });
197  await flushAsyncEvents();
198  assert_false(serializationHappened,
199               'serialization should not have happened yet');
200  const {value, done} = await reader.read();
201  assert_false(done, 'should not be done');
202  assert_equals(value.getter, 'a', 'getter should be a');
203  assert_true(serializationHappened,
204              'serialization should have happened');
205}, 'serialization should not happen until the value is read');
206
207promise_test(async t => {
208  const rs = await recordingTransferredReadableStream();
209  const reader = rs.getReader();
210  rs.controller.enqueue(new ReadableStream());
211  await promise_rejects_dom(t, 'DataCloneError', reader.read(),
212                            'closed promise should reject');
213  assert_throws_js(TypeError, () => rs.controller.enqueue(),
214                   'original stream should be errored');
215}, 'transferring a non-serializable chunk should error both sides');
216
217promise_test(async t => {
218  const rs = await createTransferredReadableStream({
219    start(controller) {
220      controller.error('foo');
221    }
222  });
223  const reader = rs.getReader();
224  return promise_rejects_exactly(t, 'foo', reader.read(),
225                                 'error should be passed through');
226}, 'errors should be passed through');
227
228promise_test(async () => {
229  const rs = await recordingTransferredReadableStream();
230  await delay(0);
231  const reader = rs.getReader();
232  reader.cancel();
233  rs.controller.error();
234  const {done} = await reader.read();
235  assert_true(done, 'should be done');
236  assert_throws_js(TypeError, () => rs.controller.enqueue(),
237                   'enqueue should throw');
238}, 'race between cancel() and error() should leave sides in different states');
239
240promise_test(async () => {
241  const rs = await recordingTransferredReadableStream();
242  await delay(0);
243  const reader = rs.getReader();
244  reader.cancel();
245  rs.controller.close();
246  const {done} = await reader.read();
247  assert_true(done, 'should be done');
248}, 'race between cancel() and close() should be benign');
249
250promise_test(async () => {
251  const rs = await recordingTransferredReadableStream();
252  await delay(0);
253  const reader = rs.getReader();
254  reader.cancel();
255  rs.controller.enqueue('a');
256  const {done} = await reader.read();
257  assert_true(done, 'should be done');
258}, 'race between cancel() and enqueue() should be benign');
259
260</script>
261