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