Bug 1337133 - Dispatch pointerDown and pointerUp actions for mouse; r?ato draft
authorMaja Frydrychowicz <mjzffr@gmail.com>
Wed, 22 Feb 2017 17:03:59 -0500
changeset 490016 a47b2b5ecdb4000f9198f29a053066f04aad6ffe
parent 490015 aa12c04f031718cf5f6891d26003a3fa0a2f90d1
child 490017 a89a00d2836782c4ef6bd7215054d6b3b38c021b
push id46970
push userbmo:mjzffr@gmail.com
push dateMon, 27 Feb 2017 14:54:25 +0000
reviewersato
bugs1337133
milestone54.0a1
Bug 1337133 - Dispatch pointerDown and pointerUp actions for mouse; r?ato MozReview-Commit-ID: FC0lF0S2Mzz
testing/marionette/action.js
testing/web-platform/tests/webdriver/actions/mouse.py
--- a/testing/marionette/action.js
+++ b/testing/marionette/action.js
@@ -361,18 +361,19 @@ action.PointerOrigin.get = function(obj)
       `web element reference, got: ${obj}`);
   }
   return origin;
 };
 
 /** Represents possible subtypes for a pointer input source. */
 action.PointerType = {
   Mouse: "mouse",
-  Pen: "pen",
-  Touch: "touch",
+  // TODO For now, only mouse is supported
+  //Pen: "pen",
+  //Touch: "touch",
 };
 
 /**
  * Look up a PointerType.
  *
  * @param {string} str
  *     Name of pointer type.
  *
@@ -528,17 +529,17 @@ action.InputState.Key = class Key extend
 
   /**
    * Remove |key| from the set of pressed keys.
    *
    * @param {string} key
    *     Normalized key value.
    *
    * @return {boolean}
-   *     True if |key| is removed successfully, false otherwise.
+   *     True if |key| was present before removal, false otherwise.
    */
   release(key) {
     return this.pressed.delete(key);
   }
 };
 
 /**
  * Input state not associated with a specific physical device.
@@ -563,16 +564,58 @@ action.InputState.Pointer = class Pointe
   constructor(subtype) {
     super();
     this.pressed = new Set();
     assert.defined(subtype, error.pprint`Expected subtype to be defined, got: ${subtype}`);
     this.subtype = action.PointerType.get(subtype);
     this.x = 0;
     this.y = 0;
   }
+
+  /**
+   * Check whether |button| is pressed.
+   *
+   * @param {number} button
+   *     Positive integer that refers to a mouse button.
+   *
+   * @return {boolean}
+   *     True if |button| is in set of pressed buttons.
+   */
+  isPressed(button) {
+    assert.positiveInteger(button);
+    return this.pressed.has(button);
+  }
+
+  /**
+   * Add |button| to the set of pressed keys.
+   *
+   * @param {number} button
+   *     Positive integer that refers to a mouse button.
+   *
+   * @return {Set}
+   *     Set of pressed buttons.
+   */
+  press(button) {
+    assert.positiveInteger(button);
+    return this.pressed.add(button);
+  }
+
+   /**
+   * Remove |button| from the set of pressed buttons.
+   *
+   * @param {number} button
+   *     A positive integer that refers to a mouse button.
+   *
+   * @return {boolean}
+   *     True if |button| was present before removals, false otherwise.
+   */
+  release(button) {
+    assert.positiveInteger(button);
+    return this.pressed.delete(button);
+  }
 };
 
 /**
  * Repesents an action for dispatch. Used in |action.Chain| and |action.Sequence|.
  *
  * @param {string} id
  *     Input source ID.
  * @param {string} type
@@ -853,16 +896,31 @@ action.Key = class {
   update(inputState) {
     this.altKey = inputState.alt;
     this.shiftKey = inputState.shift;
     this.ctrlKey = inputState.ctrl;
     this.metaKey = inputState.meta;
   }
 };
 
+/** Collect properties associated with MouseEvent */
+action.Mouse = class {
+  constructor(type, button = 0) {
+    this.type = type;
+    assert.positiveInteger(button);
+    this.button = button;
+    this.buttons = 0;
+  }
+
+  update(inputState) {
+    let allButtons = Array.from(inputState.pressed);
+    this.buttons = allButtons.reduce((a, i) => a + Math.pow(2, i), 0);
+  }
+};
+
 /**
  * Dispatch a chain of actions over |chain.length| ticks.
  *
  * This is done by creating a Promise for each tick that resolves once all the
  * Promises for individual tick-actions are resolved. The next tick's actions are
  * not dispatched until the Promise for the current tick is resolved.
  *
  * @param {action.Chain} chain
@@ -957,17 +1015,21 @@ function toEvents(tickDuration, seenEls,
     switch (a.subtype) {
       case action.KeyUp:
         return dispatchKeyUp(a, inputState, container.frame);
 
       case action.KeyDown:
         return dispatchKeyDown(a, inputState, container.frame);
 
       case action.PointerDown:
+        return dispatchPointerDown(a, inputState, container.frame);
+
       case action.PointerUp:
+        return dispatchPointerUp(a, inputState, container.frame);
+
       case action.PointerMove:
       case action.PointerCancel:
         throw new UnsupportedOperationError();
 
       case action.Pause:
         return dispatchPause(a, tickDuration);
     }
   };
@@ -1030,16 +1092,94 @@ function dispatchKeyUp(a, inputState, wi
     keyEvent.update(inputState);
     event.sendKeyUp(keyEvent.key, keyEvent, win);
 
     resolve();
   });
 }
 
 /**
+ * Dispatch a pointerDown action equivalent to pressing a pointer-device
+ * button.
+ *
+ * @param {action.Action} a
+ *     Action to dispatch.
+ * @param {action.InputState} inputState
+ *     Input state for this action's input source.
+ * @param {nsIDOMWindow} win
+ *     Current window.
+ *
+ * @return {Promise}
+ *     Promise to dispatch at least a pointerdown event.
+ */
+function dispatchPointerDown(a, inputState, win) {
+  return new Promise(resolve => {
+    if (inputState.isPressed(a.button)) {
+      resolve();
+      return;
+    }
+    inputState.press(a.button);
+    // Append a copy of |a| with pointerUp subtype
+    action.inputsToCancel.push(Object.assign({}, a, {subtype: action.PointerUp}));
+    switch (inputState.subtype) {
+      case action.PointerType.Mouse:
+        let mouseEvent = new action.Mouse("mousedown", a.button);
+        mouseEvent.update(inputState);
+        event.synthesizeMouseAtPoint(inputState.x, inputState.y, mouseEvent, win);
+        break;
+      case action.PointerType.Pen:
+      case action.PointerType.Touch:
+        throw new UnsupportedOperationError("Only 'mouse' pointer type is supported.");
+        break;
+      default:
+        throw new TypeError(`Unknown pointer type: ${inputState.subtype}`);
+    }
+    resolve();
+  });
+}
+
+/**
+ * Dispatch a pointerUp action equivalent to releasing a pointer-device
+ * button.
+ *
+ * @param {action.Action} a
+ *     Action to dispatch.
+ * @param {action.InputState} inputState
+ *     Input state for this action's input source.
+ * @param {nsIDOMWindow} win
+ *     Current window.
+ *
+ * @return {Promise}
+ *     Promise to dispatch at least a pointerup event.
+ */
+function dispatchPointerUp(a, inputState, win) {
+  return new Promise(resolve => {
+    if (!inputState.isPressed(a.button)) {
+      resolve();
+      return;
+    }
+    inputState.release(a.button);
+    switch (inputState.subtype) {
+      case action.PointerType.Mouse:
+        let mouseEvent = new action.Mouse("mouseup", a.button);
+        mouseEvent.update(inputState);
+        event.synthesizeMouseAtPoint(inputState.x, inputState.y,
+            mouseEvent, win);
+        break;
+      case action.PointerType.Pen:
+      case action.PointerType.Touch:
+        throw new UnsupportedOperationError("Only 'mouse' pointer type is supported.");
+      default:
+        throw new TypeError(`Unknown pointer type: ${inputState.subtype}`);
+    }
+    resolve();
+  });
+}
+
+/**
  * Dispatch a pause action equivalent waiting for |a.duration| milliseconds, or a
  * default time interval of |tickDuration|.
  *
  * @param {action.Action} a
  *     Action to dispatch.
  * @param {number} tickDuration
  *     Duration in milliseconds of this tick.
  *
--- a/testing/web-platform/tests/webdriver/actions/mouse.py
+++ b/testing/web-platform/tests/webdriver/actions/mouse.py
@@ -1,8 +1,8 @@
 from support.refine import get_events
 
 
-#def test_nothing(session, test_actions_page, mouse_chain):
-#    mouse_chain \
-#        .pointer_down(1) \
-#        .perform()
-#    assert True
+def test_nothing(session, test_actions_page, mouse_chain):
+    mouse_chain \
+        .pointer_down(0) \
+        .perform()
+    assert True