OLD | NEW |
(Empty) | |
| 1 <!doctype html> |
| 2 <meta charset="utf8"> |
| 3 <title>IndexedDB: large nested objects are cloned correctly</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 // 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)) { |
| 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'); |
| 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 |
| 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 }); |
| 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 |