summaryrefslogtreecommitdiffstats
path: root/remote/webdriver-bidi/RemoteValue.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'remote/webdriver-bidi/RemoteValue.sys.mjs')
-rw-r--r--remote/webdriver-bidi/RemoteValue.sys.mjs1066
1 files changed, 1066 insertions, 0 deletions
diff --git a/remote/webdriver-bidi/RemoteValue.sys.mjs b/remote/webdriver-bidi/RemoteValue.sys.mjs
new file mode 100644
index 0000000000..933b457ca1
--- /dev/null
+++ b/remote/webdriver-bidi/RemoteValue.sys.mjs
@@ -0,0 +1,1066 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
+ error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
+ generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
+ Log: "chrome://remote/content/shared/Log.sys.mjs",
+});
+
+XPCOMUtils.defineLazyGetter(lazy, "logger", () =>
+ lazy.Log.get(lazy.Log.TYPES.WEBDRIVER_BIDI)
+);
+
+/**
+ * @typedef {object} IncludeShadowTreeMode
+ */
+
+/**
+ * Enum of include shadow tree modes supported by the serialization.
+ *
+ * @readonly
+ * @enum {IncludeShadowTreeMode}
+ */
+export const IncludeShadowTreeMode = {
+ All: "all",
+ None: "none",
+ Open: "open",
+};
+
+/**
+ * @typedef {object} OwnershipModel
+ */
+
+/**
+ * Enum of ownership models supported by the serialization.
+ *
+ * @readonly
+ * @enum {OwnershipModel}
+ */
+export const OwnershipModel = {
+ None: "none",
+ Root: "root",
+};
+
+/**
+ * An object which holds the information of how
+ * ECMAScript objects should be serialized.
+ *
+ * @typedef {object} SerializationOptions
+ *
+ * @property {number} [maxDomDepth=0]
+ * Depth of a serialization of DOM Nodes. Defaults to 0.
+ * @property {number} [maxObjectDepth=null]
+ * Depth of a serialization of objects. Defaults to null.
+ * @property {IncludeShadowTreeMode} [includeShadowTree=IncludeShadowTreeMode.None]
+ * Mode of a serialization of shadow dom. Defaults to "none".
+ */
+
+const TYPED_ARRAY_CLASSES = [
+ "Uint8Array",
+ "Uint8ClampedArray",
+ "Uint16Array",
+ "Uint32Array",
+ "Int8Array",
+ "Int16Array",
+ "Int32Array",
+ "Float32Array",
+ "Float64Array",
+ "BigInt64Array",
+ "BigUint64Array",
+];
+
+/**
+ * Build the serialized RemoteValue.
+ *
+ * @returns {object}
+ * An object with a mandatory `type` property, and optional `handle`,
+ * depending on the OwnershipModel, used for the serialization and
+ * on the value's type.
+ */
+function buildSerialized(type, handle = null) {
+ const serialized = { type };
+
+ if (handle !== null) {
+ serialized.handle = handle;
+ }
+
+ return serialized;
+}
+
+/**
+ * Helper to validate if a date string follows Date Time String format.
+ *
+ * @see https://tc39.es/ecma262/#sec-date-time-string-format
+ *
+ * @param {string} dateString
+ * String which needs to be validated.
+ *
+ * @throws {InvalidArgumentError}
+ * If <var>dateString</var> doesn't follow the format.
+ */
+function checkDateTimeString(dateString) {
+ // Check if a date string follows a simplification of
+ // the ISO 8601 calendar date extended format.
+ const expandedYear = "[+-]\\d{6}";
+ const year = "\\d{4}";
+ const YYYY = `${expandedYear}|${year}`;
+ const MM = "\\d{2}";
+ const DD = "\\d{2}";
+ const date = `${YYYY}(?:-${MM})?(?:-${DD})?`;
+ const HH_mm = "\\d{2}:\\d{2}";
+ const SS = "\\d{2}";
+ const sss = "\\d{3}";
+ const TZ = `Z|[+-]${HH_mm}`;
+ const time = `T${HH_mm}(?::${SS}(?:\\.${sss})?(?:${TZ})?)?`;
+ const iso8601Format = new RegExp(`^${date}(?:${time})?$`);
+
+ // Check also if a date string is a valid date.
+ if (Number.isNaN(Date.parse(dateString)) || !iso8601Format.test(dateString)) {
+ throw new lazy.error.InvalidArgumentError(
+ `Expected "value" for Date to be a Date Time string, got ${dateString}`
+ );
+ }
+}
+
+/**
+ * Helper to deserialize value list.
+ *
+ * @see https://w3c.github.io/webdriver-bidi/#deserialize-value-list
+ *
+ * @param {Realm} realm
+ * The Realm in which the value is deserialized.
+ * @param {Array} serializedValueList
+ * List of serialized values.
+ * @param {object} options
+ * @param {NodeCache} options.nodeCache
+ * The cache containing DOM node references.
+ *
+ * @returns {Array} List of deserialized values.
+ *
+ * @throws {InvalidArgumentError}
+ * If <var>serializedValueList</var> is not an array.
+ */
+function deserializeValueList(realm, serializedValueList, options = {}) {
+ lazy.assert.array(
+ serializedValueList,
+ `Expected "serializedValueList" to be an array, got ${serializedValueList}`
+ );
+
+ const deserializedValues = [];
+
+ for (const item of serializedValueList) {
+ deserializedValues.push(deserialize(realm, item, options));
+ }
+
+ return deserializedValues;
+}
+
+/**
+ * Helper to deserialize key-value list.
+ *
+ * @see https://w3c.github.io/webdriver-bidi/#deserialize-key-value-list
+ *
+ * @param {Realm} realm
+ * The Realm in which the value is deserialized.
+ * @param {Array} serializedKeyValueList
+ * List of serialized key-value.
+ * @param {object} options
+ * @param {NodeCache} options.nodeCache
+ * The cache containing DOM node references.
+ *
+ * @returns {Array} List of deserialized key-value.
+ *
+ * @throws {InvalidArgumentError}
+ * If <var>serializedKeyValueList</var> is not an array or
+ * not an array of key-value arrays.
+ */
+function deserializeKeyValueList(realm, serializedKeyValueList, options = {}) {
+ lazy.assert.array(
+ serializedKeyValueList,
+ `Expected "serializedKeyValueList" to be an array, got ${serializedKeyValueList}`
+ );
+
+ const deserializedKeyValueList = [];
+
+ for (const serializedKeyValue of serializedKeyValueList) {
+ if (!Array.isArray(serializedKeyValue) || serializedKeyValue.length != 2) {
+ throw new lazy.error.InvalidArgumentError(
+ `Expected key-value pair to be an array with 2 elements, got ${serializedKeyValue}`
+ );
+ }
+ const [serializedKey, serializedValue] = serializedKeyValue;
+ const deserializedKey =
+ typeof serializedKey == "string"
+ ? serializedKey
+ : deserialize(realm, serializedKey, options);
+ const deserializedValue = deserialize(realm, serializedValue, options);
+
+ deserializedKeyValueList.push([deserializedKey, deserializedValue]);
+ }
+
+ return deserializedKeyValueList;
+}
+
+/**
+ * Deserialize a Node as referenced by the shared unique reference.
+ *
+ * This unique reference can be shared by WebDriver clients with the WebDriver
+ * classic implementation (Marionette) if the reference is for an Element or
+ * ShadowRoot.
+ *
+ * @param {string} sharedRef
+ * Shared unique reference of the Node.
+ * @param {Realm} realm
+ * The Realm in which the value is deserialized.
+ * @param {object} options
+ * @param {NodeCache} options.nodeCache
+ * The cache containing DOM node references.
+ *
+ * @returns {Node} The deserialized DOM node.
+ */
+function deserializeSharedReference(sharedRef, realm, options = {}) {
+ const { nodeCache } = options;
+
+ const browsingContext = realm.browsingContext;
+ if (!browsingContext) {
+ throw new lazy.error.NoSuchNodeError("Realm isn't a Window global");
+ }
+
+ const node = nodeCache.getNode(browsingContext, sharedRef);
+
+ if (node === null) {
+ throw new lazy.error.NoSuchNodeError(
+ `The node with the reference ${sharedRef} is not known`
+ );
+ }
+
+ // Bug 1819902: Instead of a browsing context check compare the origin
+ const isSameBrowsingContext = sharedRef => {
+ const nodeDetails = nodeCache.getReferenceDetails(sharedRef);
+
+ if (nodeDetails.isTopBrowsingContext && browsingContext.parent === null) {
+ // As long as Navigables are not available any cross-group navigation will
+ // cause a swap of the current top-level browsing context. The only unique
+ // identifier in such a case is the browser id the top-level browsing
+ // context actually lives in.
+ return nodeDetails.browserId === browsingContext.browserId;
+ }
+
+ return nodeDetails.browsingContextId === browsingContext.id;
+ };
+
+ if (!isSameBrowsingContext(sharedRef)) {
+ return null;
+ }
+
+ return node;
+}
+
+/**
+ * Deserialize a local value.
+ *
+ * @see https://w3c.github.io/webdriver-bidi/#deserialize-local-value
+ *
+ * @param {Realm} realm
+ * The Realm in which the value is deserialized.
+ * @param {object} serializedValue
+ * Value of any type to be deserialized.
+ * @param {object} options
+ * @param {NodeCache} options.nodeCache
+ * The cache containing DOM node references.
+ * @param {Function} options.emitScriptMessage
+ * The function to emit "script.message" event.
+ *
+ * @returns {object} Deserialized representation of the value.
+ */
+export function deserialize(realm, serializedValue, options = {}) {
+ const { handle, sharedId, type, value } = serializedValue;
+
+ // With a shared id present deserialize as node reference.
+ if (sharedId !== undefined) {
+ lazy.assert.string(
+ sharedId,
+ `Expected "sharedId" to be a string, got ${sharedId}`
+ );
+
+ return deserializeSharedReference(sharedId, realm, options);
+ }
+
+ // With a handle present deserialize as remote reference.
+ if (handle !== undefined) {
+ lazy.assert.string(
+ handle,
+ `Expected "handle" to be a string, got ${handle}`
+ );
+
+ const object = realm.getObjectForHandle(handle);
+ if (!object) {
+ throw new lazy.error.NoSuchHandleError(
+ `Unable to find an object reference for "handle" ${handle}`
+ );
+ }
+
+ return object;
+ }
+
+ lazy.assert.string(type, `Expected "type" to be a string, got ${type}`);
+
+ // Primitive protocol values
+ switch (type) {
+ case "undefined":
+ return undefined;
+ case "null":
+ return null;
+ case "string":
+ lazy.assert.string(
+ value,
+ `Expected "value" to be a string, got ${value}`
+ );
+ return value;
+ case "number":
+ // If value is already a number return its value.
+ if (typeof value === "number") {
+ return value;
+ }
+
+ // Otherwise it has to be one of the special strings
+ lazy.assert.in(
+ value,
+ ["NaN", "-0", "Infinity", "-Infinity"],
+ `Expected "value" to be one of "NaN", "-0", "Infinity", "-Infinity", got ${value}`
+ );
+ return Number(value);
+ case "boolean":
+ lazy.assert.boolean(
+ value,
+ `Expected "value" to be a boolean, got ${value}`
+ );
+ return value;
+ case "bigint":
+ lazy.assert.string(
+ value,
+ `Expected "value" to be a string, got ${value}`
+ );
+ try {
+ return BigInt(value);
+ } catch (e) {
+ throw new lazy.error.InvalidArgumentError(
+ `Failed to deserialize value as BigInt: ${value}`
+ );
+ }
+
+ // Script channel
+ case "channel": {
+ const channel = message =>
+ options.emitScriptMessage(realm, value, message);
+ return realm.cloneIntoRealm(channel);
+ }
+
+ // Non-primitive protocol values
+ case "array":
+ const array = realm.cloneIntoRealm([]);
+ deserializeValueList(realm, value, options).forEach(v => array.push(v));
+ return array;
+ case "date":
+ // We want to support only Date Time String format,
+ // check if the value follows it.
+ checkDateTimeString(value);
+
+ return realm.cloneIntoRealm(new Date(value));
+ case "map":
+ const map = realm.cloneIntoRealm(new Map());
+ deserializeKeyValueList(realm, value, options).forEach(([k, v]) =>
+ map.set(k, v)
+ );
+
+ return map;
+ case "object":
+ const object = realm.cloneIntoRealm({});
+ deserializeKeyValueList(realm, value, options).forEach(
+ ([k, v]) => (object[k] = v)
+ );
+ return object;
+ case "regexp":
+ lazy.assert.object(
+ value,
+ `Expected "value" for RegExp to be an object, got ${value}`
+ );
+ const { pattern, flags } = value;
+ lazy.assert.string(
+ pattern,
+ `Expected "pattern" for RegExp to be a string, got ${pattern}`
+ );
+ if (flags !== undefined) {
+ lazy.assert.string(
+ flags,
+ `Expected "flags" for RegExp to be a string, got ${flags}`
+ );
+ }
+ try {
+ return realm.cloneIntoRealm(new RegExp(pattern, flags));
+ } catch (e) {
+ throw new lazy.error.InvalidArgumentError(
+ `Failed to deserialize value as RegExp: ${value}`
+ );
+ }
+ case "set":
+ const set = realm.cloneIntoRealm(new Set());
+ deserializeValueList(realm, value, options).forEach(v => set.add(v));
+ return set;
+ }
+
+ lazy.logger.warn(`Unsupported type for local value ${type}`);
+ return undefined;
+}
+
+/**
+ * Helper to retrieve the handle id for a given object, for the provided realm
+ * and ownership type.
+ *
+ * See https://w3c.github.io/webdriver-bidi/#handle-for-an-object
+ *
+ * @param {Realm} realm
+ * The Realm from which comes the value being serialized.
+ * @param {OwnershipModel} ownershipType
+ * The ownership model to use for this serialization.
+ * @param {object} object
+ * The object being serialized.
+ *
+ * @returns {string} The unique handle id for the object. Will be null if the
+ * Ownership type is "none".
+ */
+function getHandleForObject(realm, ownershipType, object) {
+ if (ownershipType === OwnershipModel.None) {
+ return null;
+ }
+ return realm.getHandleForObject(object);
+}
+
+/**
+ * Gets or creates a new shared unique reference for the DOM node.
+ *
+ * This unique reference can be shared by WebDriver clients with the WebDriver
+ * classic implementation (Marionette) if the reference is for an Element or
+ * ShadowRoot.
+ *
+ * @param {Node} node
+ * Node to create the unique reference for.
+ * @param {Realm} realm
+ * The Realm in which the value is serialized.
+ * @param {object} options
+ * @param {NodeCache} options.nodeCache
+ * The cache containing DOM node references.
+ *
+ * @returns {string}
+ * Shared unique reference for the Node.
+ */
+function getSharedIdForNode(node, realm, options = {}) {
+ const { nodeCache } = options;
+
+ if (!Node.isInstance(node)) {
+ return null;
+ }
+
+ const browsingContext = realm.browsingContext;
+ if (!browsingContext) {
+ return null;
+ }
+
+ const unwrapped = Cu.unwaiveXrays(node);
+ return nodeCache.getOrCreateNodeReference(unwrapped);
+}
+
+/**
+ * Determines if <var>node</var> is shadow root.
+ *
+ * @param {Node} node
+ * Node to check.
+ *
+ * @returns {boolean}
+ * True if <var>node</var> is shadow root, false otherwise.
+ */
+function isShadowRoot(node) {
+ const DOCUMENT_FRAGMENT_NODE = 11;
+ return (
+ node &&
+ node.nodeType === DOCUMENT_FRAGMENT_NODE &&
+ node.containingShadowRoot == node
+ );
+}
+
+/**
+ * Helper to serialize an Array-like object.
+ *
+ * @see https://w3c.github.io/webdriver-bidi/#serialize-an-array-like
+ *
+ * @param {string} production
+ * Type of object
+ * @param {string} handleId
+ * The unique id of the <var>value</var>.
+ * @param {boolean} knownObject
+ * Indicates if the <var>value</var> has already been serialized.
+ * @param {object} value
+ * The Array-like object to serialize.
+ * @param {SerializationOptions} serializationOptions
+ * Options which define how ECMAScript objects should be serialized.
+ * @param {OwnershipModel} ownershipType
+ * The ownership model to use for this serialization.
+ * @param {Map} serializationInternalMap
+ * Map of internal ids.
+ * @param {Realm} realm
+ * The Realm from which comes the value being serialized.
+ * @param {object} options
+ * @param {NodeCache} options.nodeCache
+ * The cache containing DOM node references.
+ *
+ * @returns {object} Object for serialized values.
+ */
+function serializeArrayLike(
+ production,
+ handleId,
+ knownObject,
+ value,
+ serializationOptions,
+ ownershipType,
+ serializationInternalMap,
+ realm,
+ options
+) {
+ const serialized = buildSerialized(production, handleId);
+ setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
+
+ if (!knownObject && serializationOptions.maxObjectDepth !== 0) {
+ serialized.value = serializeList(
+ value,
+ serializationOptions,
+ ownershipType,
+ serializationInternalMap,
+ realm,
+ options
+ );
+ }
+
+ return serialized;
+}
+
+/**
+ * Helper to serialize as a list.
+ *
+ * @see https://w3c.github.io/webdriver-bidi/#serialize-as-a-list
+ *
+ * @param {Iterable} iterable
+ * List of values to be serialized.
+ * @param {SerializationOptions} serializationOptions
+ * Options which define how ECMAScript objects should be serialized.
+ * @param {OwnershipModel} ownershipType
+ * The ownership model to use for this serialization.
+ * @param {Map} serializationInternalMap
+ * Map of internal ids.
+ * @param {Realm} realm
+ * The Realm from which comes the value being serialized.
+ * @param {object} options
+ * @param {NodeCache} options.nodeCache
+ * The cache containing DOM node references.
+ *
+ * @returns {Array} List of serialized values.
+ */
+function serializeList(
+ iterable,
+ serializationOptions,
+ ownershipType,
+ serializationInternalMap,
+ realm,
+ options
+) {
+ const { maxObjectDepth } = serializationOptions;
+ const serialized = [];
+ const childSerializationOptions = {
+ ...serializationOptions,
+ };
+ if (maxObjectDepth !== null) {
+ childSerializationOptions.maxObjectDepth = maxObjectDepth - 1;
+ }
+
+ for (const item of iterable) {
+ serialized.push(
+ serialize(
+ item,
+ childSerializationOptions,
+ ownershipType,
+ serializationInternalMap,
+ realm,
+ options
+ )
+ );
+ }
+
+ return serialized;
+}
+
+/**
+ * Helper to serialize as a mapping.
+ *
+ * @see https://w3c.github.io/webdriver-bidi/#serialize-as-a-mapping
+ *
+ * @param {Iterable} iterable
+ * List of values to be serialized.
+ * @param {SerializationOptions} serializationOptions
+ * Options which define how ECMAScript objects should be serialized.
+ * @param {OwnershipModel} ownershipType
+ * The ownership model to use for this serialization.
+ * @param {Map} serializationInternalMap
+ * Map of internal ids.
+ * @param {Realm} realm
+ * The Realm from which comes the value being serialized.
+ * @param {object} options
+ * @param {NodeCache} options.nodeCache
+ * The cache containing DOM node references.
+ *
+ * @returns {Array} List of serialized values.
+ */
+function serializeMapping(
+ iterable,
+ serializationOptions,
+ ownershipType,
+ serializationInternalMap,
+ realm,
+ options
+) {
+ const { maxObjectDepth } = serializationOptions;
+ const serialized = [];
+ const childSerializationOptions = {
+ ...serializationOptions,
+ };
+ if (maxObjectDepth !== null) {
+ childSerializationOptions.maxObjectDepth = maxObjectDepth - 1;
+ }
+
+ for (const [key, item] of iterable) {
+ const serializedKey =
+ typeof key == "string"
+ ? key
+ : serialize(
+ key,
+ childSerializationOptions,
+ ownershipType,
+ serializationInternalMap,
+ realm,
+ options
+ );
+ const serializedValue = serialize(
+ item,
+ childSerializationOptions,
+ ownershipType,
+ serializationInternalMap,
+ realm,
+ options
+ );
+
+ serialized.push([serializedKey, serializedValue]);
+ }
+
+ return serialized;
+}
+
+/**
+ * Helper to serialize as a Node.
+ *
+ * @param {Node} node
+ * Node to be serialized.
+ * @param {SerializationOptions} serializationOptions
+ * Options which define how ECMAScript objects should be serialized.
+ * @param {OwnershipModel} ownershipType
+ * The ownership model to use for this serialization.
+ * @param {Map} serializationInternalMap
+ * Map of internal ids.
+ * @param {Realm} realm
+ * The Realm from which comes the value being serialized.
+ * @param {object} options
+ * @param {NodeCache} options.nodeCache
+ * The cache containing DOM node references.
+ *
+ * @returns {object} Serialized value.
+ */
+function serializeNode(
+ node,
+ serializationOptions,
+ ownershipType,
+ serializationInternalMap,
+ realm,
+ options
+) {
+ const { includeShadowTree, maxDomDepth } = serializationOptions;
+ const isAttribute = Attr.isInstance(node);
+ const isElement = Element.isInstance(node);
+
+ const serialized = {
+ nodeType: node.nodeType,
+ };
+
+ if (node.nodeValue !== null) {
+ serialized.nodeValue = node.nodeValue;
+ }
+
+ if (isElement || isAttribute) {
+ serialized.localName = node.localName;
+ serialized.namespaceURI = node.namespaceURI;
+ }
+
+ serialized.childNodeCount = node.childNodes.length;
+ if (
+ maxDomDepth !== 0 &&
+ (!isShadowRoot(node) ||
+ (includeShadowTree === IncludeShadowTreeMode.Open &&
+ node.mode === "open") ||
+ includeShadowTree === IncludeShadowTreeMode.All)
+ ) {
+ const children = [];
+ const childSerializationOptions = {
+ ...serializationOptions,
+ };
+ if (maxDomDepth !== null) {
+ childSerializationOptions.maxDomDepth = maxDomDepth - 1;
+ }
+ for (const child of node.childNodes) {
+ children.push(
+ serialize(
+ child,
+ childSerializationOptions,
+ ownershipType,
+ serializationInternalMap,
+ realm,
+ options
+ )
+ );
+ }
+
+ serialized.children = children;
+ }
+
+ if (isElement) {
+ serialized.attributes = [...node.attributes].reduce((map, attr) => {
+ map[attr.name] = attr.value;
+ return map;
+ }, {});
+
+ const shadowRoot = Cu.unwaiveXrays(node).openOrClosedShadowRoot;
+ serialized.shadowRoot = null;
+ if (shadowRoot !== null) {
+ serialized.shadowRoot = serialize(
+ shadowRoot,
+ serializationOptions,
+ ownershipType,
+ serializationInternalMap,
+ realm,
+ options
+ );
+ }
+ }
+
+ if (isShadowRoot(node)) {
+ serialized.mode = node.mode;
+ }
+
+ return serialized;
+}
+
+/**
+ * Serialize a value as a remote value.
+ *
+ * @see https://w3c.github.io/webdriver-bidi/#serialize-as-a-remote-value
+ *
+ * @param {object} value
+ * Value of any type to be serialized.
+ * @param {SerializationOptions} serializationOptions
+ * Options which define how ECMAScript objects should be serialized.
+ * @param {OwnershipModel} ownershipType
+ * The ownership model to use for this serialization.
+ * @param {Map} serializationInternalMap
+ * Map of internal ids.
+ * @param {Realm} realm
+ * The Realm from which comes the value being serialized.
+ * @param {object} options
+ * @param {NodeCache} options.nodeCache
+ * The cache containing DOM node references.
+ *
+ * @returns {object} Serialized representation of the value.
+ */
+export function serialize(
+ value,
+ serializationOptions,
+ ownershipType,
+ serializationInternalMap,
+ realm,
+ options
+) {
+ const { maxObjectDepth } = serializationOptions;
+ const type = typeof value;
+
+ // Primitive protocol values
+ if (type == "undefined") {
+ return { type };
+ } else if (Object.is(value, null)) {
+ return { type: "null" };
+ } else if (Object.is(value, NaN)) {
+ return { type: "number", value: "NaN" };
+ } else if (Object.is(value, -0)) {
+ return { type: "number", value: "-0" };
+ } else if (Object.is(value, Infinity)) {
+ return { type: "number", value: "Infinity" };
+ } else if (Object.is(value, -Infinity)) {
+ return { type: "number", value: "-Infinity" };
+ } else if (type == "bigint") {
+ return { type, value: value.toString() };
+ } else if (["boolean", "number", "string"].includes(type)) {
+ return { type, value };
+ }
+
+ const handleId = getHandleForObject(realm, ownershipType, value);
+ const knownObject = serializationInternalMap.has(value);
+
+ // Set the OwnershipModel to use for all complex object serializations.
+ ownershipType = OwnershipModel.None;
+
+ // Remote values
+
+ // symbols are primitive JS values which can only be serialized
+ // as remote values.
+ if (type == "symbol") {
+ return buildSerialized("symbol", handleId);
+ }
+
+ // All other remote values are non-primitives and their
+ // className can be extracted with ChromeUtils.getClassName
+ const className = ChromeUtils.getClassName(value);
+ if (["Array", "HTMLCollection", "NodeList"].includes(className)) {
+ return serializeArrayLike(
+ className.toLowerCase(),
+ handleId,
+ knownObject,
+ value,
+ serializationOptions,
+ ownershipType,
+ serializationInternalMap,
+ realm,
+ options
+ );
+ } else if (className == "RegExp") {
+ const serialized = buildSerialized("regexp", handleId);
+ serialized.value = { pattern: value.source, flags: value.flags };
+ return serialized;
+ } else if (className == "Date") {
+ const serialized = buildSerialized("date", handleId);
+ serialized.value = value.toISOString();
+ return serialized;
+ } else if (className == "Map") {
+ const serialized = buildSerialized("map", handleId);
+ setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
+
+ if (!knownObject && maxObjectDepth !== 0) {
+ serialized.value = serializeMapping(
+ value.entries(),
+ serializationOptions,
+ ownershipType,
+ serializationInternalMap,
+ realm
+ );
+ }
+ return serialized;
+ } else if (className == "Set") {
+ const serialized = buildSerialized("set", handleId);
+ setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
+
+ if (!knownObject && maxObjectDepth !== 0) {
+ serialized.value = serializeList(
+ value.values(),
+ serializationOptions,
+ ownershipType,
+ serializationInternalMap,
+ realm
+ );
+ }
+ return serialized;
+ } else if (
+ [
+ "ArrayBuffer",
+ "Function",
+ "Promise",
+ "WeakMap",
+ "WeakSet",
+ "Window",
+ ].includes(className)
+ ) {
+ return buildSerialized(className.toLowerCase(), handleId);
+ } else if (lazy.error.isError(value)) {
+ return buildSerialized("error", handleId);
+ } else if (TYPED_ARRAY_CLASSES.includes(className)) {
+ return buildSerialized("typedarray", handleId);
+ } else if (Node.isInstance(value)) {
+ const serialized = buildSerialized("node", handleId);
+
+ // Get or create the shared id for WebDriver classic compat from the node.
+ const sharedId = getSharedIdForNode(value, realm, options);
+ if (sharedId !== null) {
+ serialized.sharedId = sharedId;
+ }
+
+ setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
+
+ if (!knownObject) {
+ serialized.value = serializeNode(
+ value,
+ serializationOptions,
+ ownershipType,
+ serializationInternalMap,
+ realm,
+ options
+ );
+ }
+
+ return serialized;
+ } else if (ChromeUtils.isDOMObject(value)) {
+ const serialized = buildSerialized("object", handleId);
+ return serialized;
+ }
+
+ // Otherwise serialize the JavaScript object as generic object.
+ const serialized = buildSerialized("object", handleId);
+ setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
+
+ if (!knownObject && maxObjectDepth !== 0) {
+ serialized.value = serializeMapping(
+ Object.entries(value),
+ serializationOptions,
+ ownershipType,
+ serializationInternalMap,
+ realm,
+ options
+ );
+ }
+ return serialized;
+}
+
+/**
+ * Set default serialization options.
+ *
+ * @param {SerializationOptions} options
+ * Options which define how ECMAScript objects should be serialized.
+ * @returns {SerializationOptions}
+ * Serialiation options with default value added.
+ */
+export function setDefaultSerializationOptions(options = {}) {
+ const serializationOptions = { ...options };
+ if (!("maxDomDepth" in serializationOptions)) {
+ serializationOptions.maxDomDepth = 0;
+ }
+ if (!("maxObjectDepth" in serializationOptions)) {
+ serializationOptions.maxObjectDepth = null;
+ }
+ if (!("includeShadowTree" in serializationOptions)) {
+ serializationOptions.includeShadowTree = IncludeShadowTreeMode.None;
+ }
+
+ return serializationOptions;
+}
+
+/**
+ * Set default values and assert if serialization options have
+ * expected types.
+ *
+ * @param {SerializationOptions} options
+ * Options which define how ECMAScript objects should be serialized.
+ * @returns {SerializationOptions}
+ * Serialiation options with default value added.
+ */
+export function setDefaultAndAssertSerializationOptions(options = {}) {
+ lazy.assert.object(options);
+
+ const serializationOptions = setDefaultSerializationOptions(options);
+
+ const { includeShadowTree, maxDomDepth, maxObjectDepth } =
+ serializationOptions;
+
+ if (maxDomDepth !== null) {
+ lazy.assert.positiveNumber(maxDomDepth);
+ }
+ if (maxObjectDepth !== null) {
+ lazy.assert.positiveNumber(maxObjectDepth);
+ }
+ const includeShadowTreeModesValues = Object.values(IncludeShadowTreeMode);
+ lazy.assert.that(
+ includeShadowTree =>
+ includeShadowTreeModesValues.includes(includeShadowTree),
+ `includeShadowTree ${includeShadowTree} doesn't match allowed values "${includeShadowTreeModesValues.join(
+ "/"
+ )}"`
+ )(includeShadowTree);
+
+ return serializationOptions;
+}
+
+/**
+ * Set the internalId property of a provided serialized RemoteValue,
+ * and potentially of a previously created serialized RemoteValue,
+ * corresponding to the same provided object.
+ *
+ * @see https://w3c.github.io/webdriver-bidi/#set-internal-ids-if-needed
+ *
+ * @param {Map} serializationInternalMap
+ * Map of objects to remote values.
+ * @param {object} remoteValue
+ * A serialized RemoteValue for the provided object.
+ * @param {object} object
+ * Object of any type to be serialized.
+ */
+function setInternalIdsIfNeeded(serializationInternalMap, remoteValue, object) {
+ if (!serializationInternalMap.has(object)) {
+ // If the object was not tracked yet in the current serialization, add
+ // a new entry in the serialization internal map. An internal id will only
+ // be generated if the same object is encountered again.
+ serializationInternalMap.set(object, remoteValue);
+ } else {
+ // This is at least the second time this object is encountered, retrieve the
+ // original remote value stored for this object.
+ const previousRemoteValue = serializationInternalMap.get(object);
+
+ if (!previousRemoteValue.internalId) {
+ // If the original remote value has no internal id yet, generate a uuid
+ // and update the internalId of the original remote value with it.
+ previousRemoteValue.internalId = lazy.generateUUID();
+ }
+
+ // Copy the internalId of the original remote value to the new remote value.
+ remoteValue.internalId = previousRemoteValue.internalId;
+ }
+}
+
+/**
+ * Safely stringify a value.
+ *
+ * @param {object} obj
+ * Value of any type to be stringified.
+ *
+ * @returns {string} String representation of the value.
+ */
+export function stringify(obj) {
+ let text;
+ try {
+ text =
+ obj !== null && typeof obj === "object" ? obj.toString() : String(obj);
+ } catch (e) {
+ // The error-case will also be handled in `finally {}`.
+ } finally {
+ if (typeof text != "string") {
+ text = Object.prototype.toString.apply(obj);
+ }
+ }
+
+ return text;
+}