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