Bug 756287 - Introduce PresenterContext in AccessFu. r=davidb
authorEitan Isaacson <eitan@monotonous.org>
Fri, 18 May 2012 11:56:38 -0700
changeset 94405 ae944ea53b59d417e7f2b11a7514c2b1d7eaa46b
parent 94404 87e6d47a1657f53b0386a8bbbd9542b3bf8e00a6
child 94406 aef74c3cd5a1a9dcba989e950210b0952ca6d215
push id9592
push usereisaacson@mozilla.com
push dateFri, 18 May 2012 18:56:47 +0000
treeherdermozilla-inbound@aef74c3cd5a1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdavidb
bugs756287
milestone15.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 756287 - Introduce PresenterContext in AccessFu. r=davidb
accessible/src/jsat/AccessFu.jsm
accessible/src/jsat/Presenters.jsm
--- a/accessible/src/jsat/AccessFu.jsm
+++ b/accessible/src/jsat/AccessFu.jsm
@@ -210,22 +210,20 @@ var AccessFu = {
             // focus.
             let sel = doc.getSelection();
             sel.collapse(position.DOMNode, 0);
             Cc["@mozilla.org/focus-manager;1"]
               .getService(Ci.nsIFocusManager).moveFocus(
                 doc.defaultView, null, Ci.nsIFocusManager.MOVEFOCUS_CARET, 0);
           }
 
