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