Bug 1238744 - centralizing interactions into its own file. r=ato draft
authorYura Zenevich <yzenevich@mozilla.com>
Fri, 15 Jan 2016 14:49:02 -0500
changeset 322134 ded5dccd86982e8e6e14082a09fe115bf6f08836
parent 322070 324c50cbc40ae7c5a7965e630fba1a7e54998c2e
child 513034 74a46b061e31c784240e43991ff6cb4d526e1312
push id9531
push useryura.zenevich@gmail.com
push dateFri, 15 Jan 2016 19:49:29 +0000
reviewersato
bugs1238744, 100644
milestone46.0a1
Bug 1238744 - centralizing interactions into its own file. r=ato --- testing/marionette/accessibility.js | 335 ++++++++++++++++++++++++++++++++++++ testing/marionette/driver.js | 40 ++--- testing/marionette/elements.js | 197 +-------------------- testing/marionette/interactions.js | 317 ++++++++++++++++++++++++++++++++++ testing/marionette/jar.mn | 2 + testing/marionette/listener.js | 188 +++----------------- testing/marionette/sendkeys.js | 6 +- 7 files changed, 696 insertions(+), 389 deletions(-) create mode 100644 testing/marionette/accessibility.js create mode 100644 testing/marionette/interactions.js
testing/marionette/accessibility.js
testing/marionette/driver.js
testing/marionette/elements.js
testing/marionette/interactions.js
testing/marionette/jar.mn
testing/marionette/listener.js
testing/marionette/sendkeys.js
new file mode 100644
--- /dev/null
+++ b/testing/marionette/accessibility.js
@@ -0,0 +1,335 @@
+/* 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 Accessibility, Components, Log, ElementNotAccessibleError,
+          XPCOMUtils */
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Log.jsm');
+
+XPCOMUtils.defineLazyModuleGetter(this, 'setInterval',
+  'resource://gre/modules/Timer.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'clearInterval',
+  'resource://gre/modules/Timer.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'ElementNotAccessibleError',
+  'chrome://marionette/content/error.js');
+
+this.EXPORTED_SYMBOLS = ['Accessibility'];
+
+/**
+ * Accessible states used to check element's state from the accessiblity API
+ * perspective.
+ */
+const states = {
+  unavailable: Ci.nsIAccessibleStates.STATE_UNAVAILABLE,
+  focusable: Ci.nsIAccessibleStates.STATE_FOCUSABLE,
+  selectable: Ci.nsIAccessibleStates.STATE_SELECTABLE,
+  selected: Ci.nsIAccessibleStates.STATE_SELECTED
+};
+
+var logger = Log.repository.getLogger('Marionette');
+
+/**
+ * Component responsible for interacting with platform accessibility API. Its
+ * methods serve as wrappers for testing content and chrome accessibility as
+ * well as accessibility of user interactions.
+ *
+ * @param {Function} getCapabilies
+ *        Session capabilities getter.
+ */
+this.Accessibility = function Accessibility(getCapabilies = () => {}) {
+  // A flag indicating whether the accessibility issue should be logged or cause
+  // an exception. Default: log to stdout.
+  Object.defineProperty(this, 'strict', {
+    configurable: true,
+    get: function() {
+      let capabilies = getCapabilies();
+      return !!capabilies.raisesAccessibilityExceptions;
+    }
+  });
+  // An interface for in-process accessibility clients
+  // Note: we access it lazily to not enable accessibility when it is not needed
+  Object.defineProperty(this, 'retrieval', {
+    configurable: true,
+    get: function() {
+      delete this.retrieval;
+      this.retrieval = Cc[
+        '@mozilla.org/accessibleRetrieval;1'].getService(
+          Ci.nsIAccessibleRetrieval);
+      return this.retrieval;
+    }
+  });
+};
+
+Accessibility.prototype = {
+
+  /**
+   * Number of attempts to get an accessible object for an element. We attempt
+   * more than once because accessible tree can be out of sync with the DOM tree
+   * for a short period of time.
+   * @type {Number}
+   */
+  GET_ACCESSIBLE_ATTEMPTS: 100,
+
+  /**
+   * An interval between attempts to retrieve an accessible object for an
+   * element.
+   * @type {Number} ms
+   */
+  GET_ACCESSIBLE_ATTEMPT_INTERVAL: 10,
+
+  /**
+   * Accessible object roles that support some action
+   * @type Object
+   */
+  ACTIONABLE_ROLES: new Set([
+    'pushbutton',
+    'checkbutton',
+    'combobox',
+    'key',
+    'link',
+    'menuitem',
+    'check menu item',
+    'radio menu item',
+    'option',
+    'listbox option',
+    'listbox rich option',
+    'check rich option',
+    'combobox option',
+    'radiobutton',
+    'rowheader',
+    'switch',
+    'slider',
+    'spinbutton',
+    'pagetab',
+    'entry',
+    'outlineitem'
+  ]),
+
+  /**
+   * Get an accessible object for a DOM element
+   * @param nsIDOMElement element
+   * @param Boolean mustHaveAccessible a flag indicating that the element must
+   * have an accessible object
+   * @return nsIAccessible object for the element
+   */
+  getAccessibleObject(element, mustHaveAccessible = false) {
+    return new Promise((resolve, reject) => {
+      let acc = this.retrieval.getAccessibleFor(element);
+
+      if (acc || !mustHaveAccessible) {
+        // If accessible object is found, return it. If it is not required,
+        // also resolve.
+        resolve(acc);
+      } else {
+        // If we require an accessible object, we need to poll for it because
+        // accessible tree might be out of sync with DOM tree for a short time.
+        let attempts = this.GET_ACCESSIBLE_ATTEMPTS;
+        let intervalId = setInterval(() => {
+          let acc = this.retrieval.getAccessibleFor(element);
+          if (acc || --attempts <= 0) {
+            clearInterval(intervalId);
+            if (acc) { resolve(acc); }
+            else { reject(); }
+          }
+        }, this.GET_ACCESSIBLE_ATTEMPT_INTERVAL);
+      }
+    }).catch(() => this.error(
+      'Element does not have an accessible object', element));
+  },
+
+  /**
+   * Check if the accessible has a role that supports some action
+   * @param nsIAccessible object
+   * @return Boolean an indicator of role being actionable
+   */
+  isActionableRole(accessible) {
+    return this.ACTIONABLE_ROLES.has(
+      this.retrieval.getStringRole(accessible.role));
+  },
+
+  /**
+   * Determine if an accessible has at least one action that it supports
+   * @param nsIAccessible object
+   * @return Boolean an indicator of supporting at least one accessible action
+   */
+  hasActionCount(accessible) {
+    return accessible.actionCount > 0;
+  },
+
+  /**
+   * Determine if an accessible has a valid name
+   * @param nsIAccessible object
+   * @return Boolean an indicator that the element has a non empty valid name
+   */
+  hasValidName(accessible) {
+    return accessible.name && accessible.name.trim();
+  },
+
+  /**
+   * Check if an accessible has a set hidden attribute
+   * @param nsIAccessible object
+   * @return Boolean an indicator that the element has a hidden accessible
+   * attribute set to true
+   */
+  hasHiddenAttribute(accessible) {
+    let hidden = false;
+    try {
+      hidden = accessible.attributes.getStringProperty('hidden');
+    } finally {
+      // If the property is missing, exception will be thrown.
+      return hidden && hidden === 'true';
+    }
+  },
+
+  /**
+   * Verify if an accessible has a given state
+   * @param nsIAccessible object
+   * @param Number stateToMatch the state to match
+   * @return Boolean accessible has a state
+   */
+  matchState(accessible, stateToMatch) {
+    let state = {};
+    accessible.getState(state, {});
+    return !!(state.value & stateToMatch);
+  },
+
+  /**
+   * Check if an accessible is hidden from the user of the accessibility API
+   * @param nsIAccessible object
+   * @return Boolean an indicator that the element is hidden from the user
+   */
+  isHidden(accessible) {
+    while (accessible) {
+      if (this.hasHiddenAttribute(accessible)) {
+        return true;
+      }
+      accessible = accessible.parent;
+    }
+    return false;
+  },
+
+  /**
+   * Send an error message or log the error message in the log
+   * @param String message
+   * @param DOMElement element that caused an error
+   */
+  error(message, element) {
+    if (!message) {
+      return;
+    }
+    if (element) {
+      let {id, tagName, className} = element;
+      message += `: id: ${id}, tagName: ${tagName}, className: ${className}`;
+    }
+    if (this.strict) {
+      throw new ElementNotAccessibleError(message);
+    }
+    logger.error(message);
+  },
+
+  /**
+   * Check if the element's visible state corresponds to its accessibility API
+   * visibility
+   * @param nsIAccessible object
+   * @param WebElement corresponding to nsIAccessible object
+   * @param Boolean visible element's visibility state
+   */
+  checkVisible(accessible, element, visible) {
+    if (!accessible) {
+      return;
+    }
+    let hiddenAccessibility = this.isHidden(accessible);
+    let message;
+    if (visible && hiddenAccessibility) {
+      message = 'Element is not currently visible via the accessibility API ' +
+        'and may not be manipulated by it';
+    } else if (!visible && !hiddenAccessibility) {
+      message = 'Element is currently only visible via the accessibility API ' +
+        'and can be manipulated by it';
+    }
+    this.error(message, element);
+  },
+
+  /**
+   * Check if the element's unavailable accessibility state matches the enabled
+   * state
+   * @param nsIAccessible object
+   * @param WebElement corresponding to nsIAccessible object
+   * @param Boolean enabled element's enabled state
+   * @param Object container frame and optional ShadowDOM
+   */
+  checkEnabled(accessible, element, enabled, container) {
+    if (!accessible) {
+      return;
+    }
+    let disabledAccessibility = this.matchState(accessible, states.unavailable);
+    let explorable = container.frame.document.defaultView.getComputedStyle(
+      element).getPropertyValue('pointer-events') !== 'none';
+    let message;
+
+    if (!explorable && !disabledAccessibility) {
+      message = 'Element is enabled but is not explorable via the ' +
+        'accessibility API';
+    } else if (enabled && disabledAccessibility) {
+      message = 'Element is enabled but disabled via the accessibility API';
+    } else if (!enabled && !disabledAccessibility) {
+      message = 'Element is disabled but enabled via the accessibility API';
+    }
+    this.error(message, element);
+  },
+
+  /**
+   * Check if it is possible to activate an element with the accessibility API
+   * @param nsIAccessible object
+   * @param WebElement corresponding to nsIAccessible object
+   */
+  checkActionable(accessible, element) {
+    if (!accessible) {
+      return;
+    }
+    let message;
+    if (!this.hasActionCount(accessible)) {
+      message = 'Element does not support any accessible actions';
+    } else if (!this.isActionableRole(accessible)) {
+      message = 'Element does not have a correct accessibility role ' +
+        'and may not be manipulated via the accessibility API';
+    } else if (!this.hasValidName(accessible)) {
+      message = 'Element is missing an accessible name';
+    } else if (!this.matchState(accessible, states.focusable)) {
+      message = 'Element is not focusable via the accessibility API';
+    }
+    this.error(message, element);
+  },
+
+  /**
+   * Check if element's selected state corresponds to its accessibility API
+   * selected state.
+   * @param nsIAccessible object
+   * @param WebElement corresponding to nsIAccessible object
+   * @param Boolean selected element's selected state
+   */
+  checkSelected(accessible, element, selected) {
+    if (!accessible) {
+      return;
+    }
+    if (!this.matchState(accessible, states.selectable)) {
+      // Element is not selectable via the accessibility API
+      return;
+    }
+
+    let selectedAccessibility = this.matchState(accessible, states.selected);
+    let message;
+    if (selected && !selectedAccessibility) {
+      message =
+        'Element is selected but not selected via the accessibility API';
+    } else if (!selected && selectedAccessibility) {
+      message =
+        'Element is not selected but selected via the accessibility API';
+    }
+    this.error(message, element);
+  }
+};
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -18,16 +18,17 @@ 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/interactions.js");
 Cu.import("chrome://marionette/content/elements.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");
 
