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