structured-clone-battery-of-tests.js (31726B)
1 /* This file is mostly a remix of @zcorpan’s web worker test suite */ 2 3 structuredCloneBatteryOfTests = []; 4 5 function check(description, input, callback, requiresDocument = false) { 6 structuredCloneBatteryOfTests.push({ 7 description, 8 async f(runner) { 9 let newInput = input; 10 if (typeof input === 'function') { 11 newInput = input(); 12 } 13 const copy = await runner.structuredClone(newInput); 14 await callback(copy, newInput); 15 }, 16 requiresDocument 17 }); 18 } 19 20 function compare_primitive(actual, input) { 21 assert_equals(actual, input); 22 } 23 function compare_Array(callback) { 24 return async function(actual, input) { 25 if (typeof actual === 'string') 26 assert_unreached(actual); 27 assert_true(actual instanceof Array, 'instanceof Array'); 28 assert_not_equals(actual, input); 29 assert_equals(actual.length, input.length, 'length'); 30 await callback(actual, input); 31 } 32 } 33 34 function compare_Object(callback) { 35 return async function(actual, input) { 36 if (typeof actual === 'string') 37 assert_unreached(actual); 38 assert_true(actual instanceof Object, 'instanceof Object'); 39 assert_false(actual instanceof Array, 'instanceof Array'); 40 assert_not_equals(actual, input); 41 await callback(actual, input); 42 } 43 } 44 45 function enumerate_props(compare_func) { 46 return async function(actual, input) { 47 for (const x in input) { 48 await compare_func(actual[x], input[x]); 49 } 50 }; 51 } 52 53 check('primitive undefined', undefined, compare_primitive); 54 check('primitive null', null, compare_primitive); 55 check('primitive true', true, compare_primitive); 56 check('primitive false', false, compare_primitive); 57 check('primitive string, empty string', '', compare_primitive); 58 check('primitive string, lone high surrogate', '\uD800', compare_primitive); 59 check('primitive string, lone low surrogate', '\uDC00', compare_primitive); 60 check('primitive string, NUL', '\u0000', compare_primitive); 61 check('primitive string, astral character', '\uDBFF\uDFFD', compare_primitive); 62 check('primitive number, 0.2', 0.2, compare_primitive); 63 check('primitive number, 0', 0, compare_primitive); 64 check('primitive number, -0', -0, compare_primitive); 65 check('primitive number, NaN', NaN, compare_primitive); 66 check('primitive number, Infinity', Infinity, compare_primitive); 67 check('primitive number, -Infinity', -Infinity, compare_primitive); 68 check('primitive number, 9007199254740992', 9007199254740992, compare_primitive); 69 check('primitive number, -9007199254740992', -9007199254740992, compare_primitive); 70 check('primitive number, 9007199254740994', 9007199254740994, compare_primitive); 71 check('primitive number, -9007199254740994', -9007199254740994, compare_primitive); 72 check('primitive BigInt, 0n', 0n, compare_primitive); 73 check('primitive BigInt, -0n', -0n, compare_primitive); 74 check('primitive BigInt, -9007199254740994000n', -9007199254740994000n, compare_primitive); 75 check('primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n', -9007199254740994000900719925474099400090071992547409940009007199254740994000n, compare_primitive); 76 77 check('Array primitives', [undefined, 78 null, 79 true, 80 false, 81 '', 82 '\uD800', 83 '\uDC00', 84 '\u0000', 85 '\uDBFF\uDFFD', 86 0.2, 87 0, 88 -0, 89 NaN, 90 Infinity, 91 -Infinity, 92 9007199254740992, 93 -9007199254740992, 94 9007199254740994, 95 -9007199254740994, 96 -12n, 97 -0n, 98 0n], compare_Array(enumerate_props(compare_primitive))); 99 check('Object primitives', {'undefined':undefined, 100 'null':null, 101 'true':true, 102 'false':false, 103 'empty':'', 104 'high surrogate':'\uD800', 105 'low surrogate':'\uDC00', 106 'nul':'\u0000', 107 'astral':'\uDBFF\uDFFD', 108 '0.2':0.2, 109 '0':0, 110 '-0':-0, 111 'NaN':NaN, 112 'Infinity':Infinity, 113 '-Infinity':-Infinity, 114 '9007199254740992':9007199254740992, 115 '-9007199254740992':-9007199254740992, 116 '9007199254740994':9007199254740994, 117 '-9007199254740994':-9007199254740994}, compare_Object(enumerate_props(compare_primitive))); 118 119 function compare_Boolean(actual, input) { 120 if (typeof actual === 'string') 121 assert_unreached(actual); 122 assert_true(actual instanceof Boolean, 'instanceof Boolean'); 123 assert_equals(String(actual), String(input), 'converted to primitive'); 124 assert_not_equals(actual, input); 125 } 126 check('Boolean true', new Boolean(true), compare_Boolean); 127 check('Boolean false', new Boolean(false), compare_Boolean); 128 check('Array Boolean objects', [new Boolean(true), new Boolean(false)], compare_Array(enumerate_props(compare_Boolean))); 129 check('Object Boolean objects', {'true':new Boolean(true), 'false':new Boolean(false)}, compare_Object(enumerate_props(compare_Boolean))); 130 131 function compare_obj(what) { 132 const Type = self[what]; 133 return function(actual, input) { 134 if (typeof actual === 'string') 135 assert_unreached(actual); 136 assert_true(actual instanceof Type, 'instanceof '+what); 137 assert_equals(Type(actual), Type(input), 'converted to primitive'); 138 assert_not_equals(actual, input); 139 }; 140 } 141 check('String empty string', new String(''), compare_obj('String')); 142 check('String lone high surrogate', new String('\uD800'), compare_obj('String')); 143 check('String lone low surrogate', new String('\uDC00'), compare_obj('String')); 144 check('String NUL', new String('\u0000'), compare_obj('String')); 145 check('String astral character', new String('\uDBFF\uDFFD'), compare_obj('String')); 146 check('Array String objects', [new String(''), 147 new String('\uD800'), 148 new String('\uDC00'), 149 new String('\u0000'), 150 new String('\uDBFF\uDFFD')], compare_Array(enumerate_props(compare_obj('String')))); 151 check('Object String objects', {'empty':new String(''), 152 'high surrogate':new String('\uD800'), 153 'low surrogate':new String('\uDC00'), 154 'nul':new String('\u0000'), 155 'astral':new String('\uDBFF\uDFFD')}, compare_Object(enumerate_props(compare_obj('String')))); 156 157 check('Number 0.2', new Number(0.2), compare_obj('Number')); 158 check('Number 0', new Number(0), compare_obj('Number')); 159 check('Number -0', new Number(-0), compare_obj('Number')); 160 check('Number NaN', new Number(NaN), compare_obj('Number')); 161 check('Number Infinity', new Number(Infinity), compare_obj('Number')); 162 check('Number -Infinity', new Number(-Infinity), compare_obj('Number')); 163 check('Number 9007199254740992', new Number(9007199254740992), compare_obj('Number')); 164 check('Number -9007199254740992', new Number(-9007199254740992), compare_obj('Number')); 165 check('Number 9007199254740994', new Number(9007199254740994), compare_obj('Number')); 166 check('Number -9007199254740994', new Number(-9007199254740994), compare_obj('Number')); 167 // BigInt does not have a non-throwing constructor 168 check('BigInt -9007199254740994n', Object(-9007199254740994n), compare_obj('BigInt')); 169 170 check('Array Number objects', [new Number(0.2), 171 new Number(0), 172 new Number(-0), 173 new Number(NaN), 174 new Number(Infinity), 175 new Number(-Infinity), 176 new Number(9007199254740992), 177 new Number(-9007199254740992), 178 new Number(9007199254740994), 179 new Number(-9007199254740994)], compare_Array(enumerate_props(compare_obj('Number')))); 180 check('Object Number objects', {'0.2':new Number(0.2), 181 '0':new Number(0), 182 '-0':new Number(-0), 183 'NaN':new Number(NaN), 184 'Infinity':new Number(Infinity), 185 '-Infinity':new Number(-Infinity), 186 '9007199254740992':new Number(9007199254740992), 187 '-9007199254740992':new Number(-9007199254740992), 188 '9007199254740994':new Number(9007199254740994), 189 '-9007199254740994':new Number(-9007199254740994)}, compare_Object(enumerate_props(compare_obj('Number')))); 190 191 function compare_Date(actual, input) { 192 if (typeof actual === 'string') 193 assert_unreached(actual); 194 assert_true(actual instanceof Date, 'instanceof Date'); 195 assert_equals(Number(actual), Number(input), 'converted to primitive'); 196 assert_not_equals(actual, input); 197 } 198 check('Date 0', new Date(0), compare_Date); 199 check('Date -0', new Date(-0), compare_Date); 200 check('Date -8.64e15', new Date(-8.64e15), compare_Date); 201 check('Date 8.64e15', new Date(8.64e15), compare_Date); 202 check('Array Date objects', [new Date(0), 203 new Date(-0), 204 new Date(-8.64e15), 205 new Date(8.64e15)], compare_Array(enumerate_props(compare_Date))); 206 check('Object Date objects', {'0':new Date(0), 207 '-0':new Date(-0), 208 '-8.64e15':new Date(-8.64e15), 209 '8.64e15':new Date(8.64e15)}, compare_Object(enumerate_props(compare_Date))); 210 211 function compare_RegExp(expected_source) { 212 // XXX ES6 spec doesn't define exact serialization for `source` (it allows several ways to escape) 213 return function(actual, input) { 214 if (typeof actual === 'string') 215 assert_unreached(actual); 216 assert_true(actual instanceof RegExp, 'instanceof RegExp'); 217 assert_equals(actual.global, input.global, 'global'); 218 assert_equals(actual.ignoreCase, input.ignoreCase, 'ignoreCase'); 219 assert_equals(actual.multiline, input.multiline, 'multiline'); 220 assert_equals(actual.source, expected_source, 'source'); 221 assert_equals(actual.sticky, input.sticky, 'sticky'); 222 assert_equals(actual.unicode, input.unicode, 'unicode'); 223 assert_equals(actual.lastIndex, 0, 'lastIndex'); 224 assert_not_equals(actual, input); 225 } 226 } 227 function func_RegExp_flags_lastIndex() { 228 const r = /foo/gim; 229 r.lastIndex = 2; 230 return r; 231 } 232 function func_RegExp_sticky() { 233 return new RegExp('foo', 'y'); 234 } 235 function func_RegExp_unicode() { 236 return new RegExp('foo', 'u'); 237 } 238 check('RegExp flags and lastIndex', func_RegExp_flags_lastIndex, compare_RegExp('foo')); 239 check('RegExp sticky flag', func_RegExp_sticky, compare_RegExp('foo')); 240 check('RegExp unicode flag', func_RegExp_unicode, compare_RegExp('foo')); 241 check('RegExp empty', new RegExp(''), compare_RegExp('(?:)')); 242 check('RegExp slash', new RegExp('/'), compare_RegExp('\\/')); 243 check('RegExp new line', new RegExp('\n'), compare_RegExp('\\n')); 244 check('Array RegExp object, RegExp flags and lastIndex', [func_RegExp_flags_lastIndex()], compare_Array(enumerate_props(compare_RegExp('foo')))); 245 check('Array RegExp object, RegExp sticky flag', function() { return [func_RegExp_sticky()]; }, compare_Array(enumerate_props(compare_RegExp('foo')))); 246 check('Array RegExp object, RegExp unicode flag', function() { return [func_RegExp_unicode()]; }, compare_Array(enumerate_props(compare_RegExp('foo')))); 247 check('Array RegExp object, RegExp empty', [new RegExp('')], compare_Array(enumerate_props(compare_RegExp('(?:)')))); 248 check('Array RegExp object, RegExp slash', [new RegExp('/')], compare_Array(enumerate_props(compare_RegExp('\\/')))); 249 check('Array RegExp object, RegExp new line', [new RegExp('\n')], compare_Array(enumerate_props(compare_RegExp('\\n')))); 250 check('Object RegExp object, RegExp flags and lastIndex', {'x':func_RegExp_flags_lastIndex()}, compare_Object(enumerate_props(compare_RegExp('foo')))); 251 check('Object RegExp object, RegExp sticky flag', function() { return {'x':func_RegExp_sticky()}; }, compare_Object(enumerate_props(compare_RegExp('foo')))); 252 check('Object RegExp object, RegExp unicode flag', function() { return {'x':func_RegExp_unicode()}; }, compare_Object(enumerate_props(compare_RegExp('foo')))); 253 check('Object RegExp object, RegExp empty', {'x':new RegExp('')}, compare_Object(enumerate_props(compare_RegExp('(?:)')))); 254 check('Object RegExp object, RegExp slash', {'x':new RegExp('/')}, compare_Object(enumerate_props(compare_RegExp('\\/')))); 255 check('Object RegExp object, RegExp new line', {'x':new RegExp('\n')}, compare_Object(enumerate_props(compare_RegExp('\\n')))); 256 257 function compare_Error(actual, input) { 258 assert_true(actual instanceof Error, "Checking instanceof"); 259 assert_equals(actual.constructor, input.constructor, "Checking constructor"); 260 assert_equals(actual.name, input.name, "Checking name"); 261 assert_equals(actual.hasOwnProperty("message"), input.hasOwnProperty("message"), "Checking message existence"); 262 assert_equals(actual.message, input.message, "Checking message"); 263 assert_equals(actual.cause, input.cause, "Checking cause"); 264 assert_equals(actual.foo, undefined, "Checking for absence of custom property"); 265 } 266 267 check('Empty Error object', new Error, compare_Error); 268 269 const errorConstructors = [Error, EvalError, RangeError, ReferenceError, 270 SyntaxError, TypeError, URIError]; 271 for (const constructor of errorConstructors) { 272 check(`${constructor.name} object`, () => { 273 let error = new constructor("Error message here", { cause: "my cause" }); 274 error.foo = "testing"; 275 return error; 276 }, compare_Error); 277 } 278 279 async function compare_Blob(actual, input, expect_File) { 280 if (typeof actual === 'string') 281 assert_unreached(actual); 282 assert_true(actual instanceof Blob, 'instanceof Blob'); 283 if (!expect_File) 284 assert_false(actual instanceof File, 'instanceof File'); 285 assert_equals(actual.size, input.size, 'size'); 286 assert_equals(actual.type, input.type, 'type'); 287 assert_not_equals(actual, input); 288 const ab1 = await new Response(actual).arrayBuffer(); 289 const ab2 = await new Response(input).arrayBuffer(); 290 assert_equals(ab1.byteLength, ab2.byteLength, 'byteLength'); 291 const ta1 = new Uint8Array(ab1); 292 const ta2 = new Uint8Array(ab2); 293 for(let i = 0; i < ta1.size; i++) { 294 assert_equals(ta1[i], ta2[i]); 295 } 296 } 297 function func_Blob_basic() { 298 return new Blob(['foo'], {type:'text/x-bar'}); 299 } 300 check('Blob basic', func_Blob_basic, compare_Blob); 301 302 function b(str) { 303 return parseInt(str, 2); 304 } 305 function encode_cesu8(codeunits) { 306 // http://www.unicode.org/reports/tr26/ section 2.2 307 // only the 3-byte form is supported 308 const rv = []; 309 codeunits.forEach(function(codeunit) { 310 rv.push(b('11100000') + ((codeunit & b('1111000000000000')) >> 12)); 311 rv.push(b('10000000') + ((codeunit & b('0000111111000000')) >> 6)); 312 rv.push(b('10000000') + (codeunit & b('0000000000111111'))); 313 }); 314 return rv; 315 } 316 function func_Blob_bytes(arr) { 317 return function() { 318 const buffer = new ArrayBuffer(arr.length); 319 const view = new DataView(buffer); 320 for (let i = 0; i < arr.length; ++i) { 321 view.setUint8(i, arr[i]); 322 } 323 return new Blob([view]); 324 }; 325 } 326 check('Blob unpaired high surrogate (invalid utf-8)', func_Blob_bytes(encode_cesu8([0xD800])), compare_Blob); 327 check('Blob unpaired low surrogate (invalid utf-8)', func_Blob_bytes(encode_cesu8([0xDC00])), compare_Blob); 328 check('Blob paired surrogates (invalid utf-8)', func_Blob_bytes(encode_cesu8([0xD800, 0xDC00])), compare_Blob); 329 330 function func_Blob_empty() { 331 return new Blob(['']); 332 } 333 check('Blob empty', func_Blob_empty , compare_Blob); 334 function func_Blob_NUL() { 335 return new Blob(['\u0000']); 336 } 337 check('Blob NUL', func_Blob_NUL, compare_Blob); 338 339 check('Array Blob object, Blob basic', [func_Blob_basic()], compare_Array(enumerate_props(compare_Blob))); 340 check('Array Blob object, Blob unpaired high surrogate (invalid utf-8)', [func_Blob_bytes([0xD800])()], compare_Array(enumerate_props(compare_Blob))); 341 check('Array Blob object, Blob unpaired low surrogate (invalid utf-8)', [func_Blob_bytes([0xDC00])()], compare_Array(enumerate_props(compare_Blob))); 342 check('Array Blob object, Blob paired surrogates (invalid utf-8)', [func_Blob_bytes([0xD800, 0xDC00])()], compare_Array(enumerate_props(compare_Blob))); 343 check('Array Blob object, Blob empty', [func_Blob_empty()], compare_Array(enumerate_props(compare_Blob))); 344 check('Array Blob object, Blob NUL', [func_Blob_NUL()], compare_Array(enumerate_props(compare_Blob))); 345 check('Array Blob object, two Blobs', [func_Blob_basic(), func_Blob_empty()], compare_Array(enumerate_props(compare_Blob))); 346 347 check('Object Blob object, Blob basic', {'x':func_Blob_basic()}, compare_Object(enumerate_props(compare_Blob))); 348 check('Object Blob object, Blob unpaired high surrogate (invalid utf-8)', {'x':func_Blob_bytes([0xD800])()}, compare_Object(enumerate_props(compare_Blob))); 349 check('Object Blob object, Blob unpaired low surrogate (invalid utf-8)', {'x':func_Blob_bytes([0xDC00])()}, compare_Object(enumerate_props(compare_Blob))); 350 check('Object Blob object, Blob paired surrogates (invalid utf-8)', {'x':func_Blob_bytes([0xD800, 0xDC00])() }, compare_Object(enumerate_props(compare_Blob))); 351 check('Object Blob object, Blob empty', {'x':func_Blob_empty()}, compare_Object(enumerate_props(compare_Blob))); 352 check('Object Blob object, Blob NUL', {'x':func_Blob_NUL()}, compare_Object(enumerate_props(compare_Blob))); 353 354 async function compare_File(actual, input) { 355 assert_true(actual instanceof File, 'instanceof File'); 356 assert_equals(actual.name, input.name, 'name'); 357 assert_equals(actual.lastModified, input.lastModified, 'lastModified'); 358 await compare_Blob(actual, input, true); 359 } 360 function func_File_basic() { 361 return new File(['foo'], 'bar', {type:'text/x-bar', lastModified:42}); 362 } 363 check('File basic', func_File_basic, compare_File); 364 365 function compare_FileList(actual, input) { 366 if (typeof actual === 'string') 367 assert_unreached(actual); 368 assert_true(actual instanceof FileList, 'instanceof FileList'); 369 assert_equals(actual.length, input.length, 'length'); 370 assert_not_equals(actual, input); 371 // XXX when there's a way to populate or construct a FileList, 372 // check the items in the FileList 373 } 374 function func_FileList_empty() { 375 const input = document.createElement('input'); 376 input.type = 'file'; 377 return input.files; 378 } 379 check('FileList empty', func_FileList_empty, compare_FileList, true); 380 check('Array FileList object, FileList empty', () => ([func_FileList_empty()]), compare_Array(enumerate_props(compare_FileList)), true); 381 check('Object FileList object, FileList empty', () => ({'x':func_FileList_empty()}), compare_Object(enumerate_props(compare_FileList)), true); 382 383 function compare_ArrayBuffer(actual, input) { 384 assert_true(actual instanceof ArrayBuffer, 'instanceof ArrayBuffer'); 385 assert_equals(actual.byteLength, input.byteLength, 'byteLength'); 386 assert_equals(actual.maxByteLength, input.maxByteLength, 'maxByteLength'); 387 assert_equals(actual.resizable, input.resizable, 'resizable'); 388 assert_equals(actual.growable, input.growable, 'growable'); 389 } 390 391 function compare_ArrayBufferView(view) { 392 const Type = self[view]; 393 return function(actual, input) { 394 if (typeof actual === 'string') 395 assert_unreached(actual); 396 assert_true(actual instanceof Type, 'instanceof '+view); 397 assert_equals(actual.length, input.length, 'length'); 398 assert_equals(actual.byteLength, input.byteLength, 'byteLength'); 399 assert_equals(actual.byteOffset, input.byteOffset, 'byteOffset'); 400 assert_not_equals(actual.buffer, input.buffer, 'buffer'); 401 for (let i = 0; i < actual.length; ++i) { 402 assert_equals(actual[i], input[i], 'actual['+i+']'); 403 } 404 }; 405 } 406 function compare_ImageData(actual, input) { 407 if (typeof actual === 'string') 408 assert_unreached(actual); 409 assert_equals(actual.width, input.width, 'width'); 410 assert_equals(actual.height, input.height, 'height'); 411 assert_not_equals(actual.data, input.data, 'data'); 412 compare_ArrayBufferView('Uint8ClampedArray')(actual.data, input.data, null); 413 } 414 function func_ImageData_1x1_transparent_black() { 415 const canvas = document.createElement('canvas'); 416 const ctx = canvas.getContext('2d'); 417 return ctx.createImageData(1, 1); 418 } 419 check('ImageData 1x1 transparent black', func_ImageData_1x1_transparent_black, compare_ImageData, true); 420 function func_ImageData_1x1_non_transparent_non_black() { 421 const canvas = document.createElement('canvas'); 422 const ctx = canvas.getContext('2d'); 423 const imagedata = ctx.createImageData(1, 1); 424 imagedata.data[0] = 100; 425 imagedata.data[1] = 101; 426 imagedata.data[2] = 102; 427 imagedata.data[3] = 103; 428 return imagedata; 429 } 430 check('ImageData 1x1 non-transparent non-black', func_ImageData_1x1_non_transparent_non_black, compare_ImageData, true); 431 check('Array ImageData object, ImageData 1x1 transparent black', () => ([func_ImageData_1x1_transparent_black()]), compare_Array(enumerate_props(compare_ImageData)), true); 432 check('Array ImageData object, ImageData 1x1 non-transparent non-black', () => ([func_ImageData_1x1_non_transparent_non_black()]), compare_Array(enumerate_props(compare_ImageData)), true); 433 check('Object ImageData object, ImageData 1x1 transparent black', () => ({'x':func_ImageData_1x1_transparent_black()}), compare_Object(enumerate_props(compare_ImageData)), true); 434 check('Object ImageData object, ImageData 1x1 non-transparent non-black', () => ({'x':func_ImageData_1x1_non_transparent_non_black()}), compare_Object(enumerate_props(compare_ImageData)), true); 435 436 437 check('Array sparse', new Array(10), compare_Array(enumerate_props(compare_primitive))); 438 check('Array with non-index property', function() { 439 const rv = []; 440 rv.foo = 'bar'; 441 return rv; 442 }, compare_Array(enumerate_props(compare_primitive))); 443 check('Object with index property and length', {'0':'foo', 'length':1}, compare_Object(enumerate_props(compare_primitive))); 444 function check_circular_property(prop) { 445 return function(actual) { 446 assert_equals(actual[prop], actual); 447 }; 448 } 449 check('Array with circular reference', function() { 450 const rv = []; 451 rv[0] = rv; 452 return rv; 453 }, compare_Array(check_circular_property('0'))); 454 check('Object with circular reference', function() { 455 const rv = {}; 456 rv['x'] = rv; 457 return rv; 458 }, compare_Object(check_circular_property('x'))); 459 function check_identical_property_values(prop1, prop2) { 460 return function(actual) { 461 assert_equals(actual[prop1], actual[prop2]); 462 }; 463 } 464 check('Array with identical property values', function() { 465 const obj = {} 466 return [obj, obj]; 467 }, compare_Array(check_identical_property_values('0', '1'))); 468 check('Object with identical property values', function() { 469 const obj = {} 470 return {'x':obj, 'y':obj}; 471 }, compare_Object(check_identical_property_values('x', 'y'))); 472 473 function check_absent_property(prop) { 474 return function(actual) { 475 assert_false(prop in actual); 476 }; 477 } 478 check('Object with property on prototype', function() { 479 const Foo = function() {}; 480 Foo.prototype = {'foo':'bar'}; 481 return new Foo(); 482 }, compare_Object(check_absent_property('foo'))); 483 484 check('Object with non-enumerable property', function() { 485 const rv = {}; 486 Object.defineProperty(rv, 'foo', {value:'bar', enumerable:false, writable:true, configurable:true}); 487 return rv; 488 }, compare_Object(check_absent_property('foo'))); 489 490 function check_writable_property(prop) { 491 return function(actual, input) { 492 assert_equals(actual[prop], input[prop]); 493 actual[prop] += ' baz'; 494 assert_equals(actual[prop], input[prop] + ' baz'); 495 }; 496 } 497 check('Object with non-writable property', function() { 498 const rv = {}; 499 Object.defineProperty(rv, 'foo', {value:'bar', enumerable:true, writable:false, configurable:true}); 500 return rv; 501 }, compare_Object(check_writable_property('foo'))); 502 503 function check_configurable_property(prop) { 504 return function(actual, input) { 505 assert_equals(actual[prop], input[prop]); 506 delete actual[prop]; 507 assert_false('prop' in actual); 508 }; 509 } 510 check('Object with non-configurable property', function() { 511 const rv = {}; 512 Object.defineProperty(rv, 'foo', {value:'bar', enumerable:true, writable:true, configurable:false}); 513 return rv; 514 }, compare_Object(check_configurable_property('foo'))); 515 516 structuredCloneBatteryOfTests.push({ 517 description: 'Object with a getter that throws', 518 async f(runner, t) { 519 const exception = new Error(); 520 const testObject = { 521 get testProperty() { 522 throw exception; 523 } 524 }; 525 await promise_rejects_exactly( 526 t, 527 exception, 528 runner.structuredClone(testObject) 529 ); 530 } 531 }); 532 533 /* The tests below are inspired by @zcorpan’s work but got some 534 more substantial changed due to their previous async setup */ 535 536 function get_canvas_1x1_transparent_black() { 537 const canvas = document.createElement('canvas'); 538 canvas.width = 1; 539 canvas.height = 1; 540 return canvas; 541 } 542 543 function get_canvas_1x1_non_transparent_non_black() { 544 const canvas = document.createElement('canvas'); 545 canvas.width = 1; 546 canvas.height = 1; 547 const ctx = canvas.getContext('2d'); 548 const imagedata = ctx.getImageData(0, 0, 1, 1); 549 imagedata.data[0] = 100; 550 imagedata.data[1] = 101; 551 imagedata.data[2] = 102; 552 imagedata.data[3] = 103; 553 return canvas; 554 } 555 556 function compare_ImageBitmap(actual, input) { 557 if (typeof actual === 'string') 558 assert_unreached(actual); 559 assert_true(actual instanceof ImageBitmap, 'instanceof ImageBitmap'); 560 assert_not_equals(actual, input); 561 // XXX paint the ImageBitmap on a canvas and check the data 562 } 563 564 structuredCloneBatteryOfTests.push({ 565 description: 'ImageBitmap 1x1 transparent black', 566 async f(runner) { 567 const canvas = get_canvas_1x1_transparent_black(); 568 const bm = await createImageBitmap(canvas); 569 const copy = await runner.structuredClone(bm); 570 compare_ImageBitmap(bm, copy); 571 }, 572 requiresDocument: true 573 }); 574 575 structuredCloneBatteryOfTests.push({ 576 description: 'ImageBitmap 1x1 non-transparent non-black', 577 async f(runner) { 578 const canvas = get_canvas_1x1_non_transparent_non_black(); 579 const bm = await createImageBitmap(canvas); 580 const copy = await runner.structuredClone(bm); 581 compare_ImageBitmap(bm, copy); 582 }, 583 requiresDocument: true 584 }); 585 586 structuredCloneBatteryOfTests.push({ 587 description: 'Array ImageBitmap object, ImageBitmap 1x1 transparent black', 588 async f(runner) { 589 const canvas = get_canvas_1x1_transparent_black(); 590 const bm = [await createImageBitmap(canvas)]; 591 const copy = await runner.structuredClone(bm); 592 compare_Array(enumerate_props(compare_ImageBitmap))(bm, copy); 593 }, 594 requiresDocument: true 595 }); 596 597 structuredCloneBatteryOfTests.push({ 598 description: 'Array ImageBitmap object, ImageBitmap 1x1 transparent non-black', 599 async f(runner) { 600 const canvas = get_canvas_1x1_non_transparent_non_black(); 601 const bm = [await createImageBitmap(canvas)]; 602 const copy = await runner.structuredClone(bm); 603 compare_Array(enumerate_props(compare_ImageBitmap))(bm, copy); 604 }, 605 requiresDocument: true 606 }); 607 608 structuredCloneBatteryOfTests.push({ 609 description: 'Object ImageBitmap object, ImageBitmap 1x1 transparent black', 610 async f(runner) { 611 const canvas = get_canvas_1x1_transparent_black(); 612 const bm = {x: await createImageBitmap(canvas)}; 613 const copy = await runner.structuredClone(bm); 614 compare_Object(enumerate_props(compare_ImageBitmap))(bm, copy); 615 }, 616 requiresDocument: true 617 }); 618 619 structuredCloneBatteryOfTests.push({ 620 description: 'Object ImageBitmap object, ImageBitmap 1x1 transparent non-black', 621 async f(runner) { 622 const canvas = get_canvas_1x1_non_transparent_non_black(); 623 const bm = {x: await createImageBitmap(canvas)}; 624 const copy = await runner.structuredClone(bm); 625 compare_Object(enumerate_props(compare_ImageBitmap))(bm, copy); 626 }, 627 requiresDocument: true 628 }); 629 630 check('ObjectPrototype must lose its exotic-ness when cloned', 631 () => Object.prototype, 632 (copy, original) => { 633 assert_not_equals(copy, original); 634 assert_true(copy instanceof Object); 635 636 const newProto = { some: 'proto' }; 637 // Must not throw: 638 Object.setPrototypeOf(copy, newProto); 639 640 assert_equals(Object.getPrototypeOf(copy), newProto); 641 } 642 ); 643 644 structuredCloneBatteryOfTests.push({ 645 description: 'Serializing a non-serializable platform object fails', 646 async f(runner, t) { 647 const request = new Response(); 648 await promise_rejects_dom( 649 t, 650 "DataCloneError", 651 runner.structuredClone(request) 652 ); 653 } 654 }); 655 656 structuredCloneBatteryOfTests.push({ 657 description: 'An object whose interface is deleted from the global must still deserialize', 658 async f(runner) { 659 const blob = new Blob(); 660 const blobInterface = globalThis.Blob; 661 delete globalThis.Blob; 662 try { 663 const copy = await runner.structuredClone(blob); 664 assert_true(copy instanceof blobInterface); 665 } finally { 666 globalThis.Blob = blobInterface; 667 } 668 } 669 }); 670 671 check( 672 'A subclass instance will deserialize as its closest serializable superclass', 673 () => { 674 class FileSubclass extends File {} 675 return new FileSubclass([], ""); 676 }, 677 (copy) => { 678 assert_equals(Object.getPrototypeOf(copy), File.prototype); 679 } 680 ); 681 682 check( 683 'Resizable ArrayBuffer', 684 () => { 685 const ab = new ArrayBuffer(16, { maxByteLength: 1024 }); 686 assert_true(ab.resizable); 687 return ab; 688 }, 689 compare_ArrayBuffer); 690 691 structuredCloneBatteryOfTests.push({ 692 description: 'Growable SharedArrayBuffer', 693 async f(runner) { 694 const sab = createBuffer('SharedArrayBuffer', 16, { maxByteLength: 1024 }); 695 assert_true(sab.growable); 696 try { 697 const copy = await runner.structuredClone(sab); 698 compare_ArrayBuffer(sab, copy); 699 } catch (e) { 700 // If we're cross-origin isolated, cloning SABs should not fail. 701 if (e instanceof DOMException && e.code === DOMException.DATA_CLONE_ERR) { 702 assert_false(self.crossOriginIsolated); 703 } else { 704 throw e; 705 } 706 } 707 } 708 }); 709 710 check( 711 'Length-tracking TypedArray', 712 () => { 713 const ab = new ArrayBuffer(16, { maxByteLength: 1024 }); 714 assert_true(ab.resizable); 715 return new Uint8Array(ab); 716 }, 717 compare_ArrayBufferView('Uint8Array')); 718 719 check( 720 'Length-tracking DataView', 721 () => { 722 const ab = new ArrayBuffer(16, { maxByteLength: 1024 }); 723 assert_true(ab.resizable); 724 return new DataView(ab); 725 }, 726 compare_ArrayBufferView('DataView')); 727 728 structuredCloneBatteryOfTests.push({ 729 description: 'Serializing OOB TypedArray throws', 730 async f(runner, t) { 731 const ab = new ArrayBuffer(16, { maxByteLength: 1024 }); 732 const ta = new Uint8Array(ab, 8); 733 ab.resize(0); 734 await promise_rejects_dom( 735 t, 736 "DataCloneError", 737 runner.structuredClone(ta) 738 ); 739 } 740 }); 741 742 structuredCloneBatteryOfTests.push({ 743 description: 'Serializing OOB DataView throws', 744 async f(runner, t) { 745 const ab = new ArrayBuffer(16, { maxByteLength: 1024 }); 746 const dv = new DataView(ab, 8); 747 ab.resize(0); 748 await promise_rejects_dom( 749 t, 750 "DataCloneError", 751 runner.structuredClone(dv) 752 ); 753 } 754 });