Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 <!doctype html> | |
| 2 <meta charset="utf8"> | |
| 3 <title>IndexedDB: large nested objects are cloned correctly</title> | |
|
jsbell
2017/05/15 23:37:37
aside: I wonder if we should create a subdir for w
pwnall
2017/05/19 18:27:33
That sounds like a very reasonable proposal. So, w
| |
| 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> | |
|
jsbell
2017/05/15 23:37:37
Since this test involves multiple sequential trans
pwnall
2017/05/19 18:27:33
Done.
Both tests can probably use that tag. Thank
| |
| 10 'use strict'; | |
| 11 | |
| 12 // Should be large enough to trigger value wrapping in the IndexedDB engines | |
|
jsbell
2017/05/15 23:37:37
Maybe explain "wrapping" or say "that have special
pwnall
2017/05/19 18:27:33
Done.
| |
| 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 // Returns an IndexedDB value created from a descriptor. | |
| 34 // | |
| 35 // See the bottom of the file for descriptor samples. | |
| 36 function createValue(descriptor) { | |
| 37 if (typeof(descriptor) != 'object') | |
| 38 return descriptor; | |
| 39 | |
| 40 if (Array.isArray(descriptor)) | |
| 41 return descriptor.map((element) => createValue(element)); | |
| 42 | |
| 43 if (!descriptor.hasOwnProperty('type')) { | |
| 44 const value = {}; | |
| 45 for (let property of Object.getOwnPropertyNames(descriptor)) | |
| 46 value[property] = createValue(descriptor[property]); | |
| 47 return value; | |
| 48 } | |
| 49 | |
| 50 switch (descriptor.type) { | |
| 51 case 'blob': | |
| 52 return new Blob( | |
| 53 [largeValue(descriptor.size, descriptor.seed)], | |
| 54 { type: descriptor.type }); | |
| 55 case 'buffer': | |
| 56 return largeValue(descriptor.size, descriptor.seed); | |
| 57 } | |
| 58 } | |
| 59 | |
| 60 | |
| 61 // Checks an IndexedDB value against a descriptor. | |
| 62 // | |
| 63 // Returns a Promise that resolves if the value passes the check. | |
| 64 // | |
| 65 // See the bottom of the file for descriptor samples. | |
| 66 function checkValue(testCase, value, descriptor) { | |
| 67 if (typeof(descriptor) != 'object') { | |
| 68 assert_equals(descriptor, value, 'incorrect value'); | |
| 69 return Promise.resolve(); | |
| 70 } | |
| 71 | |
| 72 if (Array.isArray(descriptor)) { | |
| 73 assert_true(Array.isArray(value), 'incorrect value type'); | |
| 74 assert_equals(descriptor.length, value.length, 'incorrect array size'); | |
| 75 | |
| 76 const subChecks = []; | |
| 77 for (let i = 0; i < descriptor.length; ++i) | |
| 78 subChecks.push(checkValue(testCase, value[i], descriptor[i])); | |
| 79 return Promise.all(subChecks); | |
| 80 } | |
| 81 | |
| 82 if (!descriptor.hasOwnProperty('type')) { | |
| 83 assert_array_equals( | |
| 84 Object.getOwnPropertyNames(value).sort(), | |
| 85 Object.getOwnPropertyNames(descriptor).sort(), | |
| 86 'incorrect object properties'); | |
| 87 const subChecks = []; | |
| 88 for (let property of Object.getOwnPropertyNames(descriptor)) { | |
|
jsbell
2017/05/15 23:37:37
If you're feeling fancy, you could do this as:
re
pwnall
2017/05/19 18:27:33
Done.
| |
| 89 subChecks.push( | |
| 90 checkValue(testCase, value[property], descriptor[property])); | |
| 91 } | |
| 92 return Promise.all(subChecks); | |
| 93 } | |
| 94 | |
| 95 switch (descriptor.type) { | |
| 96 case 'blob': | |
| 97 assert_class_string(value, 'Blob', 'incorrect value type'); | |
|
jsbell
2017/05/15 23:37:37
nit: Word the message in terms of the *expected* o
pwnall
2017/05/19 18:27:33
Done.
| |
| 98 assert_equals(descriptor.type, value.type, 'incorrect Blob type'); | |
| 99 assert_equals(descriptor.size, value.size, 'incorrect Blob size'); | |
| 100 return new Promise((resolve, reject) => { | |
| 101 const reader = new FileReader(); | |
| 102 reader.onloadend = testCase.step_func(() => { | |
| 103 if (reader.error) { | |
| 104 reject(reader.error); | |
| 105 return; | |
| 106 } | |
| 107 const view = new Uint8Array(reader.result); | |
| 108 assert_equals( | |
| 109 view.join(','), | |
| 110 largeValue(descriptor.size, descriptor.seed).join(','), | |
| 111 'incorrect Blob content from IndexedDB'); | |
| 112 resolve(); | |
| 113 }); | |
| 114 reader.readAsArrayBuffer(value); | |
| 115 }); | |
| 116 | |
| 117 case 'buffer': | |
| 118 assert_class_string(value, 'Uint8Array', 'incorrect value type'); | |
| 119 assert_equals( | |
| 120 value.join(','), | |
| 121 largeValue(descriptor.size, descriptor.seed).join(','), | |
| 122 'incorrect typed array content'); | |
| 123 return Promise.resolve(); | |
| 124 } | |
| 125 } | |
| 126 | |
| 127 // Test that performs a series of put()s and verifies that gets() match. | |
| 128 // | |
| 129 // Each element of the valueDescriptors array is fed into createValue(), and the | |
| 130 // resulting value is written to IndexedDB via a put() request. After the writes | |
| 131 // complete, the values are read in the same order in which they were written. | |
| 132 // | |
| 133 // The test verifies that the get() values match the arguments to put() and that | |
|
jsbell
2017/05/15 23:37:37
This tests get() but not getAll()
pwnall
2017/05/19 18:27:33
Done.
Thanks, I don't know how I managed to miss t
| |
| 134 // the order in which the get() result events are fired matches the order of the | |
| 135 // get() requests. | |
| 136 function cloningTest(label, valueDescriptors) { | |
| 137 promise_test(testCase => { | |
| 138 return createDatabase(testCase, (database, transaction) => { | |
| 139 const store = database.createObjectStore('test-store', { keyPath: null }); | |
|
jsbell
2017/05/15 23:37:37
Why specify keyPath: null which is the default?
pwnall
2017/05/19 18:27:33
Done.
| |
| 140 for (let i = 0; i < valueDescriptors.length; ++i) { | |
| 141 store.put(createValue(valueDescriptors[i]), i); | |
| 142 } | |
| 143 }).then(database => { | |
| 144 const transaction = database.transaction(['test-store'], 'readonly'); | |
| 145 const store = transaction.objectStore('test-store'); | |
| 146 const subChecks = []; | |
| 147 let resultIndex = 0; | |
| 148 for (let i = 0; i < valueDescriptors.length; ++i) { | |
| 149 subChecks.push(new Promise((resolve, reject) => { | |
| 150 const requestIndex = i; | |
| 151 const request = store.get(requestIndex); | |
| 152 request.onerror = | |
| 153 testCase.step_func(() => { reject(request.error); }); | |
| 154 request.onsuccess = testCase.step_func(() => { | |
| 155 assert_equals( | |
| 156 resultIndex, requestIndex, | |
| 157 'IDBRequest success events should be fired in request order'); | |
| 158 ++resultIndex; | |
| 159 resolve(checkValue( | |
| 160 testCase, request.result, valueDescriptors[requestIndex])); | |
| 161 }); | |
| 162 })); | |
| 163 } | |
| 164 return Promise.all(subChecks); | |
| 165 }); | |
| 166 }, label); | |
| 167 } | |
| 168 | |
| 169 cloningTest('small typed array', [ | |
| 170 { type: 'buffer', size: 64, seed: 1 }, | |
| 171 ]); | |
| 172 | |
| 173 cloningTest('large typed array', [ | |
| 174 { type: 'buffer', size: wrapThreshold, seed: 1 }, | |
| 175 ]) | |
| 176 | |
| 177 cloningTest('blob', [ | |
| 178 { type: 'blob', size: wrapThreshold, seed: 1 }, | |
| 179 ]); | |
| 180 | |
| 181 cloningTest('blob with small typed array', [ | |
| 182 { | |
| 183 blob: { type: 'blob', size: wrapThreshold, seed: 1 }, | |
| 184 buffer: { type: 'buffer', size: 64, seed: 2 }, | |
| 185 }, | |
| 186 ]); | |
| 187 | |
| 188 cloningTest('blob with large typed array', [ | |
| 189 { | |
| 190 blob: { type: 'blob', size: wrapThreshold, seed: 1 }, | |
| 191 buffer: { type: 'buffer', size: wrapThreshold, seed: 2 }, | |
| 192 }, | |
| 193 ]); | |
| 194 | |
| 195 cloningTest('blob array', [ | |
| 196 [ | |
| 197 { type: 'blob', size: wrapThreshold, seed: 1 }, | |
| 198 { type: 'blob', size: wrapThreshold, seed: 2 }, | |
| 199 { type: 'blob', size: wrapThreshold, seed: 3 }, | |
| 200 ], | |
| 201 ]); | |
| 202 | |
| 203 cloningTest('array of blobs and small typed arrays', [ | |
| 204 [ | |
| 205 { type: 'blob', size: wrapThreshold, seed: 1 }, | |
| 206 { type: 'buffer', size: 64, seed: 2 }, | |
| 207 { type: 'blob', size: wrapThreshold, seed: 3 }, | |
| 208 { type: 'buffer', size: 64, seed: 4 }, | |
| 209 { type: 'blob', size: wrapThreshold, seed: 5 }, | |
| 210 ], | |
| 211 ]); | |
| 212 | |
| 213 cloningTest('array of blobs and large typed arrays', [ | |
| 214 [ | |
| 215 { type: 'blob', size: wrapThreshold, seed: 1 }, | |
| 216 { type: 'buffer', size: wrapThreshold, seed: 2 }, | |
| 217 { type: 'blob', size: wrapThreshold, seed: 3 }, | |
| 218 { type: 'buffer', size: wrapThreshold, seed: 4 }, | |
| 219 { type: 'blob', size: wrapThreshold, seed: 5 }, | |
| 220 ], | |
| 221 ]); | |
| 222 | |
| 223 cloningTest('object with blobs and large typed arrays', [ | |
| 224 { | |
| 225 blob: { type: 'blob', size: wrapThreshold, seed: 1 }, | |
| 226 more: [ | |
| 227 { type: 'buffer', size: wrapThreshold, seed: 2 }, | |
| 228 { type: 'blob', size: wrapThreshold, seed: 3 }, | |
| 229 { type: 'buffer', size: wrapThreshold, seed: 4 }, | |
| 230 ], | |
| 231 blob2: { type: 'blob', size: wrapThreshold, seed: 5 }, | |
| 232 }, | |
| 233 ]); | |
| 234 | |
| 235 cloningTest('multiple requests of objects with blobs and large typed arrays', [ | |
| 236 { | |
| 237 blob: { type: 'blob', size: wrapThreshold, seed: 1 }, | |
| 238 more: [ | |
| 239 { type: 'buffer', size: wrapThreshold, seed: 2 }, | |
| 240 { type: 'blob', size: wrapThreshold, seed: 3 }, | |
| 241 { type: 'buffer', size: wrapThreshold, seed: 4 }, | |
| 242 ], | |
| 243 blob2: { type: 'blob', size: wrapThreshold, seed: 5 }, | |
| 244 }, | |
| 245 [ | |
| 246 { type: 'blob', size: wrapThreshold, seed: 6 }, | |
| 247 { type: 'buffer', size: wrapThreshold, seed: 7 }, | |
| 248 { type: 'blob', size: wrapThreshold, seed: 8 }, | |
| 249 { type: 'buffer', size: wrapThreshold, seed: 9 }, | |
| 250 { type: 'blob', size: wrapThreshold, seed: 10 }, | |
| 251 ], | |
| 252 { | |
| 253 data: [ | |
| 254 { type: 'blob', size: wrapThreshold, seed: 11 }, | |
| 255 { type: 'buffer', size: wrapThreshold, seed: 12 }, | |
| 256 { type: 'blob', size: wrapThreshold, seed: 13 }, | |
| 257 { type: 'buffer', size: wrapThreshold, seed: 14 }, | |
| 258 { type: 'blob', size: wrapThreshold, seed: 15 }, | |
| 259 ], | |
| 260 }, | |
| 261 [ | |
| 262 { type: 'blob', size: wrapThreshold, seed: 16 }, | |
| 263 { type: 'buffer', size: wrapThreshold, seed: 17 }, | |
| 264 { type: 'blob', size: wrapThreshold, seed: 18 }, | |
| 265 { type: 'buffer', size: wrapThreshold, seed: 19 }, | |
| 266 { type: 'blob', size: wrapThreshold, seed: 20 }, | |
| 267 ], | |
| 268 ]); | |
| 269 | |
| 270 </script> | |
| OLD | NEW |