Bug 1788124 - [bidi] Deserialize remote values with handle field r=webdriver-reviewers,whimboo

Depends on D155446

With this you can retrieve a remote value with a handle from script.execute and then use it in your next call to script.callFunction

Differential Revision: https://phabricator.services.mozilla.com/D155522
This commit is contained in:
Julian Descottes 2022-08-31 22:59:31 +00:00
parent d3b9951dbf
commit fb6fa92fdf
4 changed files with 126 additions and 59 deletions

View File

@ -145,7 +145,7 @@ function checkDateTimeString(dateString) {
* @throws {InvalidArgumentError}
* If <var>serializedValueList</var> is not an array.
*/
function deserializeValueList(/* realm, */ serializedValueList) {
function deserializeValueList(realm, serializedValueList) {
lazy.assert.array(
serializedValueList,
`Expected "serializedValueList" to be an array, got ${serializedValueList}`
@ -154,7 +154,7 @@ function deserializeValueList(/* realm, */ serializedValueList) {
const deserializedValues = [];
for (const item of serializedValueList) {
deserializedValues.push(deserialize(/*realm, */ item));
deserializedValues.push(deserialize(realm, item));
}
return deserializedValues;
@ -174,7 +174,7 @@ function deserializeValueList(/* realm, */ serializedValueList) {
* If <var>serializedKeyValueList</var> is not an array or
* not an array of key-value arrays.
*/
function deserializeKeyValueList(/* realm, */ serializedKeyValueList) {
function deserializeKeyValueList(realm, serializedKeyValueList) {
lazy.assert.array(
serializedKeyValueList,
`Expected "serializedKeyValueList" to be an array, got ${serializedKeyValueList}`
@ -192,8 +192,8 @@ function deserializeKeyValueList(/* realm, */ serializedKeyValueList) {
const deserializedKey =
typeof serializedKey == "string"
? serializedKey
: deserialize(/* realm, */ serializedKey);
const deserializedValue = deserialize(/* realm, */ serializedValue);
: deserialize(realm, serializedKey);
const deserializedValue = deserialize(realm, serializedValue);
deserializedKeyValueList.push([deserializedKey, deserializedValue]);
}
@ -211,21 +211,24 @@ function deserializeKeyValueList(/* realm, */ serializedKeyValueList) {
*
* @return {Object} Deserialized representation of the value.
*/
function deserialize(/* realm, */ serializedValue) {
const { objectId, type, value } = serializedValue;
function deserialize(realm, serializedValue) {
const { handle, type, value } = serializedValue;
// With an objectId present deserialize as remote reference.
if (objectId !== undefined) {
// With a handle present deserialize as remote reference.
if (handle !== undefined) {
lazy.assert.string(
objectId,
`Expected "objectId" to be a string, got ${objectId}`
handle,
`Expected "handle" to be a string, got ${handle}`
);
// TODO: Implement deserialization of remote references (bug 1693838)
lazy.logger.warn(
`Unsupported type remote reference with objectId ${objectId}`
);
return undefined;
const object = realm.getObjectForHandle(handle);
if (!object) {
throw new lazy.error.InvalidArgumentError(
`Unable to find an object reference for "handle" ${handle}`
);
}
return object;
}
lazy.assert.string(type, `Expected "type" to be a string, got ${type}`);
@ -276,7 +279,7 @@ function deserialize(/* realm, */ serializedValue) {
// Non-primitive protocol values
case "array":
return deserializeValueList(/* realm, */ value);
return deserializeValueList(realm, value);
case "date":
// We want to support only Date Time String format,
// check if the value follows it.
@ -284,9 +287,9 @@ function deserialize(/* realm, */ serializedValue) {
return new Date(value);
case "map":
return new Map(deserializeKeyValueList(value));
return new Map(deserializeKeyValueList(realm, value));
case "object":
return Object.fromEntries(deserializeKeyValueList(value));
return Object.fromEntries(deserializeKeyValueList(realm, value));
case "regexp":
lazy.assert.object(
value,
@ -311,7 +314,7 @@ function deserialize(/* realm, */ serializedValue) {
);
}
case "set":
return new Set(deserializeValueList(/* realm, */ value));
return new Set(deserializeValueList(realm, value));
}
lazy.logger.warn(`Unsupported type for local value ${type}`);

View File

@ -233,15 +233,15 @@ class ScriptModule extends Module {
thisParameter = null,
} = options;
const realm = this.#getRealmFromSandboxName(sandboxName);
const deserializedArguments =
commandArguments !== null
? commandArguments.map(arg => lazy.deserialize(arg))
? commandArguments.map(arg => lazy.deserialize(realm, arg))
: [];
const deserializedThis =
thisParameter !== null ? lazy.deserialize(thisParameter) : null;
const realm = this.#getRealmFromSandboxName(sandboxName);
thisParameter !== null ? lazy.deserialize(realm, thisParameter) : null;
const rv = realm.executeInGlobalWithBindings(
functionDeclaration,

View File

@ -306,11 +306,13 @@ const { deserialize, serialize, stringify } = ChromeUtils.import(
);
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(serialized);
const value = deserialize(realm, serialized);
if (serialized.value == "NaN") {
ok(Number.isNaN(value), `Got expected value for ${serialized}`);
@ -327,6 +329,8 @@ add_test(function test_deserializePrimitiveTypes() {
});
add_test(function test_deserializeDateLocalValue() {
const realm = new Realm();
const validaDateStrings = [
"2009",
"2009-05",
@ -344,7 +348,7 @@ add_test(function test_deserializeDateLocalValue() {
];
for (const dateString of validaDateStrings) {
info(`Checking '${dateString}'`);
const value = deserialize({ type: "date", value: dateString });
const value = deserialize(realm, { type: "date", value: dateString });
Assert.equal(
value.getTime(),
@ -357,6 +361,8 @@ add_test(function test_deserializeDateLocalValue() {
});
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;
@ -366,36 +372,53 @@ add_test(function test_deserializeLocalValues() {
}
info(`Checking '${serialized.type}'`);
const value = deserialize(serialized);
const value = deserialize(realm, serialized);
assertLocalValue(serialized.type, value, expectedValue);
}
let formattedValue = value;
let formattedExpectedValue = expectedValue;
run_next_test();
});
// Format certain types for easier assertion
if (serialized.type == "map") {
Assert.equal(
Object.prototype.toString.call(expectedValue),
"[object Map]",
"Got expected type Map"
);
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();
formattedValue = Array.from(value.values());
formattedExpectedValue = Array.from(expectedValue.values());
} else if (serialized.type == "set") {
Assert.equal(
Object.prototype.toString.call(expectedValue),
"[object Set]",
"Got expected type Set"
);
for (const type of REMOTE_SIMPLE_VALUES.concat(REMOTE_COMPLEX_VALUES)) {
const { value: expectedValue, serialized } = type;
formattedValue = Array.from(value);
formattedExpectedValue = Array.from(expectedValue);
}
// No need to skip non-deserializable cases here.
Assert.deepEqual(
formattedValue,
formattedExpectedValue,
"Got expected structure"
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`
);
}
@ -403,6 +426,8 @@ add_test(function test_deserializeLocalValues() {
});
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", [], {}] },
@ -420,7 +445,7 @@ add_test(function test_deserializePrimitiveTypesInvalidValues() {
info(`Checking '${type}' with value ${value}`);
Assert.throws(
() => deserialize({ type, value }),
() => deserialize(realm, { type, value }),
/InvalidArgument/,
`Got expected error for type ${type} and value ${value}`
);
@ -431,6 +456,8 @@ add_test(function test_deserializePrimitiveTypesInvalidValues() {
});
add_test(function test_deserializeDateLocalValueInvalidValues() {
const realm = new Realm();
const invalidaDateStrings = [
"10",
"20009",
@ -471,7 +498,7 @@ add_test(function test_deserializeDateLocalValueInvalidValues() {
info(`Checking '${dateString}'`);
Assert.throws(
() => deserialize({ type: "date", value: dateString }),
() => deserialize(realm, { type: "date", value: dateString }),
/InvalidArgumentError:/,
`Got expected error for date string: ${dateString}`
);
@ -481,19 +508,25 @@ add_test(function test_deserializeDateLocalValueInvalidValues() {
});
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({ type: invalidType }),
() => deserialize(realm, { type: invalidType }),
/InvalidArgumentError:/,
`Got expected error for type ${invalidType}`
);
Assert.throws(
() => deserialize({ type: "array", value: [{ type: invalidType }] }),
() =>
deserialize(realm, {
type: "array",
value: [{ type: invalidType }],
}),
/InvalidArgumentError:/,
`Got expected error for nested type ${invalidType}`
);
@ -503,6 +536,8 @@ add_test(function test_deserializeLocalValuesInvalidType() {
});
add_test(function test_deserializeLocalValuesInvalidValues() {
const realm = new Realm();
const invalidValues = [
{ type: "array", values: [undefined, null, false, 42, "foo", {}] },
{
@ -604,7 +639,7 @@ add_test(function test_deserializeLocalValuesInvalidValues() {
info(`Checking '${type}' with value ${value}`);
Assert.throws(
() => deserialize({ type, value }),
() => deserialize(realm, { type, value }),
/InvalidArgumentError:/,
`Got expected error for type ${type} and value ${value}`
);
@ -739,3 +774,35 @@ add_test(function test_stringify() {
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"
);
}

View File

@ -1,6 +1,3 @@
[call_function.py]
[test_default_this]
expected: FAIL
[test_remote_value_argument]
expected: FAIL