Bug 766238 - Introduce Utils module in jsat. r=davidb
authorEitan Isaacson <eitan@monotonous.org>
Wed, 20 Jun 2012 14:07:51 -0700
changeset 97179 f08d99730f3305576b1d4e07859e30baf40e33a9
parent 97178 60468d1cc060cb9c2ef2c03df4c34d3b8b45885c
child 97180 542a27f0c445e9b2cb38ddd0de0ff1ca559362f1
push id10918
push usereisaacson@mozilla.com
push dateWed, 20 Jun 2012 21:08:01 +0000
treeherdermozilla-inbound@f08d99730f33 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdavidb
bugs766238
milestone16.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 766238 - Introduce Utils module in jsat. r=davidb
accessible/src/jsat/AccessFu.jsm
accessible/src/jsat/Presenters.jsm
accessible/src/jsat/Utils.jsm
accessible/src/jsat/VirtualCursorController.jsm
--- a/accessible/src/jsat/AccessFu.jsm
+++ b/accessible/src/jsat/AccessFu.jsm
@@ -8,16 +8,17 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 var EXPORTED_SYMBOLS = ['AccessFu'];
 
 Cu.import('resource://gre/modules/Services.jsm');
 
+Cu.import('resource://gre/modules/accessibility/Utils.jsm');
 Cu.import('resource://gre/modules/accessibility/Presenters.jsm');
 Cu.import('resource://gre/modules/accessibility/VirtualCursorController.jsm');
 
 const ACCESSFU_DISABLE = 0;
 const ACCESSFU_ENABLE = 1;
 const ACCESSFU_AUTO = 2;
 
 var AccessFu = {
@@ -29,45 +30,45 @@ var AccessFu = {
    * @param {boolean} aForceEnabled Skip platform accessibility check and enable
    *  AccessFu.
    */
   attach: function attach(aWindow) {
     if (this.chromeWin)
       // XXX: only supports attaching to one window now.
       throw new Error('Only one window could be attached to AccessFu');
 
-    dump('[AccessFu] attach\n');
+    Logger.info('attach');
     this.chromeWin = aWindow;
     this.presenters = [];
 
     this.prefsBranch = Cc['@mozilla.org/preferences-service;1']
       .getService(Ci.nsIPrefService).getBranch('accessibility.accessfu.');
     this.prefsBranch.addObserver('activate', this, false);
     this.prefsBranch.addObserver('explorebytouch', this, false);
 
-    if (Services.appinfo.OS == 'Android')
+    if (Utils.OS == 'Android')
       Services.obs.addObserver(this, 'Accessibility:Settings', false);
 
     this._processPreferences();
   },
 
   /**
    * Start AccessFu mode, this primarily means controlling the virtual cursor
    * with arrow keys.
    */
   _enable: function _enable() {
     if (this._enabled)
       return;
     this._enabled = true;
 
-    dump('[AccessFu] enable\n');
+    Logger.info('enable');
     this.addPresenter(new VisualPresenter());
 
     // Implicitly add the Android presenter on Android.
-    if (Services.appinfo.OS == 'Android')
+    if (Utils.OS == 'Android')
       this.addPresenter(new AndroidPresenter());
 
     VirtualCursorController.attach(this.chromeWin);
 
     Services.obs.addObserver(this, 'accessible-event', false);
     this.chromeWin.addEventListener('DOMActivate', this, true);
     this.chromeWin.addEventListener('resize', this, true);
     this.chromeWin.addEventListener('scroll', this, true);
@@ -78,17 +79,17 @@ var AccessFu = {
   /**
    * Disable AccessFu and return to default interaction mode.
    */
   _disable: function _disable() {
     if (!this._enabled)
       return;
     this._enabled = false;
 
-    dump('[AccessFu] disable\n');
+    Logger.info('disable');
 
     this.presenters.forEach(function(p) { p.detach(); });
     this.presenters = [];
 
     VirtualCursorController.detach();
 
     Services.obs.removeObserver(this, 'accessible-event');
     this.chromeWin.removeEventListener('DOMActivate', this, true);
@@ -108,33 +109,32 @@ var AccessFu = {
 
     let ebtPref = ACCESSFU_DISABLE;
     try {
       ebtPref = (aTouchEnabled == undefined) ?
         this.prefsBranch.getIntPref('explorebytouch') : aTouchEnabled;
     } catch (x) {
     }
 
-    if (Services.appinfo.OS == 'Android') {
+    if (Utils.OS == 'Android') {
       if (accessPref == ACCESSFU_AUTO) {
         Cc['@mozilla.org/android/bridge;1'].
           getService(Ci.nsIAndroidBridge).handleGeckoMessage(
             JSON.stringify({ gecko: { type: 'Accessibility:Ready' } }));
         return;
       }
     }
 
     if (accessPref == ACCESSFU_ENABLE)
       this._enable();
     else
       this._disable();
 
     VirtualCursorController.exploreByTouch = ebtPref == ACCESSFU_ENABLE;
-    dump('[AccessFu] Explore by touch: ' +
-          VirtualCursorController.exploreByTouch + '\n');
+    Logger.info('Explore by touch:', VirtualCursorController.exploreByTouch);
   },
 
   addPresenter: function addPresenter(presenter) {
     this.presenters.push(presenter);
     presenter.attach(this.chromeWin);
   },
 
   handleEvent: function handleEvent(aEvent) {
@@ -155,17 +155,21 @@ var AccessFu = {
       case 'TabOpen':
       {
         let browser = aEvent.target.linkedBrowser || aEvent.target;
         // Store the new browser node. We will need to check later when a new
         // content document is attached if it has been attached to this new tab.
         // If it has, than we will need to send a 'loading' message along with
         // the usual 'newdoc' to presenters.
         this._pendingDocuments[browser] = true;
-        this.presenters.forEach(function(p) { p.tabStateChanged(null, 'newtab'); });
+        this.presenters.forEach(
+          function(p) {
+            p.tabStateChanged(null, 'newtab');
+          }
+        );
         break;
       }
       case 'DOMActivate':
       {
         let activatedAcc = getAccessible(aEvent.originalTarget);
         let state = {};
         activatedAcc.getState(state, {});
 
@@ -200,23 +204,27 @@ var AccessFu = {
                                  this.prefsBranch.getIntPref('explorebytouch'));
         break;
       case 'accessible-event':
         let event;
         try {
           event = aSubject.QueryInterface(Ci.nsIAccessibleEvent);
           this._handleAccEvent(event);
         } catch (ex) {
-          dump('[AccessFu] ' + ex + '\n');
+          Logger.error(ex);
           return;
         }
     }
   },
 
   _handleAccEvent: function _handleAccEvent(aEvent) {
+    if (Logger.logLevel <= Logger.DEBUG)
+      Logger.debug(Logger.eventToString(aEvent),
+                   Logger.accessibleToString(aEvent.accessible));
+
     switch (aEvent.eventType) {
       case Ci.nsIAccessibleEvent.EVENT_VIRTUALCURSOR_CHANGED:
         {
           let pivot = aEvent.accessible.
             QueryInterface(Ci.nsIAccessibleCursorable).virtualCursor;
           let event = aEvent.
             QueryInterface(Ci.nsIAccessibleVirtualCursorChangeEvent);
           let position = pivot.position;
@@ -262,18 +270,18 @@ var AccessFu = {
           let acc = aEvent.accessible;
           if (acc.childCount) {
             let docAcc = acc.getChildAt(0);
             if (this._pendingDocuments[aEvent.DOMNode]) {
               // This is a document in a new tab. Check if it is
               // in a BUSY state (i.e. loading), and inform presenters.
               // We need to do this because a state change event will not be
               // fired when an object is created with the BUSY state.
-              // If this is not a new tab, don't bother because we sent 'loading'
-              // when the previous doc changed its state to BUSY.
+              // If this is not a new tab, don't bother because we sent
+              // 'loading' when the previous doc changed its state to BUSY.
               let state = {};
               docAcc.getState(state, {});
               if (state.value & Ci.nsIAccessibleStates.STATE_BUSY &&
                   this._isNotChromeDoc(docAcc))
                 this.presenters.forEach(
                   function(p) { p.tabStateChanged(docAcc, 'loading'); }
                 );
               delete this._pendingDocuments[aEvent.DOMNode];
@@ -317,32 +325,33 @@ var AccessFu = {
         }
       case Ci.nsIAccessibleEvent.EVENT_TEXT_INSERTED:
       case Ci.nsIAccessibleEvent.EVENT_TEXT_REMOVED:
       {
         if (aEvent.isFromUserInput) {
           // XXX support live regions as well.
           let event = aEvent.QueryInterface(Ci.nsIAccessibleTextChangeEvent);
           let isInserted = event.isInserted();
-          let textIface = aEvent.accessible.QueryInterface(Ci.nsIAccessibleText);
+          let txtIface = aEvent.accessible.QueryInterface(Ci.nsIAccessibleText);
 
           let text = '';
           try {
-            text = textIface.
+            text = txtIface.
               getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT);
           } catch (x) {
             // XXX we might have gotten an exception with of a
             // zero-length text. If we did, ignore it (bug #749810).
-            if (textIface.characterCount)
+            if (txtIface.characterCount)
               throw x;
           }
 
           this.presenters.forEach(
             function(p) {
-              p.textChanged(isInserted, event.start, event.length, text, event.modifiedText);
+              p.textChanged(isInserted, event.start, event.length,
+                            text, event.modifiedText);
             }
           );
         }
         break;
       }
       case Ci.nsIAccessibleEvent.EVENT_SCROLLING_START:
       {
         VirtualCursorController.moveCursorToObject(aEvent.accessible);
@@ -386,17 +395,17 @@ var AccessFu = {
    * @param {nsIDOMDocument} aDocument the document to check.
    * @return {boolean} true if this is not a chrome document.
    */
   _isNotChromeDoc: function _isNotChromeDoc(aDocument) {
     let location = aDocument.DOMNode.location;
     if (!location)
       return false;
 
-    return location.protocol != "about:";
+    return location.protocol != 'about:';
   },
 
   // A hash of documents that don't yet have an accessible tree.
   _pendingDocuments: {},
 
   // So we don't enable/disable twice
   _enabled: false
 };
--- a/accessible/src/jsat/Presenters.jsm
+++ b/accessible/src/jsat/Presenters.jsm
@@ -4,18 +4,18 @@
 
 'use strict';
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
+Cu.import('resource://gre/modules/accessibility/Utils.jsm');
 Cu.import('resource://gre/modules/accessibility/UtteranceGenerator.jsm');
-Cu.import('resource://gre/modules/Services.jsm');
 
 var EXPORTED_SYMBOLS = ['VisualPresenter',
                         'AndroidPresenter',
                         'DummyAndroidPresenter',
                         'PresenterContext'];
 
 /**
  * The interface for all presenter classes. A presenter could be, for example,
@@ -148,17 +148,17 @@ VisualPresenter.prototype = {
       return;
     }
 
     try {
       aContext.accessible.scrollTo(
         Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE);
       this._highlight(aContext.accessible);
     } catch (e) {
-      dump('Error getting bounds: ' + e);
+      Logger.error('Failed to get bounds: ' + e);
       return;
     }
   },
 
   tabSelected: function VisualPresenter_tabSelected(aDocContext, aVCContext) {
     this.pivotChanged(aVCContext);
   },
 
@@ -170,20 +170,17 @@ VisualPresenter.prototype = {
 
   // Internals
 
   _hide: function _hide() {
     this.highlightBox.style.display = 'none';
   },
 
   _highlight: function _highlight(aObject) {
-    let vp = (Services.appinfo.OS == 'Android') ?
-      this.chromeWin.BrowserApp.selectedTab.getViewport() :
-      { zoom: 1.0, offsetY: 0 };
-
+    let vp = Utils.getViewport(this.chromeWin) || { zoom: 1.0, offsetY: 0 };
     let bounds = this._getBounds(aObject, vp.zoom);
 
     // First hide it to avoid flickering when changing the style.
     this.highlightBox.style.display = 'none';
     this.highlightBox.style.top = bounds.top + 'px';
     this.highlightBox.style.left = bounds.left + 'px';
     this.highlightBox.style.width = bounds.width + 'px';
     this.highlightBox.style.height = bounds.height + 'px';
@@ -234,26 +231,26 @@ AndroidPresenter.prototype = {
   ANDROID_WINDOW_STATE_CHANGED: 0x20,
 
   pivotChanged: function AndroidPresenter_pivotChanged(aContext) {
     if (!aContext.accessible)
       return;
 
     let output = [];
     aContext.newAncestry.forEach(
-      function (acc) {
+      function(acc) {
         output.push.apply(output, UtteranceGenerator.genForObject(acc));
       }
     );
 
     output.push.apply(output,
                       UtteranceGenerator.genForObject(aContext.accessible));
 
     aContext.subtreePreorder.forEach(
-      function (acc) {
+      function(acc) {
         output.push.apply(output, UtteranceGenerator.genForObject(acc));
       }
     );
 
     this.sendMessageToJava({
       gecko: {
         type: 'Accessibility:Event',
         eventType: this.ANDROID_VIEW_FOCUSED,
@@ -334,17 +331,17 @@ AndroidPresenter.prototype = {
  */
 
 function DummyAndroidPresenter() {}
 
 DummyAndroidPresenter.prototype = {
   __proto__: AndroidPresenter.prototype,
 
   sendMessageToJava: function DummyAndroidPresenter_sendMessageToJava(aMsg) {
-    dump(JSON.stringify(aMsg, null, 2) + '\n');
+    Logger.debug('Android event:\n' + JSON.stringify(aMsg, null, 2));
   }
 };
 
 /**
  * PresenterContext: An object that generates and caches context information
  * for a given accessible and its relationship with another accessible.
  */
 function PresenterContext(aAccessible, aOldAccessible) {
new file mode 100644
--- /dev/null
+++ b/accessible/src/jsat/Utils.jsm
@@ -0,0 +1,121 @@
+/* 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/. */
+
+'use strict';
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import('resource://gre/modules/Services.jsm');
+
+var EXPORTED_SYMBOLS = ['Utils', 'Logger'];
+
+var gAccRetrieval = Cc['@mozilla.org/accessibleRetrieval;1'].
+  getService(Ci.nsIAccessibleRetrieval);
+
+var Utils = {
+  get OS() {
+    if (!this._OS)
+      this._OS = Services.appinfo.OS;
+    return this._OS;
+  },
+
+  get AndroidSdkVersion() {
+    if (!this._AndroidSdkVersion) {
+      let shellVersion = Services.sysinfo.get('shellVersion') || '';
+      let matches = shellVersion.match(/\((\d+)\)$/);
+      if (matches)
+        this._AndroidSdkVersion = parseInt(matches[1]);
+      else
+        this._AndroidSdkVersion = 15; // Most useful in desktop debugging.
+    }
+    return this._AndroidSdkVersion;
+  },
+
+  set AndroidSdkVersion(value) {
+    // When we want to mimic another version.
+    this._AndroidSdkVersion = value;
+  },
+
+  getBrowserApp: function getBrowserApp(aWindow) {
+    switch (this.OS) {
+      case 'Android':
+        return aWindow.BrowserApp;
+      default:
+        return aWindow.gBrowser;
+    }
+  },
+
+  getViewport: function getViewport(aWindow) {
+    switch (this.OS) {
+      case 'Android':
+        return aWindow.BrowserApp.selectedTab.getViewport();
+      default:
+        return null;
+    }
+  }
+};
+
+var Logger = {
+  DEBUG: 0,
+  INFO: 1,
+  WARNING: 2,
+  ERROR: 3,
+  _LEVEL_NAMES: ['DEBUG', 'INFO', 'WARNING', 'ERROR'],
+
+  logLevel: 1, // INFO;
+
+  log: function log(aLogLevel) {
+    if (aLogLevel < this.logLevel)
+      return;
+
+    let message = Array.prototype.slice.call(arguments, 1).join(' ');
+    dump('[AccessFu] ' + this._LEVEL_NAMES[aLogLevel] + ' ' + message + '\n');
+  },
+
+  info: function info() {
+    this.log.apply(
+      this, [this.INFO].concat(Array.prototype.slice.call(arguments)));
+  },
+
+  debug: function debug() {
+    this.log.apply(
+      this, [this.DEBUG].concat(Array.prototype.slice.call(arguments)));
+  },
+
+  warning: function warning() {
+    this.log.apply(
+      this, [this.WARNING].concat(Array.prototype.slice.call(arguments)));
+  },
+
+  error: function error() {
+    this.log.apply(
+      this, [this.ERROR].concat(Array.prototype.slice.call(arguments)));
+  },
+
+  accessibleToString: function accessibleToString(aAccessible) {
+    let str = '[ defunct ]';
+    try {
+      str = '[ ' + gAccRetrieval.getStringRole(aAccessible.role) +
+        ' | ' + aAccessible.name + ' ]';
+    } catch (x) {
+    }
+
+    return str;
+  },
+
+  eventToString: function eventToString(aEvent) {
+    let str = gAccRetrieval.getStringEventType(aEvent.eventType);
+    if (aEvent.eventType == Ci.nsIAccessibleEvent.EVENT_STATE_CHANGE) {
+      let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
+      let stateStrings = (event.isExtraState()) ?
+        gAccRetrieval.getStringStates(0, event.state) :
+        gAccRetrieval.getStringStates(event.state, 0);
+      str += ' (' + stateStrings.item(0) + ')';
+    }
+
+    return str;
+  }
+};
--- a/accessible/src/jsat/VirtualCursorController.jsm
+++ b/accessible/src/jsat/VirtualCursorController.jsm
@@ -6,18 +6,18 @@
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 var EXPORTED_SYMBOLS = ['VirtualCursorController'];
 
+Cu.import('resource://gre/modules/accessibility/Utils.jsm');
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
-Cu.import('resource://gre/modules/Services.jsm');
 
 var gAccRetrieval = Cc['@mozilla.org/accessibleRetrieval;1'].
   getService(Ci.nsIAccessibleRetrieval);
 
 var TraversalRules = {
   Simple: {
     getMatchRoles: function SimpleTraversalRule_getmatchRoles(aRules) {
       aRules.value = this._matchRoles;
@@ -414,27 +414,19 @@ var VirtualCursorController = {
     this.chromeWin = aWindow;
     this.chromeWin.document.addEventListener('keypress', this, true);
   },
 
   detach: function detach() {
     this.chromeWin.document.removeEventListener('keypress', this, true);
   },
 
-  _getBrowserApp: function _getBrowserApp() {
-    switch (Services.appinfo.OS) {
-      case 'Android':
-        return this.chromeWin.BrowserApp;
-      default:
-        return this.chromeWin.gBrowser;
-    }
-  },
-
   handleEvent: function handleEvent(aEvent) {
-    let document = this._getBrowserApp().selectedBrowser.contentDocument;
+    let document = Utils.getBrowserApp(this.chromeWin).
+      selectedBrowser.contentDocument;
     let target = aEvent.target;
 
     switch (aEvent.keyCode) {
       case 0:
         // an alphanumeric key was pressed, handle it separately.
         // If it was pressed with either alt or ctrl, just pass through.
         // If it was pressed with meta, pass the key on without the meta.
         if (this._isEditableText(target) ||
@@ -482,17 +474,17 @@ var VirtualCursorController = {
         if (this._isEditableText(target) == this.MULTI_LINE_EDITABLE) {
           if (target.selectionEnd != 0)
             // Don't blur content if caret is not at start of text area.
             return;
           else
             target.blur();
         }
 
-        if (Services.appinfo.OS == 'Android')
+        if (Utils.OS == 'Android')
           // Return focus to native Android browser chrome.
           Cc['@mozilla.org/android/bridge;1'].
             getService(Ci.nsIAndroidBridge).handleGeckoMessage(
               JSON.stringify({ gecko: { type: 'ToggleChrome:Focus' } }));
         break;
       case aEvent.DOM_VK_RETURN:
       case aEvent.DOM_VK_ENTER:
         if (this._isEditableText(target))