@@ -160,16 +161,18 @@ 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.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;
     if (topic == modal.COMMON_DIALOG_LOADED) {
@@ -1978,20 +1981,19 @@ GeckoDriver.prototype.getActiveElement =
  * @param {string} id
  *     Reference ID to the element that will be clicked.
  */
 GeckoDriver.prototype.clickElement = function(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
-      // click atom fails, fall back to click() action
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
-      el.click();
+      yield this.interactions.clickElement({ frame: win },
+        this.curBrowser.elementManager, id)
       break;
 
     case Context.CONTENT:
       // We need to protect against the click causing an OOP frame to close.
       // This fires the mozbrowserclose event when it closes so we need to
       // listen for it and then just send an error back. The person making the
       // call should be aware something isnt right and handle accordingly
       this.addFrameCloseListener("click");
@@ -2079,18 +2081,18 @@ GeckoDriver.prototype.getElementTagName 
  *     Reference ID to the element that will be inspected.
  */
 GeckoDriver.prototype.isElementDisplayed = function(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, {frame: win});
-      resp.body.value = utils.isElementDisplayed(el);
+      resp.body.value = yield this.interactions.isElementDisplayed(
+        {frame: win}, this.curBrowser.elementManager, id);
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.isElementDisplayed(id);
       break;
   }
 };
 
