Bug 1337133 - Move update of inputStateMap to action.Sequence.fromJson; r?ato
This syncs the implementation of "process an input source action sequence"
with the Webdriver spec.
Previously, Marionette populated the input state table at dispatch time.
MozReview-Commit-ID: 8v1y5uVvrI5
--- a/testing/marionette/action.js
+++ b/testing/marionette/action.js
@@ -347,19 +347,17 @@ action.PointerOrigin = {
* If |obj| is not a valid origin.
*/
action.PointerOrigin.get = function(obj) {
let origin = obj;
if (typeof obj == "undefined") {
origin = this.Viewport;
} else if (typeof obj == "string") {
let name = capitalize(obj);
- if (!(name in this)) {
- throw new InvalidArgumentError(`Unknown pointer-move origin: ${obj}`);
- }
+ assert.in(name, this, error.pprint`Unknown pointer-move origin: ${obj}`);
origin = this[name];
} else if (!element.isWebElementReference(obj)) {
throw new InvalidArgumentError("Expected 'origin' to be a string or a " +
`web element reference, got: ${obj}`);
}
return origin;
};
@@ -380,19 +378,17 @@ action.PointerType = {
* @return {string}
* A pointer type for processing pointer parameters.
*
* @throws {InvalidArgumentError}
* If |str| is not a valid pointer type.
*/
action.PointerType.get = function (str) {
let name = capitalize(str);
- if (!(name in this)) {
- throw new InvalidArgumentError(`Unknown pointerType: ${str}`);
- }
+ assert.in(name, this, error.pprint`Unknown pointerType: ${str}`);
return this[name];
};
/**
* Input state associated with current session. This is a map between input ID and
* the device state for that input source, with one entry for each active input source.
*
* Initialized in listener.js
@@ -443,19 +439,17 @@ class InputState {
* @return {action.InputState}
* An |action.InputState| object for the type of the |actionSequence|.
*
* @throws {InvalidArgumentError}
* If |actionSequence.type| is not valid.
*/
static fromJson(obj) {
let type = obj.type;
- if (!(type in ACTIONS)) {
- throw new InvalidArgumentError(`Unknown action type: ${type}`);
- }
+ assert.in(type, ACTIONS, error.pprint`Unknown action type: ${type}`);
let name = type == "none" ? "Null" : capitalize(type);
if (name == "Pointer") {
if (!obj.pointerType && (!obj.parameters || !obj.parameters.pointerType)) {
throw new InvalidArgumentError(
error.pprint`Expected obj to have pointerType, got: ${obj}`);
}
let pointerType = obj.pointerType || obj.parameters.pointerType;
return new action.InputState[name](pointerType);
@@ -627,19 +621,17 @@ action.InputState.Pointer = class Pointe
* If any parameters are undefined.
*/
action.Action = class {
constructor(id, type, subtype) {
if ([id, type, subtype].includes(undefined)) {
throw new InvalidArgumentError("Missing id, type or subtype");
}
for (let attr of [id, type, subtype]) {
- if (typeof attr != "string") {
- throw new InvalidArgumentError(`Expected string, got: ${attr}`);
- }
+ assert.string(attr, error.pprint`Expected string, got: ${attr}`);
}
this.id = id;
this.type = type;
this.subtype = subtype;
};
toString() {
return `[action ${this.type}]`;
@@ -679,21 +671,19 @@ action.Action = class {
switch (item.subtype) {
case action.KeyUp:
case action.KeyDown:
let key = actionItem.value;
// TODO countGraphemes
// TODO key.value could be a single code point like "\uE012" (see rawKey)
// or "grapheme cluster"
- if (typeof key != "string") {
- throw new InvalidArgumentError(
- "Expected 'value' to be a string that represents single code point " +
- "or grapheme cluster, got: " + key);
- }
+ assert.string(key,
+ error.pprint("Expected 'value' to be a string that represents single code point " +
+ `or grapheme cluster, got: ${key}`));
item.value = key;
break;
case action.PointerDown:
case action.PointerUp:
assert.positiveInteger(actionItem.button,
error.pprint`Expected 'button' (${actionItem.button}) to be >= 0`);
item.button = actionItem.button;
@@ -787,28 +777,28 @@ action.Sequence = class extends Array {
* Sequence of actions that can be dispatched.
*
* @throws {InvalidArgumentError}
* If |actionSequence.id| is not a string or it's aleady mapped
* to an |action.InputState} incompatible with |actionSequence.type|.
* If |actionSequence.actions| is not an Array.
*/
static fromJson(actionSequence) {
- // used here only to validate 'type' and InputState type
+ // used here to validate 'type' in addition to InputState type below
let inputSourceState = InputState.fromJson(actionSequence);
let id = actionSequence.id;
assert.defined(id, "Expected 'id' to be defined");
assert.string(id, error.pprint`Expected 'id' to be a string, got: ${id}`);
let actionItems = actionSequence.actions;
- if (!Array.isArray(actionItems)) {
- throw new InvalidArgumentError(
- `Expected 'actionSequence.actions' to be an Array, got: ${actionSequence.actions}`);
- }
-
- if (action.inputStateMap.has(id) && !action.inputStateMap.get(id).is(inputSourceState)) {
+ assert.array(actionItems,
+ error.pprint("Expected 'actionSequence.actions' to be an Array, " +
+ `got: ${actionSequence.actions}`));
+ if (!action.inputStateMap.has(id)) {
+ action.inputStateMap.set(id, inputSourceState);
+ } else if (!action.inputStateMap.get(id).is(inputSourceState)) {
throw new InvalidArgumentError(
`Expected ${id} to be mapped to ${inputSourceState}, ` +
`got: ${action.inputStateMap.get(id)}`);
}
let actions = new action.Sequence();
for (let actionItem of actionItems) {
actions.push(action.Action.fromJson(actionSequence, actionItem));
}
@@ -1037,19 +1027,16 @@ action.computePointerDestination = funct
* Object with |frame| attribute of type |nsIDOMWindow|.
*
* @return {function(action.Action): Promise}
* Function that takes an action and returns a Promise for dispatching
* the event that corresponds to that action.
*/
function toEvents(tickDuration, seenEls, container) {
return function (a) {
- if (!action.inputStateMap.has(a.id)) {
- action.inputStateMap.set(a.id, InputState.fromJson(a));
- }
let inputState = action.inputStateMap.get(a.id);
switch (a.subtype) {
case action.KeyUp:
return dispatchKeyUp(a, inputState, container.frame);
case action.KeyDown:
return dispatchKeyDown(a, inputState, container.frame);
@@ -1336,19 +1323,17 @@ function dispatchPause(a, tickDuration)
* @return {Promise}
* Promise to flush DOM events.
*/
function flushEvents(container) {
return new Promise(resolve => container.frame.requestAnimationFrame(resolve));
}
function capitalize(str) {
- if (typeof str != "string") {
- throw new InvalidArgumentError(`Expected string, got: ${str}`);
- }
+ assert.string(str);
return str.charAt(0).toUpperCase() + str.slice(1);
}
function inViewPort(x, y, win) {
assert.number(x);
assert.number(y);
// Viewport includes scrollbars if rendered.
return !(x < 0 || y < 0 || x > win.innerWidth || y > win.innerHeight);
--- a/testing/marionette/test_action.js
+++ b/testing/marionette/test_action.js
@@ -329,46 +329,51 @@ add_test(function test_processKeyActionU
run_next_test();
});
add_test(function test_processInputSourceActionSequenceValidation() {
let actionSequence = {type: "swim", id: "some id"};
let check = (message, regex) => checkErrors(
regex, action.Sequence.fromJson, [actionSequence], message);
check(`actionSequence.type: ${actionSequence.type}`, /Unknown action type/);
+ action.inputStateMap.clear();
actionSequence.type = "none";
actionSequence.id = -1;
check(`actionSequence.id: ${getTypeString(actionSequence.id)}`,
/Expected 'id' to be a string/);
+ action.inputStateMap.clear();
actionSequence.id = undefined;
check(`actionSequence.id: ${getTypeString(actionSequence.id)}`,
/Expected 'id' to be defined/);
+ action.inputStateMap.clear();
actionSequence.id = "some_id";
actionSequence.actions = -1;
check(`actionSequence.actions: ${getTypeString(actionSequence.actions)}`,
/Expected 'actionSequence.actions' to be an Array/);
+ action.inputStateMap.clear();
run_next_test();
});
add_test(function test_processInputSourceActionSequence() {
let actionItem = { type: "pause", duration: 5};
let actionSequence = {
type: "none",
id: "some id",
actions: [actionItem],
};
let expectedAction = new action.Action(actionSequence.id, "none", actionItem.type);
expectedAction.duration = actionItem.duration;
let actions = action.Sequence.fromJson(actionSequence);
equal(actions.length, 1);
deepEqual(actions[0], expectedAction);
+ action.inputStateMap.clear();
run_next_test();
});
add_test(function test_processInputSourceActionSequencePointer() {
let actionItem = {type: "pointerDown", button: 1};
let actionSequence = {
type: "pointer",
id: "9",
@@ -379,32 +384,34 @@ add_test(function test_processInputSourc
};
let expectedAction = new action.Action(
actionSequence.id, actionSequence.type, actionItem.type);
expectedAction.pointerType = actionSequence.parameters.pointerType;
expectedAction.button = actionItem.button;
let actions = action.Sequence.fromJson(actionSequence);
equal(actions.length, 1);
deepEqual(actions[0], expectedAction);
+ action.inputStateMap.clear();
run_next_test();
});
add_test(function test_processInputSourceActionSequenceKey() {
let actionItem = {type: "keyUp", value: "a"};
let actionSequence = {
type: "key",
id: "9",
actions: [actionItem],
};
let expectedAction = new action.Action(
actionSequence.id, actionSequence.type, actionItem.type);
expectedAction.value = actionItem.value;
let actions = action.Sequence.fromJson(actionSequence);
equal(actions.length, 1);
deepEqual(actions[0], expectedAction);
+ action.inputStateMap.clear();
run_next_test();
});
add_test(function test_processInputSourceActionSequenceInputStateMap() {
let id = "1";
let actionItem = {type: "pause", duration: 5000};
let actionSequence = {
@@ -500,16 +507,17 @@ add_test(function test_extractActionChai
actions: [actionItem],
};
let expectedAction = new action.Action(actionSequence.id, "none", actionItem.type);
expectedAction.duration = actionItem.duration;
let actionsByTick = action.Chain.fromJson([actionSequence]);
equal(1, actionsByTick.length);
equal(1, actionsByTick[0].length);
deepEqual(actionsByTick, [[expectedAction]]);
+ action.inputStateMap.clear();
run_next_test();
});
add_test(function test_extractActionChain_twoAndThreeTicks() {
let mouseActionItems = [
{
type: "pointerDown",
button: 2,
@@ -550,22 +558,24 @@ add_test(function test_extractActionChai
// number of ticks is same as longest action sequence
equal(keyActionItems.length, actionsByTick.length);
equal(2, actionsByTick[0].length);
equal(2, actionsByTick[1].length);
equal(1, actionsByTick[2].length);
let expectedAction = new action.Action(keyActionSequence.id, "key", keyActionItems[2].type);
expectedAction.value = keyActionItems[2].value;
deepEqual(actionsByTick[2][0], expectedAction);
+ action.inputStateMap.clear();
// one empty action sequence
actionsByTick = action.Chain.fromJson(
[keyActionSequence, {type: "none", id: "some", actions: []}]);
equal(keyActionItems.length, actionsByTick.length);
equal(1, actionsByTick[0].length);
+ action.inputStateMap.clear();
run_next_test();
});
add_test(function test_computeTickDuration() {
let expected = 8000;
let tickActions = [
{type: "none", subtype: "pause", duration: 5000},
{type: "key", subtype: "pause", duration: 1000},