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 12function writeArrayToStream(array, writableStreamWriter) { 13 array.forEach(chunk => writableStreamWriter.write(chunk)); 14 return writableStreamWriter.close(); 15} 16 17promise_test(() => { 18 let storage; 19 const ws = new WritableStream({ 20 start() { 21 storage = []; 22 }, 23 24 write(chunk) { 25 return delay(0).then(() => storage.push(chunk)); 26 }, 27 28 close() { 29 return delay(0); 30 } 31 }); 32 33 const writer = ws.getWriter(); 34 35 const input = [1, 2, 3, 4, 5]; 36 return writeArrayToStream(input, writer) 37 .then(() => assert_array_equals(storage, input, 'correct data should be relayed to underlying sink')); 38}, 'WritableStream should complete asynchronous writes before close resolves'); 39 40promise_test(() => { 41 const ws = recordingWritableStream(); 42 43 const writer = ws.getWriter(); 44 45 const input = [1, 2, 3, 4, 5]; 46 return writeArrayToStream(input, writer) 47 .then(() => assert_array_equals(ws.events, ['write', 1, 'write', 2, 'write', 3, 'write', 4, 'write', 5, 'close'], 48 'correct data should be relayed to underlying sink')); 49}, 'WritableStream should complete synchronous writes before close resolves'); 50 51promise_test(() => { 52 const ws = new WritableStream({ 53 write() { 54 return 'Hello'; 55 } 56 }); 57 58 const writer = ws.getWriter(); 59 60 const writePromise = writer.write('a'); 61 return writePromise 62 .then(value => assert_equals(value, undefined, 'fulfillment value must be undefined')); 63}, 'fulfillment value of ws.write() call should be undefined even if the underlying sink returns a non-undefined ' + 64 'value'); 65 66promise_test(() => { 67 let resolveSinkWritePromise; 68 const ws = new WritableStream({ 69 write() { 70 return new Promise(resolve => { 71 resolveSinkWritePromise = resolve; 72 }); 73 } 74 }); 75 76 const writer = ws.getWriter(); 77 78 assert_equals(writer.desiredSize, 1, 'desiredSize should be 1'); 79 80 return writer.ready.then(() => { 81 const writePromise = writer.write('a'); 82 let writePromiseResolved = false; 83 assert_not_equals(resolveSinkWritePromise, undefined, 'resolveSinkWritePromise should not be undefined'); 84 85 assert_equals(writer.desiredSize, 0, 'desiredSize should be 0 after writer.write()'); 86 87 return Promise.all([ 88 writePromise.then(value => { 89 writePromiseResolved = true; 90 assert_equals(resolveSinkWritePromise, undefined, 'sinkWritePromise should be fulfilled before writePromise'); 91 92 assert_equals(value, undefined, 'writePromise should be fulfilled with undefined'); 93 }), 94 writer.ready.then(value => { 95 assert_equals(resolveSinkWritePromise, undefined, 'sinkWritePromise should be fulfilled before writer.ready'); 96 assert_true(writePromiseResolved, 'writePromise should be fulfilled before writer.ready'); 97 98 assert_equals(writer.desiredSize, 1, 'desiredSize should be 1 again'); 99 100 assert_equals(value, undefined, 'writePromise should be fulfilled with undefined'); 101 }), 102 flushAsyncEvents().then(() => { 103 resolveSinkWritePromise(); 104 resolveSinkWritePromise = undefined; 105 }) 106 ]); 107 }); 108}, 'WritableStream should transition to waiting until write is acknowledged'); 109 110promise_test(t => { 111 let sinkWritePromiseRejectors = []; 112 const ws = new WritableStream({ 113 write() { 114 const sinkWritePromise = new Promise((r, reject) => sinkWritePromiseRejectors.push(reject)); 115 return sinkWritePromise; 116 } 117 }); 118 119 const writer = ws.getWriter(); 120 121 assert_equals(writer.desiredSize, 1, 'desiredSize should be 1'); 122 123 return writer.ready.then(() => { 124 const writePromise = writer.write('a'); 125 assert_equals(sinkWritePromiseRejectors.length, 1, 'there should be 1 rejector'); 126 assert_equals(writer.desiredSize, 0, 'desiredSize should be 0'); 127 128 const writePromise2 = writer.write('b'); 129 assert_equals(sinkWritePromiseRejectors.length, 1, 'there should be still 1 rejector'); 130 assert_equals(writer.desiredSize, -1, 'desiredSize should be -1'); 131 132 const closedPromise = writer.close(); 133 134 assert_equals(writer.desiredSize, -1, 'desiredSize should still be -1'); 135 136 return Promise.all([ 137 promise_rejects_exactly(t, error1, closedPromise, 138 'closedPromise should reject with the error returned from the sink\'s write method') 139 .then(() => assert_equals(sinkWritePromiseRejectors.length, 0, 140 'sinkWritePromise should reject before closedPromise')), 141 promise_rejects_exactly(t, error1, writePromise, 142 'writePromise should reject with the error returned from the sink\'s write method') 143 .then(() => assert_equals(sinkWritePromiseRejectors.length, 0, 144 'sinkWritePromise should reject before writePromise')), 145 promise_rejects_exactly(t, error1, writePromise2, 146 'writePromise2 should reject with the error returned from the sink\'s write method') 147 .then(() => assert_equals(sinkWritePromiseRejectors.length, 0, 148 'sinkWritePromise should reject before writePromise2')), 149 flushAsyncEvents().then(() => { 150 sinkWritePromiseRejectors[0](error1); 151 sinkWritePromiseRejectors = []; 152 }) 153 ]); 154 }); 155}, 'when write returns a rejected promise, queued writes and close should be cleared'); 156 157promise_test(t => { 158 const ws = new WritableStream({ 159 write() { 160 throw error1; 161 } 162 }); 163 164 const writer = ws.getWriter(); 165 166 return promise_rejects_exactly(t, error1, writer.write('a'), 167 'write() should reject with the error returned from the sink\'s write method') 168 .then(() => promise_rejects_js(t, TypeError, writer.close(), 'close() should be rejected')); 169}, 'when sink\'s write throws an error, the stream should become errored and the promise should reject'); 170 171promise_test(t => { 172 const ws = new WritableStream({ 173 write(chunk, controller) { 174 controller.error(error1); 175 throw error2; 176 } 177 }); 178 179 const writer = ws.getWriter(); 180 181 return promise_rejects_exactly(t, error2, writer.write('a'), 182 'write() should reject with the error returned from the sink\'s write method ') 183 .then(() => { 184 return Promise.all([ 185 promise_rejects_exactly(t, error1, writer.ready, 186 'writer.ready must reject with the error passed to the controller'), 187 promise_rejects_exactly(t, error1, writer.closed, 188 'writer.closed must reject with the error passed to the controller') 189 ]); 190 }); 191}, 'writer.write(), ready and closed reject with the error passed to controller.error() made before sink.write' + 192 ' rejection'); 193 194promise_test(() => { 195 const numberOfWrites = 1000; 196 197 let resolveFirstWritePromise; 198 let writeCount = 0; 199 const ws = new WritableStream({ 200 write() { 201 ++writeCount; 202 if (!resolveFirstWritePromise) { 203 return new Promise(resolve => { 204 resolveFirstWritePromise = resolve; 205 }); 206 } 207 return Promise.resolve(); 208 } 209 }); 210 211 const writer = ws.getWriter(); 212 return writer.ready.then(() => { 213 for (let i = 1; i < numberOfWrites; ++i) { 214 writer.write('a'); 215 } 216 const writePromise = writer.write('a'); 217 218 assert_equals(writeCount, 1, 'should have called sink\'s write once'); 219 220 resolveFirstWritePromise(); 221 222 return writePromise 223 .then(() => 224 assert_equals(writeCount, numberOfWrites, `should have called sink's write ${numberOfWrites} times`)); 225 }); 226}, 'a large queue of writes should be processed completely'); 227 228promise_test(() => { 229 const stream = recordingWritableStream(); 230 const w = stream.getWriter(); 231 const WritableStreamDefaultWriter = w.constructor; 232 w.releaseLock(); 233 const writer = new WritableStreamDefaultWriter(stream); 234 return writer.ready.then(() => { 235 writer.write('a'); 236 assert_array_equals(stream.events, ['write', 'a'], 'write() should be passed to sink'); 237 }); 238}, 'WritableStreamDefaultWriter should work when manually constructed'); 239 240promise_test(() => { 241 let thenCalled = false; 242 const ws = new WritableStream({ 243 write() { 244 return { 245 then(onFulfilled) { 246 thenCalled = true; 247 onFulfilled(); 248 } 249 }; 250 } 251 }); 252 return ws.getWriter().write('a').then(() => assert_true(thenCalled, 'thenCalled should be true')); 253}, 'returning a thenable from write() should work'); 254 255promise_test(() => { 256 const stream = new WritableStream(); 257 const writer = stream.getWriter(); 258 const WritableStreamDefaultWriter = writer.constructor; 259 assert_throws_js(TypeError, () => new WritableStreamDefaultWriter(stream), 260 'should not be able to construct on locked stream'); 261 // If stream.[[writer]] no longer points to |writer| then the closed Promise 262 // won't work properly. 263 return Promise.all([writer.close(), writer.closed]); 264}, 'failing DefaultWriter constructor should not release an existing writer'); 265 266promise_test(t => { 267 const ws = new WritableStream({ 268 start() { 269 return Promise.reject(error1); 270 } 271 }, { highWaterMark: 0 }); 272 const writer = ws.getWriter(); 273 return Promise.all([ 274 promise_rejects_exactly(t, error1, writer.ready, 'ready should be rejected'), 275 promise_rejects_exactly(t, error1, writer.write(), 'write() should be rejected') 276 ]); 277}, 'write() on a stream with HWM 0 should not cause the ready Promise to resolve'); 278 279promise_test(t => { 280 const ws = new WritableStream(); 281 const writer = ws.getWriter(); 282 writer.releaseLock(); 283 return promise_rejects_js(t, TypeError, writer.write(), 'write should reject'); 284}, 'writing to a released writer should reject the returned promise'); 285