summaryrefslogtreecommitdiffstats
path: root/remote/webdriver-bidi/test/xpcshell
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--remote/webdriver-bidi/test/xpcshell/test_Realm.js90
-rw-r--r--remote/webdriver-bidi/test/xpcshell/test_RemoteValue.js1016
-rw-r--r--remote/webdriver-bidi/test/xpcshell/test_WebDriverBiDiConnection.js27
-rw-r--r--remote/webdriver-bidi/test/xpcshell/xpcshell.ini7
4 files changed, 1140 insertions, 0 deletions
diff --git a/remote/webdriver-bidi/test/xpcshell/test_Realm.js b/remote/webdriver-bidi/test/xpcshell/test_Realm.js
new file mode 100644
index 0000000000..ff39adcd82
--- /dev/null
+++ b/remote/webdriver-bidi/test/xpcshell/test_Realm.js
@@ -0,0 +1,90 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { Realm } = ChromeUtils.importESModule(
+ "chrome://remote/content/webdriver-bidi/Realm.sys.mjs"
+);
+
+add_test(function test_id() {
+ const realm1 = new Realm();
+ const id1 = realm1.id;
+ Assert.equal(typeof id1, "string");
+
+ const realm2 = new Realm();
+ const id2 = realm2.id;
+ Assert.equal(typeof id2, "string");
+
+ Assert.notEqual(id1, id2, "Ids for different realms are different");
+
+ run_next_test();
+});
+
+add_test(function test_handleObjectMap() {
+ const realm = new Realm();
+
+ // Test an unknown handle.
+ Assert.equal(
+ realm.getObjectForHandle("unknown"),
+ undefined,
+ "Unknown handles return undefined"
+ );
+
+ // Test creating a simple handle.
+ const object = {};
+ const handle = realm.getHandleForObject(object);
+ Assert.equal(typeof handle, "string", "Created a valid handle");
+ Assert.equal(
+ realm.getObjectForHandle(handle),
+ object,
+ "Using the handle returned the original object"
+ );
+
+ // Test another handle for the same object.
+ const secondHandle = realm.getHandleForObject(object);
+ Assert.equal(typeof secondHandle, "string", "Created a valid handle");
+ Assert.notEqual(secondHandle, handle, "A different handle was generated");
+ Assert.equal(
+ realm.getObjectForHandle(secondHandle),
+ object,
+ "Using the second handle also returned the original object"
+ );
+
+ // Test using the handles in another realm.
+ const otherRealm = new Realm();
+ Assert.equal(
+ otherRealm.getObjectForHandle(handle),
+ undefined,
+ "A realm returns undefined for handles from another realm"
+ );
+
+ // Removing an unknown handle should not throw or have any side effect on
+ // existing handles.
+ realm.removeObjectHandle("unknown");
+ Assert.equal(realm.getObjectForHandle(handle), object);
+ Assert.equal(realm.getObjectForHandle(secondHandle), object);
+
+ // Remove the second handle
+ realm.removeObjectHandle(secondHandle);
+ Assert.equal(
+ realm.getObjectForHandle(handle),
+ object,
+ "The first handle is still resolving the object"
+ );
+ Assert.equal(
+ realm.getObjectForHandle(secondHandle),
+ undefined,
+ "The second handle returns undefined after calling removeObjectHandle"
+ );
+
+ // Remove the original handle
+ realm.removeObjectHandle(handle);
+ Assert.equal(
+ realm.getObjectForHandle(handle),
+ undefined,
+ "The first handle returns undefined as well"
+ );
+
+ run_next_test();
+});
diff --git a/remote/webdriver-bidi/test/xpcshell/test_RemoteValue.js b/remote/webdriver-bidi/test/xpcshell/test_RemoteValue.js
new file mode 100644
index 0000000000..8147711417
--- /dev/null
+++ b/remote/webdriver-bidi/test/xpcshell/test_RemoteValue.js
@@ -0,0 +1,1016 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const browser = Services.appShell.createWindowlessBrowser(false);
+const domEl = browser.document.createElement("div");
+browser.document.body.appendChild(domEl);
+
+const PRIMITIVE_TYPES = [
+ { value: undefined, serialized: { type: "undefined" } },
+ { value: null, serialized: { type: "null" } },
+ { value: "foo", serialized: { type: "string", value: "foo" } },
+ { value: Number.NaN, serialized: { type: "number", value: "NaN" } },
+ { value: -0, serialized: { type: "number", value: "-0" } },
+ {
+ value: Number.POSITIVE_INFINITY,
+ serialized: { type: "number", value: "Infinity" },
+ },
+ {
+ value: Number.NEGATIVE_INFINITY,
+ serialized: { type: "number", value: "-Infinity" },
+ },
+ { value: 42, serialized: { type: "number", value: 42 } },
+ { value: false, serialized: { type: "boolean", value: false } },
+ { value: 42n, serialized: { type: "bigint", value: "42" } },
+];
+
+const REMOTE_SIMPLE_VALUES = [
+ {
+ value: new RegExp(/foo/),
+ serialized: {
+ type: "regexp",
+ value: {
+ pattern: "foo",
+ flags: "",
+ },
+ },
+ deserializable: true,
+ },
+ {
+ value: new RegExp(/foo/g),
+ serialized: {
+ type: "regexp",
+ value: {
+ pattern: "foo",
+ flags: "g",
+ },
+ },
+ deserializable: true,
+ },
+ {
+ value: new Date(1654004849000),
+ serialized: {
+ type: "date",
+ value: "2022-05-31T13:47:29.000Z",
+ },
+ deserializable: true,
+ },
+];
+
+const REMOTE_COMPLEX_VALUES = [
+ { value: Symbol("foo"), serialized: { type: "symbol" } },
+ {
+ 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",
+ },
+ },
+ ],
+ },
+ deserializable: true,
+ },
+ {
+ 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" },
+ ],
+ },
+ ],
+ },
+ deserializable: true,
+ },
+ {
+ value: new Map(),
+ maxDepth: 1,
+ serialized: {
+ type: "map",
+ value: [],
+ },
+ deserializable: true,
+ },
+ {
+ value: new Map([]),
+ maxDepth: 1,
+ serialized: {
+ type: "map",
+ value: [],
+ },
+ deserializable: true,
+ },
+ {
+ 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 },
+ ],
+ ],
+ },
+ deserializable: true,
+ },
+ {
+ value: new Set(),
+ maxDepth: 1,
+ serialized: {
+ type: "set",
+ value: [],
+ },
+ deserializable: true,
+ },
+ {
+ value: new Set([]),
+ maxDepth: 1,
+ serialized: {
+ type: "set",
+ value: [],
+ },
+ deserializable: true,
+ },
+ {
+ 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 },
+ ],
+ },
+ deserializable: true,
+ },
+ { value: new WeakMap([[{}, 1]]), serialized: { type: "weakmap" } },
+ { value: new WeakSet([{}]), serialized: { type: "weakset" } },
+ { value: new Error("error message"), serialized: { type: "error" } },
+ {
+ value: new SyntaxError("syntax error message"),
+ serialized: { type: "error" },
+ },
+ {
+ value: new TypeError("type error message"),
+ serialized: { type: "error" },
+ },
+ { value: new Promise(() => true), serialized: { type: "promise" } },
+ { value: new Int8Array(), serialized: { type: "typedarray" } },
+ { value: new ArrayBuffer(), serialized: { type: "arraybuffer" } },
+ {
+ value: browser.document.querySelectorAll("div"),
+ serialized: { type: "nodelist" },
+ },
+ {
+ value: browser.document.getElementsByTagName("div"),
+ serialized: { type: "htmlcollection" },
+ },
+ {
+ value: domEl,
+ serialized: {
+ type: "node",
+ value: {
+ attributes: {},
+ childNodeCount: 0,
+ children: [],
+ localName: "div",
+ namespaceURI: "http://www.w3.org/1999/xhtml",
+ nodeType: 1,
+ },
+ },
+ },
+ { value: browser.document.defaultView, serialized: { type: "window" } },
+ { value: new URL("https://example.com"), serialized: { type: "object" } },
+ { value: () => true, serialized: { type: "function" } },
+ { value() {}, serialized: { type: "function" } },
+ {
+ value: {},
+ maxDepth: 1,
+ serialized: {
+ type: "object",
+ value: [],
+ },
+ deserializable: true,
+ },
+ {
+ value: {
+ "1": 1,
+ "2": "2",
+ foo: true,
+ },
+ serialized: {
+ type: "object",
+ },
+ },
+ {
+ value: {
+ "1": 1,
+ "2": "2",
+ foo: true,
+ },
+ maxDepth: 0,
+ serialized: {
+ type: "object",
+ },
+ },
+ {
+ value: {
+ "1": 1,
+ "2": "2",
+ foo: true,
+ },
+ maxDepth: 1,
+ serialized: {
+ type: "object",
+ value: [
+ ["1", { type: "number", value: 1 }],
+ ["2", { type: "string", value: "2" }],
+ ["foo", { type: "boolean", value: true }],
+ ],
+ },
+ deserializable: true,
+ },
+ {
+ value: {
+ "1": 1,
+ "2": "2",
+ "3": {
+ bar: "foo",
+ },
+ foo: true,
+ },
+ maxDepth: 2,
+ serialized: {
+ type: "object",
+ value: [
+ ["1", { type: "number", value: 1 }],
+ ["2", { type: "string", value: "2" }],
+ [
+ "3",
+ {
+ type: "object",
+ value: [["bar", { type: "string", value: "foo" }]],
+ },
+ ],
+ ["foo", { type: "boolean", value: true }],
+ ],
+ },
+ deserializable: true,
+ },
+];
+
+const { Realm } = ChromeUtils.importESModule(
+ "chrome://remote/content/webdriver-bidi/Realm.sys.mjs"
+);
+
+const { deserialize, serialize, stringify } = ChromeUtils.importESModule(
+ "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs"
+);
+
+add_test(function test_deserializePrimitiveTypes() {
+ const realm = new Realm();
+
+ for (const type of PRIMITIVE_TYPES) {
+ const { value: expectedValue, serialized } = type;
+
+ info(`Checking '${serialized.type}'`);
+ const value = deserialize(realm, serialized);
+
+ if (serialized.value == "NaN") {
+ ok(Number.isNaN(value), `Got expected value for ${serialized}`);
+ } else {
+ Assert.strictEqual(
+ value,
+ expectedValue,
+ `Got expected value for ${serialized}`
+ );
+ }
+ }
+
+ run_next_test();
+});
+
+add_test(function test_deserializeDateLocalValue() {
+ const realm = new Realm();
+
+ const validaDateStrings = [
+ "2009",
+ "2009-05",
+ "2009-05-19",
+ "2009T15:00",
+ "2009-05T15:00",
+ "2009-05-19T15:00",
+ "2009-05-19T15:00:15",
+ "2009-05-19T15:00:15.452",
+ "2009-05-19T15:00:15.452Z",
+ "2009-05-19T15:00:15.452+02:00",
+ "2009-05-19T15:00:15.452-02:00",
+ "-271821-04-20T00:00:00Z",
+ "+000000-01-01T00:00:00Z",
+ ];
+ for (const dateString of validaDateStrings) {
+ info(`Checking '${dateString}'`);
+ const value = deserialize(realm, { type: "date", value: dateString });
+
+ Assert.equal(
+ value.getTime(),
+ new Date(dateString).getTime(),
+ `Got expected value for ${dateString}`
+ );
+ }
+
+ run_next_test();
+});
+
+add_test(function test_deserializeLocalValues() {
+ const realm = new Realm();
+
+ for (const type of REMOTE_SIMPLE_VALUES.concat(REMOTE_COMPLEX_VALUES)) {
+ const { value: expectedValue, serialized, deserializable } = type;
+
+ // Skip non deserializable cases
+ if (!deserializable) {
+ continue;
+ }
+
+ info(`Checking '${serialized.type}'`);
+ const value = deserialize(realm, serialized);
+ assertLocalValue(serialized.type, value, expectedValue);
+ }
+
+ run_next_test();
+});
+
+add_test(function test_deserializeLocalValuesByHandle() {
+ // Create two realms, realm1 will be used to serialize values, while realm2
+ // will be used as a reference empty realm without any object reference.
+ const realm1 = new Realm();
+ const realm2 = new Realm();
+
+ for (const type of REMOTE_SIMPLE_VALUES.concat(REMOTE_COMPLEX_VALUES)) {
+ const { value: expectedValue, serialized } = type;
+
+ // No need to skip non-deserializable cases here.
+
+ info(`Checking '${serialized.type}'`);
+ // Serialize the value once to get a handle.
+ const serializedValue = serialize(
+ expectedValue,
+ 0,
+ "root",
+ new Map(),
+ realm1
+ );
+
+ // Create a remote reference containing only the handle.
+ // `deserialize` should not need any other property.
+ const remoteReference = { handle: serializedValue.handle };
+
+ // Check that the remote reference can be deserialized in realm1.
+ const deserializedValue = deserialize(realm1, remoteReference);
+ assertLocalValue(serialized.type, deserializedValue, expectedValue);
+
+ Assert.throws(
+ () => deserialize(realm2, remoteReference),
+ /InvalidArgumentError:/,
+ `Got expected error when using the wrong realm for deserialize`
+ );
+
+ realm1.removeObjectHandle(serializedValue.handle);
+ Assert.throws(
+ () => deserialize(realm1, remoteReference),
+ /InvalidArgumentError:/,
+ `Got expected error when after deleting the object handle`
+ );
+ }
+
+ run_next_test();
+});
+
+add_test(function test_deserializePrimitiveTypesInvalidValues() {
+ const realm = new Realm();
+
+ const invalidValues = [
+ { type: "bigint", values: [undefined, null, false, "foo", [], {}] },
+ { type: "boolean", values: [undefined, null, 42, "foo", [], {}] },
+ {
+ type: "number",
+ values: [undefined, null, false, "43", [], {}],
+ },
+ { type: "string", values: [undefined, null, false, 42, [], {}] },
+ ];
+
+ for (const invalidValue of invalidValues) {
+ const { type, values } = invalidValue;
+
+ for (const value of values) {
+ info(`Checking '${type}' with value ${value}`);
+
+ Assert.throws(
+ () => deserialize(realm, { type, value }),
+ /InvalidArgument/,
+ `Got expected error for type ${type} and value ${value}`
+ );
+ }
+ }
+
+ run_next_test();
+});
+
+add_test(function test_deserializeDateLocalValueInvalidValues() {
+ const realm = new Realm();
+
+ const invalidaDateStrings = [
+ "10",
+ "20009",
+ "+20009",
+ "2009-",
+ "2009-0",
+ "2009-15",
+ "2009-02-1",
+ "2009-02-50",
+ "2022-02-29",
+ "15:00",
+ "T15:00",
+ "9-05-19T15:00",
+ "2009-5-19T15:00",
+ "2009-05-1T15:00",
+ "2009-02-10T15",
+ "2009-05-19T15:",
+ "2009-05-19T1:00",
+ "2009-05-19T10:1",
+ "2022-06-31T15:00",
+ "2009-05-19T60:00",
+ "2009-05-19T15:70",
+ "2009-05-19T15:00.25",
+ "2009-05-19+10:00",
+ "2009-05-19Z",
+ "2009-05-19 15:00",
+ "2009-05-19t15:00Z",
+ "2009-05-19T15:00z",
+ "2009-05-19T15:00+01",
+ "2009-05-19T10:10+1:00",
+ "2009-05-19T10:10+01:1",
+ "2009-05-19T15:00+75:00",
+ "2009-05-19T15:00+02:80",
+ "2009-05-19T15:00-00:00",
+ "02009-05-19T15:00",
+ ];
+ for (const dateString of invalidaDateStrings) {
+ info(`Checking '${dateString}'`);
+
+ Assert.throws(
+ () => deserialize(realm, { type: "date", value: dateString }),
+ /InvalidArgumentError:/,
+ `Got expected error for date string: ${dateString}`
+ );
+ }
+
+ run_next_test();
+});
+
+add_test(function test_deserializeLocalValuesInvalidType() {
+ const realm = new Realm();
+
+ const invalidTypes = [undefined, null, false, 42, {}];
+
+ for (const invalidType of invalidTypes) {
+ info(`Checking type: '${invalidType}'`);
+
+ Assert.throws(
+ () => deserialize(realm, { type: invalidType }),
+ /InvalidArgumentError:/,
+ `Got expected error for type ${invalidType}`
+ );
+
+ Assert.throws(
+ () =>
+ deserialize(realm, {
+ type: "array",
+ value: [{ type: invalidType }],
+ }),
+ /InvalidArgumentError:/,
+ `Got expected error for nested type ${invalidType}`
+ );
+ }
+
+ run_next_test();
+});
+
+add_test(function test_deserializeLocalValuesInvalidValues() {
+ const realm = new Realm();
+
+ const invalidValues = [
+ { type: "array", values: [undefined, null, false, 42, "foo", {}] },
+ {
+ type: "regexp",
+ values: [
+ undefined,
+ null,
+ false,
+ "foo",
+ 42,
+ [],
+ {},
+ { pattern: null },
+ { pattern: 1 },
+ { pattern: true },
+ { pattern: "foo", flags: null },
+ { pattern: "foo", flags: 1 },
+ { pattern: "foo", flags: false },
+ { pattern: "foo", flags: "foo" },
+ ],
+ },
+ {
+ type: "date",
+ values: [
+ undefined,
+ null,
+ false,
+ "foo",
+ "05 October 2011 14:48 UTC",
+ "Tue Jun 14 2022 10:46:50 GMT+0200!",
+ 42,
+ [],
+ {},
+ ],
+ },
+ {
+ type: "map",
+ values: [
+ undefined,
+ null,
+ false,
+ "foo",
+ 42,
+ ["1"],
+ [[]],
+ [["1"]],
+ [{ "1": "2" }],
+ {},
+ ],
+ },
+ {
+ type: "set",
+ values: [undefined, null, false, "foo", 42, {}],
+ },
+ {
+ type: "object",
+ values: [
+ undefined,
+ null,
+ false,
+ "foo",
+ 42,
+ {},
+ ["1"],
+ [[]],
+ [["1"]],
+ [{ "1": "2" }],
+ [
+ [
+ { type: "number", value: "1" },
+ { type: "number", value: "2" },
+ ],
+ ],
+ [
+ [
+ { type: "object", value: [] },
+ { type: "number", value: "1" },
+ ],
+ ],
+ [
+ [
+ {
+ type: "regexp",
+ value: {
+ pattern: "foo",
+ },
+ },
+ { type: "number", value: "1" },
+ ],
+ ],
+ ],
+ },
+ ];
+
+ for (const invalidValue of invalidValues) {
+ const { type, values } = invalidValue;
+
+ for (const value of values) {
+ info(`Checking '${type}' with value ${value}`);
+
+ Assert.throws(
+ () => deserialize(realm, { type, value }),
+ /InvalidArgumentError:/,
+ `Got expected error for type ${type} and value ${value}`
+ );
+ }
+ }
+
+ run_next_test();
+});
+
+add_test(function test_serializePrimitiveTypes() {
+ const realm = new Realm();
+
+ for (const type of PRIMITIVE_TYPES) {
+ const { value, serialized } = type;
+
+ const serializationInternalMap = new Map();
+ const serializedValue = serialize(
+ value,
+ 0,
+ "none",
+ serializationInternalMap,
+ realm
+ );
+ assertInternalIds(serializationInternalMap, 0);
+ Assert.deepEqual(serialized, serializedValue, "Got expected structure");
+
+ // For primitive values, the serialization with ownershipType=root should
+ // be exactly identical to the one with ownershipType=none.
+ const serializationInternalMapWithRoot = new Map();
+ const serializedWithRoot = serialize(
+ value,
+ 0,
+ "root",
+ serializationInternalMapWithRoot,
+ realm
+ );
+ assertInternalIds(serializationInternalMapWithRoot, 0);
+ Assert.deepEqual(serialized, serializedWithRoot, "Got expected structure");
+ }
+
+ run_next_test();
+});
+
+add_test(function test_serializeRemoteSimpleValues() {
+ const realm = new Realm();
+
+ for (const type of REMOTE_SIMPLE_VALUES) {
+ const { value, serialized } = type;
+
+ info(`Checking '${serialized.type}' with none ownershipType`);
+ const serializationInternalMapWithNone = new Map();
+ const serializedValue = serialize(
+ value,
+ 0,
+ "none",
+ serializationInternalMapWithNone,
+ realm
+ );
+
+ assertInternalIds(serializationInternalMapWithNone, 0);
+ Assert.deepEqual(serialized, serializedValue, "Got expected structure");
+
+ info(`Checking '${serialized.type}' with root ownershipType`);
+ const serializationInternalMapWithRoot = new Map();
+ const serializedWithRoot = serialize(
+ value,
+ 0,
+ "root",
+ serializationInternalMapWithRoot,
+ realm
+ );
+
+ assertInternalIds(serializationInternalMapWithRoot, 0);
+ Assert.equal(
+ typeof serializedWithRoot.handle,
+ "string",
+ "Got a handle property"
+ );
+ Assert.deepEqual(
+ Object.assign({}, serialized, { handle: serializedWithRoot.handle }),
+ serializedWithRoot,
+ "Got expected structure, plus a generated handle id"
+ );
+ }
+
+ run_next_test();
+});
+
+add_test(function test_serializeRemoteComplexValues() {
+ const realm = new Realm();
+
+ for (const type of REMOTE_COMPLEX_VALUES) {
+ const { value, serialized, maxDepth } = type;
+
+ info(`Checking '${serialized.type}' with none ownershipType`);
+ const serializationInternalMapWithNone = new Map();
+ const serializedValue = serialize(
+ value,
+ maxDepth,
+ "none",
+ serializationInternalMapWithNone,
+ realm
+ );
+
+ assertInternalIds(serializationInternalMapWithNone, 0);
+ Assert.deepEqual(serialized, serializedValue, "Got expected structure");
+
+ info(`Checking '${serialized.type}' with root ownershipType`);
+ const serializationInternalMapWithRoot = new Map();
+ const serializedWithRoot = serialize(
+ value,
+ maxDepth,
+ "root",
+ serializationInternalMapWithRoot,
+ realm
+ );
+
+ assertInternalIds(serializationInternalMapWithRoot, 0);
+ Assert.equal(
+ typeof serializedWithRoot.handle,
+ "string",
+ "Got a handle property"
+ );
+ Assert.deepEqual(
+ Object.assign({}, serialized, { handle: serializedWithRoot.handle }),
+ serializedWithRoot,
+ "Got expected structure, plus a generated handle id"
+ );
+ }
+
+ run_next_test();
+});
+
+add_test(function test_serializeWithSerializationInternalMap() {
+ const dataSet = [
+ {
+ data: [1],
+ serializedData: [{ type: "number", value: 1 }],
+ type: "array",
+ },
+ {
+ data: new Map([[true, false]]),
+ serializedData: [
+ [
+ { type: "boolean", value: true },
+ { type: "boolean", value: false },
+ ],
+ ],
+ type: "map",
+ },
+ {
+ data: new Set(["foo"]),
+ serializedData: [{ type: "string", value: "foo" }],
+ type: "set",
+ },
+ {
+ data: { foo: "bar" },
+ serializedData: [["foo", { type: "string", value: "bar" }]],
+ type: "object",
+ },
+ ];
+ const realm = new Realm();
+
+ for (const { type, data, serializedData } of dataSet) {
+ info(`Checking '${type}' with serializationInternalMap`);
+
+ const serializationInternalMap = new Map();
+ const value = [
+ data,
+ data,
+ [data],
+ new Set([data]),
+ new Map([["bar", data]]),
+ { bar: data },
+ ];
+
+ const serializedValue = serialize(
+ value,
+ 2,
+ "none",
+ serializationInternalMap,
+ realm
+ );
+
+ assertInternalIds(serializationInternalMap, 1);
+
+ const internalId = serializationInternalMap.get(data).internalId;
+
+ const serialized = {
+ type: "array",
+ value: [
+ {
+ type,
+ value: serializedData,
+ internalId,
+ },
+ {
+ type,
+ internalId,
+ },
+ {
+ type: "array",
+ value: [{ type, internalId }],
+ },
+ {
+ type: "set",
+ value: [{ type, internalId }],
+ },
+ {
+ type: "map",
+ value: [["bar", { type, internalId }]],
+ },
+ {
+ type: "object",
+ value: [["bar", { type, internalId }]],
+ },
+ ],
+ };
+
+ Assert.deepEqual(serialized, serializedValue, "Got expected structure");
+ }
+
+ run_next_test();
+});
+
+add_test(function test_serializeMultipleValuesWithSerializationInternalMap() {
+ const realm = new Realm();
+ const serializationInternalMap = new Map();
+ const obj1 = { foo: "bar" };
+ const obj2 = [1, 2];
+ const value = [obj1, obj2, obj1, obj2];
+
+ serialize(value, 2, "none", serializationInternalMap, realm);
+
+ assertInternalIds(serializationInternalMap, 2);
+
+ const internalId1 = serializationInternalMap.get(obj1).internalId;
+ const internalId2 = serializationInternalMap.get(obj2).internalId;
+
+ Assert.notEqual(
+ internalId1,
+ internalId2,
+ "Internal ids for different object are also different"
+ );
+
+ run_next_test();
+});
+
+add_test(function test_stringify() {
+ const STRINGIFY_TEST_CASES = [
+ [undefined, "undefined"],
+ [null, "null"],
+ ["foobar", "foobar"],
+ ["2", "2"],
+ [-0, "0"],
+ [Infinity, "Infinity"],
+ [-Infinity, "-Infinity"],
+ [3, "3"],
+ [1.4, "1.4"],
+ [true, "true"],
+ [42n, "42"],
+ [{ toString: () => "bar" }, "bar", "toString: () => 'bar'"],
+ [{ toString: () => 4 }, "[object Object]", "toString: () => 4"],
+ [{ toString: undefined }, "[object Object]", "toString: undefined"],
+ [{ toString: null }, "[object Object]", "toString: null"],
+ [
+ {
+ toString: () => {
+ throw new Error("toString error");
+ },
+ },
+ "[object Object]",
+ "toString: () => { throw new Error('toString error'); }",
+ ],
+ ];
+
+ for (const [value, expectedString, description] of STRINGIFY_TEST_CASES) {
+ info(`Checking '${description || value}'`);
+ const stringifiedValue = stringify(value);
+
+ Assert.strictEqual(expectedString, stringifiedValue, "Got expected string");
+ }
+
+ run_next_test();
+});
+
+function assertLocalValue(type, value, expectedValue) {
+ let formattedValue = value;
+ let formattedExpectedValue = expectedValue;
+
+ // Format certain types for easier assertion
+ if (type == "map") {
+ Assert.equal(
+ Object.prototype.toString.call(expectedValue),
+ "[object Map]",
+ "Got expected type Map"
+ );
+
+ formattedValue = Array.from(value.values());
+ formattedExpectedValue = Array.from(expectedValue.values());
+ } else if (type == "set") {
+ Assert.equal(
+ Object.prototype.toString.call(expectedValue),
+ "[object Set]",
+ "Got expected type Set"
+ );
+
+ formattedValue = Array.from(value);
+ formattedExpectedValue = Array.from(expectedValue);
+ }
+
+ Assert.deepEqual(
+ formattedValue,
+ formattedExpectedValue,
+ "Got expected structure"
+ );
+}
+
+function assertInternalIds(serializationInternalMap, amount) {
+ const remoteValuesWithInternalIds = Array.from(
+ serializationInternalMap.values()
+ ).filter(remoteValue => !!remoteValue.internalId);
+
+ Assert.equal(
+ remoteValuesWithInternalIds.length,
+ amount,
+ "Got expected amount of internalIds in serializationInternalMap"
+ );
+}
diff --git a/remote/webdriver-bidi/test/xpcshell/test_WebDriverBiDiConnection.js b/remote/webdriver-bidi/test/xpcshell/test_WebDriverBiDiConnection.js
new file mode 100644
index 0000000000..10b43e4ed6
--- /dev/null
+++ b/remote/webdriver-bidi/test/xpcshell/test_WebDriverBiDiConnection.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { splitMethod } = ChromeUtils.importESModule(
+ "chrome://remote/content/webdriver-bidi/WebDriverBiDiConnection.sys.mjs"
+);
+
+add_test(function test_Connection_splitMethod() {
+ for (const t of [42, null, true, {}, [], undefined]) {
+ Assert.throws(() => splitMethod(t), /TypeError/, `${typeof t} throws`);
+ }
+ for (const s of ["", ".", "foo.", ".bar", "foo.bar.baz"]) {
+ Assert.throws(
+ () => splitMethod(s),
+ /Invalid method format: '.*'/,
+ `"${s}" throws`
+ );
+ }
+ deepEqual(splitMethod("foo.bar"), {
+ module: "foo",
+ command: "bar",
+ });
+
+ run_next_test();
+});
diff --git a/remote/webdriver-bidi/test/xpcshell/xpcshell.ini b/remote/webdriver-bidi/test/xpcshell/xpcshell.ini
new file mode 100644
index 0000000000..8873ae646d
--- /dev/null
+++ b/remote/webdriver-bidi/test/xpcshell/xpcshell.ini
@@ -0,0 +1,7 @@
+# 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/.
+
+[test_Realm.js]
+[test_RemoteValue.js]
+[test_WebDriverBiDiConnection.js]