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) { |
| 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 function doOperation( |
| 41 testCase, store, index, cursor, operation, requestId, results) { |
| 42 const opcode = operation[0]; |
| 43 const primaryKey = operation[1]; |
| 44 |
| 45 return new Promise((resolve, reject) => { |
| 46 let request; |
| 47 switch (opcode) { |
| 48 case 'put': // Tests returning a primary key. |
| 49 request = store.put( |
| 50 { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); |
| 51 break; |
| 52 case 'get': // Tests returning a value. |
| 53 request = store.get(primaryKey); |
| 54 break; |
| 55 case 'error': // Tests returning an error. |
| 56 request = store.put( |
| 57 { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); |
| 58 request.onerror = testCase.step_func(event => { |
| 59 results.push([requestId, request.error]); |
| 60 resolve(); |
| 61 event.preventDefault(); |
| 62 }); |
| 63 request.onsuccess = testCase.step_func(() => { |
| 64 reject(new Error('put with duplicate primary key succeded')); |
| 65 }); |
| 66 break; |
| 67 case 'continue': // Tests returning a key, primary key, and value. |
| 68 request = cursor; |
| 69 cursor.result.continue(`k${primaryKey}`); |
| 70 request.onsuccess = testCase.step_func(() => { |
| 71 const result = request.result; |
| 72 results.push( |
| 73 [requestId, result.key, result.primaryKey, result.value]); |
| 74 resolve(); |
| 75 }); |
| 76 request.onerror = null; |
| 77 break; |
| 78 case 'open': // Tests returning a cursor, key, primary key, and value. |
| 79 request = index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey}`)); |
| 80 request.onsuccess = testCase.step_func(() => { |
| 81 const result = request.result; |
| 82 results.push( |
| 83 [requestId, result.key, result.primaryKey, result.value]); |
| 84 resolve(); |
| 85 }); |
| 86 break; |
| 87 }; |
| 88 |
| 89 if (!request.onsuccess) { |
| 90 request.onsuccess = testCase.step_func(() => { |
| 91 results.push([requestId, request.result]); |
| 92 resolve(); |
| 93 }); |
| 94 } |
| 95 if (!request.onerror) |
| 96 request.onerror = testCase.step_func(event => { |
| 97 reject(request.error); |
| 98 event.preventDefault(); |
| 99 }); |
| 100 }); |
| 101 } |
| 102 |
| 103 function checkOperationResult(operation, result, requestId) { |
| 104 const opcode = operation[0]; |
| 105 const primaryKey = operation[1]; |
| 106 |
| 107 const expectedValue = (primaryKey == 1 || primaryKey == 3) ? |
| 108 largeValue(wrapThreshold, primaryKey) : [`small-${primaryKey}`]; |
| 109 |
| 110 const requestIndex = result[0]; |
| 111 assert_equals( |
| 112 requestIndex, requestId, 'result event order should match request order'); |
| 113 switch (opcode) { |
| 114 case 'put': |
| 115 assert_equals( |
| 116 result[1], primaryKey, |
| 117 "put result should be the new object's primary key"); |
| 118 break; |
| 119 case 'get': |
| 120 assert_equals( |
| 121 result[1].id, primaryKey, |
| 122 'get result should match put value (primary key)'); |
| 123 assert_equals( |
| 124 result[1].key, `k${primaryKey}`, |
| 125 'get result should match put value (key)'); |
| 126 assert_equals( |
| 127 result[1].value.join(','), expectedValue.join(','), |
| 128 'get result should match put value (nested value)'); |
| 129 break; |
| 130 case 'error': |
| 131 assert_equals( |
| 132 result[1].name, 'ConstraintError', |
| 133 'incorrect error from put with duplicate primary key'); |
| 134 break; |
| 135 case 'continue': |
| 136 case 'open': |
| 137 assert_equals( |
| 138 result[1], `k${primaryKey}`, |
| 139 `${opcode} key should match the key in the put value`); |
| 140 assert_equals( |
| 141 result[2], primaryKey, |
| 142 `${opcode} primary key should the primary key in the put value`); |
| 143 assert_equals( |
| 144 result[3].id, primaryKey, |
| 145 `${opcode} value should match put value (primary key)`); |
| 146 assert_equals( |
| 147 result[3].key, `k${primaryKey}`, |
| 148 `${opcode} value should match put value (key)`); |
| 149 assert_equals( |
| 150 result[3].value.join(','), expectedValue.join(','), |
| 151 `${opcode} value should match put value (nested value)`); |
| 152 break; |
| 153 |
| 154 } |
| 155 } |
| 156 |
| 157 function eventsTest(label, operations) { |
| 158 promise_test(testCase => { |
| 159 return createDatabase(testCase, (database, transaction) => { |
| 160 const store = database.createObjectStore( |
| 161 'test-store', { autoIncrement: true, keyPath: 'id' }); |
| 162 store.createIndex('test-index', 'key', { unique: true }); |
| 163 populateStore(store); |
| 164 }).then(database => { |
| 165 const transaction = database.transaction(['test-store'], 'readwrite'); |
| 166 const store = transaction.objectStore('test-store'); |
| 167 const index = store.index('test-index'); |
| 168 const cursor = index.openCursor(); |
| 169 return new Promise((resolve, reject) => { |
| 170 const results = []; |
| 171 const promises = []; |
| 172 cursor.onsuccess = testCase.step_func(() => { |
| 173 for (let i = 0; i < operations.length; ++i) { |
| 174 const promise = doOperation( |
| 175 testCase, store, index, cursor, operations[i], i, results); |
| 176 promises.push(promise); |
| 177 }; |
| 178 resolve(Promise.all(promises).then(() => results)); |
| 179 }); |
| 180 cursor.onerror = () => { reject(cursor.error); }; |
| 181 }); |
| 182 }).then(results => { |
| 183 assert_equals( |
| 184 results.length, operations.length, |
| 185 'Promise.all should resolve after all sub-promises resolve'); |
| 186 for (let i = 0; i < operations.length; ++i) |
| 187 checkOperationResult(operations[i], results[i], i); |
| 188 }); |
| 189 }, label); |
| 190 } |
| 191 |
| 192 eventsTest('small values', [ |
| 193 ['get', 2], |
| 194 ['put', 5], |
| 195 ['open', 2], |
| 196 ['get', 4], |
| 197 ['put', 6], |
| 198 ['error', 3], |
| 199 ['continue', 2], |
| 200 ['open', 4], |
| 201 ]); |
| 202 |
| 203 eventsTest('large values', [ |
| 204 ['open', 1], |
| 205 ['get', 1], |
| 206 ['get', 3], |
| 207 ['continue', 3], |
| 208 ['open', 3], |
| 209 ]); |
| 210 |
| 211 eventsTest('large value followed by small values', [ |
| 212 ['get', 1], |
| 213 ['open', 2], |
| 214 ['get', 2], |
| 215 ['put', 5], |
| 216 ['error', 1], |
| 217 ['continue', 2], |
| 218 ]); |
| 219 |
| 220 eventsTest('large values mixed with small values', [ |
| 221 ['get', 1], |
| 222 ['get', 2], |
| 223 ['open', 1], |
| 224 ['open', 2], |
| 225 ['put', 5], |
| 226 ['get', 3], |
| 227 ['put', 6], |
| 228 ['error', 1], |
| 229 ['continue', 2], |
| 230 ['open', 4], |
| 231 ['error', 3], |
| 232 ]); |
| 233 |
| 234 </script> |
OLD | NEW |