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 function cloningTest(label, descriptors) { | |
dmurph
2017/05/04 22:27:07
descriptors -> valueDescriptors
pwnall
2017/05/11 23:54:23
Done.
I also added a comment here that briefly exp
| |
128 promise_test(testCase => { | |
129 return createDatabase(testCase, (database, transaction) => { | |
130 const store = database.createObjectStore('test-store', { keyPath: null }); | |
131 for (let i = 0; i < descriptors.length; ++i) { | |
132 store.put(createValue(descriptors[i]), i); | |
133 } | |
134 }).then(database => { | |
135 const transaction = database.transaction(['test-store'], 'readonly'); | |
136 const store = transaction.objectStore('test-store'); | |
137 const subChecks = []; | |
138 let resultIndex = 0; | |
139 for (let i = 0; i < descriptors.length; ++i) { | |
140 subChecks.push(new Promise((resolve, reject) => { | |
141 const requestIndex = i; | |
142 const request = store.get(requestIndex); | |
143 request.onerror = | |
144 testCase.step_func(() => { reject(request.error); }); | |
145 request.onsuccess = testCase.step_func(() => { | |
146 assert_equals( | |
147 resultIndex, requestIndex, | |
148 'IDBRequest success events should be fired in request order'); | |
149 ++resultIndex; | |
150 resolve(checkValue( | |
151 testCase, request.result, descriptors[requestIndex])); | |
152 }); | |
153 })); | |
154 } | |
155 return Promise.all(subChecks); | |
156 }); | |
157 }, label); | |
158 } | |
159 | |
160 cloningTest('small typed array', [ | |
161 { type: 'buffer', size: 64, seed: 1 }, | |
162 ]); | |
163 | |
164 cloningTest('large typed array', [ | |
165 { type: 'buffer', size: wrapThreshold, seed: 1 }, | |
166 ]) | |
167 | |
168 cloningTest('blob', [ | |
169 { type: 'blob', size: wrapThreshold, seed: 1 }, | |
170 ]); | |
171 | |
172 cloningTest('blob with small typed array', [ | |
173 { | |
174 blob: { type: 'blob', size: wrapThreshold, seed: 1 }, | |
175 buffer: { type: 'buffer', size: 64, seed: 2 }, | |
176 }, | |
177 ]); | |
178 | |
179 cloningTest('blob with large typed array', [ | |
180 { | |
181 blob: { type: 'blob', size: wrapThreshold, seed: 1 }, | |
182 buffer: { type: 'buffer', size: wrapThreshold, seed: 2 }, | |
183 }, | |
184 ]); | |
185 | |
186 cloningTest('blob array', [ | |
187 [ | |
188 { type: 'blob', size: wrapThreshold, seed: 1 }, | |
189 { type: 'blob', size: wrapThreshold, seed: 2 }, | |
190 { type: 'blob', size: wrapThreshold, seed: 3 }, | |
191 ], | |
192 ]); | |
193 | |
194 cloningTest('array of blobs and small typed arrays', [ | |
195 [ | |
196 { type: 'blob', size: wrapThreshold, seed: 1 }, | |
197 { type: 'buffer', size: 64, seed: 2 }, | |
198 { type: 'blob', size: wrapThreshold, seed: 3 }, | |
199 { type: 'buffer', size: 64, seed: 4 }, | |
200 { type: 'blob', size: wrapThreshold, seed: 5 }, | |
201 ], | |
202 ]); | |
203 | |
204 cloningTest('array of blobs and large typed arrays', [ | |
205 [ | |
206 { type: 'blob', size: wrapThreshold, seed: 1 }, | |
207 { type: 'buffer', size: wrapThreshold, seed: 2 }, | |
208 { type: 'blob', size: wrapThreshold, seed: 3 }, | |
209 { type: 'buffer', size: wrapThreshold, seed: 4 }, | |
210 { type: 'blob', size: wrapThreshold, seed: 5 }, | |
211 ], | |
212 ]); | |
213 | |
214 cloningTest('object with blobs and large typed arrays', [ | |
215 { | |
216 blob: { type: 'blob', size: wrapThreshold, seed: 1 }, | |
217 more: [ | |
218 { type: 'buffer', size: wrapThreshold, seed: 2 }, | |
219 { type: 'blob', size: wrapThreshold, seed: 3 }, | |
220 { type: 'buffer', size: wrapThreshold, seed: 4 }, | |
221 ], | |
222 blob2: { type: 'blob', size: wrapThreshold, seed: 5 }, | |
223 }, | |
224 ]); | |
225 | |
226 cloningTest('multiple requests of objects with blobs and large typed arrays', [ | |
227 { | |
228 blob: { type: 'blob', size: wrapThreshold, seed: 1 }, | |
229 more: [ | |
230 { type: 'buffer', size: wrapThreshold, seed: 2 }, | |
231 { type: 'blob', size: wrapThreshold, seed: 3 }, | |
232 { type: 'buffer', size: wrapThreshold, seed: 4 }, | |
233 ], | |
234 blob2: { type: 'blob', size: wrapThreshold, seed: 5 }, | |
235 }, | |
236 [ | |
237 { type: 'blob', size: wrapThreshold, seed: 6 }, | |
238 { type: 'buffer', size: wrapThreshold, seed: 7 }, | |
239 { type: 'blob', size: wrapThreshold, seed: 8 }, | |
240 { type: 'buffer', size: wrapThreshold, seed: 9 }, | |
241 { type: 'blob', size: wrapThreshold, seed: 10 }, | |
242 ], | |
243 { | |
244 data: [ | |
245 { type: 'blob', size: wrapThreshold, seed: 11 }, | |
246 { type: 'buffer', size: wrapThreshold, seed: 12 }, | |
247 { type: 'blob', size: wrapThreshold, seed: 13 }, | |
248 { type: 'buffer', size: wrapThreshold, seed: 14 }, | |
249 { type: 'blob', size: wrapThreshold, seed: 15 }, | |
250 ], | |
251 }, | |
252 [ | |
253 { type: 'blob', size: wrapThreshold, seed: 16 }, | |
254 { type: 'buffer', size: wrapThreshold, seed: 17 }, | |
255 { type: 'blob', size: wrapThreshold, seed: 18 }, | |
256 { type: 'buffer', size: wrapThreshold, seed: 19 }, | |
257 { type: 'blob', size: wrapThreshold, seed: 20 }, | |
258 ], | |
259 ]); | |
260 | |
261 </script> | |
OLD | NEW |