structured-clone.any.js (9001B)
1 // META: title=Indexed DB and Structured Serializing/Deserializing 2 // META: timeout=long 3 // META: script=resources/support-promises.js 4 // META: script=/common/subset-tests.js 5 // META: variant=?1-20 6 // META: variant=?21-40 7 // META: variant=?41-60 8 // META: variant=?61-80 9 // META: variant=?81-100 10 // META: variant=?101-last 11 'use strict'; 12 13 // Tests Indexed DB coverage of HTML's Safe "passing of structured data" 14 // https://html.spec.whatwg.org/multipage/structured-data.html 15 16 function describe(value) { 17 let type, str; 18 if (typeof value === 'object' && value) { 19 type = Object.getPrototypeOf(value).constructor.name; 20 // Handle Number(-0), etc. 21 str = Object.is(value.valueOf(), -0) ? '-0' : String(value); 22 } else { 23 type = typeof value; 24 // Handle primitive -0. 25 str = Object.is(value, -0) ? '-0' : String(value); 26 } 27 return `${type}: ${str}`; 28 } 29 30 function cloneTest(value, verifyFunc) { 31 subsetTest(promise_test, async t => { 32 const db = await createDatabase(t, db => { 33 const store = db.createObjectStore('store'); 34 // This index is not used, but evaluating key path on each put() 35 // call will exercise (de)serialization. 36 store.createIndex('index', 'dummyKeyPath'); 37 }); 38 t.add_cleanup(() => { 39 if (db) { 40 db.close(); 41 indexedDB.deleteDatabase(db.name); 42 } 43 }); 44 const tx = db.transaction('store', 'readwrite'); 45 const store = tx.objectStore('store'); 46 await promiseForRequest(t, store.put(value, 'key')); 47 const result = await promiseForRequest(t, store.get('key')); 48 // Because the async verifyFunc may await async values that are independent 49 // of the transaction lifetime (ex: blob.text()), we must only await it 50 // after adding listeners to the transaction. 51 await promiseForTransaction(t, tx); 52 await verifyFunc(value, result); 53 }, describe(value)); 54 } 55 56 // Specialization of cloneTest() for objects, with common asserts. 57 function cloneObjectTest(value, verifyFunc) { 58 cloneTest(value, async (orig, clone) => { 59 assert_not_equals(orig, clone); 60 assert_equals(typeof clone, 'object'); 61 assert_equals(Object.getPrototypeOf(orig), Object.getPrototypeOf(clone)); 62 await verifyFunc(orig, clone); 63 }); 64 } 65 66 function cloneFailureTest(value) { 67 subsetTest(promise_test, async t => { 68 const db = await createDatabase(t, db => { 69 db.createObjectStore('store'); 70 }); 71 t.add_cleanup(() => { 72 if (db) { 73 db.close(); 74 indexedDB.deleteDatabase(db.name); 75 } 76 }); 77 const tx = db.transaction('store', 'readwrite'); 78 const store = tx.objectStore('store'); 79 assert_throws_dom('DataCloneError', () => store.put(value, 'key')); 80 }, 'Not serializable: ' + describe(value)); 81 } 82 83 // 84 // ECMAScript types 85 // 86 87 // Primitive values: Undefined, Null, Boolean, Number, BigInt, String 88 const booleans = [false, true]; 89 const numbers = [ 90 NaN, 91 -Infinity, 92 -Number.MAX_VALUE, 93 -0xffffffff, 94 -0x80000000, 95 -0x7fffffff, 96 -1, 97 -Number.MIN_VALUE, 98 -0, 99 0, 100 1, 101 Number.MIN_VALUE, 102 0x7fffffff, 103 0x80000000, 104 0xffffffff, 105 Number.MAX_VALUE, 106 Infinity, 107 ]; 108 const bigints = [ 109 -12345678901234567890n, 110 -1n, 111 0n, 112 1n, 113 12345678901234567890n, 114 ]; 115 const strings = [ 116 '', 117 'this is a sample string', 118 'null(\0)', 119 ]; 120 121 [undefined, null].concat(booleans, numbers, bigints, strings) 122 .forEach(value => cloneTest(value, (orig, clone) => { 123 assert_equals(orig, clone); 124 })); 125 126 // "Primitive" Objects (Boolean, Number, BigInt, String) 127 [].concat(booleans, numbers, bigints, strings) 128 .forEach(value => cloneObjectTest(Object(value), (orig, clone) => { 129 assert_equals(orig.valueOf(), clone.valueOf()); 130 })); 131 132 // Dates 133 [ 134 new Date(-1e13), 135 new Date(-1e12), 136 new Date(-1e9), 137 new Date(-1e6), 138 new Date(-1e3), 139 new Date(0), 140 new Date(1e3), 141 new Date(1e6), 142 new Date(1e9), 143 new Date(1e12), 144 new Date(1e13) 145 ].forEach(value => cloneTest(value, (orig, clone) => { 146 assert_not_equals(orig, clone); 147 assert_equals(typeof clone, 'object'); 148 assert_equals(Object.getPrototypeOf(orig), Object.getPrototypeOf(clone)); 149 assert_equals(orig.valueOf(), clone.valueOf()); 150 })); 151 152 // Regular Expressions 153 [ 154 new RegExp(), 155 /abc/, 156 /abc/g, 157 /abc/i, 158 /abc/gi, 159 /abc/m, 160 /abc/mg, 161 /abc/mi, 162 /abc/mgi, 163 /abc/gimsuy, 164 ].forEach(value => cloneObjectTest(value, (orig, clone) => { 165 assert_equals(orig.toString(), clone.toString()); 166 })); 167 168 // ArrayBuffer 169 cloneObjectTest(new Uint8Array([0, 1, 254, 255]).buffer, (orig, clone) => { 170 assert_array_equals(new Uint8Array(orig), new Uint8Array(clone)); 171 }); 172 173 // TODO SharedArrayBuffer 174 175 // Array Buffer Views 176 let byteArrays = [ 177 new Uint8Array([]), 178 new Uint8Array([0, 1, 254, 255]), 179 new Uint16Array([0x0000, 0x0001, 0xFFFE, 0xFFFF]), 180 new Uint32Array([0x00000000, 0x00000001, 0xFFFFFFFE, 0xFFFFFFFF]), 181 new Int8Array([0, 1, 254, 255]), 182 new Int16Array([0x0000, 0x0001, 0xFFFE, 0xFFFF]), 183 new Int32Array([0x00000000, 0x00000001, 0xFFFFFFFE, 0xFFFFFFFF]), 184 new Uint8ClampedArray([0, 1, 254, 255]), 185 new Float32Array([-Infinity, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, Infinity, NaN]), 186 new Float64Array([-Infinity, -Number.MAX_VALUE, -Number.MIN_VALUE, 0, 187 Number.MIN_VALUE, Number.MAX_VALUE, Infinity, NaN]) 188 ] 189 190 if (typeof Float16Array !== 'undefined') { 191 byteArrays.push( 192 new Float16Array([-Infinity, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, Infinity, NaN])); 193 } 194 195 byteArrays.forEach(value => cloneObjectTest(value, (orig, clone) => { 196 assert_array_equals(orig, clone); 197 })); 198 199 // Map 200 cloneObjectTest(new Map([[1,2],[3,4]]), (orig, clone) => { 201 assert_array_equals([...orig.keys()], [...clone.keys()]); 202 assert_array_equals([...orig.values()], [...clone.values()]); 203 }); 204 205 // Set 206 cloneObjectTest(new Set([1,2,3,4]), (orig, clone) => { 207 assert_array_equals([...orig.values()], [...clone.values()]); 208 }); 209 210 // Error 211 [ 212 new Error(), 213 new Error('abc', 'def'), 214 new EvalError(), 215 new EvalError('ghi', 'jkl'), 216 new RangeError(), 217 new RangeError('ghi', 'jkl'), 218 new ReferenceError(), 219 new ReferenceError('ghi', 'jkl'), 220 new SyntaxError(), 221 new SyntaxError('ghi', 'jkl'), 222 new TypeError(), 223 new TypeError('ghi', 'jkl'), 224 new URIError(), 225 new URIError('ghi', 'jkl'), 226 ].forEach(value => cloneObjectTest(value, (orig, clone) => { 227 assert_equals(orig.name, clone.name); 228 assert_equals(orig.message, clone.message); 229 })); 230 231 // Arrays 232 [ 233 [], 234 [1,2,3], 235 Object.assign( 236 ['foo', 'bar'], 237 {10: true, 11: false, 20: 123, 21: 456, 30: null}), 238 Object.assign( 239 ['foo', 'bar'], 240 {a: true, b: false, foo: 123, bar: 456, '': null}), 241 ].forEach(value => cloneObjectTest(value, (orig, clone) => { 242 assert_array_equals(orig, clone); 243 assert_array_equals(Object.keys(orig), Object.keys(clone)); 244 Object.keys(orig).forEach(key => { 245 assert_equals(orig[key], clone[key], `Property ${key}`); 246 }); 247 })); 248 249 // Objects 250 cloneObjectTest({foo: true, bar: false}, (orig, clone) => { 251 assert_array_equals(Object.keys(orig), Object.keys(clone)); 252 Object.keys(orig).forEach(key => { 253 assert_equals(orig[key], clone[key], `Property ${key}`); 254 }); 255 }); 256 257 // 258 // [Serializable] Platform objects 259 // 260 261 // TODO: Test these additional interfaces: 262 // * DOMQuad 263 // * DOMException 264 // * RTCCertificate 265 266 // Geometry types 267 [ 268 new DOMMatrix(), 269 new DOMMatrixReadOnly(), 270 new DOMPoint(), 271 new DOMPointReadOnly(), 272 new DOMRect, 273 new DOMRectReadOnly(), 274 ].forEach(value => cloneObjectTest(value, (orig, clone) => { 275 Object.keys(Object.getPrototypeOf(orig)).forEach(key => { 276 assert_equals(orig[key], clone[key], `Property ${key}`); 277 }); 278 })); 279 280 // ImageData 281 const image_data = new ImageData(8, 8); 282 for (let i = 0; i < 256; ++i) { 283 image_data.data[i] = i; 284 } 285 cloneObjectTest(image_data, (orig, clone) => { 286 assert_equals(orig.width, clone.width); 287 assert_equals(orig.height, clone.height); 288 assert_array_equals(orig.data, clone.data); 289 }); 290 291 // Blob 292 cloneObjectTest( 293 new Blob(['This is a test.'], {type: 'a/b'}), 294 async (orig, clone) => { 295 assert_equals(orig.size, clone.size); 296 assert_equals(orig.type, clone.type); 297 assert_equals(await orig.text(), await clone.text()); 298 }); 299 300 // File 301 cloneObjectTest( 302 new File(['This is a test.'], 'foo.txt', {type: 'c/d'}), 303 async (orig, clone) => { 304 assert_equals(orig.size, clone.size); 305 assert_equals(orig.type, clone.type); 306 assert_equals(orig.name, clone.name); 307 assert_equals(orig.lastModified, clone.lastModified); 308 assert_equals(await orig.text(), await clone.text()); 309 }); 310 311 312 // FileList - exposed in Workers, but not constructable. 313 if ('document' in self) { 314 // TODO: Test with populated list. 315 cloneObjectTest( 316 Object.assign(document.createElement('input'), 317 {type: 'file', multiple: true}).files, 318 async (orig, clone) => { 319 assert_equals(orig.length, clone.length); 320 }); 321 } 322 323 // 324 // Non-serializable types 325 // 326 [ 327 // ECMAScript types 328 function() {}, 329 Symbol('desc'), 330 331 // Non-[Serializable] platform objects 332 self, 333 new Event(''), 334 new MessageChannel() 335 ].forEach(cloneFailureTest);