Bug 1245153 - Employ new modules throughout Marionette; r=automatedtester
authorAndreas Tolfsen <ato@mozilla.com>
Wed, 03 Feb 2016 18:56:02 +0000
changeset 321329 adc7a3abfb6dbd0723e4135c34acc37e82163a7a
parent 321328 1e4fe33c534af1892bdaea3424e1222ac987f090
child 321330 24c62f919a71417efad348b67ce39cbc59fed556
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersautomatedtester
bugs1245153
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1245153 - Employ new modules throughout Marionette; r=automatedtester This change removes almost all the remaining uses of loadSubScript and global scope pollution. The only remaining use is for common.js, which is resolved by a later bug for evaluating scripts. MozReview-Commit-ID: 96h0yLElauq
testing/marionette/actions.js
testing/marionette/driver.js
testing/marionette/listener.js
testing/marionette/server.js
--- a/testing/marionette/actions.js
+++ b/testing/marionette/actions.js
@@ -2,29 +2,31 @@
  * 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/event.js");
+
 const CONTEXT_MENU_DELAY_PREF = "ui.click_hold_context_menus.delay";
 const DEFAULT_CONTEXT_MENU_DELAY = 750;  // ms
 
 this.EXPORTED_SYMBOLS = ["actions"];
 
 const logger = Log.repository.getLogger("Marionette");
 
 this.actions = {};
 
 /**
  * Functionality for (single finger) action chains.
  */
