1// META: global=window,worker 2// META: script=../resources/test-utils.js 3// META: script=../resources/rs-utils.js 4'use strict'; 5 6promise_test(t => { 7 8 const randomSource = new RandomPushSource(); 9 10 let cancellationFinished = false; 11 const rs = new ReadableStream({ 12 start(c) { 13 randomSource.ondata = c.enqueue.bind(c); 14 randomSource.onend = c.close.bind(c); 15 randomSource.onerror = c.error.bind(c); 16 }, 17 18 pull() { 19 randomSource.readStart(); 20 }, 21 22 cancel() { 23 randomSource.readStop(); 24 25 return new Promise(resolve => { 26 t.step_timeout(() => { 27 cancellationFinished = true; 28 resolve(); 29 }, 1); 30 }); 31 } 32 }); 33 34 const reader = rs.getReader(); 35 36 // We call delay multiple times to avoid cancelling too early for the 37 // source to enqueue at least one chunk. 38 const cancel = delay(5).then(() => delay(5)).then(() => delay(5)).then(() => { 39 const cancelPromise = reader.cancel(); 40 assert_false(cancellationFinished, 'cancellation in source should happen later'); 41 return cancelPromise; 42 }); 43 44 return readableStreamToArray(rs, reader).then(chunks => { 45 assert_greater_than(chunks.length, 0, 'at least one chunk should be read'); 46 for (let i = 0; i < chunks.length; i++) { 47 assert_equals(chunks[i].length, 128, 'chunk ' + i + ' should have 128 bytes'); 48 } 49 return cancel; 50 }).then(() => { 51 assert_true(cancellationFinished, 'it returns a promise that is fulfilled when the cancellation finishes'); 52 }); 53 54}, 'ReadableStream cancellation: integration test on an infinite stream derived from a random push source'); 55 56test(() => { 57 58 let recordedReason; 59 const rs = new ReadableStream({ 60 cancel(reason) { 61 recordedReason = reason; 62 } 63 }); 64 65 const passedReason = new Error('Sorry, it just wasn\'t meant to be.'); 66 rs.cancel(passedReason); 67 68 assert_equals(recordedReason, passedReason, 69 'the error passed to the underlying source\'s cancel method should equal the one passed to the stream\'s cancel'); 70 71}, 'ReadableStream cancellation: cancel(reason) should pass through the given reason to the underlying source'); 72 73promise_test(() => { 74 75 const rs = new ReadableStream({ 76 start(c) { 77 c.enqueue('a'); 78 c.close(); 79 }, 80 cancel() { 81 assert_unreached('underlying source cancel() should not have been called'); 82 } 83 }); 84 85 const reader = rs.getReader(); 86 87 return rs.cancel().then(() => { 88 assert_unreached('cancel() should be rejected'); 89 }, e => { 90 assert_equals(e.name, 'TypeError', 'cancel() should be rejected with a TypeError'); 91 }).then(() => { 92 return reader.read(); 93 }).then(result => { 94 assert_object_equals(result, { value: 'a', done: false }, 'read() should still work after the attempted cancel'); 95 return reader.closed; 96 }); 97 98}, 'ReadableStream cancellation: cancel() on a locked stream should fail and not call the underlying source cancel'); 99 100promise_test(() => { 101 102 let cancelReceived = false; 103 const cancelReason = new Error('I am tired of this stream, I prefer to cancel it'); 104 const rs = new ReadableStream({ 105 cancel(reason) { 106 cancelReceived = true; 107 assert_equals(reason, cancelReason, 'cancellation reason given to the underlying source should be equal to the one passed'); 108 } 109 }); 110 111 return rs.cancel(cancelReason).then(() => { 112 assert_true(cancelReceived); 113 }); 114 115}, 'ReadableStream cancellation: should fulfill promise when cancel callback went fine'); 116 117promise_test(() => { 118 119 const rs = new ReadableStream({ 120 cancel() { 121 return 'Hello'; 122 } 123 }); 124 125 return rs.cancel().then(v => { 126 assert_equals(v, undefined, 'cancel() return value should be fulfilled with undefined'); 127 }); 128 129}, 'ReadableStream cancellation: returning a value from the underlying source\'s cancel should not affect the fulfillment value of the promise returned by the stream\'s cancel'); 130 131promise_test(() => { 132 133 const thrownError = new Error('test'); 134 let cancelCalled = false; 135 136 const rs = new ReadableStream({ 137 cancel() { 138 cancelCalled = true; 139 throw thrownError; 140 } 141 }); 142 143 return rs.cancel('test').then(() => { 144 assert_unreached('cancel should reject'); 145 }, e => { 146 assert_true(cancelCalled); 147 assert_equals(e, thrownError); 148 }); 149 150}, 'ReadableStream cancellation: should reject promise when cancel callback raises an exception'); 151 152promise_test(() => { 153 154 const cancelReason = new Error('test'); 155 156 const rs = new ReadableStream({ 157 cancel(error) { 158 assert_equals(error, cancelReason); 159 return delay(1); 160 } 161 }); 162 163 return rs.cancel(cancelReason); 164 165}, 'ReadableStream cancellation: if the underlying source\'s cancel method returns a promise, the promise returned by the stream\'s cancel should fulfill when that one does (1)'); 166 167promise_test(t => { 168 169 let resolveSourceCancelPromise; 170 let sourceCancelPromiseHasFulfilled = false; 171 172 const rs = new ReadableStream({ 173 cancel() { 174 const sourceCancelPromise = new Promise(resolve => resolveSourceCancelPromise = resolve); 175 176 sourceCancelPromise.then(() => { 177 sourceCancelPromiseHasFulfilled = true; 178 }); 179 180 return sourceCancelPromise; 181 } 182 }); 183 184 t.step_timeout(() => resolveSourceCancelPromise('Hello'), 1); 185 186 return rs.cancel().then(value => { 187 assert_true(sourceCancelPromiseHasFulfilled, 'cancel() return value should be fulfilled only after the promise returned by the underlying source\'s cancel'); 188 assert_equals(value, undefined, 'cancel() return value should be fulfilled with undefined'); 189 }); 190 191}, 'ReadableStream cancellation: if the underlying source\'s cancel method returns a promise, the promise returned by the stream\'s cancel should fulfill when that one does (2)'); 192 193promise_test(t => { 194 195 let rejectSourceCancelPromise; 196 let sourceCancelPromiseHasRejected = false; 197 198 const rs = new ReadableStream({ 199 cancel() { 200 const sourceCancelPromise = new Promise((resolve, reject) => rejectSourceCancelPromise = reject); 201 202 sourceCancelPromise.catch(() => { 203 sourceCancelPromiseHasRejected = true; 204 }); 205 206 return sourceCancelPromise; 207 } 208 }); 209 210 const errorInCancel = new Error('Sorry, it just wasn\'t meant to be.'); 211 212 t.step_timeout(() => rejectSourceCancelPromise(errorInCancel), 1); 213 214 return rs.cancel().then(() => { 215 assert_unreached('cancel() return value should be rejected'); 216 }, r => { 217 assert_true(sourceCancelPromiseHasRejected, 'cancel() return value should be rejected only after the promise returned by the underlying source\'s cancel'); 218 assert_equals(r, errorInCancel, 'cancel() return value should be rejected with the underlying source\'s rejection reason'); 219 }); 220 221}, 'ReadableStream cancellation: if the underlying source\'s cancel method returns a promise, the promise returned by the stream\'s cancel should reject when that one does'); 222 223promise_test(() => { 224 225 const rs = new ReadableStream({ 226 start() { 227 return new Promise(() => {}); 228 }, 229 pull() { 230 assert_unreached('pull should not have been called'); 231 } 232 }); 233 234 return Promise.all([rs.cancel(), rs.getReader().closed]); 235 236}, 'ReadableStream cancellation: cancelling before start finishes should prevent pull() from being called'); 237