Bug 1312816 - Preemptively start a11y in chrome when raisesAccessibilityExceptions marionette capability is set. r=ato draft
authorYura Zenevich <yzenevich@mozilla.com>
Mon, 07 Nov 2016 12:15:08 -0500
changeset 434852 88e7d86bbe525ea635f22bd03b6b501ad3e6e9b0
parent 434838 060f80b690b8aaa5d927e03578673a3eff3b4c64
child 536139 b6ba78a49acf6cacf234428a5f4f0bb1efacd401
push id34851
push useryura.zenevich@gmail.com
push dateMon, 07 Nov 2016 17:15:40 +0000
reviewersato
bugs1312816
milestone52.0a1
Bug 1312816 - Preemptively start a11y in chrome when raisesAccessibilityExceptions marionette capability is set. r=ato MozReview-Commit-ID: KshEWHvz8SD
testing/marionette/accessibility.js
testing/marionette/driver.js
--- a/testing/marionette/accessibility.js
+++ b/testing/marionette/accessibility.js
@@ -4,44 +4,57 @@
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
 
+const logger = Log.repository.getLogger("Marionette");
+
 Cu.import("chrome://marionette/content/error.js");
 
 XPCOMUtils.defineLazyModuleGetter(
     this, "setInterval", "resource://gre/modules/Timer.jsm");
 XPCOMUtils.defineLazyModuleGetter(
     this, "clearInterval", "resource://gre/modules/Timer.jsm");
 
-XPCOMUtils.defineLazyGetter(this, "service",
-    () => Cc["@mozilla.org/accessibilityService;1"].getService(Ci.nsIAccessibilityService));
+XPCOMUtils.defineLazyGetter(this, "service", () => {
+  let service;
+  try {
+    service = Cc["@mozilla.org/accessibilityService;1"].getService(
+      Ci.nsIAccessibilityService);
+  } catch (e) {
+    logger.warn("Accessibility module is not present");
+  } finally {
+    return service;
+  }
+});
 
 this.EXPORTED_SYMBOLS = ["accessibility"];
 
-const logger = Log.repository.getLogger("Marionette");
-
 /**
  * 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.
  */
 const GET_ACCESSIBLE_ATTEMPTS = 100;
 
 /**
  * An interval between attempts to retrieve an accessible object for an
  * element.
  */
 const GET_ACCESSIBLE_ATTEMPT_INTERVAL = 10;
 
-this.accessibility = {};
+this.accessibility = {
+  get service() {
+    return service;
+  }
+};
 
 /**
  * Accessible states used to check element"s state from the accessiblity API
  * perspective.
  * Note: if gecko is built with --disable-accessibility, the interfaces are not
  * defined. This is why we use getters instead to be able to use these
  * statically.
  */
@@ -118,40 +131,42 @@ accessibility.Checks = class {
    * Get an accessible object for an element.
    *
    * @param {DOMElement|XULElement} element
    *     Element to get the accessible object for.
    * @param {boolean=} mustHaveAccessible
    *     Flag indicating that the element must have an accessible object.
    *     Defaults to not require this.
    *
-   * @return {nsIAccessible}
-   *     Accessibility object for the given element.
+   * @return {Promise: nsIAccessible}
+   *     Promise with an accessibility object for the given element.
    */
   getAccessible(element, mustHaveAccessible = false) {
-    return new Promise((resolve, reject) => {
-      let acc = service.getAccessibleFor(element);
+    if (!this.strict) {
+      return Promise.resolve();
+    }
 
-      // if accessible object is found, return it;
-      // if it is not required, also resolve
-      if (acc || !mustHaveAccessible) {
-        resolve(acc);
+    return new Promise((resolve, reject) => {
+      if (!accessibility.service) {
+        reject();
+        return;
+      }
 
-      // if we must have an accessible but are strict,
-      // reject now and avoid polling for an accessible object
-      } else if (mustHaveAccessible && !this.strict) {
-        reject();
-
-      // 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 acc = accessibility.service.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 = GET_ACCESSIBLE_ATTEMPTS;
         let intervalId = setInterval(() => {
-          let acc = service.getAccessibleFor(element);
+          let acc = accessibility.service.getAccessibleFor(element);
           if (acc || --attempts <= 0) {
             clearInterval(intervalId);
             if (acc) {
               resolve(acc);
             } else {
               reject();
             }
           }
@@ -169,17 +184,17 @@ accessibility.Checks = class {
    *     Accessible object.
    *
    * @return {boolean}
    *     True if an actionable role is found on the accessible, false
    *     otherwise.
    */
   isActionableRole(accessible) {
     return accessibility.ActionableRoles.has(
-        service.getStringRole(accessible.role));
+        accessibility.service.getStringRole(accessible.role));
   }
 
   /**
    * Test if an accessible has at least one action that it supports.
    *
    * @param {nsIAccessible} accessible
    *     Accessible object.
    *
@@ -407,22 +422,20 @@ accessibility.Checks = class {
    * @param {string} message
    * @param {DOMElement|XULElement} element
    *     Element that caused an error.
    *
    * @throws ElementNotAccessibleError
    *     If |strict| is true.
    */
   error(message, element) {
-    if (!message) {
+    if (!message || !this.strict) {
       return;
     }
     if (element) {
       let {id, tagName, className} = element;
       message += `: id: ${id}, tagName: ${tagName}, className: ${className}`;
     }
-    if (this.strict) {
-      throw new ElementNotAccessibleError(message);
-    }
-    logger.debug(message);
+
+    throw new ElementNotAccessibleError(message);
   }
 
 };
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -12,16 +12,17 @@ var loader = Cc["@mozilla.org/moz/jssubs
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(
     this, "cookieManager", "@mozilla.org/cookiemanager;1", "nsICookieManager2");
 
+Cu.import("chrome://marionette/content/accessibility.js");
 Cu.import("chrome://marionette/content/atom.js");
 Cu.import("chrome://marionette/content/browser.js");
 Cu.import("chrome://marionette/content/element.js");
 Cu.import("chrome://marionette/content/error.js");
 Cu.import("chrome://marionette/content/evaluate.js");
 Cu.import("chrome://marionette/content/event.js");
 Cu.import("chrome://marionette/content/interaction.js");
 Cu.import("chrome://marionette/content/legacyaction.js");
@@ -479,16 +480,24 @@ GeckoDriver.prototype.newSession = funct
     throw new SessionNotCreatedError("Maximum number of active sessions.")
   }
   this.sessionId = cmd.parameters.sessionId ||
       cmd.parameters.session_id ||
       element.generateUUID();
 
   this.newSessionCommandId = cmd.id;
   this.setSessionCapabilities(cmd.parameters.capabilities);
+  // If we are testing accessibility with marionette, start a11y service in
+  // chrome first. This will ensure that we do not have any content-only
+  // services hanging around.
+  if (this.sessionCapabilities.raisesAccessibilityExceptions &&
+      accessibility.service) {
+    logger.info("Preemptively starting accessibility service in Chrome");
+  }
+
   this.scriptTimeout = 10000;
 
   let registerBrowsers = this.registerPromise();
   let browserListening = this.listeningPromise();
 
   let waitForWindow = function() {
     let win = this.getCurrentWindow();
     if (!win) {