-          let newContext = this.getNewContext(event.oldAccessible,
-                                              pivot.position);
+          let presenterContext = new PresenterContext(pivot.position,
+                                                      event.oldAccessible);
           this.presenters.forEach(
-            function(p) {
-              p.pivotChanged(pivot.position, newContext);
-            });
+            function(p) { p.pivotChanged(presenterContext); });
           break;
         }
       case Ci.nsIAccessibleEvent.EVENT_STATE_CHANGE:
         {
           let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
           if (event.state == Ci.nsIAccessibleStates.STATE_CHECKED &&
               !(event.isExtraState())) {
             this.presenters.forEach(
@@ -308,18 +306,23 @@ var AccessFu = {
             }
           );
           break;
         }
       case Ci.nsIAccessibleEvent.EVENT_FOCUS:
         {
           if (this._isBrowserDoc(aEvent.accessible)) {
             // The document recieved focus, call tabSelected to present current tab.
+            let docContext = new PresenterContext(aEvent.accessible, null);
+            let cursorable = aEvent.accessible.
+              QueryInterface(Ci.nsIAccessibleCursorable);
+            let vcContext = new PresenterContext(
+              (cursorable) ? cursorable.virtualCursor.position : null, null);
             this.presenters.forEach(
-              function(p) { p.tabSelected(aEvent.accessible); });
+              function(p) { p.tabSelected(docContext, vcContext); });
           }
           break;
         }
       case Ci.nsIAccessibleEvent.EVENT_TEXT_INSERTED:
       case Ci.nsIAccessibleEvent.EVENT_TEXT_REMOVED:
       {
         if (aEvent.isFromUserInput) {
           // XXX support live regions as well.
@@ -378,51 +381,16 @@ var AccessFu = {
   _isNotChromeDoc: function _isNotChromeDoc(aDocument) {
     let location = aDocument.DOMNode.location;
     if (!location)
       return false;
 
     return location.protocol != "about:";
   },
 
-  getNewContext: function getNewContext(aOldObject, aNewObject) {
-    let newLineage = [];
-    let oldLineage = [];
-
-    let parent = aNewObject;
-    while ((parent = parent.parent))
-      newLineage.push(parent);
-
-    if (aOldObject) {
-      parent = aOldObject;
-      while ((parent = parent.parent))
-        oldLineage.push(parent);
-    }
-
-//    newLineage.reverse();
-//    oldLineage.reverse();
-
-    let i = 0;
-    let newContext = [];
-
-    while (true) {
-      let newAncestor = newLineage.pop();
-      let oldAncestor = oldLineage.pop();
-
-      if (newAncestor == undefined)
-        break;
-
-      if (newAncestor != oldAncestor)
-        newContext.push(newAncestor);
-      i++;
-    }
-
-    return newContext;
-  },
-
   // A hash of documents that don't yet have an accessible tree.
   _pendingDocuments: {},
 
   // So we don't enable/disable twice
   _enabled: false,
 
   // Observing accessibility settings
   _observingSystemSettings: false
--- a/accessible/src/jsat/Presenters.jsm
+++ b/accessible/src/jsat/Presenters.jsm
@@ -9,17 +9,18 @@ const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import('resource://gre/modules/accessibility/UtteranceGenerator.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
 
 var EXPORTED_SYMBOLS = ['VisualPresenter',
                         'AndroidPresenter',
-                        'DummyAndroidPresenter'];
+                        'DummyAndroidPresenter',
+                        'PresenterContext'];
 
 /**
  * The interface for all presenter classes. A presenter could be, for example,
  * a speech output module, or a visual cursor indicator.
  */
 function Presenter() {}
 
 Presenter.prototype = {
@@ -31,21 +32,20 @@ Presenter.prototype = {
 
   /**
    * Detach function.
    */
   detach: function detach() {},
 
   /**
    * The virtual cursor's position changed.
-   * @param {nsIAccessible} aObject the new position.
-   * @param {nsIAccessible[]} aNewContext the ancestry of the new position that
-   *    is different from the old virtual cursor position.
+   * @param {PresenterContext} aContext the context object for the new pivot
+   *   position.
    */
-  pivotChanged: function pivotChanged(aObject, aNewContext) {},
+  pivotChanged: function pivotChanged(aContext) {},
 
   /**
    * An object's action has been invoked.
    * @param {nsIAccessible} aObject the object that has been invoked.
    * @param {string} aActionName the name of the action.
    */
   actionInvoked: function actionInvoked(aObject, aActionName) {},
 
@@ -73,21 +73,22 @@ Presenter.prototype = {
    *    state changed, or null if the tab has no associated document yet.
    * @param {string} aPageState the state name for the tab, valid states are:
    *    'newtab', 'loading', 'newdoc', 'loaded', 'stopped', and 'reload'.
    */
   tabStateChanged: function tabStateChanged(aDocObj, aPageState) {},
 
   /**
    * The current tab has changed.
-   * @param {nsIAccessible} aObject the document contained by the tab
-   *    accessible, or null if it is a new tab with no attached
-   *    document yet.
+   * @param {PresenterContext} aDocContext context object for tab's
+   *   document.
+   * @param {PresenterContext} aVCContext context object for tab's current
+   *   virtual cursor position.
    */
-  tabSelected: function tabSelected(aDocObj) {},
+  tabSelected: function tabSelected(aDocContext, aVCContext) {},
 
   /**
    * The viewport has changed, either a scroll, pan, zoom, or
    *    landscape/portrait toggle.
    */
   viewportChanged: function viewportChanged() {}
 };
 
@@ -134,44 +135,42 @@ VisualPresenter.prototype = {
     this.highlightBox = this.stylesheet = null;
   },
 
   viewportChanged: function VisualPresenter_viewportChanged() {
     if (this._currentObject)
       this._highlight(this._currentObject);
   },
 
-  pivotChanged: function VisualPresenter_pivotChanged(aObject, aNewContext) {
-    this._currentObject = aObject;
+  pivotChanged: function VisualPresenter_pivotChanged(aContext) {
+    this._currentObject = aContext.accessible;
 
-    if (!aObject) {
+    if (!aContext.accessible) {
       this._hide();
       return;
     }
 
     try {
-      aObject.scrollTo(Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE);
-      this._highlight(aObject);
+      aContext.accessible.scrollTo(
+        Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE);
+      this._highlight(aContext.accessible);
     } catch (e) {
       dump('Error getting bounds: ' + e);
       return;
     }
   },
 
-  tabSelected: function VisualPresenter_tabSelected(aDocObj) {
-    let vcPos = aDocObj ? aDocObj.QueryInterface(Ci.nsIAccessibleCursorable).
-      virtualCursor.position : null;
-
-    this.pivotChanged(vcPos);
+  tabSelected: function VisualPresenter_tabSelected(aDocContext, aVCContext) {
+    this.pivotChanged(aVCContext);
   },
 
   tabStateChanged: function VisualPresenter_tabStateChanged(aDocObj,
                                                             aPageState) {
     if (aPageState == 'newdoc')
-      this.pivotChanged(null);
+      this._hide();
   },
 
   // Internals
 
   _hide: function _hide() {
     this.highlightBox.style.display = 'none';
   },
 
@@ -229,24 +228,25 @@ AndroidPresenter.prototype = {
   // Android AccessibilityEvent type constants.
   ANDROID_VIEW_CLICKED: 0x01,
   ANDROID_VIEW_LONG_CLICKED: 0x02,
   ANDROID_VIEW_SELECTED: 0x04,
   ANDROID_VIEW_FOCUSED: 0x08,
   ANDROID_VIEW_TEXT_CHANGED: 0x10,
   ANDROID_WINDOW_STATE_CHANGED: 0x20,
 
-  pivotChanged: function AndroidPresenter_pivotChanged(aObject, aNewContext) {
+  pivotChanged: function AndroidPresenter_pivotChanged(aContext) {
     let output = [];
-    for (let i in aNewContext)
-      output.push.apply(output,
-                        UtteranceGenerator.genForObject(aNewContext[i]));
+    for (let i in aContext.newAncestry)
+      output.push.apply(
+        output, UtteranceGenerator.genForObject(aContext.newAncestry[i]));
 
     output.push.apply(output,
-                      UtteranceGenerator.genForObject(aObject, true));
+                      UtteranceGenerator.genForObject(aContext.accessible,
+                                                      true));
 
     this.sendMessageToJava({
       gecko: {
         type: 'Accessibility:Event',
         eventType: this.ANDROID_VIEW_FOCUSED,
         text: output
       }
     });
@@ -257,31 +257,19 @@ AndroidPresenter.prototype = {
       gecko: {
         type: 'Accessibility:Event',
         eventType: this.ANDROID_VIEW_CLICKED,
         text: UtteranceGenerator.genForAction(aObject, aActionName)
       }
     });
   },
 
-  tabSelected: function AndroidPresenter_tabSelected(aDocObj) {
+  tabSelected: function AndroidPresenter_tabSelected(aDocContext, aVCContext) {
     // Send a pivot change message with the full context utterance for this doc.
-    let vcDoc = aDocObj.QueryInterface(Ci.nsIAccessibleCursorable);
-    let context = [];
-
-    let parent = vcDoc.virtualCursor.position || aDocObj;
-    while ((parent = parent.parent)) {
-      context.push(parent);
-      if (parent == aDocObj)
-        break;
-    }
-
-    context.reverse();
-
-    this.pivotChanged(vcDoc.virtualCursor.position || aDocObj, context);
+    this.pivotChanged(aVCContext);
   },
 
   tabStateChanged: function AndroidPresenter_tabStateChanged(aDocObj,
                                                              aPageState) {
     let stateUtterance = UtteranceGenerator.
       genForTabStateChange(aDocObj, aPageState);
 
     if (!stateUtterance.length)
@@ -337,8 +325,67 @@ function DummyAndroidPresenter() {}
 
 DummyAndroidPresenter.prototype = {
   __proto__: AndroidPresenter.prototype,
 
   sendMessageToJava: function DummyAndroidPresenter_sendMessageToJava(aMsg) {
     dump(JSON.stringify(aMsg, null, 2) + '\n');
   }
 };
+
+/**
+ * PresenterContext: An object that generates and caches context information
+ * for a given accessible and its relationship with another accessible.
+ */
+function PresenterContext(aAccessible, aOldAccessible) {
+  this._accessible = aAccessible;
+  this._oldAccessible = aOldAccessible;
+}
+
+PresenterContext.prototype = {
+  get accessible() {
+    return this._accessible;
+  },
+
+  get oldAccessible() {
+    return this._oldAccessible;
+  },
+
+  /*
+   * This is a list of the accessible's ancestry up to the common ancestor
+   * of the accessible and the old accessible. It is useful for giving the
+   * user context as to where they are in the heirarchy.
+   */
+  get newAncestry() {
+    if (!this._newAncestry) {
+      let newLineage = [];
+      let oldLineage = [];
+
+      let parent = this._accessible;
+      while ((parent = parent.parent))
+        newLineage.push(parent);
+
+      if (this._oldAccessible) {
+        parent = this._oldAccessible;
+        while ((parent = parent.parent))
+          oldLineage.push(parent);
+      }
+
+      let i = 0;
+      this._newAncestry = [];
+
+      while (true) {
+        let newAncestor = newLineage.pop();
+        let oldAncestor = oldLineage.pop();
+
+        if (newAncestor == undefined)
+          break;
+
+        if (newAncestor != oldAncestor)
+          this._newAncestry.push(newAncestor);
+        i++;
+      }
+
+    }
+
+    return this._newAncestry;
+  }
+};