Bug 1245153 - Convert interactions.js and elements.js to modules; r=automatedtester draft
authorAndreas Tolfsen <ato@mozilla.com>
Wed, 03 Feb 2016 18:54:23 +0000
changeset 332774 da99b4329baba3fa8a604b9b2952692568b96a75
parent 332773 bad2ebd1e5869cfea01e02041d20f928d4c159fa
child 332775 c251c58a7a086c606d3d3941adfeee26ecc49504
push id11232
push useratolfsen@mozilla.com
push dateSun, 21 Feb 2016 12:02:10 +0000
reviewersautomatedtester
bugs1245153
milestone47.0a1
Bug 1245153 - Convert interactions.js and elements.js to modules; r=automatedtester To simplify the dependency chain and reduce the number of duplicate functions in Marionette, a number of functions have been removed from interactions.js and added to elements.js. This makes them more easily re-usable and works around a circular dependency issue. MozReview-Commit-ID: TZc3VZzHqM
testing/marionette/elements.js
testing/marionette/interactions.js
--- a/testing/marionette/elements.js
+++ b/testing/marionette/elements.js
@@ -1,14 +1,17 @@
 /* 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/. */
 
-let {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+"use strict";
 
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("chrome://marionette/content/atoms.js");
 Cu.import("chrome://marionette/content/error.js");
 
 /**
  * The ElementManager manages DOM element references and
  * interactions with elements.
  *
  * A web element is an abstraction used to identify an element when it
  * is transported across the protocol, between remote- and local ends.
@@ -294,17 +297,17 @@ ElementManager.prototype = {
    *        list of arguments being passed in
    *
    * @return object
    *        If '__marionetteArgs' is in args, then
    *        it will return an object with these arguments
    *        as its members.
    */
   applyNamedArgs: function EM_applyNamedArgs(args) {
-    namedArgs = {};
+    let namedArgs = {};
     args.forEach(function(arg) {
       if (arg && typeof(arg['__marionetteArgs']) === 'object') {
         for (let prop in arg['__marionetteArgs']) {
           namedArgs[prop] = arg['__marionetteArgs'][prop];
         }
       }
     });
     return namedArgs;
@@ -587,15 +590,118 @@ ElementManager.prototype = {
           elements = [el];
         }
         break;
       default:
         throw new InvalidSelectorError(`No such strategy: ${using}`);
     }
     return elements;
   },
-}
+};
 
 this.elements = {};
+
 elements.generateUUID = function() {
   let uuid = uuidGen.generateUUID().toString();
   return uuid.substring(1, uuid.length - 1);
 };
