Bug 1245153 - Employ new modules throughout Marionette; r=automatedtester draft
authorAndreas Tolfsen <ato@mozilla.com>
Wed, 03 Feb 2016 18:56:02 +0000
changeset 332775 c251c58a7a086c606d3d3941adfeee26ecc49504
parent 332774 da99b4329baba3fa8a604b9b2952692568b96a75
child 332776 c600e7b08d512a77bf6684eb28002dd85488346b
push id11232
push useratolfsen@mozilla.com
push dateSun, 21 Feb 2016 12:02:10 +0000
reviewersautomatedtester
bugs1245153
milestone47.0a1
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";