tor-browser

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

RemoteValue.sys.mjs (30937B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 const lazy = {};
      6 
      7 ChromeUtils.defineESModuleGetters(lazy, {
      8  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
      9  dom: "chrome://remote/content/shared/DOM.sys.mjs",
     10  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
     11  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
     12  Log: "chrome://remote/content/shared/Log.sys.mjs",
     13  pprint: "chrome://remote/content/shared/Format.sys.mjs",
     14 });
     15 
     16 ChromeUtils.defineLazyGetter(lazy, "logger", () =>
     17  lazy.Log.get(lazy.Log.TYPES.WEBDRIVER_BIDI)
     18 );
     19 
     20 /**
     21 * @typedef {object} IncludeShadowTreeMode
     22 */
     23 
     24 /**
     25 * Enum of include shadow tree modes supported by the serialization.
     26 *
     27 * @readonly
     28 * @enum {IncludeShadowTreeMode}
     29 */
     30 export const IncludeShadowTreeMode = {
     31  All: "all",
     32  None: "none",
     33  Open: "open",
     34 };
     35 
     36 /**
     37 * @typedef {object} OwnershipModel
     38 */
     39 
     40 /**
     41 * Enum of ownership models supported by the serialization.
     42 *
     43 * @readonly
     44 * @enum {OwnershipModel}
     45 */
     46 export const OwnershipModel = {
     47  None: "none",
     48  Root: "root",
     49 };
     50 
     51 /**
     52 * Extra options for deserializing remote values.
     53 *
     54 * @typedef {object} ExtraDeserializationOptions
     55 *
     56 * @property {NodeCache=} nodeCache
     57 *     The cache containing DOM node references.
     58 * @property {Function=} emitScriptMessage
     59 *     The function to emit "script.message" event.
     60 */
     61 
     62 /**
     63 * Extra options for serializing remote values.
     64 *
     65 * @typedef {object} ExtraSerializationOptions
     66 *
     67 * @property {NodeCache=} nodeCache
     68 *     The cache containing DOM node references.
     69 * @property {Map<BrowsingContext, Array<string>>} seenNodeIds
     70 *     Map of browsing contexts to their seen node ids during the current
     71 *     serialization.
     72 */
     73 
     74 /**
     75 * An object which holds the information of how
     76 * ECMAScript objects should be serialized.
     77 *
     78 * @typedef {object} SerializationOptions
     79 *
     80 * @property {number} [maxDomDepth=0]
     81 *     Depth of a serialization of DOM Nodes. Defaults to 0.
     82 * @property {number} [maxObjectDepth=null]
     83 *     Depth of a serialization of objects. Defaults to null.
     84 * @property {IncludeShadowTreeMode} [includeShadowTree=IncludeShadowTreeMode.None]
     85 *     Mode of a serialization of shadow dom. Defaults to "none".
     86 */
     87 
     88 const TYPED_ARRAY_CLASSES = [
     89  "Uint8Array",
     90  "Uint8ClampedArray",
     91  "Uint16Array",
     92  "Uint32Array",
     93  "Int8Array",
     94  "Int16Array",
     95  "Int32Array",
     96  "Float32Array",
     97  "Float64Array",
     98  "BigInt64Array",
     99  "BigUint64Array",
    100 ];
    101 
    102 /**
    103 * Build the serialized RemoteValue.
    104 *
    105 * @returns {object}
    106 *     An object with a mandatory `type` property, and optional `handle`,
    107 *     depending on the OwnershipModel, used for the serialization and
    108 *     on the value's type.
    109 */
    110 function buildSerialized(type, handle = null) {
    111  const serialized = { type };
    112 
    113  if (handle !== null) {
    114    serialized.handle = handle;
    115  }
    116 
    117  return serialized;
    118 }
    119 
    120 /**
    121 * Helper to deserialize value list.
    122 *
    123 * @see https://w3c.github.io/webdriver-bidi/#deserialize-value-list
    124 *
    125 * @param {Array} serializedValueList
    126 *     List of serialized values.
    127 * @param {Realm} realm
    128 *     The Realm in which the value is deserialized.
    129 * @param {ExtraDeserializationOptions} extraOptions
    130 *     Extra Remote Value deserialization options.
    131 *
    132 * @returns {Array} List of deserialized values.
    133 *
    134 * @throws {InvalidArgumentError}
    135 *     If <var>serializedValueList</var> is not an array.
    136 */
    137 function deserializeValueList(serializedValueList, realm, extraOptions) {
    138  lazy.assert.array(
    139    serializedValueList,
    140    lazy.pprint`Expected "serializedValueList" to be an array, got ${serializedValueList}`
    141  );
    142 
    143  const deserializedValues = [];
    144 
    145  for (const item of serializedValueList) {
    146    deserializedValues.push(deserialize(item, realm, extraOptions));
    147  }
    148 
    149  return deserializedValues;
    150 }
    151 
    152 /**
    153 * Helper to deserialize key-value list.
    154 *
    155 * @see https://w3c.github.io/webdriver-bidi/#deserialize-key-value-list
    156 *
    157 * @param {Array} serializedKeyValueList
    158 *     List of serialized key-value.
    159 * @param {Realm} realm
    160 *     The Realm in which the value is deserialized.
    161 * @param {ExtraDeserializationOptions} extraOptions
    162 *     Extra Remote Value deserialization options.
    163 *
    164 * @returns {Array} List of deserialized key-value.
    165 *
    166 * @throws {InvalidArgumentError}
    167 *     If <var>serializedKeyValueList</var> is not an array or
    168 *     not an array of key-value arrays.
    169 */
    170 function deserializeKeyValueList(serializedKeyValueList, realm, extraOptions) {
    171  lazy.assert.array(
    172    serializedKeyValueList,
    173    lazy.pprint`Expected "serializedKeyValueList" to be an array, got ${serializedKeyValueList}`
    174  );
    175 
    176  const deserializedKeyValueList = [];
    177 
    178  for (const serializedKeyValue of serializedKeyValueList) {
    179    if (!Array.isArray(serializedKeyValue) || serializedKeyValue.length != 2) {
    180      throw new lazy.error.InvalidArgumentError(
    181        `Expected key-value pair to be an array with 2 elements, got ${serializedKeyValue}`
    182      );
    183    }
    184    const [serializedKey, serializedValue] = serializedKeyValue;
    185    const deserializedKey =
    186      typeof serializedKey == "string"
    187        ? serializedKey
    188        : deserialize(serializedKey, realm, extraOptions);
    189    const deserializedValue = deserialize(serializedValue, realm, extraOptions);
    190 
    191    deserializedKeyValueList.push([deserializedKey, deserializedValue]);
    192  }
    193 
    194  return deserializedKeyValueList;
    195 }
    196 
    197 /**
    198 * Deserialize a Node as referenced by the shared unique reference.
    199 *
    200 * This unique reference can be shared by WebDriver clients with the WebDriver
    201 * classic implementation (Marionette) if the reference is for an Element or
    202 * ShadowRoot.
    203 *
    204 * @param {string} sharedRef
    205 *     Shared unique reference of the Node.
    206 * @param {Realm} realm
    207 *     The Realm in which the value is deserialized.
    208 * @param {ExtraDeserializationOptions} extraOptions
    209 *     Extra Remote Value deserialization options.
    210 *
    211 * @returns {Node} The deserialized DOM node.
    212 */
    213 function deserializeSharedReference(sharedRef, realm, extraOptions) {
    214  const { nodeCache } = extraOptions;
    215 
    216  const browsingContext = realm.browsingContext;
    217  if (!browsingContext) {
    218    throw new lazy.error.NoSuchNodeError("Realm isn't a Window global");
    219  }
    220 
    221  const node = nodeCache.getNode(browsingContext, sharedRef);
    222 
    223  if (node === null) {
    224    throw new lazy.error.NoSuchNodeError(
    225      `The node with the reference ${sharedRef} is not known`
    226    );
    227  }
    228 
    229  // Bug 1819902: Instead of a browsing context check compare the origin
    230  const isSameBrowsingContext = sharedRef => {
    231    const nodeDetails = nodeCache.getReferenceDetails(sharedRef);
    232 
    233    if (nodeDetails.isTopBrowsingContext && browsingContext.parent === null) {
    234      // As long as Navigables are not available any cross-group navigation will
    235      // cause a swap of the current top-level browsing context. The only unique
    236      // identifier in such a case is the browser id the top-level browsing
    237      // context actually lives in.
    238      return nodeDetails.browserId === browsingContext.browserId;
    239    }
    240 
    241    return nodeDetails.browsingContextId === browsingContext.id;
    242  };
    243 
    244  if (!isSameBrowsingContext(sharedRef)) {
    245    return null;
    246  }
    247 
    248  return node;
    249 }
    250 
    251 /**
    252 * Deserialize a local value.
    253 *
    254 * @see https://w3c.github.io/webdriver-bidi/#deserialize-local-value
    255 *
    256 * @param {object} serializedValue
    257 *     Value of any type to be deserialized.
    258 * @param {Realm} realm
    259 *     The Realm in which the value is deserialized.
    260 * @param {ExtraDeserializationOptions} extraOptions
    261 *     Extra Remote Value deserialization options.
    262 *
    263 * @returns {object} Deserialized representation of the value.
    264 */
    265 export function deserialize(serializedValue, realm, extraOptions) {
    266  const { handle, sharedId, type, value } = serializedValue;
    267 
    268  // With a shared id present deserialize as node reference.
    269  if (sharedId !== undefined) {
    270    lazy.assert.string(
    271      sharedId,
    272      lazy.pprint`Expected "sharedId" to be a string, got ${sharedId}`
    273    );
    274 
    275    return deserializeSharedReference(sharedId, realm, extraOptions);
    276  }
    277 
    278  // With a handle present deserialize as remote reference.
    279  if (handle !== undefined) {
    280    lazy.assert.string(
    281      handle,
    282      lazy.pprint`Expected "handle" to be a string, got ${handle}`
    283    );
    284 
    285    const object = realm.getObjectForHandle(handle);
    286    if (!object) {
    287      throw new lazy.error.NoSuchHandleError(
    288        `Unable to find an object reference for "handle" ${handle}`
    289      );
    290    }
    291 
    292    return object;
    293  }
    294 
    295  lazy.assert.string(
    296    type,
    297    lazy.pprint`Expected "type" to be a string, got ${type}`
    298  );
    299 
    300  // Primitive protocol values
    301  switch (type) {
    302    case "undefined":
    303      return undefined;
    304    case "null":
    305      return null;
    306    case "string":
    307      lazy.assert.string(
    308        value,
    309        lazy.pprint`Expected "value" to be a string, got ${value}`
    310      );
    311      return value;
    312    case "number":
    313      // If value is already a number return its value.
    314      if (typeof value === "number") {
    315        return value;
    316      }
    317 
    318      // Otherwise it has to be one of the special strings
    319      lazy.assert.in(
    320        value,
    321        ["NaN", "-0", "Infinity", "-Infinity"],
    322        lazy.pprint`Expected "value" to be one of "NaN", "-0", "Infinity", "-Infinity", got ${value}`
    323      );
    324      return Number(value);
    325    case "boolean":
    326      lazy.assert.boolean(
    327        value,
    328        lazy.pprint`Expected "value" to be a boolean, got ${value}`
    329      );
    330      return value;
    331    case "bigint":
    332      lazy.assert.string(
    333        value,
    334        lazy.pprint`Expected "value" to be a string, got ${value}`
    335      );
    336      try {
    337        return BigInt(value);
    338      } catch (e) {
    339        throw new lazy.error.InvalidArgumentError(
    340          `Failed to deserialize value as BigInt: ${value}`
    341        );
    342      }
    343 
    344    // Script channel
    345    case "channel": {
    346      const channel = message =>
    347        extraOptions.emitScriptMessage(realm, value, message);
    348      return realm.cloneIntoRealm(channel);
    349    }
    350 
    351    // Non-primitive protocol values
    352    case "array": {
    353      const array = realm.cloneIntoRealm([]);
    354      deserializeValueList(value, realm, extraOptions).forEach(v =>
    355        array.push(v)
    356      );
    357      return array;
    358    }
    359    case "date":
    360      // We want to support only Date Time String format,
    361      // check if the value follows it.
    362      if (!ChromeUtils.isISOStyleDate(value)) {
    363        throw new lazy.error.InvalidArgumentError(
    364          `Expected "value" for Date to be a Date Time string, got ${value}`
    365        );
    366      }
    367 
    368      return realm.cloneIntoRealm(new Date(value));
    369    case "map": {
    370      const map = realm.cloneIntoRealm(new Map());
    371      deserializeKeyValueList(value, realm, extraOptions).forEach(([k, v]) =>
    372        map.set(k, v)
    373      );
    374 
    375      return map;
    376    }
    377    case "object": {
    378      const object = realm.cloneIntoRealm({});
    379      deserializeKeyValueList(value, realm, extraOptions).forEach(
    380        ([k, v]) => (object[k] = v)
    381      );
    382      return object;
    383    }
    384    case "regexp": {
    385      lazy.assert.object(
    386        value,
    387        lazy.pprint`Expected "value" for RegExp to be an object, got ${value}`
    388      );
    389      const { pattern, flags } = value;
    390      lazy.assert.string(
    391        pattern,
    392        lazy.pprint`Expected "pattern" for RegExp to be a string, got ${pattern}`
    393      );
    394      if (flags !== undefined) {
    395        lazy.assert.string(
    396          flags,
    397          lazy.pprint`Expected "flags" for RegExp to be a string, got ${flags}`
    398        );
    399      }
    400      try {
    401        return realm.cloneIntoRealm(new RegExp(pattern, flags));
    402      } catch (e) {
    403        throw new lazy.error.InvalidArgumentError(
    404          `Failed to deserialize value as RegExp: ${value}`
    405        );
    406      }
    407    }
    408    case "set": {
    409      const set = realm.cloneIntoRealm(new Set());
    410      deserializeValueList(value, realm, extraOptions).forEach(v => set.add(v));
    411      return set;
    412    }
    413  }
    414 
    415  lazy.logger.warn(`Unsupported type for local value ${type}`);
    416  return undefined;
    417 }
    418 
    419 /**
    420 * Helper to retrieve the handle id for a given object, for the provided realm
    421 * and ownership type.
    422 *
    423 * See https://w3c.github.io/webdriver-bidi/#handle-for-an-object
    424 *
    425 * @param {Realm} realm
    426 *     The Realm from which comes the value being serialized.
    427 * @param {OwnershipModel} ownershipType
    428 *     The ownership model to use for this serialization.
    429 * @param {object} object
    430 *     The object being serialized.
    431 *
    432 * @returns {string} The unique handle id for the object. Will be null if the
    433 *     Ownership type is "none".
    434 */
    435 function getHandleForObject(realm, ownershipType, object) {
    436  if (ownershipType === OwnershipModel.None) {
    437    return null;
    438  }
    439  return realm.getHandleForObject(object);
    440 }
    441 
    442 /**
    443 * Gets or creates a new shared unique reference for the DOM node.
    444 *
    445 * This unique reference can be shared by WebDriver clients with the WebDriver
    446 * classic implementation (Marionette) if the reference is for an Element or
    447 * ShadowRoot.
    448 *
    449 * @param {Node} node
    450 *    Node to create the unique reference for.
    451 * @param {ExtraSerializationOptions} extraOptions
    452 *     Extra Remote Value serialization options.
    453 *
    454 * @returns {string}
    455 *    Shared unique reference for the Node.
    456 */
    457 function getSharedIdForNode(node, extraOptions) {
    458  const { nodeCache, seenNodeIds } = extraOptions;
    459 
    460  if (!Node.isInstance(node)) {
    461    return null;
    462  }
    463 
    464  const browsingContext = node.ownerGlobal.browsingContext;
    465  if (!browsingContext) {
    466    return null;
    467  }
    468 
    469  return nodeCache.getOrCreateNodeReference(node, seenNodeIds);
    470 }
    471 
    472 /**
    473 * Helper to serialize an Array-like object.
    474 *
    475 * @see https://w3c.github.io/webdriver-bidi/#serialize-an-array-like
    476 *
    477 * @param {string} production
    478 *     Type of object
    479 * @param {string} handleId
    480 *     The unique id of the <var>value</var>.
    481 * @param {boolean} knownObject
    482 *     Indicates if the <var>value</var> has already been serialized.
    483 * @param {object} value
    484 *     The Array-like object to serialize.
    485 * @param {SerializationOptions} serializationOptions
    486 *     Options which define how ECMAScript objects should be serialized.
    487 * @param {OwnershipModel} ownershipType
    488 *     The ownership model to use for this serialization.
    489 * @param {Map} serializationInternalMap
    490 *     Map of internal ids.
    491 * @param {Realm} realm
    492 *     The Realm from which comes the value being serialized.
    493 * @param {ExtraSerializationOptions} extraOptions
    494 *     Extra Remote Value serialization options.
    495 *
    496 * @returns {object} Object for serialized values.
    497 */
    498 function serializeArrayLike(
    499  production,
    500  handleId,
    501  knownObject,
    502  value,
    503  serializationOptions,
    504  ownershipType,
    505  serializationInternalMap,
    506  realm,
    507  extraOptions
    508 ) {
    509  const serialized = buildSerialized(production, handleId);
    510  setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
    511 
    512  if (!knownObject && serializationOptions.maxObjectDepth !== 0) {
    513    serialized.value = serializeList(
    514      value,
    515      serializationOptions,
    516      ownershipType,
    517      serializationInternalMap,
    518      realm,
    519      extraOptions
    520    );
    521  }
    522 
    523  return serialized;
    524 }
    525 
    526 /**
    527 * Helper to serialize as a list.
    528 *
    529 * @see https://w3c.github.io/webdriver-bidi/#serialize-as-a-list
    530 *
    531 * @param {Iterable} iterable
    532 *     List of values to be serialized.
    533 * @param {SerializationOptions} serializationOptions
    534 *     Options which define how ECMAScript objects should be serialized.
    535 * @param {OwnershipModel} ownershipType
    536 *     The ownership model to use for this serialization.
    537 * @param {Map} serializationInternalMap
    538 *     Map of internal ids.
    539 * @param {Realm} realm
    540 *     The Realm from which comes the value being serialized.
    541 * @param {ExtraSerializationOptions} extraOptions
    542 *     Extra Remote Value serialization options.
    543 *
    544 * @returns {Array} List of serialized values.
    545 */
    546 function serializeList(
    547  iterable,
    548  serializationOptions,
    549  ownershipType,
    550  serializationInternalMap,
    551  realm,
    552  extraOptions
    553 ) {
    554  const { maxObjectDepth } = serializationOptions;
    555  const serialized = [];
    556  const childSerializationOptions = {
    557    ...serializationOptions,
    558  };
    559  if (maxObjectDepth !== null) {
    560    childSerializationOptions.maxObjectDepth = maxObjectDepth - 1;
    561  }
    562 
    563  for (const item of iterable) {
    564    serialized.push(
    565      serialize(
    566        item,
    567        childSerializationOptions,
    568        ownershipType,
    569        serializationInternalMap,
    570        realm,
    571        extraOptions
    572      )
    573    );
    574  }
    575 
    576  return serialized;
    577 }
    578 
    579 /**
    580 * Helper to serialize as a mapping.
    581 *
    582 * @see https://w3c.github.io/webdriver-bidi/#serialize-as-a-mapping
    583 *
    584 * @param {Iterable} iterable
    585 *     List of values to be serialized.
    586 * @param {SerializationOptions} serializationOptions
    587 *     Options which define how ECMAScript objects should be serialized.
    588 * @param {OwnershipModel} ownershipType
    589 *     The ownership model to use for this serialization.
    590 * @param {Map} serializationInternalMap
    591 *     Map of internal ids.
    592 * @param {Realm} realm
    593 *     The Realm from which comes the value being serialized.
    594 * @param {ExtraSerializationOptions} extraOptions
    595 *     Extra Remote Value serialization options.
    596 *
    597 * @returns {Array} List of serialized values.
    598 */
    599 function serializeMapping(
    600  iterable,
    601  serializationOptions,
    602  ownershipType,
    603  serializationInternalMap,
    604  realm,
    605  extraOptions
    606 ) {
    607  const { maxObjectDepth } = serializationOptions;
    608  const serialized = [];
    609  const childSerializationOptions = {
    610    ...serializationOptions,
    611  };
    612  if (maxObjectDepth !== null) {
    613    childSerializationOptions.maxObjectDepth = maxObjectDepth - 1;
    614  }
    615 
    616  for (const [key, item] of iterable) {
    617    const serializedKey =
    618      typeof key == "string"
    619        ? key
    620        : serialize(
    621            key,
    622            childSerializationOptions,
    623            ownershipType,
    624            serializationInternalMap,
    625            realm,
    626            extraOptions
    627          );
    628    const serializedValue = serialize(
    629      item,
    630      childSerializationOptions,
    631      ownershipType,
    632      serializationInternalMap,
    633      realm,
    634      extraOptions
    635    );
    636 
    637    serialized.push([serializedKey, serializedValue]);
    638  }
    639 
    640  return serialized;
    641 }
    642 
    643 /**
    644 * Helper to serialize as a Node.
    645 *
    646 * @param {Node} node
    647 *     Node to be serialized.
    648 * @param {SerializationOptions} serializationOptions
    649 *     Options which define how ECMAScript objects should be serialized.
    650 * @param {OwnershipModel} ownershipType
    651 *     The ownership model to use for this serialization.
    652 * @param {Map} serializationInternalMap
    653 *     Map of internal ids.
    654 * @param {Realm} realm
    655 *     The Realm from which comes the value being serialized.
    656 * @param {ExtraSerializationOptions} extraOptions
    657 *     Extra Remote Value serialization options.
    658 *
    659 * @returns {object} Serialized value.
    660 */
    661 function serializeNode(
    662  node,
    663  serializationOptions,
    664  ownershipType,
    665  serializationInternalMap,
    666  realm,
    667  extraOptions
    668 ) {
    669  const { includeShadowTree, maxDomDepth } = serializationOptions;
    670  const isAttribute = Attr.isInstance(node);
    671  const isElement = Element.isInstance(node);
    672 
    673  const serialized = {
    674    nodeType: node.nodeType,
    675  };
    676 
    677  if (node.nodeValue !== null) {
    678    serialized.nodeValue = node.nodeValue;
    679  }
    680 
    681  if (isElement || isAttribute) {
    682    serialized.localName = node.localName;
    683    serialized.namespaceURI = node.namespaceURI;
    684  }
    685 
    686  serialized.childNodeCount = node.childNodes.length;
    687  if (
    688    maxDomDepth !== 0 &&
    689    (!lazy.dom.isShadowRoot(node) ||
    690      (includeShadowTree === IncludeShadowTreeMode.Open &&
    691        node.mode === "open") ||
    692      includeShadowTree === IncludeShadowTreeMode.All)
    693  ) {
    694    const children = [];
    695    const childSerializationOptions = {
    696      ...serializationOptions,
    697    };
    698    if (maxDomDepth !== null) {
    699      childSerializationOptions.maxDomDepth = maxDomDepth - 1;
    700    }
    701    for (const child of node.childNodes) {
    702      children.push(
    703        serialize(
    704          child,
    705          childSerializationOptions,
    706          ownershipType,
    707          serializationInternalMap,
    708          realm,
    709          extraOptions
    710        )
    711      );
    712    }
    713 
    714    serialized.children = children;
    715  }
    716 
    717  if (isElement) {
    718    serialized.attributes = [...node.attributes].reduce((map, attr) => {
    719      map[attr.name] = attr.value;
    720      return map;
    721    }, {});
    722 
    723    const shadowRoot = node.openOrClosedShadowRoot;
    724    serialized.shadowRoot = null;
    725    if (shadowRoot !== null) {
    726      serialized.shadowRoot = serialize(
    727        shadowRoot,
    728        serializationOptions,
    729        ownershipType,
    730        serializationInternalMap,
    731        realm,
    732        extraOptions
    733      );
    734    }
    735  }
    736 
    737  if (lazy.dom.isShadowRoot(node)) {
    738    serialized.mode = node.mode;
    739  }
    740 
    741  return serialized;
    742 }
    743 
    744 /**
    745 * Serialize a value as a remote value.
    746 *
    747 * @see https://w3c.github.io/webdriver-bidi/#serialize-as-a-remote-value
    748 *
    749 * @param {object} value
    750 *     Value of any type to be serialized.
    751 * @param {SerializationOptions} serializationOptions
    752 *     Options which define how ECMAScript objects should be serialized.
    753 * @param {OwnershipModel} ownershipType
    754 *     The ownership model to use for this serialization.
    755 * @param {Map} serializationInternalMap
    756 *     Map of internal ids.
    757 * @param {Realm} realm
    758 *     The Realm from which comes the value being serialized.
    759 * @param {ExtraSerializationOptions} extraOptions
    760 *     Extra Remote Value serialization options.
    761 *
    762 * @returns {object} Serialized representation of the value.
    763 */
    764 export function serialize(
    765  value,
    766  serializationOptions,
    767  ownershipType,
    768  serializationInternalMap,
    769  realm,
    770  extraOptions
    771 ) {
    772  const { maxObjectDepth } = serializationOptions;
    773  const type = typeof value;
    774 
    775  // Primitive protocol values
    776  if (type == "undefined") {
    777    return { type };
    778  } else if (Object.is(value, null)) {
    779    return { type: "null" };
    780  } else if (Object.is(value, NaN)) {
    781    return { type: "number", value: "NaN" };
    782  } else if (Object.is(value, -0)) {
    783    return { type: "number", value: "-0" };
    784  } else if (Object.is(value, Infinity)) {
    785    return { type: "number", value: "Infinity" };
    786  } else if (Object.is(value, -Infinity)) {
    787    return { type: "number", value: "-Infinity" };
    788  } else if (type == "bigint") {
    789    return { type, value: value.toString() };
    790  } else if (["boolean", "number", "string"].includes(type)) {
    791    return { type, value };
    792  }
    793 
    794  const handleId = getHandleForObject(realm, ownershipType, value);
    795  const knownObject = serializationInternalMap.has(value);
    796 
    797  // Set the OwnershipModel to use for all complex object serializations.
    798  ownershipType = OwnershipModel.None;
    799 
    800  // Remote values
    801 
    802  // symbols are primitive JS values which can only be serialized
    803  // as remote values.
    804  if (type == "symbol") {
    805    return buildSerialized("symbol", handleId);
    806  }
    807 
    808  // All other remote values are non-primitives and their
    809  // className can be extracted with ChromeUtils.getClassName
    810  const className = ChromeUtils.getClassName(value);
    811  if (["Array", "HTMLCollection", "NodeList"].includes(className)) {
    812    return serializeArrayLike(
    813      className.toLowerCase(),
    814      handleId,
    815      knownObject,
    816      value,
    817      serializationOptions,
    818      ownershipType,
    819      serializationInternalMap,
    820      realm,
    821      extraOptions
    822    );
    823  } else if (className == "RegExp") {
    824    const serialized = buildSerialized("regexp", handleId);
    825    serialized.value = { pattern: value.source, flags: value.flags };
    826    return serialized;
    827  } else if (className == "Date") {
    828    const serialized = buildSerialized("date", handleId);
    829    serialized.value = value.toISOString();
    830    return serialized;
    831  } else if (className == "Map") {
    832    const serialized = buildSerialized("map", handleId);
    833    setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
    834 
    835    if (!knownObject && maxObjectDepth !== 0) {
    836      serialized.value = serializeMapping(
    837        value.entries(),
    838        serializationOptions,
    839        ownershipType,
    840        serializationInternalMap,
    841        realm,
    842        extraOptions
    843      );
    844    }
    845    return serialized;
    846  } else if (className == "Set") {
    847    const serialized = buildSerialized("set", handleId);
    848    setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
    849 
    850    if (!knownObject && maxObjectDepth !== 0) {
    851      serialized.value = serializeList(
    852        value.values(),
    853        serializationOptions,
    854        ownershipType,
    855        serializationInternalMap,
    856        realm,
    857        extraOptions
    858      );
    859    }
    860    return serialized;
    861  } else if (
    862    ["ArrayBuffer", "Function", "Promise", "WeakMap", "WeakSet"].includes(
    863      className
    864    )
    865  ) {
    866    return buildSerialized(className.toLowerCase(), handleId);
    867  } else if (className.includes("Generator")) {
    868    return buildSerialized("generator", handleId);
    869  } else if (lazy.error.isError(value)) {
    870    return buildSerialized("error", handleId);
    871  } else if (Cu.isProxy(value)) {
    872    return buildSerialized("proxy", handleId);
    873  } else if (TYPED_ARRAY_CLASSES.includes(className)) {
    874    return buildSerialized("typedarray", handleId);
    875  } else if (Node.isInstance(value)) {
    876    const serialized = buildSerialized("node", handleId);
    877 
    878    value = Cu.unwaiveXrays(value);
    879 
    880    // Get or create the shared id for WebDriver classic compat from the node.
    881    const sharedId = getSharedIdForNode(value, extraOptions);
    882    if (sharedId !== null) {
    883      serialized.sharedId = sharedId;
    884    }
    885 
    886    setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
    887 
    888    if (!knownObject) {
    889      serialized.value = serializeNode(
    890        value,
    891        serializationOptions,
    892        ownershipType,
    893        serializationInternalMap,
    894        realm,
    895        extraOptions
    896      );
    897    }
    898 
    899    return serialized;
    900  } else if (Window.isInstance(value)) {
    901    const serialized = buildSerialized("window", handleId);
    902    const window = Cu.unwaiveXrays(value);
    903 
    904    if (window.browsingContext.parent == null) {
    905      serialized.value = {
    906        context: window.browsingContext.browserId.toString(),
    907        isTopBrowsingContext: true,
    908      };
    909    } else {
    910      serialized.value = {
    911        context: window.browsingContext.id.toString(),
    912      };
    913    }
    914 
    915    return serialized;
    916  } else if (ChromeUtils.isDOMObject(value)) {
    917    const serialized = buildSerialized("object", handleId);
    918    return serialized;
    919  }
    920 
    921  // Otherwise serialize the JavaScript object as generic object.
    922  const serialized = buildSerialized("object", handleId);
    923  setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
    924 
    925  if (!knownObject && maxObjectDepth !== 0) {
    926    serialized.value = serializeMapping(
    927      Object.entries(value),
    928      serializationOptions,
    929      ownershipType,
    930      serializationInternalMap,
    931      realm,
    932      extraOptions
    933    );
    934  }
    935  return serialized;
    936 }
    937 
    938 /**
    939 * Set default serialization options.
    940 *
    941 * @param {SerializationOptions} options
    942 *    Options which define how ECMAScript objects should be serialized.
    943 * @returns {SerializationOptions}
    944 *    Serialiation options with default value added.
    945 */
    946 export function setDefaultSerializationOptions(options = {}) {
    947  const serializationOptions = { ...options };
    948  if (!("maxDomDepth" in serializationOptions)) {
    949    serializationOptions.maxDomDepth = 0;
    950  }
    951  if (!("maxObjectDepth" in serializationOptions)) {
    952    serializationOptions.maxObjectDepth = null;
    953  }
    954  if (!("includeShadowTree" in serializationOptions)) {
    955    serializationOptions.includeShadowTree = IncludeShadowTreeMode.None;
    956  }
    957 
    958  return serializationOptions;
    959 }
    960 
    961 /**
    962 * Set default values and assert if serialization options have
    963 * expected types.
    964 *
    965 * @param {SerializationOptions} options
    966 *    Options which define how ECMAScript objects should be serialized.
    967 * @returns {SerializationOptions}
    968 *    Serialiation options with default value added.
    969 */
    970 export function setDefaultAndAssertSerializationOptions(options = {}) {
    971  lazy.assert.object(
    972    options,
    973    lazy.pprint`Expected "options" to be an object, got ${options}`
    974  );
    975 
    976  const serializationOptions = setDefaultSerializationOptions(options);
    977 
    978  const { includeShadowTree, maxDomDepth, maxObjectDepth } =
    979    serializationOptions;
    980 
    981  if (maxDomDepth !== null) {
    982    lazy.assert.positiveInteger(
    983      maxDomDepth,
    984      lazy.pprint`Expected "maxDomDepth" to be a positive integer or null, got ${maxDomDepth}`
    985    );
    986  }
    987  if (maxObjectDepth !== null) {
    988    lazy.assert.positiveInteger(
    989      maxObjectDepth,
    990      lazy.pprint`Expected "maxObjectDepth" to be a positive integer or null, got ${maxObjectDepth}`
    991    );
    992  }
    993  const includeShadowTreeModesValues = Object.values(IncludeShadowTreeMode);
    994  lazy.assert.that(
    995    includeShadowTree =>
    996      includeShadowTreeModesValues.includes(includeShadowTree),
    997    `Expected "includeShadowTree" to be one of ${includeShadowTreeModesValues}, ` +
    998      lazy.pprint`got ${includeShadowTree}`
    999  )(includeShadowTree);
   1000 
   1001  return serializationOptions;
   1002 }
   1003 
   1004 /**
   1005 * Set the internalId property of a provided serialized RemoteValue,
   1006 * and potentially of a previously created serialized RemoteValue,
   1007 * corresponding to the same provided object.
   1008 *
   1009 * @see https://w3c.github.io/webdriver-bidi/#set-internal-ids-if-needed
   1010 *
   1011 * @param {Map} serializationInternalMap
   1012 *     Map of objects to remote values.
   1013 * @param {object} remoteValue
   1014 *     A serialized RemoteValue for the provided object.
   1015 * @param {object} object
   1016 *     Object of any type to be serialized.
   1017 */
   1018 function setInternalIdsIfNeeded(serializationInternalMap, remoteValue, object) {
   1019  if (!serializationInternalMap.has(object)) {
   1020    // If the object was not tracked yet in the current serialization, add
   1021    // a new entry in the serialization internal map. An internal id will only
   1022    // be generated if the same object is encountered again.
   1023    serializationInternalMap.set(object, remoteValue);
   1024  } else {
   1025    // This is at least the second time this object is encountered, retrieve the
   1026    // original remote value stored for this object.
   1027    const previousRemoteValue = serializationInternalMap.get(object);
   1028 
   1029    if (!previousRemoteValue.internalId) {
   1030      // If the original remote value has no internal id yet, generate a uuid
   1031      // and update the internalId of the original remote value with it.
   1032      previousRemoteValue.internalId = lazy.generateUUID();
   1033    }
   1034 
   1035    // Copy the internalId of the original remote value to the new remote value.
   1036    remoteValue.internalId = previousRemoteValue.internalId;
   1037  }
   1038 }
   1039 
   1040 /**
   1041 * Safely stringify a value.
   1042 *
   1043 * @param {object} obj
   1044 *     Value of any type to be stringified.
   1045 *
   1046 * @returns {string} String representation of the value.
   1047 */
   1048 export function stringify(obj) {
   1049  let text;
   1050  try {
   1051    text =
   1052      obj !== null && typeof obj === "object" ? obj.toString() : String(obj);
   1053  } catch (e) {
   1054    // The error-case will also be handled in `finally {}`.
   1055  } finally {
   1056    if (typeof text != "string") {
   1057      text = Object.prototype.toString.apply(obj);
   1058    }
   1059  }
   1060 
   1061  return text;
   1062 }