Bug 1337133 - Move update of inputStateMap to action.Sequence.fromJson; r?ato draft
authorMaja Frydrychowicz <mjzffr@gmail.com>
Mon, 27 Feb 2017 08:27:35 -0500
changeset 490019 d4362bd74a6f675461866f67b2052cbf0997882a
parent 490018 b2f6eca952191c5d0bc21dae1e6b64e44e230e3f
child 547142 0dcac314e3c1e38d6e9a6394b233efa20a54c729
push id46970
push userbmo:mjzffr@gmail.com
push dateMon, 27 Feb 2017 14:54:25 +0000
reviewersato
bugs1337133
milestone54.0a1
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
testing/marionette/action.js
testing/marionette/test_action.js
--- 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},