OLD | NEW |
(Empty) | |
| 1 <!doctype html> |
| 2 <meta charset="utf8"> |
| 3 <meta name="timeout" content="long"> |
| 4 <title>IndexedDB: request result events are delivered in order</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 // Assigns cursor indexes for operations that require open cursors. |
| 25 // |
| 26 // Returns the number of open cursors required to perform all operations. |
| 27 function assignCursors(operations) { |
| 28 return cursorCount; |
| 29 } |
| 30 |
| 31 // Opens index cursors for operations that require open cursors. |
| 32 // |
| 33 // onsuccess is called if all cursors are opened successfully. Otherwise, |
| 34 // onerror will be called at least once. |
| 35 function openCursors(testCase, index, operations, onerror, onsuccess) { |
| 36 let pendingCursors = 0; |
| 37 |
| 38 for (let operation of operations) { |
| 39 const opcode = operation[0]; |
| 40 const primaryKey = operation[1]; |
| 41 let request; |
| 42 switch (opcode) { |
| 43 case 'continue': |
| 44 request = index.openCursor( |
| 45 IDBKeyRange.lowerBound(`k${primaryKey - 1}`)); |
| 46 break; |
| 47 case 'continue-empty': |
| 48 // k4 is the last key in the data set, so calling continue() will get |
| 49 // the cursor past the end of the store. |
| 50 request = index.openCursor(IDBKeyRange.lowerBound('k4')); |
| 51 break; |
| 52 default: |
| 53 continue; |
| 54 } |
| 55 |
| 56 operation[2] = request; |
| 57 ++pendingCursors; |
| 58 |
| 59 request.onsuccess = testCase.step_func(() => { |
| 60 --pendingCursors; |
| 61 if (!pendingCursors) |
| 62 onsuccess(); |
| 63 }); |
| 64 request.onerror = testCase.step_func(onerror); |
| 65 } |
| 66 |
| 67 if (!pendingCursors) |
| 68 onsuccess(); |
| 69 } |
| 70 |
| 71 function doOperation(testCase, store, index, operation, requestId, results) { |
| 72 const opcode = operation[0]; |
| 73 const primaryKey = operation[1]; |
| 74 const cursor = operation[2]; |
| 75 |
| 76 return new Promise((resolve, reject) => { |
| 77 let request; |
| 78 switch (opcode) { |
| 79 case 'put': // Tests returning a primary key. |
| 80 request = store.put( |
| 81 { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); |
| 82 break; |
| 83 case 'get': // Tests returning a value. |
| 84 case 'get-empty': // Tests returning undefined. |
| 85 request = store.get(primaryKey); |
| 86 break; |
| 87 case 'getall': // Tests returning an array of values. |
| 88 request = store.getAll(); |
| 89 break; |
| 90 case 'error': // Tests returning an error. |
| 91 request = store.put( |
| 92 { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); |
| 93 request.onerror = testCase.step_func(event => { |
| 94 event.preventDefault(); |
| 95 results.push([requestId, request.error]); |
| 96 resolve(); |
| 97 }); |
| 98 request.onsuccess = testCase.step_func(() => { |
| 99 reject(new Error('put with duplicate primary key succeded')); |
| 100 }); |
| 101 break; |
| 102 case 'continue': // Tests returning a key, primary key, and value. |
| 103 request = cursor; |
| 104 cursor.result.continue(); |
| 105 request.onsuccess = testCase.step_func(() => { |
| 106 const result = request.result; |
| 107 results.push( |
| 108 [requestId, result.key, result.primaryKey, result.value]); |
| 109 resolve(); |
| 110 }); |
| 111 request.onerror = null; |
| 112 break; |
| 113 case 'open': // Tests returning a cursor, key, primary key, and value. |
| 114 request = index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey}`)); |
| 115 request.onsuccess = testCase.step_func(() => { |
| 116 const result = request.result; |
| 117 results.push( |
| 118 [requestId, result.key, result.primaryKey, result.value]); |
| 119 resolve(); |
| 120 }); |
| 121 break; |
| 122 case 'continue-empty': // Tests returning a null result. |
| 123 request = cursor; |
| 124 cursor.result.continue(); |
| 125 request.onsuccess = testCase.step_func(() => { |
| 126 results.push([requestId, request.result]); |
| 127 resolve(); |
| 128 }); |
| 129 request.onerror = null; |
| 130 break; |
| 131 case 'open-empty': // Tests returning a null cursor. |
| 132 request = index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey}`)); |
| 133 request.onsuccess = testCase.step_func(() => { |
| 134 const result = request.result; |
| 135 results.push([requestId, request.result]); |
| 136 resolve(); |
| 137 }); |
| 138 break; |
| 139 case 'count': // Tests returning a numeric result. |
| 140 request = index.count(); |
| 141 request.onsuccess = testCase.step_func(() => { |
| 142 results.push([requestId, request.result]); |
| 143 resolve(); |
| 144 }); |
| 145 break; |
| 146 }; |
| 147 |
| 148 if (!request.onsuccess) { |
| 149 request.onsuccess = testCase.step_func(() => { |
| 150 results.push([requestId, request.result]); |
| 151 resolve(); |
| 152 }); |
| 153 } |
| 154 if (!request.onerror) |
| 155 request.onerror = testCase.step_func(event => { |
| 156 event.preventDefault(); |
| 157 reject(request.error); |
| 158 }); |
| 159 }); |
| 160 } |
| 161 |
| 162 function checkOperationResult(operation, result, requestId) { |
| 163 const opcode = operation[0]; |
| 164 const primaryKey = operation[1]; |
| 165 |
| 166 const expectedValue = (primaryKey == 1 || primaryKey == 3) ? |
| 167 largeValue(wrapThreshold, primaryKey) : [`small-${primaryKey}`]; |
| 168 |
| 169 const requestIndex = result[0]; |
| 170 assert_equals( |
| 171 requestIndex, requestId, 'result event order should match request order'); |
| 172 switch (opcode) { |
| 173 case 'put': |
| 174 assert_equals( |
| 175 result[1], primaryKey, |
| 176 "put result should be the new object's primary key"); |
| 177 break; |
| 178 case 'get': |
| 179 assert_equals( |
| 180 result[1].id, primaryKey, |
| 181 'get result should match put value (primary key)'); |
| 182 assert_equals( |
| 183 result[1].key, `k${primaryKey}`, |
| 184 'get result should match put value (key)'); |
| 185 assert_equals( |
| 186 result[1].value.join(','), expectedValue.join(','), |
| 187 'get result should match put value (nested value)'); |
| 188 break; |
| 189 case 'getall': |
| 190 assert_equals( |
| 191 result[1].length, primaryKey, |
| 192 'getAll should return all the objects in the store'); |
| 193 for (let i = 0; i < primaryKey; ++i) { |
| 194 const object = result[1][i]; |
| 195 assert_equals( |
| 196 object.id, i + 1, |
| 197 `getAll result ${i + 1} should match put value (primary key)`); |
| 198 assert_equals( |
| 199 object.key, `k${i + 1}`, |
| 200 `get result ${i + 1} should match put value (key)`); |
| 201 |
| 202 const expectedValue = (i == 0 || i == 2) ? |
| 203 largeValue(wrapThreshold, i + 1) : [`small-${i + 1}`]; |
| 204 assert_equals( |
| 205 object.value.join(','), object.value.join(','), |
| 206 `get result ${i + 1} should match put value (nested value)`); |
| 207 } |
| 208 break; |
| 209 case 'get-empty': |
| 210 assert_equals( |
| 211 result[1], undefined, 'get-empty result should be undefined'); |
| 212 break; |
| 213 case 'error': |
| 214 assert_equals( |
| 215 result[1].name, 'ConstraintError', |
| 216 'incorrect error from put with duplicate primary key'); |
| 217 break; |
| 218 case 'continue': |
| 219 case 'open': |
| 220 assert_equals( |
| 221 result[1], `k${primaryKey}`, |
| 222 `${opcode} key should match the key in the put value`); |
| 223 assert_equals( |
| 224 result[2], primaryKey, |
| 225 `${opcode} primary key should match the put value's primary key`); |
| 226 assert_equals( |
| 227 result[3].id, primaryKey, |
| 228 `${opcode} value should match put value (primary key)`); |
| 229 assert_equals( |
| 230 result[3].key, `k${primaryKey}`, |
| 231 `${opcode} value should match put value (key)`); |
| 232 assert_equals( |
| 233 result[3].value.join(','), expectedValue.join(','), |
| 234 `${opcode} value should match put value (nested value)`); |
| 235 break; |
| 236 case 'continue-empty': |
| 237 case 'open-empty': |
| 238 assert_equals(result[1], null, `${opcode} result should be null`); |
| 239 break; |
| 240 } |
| 241 } |
| 242 |
| 243 function eventsTest(label, operations) { |
| 244 promise_test(testCase => { |
| 245 return createDatabase(testCase, (database, transaction) => { |
| 246 const store = database.createObjectStore( |
| 247 'test-store', { autoIncrement: true, keyPath: 'id' }); |
| 248 store.createIndex('test-index', 'key', { unique: true }); |
| 249 populateStore(store); |
| 250 }).then(database => { |
| 251 const transaction = database.transaction(['test-store'], 'readwrite'); |
| 252 const store = transaction.objectStore('test-store'); |
| 253 const index = store.index('test-index'); |
| 254 return new Promise((resolve, reject) => { |
| 255 openCursors(testCase, index, operations, reject, () => { |
| 256 const results = []; |
| 257 const promises = []; |
| 258 for (let i = 0; i < operations.length; ++i) { |
| 259 const promise = doOperation( |
| 260 testCase, store, index, operations[i], i, results); |
| 261 promises.push(promise); |
| 262 }; |
| 263 resolve(Promise.all(promises).then(() => results)); |
| 264 }); |
| 265 }); |
| 266 }).then(results => { |
| 267 assert_equals( |
| 268 results.length, operations.length, |
| 269 'Promise.all should resolve after all sub-promises resolve'); |
| 270 for (let i = 0; i < operations.length; ++i) |
| 271 checkOperationResult(operations[i], results[i], i); |
| 272 }); |
| 273 }, label); |
| 274 } |
| 275 |
| 276 eventsTest('small values', [ |
| 277 ['get', 2], |
| 278 ['count', 4], |
| 279 ['continue-empty', null], |
| 280 ['get-empty', 5], |
| 281 ['put', 5], |
| 282 ['open', 2], |
| 283 ['continue', 2], |
| 284 ['get', 4], |
| 285 ['get-empty', 6], |
| 286 ['count', 5], |
| 287 ['put', 6], |
| 288 ['error', 3], |
| 289 ['continue', 4], |
| 290 ['count', 6], |
| 291 ['get-empty', 7], |
| 292 ['open', 4], |
| 293 ['open-empty', 7], |
| 294 ]); |
| 295 |
| 296 eventsTest('large values', [ |
| 297 ['open', 1], |
| 298 ['get', 1], |
| 299 ['getall', 4], |
| 300 ['get', 3], |
| 301 ['continue', 3], |
| 302 ['open', 3], |
| 303 ]); |
| 304 |
| 305 eventsTest('large value followed by small values', [ |
| 306 ['get', 1], |
| 307 ['getall', 4], |
| 308 ['open', 2], |
| 309 ['continue-empty', null], |
| 310 ['get', 2], |
| 311 ['get-empty', 5], |
| 312 ['count', 4], |
| 313 ['continue-empty', null], |
| 314 ['open-empty', 5], |
| 315 ['put', 5], |
| 316 ['error', 1], |
| 317 ['continue', 2], |
| 318 ['get-empty', 6], |
| 319 ]); |
| 320 |
| 321 eventsTest('large values mixed with small values', [ |
| 322 ['get', 1], |
| 323 ['get', 2], |
| 324 ['get-empty', 5], |
| 325 ['count', 4], |
| 326 ['continue-empty', null], |
| 327 ['open', 1], |
| 328 ['continue', 2], |
| 329 ['open-empty', 5], |
| 330 ['getall', 4], |
| 331 ['open', 2], |
| 332 ['continue-empty', null], |
| 333 ['put', 5], |
| 334 ['get', 3], |
| 335 ['count', 5], |
| 336 ['get-empty', 6], |
| 337 ['getall', 5], |
| 338 ['continue', 3], |
| 339 ['open-empty', 6], |
| 340 ['put', 6], |
| 341 ['error', 1], |
| 342 ['continue', 2], |
| 343 ['open', 4], |
| 344 ['get-empty', 7], |
| 345 ['continue', 3], |
| 346 ['getall', 6], |
| 347 ['error', 3], |
| 348 ['count', 6], |
| 349 ]); |
| 350 |
| 351 </script> |
OLD | NEW |