-actions.Chain = function(utils, checkForInterrupted) {
+actions.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;
@@ -38,19 +40,16 @@ actions.Chain = function(utils, checkFor
   if (typeof checkForInterrupted == "function") {
     this.checkForInterrupted = checkForInterrupted;
   } else {
     this.checkForInterrupted = () => {};
   }
 
   // determines if we create touch events
   this.inputSource = null;
-
-  // test utilities providing some event synthesis code
-  this.utils = utils;
 };
 
 actions.Chain.prototype.dispatchActions = function(
     args,
     touchId,
     container,
     elementManager,
     callbacks,
@@ -122,17 +121,17 @@ actions.Chain.prototype.emitMouseEvent =
       `clickCount: ${clickCount}`);
 
     let win = doc.defaultView;
     let domUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
         .getInterface(Ci.nsIDOMWindowUtils);
 
     let mods;
     if (typeof modifiers != "undefined") {
-      mods = this.utils._parseModifiers(modifiers);
+      mods = event.parseModifiers_(modifiers);
     } else {
       mods = 0;
     }
 
     domUtils.sendMouseEvent(
         type,
         elClientX,
         elClientY,
@@ -182,22 +181,22 @@ actions.Chain.prototype.actions = functi
     if (!(touchId in this.touchIds) && !this.mouseEventsOnly) {
       this.resetValues();
       throw new WebDriverError("Element has not been pressed");
     }
   }
 
   switch(command) {
     case "keyDown":
-      this.utils.sendKeyDown(pack[1], keyModifiers, this.container.frame);
+      event.sendKeyDown(pack[1], keyModifiers, this.container.frame);
       this.actions(chain, touchId, i, keyModifiers);
       break;
 
     case "keyUp":
-      this.utils.sendKeyUp(pack[1], keyModifiers, this.container.frame);
+      event.sendKeyUp(pack[1], keyModifiers, this.container.frame);
       this.actions(chain, touchId, i, keyModifiers);
       break;
 
     case "click":
       el = this.elementManager.getKnownElement(pack[1], this.container);
       let button = pack[2];
       let clickCount = pack[3];
       c = this.coordinates(el, null, null);
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -19,33 +19,28 @@ Cu.import("resource://gre/modules/XPCOMU
 
 var {devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 this.DevToolsUtils = devtools.require("devtools/shared/DevToolsUtils");
 
 XPCOMUtils.defineLazyServiceGetter(
     this, "cookieManager", "@mozilla.org/cookiemanager;1", "nsICookieManager2");
 
 Cu.import("chrome://marionette/content/actions.js");
+Cu.import("chrome://marionette/content/atoms.js");
 Cu.import("chrome://marionette/content/interactions.js");
 Cu.import("chrome://marionette/content/elements.js");
+Cu.import("chrome://marionette/content/event.js");
+Cu.import("chrome://marionette/content/frame-manager.js");
 Cu.import("chrome://marionette/content/error.js");
 Cu.import("chrome://marionette/content/modal.js");
 Cu.import("chrome://marionette/content/proxy.js");
 Cu.import("chrome://marionette/content/simpletest.js");
 
 loader.loadSubScript("chrome://marionette/content/common.js");
 
-// preserve this import order:
-var utils = {};
-loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils);
-loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", utils);
-loader.loadSubScript("chrome://marionette/content/atoms.js", utils);
-loader.loadSubScript("chrome://marionette/content/sendkeys.js", utils);
-loader.loadSubScript("chrome://marionette/content/frame-manager.js");
-
 this.EXPORTED_SYMBOLS = ["GeckoDriver", "Context"];
 
 var FRAME_SCRIPT = "chrome://marionette/content/listener.js";
 const BROWSER_STARTUP_FINISHED = "browser-delayed-startup-finished";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const CLICK_TO_START_PREF = "marionette.debugging.clicktostart";
 const CONTENT_LISTENER_PREF = "marionette.contentListener";
 
@@ -133,17 +128,17 @@ this.GeckoDriver = function(appName, dev
   this.currentFrameElement = null;
   this.testName = null;
   this.mozBrowserClose = null;
   this.sandboxes = {};
   // frame ID of the current remote frame, used for mozbrowserclose events
   this.oopFrameId = null;
   this.observing = null;
   this._browserIds = new WeakMap();
-  this.actions = new actions.Chain(utils);
+  this.actions = new actions.Chain();
 
   this.sessionCapabilities = {
     // mandated capabilities
     "browserName": Services.appinfo.name,
     "browserVersion": Services.appinfo.version,
     "platformName": Services.sysinfo.getProperty("name"),
     "platformVersion": Services.sysinfo.getProperty("version"),
     "specificationLevel": "1",
@@ -161,17 +156,17 @@ this.GeckoDriver = function(appName, dev
 
     // proprietary extensions
     "XULappId" : Services.appinfo.ID,
     "appBuildId" : Services.appinfo.appBuildID,
     "device": device,
     "version": Services.appinfo.version,
   };
 
-  this.interactions = new Interactions(utils, () => this.sessionCapabilities);
+  this.interactions = new Interactions(() => this.sessionCapabilities);
 
   this.mm = globalMessageManager;
   this.listener = proxy.toListener(() => this.mm, this.sendAsync.bind(this));
 
   // always keep weak reference to current dialogue
   this.dialog = null;
   let handleDialog = (subject, topic) => {
     let winr;
@@ -349,18 +344,16 @@ GeckoDriver.prototype.startBrowser = fun
  * Loads the Marionette frame script into the browser if needed.
  *
  * @param {nsIDOMWindow} win
  *     Window whose browser we need to access.
  * @param {boolean} isNewSession
  *     True if this is the first time we're talking to this browser.
  */
 GeckoDriver.prototype.whenBrowserStarted = function(win, isNewSession) {
-  utils.window = win;
-
   try {
     let mm = win.window.messageManager;
     if (!isNewSession) {
       // Loading the frame script corresponds to a situation we need to
       // return to the server. If the messageManager is a message broadcaster
       // with no children, we don't have a hope of coming back from this call,
       // so send the ack here. Otherwise, make a note of how many child scripts
       // will be loaded so we known when it's safe to return.
@@ -385,17 +378,17 @@ GeckoDriver.prototype.whenBrowserStarted
  *
  * @param {nsIDOMElement} el
  *     The parent element.
  * @param {Array.<string>} lines
  *      Array that holds the text lines.
  */
 GeckoDriver.prototype.getVisibleText = function(el, lines) {
   try {
-    if (utils.isElementDisplayed(el)) {
+    if (atom.isElementDisplayed(el, this.getCurrentWindow())) {
       if (el.value) {
         lines.push(el.value);
       }
       for (let child in el.childNodes) {
         this.getVisibleText(el.childNodes[child], lines);
       }
     }
   } catch (e) {
@@ -781,17 +774,16 @@ GeckoDriver.prototype.createExecuteSandb
   let principal = win;
   if (sandboxName == 'system') {
     principal = Cc["@mozilla.org/systemprincipal;1"].
                 createInstance(Ci.nsIPrincipal);
   }
   let sb = new Cu.Sandbox(principal,
       {sandboxPrototype: win, wantXrays: false, sandboxName: ""});
   sb.global = sb;
-  sb.testUtils = utils;
   sb.proto = win;
 
   mn.exports.forEach(function(fn) {
     if (typeof mn[fn] === 'function') {
       sb[fn] = mn[fn].bind(mn);
     } else {
       sb[fn] = mn[fn];
     }
@@ -1585,17 +1577,16 @@ GeckoDriver.prototype.switchToWindow = f
 
       this.startBrowser(found.win, false /* isNewSession */);
 
       if (registerBrowsers && browserListening) {
         yield registerBrowsers;
         yield browserListening;
       }
     } else {
-      utils.window = found.win;
       this.curBrowser = this.browsers[found.outerId];
 
       if ("tabIndex" in found) {
         this.curBrowser.switchToTab(found.tabIndex, found.win);
       }
     }
   } else {
     throw new NoSuchWindowError(`Unable to locate window: ${switchTo}`);
@@ -2012,17 +2003,17 @@ GeckoDriver.prototype.clickElement = fun
  */
 GeckoDriver.prototype.getElementAttribute = function*(cmd, resp) {
   let {id, name} = cmd.parameters;
 
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
       let el = this.curBrowser.elementManager.getKnownElement(id, {frame: win});
-      resp.body.value = utils.getElementAttribute(el, name);
+      resp.body.value = atom.getElementAttribute(el, name, this.getCurrentWindow());
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.getElementAttribute(id, name);
       break;
   }
 };
 
@@ -2787,21 +2778,21 @@ GeckoDriver.prototype.sendKeysToDialog =
 
   // see toolkit/components/prompts/content/commonDialog.js
   let {loginContainer, loginTextbox} = this.dialog.ui;
   if (loginContainer.hidden) {
     throw new ElementNotVisibleError("This prompt does not accept text input");
   }
 
   let win = this.dialog.window ? this.dialog.window : this.getCurrentWindow();
-  utils.sendKeysToElement(
-      win,
+  event.sendKeysToElement(
+      cmd.parameters.value,
       loginTextbox,
-      cmd.parameters.value,
-      true /* ignore visibility check */);
+      {ignoreVisibility: true},
+      win);
 };
 
 /**
  * Quits Firefox with the provided flags and tears down the current
  * session.
  */
 GeckoDriver.prototype.quitApplication = function(cmd, resp) {
   if (this.appName != "Firefox") {
@@ -3063,17 +3054,17 @@ var BrowserObj = function(win, driver) {
   this.elementManager = new ElementManager([NAME, LINK_TEXT, PARTIAL_LINK_TEXT]);
   this.setBrowser(win);
 
   // A reference to the tab corresponding to the current window handle, if any.
   this.tab = null;
   this.pendingCommands = [];
 
   // we should have one FM per BO so that we can handle modals in each Browser
-  this.frameManager = new FrameManager(driver);
+  this.frameManager = new frame.Manager(driver);
   this.frameRegsPending = 0;
 
   // register all message listeners
   this.frameManager.addMessageManagerListeners(driver.mm);
   this.getIdForBrowser = driver.getIdForBrowser.bind(driver);
   this.updateIdForBrowser = driver.updateIdForBrowser.bind(driver);
   this._curFrameId = null;
   this._browserWasRemote = null;
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -1,61 +1,56 @@
 /* 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/. */
 
 var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 var uuidGen = Cc["@mozilla.org/uuid-generator;1"]
-                .getService(Ci.nsIUUIDGenerator);
+    .getService(Ci.nsIUUIDGenerator);
 
 var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
-               .getService(Ci.mozIJSSubScriptLoader);
+    .getService(Ci.mozIJSSubScriptLoader);
 
 loader.loadSubScript("chrome://marionette/content/simpletest.js");
 loader.loadSubScript("chrome://marionette/content/common.js");
 
 Cu.import("chrome://marionette/content/actions.js");
+Cu.import("chrome://marionette/content/atoms.js");
 Cu.import("chrome://marionette/content/capture.js");
 Cu.import("chrome://marionette/content/cookies.js");
 Cu.import("chrome://marionette/content/elements.js");
 Cu.import("chrome://marionette/content/error.js");
+Cu.import("chrome://marionette/content/event.js");
 Cu.import("chrome://marionette/content/proxy.js");
 Cu.import("chrome://marionette/content/interactions.js");
 
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-var utils = {};
-utils.window = content;
-// Load Event/ChromeUtils for use with JS scripts:
-loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils);
-loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", utils);
-loader.loadSubScript("chrome://marionette/content/atoms.js", utils);
-loader.loadSubScript("chrome://marionette/content/sendkeys.js", utils);
 
 var marionetteLogObj = new MarionetteLogObj();
 
 var isB2G = false;
 
 var marionetteTestName;
 var winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor)
     .getInterface(Ci.nsIDOMWindowUtils);
 var listenerId = null; // unique ID of this listener
 var curContainer = { frame: content, shadowRoot: null };
 var isRemoteBrowser = () => curContainer.frame.contentWindow !== null;
 var previousContainer = null;
 var elementManager = new ElementManager([]);
 
 // Holds session capabilities.
 var capabilities = {};
-var interactions = new Interactions(utils, () => capabilities);
+var interactions = new Interactions(() => capabilities);
 
-var actions = new actions.Chain(utils, checkForInterrupted);
+var actions = new actions.Chain(checkForInterrupted);
 var importedScripts = null;
 
 // Contains the last file input element that was the target of
 // sendKeysToElement.
 var fileInputElement;
 
 // A dict of sandboxes used this session
 var sandboxes = {};
@@ -524,17 +519,16 @@ function createExecuteContentSandbox(win
   if (sandboxName == "system") {
     principal = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
   }
   let sandbox = new Cu.Sandbox(principal, {sandboxPrototype: win});
   sandbox.global = sandbox;
   sandbox.window = win;
   sandbox.document = sandbox.window.document;
   sandbox.navigator = sandbox.window.navigator;
-  sandbox.testUtils = utils;
   sandbox.asyncTestCommandId = asyncTestCommandId;
   sandbox.marionette = mn;
 
   mn.exports.forEach(fn => {
     if (typeof mn[fn] == "function") {
       sandbox[fn] = mn[fn].bind(mn);
     } else {
       sandbox[fn] = mn[fn];
@@ -888,77 +882,23 @@ function coordinates(target, x, y) {
     y = box.height / 2;
   }
   let coords = {};
   coords.x = box.left + x;
   coords.y = box.top + y;
   return coords;
 }
 
-
-/**
- * This function returns true if the given coordinates are in the viewport.
- * @param 'x', and 'y' are the coordinates relative to the target.
- *        If they are not specified, then the center of the target is used.
- */
-function elementInViewport(el, x, y) {
-  let c = coordinates(el, x, y);
-  let curFrame = curContainer.frame;
-  let viewPort = {top: curFrame.pageYOffset,
-                  left: curFrame.pageXOffset,
-                  bottom: (curFrame.pageYOffset + curFrame.innerHeight),
-                  right:(curFrame.pageXOffset + curFrame.innerWidth)};
-  return (viewPort.left <= c.x + curFrame.pageXOffset &&
-          c.x + curFrame.pageXOffset <= viewPort.right &&
-          viewPort.top <= c.y + curFrame.pageYOffset &&
-          c.y + curFrame.pageYOffset <= viewPort.bottom);
-}
-
-/**
- * This function throws the visibility of the element error if the element is
- * not displayed or the given coordinates are not within the viewport.
- * @param 'x', and 'y' are the coordinates relative to the target.
- *        If they are not specified, then the center of the target is used.
- */
-function checkVisible(el, x, y) {
-  // Bug 1094246 - Webdriver's isShown doesn't work with content xul
-  if (utils.getElementAttribute(el, "namespaceURI").indexOf("there.is.only.xul") == -1) {
-    //check if the element is visible
-    let visible = utils.isElementDisplayed(el);
-    if (!visible) {
-      return false;
-    }
-  }
-
-  if (el.tagName.toLowerCase() === 'body') {
-    return true;
-  }
-  if (!elementInViewport(el, x, y)) {
-    //check if scroll function exist. If so, call it.
-    if (el.scrollIntoView) {
-      el.scrollIntoView(false);
-      if (!elementInViewport(el)) {
-        return false;
-      }
-    }
-    else {
-      return false;
-    }
-  }
-  return true;
-}
-
-
 /**
  * Function that perform a single tap
  */
 function singleTap(id, corx, cory) {
   let el = elementManager.getKnownElement(id, curContainer);
   // after this block, the element will be scrolled into view
-  let visible = checkVisible(el, corx, cory);
+  let visible = elements.checkVisible(el, curContainer.frame, corx, cory);
   if (!visible) {
     throw new ElementNotVisibleError("Element is not currently visible and may not be manipulated");
   }
   return interactions.accessibility.getAccessibleObject(el, true).then(acc => {
     interactions.accessibility.checkVisible(acc, el, visible);
     interactions.accessibility.checkActionable(acc, el);
     if (!curContainer.frame.document.createTouch) {
       actions.mouseEventsOnly = true;
@@ -1388,31 +1328,31 @@ function clickElement(id) {
  * @param {string} name
  *     Name of the attribute.
  *
  * @return {string}
  *     The value of the attribute.
  */
 function getElementAttribute(id, name) {
   let el = elementManager.getKnownElement(id, curContainer);
-  return utils.getElementAttribute(el, name);
+  return atom.getElementAttribute(el, name, curContainer.frame);
 }
 
 /**
  * Get the text of this element. This includes text from child elements.
  *
  * @param {WebElement} id
  *     Reference to web element.
  *
  * @return {string}
  *     Text of element.
  */
 function getElementText(id) {
   let el = elementManager.getKnownElement(id, curContainer);
-  return utils.getElementText(el);
+  return atom.getElementText(el, curContainer.frame);
 }
 
 /**
  * Get the tag name of an element.
  *
  * @param {WebElement} id
  *     Reference to web element.
  *
@@ -1506,34 +1446,34 @@ function sendKeysToElement(msg) {
 
   if (el.type == "file") {
     let p = val.join("");
         fileInputElement = el;
         // In e10s, we can only construct File objects in the parent process,
         // so pass the filename to driver.js, which in turn passes them back
         // to this frame script in receiveFiles.
         sendSyncMessage("Marionette:getFiles",
-                        {value: p, command_id: command_id});
+            {value: p, command_id: command_id});
   } else {
     interactions.sendKeysToElement(curContainer, elementManager, id, val)
-      .then(() => sendOk(command_id))
-      .catch(e => sendError(e, command_id));
+        .then(() => sendOk(command_id))
+        .catch(e => sendError(e, command_id));
   }
 }
 
 /**
  * Clear the text of an element.
  */
 function clearElement(id) {
   try {
     let el = elementManager.getKnownElement(id, curContainer);
     if (el.type == "file") {
       el.value = null;
     } else {
-      utils.clearElement(el);
+      atom.clearElement(el, curContainer.frame);
     }
   } catch (e) {
     // Bug 964738: Newer atoms contain status codes which makes wrapping
     // this in an error prototype that has a status property unnecessary
     if (e.name == "InvalidElementStateError") {
       throw new InvalidElementStateError(e.message);
     } else {
       throw e;
--- a/testing/marionette/server.js
+++ b/testing/marionette/server.js
@@ -18,19 +18,17 @@ Cu.import("chrome://marionette/content/d
 Cu.import("chrome://marionette/content/elements.js");
 Cu.import("chrome://marionette/content/simpletest.js");
 
 // Bug 1083711: Load transport.js as an SDK module instead of subscript
 loader.loadSubScript("resource://devtools/shared/transport/transport.js");
 
 // Preserve this import order:
 var events = {};
-loader.loadSubScript("chrome://marionette/content/EventUtils.js", events);
 loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", events);
-loader.loadSubScript("chrome://marionette/content/frame-manager.js");
 
 const logger = Log.repository.getLogger("Marionette");
 
 this.EXPORTED_SYMBOLS = ["MarionetteServer"];
 
 const CONTENT_LISTENER_PREF = "marionette.contentListener";
 const MANAGE_OFFLINE_STATUS_PREF = "network.gonk.manage-offline-status";