summaryrefslogtreecommitdiffstats
path: root/testing/marionette/legacyaction.js
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /testing/marionette/legacyaction.js
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'testing/marionette/legacyaction.js')
-rw-r--r--testing/marionette/legacyaction.js477
1 files changed, 477 insertions, 0 deletions
diff --git a/testing/marionette/legacyaction.js b/testing/marionette/legacyaction.js
new file mode 100644
index 000000000..5d108210c
--- /dev/null
+++ b/testing/marionette/legacyaction.js
@@ -0,0 +1,477 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+
+Cu.import("chrome://marionette/content/element.js");
+Cu.import("chrome://marionette/content/event.js");
+
+const CONTEXT_MENU_DELAY_PREF = "ui.click_hold_context_menus.delay";
+const DEFAULT_CONTEXT_MENU_DELAY = 750; // ms
+
+this.EXPORTED_SYMBOLS = ["legacyaction"];
+
+const logger = Log.repository.getLogger("Marionette");
+
+this.legacyaction = this.action = {};
+
+/**
+ * Functionality for (single finger) action chains.
+ */
+action.Chain = function (checkForInterrupted) {
+ // for assigning unique ids to all touches
+ this.nextTouchId = 1000;
+ // keep track of active Touches
+ this.touchIds = {};
+ // last touch for each fingerId
+ this.lastCoordinates = null;
+ this.isTap = false;
+ this.scrolling = false;
+ // whether to send mouse event
+ this.mouseEventsOnly = false;
+ this.checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+
+ if (typeof checkForInterrupted == "function") {
+ this.checkForInterrupted = checkForInterrupted;
+ } else {
+ this.checkForInterrupted = () => {};
+ }
+
+ // determines if we create touch events
+ this.inputSource = null;
+};
+
+action.Chain.prototype.dispatchActions = function (
+ args,
+ touchId,
+ container,
+ seenEls,
+ touchProvider) {
+ // Some touch events code in the listener needs to do ipc, so we can't
+ // share this code across chrome/content.
+ if (touchProvider) {
+ this.touchProvider = touchProvider;
+ }
+
+ this.seenEls = seenEls;
+ this.container = container;
+ let commandArray = element.fromJson(
+ args, seenEls, container.frame, container.shadowRoot);
+
+ if (touchId == null) {
+ touchId = this.nextTouchId++;
+ }
+
+ if (!container.frame.document.createTouch) {
+ this.mouseEventsOnly = true;
+ }
+
+ let keyModifiers = {
+ shiftKey: false,
+ ctrlKey: false,
+ altKey: false,
+ metaKey: false,
+ };
+
+ return new Promise(resolve => {
+ this.actions(commandArray, touchId, 0, keyModifiers, resolve);
+ }).catch(this.resetValues);
+};
+
+/**
+ * This function emit mouse event.
+ *
+ * @param {Document} doc
+ * Current document.
+ * @param {string} type
+ * Type of event to dispatch.
+ * @param {number} clickCount
+ * Number of clicks, button notes the mouse button.
+ * @param {number} elClientX
+ * X coordinate of the mouse relative to the viewport.
+ * @param {number} elClientY
+ * Y coordinate of the mouse relative to the viewport.
+ * @param {Object} modifiers
+ * An object of modifier keys present.
+ */
+action.Chain.prototype.emitMouseEvent = function (
+ doc,
+ type,
+ elClientX,
+ elClientY,
+ button,
+ clickCount,
+ modifiers) {
+ if (!this.checkForInterrupted()) {
+ logger.debug(`Emitting ${type} mouse event ` +
+ `at coordinates (${elClientX}, ${elClientY}) ` +
+ `relative to the viewport, ` +
+ `button: ${button}, ` +
+ `clickCount: ${clickCount}`);
+
+ let win = doc.defaultView;
+ let domUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ let mods;
+ if (typeof modifiers != "undefined") {
+ mods = event.parseModifiers_(modifiers);
+ } else {
+ mods = 0;
+ }
+
+ domUtils.sendMouseEvent(
+ type,
+ elClientX,
+ elClientY,
+ button || 0,
+ clickCount || 1,
+ mods,
+ false,
+ 0,
+ this.inputSource);
+ }
+};
+
+/**
+ * Reset any persisted values after a command completes.
+ */
+action.Chain.prototype.resetValues = function() {
+ this.container = null;
+ this.seenEls = null;
+ this.touchProvider = null;
+ this.mouseEventsOnly = false;
+};
+
+/**
+ * Emit events for each action in the provided chain.
+ *
+ * To emit touch events for each finger, one might send a [["press", id],
+ * ["wait", 5], ["release"]] chain.
+ *
+ * @param {Array.<Array<?>>} chain
+ * A multi-dimensional array of actions.
+ * @param {Object.<string, number>} touchId
+ * Represents the finger ID.
+ * @param {number} i
+ * Keeps track of the current action of the chain.
+ * @param {Object.<string, boolean>} keyModifiers
+ * Keeps track of keyDown/keyUp pairs through an action chain.
+ * @param {function(?)} cb
+ * Called on success.
+ *
+ * @return {Object.<string, number>}
+ * Last finger ID, or an empty object.
+ */
+action.Chain.prototype.actions = function (chain, touchId, i, keyModifiers, cb) {
+ if (i == chain.length) {
+ cb(touchId || null);
+ this.resetValues();
+ return;
+ }
+
+ let pack = chain[i];
+ let command = pack[0];
+ let el;
+ let c;
+ i++;
+
+ if (["press", "wait", "keyDown", "keyUp", "click"].indexOf(command) == -1) {
+ // if mouseEventsOnly, then touchIds isn't used
+ if (!(touchId in this.touchIds) && !this.mouseEventsOnly) {
+ this.resetValues();
+ throw new WebDriverError("Element has not been pressed");
+ }
+ }
+
+ switch (command) {
+ case "keyDown":
+ event.sendKeyDown(pack[1], keyModifiers, this.container.frame);
+ this.actions(chain, touchId, i, keyModifiers, cb);
+ break;
+
+ case "keyUp":
+ event.sendKeyUp(pack[1], keyModifiers, this.container.frame);
+ this.actions(chain, touchId, i, keyModifiers, cb);
+ break;
+
+ case "click":
+ el = this.seenEls.get(pack[1], this.container);
+ let button = pack[2];
+ let clickCount = pack[3];
+ c = element.coordinates(el);
+ this.mouseTap(el.ownerDocument, c.x, c.y, button, clickCount, keyModifiers);
+ if (button == 2) {
+ this.emitMouseEvent(el.ownerDocument, "contextmenu", c.x, c.y,
+ button, clickCount, keyModifiers);
+ }
+ this.actions(chain, touchId, i, keyModifiers, cb);
+ break;
+
+ case "press":
+ if (this.lastCoordinates) {
+ this.generateEvents(
+ "cancel",
+ this.lastCoordinates[0],
+ this.lastCoordinates[1],
+ touchId,
+ null,
+ keyModifiers);
+ this.resetValues();
+ throw new WebDriverError(
+ "Invalid Command: press cannot follow an active touch event");
+ }
+
+ // look ahead to check if we're scrolling,
+ // needed for APZ touch dispatching
+ if ((i != chain.length) && (chain[i][0].indexOf('move') !== -1)) {
+ this.scrolling = true;
+ }
+ el = this.seenEls.get(pack[1], this.container);
+ c = element.coordinates(el, pack[2], pack[3]);
+ touchId = this.generateEvents("press", c.x, c.y, null, el, keyModifiers);
+ this.actions(chain, touchId, i, keyModifiers, cb);
+ break;
+
+ case "release":
+ this.generateEvents(
+ "release",
+ this.lastCoordinates[0],
+ this.lastCoordinates[1],
+ touchId,
+ null,
+ keyModifiers);
+ this.actions(chain, null, i, keyModifiers, cb);
+ this.scrolling = false;
+ break;
+
+ case "move":
+ el = this.seenEls.get(pack[1], this.container);
+ c = element.coordinates(el);
+ this.generateEvents("move", c.x, c.y, touchId, null, keyModifiers);
+ this.actions(chain, touchId, i, keyModifiers, cb);
+ break;
+
+ case "moveByOffset":
+ this.generateEvents(
+ "move",
+ this.lastCoordinates[0] + pack[1],
+ this.lastCoordinates[1] + pack[2],
+ touchId,
+ null,
+ keyModifiers);
+ this.actions(chain, touchId, i, keyModifiers, cb);
+ break;
+
+ case "wait":
+ if (pack[1] != null) {
+ let time = pack[1] * 1000;
+
+ // standard waiting time to fire contextmenu
+ let standard = Preferences.get(
+ CONTEXT_MENU_DELAY_PREF,
+ DEFAULT_CONTEXT_MENU_DELAY);
+
+ if (time >= standard && this.isTap) {
+ chain.splice(i, 0, ["longPress"], ["wait", (time - standard) / 1000]);
+ time = standard;
+ }
+ this.checkTimer.initWithCallback(
+ () => this.actions(chain, touchId, i, keyModifiers, cb),
+ time, Ci.nsITimer.TYPE_ONE_SHOT);
+ } else {
+ this.actions(chain, touchId, i, keyModifiers, cb);
+ }
+ break;
+
+ case "cancel":
+ this.generateEvents(
+ "cancel",
+ this.lastCoordinates[0],
+ this.lastCoordinates[1],
+ touchId,
+ null,
+ keyModifiers);
+ this.actions(chain, touchId, i, keyModifiers, cb);
+ this.scrolling = false;
+ break;
+
+ case "longPress":
+ this.generateEvents(
+ "contextmenu",
+ this.lastCoordinates[0],
+ this.lastCoordinates[1],
+ touchId,
+ null,
+ keyModifiers);
+ this.actions(chain, touchId, i, keyModifiers, cb);
+ break;
+ }
+};
+
+/**
+ * Given an element and a pair of coordinates, returns an array of the
+ * form [clientX, clientY, pageX, pageY, screenX, screenY].
+ */
+action.Chain.prototype.getCoordinateInfo = function (el, corx, cory) {
+ let win = el.ownerDocument.defaultView;
+ return [
+ corx, // clientX
+ cory, // clientY
+ corx + win.pageXOffset, // pageX
+ cory + win.pageYOffset, // pageY
+ corx + win.mozInnerScreenX, // screenX
+ cory + win.mozInnerScreenY // screenY
+ ];
+};
+
+/**
+ * @param {number} x
+ * X coordinate of the location to generate the event that is relative
+ * to the viewport.
+ * @param {number} y
+ * Y coordinate of the location to generate the event that is relative
+ * to the viewport.
+ */
+action.Chain.prototype.generateEvents = function (
+ type, x, y, touchId, target, keyModifiers) {
+ this.lastCoordinates = [x, y];
+ let doc = this.container.frame.document;
+
+ switch (type) {
+ case "tap":
+ if (this.mouseEventsOnly) {
+ this.mouseTap(
+ touch.target.ownerDocument,
+ touch.clientX,
+ touch.clientY,
+ null,
+ null,
+ keyModifiers);
+ } else {
+ touchId = this.nextTouchId++;
+ let touch = this.touchProvider.createATouch(target, x, y, touchId);
+ this.touchProvider.emitTouchEvent("touchstart", touch);
+ this.touchProvider.emitTouchEvent("touchend", touch);
+ this.mouseTap(
+ touch.target.ownerDocument,
+ touch.clientX,
+ touch.clientY,
+ null,
+ null,
+ keyModifiers);
+ }
+ this.lastCoordinates = null;
+ break;
+
+ case "press":
+ this.isTap = true;
+ if (this.mouseEventsOnly) {
+ this.emitMouseEvent(doc, "mousemove", x, y, null, null, keyModifiers);
+ this.emitMouseEvent(doc, "mousedown", x, y, null, null, keyModifiers);
+ } else {
+ touchId = this.nextTouchId++;
+ let touch = this.touchProvider.createATouch(target, x, y, touchId);
+ this.touchProvider.emitTouchEvent("touchstart", touch);
+ this.touchIds[touchId] = touch;
+ return touchId;
+ }
+ break;
+
+ case "release":
+ if (this.mouseEventsOnly) {
+ let [x, y] = this.lastCoordinates;
+ this.emitMouseEvent(doc, "mouseup", x, y, null, null, keyModifiers);
+ } else {
+ let touch = this.touchIds[touchId];
+ let [x, y] = this.lastCoordinates;
+
+ touch = this.touchProvider.createATouch(touch.target, x, y, touchId);
+ this.touchProvider.emitTouchEvent("touchend", touch);
+
+ if (this.isTap) {
+ this.mouseTap(
+ touch.target.ownerDocument,
+ touch.clientX,
+ touch.clientY,
+ null,
+ null,
+ keyModifiers);
+ }
+ delete this.touchIds[touchId];
+ }
+
+ this.isTap = false;
+ this.lastCoordinates = null;
+ break;
+
+ case "cancel":
+ this.isTap = false;
+ if (this.mouseEventsOnly) {
+ let [x, y] = this.lastCoordinates;
+ this.emitMouseEvent(doc, "mouseup", x, y, null, null, keyModifiers);
+ } else {
+ this.touchProvider.emitTouchEvent("touchcancel", this.touchIds[touchId]);
+ delete this.touchIds[touchId];
+ }
+ this.lastCoordinates = null;
+ break;
+
+ case "move":
+ this.isTap = false;
+ if (this.mouseEventsOnly) {
+ this.emitMouseEvent(doc, "mousemove", x, y, null, null, keyModifiers);
+ } else {
+ let touch = this.touchProvider.createATouch(
+ this.touchIds[touchId].target, x, y, touchId);
+ this.touchIds[touchId] = touch;
+ this.touchProvider.emitTouchEvent("touchmove", touch);
+ }
+ break;
+
+ case "contextmenu":
+ this.isTap = false;
+ let event = this.container.frame.document.createEvent("MouseEvents");
+ if (this.mouseEventsOnly) {
+ target = doc.elementFromPoint(this.lastCoordinates[0], this.lastCoordinates[1]);
+ } else {
+ target = this.touchIds[touchId].target;
+ }
+
+ let [clientX, clientY, pageX, pageY, screenX, screenY] =
+ this.getCoordinateInfo(target, x, y);
+
+ event.initMouseEvent(
+ "contextmenu",
+ true,
+ true,
+ target.ownerDocument.defaultView,
+ 1,
+ screenX,
+ screenY,
+ clientX,
+ clientY,
+ false,
+ false,
+ false,
+ false,
+ 0,
+ null);
+ target.dispatchEvent(event);
+ break;
+
+ default:
+ throw new WebDriverError("Unknown event type: " + type);
+ }
+ this.checkForInterrupted();
+};
+
+action.Chain.prototype.mouseTap = function (doc, x, y, button, count, mod) {
+ this.emitMouseEvent(doc, "mousemove", x, y, button, count, mod);
+ this.emitMouseEvent(doc, "mousedown", x, y, button, count, mod);
+ this.emitMouseEvent(doc, "mouseup", x, y, button, count, mod);
+};