1// META: global=window,worker 2// META: script=../resources/test-utils.js 3// META: script=../resources/recording-streams.js 4'use strict'; 5 6const error1 = new Error('error1'); 7error1.name = 'error1'; 8 9const error2 = new Error('error2'); 10error2.name = 'error2'; 11 12promise_test(() => { 13 const ws = new WritableStream({ 14 close() { 15 return 'Hello'; 16 } 17 }); 18 19 const writer = ws.getWriter(); 20 21 const closePromise = writer.close(); 22 return closePromise.then(value => assert_equals(value, undefined, 'fulfillment value must be undefined')); 23}, 'fulfillment value of writer.close() call must be undefined even if the underlying sink returns a non-undefined ' + 24 'value'); 25 26promise_test(() => { 27 let controller; 28 let resolveClose; 29 const ws = new WritableStream({ 30 start(c) { 31 controller = c; 32 }, 33 close() { 34 return new Promise(resolve => { 35 resolveClose = resolve; 36 }); 37 } 38 }); 39 40 const writer = ws.getWriter(); 41 42 const closePromise = writer.close(); 43 return flushAsyncEvents().then(() => { 44 controller.error(error1); 45 return flushAsyncEvents(); 46 }).then(() => { 47 resolveClose(); 48 return Promise.all([ 49 closePromise, 50 writer.closed, 51 flushAsyncEvents().then(() => writer.closed)]); 52 }); 53}, 'when sink calls error asynchronously while sink close is in-flight, the stream should not become errored'); 54 55promise_test(() => { 56 let controller; 57 const passedError = new Error('error me'); 58 const ws = new WritableStream({ 59 start(c) { 60 controller = c; 61 }, 62 close() { 63 controller.error(passedError); 64 } 65 }); 66 67 const writer = ws.getWriter(); 68 69 return writer.close().then(() => writer.closed); 70}, 'when sink calls error synchronously while closing, the stream should not become errored'); 71 72promise_test(t => { 73 const ws = new WritableStream({ 74 close() { 75 throw error1; 76 } 77 }); 78 79 const writer = ws.getWriter(); 80 81 return Promise.all([ 82 writer.write('y'), 83 promise_rejects_exactly(t, error1, writer.close(), 'close() must reject with the error'), 84 promise_rejects_exactly(t, error1, writer.closed, 'closed must reject with the error') 85 ]); 86}, 'when the sink throws during close, and the close is requested while a write is still in-flight, the stream should ' + 87 'become errored during the close'); 88 89promise_test(() => { 90 const ws = new WritableStream({ 91 write(chunk, controller) { 92 controller.error(error1); 93 return new Promise(() => {}); 94 } 95 }); 96 97 const writer = ws.getWriter(); 98 writer.write('a'); 99 100 return delay(0).then(() => { 101 writer.releaseLock(); 102 }); 103}, 'releaseLock on a stream with a pending write in which the stream has been errored'); 104 105promise_test(() => { 106 let controller; 107 const ws = new WritableStream({ 108 start(c) { 109 controller = c; 110 }, 111 close() { 112 controller.error(error1); 113 return new Promise(() => {}); 114 } 115 }); 116 117 const writer = ws.getWriter(); 118 writer.close(); 119 120 return delay(0).then(() => { 121 writer.releaseLock(); 122 }); 123}, 'releaseLock on a stream with a pending close in which controller.error() was called'); 124 125promise_test(() => { 126 const ws = recordingWritableStream(); 127 128 const writer = ws.getWriter(); 129 130 return writer.ready.then(() => { 131 assert_equals(writer.desiredSize, 1, 'desiredSize should be 1'); 132 133 writer.close(); 134 assert_equals(writer.desiredSize, 1, 'desiredSize should be still 1'); 135 136 return writer.ready.then(v => { 137 assert_equals(v, undefined, 'ready promise should be fulfilled with undefined'); 138 assert_array_equals(ws.events, ['close'], 'write and abort should not be called'); 139 }); 140 }); 141}, 'when close is called on a WritableStream in writable state, ready should return a fulfilled promise'); 142 143promise_test(() => { 144 const ws = recordingWritableStream({ 145 write() { 146 return new Promise(() => {}); 147 } 148 }); 149 150 const writer = ws.getWriter(); 151 152 return writer.ready.then(() => { 153 writer.write('a'); 154 155 assert_equals(writer.desiredSize, 0, 'desiredSize should be 0'); 156 157 let calledClose = false; 158 return Promise.all([ 159 writer.ready.then(v => { 160 assert_equals(v, undefined, 'ready promise should be fulfilled with undefined'); 161 assert_true(calledClose, 'ready should not be fulfilled before writer.close() is called'); 162 assert_array_equals(ws.events, ['write', 'a'], 'sink abort() should not be called'); 163 }), 164 flushAsyncEvents().then(() => { 165 writer.close(); 166 calledClose = true; 167 }) 168 ]); 169 }); 170}, 'when close is called on a WritableStream in waiting state, ready promise should be fulfilled'); 171 172promise_test(() => { 173 let asyncCloseFinished = false; 174 const ws = recordingWritableStream({ 175 close() { 176 return flushAsyncEvents().then(() => { 177 asyncCloseFinished = true; 178 }); 179 } 180 }); 181 182 const writer = ws.getWriter(); 183 return writer.ready.then(() => { 184 writer.write('a'); 185 186 writer.close(); 187 188 return writer.ready.then(v => { 189 assert_false(asyncCloseFinished, 'ready promise should be fulfilled before async close completes'); 190 assert_equals(v, undefined, 'ready promise should be fulfilled with undefined'); 191 assert_array_equals(ws.events, ['write', 'a', 'close'], 'sink abort() should not be called'); 192 }); 193 }); 194}, 'when close is called on a WritableStream in waiting state, ready should be fulfilled immediately even if close ' + 195 'takes a long time'); 196 197promise_test(t => { 198 const rejection = { name: 'letter' }; 199 const ws = new WritableStream({ 200 close() { 201 return { 202 then(onFulfilled, onRejected) { onRejected(rejection); } 203 }; 204 } 205 }); 206 return promise_rejects_exactly(t, rejection, ws.getWriter().close(), 'close() should return a rejection'); 207}, 'returning a thenable from close() should work'); 208 209promise_test(t => { 210 const ws = new WritableStream(); 211 const writer = ws.getWriter(); 212 return writer.ready.then(() => { 213 const closePromise = writer.close(); 214 const closedPromise = writer.closed; 215 writer.releaseLock(); 216 return Promise.all([ 217 closePromise, 218 promise_rejects_js(t, TypeError, closedPromise, '.closed promise should be rejected') 219 ]); 220 }); 221}, 'releaseLock() should not change the result of sync close()'); 222 223promise_test(t => { 224 const ws = new WritableStream({ 225 close() { 226 return flushAsyncEvents(); 227 } 228 }); 229 const writer = ws.getWriter(); 230 return writer.ready.then(() => { 231 const closePromise = writer.close(); 232 const closedPromise = writer.closed; 233 writer.releaseLock(); 234 return Promise.all([ 235 closePromise, 236 promise_rejects_js(t, TypeError, closedPromise, '.closed promise should be rejected') 237 ]); 238 }); 239}, 'releaseLock() should not change the result of async close()'); 240 241promise_test(() => { 242 let resolveClose; 243 const ws = new WritableStream({ 244 close() { 245 const promise = new Promise(resolve => { 246 resolveClose = resolve; 247 }); 248 return promise; 249 } 250 }); 251 const writer = ws.getWriter(); 252 const closePromise = writer.close(); 253 writer.releaseLock(); 254 return delay(0).then(() => { 255 resolveClose(); 256 return closePromise.then(() => { 257 assert_equals(ws.getWriter().desiredSize, 0, 'desiredSize should be 0'); 258 }); 259 }); 260}, 'close() should set state to CLOSED even if writer has detached'); 261 262promise_test(() => { 263 let resolveClose; 264 const ws = new WritableStream({ 265 close() { 266 const promise = new Promise(resolve => { 267 resolveClose = resolve; 268 }); 269 return promise; 270 } 271 }); 272 const writer = ws.getWriter(); 273 writer.close(); 274 writer.releaseLock(); 275 return delay(0).then(() => { 276 const abortingWriter = ws.getWriter(); 277 const abortPromise = abortingWriter.abort(); 278 abortingWriter.releaseLock(); 279 resolveClose(); 280 return abortPromise; 281 }); 282}, 'the promise returned by async abort during close should resolve'); 283 284// Though the order in which the promises are fulfilled or rejected is arbitrary, we're checking it for 285// interoperability. We can change the order as long as we file bugs on all implementers to update to the latest tests 286// to keep them interoperable. 287 288promise_test(() => { 289 const ws = new WritableStream({}); 290 291 const writer = ws.getWriter(); 292 293 const closePromise = writer.close(); 294 295 const events = []; 296 return Promise.all([ 297 closePromise.then(() => { 298 events.push('closePromise'); 299 }), 300 writer.closed.then(() => { 301 events.push('closed'); 302 }) 303 ]).then(() => { 304 assert_array_equals(events, ['closePromise', 'closed'], 305 'promises must fulfill/reject in the expected order'); 306 }); 307}, 'promises must fulfill/reject in the expected order on closure'); 308 309promise_test(() => { 310 const ws = new WritableStream({}); 311 312 // Wait until the WritableStream starts so that the close() call gets processed. Otherwise, abort() will be 313 // processed without waiting for completion of the close(). 314 return delay(0).then(() => { 315 const writer = ws.getWriter(); 316 317 const closePromise = writer.close(); 318 const abortPromise = writer.abort(error1); 319 320 const events = []; 321 return Promise.all([ 322 closePromise.then(() => { 323 events.push('closePromise'); 324 }), 325 abortPromise.then(() => { 326 events.push('abortPromise'); 327 }), 328 writer.closed.then(() => { 329 events.push('closed'); 330 }) 331 ]).then(() => { 332 assert_array_equals(events, ['closePromise', 'abortPromise', 'closed'], 333 'promises must fulfill/reject in the expected order'); 334 }); 335 }); 336}, 'promises must fulfill/reject in the expected order on aborted closure'); 337 338promise_test(t => { 339 const ws = new WritableStream({ 340 close() { 341 return Promise.reject(error1); 342 } 343 }); 344 345 // Wait until the WritableStream starts so that the close() call gets processed. 346 return delay(0).then(() => { 347 const writer = ws.getWriter(); 348 349 const closePromise = writer.close(); 350 const abortPromise = writer.abort(error2); 351 352 const events = []; 353 closePromise.catch(() => events.push('closePromise')); 354 abortPromise.catch(() => events.push('abortPromise')); 355 writer.closed.catch(() => events.push('closed')); 356 return Promise.all([ 357 promise_rejects_exactly(t, error1, closePromise, 358 'closePromise must reject with the error returned from the sink\'s close method'), 359 promise_rejects_exactly(t, error1, abortPromise, 360 'abortPromise must reject with the error returned from the sink\'s close method'), 361 promise_rejects_exactly(t, error2, writer.closed, 362 'writer.closed must reject with error2') 363 ]).then(() => { 364 assert_array_equals(events, ['closePromise', 'abortPromise', 'closed'], 365 'promises must fulfill/reject in the expected order'); 366 }); 367 }); 368}, 'promises must fulfill/reject in the expected order on aborted and errored closure'); 369 370promise_test(t => { 371 let resolveWrite; 372 let controller; 373 const ws = new WritableStream({ 374 write(chunk, c) { 375 controller = c; 376 return new Promise(resolve => { 377 resolveWrite = resolve; 378 }); 379 } 380 }); 381 const writer = ws.getWriter(); 382 return writer.ready.then(() => { 383 const writePromise = writer.write('c'); 384 controller.error(error1); 385 const closePromise = writer.close(); 386 let closeRejected = false; 387 closePromise.catch(() => { 388 closeRejected = true; 389 }); 390 return flushAsyncEvents().then(() => { 391 assert_false(closeRejected); 392 resolveWrite(); 393 return Promise.all([ 394 writePromise, 395 promise_rejects_exactly(t, error1, closePromise, 'close() should reject') 396 ]).then(() => { 397 assert_true(closeRejected); 398 }); 399 }); 400 }); 401}, 'close() should not reject until no sink methods are in flight'); 402 403promise_test(() => { 404 const ws = new WritableStream(); 405 const writer1 = ws.getWriter(); 406 return writer1.close().then(() => { 407 writer1.releaseLock(); 408 const writer2 = ws.getWriter(); 409 const ready = writer2.ready; 410 assert_equals(ready.constructor, Promise); 411 return ready; 412 }); 413}, 'ready promise should be initialised as fulfilled for a writer on a closed stream'); 414 415promise_test(() => { 416 const ws = new WritableStream(); 417 ws.close(); 418 const writer = ws.getWriter(); 419 return writer.closed; 420}, 'close() on a writable stream should work'); 421 422promise_test(t => { 423 const ws = new WritableStream(); 424 ws.getWriter(); 425 return promise_rejects_js(t, TypeError, ws.close(), 'close should reject'); 426}, 'close() on a locked stream should reject'); 427 428promise_test(t => { 429 const ws = new WritableStream({ 430 start(controller) { 431 controller.error(error1); 432 } 433 }); 434 return promise_rejects_exactly(t, error1, ws.close(), 'close should reject with error1'); 435}, 'close() on an erroring stream should reject'); 436 437promise_test(t => { 438 const ws = new WritableStream({ 439 start(controller) { 440 controller.error(error1); 441 } 442 }); 443 const writer = ws.getWriter(); 444 return promise_rejects_exactly(t, error1, writer.closed, 'closed should reject with the error').then(() => { 445 writer.releaseLock(); 446 return promise_rejects_js(t, TypeError, ws.close(), 'close should reject'); 447 }); 448}, 'close() on an errored stream should reject'); 449 450promise_test(t => { 451 const ws = new WritableStream(); 452 const writer = ws.getWriter(); 453 return writer.close().then(() => { 454 return promise_rejects_js(t, TypeError, ws.close(), 'close should reject'); 455 }); 456}, 'close() on an closed stream should reject'); 457 458promise_test(t => { 459 const ws = new WritableStream({ 460 close() { 461 return new Promise(() => {}); 462 } 463 }); 464 465 const writer = ws.getWriter(); 466 writer.close(); 467 writer.releaseLock(); 468 469 return promise_rejects_js(t, TypeError, ws.close(), 'close should reject'); 470}, 'close() on a stream with a pending close should reject'); 471