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/. */
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",
15 ChromeUtils.defineLazyGetter(lazy, "logger", () =>
16 lazy.Log.get(lazy.Log.TYPES.WEBDRIVER_BIDI)
20 * @typedef {object} IncludeShadowTreeMode
24 * Enum of include shadow tree modes supported by the serialization.
27 * @enum {IncludeShadowTreeMode}
29 export const IncludeShadowTreeMode = {
36 * @typedef {object} OwnershipModel
40 * Enum of ownership models supported by the serialization.
43 * @enum {OwnershipModel}
45 export const OwnershipModel = {
51 * Extra options for deserializing remote values.
53 * @typedef {object} ExtraDeserializationOptions
55 * @property {NodeCache=} nodeCache
56 * The cache containing DOM node references.
57 * @property {Function=} emitScriptMessage
58 * The function to emit "script.message" event.
62 * Extra options for serializing remote values.
64 * @typedef {object} ExtraSerializationOptions
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
74 * An object which holds the information of how
75 * ECMAScript objects should be serialized.
77 * @typedef {object} SerializationOptions
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".
87 const TYPED_ARRAY_CLASSES = [
102 * Build the serialized RemoteValue.
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.
109 function buildSerialized(type, handle = null) {
110 const serialized = { type };
112 if (handle !== null) {
113 serialized.handle = handle;
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.
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}`;
138 const date = `${YYYY}(?:-${MM})?(?:-${DD})?`;
139 const HH_mm = "\\d{2}:\\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}`
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.
171 function deserializeValueList(realm, serializedValueList, extraOptions) {
174 `Expected "serializedValueList" to be an array, got ${serializedValueList}`
177 const deserializedValues = [];
179 for (const item of serializedValueList) {
180 deserializedValues.push(deserialize(realm, item, extraOptions));
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.
204 function deserializeKeyValueList(realm, serializedKeyValueList, extraOptions) {
206 serializedKeyValueList,
207 `Expected "serializedKeyValueList" to be an array, got ${serializedKeyValueList}`
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}`
218 const [serializedKey, serializedValue] = serializedKeyValue;
219 const deserializedKey =
220 typeof serializedKey == "string"
222 : deserialize(realm, serializedKey, extraOptions);
223 const deserializedValue = deserialize(realm, serializedValue, extraOptions);
225 deserializedKeyValueList.push([deserializedKey, deserializedValue]);
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
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.
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");
255 const node = nodeCache.getNode(browsingContext, sharedRef);
258 throw new lazy.error.NoSuchNodeError(
259 `The node with the reference ${sharedRef} is not known`
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;
275 return nodeDetails.browsingContextId === browsingContext.id;
278 if (!isSameBrowsingContext(sharedRef)) {
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.
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) {
306 `Expected "sharedId" to be a string, got ${sharedId}`
309 return deserializeSharedReference(sharedId, realm, extraOptions);
312 // With a handle present deserialize as remote reference.
313 if (handle !== undefined) {
316 `Expected "handle" to be a string, got ${handle}`
319 const object = realm.getObjectForHandle(handle);
321 throw new lazy.error.NoSuchHandleError(
322 `Unable to find an object reference for "handle" ${handle}`
329 lazy.assert.string(type, `Expected "type" to be a string, got ${type}`);
331 // Primitive protocol values
340 `Expected "value" to be a string, got ${value}`
344 // If value is already a number return its value.
345 if (typeof value === "number") {
349 // Otherwise it has to be one of the special strings
352 ["NaN", "-0", "Infinity", "-Infinity"],
353 `Expected "value" to be one of "NaN", "-0", "Infinity", "-Infinity", got ${value}`
355 return Number(value);
359 `Expected "value" to be a boolean, got ${value}`
365 `Expected "value" to be a string, got ${value}`
368 return BigInt(value);
370 throw new lazy.error.InvalidArgumentError(
371 `Failed to deserialize value as BigInt: ${value}`
377 const channel = message =>
378 extraOptions.emitScriptMessage(realm, value, message);
379 return realm.cloneIntoRealm(channel);
382 // Non-primitive protocol values
384 const array = realm.cloneIntoRealm([]);
385 deserializeValueList(realm, value, extraOptions).forEach(v =>
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));
396 const map = realm.cloneIntoRealm(new Map());
397 deserializeKeyValueList(realm, value, extraOptions).forEach(([k, v]) =>
403 const object = realm.cloneIntoRealm({});
404 deserializeKeyValueList(realm, value, extraOptions).forEach(
405 ([k, v]) => (object[k] = v)
411 `Expected "value" for RegExp to be an object, got ${value}`
413 const { pattern, flags } = value;
416 `Expected "pattern" for RegExp to be a string, got ${pattern}`
418 if (flags !== undefined) {
421 `Expected "flags" for RegExp to be a string, got ${flags}`
425 return realm.cloneIntoRealm(new RegExp(pattern, flags));
427 throw new lazy.error.InvalidArgumentError(
428 `Failed to deserialize value as RegExp: ${value}`
432 const set = realm.cloneIntoRealm(new Set());
433 deserializeValueList(realm, value, extraOptions).forEach(v => set.add(v));
437 lazy.logger.warn(`Unsupported type for local value ${type}`);
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".
457 function getHandleForObject(realm, ownershipType, object) {
458 if (ownershipType === OwnershipModel.None) {
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
472 * Node to create the unique reference for.
473 * @param {ExtraSerializationOptions} extraOptions
474 * Extra Remote Value serialization options.
477 * Shared unique reference for the Node.
479 function getSharedIdForNode(node, extraOptions) {
480 const { nodeCache, seenNodeIds } = extraOptions;
482 if (!Node.isInstance(node)) {
486 const browsingContext = node.ownerGlobal.browsingContext;
487 if (!browsingContext) {
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
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.
520 function serializeArrayLike(
525 serializationOptions,
527 serializationInternalMap,
531 const serialized = buildSerialized(production, handleId);
532 setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
534 if (!knownObject && serializationOptions.maxObjectDepth !== 0) {
535 serialized.value = serializeList(
537 serializationOptions,
539 serializationInternalMap,
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.
568 function serializeList(
570 serializationOptions,
572 serializationInternalMap,
576 const { maxObjectDepth } = serializationOptions;
577 const serialized = [];
578 const childSerializationOptions = {
579 ...serializationOptions,
581 if (maxObjectDepth !== null) {
582 childSerializationOptions.maxObjectDepth = maxObjectDepth - 1;
585 for (const item of iterable) {
589 childSerializationOptions,
591 serializationInternalMap,
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.
621 function serializeMapping(
623 serializationOptions,
625 serializationInternalMap,
629 const { maxObjectDepth } = serializationOptions;
630 const serialized = [];
631 const childSerializationOptions = {
632 ...serializationOptions,
634 if (maxObjectDepth !== null) {
635 childSerializationOptions.maxObjectDepth = maxObjectDepth - 1;
638 for (const [key, item] of iterable) {
639 const serializedKey =
640 typeof key == "string"
644 childSerializationOptions,
646 serializationInternalMap,
650 const serializedValue = serialize(
652 childSerializationOptions,
654 serializationInternalMap,
659 serialized.push([serializedKey, serializedValue]);
666 * Helper to serialize as a 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.
683 function serializeNode(
685 serializationOptions,
687 serializationInternalMap,
691 const { includeShadowTree, maxDomDepth } = serializationOptions;
692 const isAttribute = Attr.isInstance(node);
693 const isElement = Element.isInstance(node);
696 nodeType: node.nodeType,
699 if (node.nodeValue !== null) {
700 serialized.nodeValue = node.nodeValue;
703 if (isElement || isAttribute) {
704 serialized.localName = node.localName;
705 serialized.namespaceURI = node.namespaceURI;
708 serialized.childNodeCount = node.childNodes.length;
711 (!lazy.dom.isShadowRoot(node) ||
712 (includeShadowTree === IncludeShadowTreeMode.Open &&
713 node.mode === "open") ||
714 includeShadowTree === IncludeShadowTreeMode.All)
717 const childSerializationOptions = {
718 ...serializationOptions,
720 if (maxDomDepth !== null) {
721 childSerializationOptions.maxDomDepth = maxDomDepth - 1;
723 for (const child of node.childNodes) {
727 childSerializationOptions,
729 serializationInternalMap,
736 serialized.children = children;
740 serialized.attributes = [...node.attributes].reduce((map, attr) => {
741 map[attr.name] = attr.value;
745 const shadowRoot = node.openOrClosedShadowRoot;
746 serialized.shadowRoot = null;
747 if (shadowRoot !== null) {
748 serialized.shadowRoot = serialize(
750 serializationOptions,
752 serializationInternalMap,
759 if (lazy.dom.isShadowRoot(node)) {
760 serialized.mode = node.mode;
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.
786 export function serialize(
788 serializationOptions,
790 serializationInternalMap,
794 const { maxObjectDepth } = serializationOptions;
795 const type = typeof value;
797 // Primitive protocol values
798 if (type == "undefined") {
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 };
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;
824 // symbols are primitive JS values which can only be serialized
826 if (type == "symbol") {
827 return buildSerialized("symbol", handleId);
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(),
839 serializationOptions,
841 serializationInternalMap,
845 } else if (className == "RegExp") {
846 const serialized = buildSerialized("regexp", handleId);
847 serialized.value = { pattern: value.source, flags: value.flags };
849 } else if (className == "Date") {
850 const serialized = buildSerialized("date", handleId);
851 serialized.value = value.toISOString();
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(
860 serializationOptions,
862 serializationInternalMap,
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(
875 serializationOptions,
877 serializationInternalMap,
884 ["ArrayBuffer", "Function", "Promise", "WeakMap", "WeakSet"].includes(
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;
908 setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
911 serialized.value = serializeNode(
913 serializationOptions,
915 serializationInternalMap,
922 } else if (className === "Window") {
923 const serialized = buildSerialized("window", handleId);
924 const window = Cu.unwaiveXrays(value);
926 if (window.browsingContext.parent == null) {
928 context: window.browsingContext.browserId.toString(),
929 isTopBrowsingContext: true,
933 context: window.browsingContext.id.toString(),
938 } else if (ChromeUtils.isDOMObject(value)) {
939 const serialized = buildSerialized("object", handleId);
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,
952 serializationInternalMap,
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.
968 export function setDefaultSerializationOptions(options = {}) {
969 const serializationOptions = { ...options };
970 if (!("maxDomDepth" in serializationOptions)) {
971 serializationOptions.maxDomDepth = 0;
973 if (!("maxObjectDepth" in serializationOptions)) {
974 serializationOptions.maxObjectDepth = null;
976 if (!("includeShadowTree" in serializationOptions)) {
977 serializationOptions.includeShadowTree = IncludeShadowTreeMode.None;
980 return serializationOptions;
984 * Set default values and assert if serialization options have
987 * @param {SerializationOptions} options
988 * Options which define how ECMAScript objects should be serialized.
989 * @returns {SerializationOptions}
990 * Serialiation options with default value added.
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);
1003 if (maxObjectDepth !== null) {
1004 lazy.assert.positiveInteger(maxObjectDepth);
1006 const includeShadowTreeModesValues = Object.values(IncludeShadowTreeMode);
1008 includeShadowTree =>
1009 includeShadowTreeModesValues.includes(includeShadowTree),
1010 `includeShadowTree ${includeShadowTree} doesn't match allowed values "${includeShadowTreeModesValues.join(
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.
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);
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();
1049 // Copy the internalId of the original remote value to the new remote value.
1050 remoteValue.internalId = previousRemoteValue.internalId;
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.
1062 export function stringify(obj) {
1066 obj !== null && typeof obj === "object" ? obj.toString() : String(obj);
1068 // The error-case will also be handled in `finally {}`.
1070 if (typeof text != "string") {
1071 text = Object.prototype.toString.apply(obj);