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