OLD | NEW |
(Empty) | |
| 1 <!doctype html> |
| 2 <meta charset="utf8"> |
| 3 <meta name="timeout" content="long"> |
| 4 <title>IndexedDB: large nested objects are cloned correctly</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 // Returns an IndexedDB value created from a descriptor. |
| 18 // |
| 19 // See the bottom of the file for descriptor samples. |
| 20 function createValue(descriptor) { |
| 21 if (typeof(descriptor) != 'object') |
| 22 return descriptor; |
| 23 |
| 24 if (Array.isArray(descriptor)) |
| 25 return descriptor.map((element) => createValue(element)); |
| 26 |
| 27 if (!descriptor.hasOwnProperty('type')) { |
| 28 const value = {}; |
| 29 for (let property of Object.getOwnPropertyNames(descriptor)) |
| 30 value[property] = createValue(descriptor[property]); |
| 31 return value; |
| 32 } |
| 33 |
| 34 switch (descriptor.type) { |
| 35 case 'blob': |
| 36 return new Blob( |
| 37 [largeValue(descriptor.size, descriptor.seed)], |
| 38 { type: descriptor.mimeType }); |
| 39 case 'buffer': |
| 40 return largeValue(descriptor.size, descriptor.seed); |
| 41 } |
| 42 } |
| 43 |
| 44 // Checks an IndexedDB value against a descriptor. |
| 45 // |
| 46 // Returns a Promise that resolves if the value passes the check. |
| 47 // |
| 48 // See the bottom of the file for descriptor samples. |
| 49 function checkValue(testCase, value, descriptor) { |
| 50 if (typeof(descriptor) != 'object') { |
| 51 assert_equals( |
| 52 descriptor, value, |
| 53 'IndexedDB result should match put() argument'); |
| 54 return Promise.resolve(); |
| 55 } |
| 56 |
| 57 if (Array.isArray(descriptor)) { |
| 58 assert_true( |
| 59 Array.isArray(value), |
| 60 'IndexedDB result type should match put() argument'); |
| 61 assert_equals( |
| 62 descriptor.length, value.length, |
| 63 'IndexedDB result array size should match put() argument'); |
| 64 |
| 65 const subChecks = []; |
| 66 for (let i = 0; i < descriptor.length; ++i) |
| 67 subChecks.push(checkValue(testCase, value[i], descriptor[i])); |
| 68 return Promise.all(subChecks); |
| 69 } |
| 70 |
| 71 if (!descriptor.hasOwnProperty('type')) { |
| 72 assert_array_equals( |
| 73 Object.getOwnPropertyNames(value).sort(), |
| 74 Object.getOwnPropertyNames(descriptor).sort(), |
| 75 'IndexedDB result object properties should match put() argument'); |
| 76 const subChecks = []; |
| 77 return Promise.all(Object.getOwnPropertyNames(descriptor).map(property => |
| 78 checkValue(testCase, value[property], descriptor[property]))); |
| 79 } |
| 80 |
| 81 switch (descriptor.type) { |
| 82 case 'blob': |
| 83 assert_class_string( |
| 84 value, 'Blob', |
| 85 'IndexedDB result class should match put() argument'); |
| 86 assert_equals( |
| 87 descriptor.mimeType, value.type, |
| 88 'IndexedDB result Blob MIME type should match put() argument'); |
| 89 assert_equals(descriptor.size, value.size, 'incorrect Blob size'); |
| 90 return new Promise((resolve, reject) => { |
| 91 const reader = new FileReader(); |
| 92 reader.onloadend = testCase.step_func(() => { |
| 93 if (reader.error) { |
| 94 reject(reader.error); |
| 95 return; |
| 96 } |
| 97 const view = new Uint8Array(reader.result); |
| 98 assert_equals( |
| 99 view.join(','), |
| 100 largeValue(descriptor.size, descriptor.seed).join(','), |
| 101 'IndexedDB result Blob content should match put() argument'); |
| 102 resolve(); |
| 103 }); |
| 104 reader.readAsArrayBuffer(value); |
| 105 }); |
| 106 |
| 107 case 'buffer': |
| 108 assert_class_string( |
| 109 value, 'Uint8Array', |
| 110 'IndexedDB result type should match put() argument'); |
| 111 assert_equals( |
| 112 value.join(','), |
| 113 largeValue(descriptor.size, descriptor.seed).join(','), |
| 114 'IndexedDB result typed array content should match put() argument'); |
| 115 return Promise.resolve(); |
| 116 } |
| 117 } |
| 118 |
| 119 // Performs a series of put()s and verifies that get()s and getAll() match. |
| 120 // |
| 121 // Each element of the valueDescriptors array is fed into createValue(), and the |
| 122 // resulting value is written to IndexedDB via a put() request. After the writes |
| 123 // complete, the values are read in the same order in which they were written. |
| 124 // Last, all the results are read one more time via a getAll(). |
| 125 // |
| 126 // The test verifies that the get() / getAll() results match the arguments to |
| 127 // put() and that the order in which the get() result events are fired matches |
| 128 // the order of the get() requests. |
| 129 function cloningTest(label, valueDescriptors) { |
| 130 promise_test(testCase => { |
| 131 return createDatabase(testCase, (database, transaction) => { |
| 132 const store = database.createObjectStore('test-store'); |
| 133 for (let i = 0; i < valueDescriptors.length; ++i) { |
| 134 store.put(createValue(valueDescriptors[i]), i); |
| 135 } |
| 136 }).then(database => { |
| 137 const transaction = database.transaction(['test-store'], 'readonly'); |
| 138 const store = transaction.objectStore('test-store'); |
| 139 const subChecks = []; |
| 140 let resultIndex = 0; |
| 141 for (let i = 0; i < valueDescriptors.length; ++i) { |
| 142 subChecks.push(new Promise((resolve, reject) => { |
| 143 const requestIndex = i; |
| 144 const request = store.get(requestIndex); |
| 145 request.onerror = |
| 146 testCase.step_func(() => { reject(request.error); }); |
| 147 request.onsuccess = testCase.step_func(() => { |
| 148 assert_equals( |
| 149 resultIndex, requestIndex, |
| 150 'IDBRequest success events should be fired in request order'); |
| 151 ++resultIndex; |
| 152 resolve(checkValue( |
| 153 testCase, request.result, valueDescriptors[requestIndex])); |
| 154 }); |
| 155 })); |
| 156 } |
| 157 |
| 158 subChecks.push(new Promise((resolve, reject) => { |
| 159 const requestIndex = valueDescriptors.length; |
| 160 const request = store.getAll(); |
| 161 request.onerror = |
| 162 testCase.step_func(() => { reject(request.error); }); |
| 163 request.onsuccess = testCase.step_func(() => { |
| 164 assert_equals( |
| 165 resultIndex, requestIndex, |
| 166 'IDBRequest success events should be fired in request order'); |
| 167 ++resultIndex; |
| 168 resolve(checkValue( |
| 169 testCase, request.result, valueDescriptors)); |
| 170 }); |
| 171 })); |
| 172 |
| 173 return Promise.all(subChecks); |
| 174 }); |
| 175 }, label); |
| 176 } |
| 177 |
| 178 cloningTest('small typed array', [ |
| 179 { type: 'buffer', size: 64, seed: 1 }, |
| 180 ]); |
| 181 |
| 182 cloningTest('large typed array', [ |
| 183 { type: 'buffer', size: wrapThreshold, seed: 1 }, |
| 184 ]) |
| 185 |
| 186 cloningTest('blob', [ |
| 187 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-1', seed: 1 }, |
| 188 ]); |
| 189 |
| 190 cloningTest('blob with small typed array', [ |
| 191 { |
| 192 blob: { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-01', |
| 193 seed: 1 }, |
| 194 buffer: { type: 'buffer', size: 64, seed: 2 }, |
| 195 }, |
| 196 ]); |
| 197 |
| 198 cloningTest('blob with large typed array', [ |
| 199 { |
| 200 blob: { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-01', |
| 201 seed: 1 }, |
| 202 buffer: { type: 'buffer', size: wrapThreshold, seed: 2 }, |
| 203 }, |
| 204 ]); |
| 205 |
| 206 cloningTest('blob array', [ |
| 207 [ |
| 208 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-1', seed: 1 }, |
| 209 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-2', seed: 2 }, |
| 210 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-3', seed: 3 }, |
| 211 ], |
| 212 ]); |
| 213 |
| 214 cloningTest('array of blobs and small typed arrays', [ |
| 215 [ |
| 216 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-01', seed: 1 }, |
| 217 { type: 'buffer', size: 64, seed: 2 }, |
| 218 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-03', seed: 3 }, |
| 219 { type: 'buffer', size: 64, seed: 4 }, |
| 220 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-05', seed: 5 }, |
| 221 ], |
| 222 ]); |
| 223 |
| 224 cloningTest('array of blobs and large typed arrays', [ |
| 225 [ |
| 226 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-01', seed: 1 }, |
| 227 { type: 'buffer', size: wrapThreshold, seed: 2 }, |
| 228 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-03', seed: 3 }, |
| 229 { type: 'buffer', size: wrapThreshold, seed: 4 }, |
| 230 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-05', seed: 5 }, |
| 231 ], |
| 232 ]); |
| 233 |
| 234 cloningTest('object with blobs and large typed arrays', [ |
| 235 { |
| 236 blob: { type: 'blob', size: wrapThreshold, |
| 237 mimeType: 'text/x-blink1', seed: 1 }, |
| 238 more: [ |
| 239 { type: 'buffer', size: wrapThreshold, seed: 2 }, |
| 240 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink3', seed: 3 }, |
| 241 { type: 'buffer', size: wrapThreshold, seed: 4 }, |
| 242 ], |
| 243 blob2: { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink5', |
| 244 seed: 5 }, |
| 245 }, |
| 246 ]); |
| 247 |
| 248 cloningTest('multiple requests of objects with blobs and large typed arrays', [ |
| 249 { |
| 250 blob: { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink1', |
| 251 seed: 1 }, |
| 252 more: [ |
| 253 { type: 'buffer', size: wrapThreshold, seed: 2 }, |
| 254 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink3', seed: 3 }, |
| 255 { type: 'buffer', size: wrapThreshold, seed: 4 }, |
| 256 ], |
| 257 blob2: { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink5', |
| 258 seed: 5 }, |
| 259 }, |
| 260 [ |
| 261 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink06', seed: 6 }, |
| 262 { type: 'buffer', size: wrapThreshold, seed: 7 }, |
| 263 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink08', seed: 8 }, |
| 264 { type: 'buffer', size: wrapThreshold, seed: 9 }, |
| 265 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink10', seed: 10 }, |
| 266 ], |
| 267 { |
| 268 data: [ |
| 269 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-11', |
| 270 seed: 11 }, |
| 271 { type: 'buffer', size: wrapThreshold, seed: 12 }, |
| 272 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-13', |
| 273 seed: 13 }, |
| 274 { type: 'buffer', size: wrapThreshold, seed: 14 }, |
| 275 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink-15', |
| 276 seed: 15 }, |
| 277 ], |
| 278 }, |
| 279 [ |
| 280 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink16', seed: 16 }, |
| 281 { type: 'buffer', size: wrapThreshold, seed: 17 }, |
| 282 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink18', seed: 18 }, |
| 283 { type: 'buffer', size: wrapThreshold, seed: 19 }, |
| 284 { type: 'blob', size: wrapThreshold, mimeType: 'text/x-blink20', seed: 20 }, |
| 285 ], |
| 286 ]); |
| 287 |
| 288 </script> |
OLD | NEW |