tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 });