1// META: global=window,worker 2// META: script=../resources/recording-streams.js 3// META: script=../resources/rs-utils.js 4// META: script=../resources/test-utils.js 5'use strict'; 6 7// The size() function of the readable strategy can re-entrantly call back into the ReadableStream implementation. This 8// makes it risky to cache state across the call to ReadableStreamDefaultControllerEnqueue. These tests attempt to catch 9// such errors. They are separated from the other strategy tests because no real user code should ever do anything like 10// this. 11 12const error1 = new Error('error1'); 13error1.name = 'error1'; 14 15promise_test(() => { 16 let controller; 17 let calls = 0; 18 const rs = new ReadableStream({ 19 start(c) { 20 controller = c; 21 } 22 }, { 23 size() { 24 ++calls; 25 if (calls < 2) { 26 controller.enqueue('b'); 27 } 28 return 1; 29 } 30 }); 31 controller.enqueue('a'); 32 controller.close(); 33 return readableStreamToArray(rs) 34 .then(array => assert_array_equals(array, ['b', 'a'], 'array should contain two chunks')); 35}, 'enqueue() inside size() should work'); 36 37promise_test(() => { 38 let controller; 39 const rs = new ReadableStream({ 40 start(c) { 41 controller = c; 42 } 43 }, { 44 size() { 45 // The queue is empty. 46 controller.close(); 47 // The state has gone from "readable" to "closed". 48 return 1; 49 // This chunk will be enqueued, but will be impossible to read because the state is already "closed". 50 } 51 }); 52 controller.enqueue('a'); 53 return readableStreamToArray(rs) 54 .then(array => assert_array_equals(array, [], 'array should contain no chunks')); 55 // The chunk 'a' is still in rs's queue. It is closed so 'a' cannot be read. 56}, 'close() inside size() should not crash'); 57 58promise_test(() => { 59 let controller; 60 let calls = 0; 61 const rs = new ReadableStream({ 62 start(c) { 63 controller = c; 64 } 65 }, { 66 size() { 67 ++calls; 68 if (calls === 2) { 69 // The queue contains one chunk. 70 controller.close(); 71 // The state is still "readable", but closeRequest is now true. 72 } 73 return 1; 74 } 75 }); 76 controller.enqueue('a'); 77 controller.enqueue('b'); 78 return readableStreamToArray(rs) 79 .then(array => assert_array_equals(array, ['a', 'b'], 'array should contain two chunks')); 80}, 'close request inside size() should work'); 81 82promise_test(t => { 83 let controller; 84 const rs = new ReadableStream({ 85 start(c) { 86 controller = c; 87 } 88 }, { 89 size() { 90 controller.error(error1); 91 return 1; 92 } 93 }); 94 controller.enqueue('a'); 95 return promise_rejects_exactly(t, error1, rs.getReader().read(), 'read() should reject'); 96}, 'error() inside size() should work'); 97 98promise_test(() => { 99 let controller; 100 const rs = new ReadableStream({ 101 start(c) { 102 controller = c; 103 } 104 }, { 105 size() { 106 assert_equals(controller.desiredSize, 1, 'desiredSize should be 1'); 107 return 1; 108 }, 109 highWaterMark: 1 110 }); 111 controller.enqueue('a'); 112 controller.close(); 113 return readableStreamToArray(rs) 114 .then(array => assert_array_equals(array, ['a'], 'array should contain one chunk')); 115}, 'desiredSize inside size() should work'); 116 117promise_test(t => { 118 let cancelPromise; 119 let controller; 120 const rs = new ReadableStream({ 121 start(c) { 122 controller = c; 123 }, 124 cancel: t.step_func(reason => { 125 assert_equals(reason, error1, 'reason should be error1'); 126 assert_throws_js(TypeError, () => controller.enqueue(), 'enqueue() should throw'); 127 }) 128 }, { 129 size() { 130 cancelPromise = rs.cancel(error1); 131 return 1; 132 }, 133 highWaterMark: Infinity 134 }); 135 controller.enqueue('a'); 136 const reader = rs.getReader(); 137 return Promise.all([ 138 reader.closed, 139 cancelPromise 140 ]); 141}, 'cancel() inside size() should work'); 142 143promise_test(() => { 144 let controller; 145 let pipeToPromise; 146 const ws = recordingWritableStream(); 147 const rs = new ReadableStream({ 148 start(c) { 149 controller = c; 150 } 151 }, { 152 size() { 153 if (!pipeToPromise) { 154 pipeToPromise = rs.pipeTo(ws); 155 } 156 return 1; 157 }, 158 highWaterMark: 1 159 }); 160 controller.enqueue('a'); 161 assert_not_equals(pipeToPromise, undefined); 162 163 // Some pipeTo() implementations need an additional chunk enqueued in order for the first one to be processed. See 164 // https://github.com/whatwg/streams/issues/794 for background. 165 controller.enqueue('a'); 166 167 // Give pipeTo() a chance to process the queued chunks. 168 return delay(0).then(() => { 169 assert_array_equals(ws.events, ['write', 'a', 'write', 'a'], 'ws should contain two chunks'); 170 controller.close(); 171 return pipeToPromise; 172 }).then(() => { 173 assert_array_equals(ws.events, ['write', 'a', 'write', 'a', 'close'], 'target should have been closed'); 174 }); 175}, 'pipeTo() inside size() should behave as expected'); 176 177promise_test(() => { 178 let controller; 179 let readPromise; 180 let calls = 0; 181 let readResolved = false; 182 let reader; 183 const rs = new ReadableStream({ 184 start(c) { 185 controller = c; 186 } 187 }, { 188 size() { 189 // This is triggered by controller.enqueue(). The queue is empty and there are no pending reads. This read is 190 // added to the list of pending reads. 191 readPromise = reader.read(); 192 ++calls; 193 return 1; 194 }, 195 highWaterMark: 0 196 }); 197 reader = rs.getReader(); 198 controller.enqueue('a'); 199 readPromise.then(() => { 200 readResolved = true; 201 }); 202 return flushAsyncEvents().then(() => { 203 assert_false(readResolved); 204 controller.enqueue('b'); 205 assert_equals(calls, 1, 'size() should have been called once'); 206 return delay(0); 207 }).then(() => { 208 assert_true(readResolved); 209 assert_equals(calls, 1, 'size() should only be called once'); 210 return readPromise; 211 }).then(({ value, done }) => { 212 assert_false(done, 'done should be false'); 213 // See https://github.com/whatwg/streams/issues/794 for why this chunk is not 'a'. 214 assert_equals(value, 'b', 'chunk should have been read'); 215 assert_equals(calls, 1, 'calls should still be 1'); 216 return reader.read(); 217 }).then(({ value, done }) => { 218 assert_false(done, 'done should be false again'); 219 assert_equals(value, 'a', 'chunk a should come after b'); 220 }); 221}, 'read() inside of size() should behave as expected'); 222 223promise_test(() => { 224 let controller; 225 let reader; 226 const rs = new ReadableStream({ 227 start(c) { 228 controller = c; 229 } 230 }, { 231 size() { 232 reader = rs.getReader(); 233 return 1; 234 } 235 }); 236 controller.enqueue('a'); 237 return reader.read().then(({ value, done }) => { 238 assert_false(done, 'done should be false'); 239 assert_equals(value, 'a', 'value should be a'); 240 }); 241}, 'getReader() inside size() should work'); 242 243promise_test(() => { 244 let controller; 245 let branch1; 246 let branch2; 247 const rs = new ReadableStream({ 248 start(c) { 249 controller = c; 250 } 251 }, { 252 size() { 253 [branch1, branch2] = rs.tee(); 254 return 1; 255 } 256 }); 257 controller.enqueue('a'); 258 assert_true(rs.locked, 'rs should be locked'); 259 controller.close(); 260 return Promise.all([ 261 readableStreamToArray(branch1).then(array => assert_array_equals(array, ['a'], 'branch1 should have one chunk')), 262 readableStreamToArray(branch2).then(array => assert_array_equals(array, ['a'], 'branch2 should have one chunk')) 263 ]); 264}, 'tee() inside size() should work'); 265