• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// META: global=window,worker
2// META: script=../resources/rs-utils.js
3// META: script=../resources/test-utils.js
4// META: script=../resources/recording-streams.js
5'use strict';
6
7function duckTypedPassThroughTransform() {
8  let enqueueInReadable;
9  let closeReadable;
10
11  return {
12    writable: new WritableStream({
13      write(chunk) {
14        enqueueInReadable(chunk);
15      },
16
17      close() {
18        closeReadable();
19      }
20    }),
21
22    readable: new ReadableStream({
23      start(c) {
24        enqueueInReadable = c.enqueue.bind(c);
25        closeReadable = c.close.bind(c);
26      }
27    })
28  };
29}
30
31function uninterestingReadableWritablePair() {
32  return { writable: new WritableStream(), readable: new ReadableStream() };
33}
34
35promise_test(() => {
36  const readableEnd = sequentialReadableStream(5).pipeThrough(duckTypedPassThroughTransform());
37
38  return readableStreamToArray(readableEnd).then(chunks =>
39    assert_array_equals(chunks, [1, 2, 3, 4, 5]), 'chunks should match');
40}, 'Piping through a duck-typed pass-through transform stream should work');
41
42promise_test(() => {
43  const transform = {
44    writable: new WritableStream({
45      start(c) {
46        c.error(new Error('this rejection should not be reported as unhandled'));
47      }
48    }),
49    readable: new ReadableStream()
50  };
51
52  sequentialReadableStream(5).pipeThrough(transform);
53
54  // The test harness should complain about unhandled rejections by then.
55  return flushAsyncEvents();
56
57}, 'Piping through a transform errored on the writable end does not cause an unhandled promise rejection');
58
59test(() => {
60  let calledPipeTo = false;
61  class BadReadableStream extends ReadableStream {
62    pipeTo() {
63      calledPipeTo = true;
64    }
65  }
66
67  const brs = new BadReadableStream({
68    start(controller) {
69      controller.close();
70    }
71  });
72  const readable = new ReadableStream();
73  const writable = new WritableStream();
74  const result = brs.pipeThrough({ readable, writable });
75
76  assert_false(calledPipeTo, 'the overridden pipeTo should not have been called');
77  assert_equals(result, readable, 'return value should be the passed readable property');
78}, 'pipeThrough should not call pipeTo on this');
79
80test(t => {
81  let calledFakePipeTo = false;
82  const realPipeTo = ReadableStream.prototype.pipeTo;
83  t.add_cleanup(() => {
84    ReadableStream.prototype.pipeTo = realPipeTo;
85  });
86  ReadableStream.prototype.pipeTo = () => {
87    calledFakePipeTo = true;
88  };
89  const rs = new ReadableStream();
90  const readable = new ReadableStream();
91  const writable = new WritableStream();
92  const result = rs.pipeThrough({ readable, writable });
93
94  assert_false(calledFakePipeTo, 'the monkey-patched pipeTo should not have been called');
95  assert_equals(result, readable, 'return value should be the passed readable property');
96
97}, 'pipeThrough should not call pipeTo on the ReadableStream prototype');
98
99const badReadables = [null, undefined, 0, NaN, true, 'ReadableStream', Object.create(ReadableStream.prototype)];
100for (const readable of badReadables) {
101  test(() => {
102    assert_throws_js(TypeError,
103                     ReadableStream.prototype.pipeThrough.bind(readable, uninterestingReadableWritablePair()),
104                     'pipeThrough should throw');
105  }, `pipeThrough should brand-check this and not allow '${readable}'`);
106
107  test(() => {
108    const rs = new ReadableStream();
109    let writableGetterCalled = false;
110    assert_throws_js(
111      TypeError,
112      () => rs.pipeThrough({
113        get writable() {
114          writableGetterCalled = true;
115          return new WritableStream();
116        },
117        readable
118      }),
119      'pipeThrough should brand-check readable'
120    );
121    assert_false(writableGetterCalled, 'writable should not have been accessed');
122  }, `pipeThrough should brand-check readable and not allow '${readable}'`);
123}
124
125const badWritables = [null, undefined, 0, NaN, true, 'WritableStream', Object.create(WritableStream.prototype)];
126for (const writable of badWritables) {
127  test(() => {
128    const rs = new ReadableStream({
129      start(c) {
130        c.close();
131      }
132    });
133    let readableGetterCalled = false;
134    assert_throws_js(TypeError, () => rs.pipeThrough({
135      get readable() {
136        readableGetterCalled = true;
137        return new ReadableStream();
138      },
139      writable
140    }),
141                  'pipeThrough should brand-check writable');
142    assert_true(readableGetterCalled, 'readable should have been accessed');
143  }, `pipeThrough should brand-check writable and not allow '${writable}'`);
144}
145
146test(t => {
147  const error = new Error();
148  error.name = 'custom';
149
150  const rs = new ReadableStream({
151    pull: t.unreached_func('pull should not be called')
152  }, { highWaterMark: 0 });
153
154  const throwingWritable = {
155    readable: rs,
156    get writable() {
157      throw error;
158    }
159  };
160  assert_throws_exactly(error,
161                        () => ReadableStream.prototype.pipeThrough.call(rs, throwingWritable, {}),
162                        'pipeThrough should rethrow the error thrown by the writable getter');
163
164  const throwingReadable = {
165    get readable() {
166      throw error;
167    },
168    writable: {}
169  };
170  assert_throws_exactly(error,
171                        () => ReadableStream.prototype.pipeThrough.call(rs, throwingReadable, {}),
172                        'pipeThrough should rethrow the error thrown by the readable getter');
173
174}, 'pipeThrough should rethrow errors from accessing readable or writable');
175
176const badSignals = [null, 0, NaN, true, 'AbortSignal', Object.create(AbortSignal.prototype)];
177for (const signal of badSignals) {
178  test(() => {
179    const rs = new ReadableStream();
180    assert_throws_js(TypeError, () => rs.pipeThrough(uninterestingReadableWritablePair(), { signal }),
181                     'pipeThrough should throw');
182  }, `invalid values of signal should throw; specifically '${signal}'`);
183}
184
185test(() => {
186  const rs = new ReadableStream();
187  const controller = new AbortController();
188  const signal = controller.signal;
189  rs.pipeThrough(uninterestingReadableWritablePair(), { signal });
190}, 'pipeThrough should accept a real AbortSignal');
191
192test(() => {
193  const rs = new ReadableStream();
194  rs.getReader();
195  assert_throws_js(TypeError, () => rs.pipeThrough(uninterestingReadableWritablePair()),
196                   'pipeThrough should throw');
197}, 'pipeThrough should throw if this is locked');
198
199test(() => {
200  const rs = new ReadableStream();
201  const writable = new WritableStream();
202  const readable = new ReadableStream();
203  writable.getWriter();
204  assert_throws_js(TypeError, () => rs.pipeThrough({writable, readable}),
205                   'pipeThrough should throw');
206}, 'pipeThrough should throw if writable is locked');
207
208test(() => {
209  const rs = new ReadableStream();
210  const writable = new WritableStream();
211  const readable = new ReadableStream();
212  readable.getReader();
213  assert_equals(rs.pipeThrough({ writable, readable }), readable,
214                'pipeThrough should not throw');
215}, 'pipeThrough should not care if readable is locked');
216
217promise_test(() => {
218  const rs = recordingReadableStream();
219  const writable = new WritableStream({
220    start(controller) {
221      controller.error();
222    }
223  });
224  const readable = new ReadableStream();
225  rs.pipeThrough({ writable, readable }, { preventCancel: true });
226  return flushAsyncEvents(0).then(() => {
227    assert_array_equals(rs.events, ['pull'], 'cancel should not have been called');
228  });
229}, 'preventCancel should work');
230
231promise_test(() => {
232  const rs = new ReadableStream({
233    start(controller) {
234      controller.close();
235    }
236  });
237  const writable = recordingWritableStream();
238  const readable = new ReadableStream();
239  rs.pipeThrough({ writable, readable }, { preventClose: true });
240  return flushAsyncEvents(0).then(() => {
241    assert_array_equals(writable.events, [], 'writable should not be closed');
242  });
243}, 'preventClose should work');
244
245promise_test(() => {
246  const rs = new ReadableStream({
247    start(controller) {
248      controller.error();
249    }
250  });
251  const writable = recordingWritableStream();
252  const readable = new ReadableStream();
253  rs.pipeThrough({ writable, readable }, { preventAbort: true });
254  return flushAsyncEvents(0).then(() => {
255    assert_array_equals(writable.events, [], 'writable should not be aborted');
256  });
257}, 'preventAbort should work');
258
259test(() => {
260  const rs = new ReadableStream();
261  const readable = new ReadableStream();
262  const writable = new WritableStream();
263  assert_throws_js(TypeError, () => rs.pipeThrough({readable, writable}, {
264    get preventAbort() {
265      writable.getWriter();
266    }
267  }), 'pipeThrough should throw');
268}, 'pipeThrough() should throw if an option getter grabs a writer');
269
270test(() => {
271  const rs = new ReadableStream();
272  const readable = new ReadableStream();
273  const writable = new WritableStream();
274  rs.pipeThrough({readable, writable}, null);
275}, 'pipeThrough() should not throw if option is null');
276
277test(() => {
278  const rs = new ReadableStream();
279  const readable = new ReadableStream();
280  const writable = new WritableStream();
281  rs.pipeThrough({readable, writable}, {signal:undefined});
282}, 'pipeThrough() should not throw if signal is undefined');
283
284function tryPipeThrough(pair, options)
285{
286  const rs = new ReadableStream();
287  if (!pair)
288    pair = {readable:new ReadableStream(), writable:new WritableStream()};
289  try {
290    rs.pipeThrough(pair, options)
291  } catch (e) {
292    return e;
293  }
294}
295
296test(() => {
297  let result = tryPipeThrough({
298    get readable() {
299      return new ReadableStream();
300    },
301    get writable() {
302      throw "writable threw";
303    }
304  }, { });
305  assert_equals(result, "writable threw");
306
307  result = tryPipeThrough({
308    get readable() {
309      throw "readable threw";
310    },
311    get writable() {
312      throw "writable threw";
313    }
314  }, { });
315  assert_equals(result, "readable threw");
316
317  result = tryPipeThrough({
318    get readable() {
319      throw "readable threw";
320    },
321    get writable() {
322      throw "writable threw";
323    }
324  }, {
325    get preventAbort() {
326      throw "preventAbort threw";
327    }
328  });
329  assert_equals(result, "readable threw");
330
331}, 'pipeThrough() should throw if readable/writable getters throw');
332