large-requests-abort.any.js (7184B)
1 // META: title=IndexedDB: transactions with large request results are aborted correctly 2 // META: global=window,worker 3 // META: script=resources/support-promises.js 4 // META: timeout=long 5 6 // Spec: https://w3c.github.io/IndexedDB/#abort-transaction 7 8 'use strict'; 9 10 // Should be large enough to trigger large value handling in the IndexedDB 11 // engines that have special code paths for large values. 12 const wrapThreshold = 128 * 1024; 13 14 function populateStore(store) { 15 store.put({id: 1, key: 'k1', value: largeValue(wrapThreshold, 1)}); 16 store.put({id: 2, key: 'k2', value: ['small-2']}); 17 store.put({id: 3, key: 'k3', value: largeValue(wrapThreshold, 3)}); 18 store.put({id: 4, key: 'k4', value: ['small-4']}); 19 } 20 21 // Opens index cursors for operations that require open cursors. 22 // 23 // onsuccess is called if all cursors are opened successfully. Otherwise, 24 // onerror will be called at least once. 25 function openCursors(testCase, index, operations, onerror, onsuccess) { 26 let pendingCursors = 0; 27 28 for (let operation of operations) { 29 const opcode = operation[0]; 30 const primaryKey = operation[1]; 31 let request; 32 switch (opcode) { 33 case 'continue': 34 request = 35 index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey - 1}`)); 36 break; 37 case 'continue-empty': 38 // k4 is the last key in the data set, so calling continue() will get 39 // the cursor past the end of the store. 40 request = index.openCursor(IDBKeyRange.lowerBound('k4')); 41 break; 42 default: 43 continue; 44 } 45 46 operation[2] = request; 47 ++pendingCursors; 48 49 request.onsuccess = testCase.step_func(() => { 50 --pendingCursors; 51 if (!pendingCursors) 52 onsuccess(); 53 }); 54 request.onerror = testCase.step_func(onerror); 55 } 56 57 if (!pendingCursors) 58 onsuccess(); 59 } 60 61 function doOperation(testCase, store, index, operation, requestId, results) { 62 const opcode = operation[0]; 63 const primaryKey = operation[1]; 64 const cursor = operation[2]; 65 66 return new Promise((resolve, reject) => { 67 let request; 68 switch (opcode) { 69 case 'add': // Tests returning a primary key. 70 request = 71 store.add({key: `k${primaryKey}`, value: [`small-${primaryKey}`]}); 72 break; 73 case 'put': // Tests returning a primary key. 74 request = 75 store.put({key: `k${primaryKey}`, value: [`small-${primaryKey}`]}); 76 break; 77 case 'put-with-id': // Tests returning success or a primary key. 78 request = store.put({ 79 key: `k${primaryKey}`, 80 value: [`small-${primaryKey}`], 81 id: primaryKey 82 }); 83 break; 84 case 'get': // Tests returning a value. 85 case 'get-empty': // Tests returning undefined. 86 request = store.get(primaryKey); 87 break; 88 case 'getall': // Tests returning an array of values. 89 request = store.getAll(); 90 break; 91 case 'error': // Tests returning an error. 92 request = 93 store.put({key: `k${primaryKey}`, value: [`small-${primaryKey}`]}); 94 break; 95 case 'continue': // Tests returning a key, primary key, and value. 96 case 'continue-empty': // Tests returning null. 97 request = cursor; 98 cursor.result.continue(); 99 break; 100 case 'open': // Tests returning a cursor, key, primary key, and value. 101 case 'open-empty': // Tests returning null. 102 request = index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey}`)); 103 break; 104 case 'count': // Tests returning a numeric result. 105 request = index.count(); 106 break; 107 }; 108 109 request.onsuccess = testCase.step_func(() => { 110 reject(new Error( 111 'requests should not succeed after the transaction is aborted')); 112 }); 113 request.onerror = testCase.step_func(event => { 114 event.preventDefault(); 115 results.push([requestId, request.error]); 116 resolve(); 117 }); 118 }); 119 } 120 121 function abortTest(label, operations) { 122 promise_test(testCase => { 123 return createDatabase( 124 testCase, 125 (database, transaction) => { 126 const store = database.createObjectStore( 127 'test-store', {autoIncrement: true, keyPath: 'id'}); 128 store.createIndex('test-index', 'key', {unique: true}); 129 populateStore(store); 130 }) 131 .then(database => { 132 const transaction = database.transaction(['test-store'], 'readwrite'); 133 const store = transaction.objectStore('test-store'); 134 const index = store.index('test-index'); 135 return new Promise((resolve, reject) => { 136 openCursors(testCase, index, operations, reject, () => { 137 const results = []; 138 const promises = []; 139 for (let i = 0; i < operations.length; ++i) { 140 const promise = doOperation( 141 testCase, store, index, operations[i], i, results); 142 promises.push(promise); 143 }; 144 transaction.abort(); 145 resolve(Promise.all(promises).then(() => results)); 146 }); 147 }); 148 }) 149 .then(results => { 150 assert_equals( 151 results.length, operations.length, 152 'Promise.all should resolve after all sub-promises resolve'); 153 for (let i = 0; i < operations.length; ++i) { 154 assert_equals( 155 results[i][0], i, 156 'error event order should match request order'); 157 assert_equals( 158 results[i][1].name, 'AbortError', 159 'transaction aborting should result in AbortError on all requests'); 160 } 161 }); 162 }, label); 163 } 164 165 abortTest('small values', [ 166 ['get', 2], 167 ['count', null], 168 ['continue-empty', null], 169 ['get-empty', 5], 170 ['add', 5], 171 ['open', 2], 172 ['continue', 2], 173 ['get', 4], 174 ['get-empty', 6], 175 ['count', null], 176 ['put-with-id', 5], 177 ['put', 6], 178 ['error', 3], 179 ['continue', 4], 180 ['count', null], 181 ['get-empty', 7], 182 ['open', 4], 183 ['open-empty', 7], 184 ['add', 7], 185 ]); 186 187 abortTest('large values', [ 188 ['open', 1], 189 ['get', 1], 190 ['getall', 4], 191 ['get', 3], 192 ['continue', 3], 193 ['open', 3], 194 ]); 195 196 abortTest('large value followed by small values', [ 197 ['get', 1], 198 ['getall', null], 199 ['open', 2], 200 ['continue-empty', null], 201 ['get', 2], 202 ['get-empty', 5], 203 ['count', null], 204 ['continue-empty', null], 205 ['open-empty', 5], 206 ['add', 5], 207 ['error', 1], 208 ['continue', 2], 209 ['get-empty', 6], 210 ['put-with-id', 5], 211 ['put', 6], 212 ]); 213 214 abortTest('large values mixed with small values', [ 215 ['get', 1], 216 ['get', 2], 217 ['get-empty', 5], 218 ['count', null], 219 ['continue-empty', null], 220 ['open', 1], 221 ['continue', 2], 222 ['open-empty', 5], 223 ['getall', 4], 224 ['open', 2], 225 ['continue-empty', null], 226 ['add', 5], 227 ['get', 3], 228 ['count', null], 229 ['get-empty', 6], 230 ['put-with-id', 5], 231 ['getall', null], 232 ['continue', 3], 233 ['open-empty', 6], 234 ['put', 6], 235 ['error', 1], 236 ['continue', 2], 237 ['open', 4], 238 ['get-empty', 7], 239 ['count', null], 240 ['continue', 3], 241 ['add', 7], 242 ['getall', null], 243 ['error', 3], 244 ['count', null], 245 ]);