+
+/**
+ * This function generates a pair of coordinates relative to the viewport
+ * given a target element and coordinates relative to that element's
+ * top-left corner.
+ *
+ * @param {Node} node
+ *     Target node.
+ * @param {number=} x
+ *     Horizontal offset relative to target.  Defaults to the centre of
+ *     the target's bounding box.
+ * @param {number=} y
+ *     Vertical offset relative to target.  Defaults to the centre of
+ *     the target's bounding box.
+ */
+// TODO(ato): Replicated from listener.js for the time being
+elements.coordinates = function(node, x = undefined, y = undefined) {
+  let box = node.getBoundingClientRect();
+  if (!x) {
+    x = box.width / 2.0;
+  }
+  if (!y) {
+    y = box.height / 2.0;
+  }
+  return {
+    x: box.left + x,
+    y: box.top + y,
+  };
+}
+
+/**
+ * This function returns true if the node is in the viewport.
+ *
+ * @param {Element} element
+ *     Target element.
+ * @param {number=} x
+ *     Horizontal offset relative to target.  Defaults to the centre of
+ *     the target's bounding box.
+ * @param {number=} y
+ *     Vertical offset relative to target.  Defaults to the centre of
+ *     the target's bounding box.
+ */
+elements.inViewport = function(el, x = undefined, y = undefined) {
+  let win = el.ownerDocument.defaultView;
+  let c = elements.coordinates(el, x, y);
+  let vp = {
+    top: win.pageYOffset,
+    left: win.pageXOffset,
+    bottom: (win.pageYOffset + win.innerHeight),
+    right: (win.pageXOffset + win.innerWidth)
+  };
+
+  return (vp.left <= c.x + win.pageXOffset &&
+      c.x + win.pageXOffset <= vp.right &&
+      vp.top <= c.y + win.pageYOffset &&
+      c.y + win.pageYOffset <= vp.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 {Element} element
+ *     Element to check if visible.
+ * @param {Window} window
+ *     Window object.
+ * @param {number=} x
+ *     Horizontal offset relative to target.  Defaults to the centre of
+ *     the target's bounding box.
+ * @param {number=} y
+ *     Vertical offset relative to target.  Defaults to the centre of
+ *     the target's bounding box.
+ */
+elements.checkVisible = function(el, win, x = undefined, y = undefined) {
+  // Bug 1094246: Webdriver's isShown doesn't work with content xul
+  let ns = atom.getElementAttribute(el, "namespaceURI", win);
+  if (ns.indexOf("there.is.only.xul") < 0 &&
+      !atom.isElementDisplayed(el, win)) {
+    return false;
+  }
+
+  if (el.tagName.toLowerCase() == "body") {
+    return true;
+  }
+
+  if (!elements.inViewport(el, x, y)) {
+    if (el.scrollIntoView) {
+      el.scrollIntoView(false);
+      if (!elements.inViewport(el)) {
+        return false;
+      }
+    } else {
+      return false;
+    }
+  }
+  return true;
+};
+
+elements.isXULElement = function(el) {
+  let ns = atom.getElementAttribute(el, "namespaceURI");
+  return ns.indexOf("there.is.only.xul") >= 0;
+};
--- a/testing/marionette/interactions.js
+++ b/testing/marionette/interactions.js
@@ -1,21 +1,23 @@
 /* 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/. */
 
-/* global Components, Accessibility, ElementNotVisibleError,
-   InvalidElementStateError, Interactions */
+"use strict";
 
-var {utils: Cu} = Components;
+const {utils: Cu} = Components;
 
-this.EXPORTED_SYMBOLS = ['Interactions'];
+Cu.import("chrome://marionette/content/accessibility.js");
+Cu.import("chrome://marionette/content/atoms.js");
+Cu.import("chrome://marionette/content/error.js");
+Cu.import("chrome://marionette/content/elements.js");
+Cu.import("chrome://marionette/content/event.js");
 
-Cu.import('chrome://marionette/content/accessibility.js');
-Cu.import('chrome://marionette/content/error.js');
+this.EXPORTED_SYMBOLS = ["Interactions"];
 
 /**
  * XUL elements that support disabled attribtue.
  */
 const DISABLED_ATTRIBUTE_SUPPORTED_XUL = new Set([
   'ARROWSCROLLBOX',
   'BUTTON',
   'CHECKBOX',
@@ -68,41 +70,20 @@ const SELECTED_PROPERTY_SUPPORTED_XUL = 
   'MENUITEM',
   'MENUSEPARATOR',
   'RADIO',
   'RICHLISTITEM',
   'TAB'
 ]);
 
 /**
- * This function generates a pair of coordinates relative to the viewport given
- * a target element and coordinates relative to that element's top-left corner.
- * @param 'x', and 'y' are the relative to the target.
- *        If they are not specified, then the center of the target is used.
- */
-function coordinates(target, x, y) {
-  let box = target.getBoundingClientRect();
-  if (typeof x === 'undefined') {
-    x = box.width / 2;
-  }
-  if (typeof y === 'undefined') {
-    y = box.height / 2;
-  }
-  return {
-    x: box.left + x,
-    y: box.top + y
-  };
-}
-
-/**
  * A collection of interactions available in marionette.
  * @type {Object}
  */
-this.Interactions = function(utils, getCapabilies) {
-  this.utils = utils;
+this.Interactions = function(getCapabilies) {
   this.accessibility = new Accessibility(getCapabilies);
 };
 
 Interactions.prototype = {
   /**
    * Send click event to element.
    *
    * @param nsIDOMWindow, ShadowRoot container
@@ -110,32 +91,35 @@ Interactions.prototype = {
    *
    * @param ElementManager elementManager
    *
    * @param String id
    *        The DOM reference ID
    */
   clickElement(container, elementManager, id) {
     let el = elementManager.getKnownElement(id, container);
-    let visible = this.checkVisible(container, el);
+    let visible = elements.checkVisible(el, container.frame);
     if (!visible) {
       throw new ElementNotVisibleError('Element is not visible');
     }
     return this.accessibility.getAccessibleObject(el, true).then(acc => {
       this.accessibility.checkVisible(acc, el, visible);
-      if (this.utils.isElementEnabled(el)) {
+      if (atom.isElementEnabled(el)) {
         this.accessibility.checkEnabled(acc, el, true, container);
         this.accessibility.checkActionable(acc, el);
-        if (this.isXULElement(el)) {
+        if (elements.isXULElement(el)) {
           el.click();
         } else {
           let rects = el.getClientRects();
-          this.utils.synthesizeMouseAtPoint(rects[0].left + rects[0].width/2,
-                                            rects[0].top + rects[0].height/2,
-                                            {}, el.ownerDocument.defaultView);
+          let win = el.ownerDocument.defaultView;
+          event.synthesizeMouseAtPoint(
+              rects[0].left + rects[0].width / 2,
+              rects[0].top + rects[0].height / 2,
+              {} /* opts */,
+              win);
         }
       } else {
         throw new InvalidElementStateError('Element is not enabled');
       }
     });
   },
 
   /**
@@ -154,18 +138,18 @@ Interactions.prototype = {
    *
    * @param Boolean ignoreVisibility
    *        A flag to check element visibility
    */
   sendKeysToElement(container, elementManager, id, value, ignoreVisibility) {
     let el = elementManager.getKnownElement(id, container);
     return this.accessibility.getAccessibleObject(el, true).then(acc => {
       this.accessibility.checkActionable(acc, el);
-      this.utils.sendKeysToElement(
-        container.frame, el, value, ignoreVisibility);
+      event.sendKeysToElement(
+          value, el, {ignoreVisibility: false}, container.frame);
     });
   },
 
   /**
    * Determine the element displayedness of the given web element.
    *
    * @param nsIDOMWindow, ShadowRoot container
    *        The window and an optional shadow root that contains the element
@@ -175,17 +159,17 @@ Interactions.prototype = {
    * @param {WebElement} id
    *     Reference to web element.
    *
    * Also performs additional accessibility checks if enabled by session
    * capability.
    */
   isElementDisplayed(container, elementManager, id) {
     let el = elementManager.getKnownElement(id, container);
-    let displayed = this.utils.isElementDisplayed(el);
+    let displayed = atom.isElementDisplayed(el, container.frame);
     return this.accessibility.getAccessibleObject(el).then(acc => {
       this.accessibility.checkVisible(acc, el, displayed);
       return displayed;
     });
   },
 
   /**
    * Check if element is enabled.
@@ -199,26 +183,26 @@ Interactions.prototype = {
    *     Reference to web element.
    *
    * @return {boolean}
    *     True if enabled, false otherwise.
    */
   isElementEnabled(container, elementManager, id) {
     let el = elementManager.getKnownElement(id, container);
     let enabled = true;
-    if (this.isXULElement(el)) {
+    if (elements.isXULElement(el)) {
       // Check if XUL element supports disabled attribute
       if (DISABLED_ATTRIBUTE_SUPPORTED_XUL.has(el.tagName.toUpperCase())) {
-        let disabled = this.utils.getElementAttribute(el, 'disabled');
+        let disabled = atom.getElementAttribute(el, 'disabled', container.frame);
         if (disabled && disabled === 'true') {
           enabled = false;
         }
       }
     } else {
-      enabled = this.utils.isElementEnabled(el);
+      enabled = atom.isElementEnabled(el, container.frame);
     }
     return this.accessibility.getAccessibleObject(el).then(acc => {
       this.accessibility.checkEnabled(acc, el, enabled, container);
       return enabled;
     });
   },
 
   /**
@@ -233,85 +217,25 @@ Interactions.prototype = {
    * @param ElementManager elementManager
    *
    * @param {WebElement} id
    *     Reference to web element.
    */
   isElementSelected(container, elementManager, id) {
     let el = elementManager.getKnownElement(id, container);
     let selected = true;
-    if (this.isXULElement(el)) {
+    if (elements.isXULElement(el)) {
       let tagName = el.tagName.toUpperCase();
       if (CHECKED_PROPERTY_SUPPORTED_XUL.has(tagName)) {
         selected = el.checked;
       }
       if (SELECTED_PROPERTY_SUPPORTED_XUL.has(tagName)) {
         selected = el.selected;
       }
     } else {
-      selected = this.utils.isElementSelected(el);
+      selected = atom.isElementSelected(el, container.frame);
     }
     return this.accessibility.getAccessibleObject(el).then(acc => {
       this.accessibility.checkSelected(acc, el, selected);
       return selected;
     });
   },
-
-  /**
-   * 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.
-   */
-  checkVisible(container, el, x, y) {
-    // Bug 1094246 - Webdriver's isShown doesn't work with content xul
-    if (!this.isXULElement(el)) {
-      //check if the element is visible
-      let visible = this.utils.isElementDisplayed(el);
-      if (!visible) {
-        return false;
-      }
-    }
-
-    if (el.tagName.toLowerCase() === 'body') {
-      return true;
-    }
-    if (!this.elementInViewport(container, el, x, y)) {
-      //check if scroll function exist. If so, call it.
-      if (el.scrollIntoView) {
-        el.scrollIntoView(false);
-        if (!this.elementInViewport(container, el)) {
-          return false;
-        }
-      }
-      else {
-        return false;
-      }
-    }
-    return true;
-  },
-
-  isXULElement(el) {
-    return this.utils.getElementAttribute(el, 'namespaceURI').indexOf(
-      'there.is.only.xul') >= 0;
-  },
-
-  /**
-   * 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.
-   */
-  elementInViewport(container, el, x, y) {
-    let c = coordinates(el, x, y);
-    let win = container.frame;
-    let viewPort = {
-      top: win.pageYOffset,
-      left: win.pageXOffset,
-      bottom: win.pageYOffset + win.innerHeight,
-      right: win.pageXOffset + win.innerWidth
-    };
-    return (viewPort.left <= c.x + win.pageXOffset &&
-            c.x + win.pageXOffset <= viewPort.right &&
-            viewPort.top <= c.y + win.pageYOffset &&
-            c.y + win.pageYOffset <= viewPort.bottom);
-  }
 };