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 'add': // Tests returning a primary key. |
| 80 request = store.add( |
| 81 { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); |
| 82 break; |
| 83 case 'put': // Tests returning a primary key. |
| 84 request = store.put( |
| 85 { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); |
| 86 break; |
| 87 case 'put-with-id': // Tests returning success or a primary key. |
| 88 request = store.put( |
| 89 { key: `k${primaryKey}`, value: [`small-${primaryKey}`], |
| 90 id: primaryKey }); |
| 91 break; |
| 92 case 'get': // Tests returning a value. |
| 93 case 'get-empty': // Tests returning undefined. |
| 94 request = store.get(primaryKey); |
| 95 break; |
| 96 case 'getall': // Tests returning an array of values. |
| 97 request = store.getAll(); |
| 98 break; |
| 99 case 'error': // Tests returning an error. |
| 100 request = store.put( |
| 101 { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); |
| 102 request.onerror = testCase.step_func(event => { |
| 103 event.preventDefault(); |
| 104 results.push([requestId, request.error]); |
| 105 resolve(); |
| 106 }); |
| 107 request.onsuccess = testCase.step_func(() => { |
| 108 reject(new Error('put with duplicate primary key succeded')); |
| 109 }); |
| 110 break; |
| 111 case 'continue': // Tests returning a key, primary key, and value. |
| 112 request = cursor; |
| 113 cursor.result.continue(); |
| 114 request.onsuccess = testCase.step_func(() => { |
| 115 const result = request.result; |
| 116 results.push( |
| 117 [requestId, result.key, result.primaryKey, result.value]); |
| 118 resolve(); |
| 119 }); |
| 120 request.onerror = null; |
| 121 break; |
| 122 case 'open': // Tests returning a cursor, key, primary key, and value. |
| 123 request = index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey}`)); |
| 124 request.onsuccess = testCase.step_func(() => { |
| 125 const result = request.result; |
| 126 results.push( |
| 127 [requestId, result.key, result.primaryKey, result.value]); |
| 128 resolve(); |
| 129 }); |
| 130 break; |
| 131 case 'continue-empty': // Tests returning a null result. |
| 132 request = cursor; |
| 133 cursor.result.continue(); |
| 134 request.onsuccess = testCase.step_func(() => { |
| 135 results.push([requestId, request.result]); |
| 136 resolve(); |
| 137 }); |
| 138 request.onerror = null; |
| 139 break; |
| 140 case 'open-empty': // Tests returning a null cursor. |
| 141 request = index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey}`)); |
| 142 request.onsuccess = testCase.step_func(() => { |
| 143 const result = request.result; |
| 144 results.push([requestId, request.result]); |
| 145 resolve(); |
| 146 }); |
| 147 break; |
| 148 case 'count': // Tests returning a numeric result. |
| 149 request = index.count(); |
| 150 request.onsuccess = testCase.step_func(() => { |
| 151 results.push([requestId, request.result]); |
| 152 resolve(); |
| 153 }); |
| 154 break; |
| 155 }; |
| 156 |
| 157 if (!request.onsuccess) { |
| 158 request.onsuccess = testCase.step_func(() => { |
| 159 results.push([requestId, request.result]); |
| 160 resolve(); |
| 161 }); |
| 162 } |
| 163 if (!request.onerror) |
| 164 request.onerror = testCase.step_func(event => { |
| 165 event.preventDefault(); |
| 166 reject(request.error); |
| 167 }); |
| 168 }); |
| 169 } |
| 170 |
| 171 function checkOperationResult(operation, result, requestId) { |
| 172 const opcode = operation[0]; |
| 173 const primaryKey = operation[1]; |
| 174 |
| 175 const expectedValue = (primaryKey == 1 || primaryKey == 3) ? |
| 176 largeValue(wrapThreshold, primaryKey) : [`small-${primaryKey}`]; |
| 177 |
| 178 const requestIndex = result[0]; |
| 179 assert_equals( |
| 180 requestIndex, requestId, 'result event order should match request order'); |
| 181 switch (opcode) { |
| 182 case 'put': |
| 183 case 'put-with-id': |
| 184 case 'add': |
| 185 assert_equals( |
| 186 result[1], primaryKey, |
| 187 `${opcode} result should be the new object's primary key`); |
| 188 break; |
| 189 case 'get': |
| 190 assert_equals( |
| 191 result[1].id, primaryKey, |
| 192 'get result should match put value (primary key)'); |
| 193 assert_equals( |
| 194 result[1].key, `k${primaryKey}`, |
| 195 'get result should match put value (key)'); |
| 196 assert_equals( |
| 197 result[1].value.join(','), expectedValue.join(','), |
| 198 'get result should match put value (nested value)'); |
| 199 break; |
| 200 case 'getall': |
| 201 assert_equals( |
| 202 result[1].length, primaryKey, |
| 203 'getAll should return all the objects in the store'); |
| 204 for (let i = 0; i < primaryKey; ++i) { |
| 205 const object = result[1][i]; |
| 206 assert_equals( |
| 207 object.id, i + 1, |
| 208 `getAll result ${i + 1} should match put value (primary key)`); |
| 209 assert_equals( |
| 210 object.key, `k${i + 1}`, |
| 211 `get result ${i + 1} should match put value (key)`); |
| 212 |
| 213 const expectedValue = (i == 0 || i == 2) ? |
| 214 largeValue(wrapThreshold, i + 1) : [`small-${i + 1}`]; |
| 215 assert_equals( |
| 216 object.value.join(','), object.value.join(','), |
| 217 `get result ${i + 1} should match put value (nested value)`); |
| 218 } |
| 219 break; |
| 220 case 'get-empty': |
| 221 assert_equals( |
| 222 result[1], undefined, 'get-empty result should be undefined'); |
| 223 break; |
| 224 case 'error': |
| 225 assert_equals( |
| 226 result[1].name, 'ConstraintError', |
| 227 'incorrect error from put with duplicate primary key'); |
| 228 break; |
| 229 case 'continue': |
| 230 case 'open': |
| 231 assert_equals( |
| 232 result[1], `k${primaryKey}`, |
| 233 `${opcode} key should match the key in the put value`); |
| 234 assert_equals( |
| 235 result[2], primaryKey, |
| 236 `${opcode} primary key should match the put value's primary key`); |
| 237 assert_equals( |
| 238 result[3].id, primaryKey, |
| 239 `${opcode} value should match put value (primary key)`); |
| 240 assert_equals( |
| 241 result[3].key, `k${primaryKey}`, |
| 242 `${opcode} value should match put value (key)`); |
| 243 assert_equals( |
| 244 result[3].value.join(','), expectedValue.join(','), |
| 245 `${opcode} value should match put value (nested value)`); |
| 246 break; |
| 247 case 'continue-empty': |
| 248 case 'open-empty': |
| 249 assert_equals(result[1], null, `${opcode} result should be null`); |
| 250 break; |
| 251 } |
| 252 } |
| 253 |
| 254 function eventsTest(label, operations) { |
| 255 promise_test(testCase => { |
| 256 return createDatabase(testCase, (database, transaction) => { |
| 257 const store = database.createObjectStore( |
| 258 'test-store', { autoIncrement: true, keyPath: 'id' }); |
| 259 store.createIndex('test-index', 'key', { unique: true }); |
| 260 populateStore(store); |
| 261 }).then(database => { |
| 262 const transaction = database.transaction(['test-store'], 'readwrite'); |
| 263 const store = transaction.objectStore('test-store'); |
| 264 const index = store.index('test-index'); |
| 265 return new Promise((resolve, reject) => { |
| 266 openCursors(testCase, index, operations, reject, () => { |
| 267 const results = []; |
| 268 const promises = []; |
| 269 for (let i = 0; i < operations.length; ++i) { |
| 270 const promise = doOperation( |
| 271 testCase, store, index, operations[i], i, results); |
| 272 promises.push(promise); |
| 273 }; |
| 274 resolve(Promise.all(promises).then(() => results)); |
| 275 }); |
| 276 }); |
| 277 }).then(results => { |
| 278 assert_equals( |
| 279 results.length, operations.length, |
| 280 'Promise.all should resolve after all sub-promises resolve'); |
| 281 for (let i = 0; i < operations.length; ++i) |
| 282 checkOperationResult(operations[i], results[i], i); |
| 283 }); |
| 284 }, label); |
| 285 } |
| 286 |
| 287 eventsTest('small values', [ |
| 288 ['get', 2], |
| 289 ['count', 4], |
| 290 ['continue-empty', null], |
| 291 ['get-empty', 5], |
| 292 ['add', 5], |
| 293 ['open', 2], |
| 294 ['continue', 2], |
| 295 ['get', 4], |
| 296 ['get-empty', 6], |
| 297 ['count', 5], |
| 298 ['put-with-id', 5], |
| 299 ['put', 6], |
| 300 ['error', 3], |
| 301 ['continue', 4], |
| 302 ['count', 6], |
| 303 ['get-empty', 7], |
| 304 ['open', 4], |
| 305 ['open-empty', 7], |
| 306 ['add', 7], |
| 307 ]); |
| 308 |
| 309 eventsTest('large values', [ |
| 310 ['open', 1], |
| 311 ['get', 1], |
| 312 ['getall', 4], |
| 313 ['get', 3], |
| 314 ['continue', 3], |
| 315 ['open', 3], |
| 316 ]); |
| 317 |
| 318 eventsTest('large value followed by small values', [ |
| 319 ['get', 1], |
| 320 ['getall', 4], |
| 321 ['open', 2], |
| 322 ['continue-empty', null], |
| 323 ['get', 2], |
| 324 ['get-empty', 5], |
| 325 ['count', 4], |
| 326 ['continue-empty', null], |
| 327 ['open-empty', 5], |
| 328 ['add', 5], |
| 329 ['error', 1], |
| 330 ['continue', 2], |
| 331 ['get-empty', 6], |
| 332 ['put-with-id', 5], |
| 333 ['put', 6], |
| 334 ]); |
| 335 |
| 336 eventsTest('large values mixed with small values', [ |
| 337 ['get', 1], |
| 338 ['get', 2], |
| 339 ['get-empty', 5], |
| 340 ['count', 4], |
| 341 ['continue-empty', null], |
| 342 ['open', 1], |
| 343 ['continue', 2], |
| 344 ['open-empty', 5], |
| 345 ['getall', 4], |
| 346 ['open', 2], |
| 347 ['continue-empty', null], |
| 348 ['add', 5], |
| 349 ['get', 3], |
| 350 ['count', 5], |
| 351 ['get-empty', 6], |
| 352 ['put-with-id', 5], |
| 353 ['getall', 5], |
| 354 ['continue', 3], |
| 355 ['open-empty', 6], |
| 356 ['put', 6], |
| 357 ['error', 1], |
| 358 ['continue', 2], |
| 359 ['open', 4], |
| 360 ['get-empty', 7], |
| 361 ['count', 6], |
| 362 ['continue', 3], |
| 363 ['add', 7], |
| 364 ['getall', 7], |
| 365 ['error', 3], |
| 366 ['count', 7], |
| 367 ]); |
| 368 |
| 369 </script> |
OLD | NEW |