Formalize PointerType and ACTIONS; rework InputState
MozReview-Commit-ID: 3bUmPdUHVrn
--- a/testing/marionette/action.js
+++ b/testing/marionette/action.js
@@ -11,74 +11,121 @@ Cu.import("resource://gre/modules/Log.js
Cu.import("chrome://marionette/content/element.js");
Cu.import("chrome://marionette/content/error.js");
this.EXPORTED_SYMBOLS = ["action"];
const logger = Log.repository.getLogger("Marionette");
-this.action = {};
+this.action = {
+ Pause: "pause",
+ KeyDown: "keyDown",
+ KeyUp: "keyUp",
+ PointerDown: "pointerDown",
+ PointerUp: "pointerUp",
+ PointerMove: "pointerMove",
+ PointerCancel: "pointerCancel",
+};
-action.Types = new Set([
- "none",
- "key",
- "pointer",
-]);
+const ACTIONS = {
+ none: new Set([action.Pause]),
+ key: new Set([action.Pause, action.KeyDown, action.KeyUp]),
+ pointer: new Set([
+ action.Pause,
+ action.PointerDown,
+ action.PointerUp,
+ action.PointerMove,
+ action.PointerCancel,
+ ]),
+};
+
+
+action.PointerType = {
+ Mouse: "mouse",
+ Pen: "pen",
+ Touch: "touch",
+};
+
+action.PointerType.get = function(str){
+ let name = capitalize(str);
+ if (!(capitalize(str) in this)) {
+ throw new InvalidArgumentError(`Unknown pointerType: ${str}`);
+ }
+ else {
+ return this[name];
+ }
+};
// map between input id (string) and device state for that input source
// TODO associate with session
// populated by "dispatch tick actions"?
action.inputStateMap = new Map();
// input source: virtual device that has an string id and a source type
// This doesn't seem to be really used in spec. Each "actionSequence" has id
// and type already, which is all we really need.
// action.InputSource = function InputSource(id, type) {
// this.id = id;
// // null, key, pointer
// this.type = type;
// }
-action.NullInputState = function NullInputState(){
- // a named empty object
-};
+class InputState {
+ constructor() {
+ this.type = this.constructor.name.toLowerCase();
+ }
+
+ is(other) {
+ return this.type == other.type;
+ }
+
+ toString() {
+ return `[object ${this.constructor.name}InputState]`;
+ }
-action.NullInputState.prototype.toString = function() {
- return '[object NullInputState]';
-};
+ static fromJson(json) {
+ let type = json.type;
+ if (!(type in ACTIONS)) {
+ throw new InvalidArgumentError(`Unknown action type: ${type}`);
+ }
+ let name = type == "none" ? "Null" : capitalize(type);
+ return new action.InputState[name]();
+ }
+}
-action.KeyInputState = function KeyInputState() {
- this.pressed = new Set();
- this.alt = false;
- this.shift = false;
- this.ctrl = false;
- this.meta = false;
+action.InputState = {};
+
+action.InputState.Key = class extends InputState {
+ constructor() {
+ super();
+ this.pressed = new Set();
+ this.alt = false;
+ this.shift = false;
+ this.ctrl = false;
+ this.meta = false;
+ }
};
-action.KeyInputState.prototype.toString = function() {
- return '[object KeyInputState]';
+action.InputState.Null = class extends InputState {
+ constructor() {
+ super();
+ this.type = 'none';
+ }
};
-action.PointerInputState = function PointerInputState(subtype, primary) {
- this.pressed = new Set();
- this.subtype = subtype;
- this.primary = primary;
- this.x = 0;
- this.y = 0;
-};
-
-action.PointerInputState.prototype.toString = function() {
- return '[object PointerInputState]';
-};
-
-const ACTION_INPUT_STATE = {
- none: action.NullInputState,
- key: action.KeyInputState,
- pointer: action.PointerInputState,
+action.InputState.Pointer = class extends InputState {
+ constructor(subtype, primary) {
+ super();
+ this.pressed = new Set();
+ this.subtype = subtype;
+ this.primary = primary;
+ this.x = 0;
+ this.y = 0;
+ }
};
action.Action = function Action(id, type, subtype) {
// represents action object for actionByTick
this.id = id;
this.type = type;
this.subtype = subtype;
};
@@ -102,19 +149,18 @@ action.extractActionChain = function ext
}
return actionsByTick;
};
// action_sequence has a list of actionItems for one input source
action.processInputSourceActionSequence = function processInputSourceActionSequence(
actionSequence) {
let type = actionSequence.type;
- if (!action.Types.has(type)) {
- throw new InvalidArgumentError(`Invalid 'actionSequence.type', got: ${type}`);
- }
+ // used here only to validate 'type' and InputState type
+ let inputSourceState = InputState.fromJson(actionSequence);
let id = actionSequence.id;
if (typeof id == 'undefined') {
id = element.generateUUID();
} else if (typeof id != 'string') {
throw new InvalidArgumentError(`Expected 'id' to be a string, got: ${id}`);
}
let actionItems = actionSequence.actions;
if (!Array.isArray(actionItems)) {
@@ -122,23 +168,21 @@ action.processInputSourceActionSequence
`Expected 'actionSequence.actions' to be an Array, got: ${actionSequence.actions}`);
}
let pointerParams;
if (type === "pointer") {
pointerParams = action.processPointerParameters(actionSequence.parameters);
}
- if (action.inputStateMap.has(id) &&
- !(action.inputStateMap.get(id) instanceof ACTION_INPUT_STATE[type])) {
+ if (action.inputStateMap.has(id) && !action.inputStateMap.get(id).is(inputSourceState)) {
throw new InvalidArgumentError(
- `Expected ${id} to be mapped to ${ACTION_INPUT_STATE[type].name}, ` +
+ `Expected ${id} to be mapped to ${inputSourceState}, ` +
`got: ${action.inputStateMap.get(id)}`);
}
-
let actions = [];
for (let actionItem of actionItems) {
let act;
switch(type) {
case "none":
act = action.processNullAction(id, actionItem);
break;
case "key":
@@ -150,79 +194,63 @@ action.processInputSourceActionSequence
}
actions.push(act);
}
return actions;
};
action.processPointerParameters = function processPointerParameters(parametersData) {
let pointerParams = {
- pointerType: "mouse",
+ pointerType: action.PointerType.Mouse,
primary: true,
};
if (typeof parametersData == 'undefined') {
return pointerParams;
}
- let pointerType = parametersData.pointerType;
- if (typeof pointerType != 'undefined') {
- let types = ["mouse", "pen", "touch"];
- if(!types.includes(pointerType)){
- throw new InvalidArgumentError(
- `Expected 'pointerType' to be one of ${types}, got: ${pointerType}`);
- }
- pointerParams.pointerType = pointerType;
+ if (typeof parametersData.pointerType != 'undefined') {
+ pointerParams.pointerType = action.PointerType.get(parametersData.pointerType);
}
let primary = parametersData.primary;
if (typeof primary != 'undefined') {
assertBoolean(primary, 'primary');
pointerParams.primary = primary;
}
return pointerParams;
};
action.processNullAction = function processNullAction(id, actionItem) {
let subtype = actionItem.type;
- if (subtype !== "pause") {
- throw new InvalidArgumentError("Expected 'subtype' to be 'pause', got: " + subtype);
- }
+ assertIsValidSubtype(subtype, "none");
let act = new action.Action(id, "none", subtype);
action.processPauseAction(actionItem, act);
return act;
};
action.processKeyAction = function processKeyAction(id, actionItem) {
let subtype = actionItem.type;
- let types = ["keyUp", "keyDown", "pause"];
- if (!types.includes(subtype)){
- throw new InvalidArgumentError(`Expected 'subtype' to be one of ${Array.from(types)}, ` +
- `got: ${subtype}`);
- }
+ assertIsValidSubtype(subtype, "key");
let act = new action.Action(id, "key", subtype);
if (subtype === "pause"){
action.processPauseAction(actionItem, act);
return act;
}
let key = actionItem.value
+ // todo countGraphemes?
// what about key codes like arrow, versus unicode chars?
if (typeof key != 'string' || (typeof key == 'string' && key.length != 1)) {
throw new InvalidArgumentError("Expected 'key' to be a single-character String, " +
"got: " + key);
}
act.value = key;
return act;
};
action.processPointerAction = function processPointerAction(id, pointerParams, actionItem) {
let subtype = actionItem.type;
- let types = ["pause", "pointerUp", "pointerDown", "pointerMove", "pointerCancel"];
- if (!types.includes(subtype)) {
- throw new InvalidArgumentError(`Expected 'subtype' to be one of ${Array.from(types)}, ` +
- `got ${subtype}`);
- }
-
+ assertIsValidSubtype(subtype, "pointer");
if (action.inputStateMap.has(id) && action.inputStateMap.get(id).subtype !== subtype) {
throw new InvalidArgumentError(
`Expected 'id' ${id} to be mapped to InputState whose subtype is ` +
`${action.inputStateMap.get(id).subtype}, got: ${subtype}`);
}
let act = new action.Action(id, "pointer", subtype);
if (subtype === "pause") {
@@ -251,21 +279,18 @@ action.processPointerUpDownAction = func
assertPositiveInteger(actionItem.button, 'button');
act.button = actionItem.button;
};
action.processPointerMoveAction = function processPointerMoveAction(actionItem, act) {
assertPositiveInteger(actionItem.duration, 'duration');
act.duration = actionItem.duration;
let webElement = actionItem.element;
- let isElement = function(el){
- let properties = Object.getOwnPropertyNames(el);
- return properties.includes(element.Key) || properties.includes(element.LegacyKey);
- }
- if (typeof webElement != "undefined" && !isElement(webElement)) {
+
+ if (typeof webElement != "undefined" && !element.isWebElementReference(webElement)) {
throw new InvalidArgumentError(
"Expected 'actionItem.element' to be an Object that " +
`represents a web element, got: ${webElement}`);
}
act.element = webElement;
act.x = actionItem.x;
if (typeof act.x != "undefined") {
@@ -284,8 +309,21 @@ function assertPositiveInteger(value, na
}
}
function assertBoolean(value, name = undefined) {
if (typeof(value) != "boolean") {
throw new InvalidArgumentError(`Expected '${name}' to be a boolean, got: ${value}`);
}
}
+
+function assertIsValidSubtype(value, name = undefined) {
+ if (!ACTIONS[name].has(value)) {
+ throw new InvalidArgumentError(`Unknown subtype for ${name} action: ${value}`);
+ }
+}
+
+function capitalize(str) {
+ if (typeof str != 'string'){
+ throw new InvalidArgumentError(`Expected string, got: ${str}`);
+ }
+ return str.charAt(0).toUpperCase() + str.slice(1);
+}
--- a/testing/marionette/element.js
+++ b/testing/marionette/element.js
@@ -634,16 +634,21 @@ element.isCollection = function(seq) {
element.makeWebElement = function(uuid) {
return {
[element.Key]: uuid,
[element.LegacyKey]: uuid,
};
};
+element.isWebElementReference = function(el){
+ let properties = Object.getOwnPropertyNames(el);
+ return properties.includes(element.Key) || properties.includes(element.LegacyKey);
+};
+
element.generateUUID = function() {
let uuid = uuidGen.generateUUID().toString();
return uuid.substring(1, uuid.length - 1);
};
/**
* Convert any web elements in arbitrary objects to DOM elements by
* looking them up in the seen element store.
--- a/testing/marionette/test_action.js
+++ b/testing/marionette/test_action.js
@@ -6,38 +6,38 @@
const {utils: Cu} = Components;
Cu.import("chrome://marionette/content/error.js");
Cu.import("chrome://marionette/content/element.js");
Cu.import("chrome://marionette/content/action.js");
add_test(function test_defaultPointerParameters() {
- let defaultParameters = {pointerType: "mouse", primary: true};
+ let defaultParameters = {pointerType: action.PointerType.Mouse, primary: true};
deepEqual(action.processPointerParameters(), defaultParameters);
deepEqual(action.processPointerParameters({blah: 'nonsense'}), defaultParameters);
run_next_test();
});
add_test(function test_processPointerParameters() {
let check = function(regex, message, args) {
checkErrors(regex, action.processPointerParameters, args, message);
}
let parametersData = {pointerType: "foo"};
let message = `parametersData: ${parametersData}`;
- check(/Expected 'pointerType' to be one of/, message, [parametersData]);
+ check(/Unknown pointerType/, message, [parametersData]);
parametersData.pointerType = "pen";
parametersData.primary = 'a';
check(/Expected 'primary' to be a boolean/, message, [parametersData]);
parametersData.primary = false;
deepEqual(action.processPointerParameters(parametersData),
- {pointerType: "pen", primary: false});
+ {pointerType: action.PointerType.Pen, primary: false});
run_next_test();
});
add_test(function test_processPointerUpDownAction() {
let actionItem = {};
for (let d of [-1, 'a']) {
@@ -209,19 +209,19 @@ add_test(function test_processNullAction
add_test(function test_processActionSubtypeValidation() {
var actionItem = {};
var id = "some_id";
actionItem.type = "dancing";
let check = function(regex, actionFunc, args=[id, actionItem]) {
let message = `actionItem.type: ${actionItem.type}; actionFunc: ${actionFunc.name}`;
checkErrors(regex, actionFunc, args, message);
};
- check(/Expected 'subtype' to be 'pause'/, action.processNullAction);
- check(/Expected 'subtype' to be one of/, action.processKeyAction);
- check(/Expected 'subtype' to be one of/,
+ check(/Unknown subtype for none action/, action.processNullAction);
+ check(/Unknown subtype for key action/, action.processKeyAction);
+ check(/Unknown subtype for pointer action/,
action.processPointerAction,
[id, [], actionItem]);
run_next_test();
});
add_test(function test_processKeyActionPause() {
let actionItem = {};
@@ -272,17 +272,17 @@ add_test(function test_processInputSourc
type: "swim",
id: "some id",
};
let check = function(message, regex) {
checkErrors(regex, action.processInputSourceActionSequence,
[actionSequence], message);
};
check(`actionSequence.type: ${actionSequence.type}`,
- /Invalid 'actionSequence\.type'/);
+ /Unknown action type/);
actionSequence.type = "none";
actionSequence.id = -1;
check(`actionSequence.id: ${getTypeString(actionSequence.id)}`,
/Expected 'id' to be a string/);
actionSequence.id = "some_id";
actionSequence.actions = -1;
@@ -320,18 +320,18 @@ add_test(function test_processInputSourc
id: "9",
actions: [actionItem],
parameters: {
pointerType: "pen",
primary: false,
},
};
let expectedAction = new action.Action(actionSequence.id,
- actionSequence.type,
- actionItem.type);
+ actionSequence.type,
+ actionItem.type);
expectedAction.pointerType = actionSequence.parameters.pointerType;
expectedAction.primary = actionSequence.parameters.primary;
expectedAction.button = actionItem.button;
let actions = action.processInputSourceActionSequence(actionSequence);
equal(actions.length, 1);
deepEqual(actions[0], expectedAction);
run_next_test();
});
@@ -342,18 +342,18 @@ add_test(function test_processInputSourc
value: 'a',
};
let actionSequence = {
type: "key",
id: "9",
actions: [actionItem],
};
let expectedAction = new action.Action(actionSequence.id,
- actionSequence.type,
- actionItem.type);
+ actionSequence.type,
+ actionItem.type);
expectedAction.value = actionItem.value;
let actions = action.processInputSourceActionSequence(actionSequence);
equal(actions.length, 1);
deepEqual(actions[0], expectedAction);
run_next_test();
});
@@ -380,17 +380,17 @@ add_test(function test_processInputSourc
type: "pause",
duration: 5,
};
let actionSequence = {
type: "pointer",
id: "1",
actions: [actionItem],
};
- let wrongInputState = new action.NullInputState();
+ let wrongInputState = new action.InputState.Null();
action.inputStateMap.set(actionSequence.id, wrongInputState);
checkErrors(/to be mapped to/,
action.processInputSourceActionSequence,
[actionSequence],
`${actionSequence.type} using ${wrongInputState}`);
action.inputStateMap.clear();
run_next_test();
});
@@ -399,17 +399,17 @@ add_test(function test_processPointerAct
let actionItem = {
type: "pointerDown",
};
let id = 1;
let parameters = {
pointerType: "mouse",
primary: true,
};
- let wrongInputState = new action.PointerInputState("pause", true);
+ let wrongInputState = new action.InputState.Pointer("pause", true);
action.inputStateMap.set(id, wrongInputState)
checkErrors(/to be mapped to InputState whose subtype is/,
action.processPointerAction,
[id, parameters, actionItem],
`$subtype {actionItem.type} with ${wrongInputState.subtype} in inputState`);
action.inputStateMap.clear();
run_next_test();
});
@@ -491,18 +491,18 @@ add_test(function test_extractActionChai
};
let actionsByTick = action.extractActionChain([keyActionSequence, mouseActionSequence]);
// 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);
+ "key",
+ keyActionItems[2].type);
expectedAction.value = keyActionItems[2].value;
deepEqual(actionsByTick[2][0], expectedAction);
// one empty action sequence
actionsByTick = action.extractActionChain([keyActionSequence,
{type:"none", actions: []}]);
equal(keyActionItems.length, actionsByTick.length);
equal(1, actionsByTick[0].length);