@@ -2127,18 +2129,18 @@ GeckoDriver.prototype.getElementValueOfC
  */
 GeckoDriver.prototype.isElementEnabled = function(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       // Selenium atom doesn't quite work here
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, {frame: win});
-      resp.body.value = !(!!el.disabled);
+      resp.body.value = yield this.interactions.isElementEnabled(
+        {frame: win}, this.curBrowser.elementManager, id);
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.isElementEnabled(id);
       break;
   }
 },
 
@@ -2150,24 +2152,18 @@ GeckoDriver.prototype.isElementEnabled =
  */
 GeckoDriver.prototype.isElementSelected = function(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       // Selenium atom doesn't quite work here
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
-      if (typeof el.checked != "undefined") {
-        resp.body.value = !!el.checked;
-      } else if (typeof el.selected != "undefined") {
-        resp.body.value = !!el.selected;
-      } else {
-        resp.body.value = true;
-      }
+      resp.body.value = yield this.interactions.isElementSelected(
+        { frame: win }, this.curBrowser.elementManager, id);
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.isElementSelected(id);
       break;
   }
 };
 
@@ -2206,25 +2202,18 @@ GeckoDriver.prototype.sendKeysToElement 
 
   if (!value) {
     throw new InvalidArgumentError(`Expected character sequence: ${value}`);
   }
 
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
-      utils.sendKeysToElement(
-          win,
-          el,
-          value,
-          () => {},
-          e => { throw e; },
-          cmd.id,
-          true /* ignore visibility check */);
+      yield this.interactions.sendKeysToElement(
+        { frame: win }, this.curBrowser.elementManager, id, value, true);
       break;
 
     case Context.CONTENT:
       let err;
       let listener = function(msg) {
         this.mm.removeMessageListener("Marionette:setElementValue", listener);
 
         let val = msg.data.value;
@@ -2803,19 +2792,16 @@ GeckoDriver.prototype.sendKeysToDialog =
     throw new ElementNotVisibleError("This prompt does not accept text input");
   }
 
   let win = this.dialog.window ? this.dialog.window : this.getCurrentWindow();
   utils.sendKeysToElement(
       win,
       loginTextbox,
       cmd.parameters.value,
-      () => {},
-      e => { throw e; },
-      this.command_id,
       true /* ignore visibility check */);
 };
 
 /**
  * Quits Firefox with the provided flags and tears down the current
  * session.
  */
 GeckoDriver.prototype.quitApplication = function(cmd, resp) {
--- a/testing/marionette/elements.js
+++ b/testing/marionette/elements.js
@@ -1,32 +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/. */
 
-var {utils: Cu} = Components;
-
-Cu.import("chrome://marionette/content/error.js");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, 'setInterval',
-  'resource://gre/modules/Timer.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'clearInterval',
-  'resource://gre/modules/Timer.jsm');
+Components.utils.import("chrome://marionette/content/error.js");
 
 /**
  * The ElementManager manages DOM references and interactions with elements.
  * According to the WebDriver spec (http://code.google.com/p/selenium/wiki/JsonWireProtocol), the
  * server sends the client an element reference, and maintains the map of reference to element.
  * The client uses this reference when querying/interacting with the element, and the
  * server uses maps this reference to the actual element when it executes the command.
  */
 
 this.EXPORTED_SYMBOLS = [
-  "Accessibility",
   "ElementManager",
   "CLASS_NAME",
   "SELECTOR",
   "ID",
   "NAME",
   "LINK_TEXT",
   "PARTIAL_LINK_TEXT",
   "TAG",
@@ -46,202 +37,16 @@ this.ID = "id";
 this.NAME = "name";
 this.LINK_TEXT = "link text";
 this.PARTIAL_LINK_TEXT = "partial link text";
 this.TAG = "tag name";
 this.XPATH = "xpath";
 this.ANON= "anon";
 this.ANON_ATTRIBUTE = "anon attribute";
 
-this.Accessibility = function Accessibility() {
-  // A flag indicating whether the accessibility issue should be logged or cause
-  // an exception. Default: log to stdout.
-  this.strict = false;
-  // An interface for in-process accessibility clients
-  // Note: we access it lazily to not enable accessibility when it is not needed
-  Object.defineProperty(this, 'accessibleRetrieval', {
-    configurable: true,
-    get: function() {
-      delete this.accessibleRetrieval;
-      this.accessibleRetrieval = Components.classes[
-        '@mozilla.org/accessibleRetrieval;1'].getService(
-          Components.interfaces.nsIAccessibleRetrieval);
-      return this.accessibleRetrieval;
-    }
-  });
-};
-
-Accessibility.prototype = {
-
-  /**
-   * Number of attempts to get an accessible object for an element. We attempt
-   * more than once because accessible tree can be out of sync with the DOM tree
-   * for a short period of time.
-   * @type {Number}
-   */
-  GET_ACCESSIBLE_ATTEMPTS: 100,
-
-  /**
-   * An interval between attempts to retrieve an accessible object for an
-   * element.
-   * @type {Number} ms
-   */
-  GET_ACCESSIBLE_ATTEMPT_INTERVAL: 10,
-
-  /**
-   * Accessible object roles that support some action
-   * @type Object
-   */
-  actionableRoles: new Set([
-    'pushbutton',
-    'checkbutton',
-    'combobox',
-    'key',
-    'link',
-    'menuitem',
-    'check menu item',
-    'radio menu item',
-    'option',
-    'listbox option',
-    'listbox rich option',
-    'check rich option',
-    'combobox option',
-    'radiobutton',
-    'rowheader',
-    'switch',
-    'slider',
-    'spinbutton',
-    'pagetab',
-    'entry',
-    'outlineitem'
-  ]),
-
-  /**
-   * Get an accessible object for a DOM element
-   * @param nsIDOMElement element
-   * @param Boolean mustHaveAccessible a flag indicating that the element must
-   * have an accessible object
-   * @return nsIAccessible object for the element
-   */
-  getAccessibleObject(element, mustHaveAccessible = false) {
-    return new Promise((resolve, reject) => {
-      let acc = this.accessibleRetrieval.getAccessibleFor(element);
-
-      if (acc || !mustHaveAccessible) {
-        // If accessible object is found, return it. If it is not required,
-        // also resolve.
-        resolve(acc);
-      } else {
-        // If we require an accessible object, we need to poll for it because
-        // accessible tree might be out of sync with DOM tree for a short time.
-        let attempts = this.GET_ACCESSIBLE_ATTEMPTS;
-        let intervalId = setInterval(() => {
-          let acc = this.accessibleRetrieval.getAccessibleFor(element);
-          if (acc || --attempts <= 0) {
-            clearInterval(intervalId);
-            if (acc) { resolve(acc); }
-            else { reject(); }
-          }
-        }, this.GET_ACCESSIBLE_ATTEMPT_INTERVAL);
-      }
-    }).catch(() => this.handleErrorMessage(
-      'Element does not have an accessible object', element));
-  },
-
-  /**
-   * Check if the accessible has a role that supports some action
-   * @param nsIAccessible object
-   * @return Boolean an indicator of role being actionable
-   */
-  isActionableRole(accessible) {
-    return this.actionableRoles.has(
-      this.accessibleRetrieval.getStringRole(accessible.role));
-  },
-
-  /**
-   * Determine if an accessible has at least one action that it supports
-   * @param nsIAccessible object
-   * @return Boolean an indicator of supporting at least one accessible action
-   */
-  hasActionCount(accessible) {
-    return accessible.actionCount > 0;
-  },
-
-  /**
-   * Determine if an accessible has a valid name
-   * @param nsIAccessible object
-   * @return Boolean an indicator that the element has a non empty valid name
-   */
-  hasValidName(accessible) {
-    return accessible.name && accessible.name.trim();
-  },
-
-  /**
-   * Check if an accessible has a set hidden attribute
-   * @param nsIAccessible object
-   * @return Boolean an indicator that the element has a hidden accessible
-   * attribute set to true
-   */
-  hasHiddenAttribute(accessible) {
-    let hidden;
-    try {
-      hidden = accessible.attributes.getStringProperty('hidden');
-    } finally {
-      // If the property is missing, exception will be thrown.
-      return hidden && hidden === 'true';
-    }
-  },
-
-  /**
-   * Verify if an accessible has a given state
-   * @param nsIAccessible object
-   * @param String stateName name of the state to match
-   * @return Boolean accessible has a state
-   */
-  matchState(accessible, stateName) {
-    let stateToMatch = Components.interfaces.nsIAccessibleStates[stateName];
-    let state = {};
-    accessible.getState(state, {});
-    return !!(state.value & stateToMatch);
-  },
-
-  /**
-   * Check if an accessible is hidden from the user of the accessibility API
-   * @param nsIAccessible object
-   * @return Boolean an indicator that the element is hidden from the user
-   */
-  isHidden(accessible) {
-    while (accessible) {
-      if (this.hasHiddenAttribute(accessible)) {
-        return true;
-      }
-      accessible = accessible.parent;
-    }
-    return false;
-  },
-
-  /**
-   * Send an error message or log the error message in the log
-   * @param String message
-   * @param DOMElement element that caused an error
-   */
-  handleErrorMessage(message, element) {
-    if (!message) {
-      return;
-    }
-    if (element) {
-      message += ` -> id: ${element.id}, tagName: ${element.tagName}, className: ${element.className}\n`;
-    }
-    if (this.strict) {
-      throw new ElementNotAccessibleError(message);
-    }
-    dump(Date.now() + " Marionette: " + message);
-  }
-};
-
 this.ElementManager = function ElementManager(notSupported) {
   this.seenItems = {};
   this.timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
   this.elementKey = 'ELEMENT';
   this.w3cElementKey = 'element-6066-11e4-a52e-4f735466cecf';
   this.elementStrategies = [CLASS_NAME, SELECTOR, ID, NAME, LINK_TEXT, PARTIAL_LINK_TEXT, TAG, XPATH, ANON, ANON_ATTRIBUTE];
   for (let i = 0; i < notSupported.length; i++) {
     this.elementStrategies.splice(this.elementStrategies.indexOf(notSupported[i]), 1);
new file mode 100644
--- /dev/null
+++ b/testing/marionette/interactions.js
@@ -0,0 +1,317 @@
+/* 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 */
+
+var {utils: Cu} = Components;
+
+this.EXPORTED_SYMBOLS = ['Interactions'];
+
+Cu.import('chrome://marionette/content/accessibility.js');
+Cu.import('chrome://marionette/content/error.js');
+
+/**
+ * XUL elements that support disabled attribtue.
+ */
+const DISABLED_ATTRIBUTE_SUPPORTED_XUL = new Set([
+  'ARROWSCROLLBOX',
+  'BUTTON',
+  'CHECKBOX',
+  'COLORPICKER',
+  'COMMAND',
+  'DATEPICKER',
+  'DESCRIPTION',
+  'KEY',
+  'KEYSET',
+  'LABEL',
+  'LISTBOX',
+  'LISTCELL',
+  'LISTHEAD',
+  'LISTHEADER',
+  'LISTITEM',
+  'MENU',
+  'MENUITEM',
+  'MENULIST',
+  'MENUSEPARATOR',
+  'PREFERENCE',
+  'RADIO',
+  'RADIOGROUP',
+  'RICHLISTBOX',
+  'RICHLISTITEM',
+  'SCALE',
+  'TAB',
+  'TABS',
+  'TEXTBOX',
+  'TIMEPICKER',
+  'TOOLBARBUTTON',
+  'TREE'
+]);
+
+/**
+ * XUL elements that support checked property.
+ */
+const CHECKED_PROPERTY_SUPPORTED_XUL = new Set([
+  'BUTTON',
+  'CHECKBOX',
+  'LISTITEM',
+  'TOOLBARBUTTON'
+]);
+
+/**
+ * XUL elements that support selected property.
+ */
+const SELECTED_PROPERTY_SUPPORTED_XUL = new Set([
+  'LISTITEM',
+  'MENU',
+  '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.accessibility = new Accessibility(getCapabilies);
+};
+
+Interactions.prototype = {
+  /**
+   * Send click event to element.
+   *
+   * @param nsIDOMWindow, ShadowRoot container
+   *        The window and an optional shadow root that contains the element
+   *
+   * @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);
+    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)) {
+        this.accessibility.checkEnabled(acc, el, true, container);
+        this.accessibility.checkActionable(acc, el);
+        if (this.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);
+        }
+      } else {
+        throw new InvalidElementStateError('Element is not enabled');
+      }
+    });
+  },
+
+  /**
+   * Send keys to element
+   *
+   * @param nsIDOMWindow, ShadowRoot container
+   *        The window and an optional shadow root that contains the element
+   *
+   * @param ElementManager elementManager
+   *
+   * @param String id
+   *        The DOM reference ID
+   *
+   * @param String?Array value
+   *        Value to send to an element
+   *
+   * @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);
+    });
+  },
+
+  /**
+   * Determine the element displayedness of the given web element.
+   *
+   * @param nsIDOMWindow, ShadowRoot container
+   *        The window and an optional shadow root that contains the element
+   *
+   * @param ElementManager elementManager
+   *
+   * @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);
+    return this.accessibility.getAccessibleObject(el).then(acc => {
+      this.accessibility.checkVisible(acc, el, displayed);
+      return displayed;
+    });
+  },
+
+  /**
+   * Check if element is enabled.
+   *
+   * @param nsIDOMWindow, ShadowRoot container
+   *        The window and an optional shadow root that contains the element
+   *
+   * @param ElementManager elementManager
+   *
+   * @param {WebElement} id
+   *     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)) {
+      // Check if XUL element supports disabled attribute
+      if (DISABLED_ATTRIBUTE_SUPPORTED_XUL.has(el.tagName.toUpperCase())) {
+        let disabled = this.utils.getElementAttribute(el, 'disabled');
+        if (disabled && disabled === 'true') {
+          enabled = false;
+        }
+      }
+    } else {
+      enabled = this.utils.isElementEnabled(el);
+    }
+    return this.accessibility.getAccessibleObject(el).then(acc => {
+      this.accessibility.checkEnabled(acc, el, enabled, container);
+      return enabled;
+    });
+  },
+
+  /**
+   * Determines if the referenced element is selected or not.
+   *
+   * This operation only makes sense on input elements of the Checkbox-
+   * and Radio Button states, or option elements.
+   *
+   * @param nsIDOMWindow, ShadowRoot container
+   *        The window and an optional shadow root that contains the element
+   *
+   * @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)) {
+      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);
+    }
+    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);
+  }
+};
--- a/testing/marionette/jar.mn
+++ b/testing/marionette/jar.mn
@@ -2,16 +2,18 @@
 # 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/.
 
 marionette.jar:
 % content marionette %content/
   content/server.js (server.js)
   content/driver.js (driver.js)
   content/actions.js (actions.js)
+  content/interactions.js (interactions.js)
+  content/accessibility.js (accessibility.js)
   content/listener.js (listener.js)
   content/elements.js (elements.js)
   content/sendkeys.js (sendkeys.js)
   content/common.js (common.js)
   content/simpletest.js (simpletest.js)
   content/frame-manager.js (frame-manager.js)
   content/EventUtils.js  (EventUtils.js)
   content/ChromeUtils.js  (ChromeUtils.js)
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -13,16 +13,17 @@ var loader = Cc["@mozilla.org/moz/jssubs
 loader.loadSubScript("chrome://marionette/content/simpletest.js");
 loader.loadSubScript("chrome://marionette/content/common.js");
 loader.loadSubScript("chrome://marionette/content/actions.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/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:
@@ -38,17 +39,21 @@ 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([]);
-var accessibility = new Accessibility();
+
+// Holds session capabilities.
+var capabilities = {};
+var interactions = new Interactions(utils, () => capabilities);
+
 var actions = new ActionChain(utils, 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
@@ -103,19 +108,18 @@ var modalHandler = function() {
  */
 function registerSelf() {
   let msg = {value: winUtil.outerWindowID};
   // register will have the ID and a boolean describing if this is the main process or not
   let register = sendSyncMessage("Marionette:register", msg);
 
   if (register[0]) {
     let {id, remotenessChange} = register[0][0];
-    let {B2G, raisesAccessibilityExceptions} = register[0][2];
-    isB2G = B2G;
-    accessibility.strict = raisesAccessibilityExceptions;
+    capabilities = register[0][2];
+    isB2G = capabilities.B2G;
     listenerId = id;
     if (typeof id != "undefined") {
       // check if we're the main process
       if (register[0][1] == true) {
         addMessageListener("MarionetteMainListener:emitTouchEvent", emitTouchEventForIFrame);
       }
       importedScripts = FileUtils.getDir('TmpD', [], false);
       importedScripts.append('marionetteContentScripts');
@@ -294,18 +298,18 @@ function waitForReady() {
   }
 }
 
 /**
  * Called when we start a new session. It registers the
  * current environment, and resets all values
  */
 function newSession(msg) {
-  isB2G = msg.json.B2G;
-  accessibility.strict = msg.json.raisesAccessibilityExceptions;
+  capabilities = msg.json;
+  isB2G = capabilities.B2G;
   resetValues();
   if (isB2G) {
     readyStateTimer.initWithCallback(waitForReady, 100, Ci.nsITimer.TYPE_ONE_SHOT);
     // We have to set correct mouse event source to MOZ_SOURCE_TOUCH
     // to offer a way for event listeners to differentiate
     // events being the result of a physical mouse action.
     // This is especially important for the touch event shim,
     // in order to prevent creating touch event for these fake mouse events.
@@ -933,136 +937,34 @@ function checkVisible(el, x, y) {
  */
 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);
   if (!visible) {
     throw new ElementNotVisibleError("Element is not currently visible and may not be manipulated");
   }
-  return accessibility.getAccessibleObject(el, true).then(acc => {
-    checkVisibleAccessibility(acc, el, visible);
-    checkActionableAccessibility(acc, el);
+  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;
     }
     let c = coordinates(el, corx, cory);
     if (!actions.mouseEventsOnly) {
       let touchId = actions.nextTouchId++;
       let touch = createATouch(el, c.x, c.y, touchId);
       emitTouchEvent('touchstart', touch);
       emitTouchEvent('touchend', touch);
     }
     actions.mouseTap(el.ownerDocument, c.x, c.y);
   });
 }
 
 /**
- * Check if the element's unavailable accessibility state matches the enabled
- * state
- * @param nsIAccessible object
- * @param WebElement corresponding to nsIAccessible object
- * @param Boolean enabled element's enabled state
- */
-function checkEnabledAccessibility(accesible, element, enabled) {
-  if (!accesible) {
-    return;
-  }
-  let disabledAccessibility = accessibility.matchState(
-    accesible, 'STATE_UNAVAILABLE');
-  let explorable = curContainer.frame.document.defaultView.getComputedStyle(
-    element, null).getPropertyValue('pointer-events') !== 'none';
-  let message;
-
-  if (!explorable && !disabledAccessibility) {
-    message = 'Element is enabled but is not explorable via the ' +
-      'accessibility API';
-  } else if (enabled && disabledAccessibility) {
-    message = 'Element is enabled but disabled via the accessibility API';
-  } else if (!enabled && !disabledAccessibility) {
-    message = 'Element is disabled but enabled via the accessibility API';
-  }
-  accessibility.handleErrorMessage(message, element);
-}
-
-/**
- * Check if the element's visible state corresponds to its accessibility API
- * visibility
- * @param nsIAccessible object
- * @param WebElement corresponding to nsIAccessible object
- * @param Boolean visible element's visibility state
- */
-function checkVisibleAccessibility(accesible, element, visible) {
-  if (!accesible) {
-    return;
-  }
-  let hiddenAccessibility = accessibility.isHidden(accesible);
-  let message;
-  if (visible && hiddenAccessibility) {
-    message = 'Element is not currently visible via the accessibility API ' +
-      'and may not be manipulated by it';
-  } else if (!visible && !hiddenAccessibility) {
-    message = 'Element is currently only visible via the accessibility API ' +
-      'and can be manipulated by it';
-  }
-  accessibility.handleErrorMessage(message, element);
-}
-
-/**
- * Check if it is possible to activate an element with the accessibility API
- * @param nsIAccessible object
- * @param WebElement corresponding to nsIAccessible object
- */
-function checkActionableAccessibility(accesible, element) {
-  if (!accesible) {
-    return;
-  }
-  let message;
-  if (!accessibility.hasActionCount(accesible)) {
-    message = 'Element does not support any accessible actions';
-  } else if (!accessibility.isActionableRole(accesible)) {
-    message = 'Element does not have a correct accessibility role ' +
-      'and may not be manipulated via the accessibility API';
-  } else if (!accessibility.hasValidName(accesible)) {
-    message = 'Element is missing an accesible name';
-  } else if (!accessibility.matchState(accesible, 'STATE_FOCUSABLE')) {
-    message = 'Element is not focusable via the accessibility API';
-  }
-  accessibility.handleErrorMessage(message, element);
-}
-
-/**
- * Check if element's selected state corresponds to its accessibility API
- * selected state.
- * @param nsIAccessible object
- * @param WebElement corresponding to nsIAccessible object
- * @param Boolean selected element's selected state
- */
-function checkSelectedAccessibility(accessible, element, selected) {
-  if (!accessible) {
-    return;
-  }
-  if (!accessibility.matchState(accessible, 'STATE_SELECTABLE')) {
-    // Element is not selectable via the accessibility API
-    return;
-  }
-
-  let selectedAccessibility = accessibility.matchState(
-    accessible, 'STATE_SELECTED');
-  let message;
-  if (selected && !selectedAccessibility) {
-    message = 'Element is selected but not selected via the accessibility API';
-  } else if (!selected && selectedAccessibility) {
-    message = 'Element is not selected but selected via the accessibility API';
-  }
-  accessibility.handleErrorMessage(message, element);
-}
-
-
-/**
  * Function to create a touch based on the element
  * corx and cory are relative to the viewport, id is the touchId
  */
 function createATouch(el, corx, cory, touchId) {
   let doc = el.ownerDocument;
   let win = doc.defaultView;
   let [clientX, clientY, pageX, pageY, screenX, screenY] =
     actions.getCoordinateInfo(el, corx, cory);
@@ -1455,34 +1357,17 @@ function getActiveElement() {
 
 /**
  * Send click event to element.
  *
  * @param {WebElement} id
  *     Reference to the web element to click.
  */
 function clickElement(id) {
-  let el = elementManager.getKnownElement(id, curContainer);
-  let visible = checkVisible(el);
-  if (!visible) {
-    throw new ElementNotVisibleError("Element is not visible");
-  }
-  return accessibility.getAccessibleObject(el, true).then(acc => {
-    checkVisibleAccessibility(acc, el, visible);
-    if (utils.isElementEnabled(el)) {
-      checkEnabledAccessibility(acc, el, true);
-      checkActionableAccessibility(acc, el);
-      let rects = el.getClientRects();
-      utils.synthesizeMouseAtPoint(rects[0].left + rects[0].width/2,
-                                   rects[0].top + rects[0].height/2,
-                                   {}, el.ownerDocument.defaultView);
-    } else {
-      throw new InvalidElementStateError("Element is not Enabled");
-    }
-  });
+  return interactions.clickElement(curContainer, elementManager, id);
 }
 
 /**
  * Get a given attribute of an element.
  *
  * @param {WebElement} id
  *     Reference to the web element to get the attribute of.
  * @param {string} name
@@ -1526,22 +1411,17 @@ function getElementTagName(id) {
 
 /**
  * Determine the element displayedness of the given web element.
  *
  * Also performs additional accessibility checks if enabled by session
  * capability.
  */
 function isElementDisplayed(id) {
-  let el = elementManager.getKnownElement(id, curContainer);
-  let displayed = utils.isElementDisplayed(el);
-  return accessibility.getAccessibleObject(el).then(acc => {
-    checkVisibleAccessibility(acc, el, displayed);
-    return displayed;
-  });
+  return interactions.isElementDisplayed(curContainer, elementManager, id);
 }
 
 /**
  * Retrieves the computed value of the given CSS property of the given
  * web element.
  *
  * @param {String} id
  *     Web element reference.
@@ -1582,67 +1462,51 @@ function getElementRect(id) {
  *
  * @param {WebElement} id
  *     Reference to web element.
  *
  * @return {boolean}
  *     True if enabled, false otherwise.
  */
 function isElementEnabled(id) {
-  let el = elementManager.getKnownElement(id, curContainer);
-  let enabled = utils.isElementEnabled(el);
-  return accessibility.getAccessibleObject(el).then(acc => {
-    checkEnabledAccessibility(acc, el, enabled);
-    return enabled;
-  });
+  return interactions.isElementEnabled(curContainer, elementManager, id);
 }
 
 /**
  * Determines if the referenced element is selected or not.
  *
  * This operation only makes sense on input elements of the Checkbox-
  * and Radio Button states, or option elements.
  */
 function isElementSelected(id) {
-  let el = elementManager.getKnownElement(id, curContainer);
-  let selected = utils.isElementSelected(el);
-  return accessibility.getAccessibleObject(el).then(acc => {
-    checkSelectedAccessibility(acc, el, selected);
-    return selected;
-  });
+  return interactions.isElementSelected(curContainer, elementManager, id);
 }
 
 /**
  * Send keys to element
  */
 function sendKeysToElement(msg) {
   let command_id = msg.json.command_id;
   let val = msg.json.value;
-  let el;
+  let id = msg.json.id;
+  let el = elementManager.getKnownElement(id, curContainer);
 
-  return Promise.resolve(elementManager.getKnownElement(msg.json.id, curContainer))
-    .then(knownEl => {
-      el = knownEl;
-      // Element should be actionable from the accessibility standpoint to be able
-      // to send keys to it.
-      return accessibility.getAccessibleObject(el, true)
-    }).then(acc => {
-      checkActionableAccessibility(acc, el);
-      if (el.type == "file") {
-        let p = val.join("");
+  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});
-      } else {
-        utils.sendKeysToElement(curContainer.frame, el, val, sendOk, sendError, command_id);
-      }
-    }).catch(e => sendError(e, command_id));
+  } else {
+    interactions.sendKeysToElement(curContainer, elementManager, id, val)
+      .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);
--- a/testing/marionette/sendkeys.js
+++ b/testing/marionette/sendkeys.js
@@ -139,29 +139,27 @@ function focusElement(el) {
     if (el.selectionEnd == 0) {
       let len = el.value.length;
       el.setSelectionRange(len, len);
     }
   }
   el.focus();
 }
 
-function sendKeysToElement(document, element, keysToSend, successCallback, errorCallback, command_id, ignoreVisibility) {
+function sendKeysToElement(document, element, keysToSend, ignoreVisibility) {
   if (ignoreVisibility || checkVisible(element)) {
     focusElement(element);
 
     let modifiers = {
       shiftKey: false,
       ctrlKey: false,
       altKey: false,
       metaKey: false
     };
     let value = keysToSend.join("");
     for (var i = 0; i < value.length; i++) {
       var c = value.charAt(i);
       sendSingleKey(c, modifiers, document);
     }
-
-    successCallback(command_id);
   } else {
-    errorCallback(new ElementNotVisibleError("Element is not visible"), command_id);
+    throw new ElementNotVisibleError("Element is not visible");
   }
 };