Bug 963298 - Add states to Constants and unify states and extended states. r=yzen
authorEitan Isaacson <eitan@monotonous.org>
Mon, 27 Jan 2014 16:35:13 -0800
changeset 181454 a16b224233d9e7241300e91e874a1d7bef4aa4c5
parent 181453 13bffcd29d309406509f0df401920b07ea4036d3
child 181455 4a1e002cb3c2ee6d93d271ecb750dca80c7fd2a8
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyzen
bugs963298
milestone29.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 963298 - Add states to Constants and unify states and extended states. r=yzen
accessible/src/jsat/Constants.jsm
accessible/src/jsat/EventManager.jsm
accessible/src/jsat/OutputGenerator.jsm
accessible/src/jsat/Presentation.jsm
accessible/src/jsat/TraversalRules.jsm
accessible/src/jsat/Utils.jsm
--- a/accessible/src/jsat/Constants.jsm
+++ b/accessible/src/jsat/Constants.jsm
@@ -1,39 +1,52 @@
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 
-this.EXPORTED_SYMBOLS = ['Roles', 'Events', 'Relations', 'Filters'];
+this.EXPORTED_SYMBOLS = ['Roles', 'Events', 'Relations', 'Filters', 'States'];
 
-function ConstantsMap (aObject, aPrefix) {
+function ConstantsMap (aObject, aPrefix, aMap = {}, aModifier = null) {
   let offset = aPrefix.length;
   for (var name in aObject) {
     if (name.indexOf(aPrefix) === 0) {
-      this[name.slice(offset)] = aObject[name];
+      aMap[name.slice(offset)] = aModifier ?
+        aModifier(aObject[name]) : aObject[name];
     }
   }
+
+  return aMap;
 }
 
 XPCOMUtils.defineLazyGetter(
   this, 'Roles',
   function() {
-    return new ConstantsMap(Ci.nsIAccessibleRole, 'ROLE_');
+    return ConstantsMap(Ci.nsIAccessibleRole, 'ROLE_');
   });
 
 XPCOMUtils.defineLazyGetter(
   this, 'Events',
   function() {
-    return new ConstantsMap(Ci.nsIAccessibleEvent, 'EVENT_');
+    return ConstantsMap(Ci.nsIAccessibleEvent, 'EVENT_');
   });
 
 XPCOMUtils.defineLazyGetter(
   this, 'Relations',
   function() {
-    return new ConstantsMap(Ci.nsIAccessibleRelation, 'RELATION_');
+    return ConstantsMap(Ci.nsIAccessibleRelation, 'RELATION_');
   });
 
 XPCOMUtils.defineLazyGetter(
   this, 'Filters',
   function() {
-    return new ConstantsMap(Ci.nsIAccessibleTraversalRule, 'FILTER_');
+    return ConstantsMap(Ci.nsIAccessibleTraversalRule, 'FILTER_');
   });
+
+XPCOMUtils.defineLazyGetter(
+  this, 'States',
+  function() {
+    let statesMap = ConstantsMap(Ci.nsIAccessibleStates, 'STATE_', {},
+                                 (val) => { return { base: val, extended: 0 }; });
+    ConstantsMap(Ci.nsIAccessibleStates, 'EXT_STATE_', statesMap,
+                 (val) => { return { base: 0, extended: val }; });
+    return statesMap;
+  });
--- a/accessible/src/jsat/EventManager.jsm
+++ b/accessible/src/jsat/EventManager.jsm
@@ -19,16 +19,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, 'Presentation',
   'resource://gre/modules/accessibility/Presentation.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'TraversalRules',
   'resource://gre/modules/accessibility/TraversalRules.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
   'resource://gre/modules/accessibility/Constants.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Events',
   'resource://gre/modules/accessibility/Constants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'States',
+  'resource://gre/modules/accessibility/Constants.jsm');
 
 this.EXPORTED_SYMBOLS = ['EventManager'];
 
 this.EventManager = function EventManager(aContentScope) {
   this.contentScope = aContentScope;
   this.addEventListener = this.contentScope.addEventListener.bind(
     this.contentScope);
   this.removeEventListener = this.contentScope.removeEventListener.bind(
@@ -142,23 +144,23 @@ this.EventManager.prototype = {
           Presentation.pivotChanged(position, event.oldAccessible, reason,
                                     pivot.startOffset, pivot.endOffset));
 
         break;
       }
       case Events.STATE_CHANGE:
       {
         let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
-        if (event.state == Ci.nsIAccessibleStates.STATE_CHECKED &&
-            !(event.isExtraState)) {
+        let state = Utils.getState(event);
+        if (state.contains(States.CHECKED)) {
           this.present(
             Presentation.
               actionInvoked(aEvent.accessible,
                             event.isEnabled ? 'check' : 'uncheck'));
-        } else if (event.state == Ci.nsIAccessibleStates.STATE_SELECTED) {
+        } else if (state.contains(States.SELECTED)) {
           this.present(
             Presentation.
               actionInvoked(aEvent.accessible,
                             event.isEnabled ? 'select' : 'unselect'));
         }
         break;
       }
       case Events.SCROLLING_START:
@@ -171,20 +173,20 @@ this.EventManager.prototype = {
       {
         let acc = aEvent.accessible;
         let characterCount = acc.
           QueryInterface(Ci.nsIAccessibleText).characterCount;
         let caretOffset = aEvent.
           QueryInterface(Ci.nsIAccessibleCaretMoveEvent).caretOffset;
 
         // Update editing state, both for presenter and other things
-        let [,extState] = Utils.getStates(acc);
+        let state = Utils.getState(acc);
         let editState = {
-          editing: !!(extState & Ci.nsIAccessibleStates.EXT_STATE_EDITABLE),
-          multiline: !!(extState & Ci.nsIAccessibleStates.EXT_STATE_MULTI_LINE),
+          editing: state.contains(States.EDITABLE),
+          multiline: state.contains(States.MULTI_LINE),
           atStart: caretOffset == 0,
           atEnd: caretOffset == characterCount
         };
 
         // Not interesting
         if (!editState.editing && editState.editing == this.editState.editing)
           break;
 
--- a/accessible/src/jsat/OutputGenerator.jsm
+++ b/accessible/src/jsat/OutputGenerator.jsm
@@ -24,16 +24,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, 'PrefCache',
   'resource://gre/modules/accessibility/Utils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
   'resource://gre/modules/accessibility/Utils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'PluralForm',
   'resource://gre/modules/PluralForm.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
   'resource://gre/modules/accessibility/Constants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'States',
+  'resource://gre/modules/accessibility/Constants.jsm');
 
 var gStringBundle = Cc['@mozilla.org/intl/stringbundle;1'].
   getService(Ci.nsIStringBundleService).
   createBundle('chrome://global/locale/AccessFu.properties');
 
 this.EXPORTED_SYMBOLS = ['UtteranceGenerator', 'BrailleGenerator'];
 
 this.OutputGenerator = {
@@ -92,36 +94,33 @@ this.OutputGenerator = {
   /**
    * Generates output for an object.
    * @param {nsIAccessible} aAccessible accessible object to generate output
    *    for.
    * @param {PivotContext} aContext object that generates and caches
    *    context information for a given accessible and its relationship with
    *    another accessible.
    * @return {Array} Two string array. The first string describes the object
-   *    and its states. The second string is the object's name. Whether the
+   *    and its state. The second string is the object's name. Whether the
    *    object's description or it's role is included is determined by
    *    {@link roleRuleMap}.
    */
   genForObject: function genForObject(aAccessible, aContext) {
     let roleString = Utils.AccRetrieval.getStringRole(aAccessible.role);
     let func = this.objectOutputFunctions[
       OutputGenerator._getOutputName(roleString)] ||
       this.objectOutputFunctions.defaultFunc;
 
     let flags = this.roleRuleMap[roleString] || 0;
 
     if (aAccessible.childCount == 0)
       flags |= INCLUDE_NAME;
 
-    let state = {};
-    let extState = {};
-    aAccessible.getState(state, extState);
-    let states = {base: state.value, ext: extState.value};
-    return func.apply(this, [aAccessible, roleString, states, flags, aContext]);
+    return func.apply(this, [aAccessible, roleString,
+                             Utils.getState(aAccessible), flags, aContext]);
   },
 
   /**
    * Generates output for an action performed.
    * @param {nsIAccessible} aAccessible accessible object that the action was
    *    invoked in.
    * @param {string} aActionName the name of the action, one of the keys in
    *    {@link gActionMap}.
@@ -235,17 +234,17 @@ this.OutputGenerator = {
   },
 
   _getOutputName: function _getOutputName(aName) {
     return aName.replace(' ', '');
   },
 
   _getLocalizedRole: function _getLocalizedRole(aRoleStr) {},
 
-  _getLocalizedStates: function _getLocalizedStates(aStates) {},
+  _getLocalizedState: function _getLocalizedState(aState) {},
 
   _getPluralFormString: function _getPluralFormString(aString, aCount) {
     let str = gStringBundle.GetStringFromName(this._getOutputName(aString));
     str = PluralForm.get(aCount, str);
     return str.replace('#1', aCount);
   },
 
   roleRuleMap: {
@@ -317,21 +316,21 @@ this.OutputGenerator = {
     'definition': NAME_FROM_SUBTREE_RULE,
     'key': NAME_FROM_SUBTREE_RULE,
     'image map': INCLUDE_DESC,
     'option': INCLUDE_DESC,
     'listbox': INCLUDE_DESC,
     'definitionlist': INCLUDE_DESC | INCLUDE_NAME},
 
   objectOutputFunctions: {
-    _generateBaseOutput: function _generateBaseOutput(aAccessible, aRoleStr, aStates, aFlags) {
+    _generateBaseOutput: function _generateBaseOutput(aAccessible, aRoleStr, aState, aFlags) {
       let output = [];
 
       if (aFlags & INCLUDE_DESC) {
-        let desc = this._getLocalizedStates(aStates);
+        let desc = this._getLocalizedState(aState);
         let roleStr = this._getLocalizedRole(aRoleStr);
         if (roleStr) {
           this._addType(desc, aAccessible, aRoleStr);
           desc.push(roleStr);
         }
         output.push(desc.join(' '));
       }
 
@@ -344,53 +343,52 @@ this.OutputGenerator = {
       }
 
       this._addName(output, aAccessible, aFlags);
       this._addLandmark(output, aAccessible);
 
       return output;
     },
 
-    label: function label(aAccessible, aRoleStr, aStates, aFlags, aContext) {
+    label: function label(aAccessible, aRoleStr, aState, aFlags, aContext) {
       if (aContext.isNestedControl ||
           aContext.accessible == Utils.getEmbeddedControl(aAccessible)) {
         // If we are on a nested control, or a nesting label,
         // we don't need the context.
         return [];
       }
 
       return this.objectOutputFunctions.defaultFunc.apply(this, arguments);
     },
 
-    entry: function entry(aAccessible, aRoleStr, aStates, aFlags) {
-      let rolestr = (aStates.ext & Ci.nsIAccessibleStates.EXT_STATE_MULTI_LINE) ?
-            'textarea' : 'entry';
+    entry: function entry(aAccessible, aRoleStr, aState, aFlags) {
+      let rolestr = aState.contains(States.MULTI_LINE) ? 'textarea' : 'entry';
       return this.objectOutputFunctions.defaultFunc.apply(
-        this, [aAccessible, rolestr, aStates, aFlags]);
+        this, [aAccessible, rolestr, aState, aFlags]);
     },
 
-    pagetab: function pagetab(aAccessible, aRoleStr, aStates, aFlags) {
+    pagetab: function pagetab(aAccessible, aRoleStr, aState, aFlags) {
       let localizedRole = this._getLocalizedRole(aRoleStr);
       let itemno = {};
       let itemof = {};
       aAccessible.groupPosition({}, itemof, itemno);
       let output = [];
-      let desc = this._getLocalizedStates(aStates);
+      let desc = this._getLocalizedState(aState);
       desc.push(
         gStringBundle.formatStringFromName(
           'objItemOf', [localizedRole, itemno.value, itemof.value], 3));
       output.push(desc.join(' '));
 
       this._addName(output, aAccessible, aFlags);
       this._addLandmark(output, aAccessible);
 
       return output;
     },
 
-    table: function table(aAccessible, aRoleStr, aStates, aFlags) {
+    table: function table(aAccessible, aRoleStr, aState, aFlags) {
       let output = [];
       let table;
       try {
         table = aAccessible.QueryInterface(Ci.nsIAccessibleTable);
       } catch (x) {
         Logger.logException(x);
         return output;
       } finally {
@@ -494,68 +492,68 @@ this.UtteranceGenerator = {
     return [gStringBundle.GetStringFromName(
               aIsEditing ? 'editingMode' : 'navigationMode')];
   },
 
   objectOutputFunctions: {
 
     __proto__: OutputGenerator.objectOutputFunctions,
 
-    defaultFunc: function defaultFunc(aAccessible, aRoleStr, aStates, aFlags) {
+    defaultFunc: function defaultFunc(aAccessible, aRoleStr, aState, aFlags) {
       return this.objectOutputFunctions._generateBaseOutput.apply(this, arguments);
     },
 
-    heading: function heading(aAccessible, aRoleStr, aStates, aFlags) {
+    heading: function heading(aAccessible, aRoleStr, aState, aFlags) {
       let level = {};
       aAccessible.groupPosition(level, {}, {});
       let utterance =
         [gStringBundle.formatStringFromName('headingLevel', [level.value], 1)];
 
       this._addName(utterance, aAccessible, aFlags);
       this._addLandmark(utterance, aAccessible);
 
       return utterance;
     },
 
-    listitem: function listitem(aAccessible, aRoleStr, aStates, aFlags) {
+    listitem: function listitem(aAccessible, aRoleStr, aState, aFlags) {
       let itemno = {};
       let itemof = {};
       aAccessible.groupPosition({}, itemof, itemno);
       let utterance = [];
       if (itemno.value == 1) // Start of list
         utterance.push(gStringBundle.GetStringFromName('listStart'));
       else if (itemno.value == itemof.value) // last item
         utterance.push(gStringBundle.GetStringFromName('listEnd'));
 
       this._addName(utterance, aAccessible, aFlags);
       this._addLandmark(utterance, aAccessible);
 
       return utterance;
     },
 
-    list: function list(aAccessible, aRoleStr, aStates, aFlags) {
+    list: function list(aAccessible, aRoleStr, aState, aFlags) {
       return this._getListUtterance
         (aAccessible, aRoleStr, aFlags, aAccessible.childCount);
     },
 
-    definitionlist: function definitionlist(aAccessible, aRoleStr, aStates, aFlags) {
+    definitionlist: function definitionlist(aAccessible, aRoleStr, aState, aFlags) {
       return this._getListUtterance
         (aAccessible, aRoleStr, aFlags, aAccessible.childCount / 2);
     },
 
-    application: function application(aAccessible, aRoleStr, aStates, aFlags) {
+    application: function application(aAccessible, aRoleStr, aState, aFlags) {
       // Don't utter location of applications, it gets tiring.
       if (aAccessible.name != aAccessible.DOMNode.location)
         return this.objectOutputFunctions.defaultFunc.apply(this,
-          [aAccessible, aRoleStr, aStates, aFlags]);
+          [aAccessible, aRoleStr, aState, aFlags]);
 
       return [];
     },
 
-    cell: function cell(aAccessible, aRoleStr, aStates, aFlags, aContext) {
+    cell: function cell(aAccessible, aRoleStr, aState, aFlags, aContext) {
       let utterance = [];
       let cell = aContext.getCellInfo(aAccessible);
       if (cell) {
         let desc = [];
         let addCellChanged = function addCellChanged(aDesc, aChanged, aString, aIndex) {
           if (aChanged) {
             aDesc.push(gStringBundle.formatStringFromName(aString,
               [aIndex + 1], 1));
@@ -607,52 +605,52 @@ this.UtteranceGenerator = {
   _getLocalizedRole: function _getLocalizedRole(aRoleStr) {
     try {
       return gStringBundle.GetStringFromName(this._getOutputName(aRoleStr));
     } catch (x) {
       return '';
     }
   },
 
-  _getLocalizedStates: function _getLocalizedStates(aStates) {
+  _getLocalizedState: function _getLocalizedState(aState) {
     let stateUtterances = [];
 
-    if (aStates.base & Ci.nsIAccessibleStates.STATE_UNAVAILABLE) {
+    if (aState.contains(States.UNAVAILABLE)) {
       stateUtterances.push(gStringBundle.GetStringFromName('stateUnavailable'));
     }
 
     // Don't utter this in Jelly Bean, we let TalkBack do it for us there.
     // This is because we expose the checked information on the node itself.
     // XXX: this means the checked state is always appended to the end, regardless
     // of the utterance ordering preference.
-    if (Utils.AndroidSdkVersion < 16 && aStates.base & Ci.nsIAccessibleStates.STATE_CHECKABLE) {
-      let stateStr = (aStates.base & Ci.nsIAccessibleStates.STATE_CHECKED) ?
+    if (Utils.AndroidSdkVersion < 16 && aState.contains(States.CHECKABLE)) {
+      let statetr = aState.contains(States.CHECKED) ?
         'stateChecked' : 'stateNotChecked';
-      stateUtterances.push(gStringBundle.GetStringFromName(stateStr));
+      stateUtterances.push(gStringBundle.GetStringFromName(statetr));
     }
 
-    if (aStates.ext & Ci.nsIAccessibleStates.EXT_STATE_EXPANDABLE) {
-      let stateStr = (aStates.base & Ci.nsIAccessibleStates.STATE_EXPANDED) ?
+    if (aState.contains(States.EXPANDABLE)) {
+      let statetr = aState.contains(States.EXPANDED) ?
         'stateExpanded' : 'stateCollapsed';
-      stateUtterances.push(gStringBundle.GetStringFromName(stateStr));
+      stateUtterances.push(gStringBundle.GetStringFromName(statetr));
     }
 
-    if (aStates.base & Ci.nsIAccessibleStates.STATE_REQUIRED) {
+    if (aState.contains(States.REQUIRED)) {
       stateUtterances.push(gStringBundle.GetStringFromName('stateRequired'));
     }
 
-    if (aStates.base & Ci.nsIAccessibleStates.STATE_TRAVERSED) {
+    if (aState.contains(States.TRAVERSED)) {
       stateUtterances.push(gStringBundle.GetStringFromName('stateTraversed'));
     }
 
-    if (aStates.base & Ci.nsIAccessibleStates.STATE_HASPOPUP) {
+    if (aState.contains(States.HASPOPUP)) {
       stateUtterances.push(gStringBundle.GetStringFromName('stateHasPopup'));
     }
 
-    if (aStates.base & Ci.nsIAccessibleStates.STATE_SELECTED) {
+    if (aState.contains(States.SELECTED)) {
       stateUtterances.push(gStringBundle.GetStringFromName('stateSelected'));
     }
 
     return stateUtterances;
   },
 
   _getListUtterance: function _getListUtterance(aAccessible, aRoleStr, aFlags, aItemCount) {
     let desc = [];
@@ -712,30 +710,30 @@ this.BrailleGenerator = {
 
     return output;
   },
 
   objectOutputFunctions: {
 
     __proto__: OutputGenerator.objectOutputFunctions,
 
-    defaultFunc: function defaultFunc(aAccessible, aRoleStr, aStates, aFlags) {
+    defaultFunc: function defaultFunc(aAccessible, aRoleStr, aState, aFlags) {
       return this.objectOutputFunctions._generateBaseOutput.apply(this, arguments);
     },
 
-    listitem: function listitem(aAccessible, aRoleStr, aStates, aFlags) {
+    listitem: function listitem(aAccessible, aRoleStr, aState, aFlags) {
       let braille = [];
 
       this._addName(braille, aAccessible, aFlags);
       this._addLandmark(braille, aAccessible);
 
       return braille;
     },
 
-    cell: function cell(aAccessible, aRoleStr, aStates, aFlags, aContext) {
+    cell: function cell(aAccessible, aRoleStr, aState, aFlags, aContext) {
       let braille = [];
       let cell = aContext.getCellInfo(aAccessible);
       if (cell) {
         let desc = [];
         let addHeaders = function addHeaders(aDesc, aHeaders) {
           if (aHeaders.length > 0) {
             aDesc.push.apply(aDesc, aHeaders);
           }
@@ -758,47 +756,47 @@ this.BrailleGenerator = {
     columnheader: function columnheader() {
       return this.objectOutputFunctions.cell.apply(this, arguments);
     },
 
     rowheader: function rowheader() {
       return this.objectOutputFunctions.cell.apply(this, arguments);
     },
 
-    statictext: function statictext(aAccessible, aRoleStr, aStates, aFlags) {
+    statictext: function statictext(aAccessible, aRoleStr, aState, aFlags) {
       // Since we customize the list bullet's output, we add the static
       // text from the first node in each listitem, so skip it here.
       if (aAccessible.parent.role == Roles.LISTITEM) {
         return [];
       }
 
       return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
     },
 
-    _useStateNotRole: function _useStateNotRole(aAccessible, aRoleStr, aStates, aFlags) {
+    _useStateNotRole: function _useStateNotRole(aAccessible, aRoleStr, aState, aFlags) {
       let braille = [];
 
-      let desc = this._getLocalizedStates(aStates);
+      let desc = this._getLocalizedState(aState);
       braille.push(desc.join(' '));
 
       this._addName(braille, aAccessible, aFlags);
       this._addLandmark(braille, aAccessible);
 
       return braille;
     },
 
-    checkbutton: function checkbutton(aAccessible, aRoleStr, aStates, aFlags) {
+    checkbutton: function checkbutton(aAccessible, aRoleStr, aState, aFlags) {
       return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
     },
 
-    radiobutton: function radiobutton(aAccessible, aRoleStr, aStates, aFlags) {
+    radiobutton: function radiobutton(aAccessible, aRoleStr, aState, aFlags) {
       return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
     },
 
-    togglebutton: function radiobutton(aAccessible, aRoleStr, aStates, aFlags) {
+    togglebutton: function radiobutton(aAccessible, aRoleStr, aState, aFlags) {
       return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
     }
   },
 
   _getContextStart: function _getContextStart(aContext) {
     if (aContext.accessible.parent.role == Roles.LINK) {
       return [aContext.accessible.parent];
     }
@@ -818,32 +816,32 @@ this.BrailleGenerator = {
         return gStringBundle.GetStringFromName(
           OutputGenerator._getOutputName(aRoleStr));
       } catch (y) {
         return '';
       }
     }
   },
 
-  _getLocalizedStates: function _getLocalizedStates(aStates) {
+  _getLocalizedState: function _getLocalizedState(aState) {
     let stateBraille = [];
 
     let getCheckedState = function getCheckedState() {
       let resultMarker = [];
-      let state = aStates.base;
-      let fill = !!(state & Ci.nsIAccessibleStates.STATE_CHECKED) ||
-                 !!(state & Ci.nsIAccessibleStates.STATE_PRESSED);
+      let state = aState;
+      let fill = state.contains(States.CHECKED) ||
+        state.contains(States.PRESSED);
 
       resultMarker.push('(');
       resultMarker.push(fill ? 'x' : ' ');
       resultMarker.push(')');
 
       return resultMarker.join('');
     };
 
-    if (aStates.base & Ci.nsIAccessibleStates.STATE_CHECKABLE) {
+    if (aState.contains(States.CHECKABLE)) {
       stateBraille.push(getCheckedState());
     }
 
     return stateBraille;
   }
 
 };
--- a/accessible/src/jsat/Presentation.jsm
+++ b/accessible/src/jsat/Presentation.jsm
@@ -17,16 +17,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, 'PivotContext',
   'resource://gre/modules/accessibility/Utils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'UtteranceGenerator',
   'resource://gre/modules/accessibility/OutputGenerator.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'BrailleGenerator',
   'resource://gre/modules/accessibility/OutputGenerator.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
   'resource://gre/modules/accessibility/Constants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'States',
+  'resource://gre/modules/accessibility/Constants.jsm');
 
 this.EXPORTED_SYMBOLS = ['Presentation'];
 
 /**
  * The interface for all presenter classes. A presenter could be, for example,
  * a speech output module, or a visual cursor indicator.
  */
 function Presenter() {}
@@ -279,49 +281,47 @@ AndroidPresenter.prototype = {
         androidEvents.push({
           eventType: this.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
           text: [adjustedText.text],
           fromIndex: adjustedText.startOffset,
           toIndex: adjustedText.endOffset
         });
       }
     } else {
-      let state = Utils.getStates(aContext.accessible)[0];
+      let state = Utils.getState(aContext.accessible);
       androidEvents.push({eventType: (isExploreByTouch) ?
                            this.ANDROID_VIEW_HOVER_ENTER : focusEventType,
                          text: UtteranceGenerator.genForContext(aContext).output,
                          bounds: aContext.bounds,
                          clickable: aContext.accessible.actionCount > 0,
-                         checkable: !!(state &
-                                       Ci.nsIAccessibleStates.STATE_CHECKABLE),
-                         checked: !!(state &
-                                     Ci.nsIAccessibleStates.STATE_CHECKED),
+                         checkable: state.contains(States.CHECKABLE),
+                         checked: state.contains(States.CHECKED),
                          brailleOutput: brailleOutput});
     }
 
 
     return {
       type: this.type,
       details: androidEvents
     };
   },
 
   actionInvoked: function AndroidPresenter_actionInvoked(aObject, aActionName) {
-    let state = Utils.getStates(aObject)[0];
+    let state = Utils.getState(aObject);
 
     // Checkable objects will have a state changed event we will use instead.
-    if (state & Ci.nsIAccessibleStates.STATE_CHECKABLE)
+    if (state.contains(States.CHECKABLE))
       return null;
 
     return {
       type: this.type,
       details: [{
         eventType: this.ANDROID_VIEW_CLICKED,
         text: UtteranceGenerator.genForAction(aObject, aActionName),
-        checked: !!(state & Ci.nsIAccessibleStates.STATE_CHECKED)
+        checked: state.contains(States.CHECKED)
       }]
     };
   },
 
   tabSelected: function AndroidPresenter_tabSelected(aDocContext, aVCContext) {
     // Send a pivot change message with the full context utterance for this doc.
     return this.pivotChanged(aVCContext, Ci.nsIAccessiblePivot.REASON_NONE);
   },
--- a/accessible/src/jsat/TraversalRules.jsm
+++ b/accessible/src/jsat/TraversalRules.jsm
@@ -12,16 +12,18 @@ const Cr = Components.results;
 this.EXPORTED_SYMBOLS = ['TraversalRules'];
 
 Cu.import('resource://gre/modules/accessibility/Utils.jsm');
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
   'resource://gre/modules/accessibility/Constants.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Filters',
   'resource://gre/modules/accessibility/Constants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'States',
+  'resource://gre/modules/accessibility/Constants.jsm');
 
 let gSkipEmptyImages = new PrefCache('accessibility.accessfu.skip_empty_images');
 
 function BaseTraversalRule(aRoles, aMatchFunc) {
   this._explicitMatchRoles = new Set(aRoles);
   this._matchRoles = aRoles;
   if (aRoles.indexOf(Roles.LABEL) < 0) {
     this._matchRoles.push(Roles.LABEL);
@@ -148,20 +150,17 @@ this.TraversalRules = {
     }
   ),
 
   Anchor: new BaseTraversalRule(
     [Roles.LINK],
     function Anchor_match(aAccessible)
     {
       // We want to ignore links, only focus named anchors.
-      let state = {};
-      let extraState = {};
-      aAccessible.getState(state, extraState);
-      if (state.value & Ci.nsIAccessibleStates.STATE_LINKED) {
+      if (Utils.getState(aAccessible).contains(States.LINKED)) {
         return Filters.IGNORE;
       } else {
         return Filters.MATCH;
       }
     }),
 
   Button: new BaseTraversalRule(
     [Roles.PUSHBUTTON,
@@ -216,20 +215,17 @@ this.TraversalRules = {
     [Roles.LISTITEM,
      Roles.TERM]),
 
   Link: new BaseTraversalRule(
     [Roles.LINK],
     function Link_match(aAccessible)
     {
       // We want to ignore anchors, only focus real links.
-      let state = {};
-      let extraState = {};
-      aAccessible.getState(state, extraState);
-      if (state.value & Ci.nsIAccessibleStates.STATE_LINKED) {
+      if (Utils.getState(aAccessible).contains(States.LINKED)) {
         return Filters.MATCH;
       } else {
         return Filters.IGNORE;
       }
     }),
 
   List: new BaseTraversalRule(
     [Roles.LIST,
--- a/accessible/src/jsat/Utils.jsm
+++ b/accessible/src/jsat/Utils.jsm
@@ -14,16 +14,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, 'Rect',
   'resource://gre/modules/Geometry.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
   'resource://gre/modules/accessibility/Constants.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Events',
   'resource://gre/modules/accessibility/Constants.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Relations',
   'resource://gre/modules/accessibility/Constants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'States',
+  'resource://gre/modules/accessibility/Constants.jsm');
 
 this.EXPORTED_SYMBOLS = ['Utils', 'Logger', 'PivotContext', 'PrefCache', 'SettingCache'];
 
 this.Utils = {
   _buildAppMap: {
     '{3c2e2abc-06d4-11e1-ac3b-374f68613e61}': 'b2g',
     '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': 'browser',
     '{aa3c5121-dab2-40e2-81ca-7ea25febc110}': 'mobile/android',
@@ -181,24 +183,46 @@ this.Utils = {
     switch (this.MozBuildApp) {
       case 'mobile/android':
         return aWindow.BrowserApp.selectedTab.getViewport();
       default:
         return null;
     }
   },
 
-  getStates: function getStates(aAccessible) {
-    if (!aAccessible)
-      return [0, 0];
+  getState: function getState(aAccessibleOrEvent) {
+    function State(aBase, aExtended) {
+      this.base = aBase;
+      this.extended = aExtended;
+
+      this.contains = (other) => {
+        return !!(this.base & other.base || this.extended & other.extended);
+      };
 
-    let state = {};
-    let extState = {};
-    aAccessible.getState(state, extState);
-    return [state.value, extState.value];
+      this.toString = () => {
+        let stateStrings = Utils.AccRetrieval.
+          getStringStates(this.base, this.extended);
+        let statesArray = new Array(stateStrings.length);
+        for (let i = 0; i < statesArray.length; i++) {
+          statesArray[i] = stateStrings.item(i);
+        }
+        return '[' + statesArray.join(', ') + ']';
+      };
+    }
+
+    if (aAccessibleOrEvent instanceof Ci.nsIAccessibleStateChangeEvent) {
+      return new State(
+        aAccessibleOrEvent.isExtraState ? 0 : aAccessibleOrEvent.state,
+        aAccessibleOrEvent.isExtraState ? aAccessibleOrEvent.state : 0);
+    } else {
+      let state = {};
+      let extState = {};
+      aAccessibleOrEvent.getState(state, extState);
+      return new State(state.value, extState.value);
+    }
   },
 
   getAttributes: function getAttributes(aAccessible) {
     let attributes = {};
 
     if (aAccessible && aAccessible.attributes) {
       let attributesEnum = aAccessible.attributes.enumerate();
 
@@ -246,21 +270,18 @@ this.Utils = {
   },
 
   isAliveAndVisible: function isAliveAndVisible(aAccessible) {
     if (!aAccessible) {
       return false;
     }
 
     try {
-      let extstate = {};
-      let state = {};
-      aAccessible.getState(state, extstate);
-      if (extstate.value & Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT ||
-          state.value & Ci.nsIAccessibleStates.STATE_INVISIBLE ||
+      let state = this.getState(aAccessible);
+      if (state.contains(States.DEFUNCT) || state.contains(States.INVISIBLE) ||
           Utils.inHiddenSubtree(aAccessible)) {
         return false;
       }
     } catch (x) {
       return false;
     }
 
     return true;
@@ -404,22 +425,17 @@ this.Logger = {
         Utils.AccRetrieval.getStringStates(event.state, 0);
       str += ' (' + stateStrings.item(0) + ')';
     }
 
     return str;
   },
 
   statesToString: function statesToString(aAccessible) {
-    let [state, extState] = Utils.getStates(aAccessible);
-    let stringArray = [];
-    let stateStrings = Utils.AccRetrieval.getStringStates(state, extState);
-    for (var i=0; i < stateStrings.length; i++)
-      stringArray.push(stateStrings.item(i));
-    return stringArray.join(' ');
+    return Utils.getState(aAccessible).toString();
   },
 
   dumpTree: function dumpTree(aLogLevel, aRootAccessible) {
     if (aLogLevel < this.logLevel)
       return;
 
     this._dumpTreeInternal(aLogLevel, aRootAccessible, 0);
   },
@@ -582,18 +598,17 @@ PivotContext.prototype = {
       return;
     }
     let child = aAccessible.firstChild;
     while (child) {
       let include;
       if (this._includeInvisible) {
         include = true;
       } else {
-        let [state,] = Utils.getStates(child);
-        include = !(state & Ci.nsIAccessibleStates.STATE_INVISIBLE);
+        include = !(Utils.getState(child).contains(States.INVISIBLE));
       }
       if (include) {
         if (aPreorder) {
           yield child;
           [yield node for (node of this._traverse(child, aPreorder, aStop))];
         } else {
           [yield node for (node of this._traverse(child, aPreorder, aStop))];
           yield child;
@@ -711,19 +726,17 @@ PivotContext.prototype = {
       this._bounds = Utils.getBounds(this.accessibleForBounds);
     }
 
     return this._bounds.clone();
   },
 
   _isDefunct: function _isDefunct(aAccessible) {
     try {
-      let extstate = {};
-      aAccessible.getState({}, extstate);
-      return !!(extstate.value & Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT);
+      return Utils.getState(aAccessible).contains(States.DEFUNCT);
     } catch (x) {
       return true;
     }
   }
 };
 
 this.PrefCache = function PrefCache(aName, aCallback, aRunCallbackNow) {
   this.name = aName;