From 2794384788168c3f48d5750cb1b7f898ed25ea7a Mon Sep 17 00:00:00 2001 From: Alexandra Borovova Date: Mon, 13 Jun 2022 14:26:56 +0000 Subject: [PATCH] Bug 1770752 - Add support for serialization of complex objects with simple value fields. r=webdriver-reviewers,jdescottes,whimboo Differential Revision: https://phabricator.services.mozilla.com/D148100 --- remote/webdriver-bidi/RemoteValue.jsm | 153 ++++++++++++- .../test/xpcshell/test_RemoteValue.js | 213 ++++++++++++++++++ 2 files changed, 364 insertions(+), 2 deletions(-) diff --git a/remote/webdriver-bidi/RemoteValue.jsm b/remote/webdriver-bidi/RemoteValue.jsm index fe969558aa03..ea9d0f239c70 100644 --- a/remote/webdriver-bidi/RemoteValue.jsm +++ b/remote/webdriver-bidi/RemoteValue.jsm @@ -98,6 +98,89 @@ function deserialize(serializedValue) { return undefined; } +/** + * 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 {number=} maxDepth + * Depth of a serialization. + * + * @return {Array} List of serialized values. + */ +function serializeList( + iterable, + maxDepth /*, + childOwnership, + serializationInternalMap, + realm*/ +) { + const serialized = []; + const childDepth = maxDepth !== null ? maxDepth - 1 : null; + + for (const item of iterable) { + serialized.push( + serialize( + item, + childDepth /*, childOwnership, serializationInternalMap, realm*/ + ) + ); + } + + 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 {number=} maxDepth + * Depth of a serialization. + * + * @return {Array} List of serialized values. + */ +function serializeMapping( + iterable, + maxDepth /*, + childOwnership, + serializationInternalMap, + realm*/ +) { + const serialized = []; + const childDepth = maxDepth !== null ? maxDepth - 1 : null; + + for (const [key, item] of iterable) { + const serializedKey = + typeof key == "string" + ? key + : serialize( + key, + childDepth /*, + childOwnership, + serializationInternalMap, + realm*/ + ); + const serializedValue = serialize( + item, + childDepth /*, + childOwnership, + serializationInternalMap, + realm*/ + ); + + serialized.push([serializedKey, serializedValue]); + } + + return serialized; +} + /** * Serialize a value as a remote value. * @@ -106,9 +189,18 @@ function deserialize(serializedValue) { * @param {Object} value * Value of any type to be serialized. * + * @param {number=} maxDepth + * Depth of a serialization. + * * @returns {Object} Serialized representation of the value. */ -function serialize(value /*, maxDepth, nodeDetails, knownObjects */) { +function serialize( + value, + maxDepth /*, + ownershipType, + serializationInternalMap, + realm */ +) { const type = typeof value; // Primitive protocol values @@ -130,6 +222,63 @@ function serialize(value /*, maxDepth, nodeDetails, knownObjects */) { return { type, value }; } - lazy.logger.warn(`Unsupported type for remote value: ${value.toString()}`); + const className = ChromeUtils.getClassName(value); + + // Remote values + if (className == "Array") { + const remoteValue = { type: "array" }; + + if (maxDepth !== null && maxDepth > 0) { + remoteValue.value = serializeList( + value, + maxDepth /*, + ownershipType, + serializationInternalMap, + realm */ + ); + } + + return remoteValue; + } else if (className == "RegExp") { + return { + type: "regexp", + value: { pattern: value.source, flags: value.flags }, + }; + } else if (className == "Date") { + return { type: "date", value: value.toString() }; + } else if (className == "Map") { + const remoteValue = { type: "map" }; + + if (maxDepth !== null && maxDepth > 0) { + remoteValue.value = serializeMapping( + value.entries(), + maxDepth /*, + ownershipType, + serializationInternalMap, + realm */ + ); + } + + return remoteValue; + } else if (className == "Set") { + const remoteValue = { type: "set" }; + + if (maxDepth !== null && maxDepth > 0) { + remoteValue.value = serializeList( + value.values(), + maxDepth /*, + ownershipType, + serializationInternalMap, + realm */ + ); + } + + return remoteValue; + } + + lazy.logger.warn( + `Unsupported type: ${type} for remote value: ${value.toString()}` + ); + return undefined; } diff --git a/remote/webdriver-bidi/test/xpcshell/test_RemoteValue.js b/remote/webdriver-bidi/test/xpcshell/test_RemoteValue.js index 307ea0d8d1c5..26b5a943fc82 100644 --- a/remote/webdriver-bidi/test/xpcshell/test_RemoteValue.js +++ b/remote/webdriver-bidi/test/xpcshell/test_RemoteValue.js @@ -3,6 +3,13 @@ "use strict"; +class MockDate extends Date {} + +// Mock `toString` to avoid timezone differences. +MockDate.prototype.toString = function() { + return "Tue May 31 2022 15:47:29 GMT+0200 (Central European Summer Time)"; +}; + const PRIMITIVE_TYPES = [ { value: undefined, serialized: { type: "undefined" } }, { value: null, serialized: { type: "null" } }, @@ -22,6 +29,186 @@ const PRIMITIVE_TYPES = [ { value: 42n, serialized: { type: "bigint", value: "42" } }, ]; +const REMOTE_SIMPLE_VALUES = [ + { + value: new RegExp(/foo/g), + serialized: { + type: "regexp", + value: { + pattern: "foo", + flags: "g", + }, + }, + }, + { + value: new MockDate(1654004849000), + serialized: { + type: "date", + value: "Tue May 31 2022 15:47:29 GMT+0200 (Central European Summer Time)", + }, + }, +]; + +const REMOTE_COMPLEX_VALUES = [ + { + value: [1], + serialized: { + type: "array", + }, + }, + { + value: [1], + maxDepth: 0, + serialized: { + type: "array", + }, + }, + { + value: [1, "2", true, new RegExp(/foo/g)], + maxDepth: 1, + serialized: { + type: "array", + value: [ + { type: "number", value: 1 }, + { type: "string", value: "2" }, + { type: "boolean", value: true }, + { + type: "regexp", + value: { + pattern: "foo", + flags: "g", + }, + }, + ], + }, + }, + { + value: [1, [3, "4"]], + maxDepth: 1, + serialized: { + type: "array", + value: [{ type: "number", value: 1 }, { type: "array" }], + }, + }, + { + value: [1, [3, "4"]], + maxDepth: 2, + serialized: { + type: "array", + value: [ + { type: "number", value: 1 }, + { + type: "array", + value: [ + { type: "number", value: 3 }, + { type: "string", value: "4" }, + ], + }, + ], + }, + }, + { + value: new Map(), + maxDepth: 1, + serialized: { + type: "map", + value: [], + }, + }, + { + value: new Map([]), + maxDepth: 1, + serialized: { + type: "map", + value: [], + }, + }, + { + value: new Map([ + [1, 2], + ["2", "3"], + [true, false], + ]), + serialized: { + type: "map", + }, + }, + { + value: new Map([ + [1, 2], + ["2", "3"], + [true, false], + ]), + maxDepth: 0, + serialized: { + type: "map", + }, + }, + { + value: new Map([ + [1, 2], + ["2", "3"], + [true, false], + ]), + maxDepth: 1, + serialized: { + type: "map", + value: [ + [ + { type: "number", value: 1 }, + { type: "number", value: 2 }, + ], + ["2", { type: "string", value: "3" }], + [ + { type: "boolean", value: true }, + { type: "boolean", value: false }, + ], + ], + }, + }, + { + value: new Set(), + maxDepth: 1, + serialized: { + type: "set", + value: [], + }, + }, + { + value: new Set([]), + maxDepth: 1, + serialized: { + type: "set", + value: [], + }, + }, + { + value: new Set([1, "2", true]), + serialized: { + type: "set", + }, + }, + { + value: new Set([1, "2", true]), + maxDepth: 0, + serialized: { + type: "set", + }, + }, + { + value: new Set([1, "2", true]), + maxDepth: 1, + serialized: { + type: "set", + value: [ + { type: "number", value: 1 }, + { type: "string", value: "2" }, + { type: "boolean", value: true }, + ], + }, + }, +]; + const { deserialize, serialize } = ChromeUtils.import( "chrome://remote/content/webdriver-bidi/RemoteValue.jsm" ); @@ -93,3 +280,29 @@ add_test(function test_serializePrimitiveTypes() { run_next_test(); }); + +add_test(function test_serializeRemoteSimpleValues() { + for (const type of REMOTE_SIMPLE_VALUES) { + const { value, serialized } = type; + + info(`Checking '${serialized.type}'`); + const serializedValue = serialize(value); + + Assert.deepEqual(serialized, serializedValue, "Got expected structure"); + } + + run_next_test(); +}); + +add_test(function test_serializeRemoteComplexValues() { + for (const type of REMOTE_COMPLEX_VALUES) { + const { value, serialized, maxDepth } = type; + + info(`Checking '${serialized.type}'`); + const serializedValue = serialize(value, maxDepth); + + Assert.deepEqual(serialized, serializedValue, "Got expected structure"); + } + + run_next_test(); +});