tor-browser

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

browser_RemoteValue.js (27057B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const { Realm } = ChromeUtils.importESModule(
      7  "chrome://remote/content/shared/Realm.sys.mjs"
      8 );
      9 const { deserialize, serialize, setDefaultSerializationOptions, stringify } =
     10  ChromeUtils.importESModule(
     11    "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs"
     12  );
     13 
     14 const PRIMITIVE_TYPES = [
     15  { value: undefined, serialized: { type: "undefined" } },
     16  { value: null, serialized: { type: "null" } },
     17  { value: "foo", serialized: { type: "string", value: "foo" } },
     18  { value: Number.NaN, serialized: { type: "number", value: "NaN" } },
     19  { value: -0, serialized: { type: "number", value: "-0" } },
     20  {
     21    value: Number.POSITIVE_INFINITY,
     22    serialized: { type: "number", value: "Infinity" },
     23  },
     24  {
     25    value: Number.NEGATIVE_INFINITY,
     26    serialized: { type: "number", value: "-Infinity" },
     27  },
     28  { value: 42, serialized: { type: "number", value: 42 } },
     29  { value: false, serialized: { type: "boolean", value: false } },
     30  { value: 42n, serialized: { type: "bigint", value: "42" } },
     31 ];
     32 
     33 const REMOTE_SIMPLE_VALUES = [
     34  {
     35    value: new RegExp(/foo/),
     36    serialized: {
     37      type: "regexp",
     38      value: {
     39        pattern: "foo",
     40        flags: "",
     41      },
     42    },
     43    deserializable: true,
     44  },
     45  {
     46    value: new RegExp(/foo/g),
     47    serialized: {
     48      type: "regexp",
     49      value: {
     50        pattern: "foo",
     51        flags: "g",
     52      },
     53    },
     54    deserializable: true,
     55  },
     56  {
     57    value: new Date(1654004849000),
     58    serialized: {
     59      type: "date",
     60      value: "2022-05-31T13:47:29.000Z",
     61    },
     62    deserializable: true,
     63  },
     64 ];
     65 
     66 const REMOTE_COMPLEX_VALUES = [
     67  { value: Symbol("foo"), serialized: { type: "symbol" } },
     68  {
     69    value: [1],
     70    serialized: {
     71      type: "array",
     72      value: [{ type: "number", value: 1 }],
     73    },
     74  },
     75  {
     76    value: [1],
     77    serializationOptions: {
     78      maxObjectDepth: 0,
     79    },
     80    serialized: {
     81      type: "array",
     82    },
     83  },
     84  {
     85    value: [1, "2", true, new RegExp(/foo/g)],
     86    serializationOptions: {
     87      maxObjectDepth: 1,
     88    },
     89    serialized: {
     90      type: "array",
     91      value: [
     92        { type: "number", value: 1 },
     93        { type: "string", value: "2" },
     94        { type: "boolean", value: true },
     95        {
     96          type: "regexp",
     97          value: {
     98            pattern: "foo",
     99            flags: "g",
    100          },
    101        },
    102      ],
    103    },
    104    deserializable: true,
    105  },
    106  {
    107    value: [1, [3, "4"]],
    108    serializationOptions: {
    109      maxObjectDepth: 1,
    110    },
    111    serialized: {
    112      type: "array",
    113      value: [{ type: "number", value: 1 }, { type: "array" }],
    114    },
    115  },
    116  {
    117    value: [1, [3, "4"]],
    118    serializationOptions: {
    119      maxObjectDepth: 2,
    120    },
    121    serialized: {
    122      type: "array",
    123      value: [
    124        { type: "number", value: 1 },
    125        {
    126          type: "array",
    127          value: [
    128            { type: "number", value: 3 },
    129            { type: "string", value: "4" },
    130          ],
    131        },
    132      ],
    133    },
    134    deserializable: true,
    135  },
    136  {
    137    value: new Map(),
    138    serializationOptions: {
    139      maxObjectDepth: 1,
    140    },
    141    serialized: {
    142      type: "map",
    143      value: [],
    144    },
    145    deserializable: true,
    146  },
    147  {
    148    value: new Map([]),
    149    serializationOptions: {
    150      maxObjectDepth: 1,
    151    },
    152    serialized: {
    153      type: "map",
    154      value: [],
    155    },
    156    deserializable: true,
    157  },
    158  {
    159    value: new Map([
    160      [1, 2],
    161      ["2", "3"],
    162      [true, false],
    163    ]),
    164    serialized: {
    165      type: "map",
    166      value: [
    167        [
    168          { type: "number", value: 1 },
    169          { type: "number", value: 2 },
    170        ],
    171        ["2", { type: "string", value: "3" }],
    172        [
    173          { type: "boolean", value: true },
    174          { type: "boolean", value: false },
    175        ],
    176      ],
    177    },
    178  },
    179  {
    180    value: new Map([
    181      [1, 2],
    182      ["2", "3"],
    183      [true, false],
    184    ]),
    185    serializationOptions: {
    186      maxObjectDepth: 0,
    187    },
    188    serialized: {
    189      type: "map",
    190    },
    191  },
    192  {
    193    value: new Map([
    194      [1, 2],
    195      ["2", "3"],
    196      [true, false],
    197    ]),
    198    serializationOptions: {
    199      maxObjectDepth: 1,
    200    },
    201    serialized: {
    202      type: "map",
    203      value: [
    204        [
    205          { type: "number", value: 1 },
    206          { type: "number", value: 2 },
    207        ],
    208        ["2", { type: "string", value: "3" }],
    209        [
    210          { type: "boolean", value: true },
    211          { type: "boolean", value: false },
    212        ],
    213      ],
    214    },
    215    deserializable: true,
    216  },
    217  {
    218    value: new Set(),
    219    serializationOptions: {
    220      maxObjectDepth: 1,
    221    },
    222    serialized: {
    223      type: "set",
    224      value: [],
    225    },
    226    deserializable: true,
    227  },
    228  {
    229    value: new Set([]),
    230    serializationOptions: {
    231      maxObjectDepth: 1,
    232    },
    233    serialized: {
    234      type: "set",
    235      value: [],
    236    },
    237    deserializable: true,
    238  },
    239  {
    240    value: new Set([1, "2", true]),
    241    serialized: {
    242      type: "set",
    243      value: [
    244        { type: "number", value: 1 },
    245        { type: "string", value: "2" },
    246        { type: "boolean", value: true },
    247      ],
    248    },
    249  },
    250  {
    251    value: new Set([1, "2", true]),
    252    serializationOptions: {
    253      maxObjectDepth: 0,
    254    },
    255    serialized: {
    256      type: "set",
    257    },
    258  },
    259  {
    260    value: new Set([1, "2", true]),
    261    serializationOptions: {
    262      maxObjectDepth: 1,
    263    },
    264    serialized: {
    265      type: "set",
    266      value: [
    267        { type: "number", value: 1 },
    268        { type: "string", value: "2" },
    269        { type: "boolean", value: true },
    270      ],
    271    },
    272    deserializable: true,
    273  },
    274  { value: new WeakMap([[{}, 1]]), serialized: { type: "weakmap" } },
    275  { value: new WeakSet([{}]), serialized: { type: "weakset" } },
    276  {
    277    value: (function* () {
    278      yield "a";
    279    })(),
    280    serialized: { type: "generator" },
    281  },
    282  {
    283    value: (async function* () {
    284      yield await Promise.resolve(1);
    285    })(),
    286    serialized: { type: "generator" },
    287  },
    288  { value: new Error("error message"), serialized: { type: "error" } },
    289  {
    290    value: new SyntaxError("syntax error message"),
    291    serialized: { type: "error" },
    292  },
    293  {
    294    value: new TypeError("type error message"),
    295    serialized: { type: "error" },
    296  },
    297  { value: new Proxy({}, {}), serialized: { type: "proxy" } },
    298  { value: new Promise(() => true), serialized: { type: "promise" } },
    299  { value: new Int8Array(), serialized: { type: "typedarray" } },
    300  { value: new ArrayBuffer(), serialized: { type: "arraybuffer" } },
    301  { value: new URL("https://example.com"), serialized: { type: "object" } },
    302  { value: () => true, serialized: { type: "function" } },
    303  { value() {}, serialized: { type: "function" } },
    304  {
    305    value: {},
    306    serializationOptions: {
    307      maxObjectDepth: 1,
    308    },
    309    serialized: {
    310      type: "object",
    311      value: [],
    312    },
    313    deserializable: true,
    314  },
    315  {
    316    value: {
    317      1: 1,
    318      2: "2",
    319      foo: true,
    320    },
    321    serialized: {
    322      type: "object",
    323      value: [
    324        ["1", { type: "number", value: 1 }],
    325        ["2", { type: "string", value: "2" }],
    326        ["foo", { type: "boolean", value: true }],
    327      ],
    328    },
    329  },
    330  {
    331    value: {
    332      1: 1,
    333      2: "2",
    334      foo: true,
    335    },
    336    serializationOptions: {
    337      maxObjectDepth: 0,
    338    },
    339    serialized: {
    340      type: "object",
    341    },
    342  },
    343  {
    344    value: {
    345      1: 1,
    346      2: "2",
    347      foo: true,
    348    },
    349    serializationOptions: {
    350      maxObjectDepth: 1,
    351    },
    352    serialized: {
    353      type: "object",
    354      value: [
    355        ["1", { type: "number", value: 1 }],
    356        ["2", { type: "string", value: "2" }],
    357        ["foo", { type: "boolean", value: true }],
    358      ],
    359    },
    360    deserializable: true,
    361  },
    362  {
    363    value: {
    364      1: 1,
    365      2: "2",
    366      3: {
    367        bar: "foo",
    368      },
    369      foo: true,
    370    },
    371    serializationOptions: {
    372      maxObjectDepth: 2,
    373    },
    374    serialized: {
    375      type: "object",
    376      value: [
    377        ["1", { type: "number", value: 1 }],
    378        ["2", { type: "string", value: "2" }],
    379        [
    380          "3",
    381          {
    382            type: "object",
    383            value: [["bar", { type: "string", value: "foo" }]],
    384          },
    385        ],
    386        ["foo", { type: "boolean", value: true }],
    387      ],
    388    },
    389    deserializable: true,
    390  },
    391 ];
    392 
    393 add_task(function test_deserializePrimitiveTypes() {
    394  const realm = new Realm();
    395 
    396  for (const type of PRIMITIVE_TYPES) {
    397    const { value: expectedValue, serialized } = type;
    398 
    399    info(`Checking '${serialized.type}'`);
    400    const value = deserialize(serialized, realm, {});
    401 
    402    if (serialized.value == "NaN") {
    403      ok(Number.isNaN(value), `Got expected value for ${serialized}`);
    404    } else {
    405      Assert.strictEqual(
    406        value,
    407        expectedValue,
    408        `Got expected value for ${serialized}`
    409      );
    410    }
    411  }
    412 });
    413 
    414 add_task(function test_deserializeDateLocalValue() {
    415  const realm = new Realm();
    416 
    417  const validaDateStrings = [
    418    "2009",
    419    "2009-05",
    420    "2009-05-19",
    421    "2022-02-29",
    422    "2009T15:00",
    423    "2009-05T15:00",
    424    "2022-06-31T15:00",
    425    "2009-05-19T15:00",
    426    "2009-05-19T15:00:15",
    427    "2009-05-19T15:00-00:00",
    428    "2009-05-19T15:00:15.452",
    429    "2009-05-19T15:00:15.452Z",
    430    "2009-05-19T15:00:15.452+02:00",
    431    "2009-05-19T15:00:15.452-02:00",
    432    "-271821-04-20T00:00:00Z",
    433    "+000000-01-01T00:00:00Z",
    434  ];
    435  for (const dateString of validaDateStrings) {
    436    info(`Checking '${dateString}'`);
    437    const value = deserialize({ type: "date", value: dateString }, realm, {});
    438 
    439    Assert.equal(
    440      value.getTime(),
    441      new Date(dateString).getTime(),
    442      `Got expected value for ${dateString}`
    443    );
    444  }
    445 });
    446 
    447 add_task(function test_deserializeLocalValues() {
    448  const realm = new Realm();
    449 
    450  for (const type of REMOTE_SIMPLE_VALUES.concat(REMOTE_COMPLEX_VALUES)) {
    451    const { value: expectedValue, serialized, deserializable } = type;
    452 
    453    // Skip non deserializable cases
    454    if (!deserializable) {
    455      continue;
    456    }
    457 
    458    info(`Checking '${serialized.type}'`);
    459    const value = deserialize(serialized, realm, {});
    460    assertLocalValue(serialized.type, value, expectedValue);
    461  }
    462 });
    463 
    464 add_task(async function test_deserializeLocalValuesInWindowRealm() {
    465  for (const type of REMOTE_SIMPLE_VALUES.concat(REMOTE_COMPLEX_VALUES)) {
    466    const { value: expectedValue, serialized, deserializable } = type;
    467 
    468    // Skip non deserializable cases
    469    if (!deserializable) {
    470      continue;
    471    }
    472 
    473    const value = await deserializeInWindowRealm(serialized);
    474    assertLocalValue(serialized.type, value, expectedValue);
    475  }
    476 });
    477 
    478 add_task(async function test_deserializeChannel() {
    479  const realm = new Realm();
    480  const channel = {
    481    type: "channel",
    482    value: { channel: "channel_name" },
    483  };
    484  const deserializationOptions = {
    485    emitScriptMessage: (realm, channelProperties, message) => message,
    486  };
    487 
    488  info(`Checking 'channel'`);
    489  const value = deserialize(channel, realm, deserializationOptions, {});
    490  Assert.equal(
    491    Object.prototype.toString.call(value),
    492    "[object Function]",
    493    "Got expected type Function"
    494  );
    495  Assert.equal(value("foo"), "foo", "Got expected result");
    496 });
    497 
    498 add_task(function test_deserializeLocalValuesByHandle() {
    499  // Create two realms, realm1 will be used to serialize values, while realm2
    500  // will be used as a reference empty realm without any object reference.
    501  const realm1 = new Realm();
    502  const realm2 = new Realm();
    503 
    504  for (const type of REMOTE_SIMPLE_VALUES.concat(REMOTE_COMPLEX_VALUES)) {
    505    const { value: expectedValue, serialized } = type;
    506 
    507    // No need to skip non-deserializable cases here.
    508 
    509    info(`Checking '${serialized.type}'`);
    510    // Serialize the value once to get a handle.
    511    const serializedValue = serialize(
    512      expectedValue,
    513      { maxObjectDepth: 0 },
    514      "root",
    515      new Map(),
    516      realm1,
    517      {}
    518    );
    519 
    520    // Create a remote reference containing only the handle.
    521    // `deserialize` should not need any other property.
    522    const remoteReference = { handle: serializedValue.handle };
    523 
    524    // Check that the remote reference can be deserialized in realm1.
    525    const value = deserialize(remoteReference, realm1, {});
    526    assertLocalValue(serialized.type, value, expectedValue);
    527 
    528    Assert.throws(
    529      () => deserialize(remoteReference, realm2, {}),
    530      /NoSuchHandleError:/,
    531      `Got expected error when using the wrong realm for deserialize`
    532    );
    533 
    534    realm1.removeObjectHandle(serializedValue.handle);
    535    Assert.throws(
    536      () => deserialize(remoteReference, realm1, {}),
    537      /NoSuchHandleError:/,
    538      `Got expected error when after deleting the object handle`
    539    );
    540  }
    541 });
    542 
    543 add_task(function test_deserializeHandleInvalidTypes() {
    544  const realm = new Realm();
    545 
    546  for (const invalidType of [false, 42, {}, []]) {
    547    info(`Checking type: '${invalidType}'`);
    548 
    549    Assert.throws(
    550      () => deserialize({ type: "object", handle: invalidType }, realm, {}),
    551      /InvalidArgumentError:/,
    552      `Got expected error for type ${invalidType}`
    553    );
    554  }
    555 });
    556 
    557 add_task(function test_deserializePrimitiveTypesInvalidValues() {
    558  const realm = new Realm();
    559 
    560  const invalidValues = [
    561    { type: "bigint", values: [undefined, null, false, "foo", [], {}] },
    562    { type: "boolean", values: [undefined, null, 42, "foo", [], {}] },
    563    {
    564      type: "number",
    565      values: [undefined, null, false, "43", [], {}],
    566    },
    567    { type: "string", values: [undefined, null, false, 42, [], {}] },
    568  ];
    569 
    570  for (const invalidValue of invalidValues) {
    571    const { type, values } = invalidValue;
    572 
    573    for (const value of values) {
    574      info(`Checking '${type}' with value ${value}`);
    575 
    576      Assert.throws(
    577        () => deserialize({ type, value }, realm, {}),
    578        /InvalidArgument/,
    579        `Got expected error for type ${type} and value ${value}`
    580      );
    581    }
    582  }
    583 });
    584 
    585 add_task(function test_deserializeDateLocalValueInvalidValues() {
    586  const realm = new Realm();
    587 
    588  const invalidaDateStrings = [
    589    "10",
    590    "20009",
    591    "+20009",
    592    "2009-",
    593    "2009-0",
    594    "2009-15",
    595    "2009-02-1",
    596    "2009-02-50",
    597    "15:00",
    598    "T15:00",
    599    "9-05-19T15:00",
    600    "2009-5-19T15:00",
    601    "2009-05-1T15:00",
    602    "2009-02-10T15",
    603    "2009-05-19T15:",
    604    "2009-05-19T1:00",
    605    "2009-05-19T10:1",
    606    "2009-05-19T60:00",
    607    "2009-05-19T15:70",
    608    "2009-05-19T15:00.25",
    609    "2009-05-19+10:00",
    610    "2009-05-19Z",
    611    "2009-05-19 15:00",
    612    "2009-05-19t15:00Z",
    613    "2009-05-19T15:00z",
    614    "2009-05-19T15:00+01",
    615    "2009-05-19T10:10+1:00",
    616    "2009-05-19T10:10+01:1",
    617    "2009-05-19T15:00+75:00",
    618    "2009-05-19T15:00+02:80",
    619    "02009-05-19T15:00",
    620  ];
    621  for (const dateString of invalidaDateStrings) {
    622    info(`Checking '${dateString}'`);
    623 
    624    Assert.throws(
    625      () => deserialize({ type: "date", value: dateString }, realm, {}),
    626      /InvalidArgumentError:/,
    627      `Got expected error for date string: ${dateString}`
    628    );
    629  }
    630 });
    631 
    632 add_task(function test_deserializeLocalValuesInvalidType() {
    633  const realm = new Realm();
    634 
    635  const invalidTypes = [undefined, null, false, 42, {}];
    636 
    637  for (const invalidType of invalidTypes) {
    638    info(`Checking type: '${invalidType}'`);
    639 
    640    Assert.throws(
    641      () => deserialize({ type: invalidType }, realm, {}),
    642      /InvalidArgumentError:/,
    643      `Got expected error for type ${invalidType}`
    644    );
    645 
    646    Assert.throws(
    647      () =>
    648        deserialize(
    649          {
    650            type: "array",
    651            value: [{ type: invalidType }],
    652          },
    653          realm,
    654          {}
    655        ),
    656      /InvalidArgumentError:/,
    657      `Got expected error for nested type ${invalidType}`
    658    );
    659  }
    660 });
    661 
    662 add_task(function test_deserializeLocalValuesInvalidValues() {
    663  const realm = new Realm();
    664 
    665  const invalidValues = [
    666    { type: "array", values: [undefined, null, false, 42, "foo", {}] },
    667    {
    668      type: "regexp",
    669      values: [
    670        undefined,
    671        null,
    672        false,
    673        "foo",
    674        42,
    675        [],
    676        {},
    677        { pattern: null },
    678        { pattern: 1 },
    679        { pattern: true },
    680        { pattern: "foo", flags: null },
    681        { pattern: "foo", flags: 1 },
    682        { pattern: "foo", flags: false },
    683        { pattern: "foo", flags: "foo" },
    684      ],
    685    },
    686    {
    687      type: "date",
    688      values: [
    689        undefined,
    690        null,
    691        false,
    692        "foo",
    693        "05 October 2011 14:48 UTC",
    694        "Tue Jun 14 2022 10:46:50 GMT+0200!",
    695        42,
    696        [],
    697        {},
    698      ],
    699    },
    700    {
    701      type: "map",
    702      values: [
    703        undefined,
    704        null,
    705        false,
    706        "foo",
    707        42,
    708        ["1"],
    709        [[]],
    710        [["1"]],
    711        [{ 1: "2" }],
    712        {},
    713      ],
    714    },
    715    {
    716      type: "set",
    717      values: [undefined, null, false, "foo", 42, {}],
    718    },
    719    {
    720      type: "object",
    721      values: [
    722        undefined,
    723        null,
    724        false,
    725        "foo",
    726        42,
    727        {},
    728        ["1"],
    729        [[]],
    730        [["1"]],
    731        [{ 1: "2" }],
    732        [
    733          [
    734            { type: "number", value: "1" },
    735            { type: "number", value: "2" },
    736          ],
    737        ],
    738        [
    739          [
    740            { type: "object", value: [] },
    741            { type: "number", value: "1" },
    742          ],
    743        ],
    744        [
    745          [
    746            {
    747              type: "regexp",
    748              value: {
    749                pattern: "foo",
    750              },
    751            },
    752            { type: "number", value: "1" },
    753          ],
    754        ],
    755      ],
    756    },
    757  ];
    758 
    759  for (const invalidValue of invalidValues) {
    760    const { type, values } = invalidValue;
    761 
    762    for (const value of values) {
    763      info(`Checking '${type}' with value ${value}`);
    764 
    765      Assert.throws(
    766        () => deserialize({ type, value }, realm, {}),
    767        /InvalidArgumentError:/,
    768        `Got expected error for type ${type} and value ${value}`
    769      );
    770    }
    771  }
    772 });
    773 
    774 add_task(function test_serializePrimitiveTypes() {
    775  const realm = new Realm();
    776 
    777  for (const type of PRIMITIVE_TYPES) {
    778    const { value, serialized } = type;
    779    const defaultSerializationOptions = setDefaultSerializationOptions();
    780 
    781    const serializationInternalMap = new Map();
    782    const serializedValue = serialize(
    783      value,
    784      defaultSerializationOptions,
    785      "none",
    786      serializationInternalMap,
    787      realm,
    788      {}
    789    );
    790    assertInternalIds(serializationInternalMap, 0);
    791    Assert.deepEqual(serialized, serializedValue, "Got expected structure");
    792 
    793    // For primitive values, the serialization with ownershipType=root should
    794    // be exactly identical to the one with ownershipType=none.
    795    const serializationInternalMapWithRoot = new Map();
    796    const serializedWithRoot = serialize(
    797      value,
    798      defaultSerializationOptions,
    799      "root",
    800      serializationInternalMapWithRoot,
    801      realm,
    802      {}
    803    );
    804    assertInternalIds(serializationInternalMapWithRoot, 0);
    805    Assert.deepEqual(serialized, serializedWithRoot, "Got expected structure");
    806  }
    807 });
    808 
    809 add_task(function test_serializeRemoteSimpleValues() {
    810  const realm = new Realm();
    811 
    812  for (const type of REMOTE_SIMPLE_VALUES) {
    813    const { value, serialized } = type;
    814    const defaultSerializationOptions = setDefaultSerializationOptions();
    815 
    816    info(`Checking '${serialized.type}' with none ownershipType`);
    817    const serializationInternalMapWithNone = new Map();
    818    const serializedValue = serialize(
    819      value,
    820      defaultSerializationOptions,
    821      "none",
    822      serializationInternalMapWithNone,
    823      realm,
    824      {}
    825    );
    826 
    827    assertInternalIds(serializationInternalMapWithNone, 0);
    828    Assert.deepEqual(serialized, serializedValue, "Got expected structure");
    829 
    830    info(`Checking '${serialized.type}' with root ownershipType`);
    831    const serializationInternalMapWithRoot = new Map();
    832    const serializedWithRoot = serialize(
    833      value,
    834      defaultSerializationOptions,
    835      "root",
    836      serializationInternalMapWithRoot,
    837      realm,
    838      {}
    839    );
    840 
    841    assertInternalIds(serializationInternalMapWithRoot, 0);
    842    Assert.equal(
    843      typeof serializedWithRoot.handle,
    844      "string",
    845      "Got a handle property"
    846    );
    847    Assert.deepEqual(
    848      Object.assign({}, serialized, { handle: serializedWithRoot.handle }),
    849      serializedWithRoot,
    850      "Got expected structure, plus a generated handle id"
    851    );
    852  }
    853 });
    854 
    855 add_task(function test_serializeRemoteComplexValues() {
    856  for (const type of REMOTE_COMPLEX_VALUES) {
    857    const { value, serialized, serializationOptions } = type;
    858    const serializationOptionsWithDefaults =
    859      setDefaultSerializationOptions(serializationOptions);
    860 
    861    info(`Checking '${serialized.type}' with none ownershipType`);
    862    const realm = new Realm();
    863    const serializationInternalMapWithNone = new Map();
    864 
    865    const serializedValue = serialize(
    866      value,
    867      serializationOptionsWithDefaults,
    868      "none",
    869      serializationInternalMapWithNone,
    870      realm,
    871      {}
    872    );
    873 
    874    assertInternalIds(serializationInternalMapWithNone, 0);
    875    Assert.deepEqual(serialized, serializedValue, "Got expected structure");
    876 
    877    info(`Checking '${serialized.type}' with root ownershipType`);
    878    const serializationInternalMapWithRoot = new Map();
    879    const serializedWithRoot = serialize(
    880      value,
    881      serializationOptionsWithDefaults,
    882      "root",
    883      serializationInternalMapWithRoot,
    884      realm,
    885      {}
    886    );
    887 
    888    assertInternalIds(serializationInternalMapWithRoot, 0);
    889    Assert.equal(
    890      typeof serializedWithRoot.handle,
    891      "string",
    892      "Got a handle property"
    893    );
    894    Assert.deepEqual(
    895      Object.assign({}, serialized, { handle: serializedWithRoot.handle }),
    896      serializedWithRoot,
    897      "Got expected structure, plus a generated handle id"
    898    );
    899  }
    900 });
    901 
    902 add_task(function test_serializeWithSerializationInternalMap() {
    903  const dataSet = [
    904    {
    905      data: [1],
    906      serializedData: [{ type: "number", value: 1 }],
    907      type: "array",
    908    },
    909    {
    910      data: new Map([[true, false]]),
    911      serializedData: [
    912        [
    913          { type: "boolean", value: true },
    914          { type: "boolean", value: false },
    915        ],
    916      ],
    917      type: "map",
    918    },
    919    {
    920      data: new Set(["foo"]),
    921      serializedData: [{ type: "string", value: "foo" }],
    922      type: "set",
    923    },
    924    {
    925      data: { foo: "bar" },
    926      serializedData: [["foo", { type: "string", value: "bar" }]],
    927      type: "object",
    928    },
    929  ];
    930  const realm = new Realm();
    931 
    932  for (const { type, data, serializedData } of dataSet) {
    933    info(`Checking '${type}' with serializationInternalMap`);
    934 
    935    const serializationInternalMap = new Map();
    936    const value = [
    937      data,
    938      data,
    939      [data],
    940      new Set([data]),
    941      new Map([["bar", data]]),
    942      { bar: data },
    943    ];
    944 
    945    const serializedValue = serialize(
    946      value,
    947      { maxObjectDepth: 2 },
    948      "none",
    949      serializationInternalMap,
    950      realm,
    951      {}
    952    );
    953 
    954    assertInternalIds(serializationInternalMap, 1);
    955 
    956    const internalId = serializationInternalMap.get(data).internalId;
    957 
    958    const serialized = {
    959      type: "array",
    960      value: [
    961        {
    962          type,
    963          value: serializedData,
    964          internalId,
    965        },
    966        {
    967          type,
    968          internalId,
    969        },
    970        {
    971          type: "array",
    972          value: [{ type, internalId }],
    973        },
    974        {
    975          type: "set",
    976          value: [{ type, internalId }],
    977        },
    978        {
    979          type: "map",
    980          value: [["bar", { type, internalId }]],
    981        },
    982        {
    983          type: "object",
    984          value: [["bar", { type, internalId }]],
    985        },
    986      ],
    987    };
    988 
    989    Assert.deepEqual(serialized, serializedValue, "Got expected structure");
    990  }
    991 });
    992 
    993 add_task(function test_serializeMultipleValuesWithSerializationInternalMap() {
    994  const realm = new Realm();
    995  const serializationInternalMap = new Map();
    996  const obj1 = { foo: "bar" };
    997  const obj2 = [1, 2];
    998  const value = [obj1, obj2, obj1, obj2];
    999 
   1000  serialize(
   1001    value,
   1002    { maxObjectDepth: 2 },
   1003    "none",
   1004    serializationInternalMap,
   1005    realm,
   1006    {}
   1007  );
   1008 
   1009  assertInternalIds(serializationInternalMap, 2);
   1010 
   1011  const internalId1 = serializationInternalMap.get(obj1).internalId;
   1012  const internalId2 = serializationInternalMap.get(obj2).internalId;
   1013 
   1014  Assert.notEqual(
   1015    internalId1,
   1016    internalId2,
   1017    "Internal ids for different object are also different"
   1018  );
   1019 });
   1020 
   1021 add_task(function test_stringify() {
   1022  const STRINGIFY_TEST_CASES = [
   1023    [undefined, "undefined"],
   1024    [null, "null"],
   1025    ["foobar", "foobar"],
   1026    ["2", "2"],
   1027    [-0, "0"],
   1028    [Infinity, "Infinity"],
   1029    [-Infinity, "-Infinity"],
   1030    [3, "3"],
   1031    [1.4, "1.4"],
   1032    [true, "true"],
   1033    [42n, "42"],
   1034    [{ toString: () => "bar" }, "bar", "toString: () => 'bar'"],
   1035    [{ toString: () => 4 }, "[object Object]", "toString: () => 4"],
   1036    [{ toString: undefined }, "[object Object]", "toString: undefined"],
   1037    [{ toString: null }, "[object Object]", "toString: null"],
   1038    [
   1039      {
   1040        toString: () => {
   1041          throw new Error("toString error");
   1042        },
   1043      },
   1044      "[object Object]",
   1045      "toString: () => { throw new Error('toString error'); }",
   1046    ],
   1047  ];
   1048 
   1049  for (const [value, expectedString, description] of STRINGIFY_TEST_CASES) {
   1050    info(`Checking '${description || value}'`);
   1051    const stringifiedValue = stringify(value);
   1052 
   1053    Assert.strictEqual(expectedString, stringifiedValue, "Got expected string");
   1054  }
   1055 });
   1056 
   1057 function assertLocalValue(type, value, expectedValue) {
   1058  let formattedValue = value;
   1059  let formattedExpectedValue = expectedValue;
   1060 
   1061  // Format certain types for easier assertion
   1062  if (type == "map") {
   1063    Assert.equal(
   1064      Object.prototype.toString.call(expectedValue),
   1065      "[object Map]",
   1066      "Got expected type Map"
   1067    );
   1068 
   1069    formattedValue = Array.from(value.values());
   1070    formattedExpectedValue = Array.from(expectedValue.values());
   1071  } else if (type == "set") {
   1072    Assert.equal(
   1073      Object.prototype.toString.call(expectedValue),
   1074      "[object Set]",
   1075      "Got expected type Set"
   1076    );
   1077 
   1078    formattedValue = Array.from(value);
   1079    formattedExpectedValue = Array.from(expectedValue);
   1080  }
   1081 
   1082  Assert.deepEqual(
   1083    formattedValue,
   1084    formattedExpectedValue,
   1085    "Got expected structure"
   1086  );
   1087 }
   1088 
   1089 function assertInternalIds(serializationInternalMap, amount) {
   1090  const remoteValuesWithInternalIds = Array.from(
   1091    serializationInternalMap.values()
   1092  ).filter(remoteValue => !!remoteValue.internalId);
   1093 
   1094  Assert.equal(
   1095    remoteValuesWithInternalIds.length,
   1096    amount,
   1097    "Got expected amount of internalIds in serializationInternalMap"
   1098  );
   1099 }
   1100 
   1101 function deserializeInWindowRealm(serialized) {
   1102  return SpecialPowers.spawn(
   1103    gBrowser.selectedBrowser,
   1104    [serialized],
   1105    async _serialized => {
   1106      const { WindowRealm } = ChromeUtils.importESModule(
   1107        "chrome://remote/content/shared/Realm.sys.mjs"
   1108      );
   1109      const { deserialize } = ChromeUtils.importESModule(
   1110        "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs"
   1111      );
   1112      const realm = new WindowRealm(content);
   1113      info(`Checking '${_serialized.type}'`);
   1114      return deserialize(_serialized, realm, {});
   1115    }
   1116  );
   1117 }