Bug 1866894 - Update failing subtest for content-visibility-auto-resize.html. r=fredw
[gecko.git] / remote / webdriver-bidi / RemoteValue.sys.mjs
blobcfe1002815919d300fc39551c1610242727c4d71
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 validate if a date string follows Date Time String format.
122  * @see https://tc39.es/ecma262/#sec-date-time-string-format
124  * @param {string} dateString
125  *     String which needs to be validated.
127  * @throws {InvalidArgumentError}
128  *     If <var>dateString</var> doesn't follow the format.
129  */
130 function checkDateTimeString(dateString) {
131   // Check if a date string follows a simplification of
132   // the ISO 8601 calendar date extended format.
133   const expandedYear = "[+-]\\d{6}";
134   const year = "\\d{4}";
135   const YYYY = `${expandedYear}|${year}`;
136   const MM = "\\d{2}";
137   const DD = "\\d{2}";
138   const date = `${YYYY}(?:-${MM})?(?:-${DD})?`;
139   const HH_mm = "\\d{2}:\\d{2}";
140   const SS = "\\d{2}";
141   const sss = "\\d{3}";
142   const TZ = `Z|[+-]${HH_mm}`;
143   const time = `T${HH_mm}(?::${SS}(?:\\.${sss})?(?:${TZ})?)?`;
144   const iso8601Format = new RegExp(`^${date}(?:${time})?$`);
146   // Check also if a date string is a valid date.
147   if (Number.isNaN(Date.parse(dateString)) || !iso8601Format.test(dateString)) {
148     throw new lazy.error.InvalidArgumentError(
149       `Expected "value" for Date to be a Date Time string, got ${dateString}`
150     );
151   }
155  * Helper to deserialize value list.
157  * @see https://w3c.github.io/webdriver-bidi/#deserialize-value-list
159  * @param {Realm} realm
160  *     The Realm in which the value is deserialized.
161  * @param {Array} serializedValueList
162  *     List of serialized values.
163  * @param {ExtraDeserializationOptions} extraOptions
164  *     Extra Remote Value deserialization options.
166  * @returns {Array} List of deserialized values.
168  * @throws {InvalidArgumentError}
169  *     If <var>serializedValueList</var> is not an array.
170  */
171 function deserializeValueList(realm, serializedValueList, extraOptions) {
172   lazy.assert.array(
173     serializedValueList,
174     `Expected "serializedValueList" to be an array, got ${serializedValueList}`
175   );
177   const deserializedValues = [];
179   for (const item of serializedValueList) {
180     deserializedValues.push(deserialize(realm, item, extraOptions));
181   }
183   return deserializedValues;
187  * Helper to deserialize key-value list.
189  * @see https://w3c.github.io/webdriver-bidi/#deserialize-key-value-list
191  * @param {Realm} realm
192  *     The Realm in which the value is deserialized.
193  * @param {Array} serializedKeyValueList
194  *     List of serialized key-value.
195  * @param {ExtraDeserializationOptions} extraOptions
196  *     Extra Remote Value deserialization options.
198  * @returns {Array} List of deserialized key-value.
200  * @throws {InvalidArgumentError}
201  *     If <var>serializedKeyValueList</var> is not an array or
202  *     not an array of key-value arrays.
203  */
204 function deserializeKeyValueList(realm, serializedKeyValueList, extraOptions) {
205   lazy.assert.array(
206     serializedKeyValueList,
207     `Expected "serializedKeyValueList" to be an array, got ${serializedKeyValueList}`
208   );
210   const deserializedKeyValueList = [];
212   for (const serializedKeyValue of serializedKeyValueList) {
213     if (!Array.isArray(serializedKeyValue) || serializedKeyValue.length != 2) {
214       throw new lazy.error.InvalidArgumentError(
215         `Expected key-value pair to be an array with 2 elements, got ${serializedKeyValue}`
216       );
217     }
218     const [serializedKey, serializedValue] = serializedKeyValue;
219     const deserializedKey =
220       typeof serializedKey == "string"
221         ? serializedKey
222         : deserialize(realm, serializedKey, extraOptions);
223     const deserializedValue = deserialize(realm, serializedValue, extraOptions);
225     deserializedKeyValueList.push([deserializedKey, deserializedValue]);
226   }
228   return deserializedKeyValueList;
232  * Deserialize a Node as referenced by the shared unique reference.
234  * This unique reference can be shared by WebDriver clients with the WebDriver
235  * classic implementation (Marionette) if the reference is for an Element or
236  * ShadowRoot.
238  * @param {string} sharedRef
239  *     Shared unique reference of the Node.
240  * @param {Realm} realm
241  *     The Realm in which the value is deserialized.
242  * @param {ExtraDeserializationOptions} extraOptions
243  *     Extra Remote Value deserialization options.
245  * @returns {Node} The deserialized DOM node.
246  */
247 function deserializeSharedReference(sharedRef, realm, extraOptions) {
248   const { nodeCache } = extraOptions;
250   const browsingContext = realm.browsingContext;
251   if (!browsingContext) {
252     throw new lazy.error.NoSuchNodeError("Realm isn't a Window global");
253   }
255   const node = nodeCache.getNode(browsingContext, sharedRef);
257   if (node === null) {
258     throw new lazy.error.NoSuchNodeError(
259       `The node with the reference ${sharedRef} is not known`
260     );
261   }
263   // Bug 1819902: Instead of a browsing context check compare the origin
264   const isSameBrowsingContext = sharedRef => {
265     const nodeDetails = nodeCache.getReferenceDetails(sharedRef);
267     if (nodeDetails.isTopBrowsingContext && browsingContext.parent === null) {
268       // As long as Navigables are not available any cross-group navigation will
269       // cause a swap of the current top-level browsing context. The only unique
270       // identifier in such a case is the browser id the top-level browsing
271       // context actually lives in.
272       return nodeDetails.browserId === browsingContext.browserId;
273     }
275     return nodeDetails.browsingContextId === browsingContext.id;
276   };
278   if (!isSameBrowsingContext(sharedRef)) {
279     return null;
280   }
282   return node;
286  * Deserialize a local value.
288  * @see https://w3c.github.io/webdriver-bidi/#deserialize-local-value
290  * @param {Realm} realm
291  *     The Realm in which the value is deserialized.
292  * @param {object} serializedValue
293  *     Value of any type to be deserialized.
294  * @param {ExtraDeserializationOptions} extraOptions
295  *     Extra Remote Value deserialization options.
297  * @returns {object} Deserialized representation of the value.
298  */
299 export function deserialize(realm, serializedValue, extraOptions) {
300   const { handle, sharedId, type, value } = serializedValue;
302   // With a shared id present deserialize as node reference.
303   if (sharedId !== undefined) {
304     lazy.assert.string(
305       sharedId,
306       `Expected "sharedId" to be a string, got ${sharedId}`
307     );
309     return deserializeSharedReference(sharedId, realm, extraOptions);
310   }
312   // With a handle present deserialize as remote reference.
313   if (handle !== undefined) {
314     lazy.assert.string(
315       handle,
316       `Expected "handle" to be a string, got ${handle}`
317     );
319     const object = realm.getObjectForHandle(handle);
320     if (!object) {
321       throw new lazy.error.NoSuchHandleError(
322         `Unable to find an object reference for "handle" ${handle}`
323       );
324     }
326     return object;
327   }
329   lazy.assert.string(type, `Expected "type" to be a string, got ${type}`);
331   // Primitive protocol values
332   switch (type) {
333     case "undefined":
334       return undefined;
335     case "null":
336       return null;
337     case "string":
338       lazy.assert.string(
339         value,
340         `Expected "value" to be a string, got ${value}`
341       );
342       return value;
343     case "number":
344       // If value is already a number return its value.
345       if (typeof value === "number") {
346         return value;
347       }
349       // Otherwise it has to be one of the special strings
350       lazy.assert.in(
351         value,
352         ["NaN", "-0", "Infinity", "-Infinity"],
353         `Expected "value" to be one of "NaN", "-0", "Infinity", "-Infinity", got ${value}`
354       );
355       return Number(value);
356     case "boolean":
357       lazy.assert.boolean(
358         value,
359         `Expected "value" to be a boolean, got ${value}`
360       );
361       return value;
362     case "bigint":
363       lazy.assert.string(
364         value,
365         `Expected "value" to be a string, got ${value}`
366       );
367       try {
368         return BigInt(value);
369       } catch (e) {
370         throw new lazy.error.InvalidArgumentError(
371           `Failed to deserialize value as BigInt: ${value}`
372         );
373       }
375     // Script channel
376     case "channel": {
377       const channel = message =>
378         extraOptions.emitScriptMessage(realm, value, message);
379       return realm.cloneIntoRealm(channel);
380     }
382     // Non-primitive protocol values
383     case "array":
384       const array = realm.cloneIntoRealm([]);
385       deserializeValueList(realm, value, extraOptions).forEach(v =>
386         array.push(v)
387       );
388       return array;
389     case "date":
390       // We want to support only Date Time String format,
391       // check if the value follows it.
392       checkDateTimeString(value);
394       return realm.cloneIntoRealm(new Date(value));
395     case "map":
396       const map = realm.cloneIntoRealm(new Map());
397       deserializeKeyValueList(realm, value, extraOptions).forEach(([k, v]) =>
398         map.set(k, v)
399       );
401       return map;
402     case "object":
403       const object = realm.cloneIntoRealm({});
404       deserializeKeyValueList(realm, value, extraOptions).forEach(
405         ([k, v]) => (object[k] = v)
406       );
407       return object;
408     case "regexp":
409       lazy.assert.object(
410         value,
411         `Expected "value" for RegExp to be an object, got ${value}`
412       );
413       const { pattern, flags } = value;
414       lazy.assert.string(
415         pattern,
416         `Expected "pattern" for RegExp to be a string, got ${pattern}`
417       );
418       if (flags !== undefined) {
419         lazy.assert.string(
420           flags,
421           `Expected "flags" for RegExp to be a string, got ${flags}`
422         );
423       }
424       try {
425         return realm.cloneIntoRealm(new RegExp(pattern, flags));
426       } catch (e) {
427         throw new lazy.error.InvalidArgumentError(
428           `Failed to deserialize value as RegExp: ${value}`
429         );
430       }
431     case "set":
432       const set = realm.cloneIntoRealm(new Set());
433       deserializeValueList(realm, value, extraOptions).forEach(v => set.add(v));
434       return set;
435   }
437   lazy.logger.warn(`Unsupported type for local value ${type}`);
438   return undefined;
442  * Helper to retrieve the handle id for a given object, for the provided realm
443  * and ownership type.
445  * See https://w3c.github.io/webdriver-bidi/#handle-for-an-object
447  * @param {Realm} realm
448  *     The Realm from which comes the value being serialized.
449  * @param {OwnershipModel} ownershipType
450  *     The ownership model to use for this serialization.
451  * @param {object} object
452  *     The object being serialized.
454  * @returns {string} The unique handle id for the object. Will be null if the
455  *     Ownership type is "none".
456  */
457 function getHandleForObject(realm, ownershipType, object) {
458   if (ownershipType === OwnershipModel.None) {
459     return null;
460   }
461   return realm.getHandleForObject(object);
465  * Gets or creates a new shared unique reference for the DOM node.
467  * This unique reference can be shared by WebDriver clients with the WebDriver
468  * classic implementation (Marionette) if the reference is for an Element or
469  * ShadowRoot.
471  * @param {Node} node
472  *    Node to create the unique reference for.
473  * @param {ExtraSerializationOptions} extraOptions
474  *     Extra Remote Value serialization options.
476  * @returns {string}
477  *    Shared unique reference for the Node.
478  */
479 function getSharedIdForNode(node, extraOptions) {
480   const { nodeCache, seenNodeIds } = extraOptions;
482   if (!Node.isInstance(node)) {
483     return null;
484   }
486   const browsingContext = node.ownerGlobal.browsingContext;
487   if (!browsingContext) {
488     return null;
489   }
491   return nodeCache.getOrCreateNodeReference(node, seenNodeIds);
495  * Helper to serialize an Array-like object.
497  * @see https://w3c.github.io/webdriver-bidi/#serialize-an-array-like
499  * @param {string} production
500  *     Type of object
501  * @param {string} handleId
502  *     The unique id of the <var>value</var>.
503  * @param {boolean} knownObject
504  *     Indicates if the <var>value</var> has already been serialized.
505  * @param {object} value
506  *     The Array-like object to serialize.
507  * @param {SerializationOptions} serializationOptions
508  *     Options which define how ECMAScript objects should be serialized.
509  * @param {OwnershipModel} ownershipType
510  *     The ownership model to use for this serialization.
511  * @param {Map} serializationInternalMap
512  *     Map of internal ids.
513  * @param {Realm} realm
514  *     The Realm from which comes the value being serialized.
515  * @param {ExtraSerializationOptions} extraOptions
516  *     Extra Remote Value serialization options.
518  * @returns {object} Object for serialized values.
519  */
520 function serializeArrayLike(
521   production,
522   handleId,
523   knownObject,
524   value,
525   serializationOptions,
526   ownershipType,
527   serializationInternalMap,
528   realm,
529   extraOptions
530 ) {
531   const serialized = buildSerialized(production, handleId);
532   setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
534   if (!knownObject && serializationOptions.maxObjectDepth !== 0) {
535     serialized.value = serializeList(
536       value,
537       serializationOptions,
538       ownershipType,
539       serializationInternalMap,
540       realm,
541       extraOptions
542     );
543   }
545   return serialized;
549  * Helper to serialize as a list.
551  * @see https://w3c.github.io/webdriver-bidi/#serialize-as-a-list
553  * @param {Iterable} iterable
554  *     List of values to be serialized.
555  * @param {SerializationOptions} serializationOptions
556  *     Options which define how ECMAScript objects should be serialized.
557  * @param {OwnershipModel} ownershipType
558  *     The ownership model to use for this serialization.
559  * @param {Map} serializationInternalMap
560  *     Map of internal ids.
561  * @param {Realm} realm
562  *     The Realm from which comes the value being serialized.
563  * @param {ExtraSerializationOptions} extraOptions
564  *     Extra Remote Value serialization options.
566  * @returns {Array} List of serialized values.
567  */
568 function serializeList(
569   iterable,
570   serializationOptions,
571   ownershipType,
572   serializationInternalMap,
573   realm,
574   extraOptions
575 ) {
576   const { maxObjectDepth } = serializationOptions;
577   const serialized = [];
578   const childSerializationOptions = {
579     ...serializationOptions,
580   };
581   if (maxObjectDepth !== null) {
582     childSerializationOptions.maxObjectDepth = maxObjectDepth - 1;
583   }
585   for (const item of iterable) {
586     serialized.push(
587       serialize(
588         item,
589         childSerializationOptions,
590         ownershipType,
591         serializationInternalMap,
592         realm,
593         extraOptions
594       )
595     );
596   }
598   return serialized;
602  * Helper to serialize as a mapping.
604  * @see https://w3c.github.io/webdriver-bidi/#serialize-as-a-mapping
606  * @param {Iterable} iterable
607  *     List of values to be serialized.
608  * @param {SerializationOptions} serializationOptions
609  *     Options which define how ECMAScript objects should be serialized.
610  * @param {OwnershipModel} ownershipType
611  *     The ownership model to use for this serialization.
612  * @param {Map} serializationInternalMap
613  *     Map of internal ids.
614  * @param {Realm} realm
615  *     The Realm from which comes the value being serialized.
616  * @param {ExtraSerializationOptions} extraOptions
617  *     Extra Remote Value serialization options.
619  * @returns {Array} List of serialized values.
620  */
621 function serializeMapping(
622   iterable,
623   serializationOptions,
624   ownershipType,
625   serializationInternalMap,
626   realm,
627   extraOptions
628 ) {
629   const { maxObjectDepth } = serializationOptions;
630   const serialized = [];
631   const childSerializationOptions = {
632     ...serializationOptions,
633   };
634   if (maxObjectDepth !== null) {
635     childSerializationOptions.maxObjectDepth = maxObjectDepth - 1;
636   }
638   for (const [key, item] of iterable) {
639     const serializedKey =
640       typeof key == "string"
641         ? key
642         : serialize(
643             key,
644             childSerializationOptions,
645             ownershipType,
646             serializationInternalMap,
647             realm,
648             extraOptions
649           );
650     const serializedValue = serialize(
651       item,
652       childSerializationOptions,
653       ownershipType,
654       serializationInternalMap,
655       realm,
656       extraOptions
657     );
659     serialized.push([serializedKey, serializedValue]);
660   }
662   return serialized;
666  * Helper to serialize as a Node.
668  * @param {Node} node
669  *     Node to be serialized.
670  * @param {SerializationOptions} serializationOptions
671  *     Options which define how ECMAScript objects should be serialized.
672  * @param {OwnershipModel} ownershipType
673  *     The ownership model to use for this serialization.
674  * @param {Map} serializationInternalMap
675  *     Map of internal ids.
676  * @param {Realm} realm
677  *     The Realm from which comes the value being serialized.
678  * @param {ExtraSerializationOptions} extraOptions
679  *     Extra Remote Value serialization options.
681  * @returns {object} Serialized value.
682  */
683 function serializeNode(
684   node,
685   serializationOptions,
686   ownershipType,
687   serializationInternalMap,
688   realm,
689   extraOptions
690 ) {
691   const { includeShadowTree, maxDomDepth } = serializationOptions;
692   const isAttribute = Attr.isInstance(node);
693   const isElement = Element.isInstance(node);
695   const serialized = {
696     nodeType: node.nodeType,
697   };
699   if (node.nodeValue !== null) {
700     serialized.nodeValue = node.nodeValue;
701   }
703   if (isElement || isAttribute) {
704     serialized.localName = node.localName;
705     serialized.namespaceURI = node.namespaceURI;
706   }
708   serialized.childNodeCount = node.childNodes.length;
709   if (
710     maxDomDepth !== 0 &&
711     (!lazy.dom.isShadowRoot(node) ||
712       (includeShadowTree === IncludeShadowTreeMode.Open &&
713         node.mode === "open") ||
714       includeShadowTree === IncludeShadowTreeMode.All)
715   ) {
716     const children = [];
717     const childSerializationOptions = {
718       ...serializationOptions,
719     };
720     if (maxDomDepth !== null) {
721       childSerializationOptions.maxDomDepth = maxDomDepth - 1;
722     }
723     for (const child of node.childNodes) {
724       children.push(
725         serialize(
726           child,
727           childSerializationOptions,
728           ownershipType,
729           serializationInternalMap,
730           realm,
731           extraOptions
732         )
733       );
734     }
736     serialized.children = children;
737   }
739   if (isElement) {
740     serialized.attributes = [...node.attributes].reduce((map, attr) => {
741       map[attr.name] = attr.value;
742       return map;
743     }, {});
745     const shadowRoot = node.openOrClosedShadowRoot;
746     serialized.shadowRoot = null;
747     if (shadowRoot !== null) {
748       serialized.shadowRoot = serialize(
749         shadowRoot,
750         serializationOptions,
751         ownershipType,
752         serializationInternalMap,
753         realm,
754         extraOptions
755       );
756     }
757   }
759   if (lazy.dom.isShadowRoot(node)) {
760     serialized.mode = node.mode;
761   }
763   return serialized;
767  * Serialize a value as a remote value.
769  * @see https://w3c.github.io/webdriver-bidi/#serialize-as-a-remote-value
771  * @param {object} value
772  *     Value of any type to be serialized.
773  * @param {SerializationOptions} serializationOptions
774  *     Options which define how ECMAScript objects should be serialized.
775  * @param {OwnershipModel} ownershipType
776  *     The ownership model to use for this serialization.
777  * @param {Map} serializationInternalMap
778  *     Map of internal ids.
779  * @param {Realm} realm
780  *     The Realm from which comes the value being serialized.
781  * @param {ExtraSerializationOptions} extraOptions
782  *     Extra Remote Value serialization options.
784  * @returns {object} Serialized representation of the value.
785  */
786 export function serialize(
787   value,
788   serializationOptions,
789   ownershipType,
790   serializationInternalMap,
791   realm,
792   extraOptions
793 ) {
794   const { maxObjectDepth } = serializationOptions;
795   const type = typeof value;
797   // Primitive protocol values
798   if (type == "undefined") {
799     return { type };
800   } else if (Object.is(value, null)) {
801     return { type: "null" };
802   } else if (Object.is(value, NaN)) {
803     return { type: "number", value: "NaN" };
804   } else if (Object.is(value, -0)) {
805     return { type: "number", value: "-0" };
806   } else if (Object.is(value, Infinity)) {
807     return { type: "number", value: "Infinity" };
808   } else if (Object.is(value, -Infinity)) {
809     return { type: "number", value: "-Infinity" };
810   } else if (type == "bigint") {
811     return { type, value: value.toString() };
812   } else if (["boolean", "number", "string"].includes(type)) {
813     return { type, value };
814   }
816   const handleId = getHandleForObject(realm, ownershipType, value);
817   const knownObject = serializationInternalMap.has(value);
819   // Set the OwnershipModel to use for all complex object serializations.
820   ownershipType = OwnershipModel.None;
822   // Remote values
824   // symbols are primitive JS values which can only be serialized
825   // as remote values.
826   if (type == "symbol") {
827     return buildSerialized("symbol", handleId);
828   }
830   // All other remote values are non-primitives and their
831   // className can be extracted with ChromeUtils.getClassName
832   const className = ChromeUtils.getClassName(value);
833   if (["Array", "HTMLCollection", "NodeList"].includes(className)) {
834     return serializeArrayLike(
835       className.toLowerCase(),
836       handleId,
837       knownObject,
838       value,
839       serializationOptions,
840       ownershipType,
841       serializationInternalMap,
842       realm,
843       extraOptions
844     );
845   } else if (className == "RegExp") {
846     const serialized = buildSerialized("regexp", handleId);
847     serialized.value = { pattern: value.source, flags: value.flags };
848     return serialized;
849   } else if (className == "Date") {
850     const serialized = buildSerialized("date", handleId);
851     serialized.value = value.toISOString();
852     return serialized;
853   } else if (className == "Map") {
854     const serialized = buildSerialized("map", handleId);
855     setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
857     if (!knownObject && maxObjectDepth !== 0) {
858       serialized.value = serializeMapping(
859         value.entries(),
860         serializationOptions,
861         ownershipType,
862         serializationInternalMap,
863         realm,
864         extraOptions
865       );
866     }
867     return serialized;
868   } else if (className == "Set") {
869     const serialized = buildSerialized("set", handleId);
870     setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
872     if (!knownObject && maxObjectDepth !== 0) {
873       serialized.value = serializeList(
874         value.values(),
875         serializationOptions,
876         ownershipType,
877         serializationInternalMap,
878         realm,
879         extraOptions
880       );
881     }
882     return serialized;
883   } else if (
884     ["ArrayBuffer", "Function", "Promise", "WeakMap", "WeakSet"].includes(
885       className
886     )
887   ) {
888     return buildSerialized(className.toLowerCase(), handleId);
889   } else if (className.includes("Generator")) {
890     return buildSerialized("generator", handleId);
891   } else if (lazy.error.isError(value)) {
892     return buildSerialized("error", handleId);
893   } else if (Cu.isProxy(value)) {
894     return buildSerialized("proxy", handleId);
895   } else if (TYPED_ARRAY_CLASSES.includes(className)) {
896     return buildSerialized("typedarray", handleId);
897   } else if (Node.isInstance(value)) {
898     const serialized = buildSerialized("node", handleId);
900     value = Cu.unwaiveXrays(value);
902     // Get or create the shared id for WebDriver classic compat from the node.
903     const sharedId = getSharedIdForNode(value, extraOptions);
904     if (sharedId !== null) {
905       serialized.sharedId = sharedId;
906     }
908     setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
910     if (!knownObject) {
911       serialized.value = serializeNode(
912         value,
913         serializationOptions,
914         ownershipType,
915         serializationInternalMap,
916         realm,
917         extraOptions
918       );
919     }
921     return serialized;
922   } else if (className === "Window") {
923     const serialized = buildSerialized("window", handleId);
924     const window = Cu.unwaiveXrays(value);
926     if (window.browsingContext.parent == null) {
927       serialized.value = {
928         context: window.browsingContext.browserId.toString(),
929         isTopBrowsingContext: true,
930       };
931     } else {
932       serialized.value = {
933         context: window.browsingContext.id.toString(),
934       };
935     }
937     return serialized;
938   } else if (ChromeUtils.isDOMObject(value)) {
939     const serialized = buildSerialized("object", handleId);
940     return serialized;
941   }
943   // Otherwise serialize the JavaScript object as generic object.
944   const serialized = buildSerialized("object", handleId);
945   setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
947   if (!knownObject && maxObjectDepth !== 0) {
948     serialized.value = serializeMapping(
949       Object.entries(value),
950       serializationOptions,
951       ownershipType,
952       serializationInternalMap,
953       realm,
954       extraOptions
955     );
956   }
957   return serialized;
961  * Set default serialization options.
963  * @param {SerializationOptions} options
964  *    Options which define how ECMAScript objects should be serialized.
965  * @returns {SerializationOptions}
966  *    Serialiation options with default value added.
967  */
968 export function setDefaultSerializationOptions(options = {}) {
969   const serializationOptions = { ...options };
970   if (!("maxDomDepth" in serializationOptions)) {
971     serializationOptions.maxDomDepth = 0;
972   }
973   if (!("maxObjectDepth" in serializationOptions)) {
974     serializationOptions.maxObjectDepth = null;
975   }
976   if (!("includeShadowTree" in serializationOptions)) {
977     serializationOptions.includeShadowTree = IncludeShadowTreeMode.None;
978   }
980   return serializationOptions;
984  * Set default values and assert if serialization options have
985  * expected types.
987  * @param {SerializationOptions} options
988  *    Options which define how ECMAScript objects should be serialized.
989  * @returns {SerializationOptions}
990  *    Serialiation options with default value added.
991  */
992 export function setDefaultAndAssertSerializationOptions(options = {}) {
993   lazy.assert.object(options);
995   const serializationOptions = setDefaultSerializationOptions(options);
997   const { includeShadowTree, maxDomDepth, maxObjectDepth } =
998     serializationOptions;
1000   if (maxDomDepth !== null) {
1001     lazy.assert.positiveInteger(maxDomDepth);
1002   }
1003   if (maxObjectDepth !== null) {
1004     lazy.assert.positiveInteger(maxObjectDepth);
1005   }
1006   const includeShadowTreeModesValues = Object.values(IncludeShadowTreeMode);
1007   lazy.assert.that(
1008     includeShadowTree =>
1009       includeShadowTreeModesValues.includes(includeShadowTree),
1010     `includeShadowTree ${includeShadowTree} doesn't match allowed values "${includeShadowTreeModesValues.join(
1011       "/"
1012     )}"`
1013   )(includeShadowTree);
1015   return serializationOptions;
1019  * Set the internalId property of a provided serialized RemoteValue,
1020  * and potentially of a previously created serialized RemoteValue,
1021  * corresponding to the same provided object.
1023  * @see https://w3c.github.io/webdriver-bidi/#set-internal-ids-if-needed
1025  * @param {Map} serializationInternalMap
1026  *     Map of objects to remote values.
1027  * @param {object} remoteValue
1028  *     A serialized RemoteValue for the provided object.
1029  * @param {object} object
1030  *     Object of any type to be serialized.
1031  */
1032 function setInternalIdsIfNeeded(serializationInternalMap, remoteValue, object) {
1033   if (!serializationInternalMap.has(object)) {
1034     // If the object was not tracked yet in the current serialization, add
1035     // a new entry in the serialization internal map. An internal id will only
1036     // be generated if the same object is encountered again.
1037     serializationInternalMap.set(object, remoteValue);
1038   } else {
1039     // This is at least the second time this object is encountered, retrieve the
1040     // original remote value stored for this object.
1041     const previousRemoteValue = serializationInternalMap.get(object);
1043     if (!previousRemoteValue.internalId) {
1044       // If the original remote value has no internal id yet, generate a uuid
1045       // and update the internalId of the original remote value with it.
1046       previousRemoteValue.internalId = lazy.generateUUID();
1047     }
1049     // Copy the internalId of the original remote value to the new remote value.
1050     remoteValue.internalId = previousRemoteValue.internalId;
1051   }
1055  * Safely stringify a value.
1057  * @param {object} obj
1058  *     Value of any type to be stringified.
1060  * @returns {string} String representation of the value.
1061  */
1062 export function stringify(obj) {
1063   let text;
1064   try {
1065     text =
1066       obj !== null && typeof obj === "object" ? obj.toString() : String(obj);
1067   } catch (e) {
1068     // The error-case will also be handled in `finally {}`.
1069   } finally {
1070     if (typeof text != "string") {
1071       text = Object.prototype.toString.apply(obj);
1072     }
1073   }
1075   return text;