accessible/src/jsat/Utils.jsm

Wed, 31 Dec 2014 07:16:47 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:16:47 +0100
branch
TOR_BUG_9701
changeset 3
141e0f1194b1
permissions
-rw-r--r--

Revert simplistic fix pending revisit of Mozilla integration attempt.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 'use strict';
     7 const Cu = Components.utils;
     8 const Cc = Components.classes;
     9 const Ci = Components.interfaces;
    11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    12 XPCOMUtils.defineLazyModuleGetter(this, 'Services',
    13   'resource://gre/modules/Services.jsm');
    14 XPCOMUtils.defineLazyModuleGetter(this, 'Rect',
    15   'resource://gre/modules/Geometry.jsm');
    16 XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
    17   'resource://gre/modules/accessibility/Constants.jsm');
    18 XPCOMUtils.defineLazyModuleGetter(this, 'Events',
    19   'resource://gre/modules/accessibility/Constants.jsm');
    20 XPCOMUtils.defineLazyModuleGetter(this, 'Relations',
    21   'resource://gre/modules/accessibility/Constants.jsm');
    22 XPCOMUtils.defineLazyModuleGetter(this, 'States',
    23   'resource://gre/modules/accessibility/Constants.jsm');
    25 this.EXPORTED_SYMBOLS = ['Utils', 'Logger', 'PivotContext', 'PrefCache', 'SettingCache'];
    27 this.Utils = {
    28   _buildAppMap: {
    29     '{3c2e2abc-06d4-11e1-ac3b-374f68613e61}': 'b2g',
    30     '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': 'browser',
    31     '{aa3c5121-dab2-40e2-81ca-7ea25febc110}': 'mobile/android',
    32     '{a23983c0-fd0e-11dc-95ff-0800200c9a66}': 'mobile/xul'
    33   },
    35   init: function Utils_init(aWindow) {
    36     if (this._win)
    37       // XXX: only supports attaching to one window now.
    38       throw new Error('Only one top-level window could used with AccessFu');
    40     this._win = Cu.getWeakReference(aWindow);
    41   },
    43   uninit: function Utils_uninit() {
    44     if (!this._win) {
    45       return;
    46     }
    47     delete this._win;
    48   },
    50   get win() {
    51     if (!this._win) {
    52       return null;
    53     }
    54     return this._win.get();
    55   },
    57   get AccRetrieval() {
    58     if (!this._AccRetrieval) {
    59       this._AccRetrieval = Cc['@mozilla.org/accessibleRetrieval;1'].
    60         getService(Ci.nsIAccessibleRetrieval);
    61     }
    63     return this._AccRetrieval;
    64   },
    66   set MozBuildApp(value) {
    67     this._buildApp = value;
    68   },
    70   get MozBuildApp() {
    71     if (!this._buildApp)
    72       this._buildApp = this._buildAppMap[Services.appinfo.ID];
    73     return this._buildApp;
    74   },
    76   get OS() {
    77     if (!this._OS)
    78       this._OS = Services.appinfo.OS;
    79     return this._OS;
    80   },
    82   get widgetToolkit() {
    83     if (!this._widgetToolkit)
    84       this._widgetToolkit = Services.appinfo.widgetToolkit;
    85     return this._widgetToolkit;
    86   },
    88   get ScriptName() {
    89     if (!this._ScriptName)
    90       this._ScriptName =
    91         (Services.appinfo.processType == 2) ? 'AccessFuContent' : 'AccessFu';
    92     return this._ScriptName;
    93   },
    95   get AndroidSdkVersion() {
    96     if (!this._AndroidSdkVersion) {
    97       if (Services.appinfo.OS == 'Android') {
    98         this._AndroidSdkVersion = Services.sysinfo.getPropertyAsInt32('version');
    99       } else {
   100         // Most useful in desktop debugging.
   101         this._AndroidSdkVersion = 16;
   102       }
   103     }
   104     return this._AndroidSdkVersion;
   105   },
   107   set AndroidSdkVersion(value) {
   108     // When we want to mimic another version.
   109     this._AndroidSdkVersion = value;
   110   },
   112   get BrowserApp() {
   113     if (!this.win) {
   114       return null;
   115     }
   116     switch (this.MozBuildApp) {
   117       case 'mobile/android':
   118         return this.win.BrowserApp;
   119       case 'browser':
   120         return this.win.gBrowser;
   121       case 'b2g':
   122         return this.win.shell;
   123       default:
   124         return null;
   125     }
   126   },
   128   get CurrentBrowser() {
   129     if (!this.BrowserApp) {
   130       return null;
   131     }
   132     if (this.MozBuildApp == 'b2g')
   133       return this.BrowserApp.contentBrowser;
   134     return this.BrowserApp.selectedBrowser;
   135   },
   137   get CurrentContentDoc() {
   138     let browser = this.CurrentBrowser;
   139     return browser ? browser.contentDocument : null;
   140   },
   142   get AllMessageManagers() {
   143     let messageManagers = [];
   145     for (let i = 0; i < this.win.messageManager.childCount; i++)
   146       messageManagers.push(this.win.messageManager.getChildAt(i));
   148     let document = this.CurrentContentDoc;
   150     if (document) {
   151       let remoteframes = document.querySelectorAll('iframe');
   153       for (let i = 0; i < remoteframes.length; ++i) {
   154         let mm = this.getMessageManager(remoteframes[i]);
   155         if (mm) {
   156           messageManagers.push(mm);
   157         }
   158       }
   160     }
   162     return messageManagers;
   163   },
   165   get isContentProcess() {
   166     delete this.isContentProcess;
   167     this.isContentProcess =
   168       Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
   169     return this.isContentProcess;
   170   },
   172   get stringBundle() {
   173     delete this.stringBundle;
   174     this.stringBundle = Services.strings.createBundle(
   175       'chrome://global/locale/AccessFu.properties');
   176     return this.stringBundle;
   177   },
   179   getMessageManager: function getMessageManager(aBrowser) {
   180     try {
   181       return aBrowser.QueryInterface(Ci.nsIFrameLoaderOwner).
   182          frameLoader.messageManager;
   183     } catch (x) {
   184       Logger.logException(x);
   185       return null;
   186     }
   187   },
   189   getViewport: function getViewport(aWindow) {
   190     switch (this.MozBuildApp) {
   191       case 'mobile/android':
   192         return aWindow.BrowserApp.selectedTab.getViewport();
   193       default:
   194         return null;
   195     }
   196   },
   198   getState: function getState(aAccessibleOrEvent) {
   199     if (aAccessibleOrEvent instanceof Ci.nsIAccessibleStateChangeEvent) {
   200       return new State(
   201         aAccessibleOrEvent.isExtraState ? 0 : aAccessibleOrEvent.state,
   202         aAccessibleOrEvent.isExtraState ? aAccessibleOrEvent.state : 0);
   203     } else {
   204       let state = {};
   205       let extState = {};
   206       aAccessibleOrEvent.getState(state, extState);
   207       return new State(state.value, extState.value);
   208     }
   209   },
   211   getAttributes: function getAttributes(aAccessible) {
   212     let attributes = {};
   214     if (aAccessible && aAccessible.attributes) {
   215       let attributesEnum = aAccessible.attributes.enumerate();
   217       // Populate |attributes| object with |aAccessible|'s attribute key-value
   218       // pairs.
   219       while (attributesEnum.hasMoreElements()) {
   220         let attribute = attributesEnum.getNext().QueryInterface(
   221           Ci.nsIPropertyElement);
   222         attributes[attribute.key] = attribute.value;
   223       }
   224     }
   226     return attributes;
   227   },
   229   getVirtualCursor: function getVirtualCursor(aDocument) {
   230     let doc = (aDocument instanceof Ci.nsIAccessible) ? aDocument :
   231       this.AccRetrieval.getAccessibleFor(aDocument);
   233     return doc.QueryInterface(Ci.nsIAccessibleDocument).virtualCursor;
   234   },
   236   getBounds: function getBounds(aAccessible) {
   237       let objX = {}, objY = {}, objW = {}, objH = {};
   238       aAccessible.getBounds(objX, objY, objW, objH);
   239       return new Rect(objX.value, objY.value, objW.value, objH.value);
   240   },
   242   getTextBounds: function getTextBounds(aAccessible, aStart, aEnd) {
   243       let accText = aAccessible.QueryInterface(Ci.nsIAccessibleText);
   244       let objX = {}, objY = {}, objW = {}, objH = {};
   245       accText.getRangeExtents(aStart, aEnd, objX, objY, objW, objH,
   246                               Ci.nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE);
   247       return new Rect(objX.value, objY.value, objW.value, objH.value);
   248   },
   250   /**
   251    * Get current display DPI.
   252    */
   253   get dpi() {
   254     delete this.dpi;
   255     this.dpi = this.win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(
   256       Ci.nsIDOMWindowUtils).displayDPI;
   257     return this.dpi;
   258   },
   260   isInSubtree: function isInSubtree(aAccessible, aSubTreeRoot) {
   261     let acc = aAccessible;
   262     while (acc) {
   263       if (acc == aSubTreeRoot) {
   264         return true;
   265       }
   267       try {
   268         acc = acc.parent;
   269       } catch (x) {
   270         Logger.debug('Failed to get parent:', x);
   271         acc = null;
   272       }
   273     }
   275     return false;
   276   },
   278   inHiddenSubtree: function inHiddenSubtree(aAccessible) {
   279     for (let acc=aAccessible; acc; acc=acc.parent) {
   280       let hidden = Utils.getAttributes(acc).hidden;
   281       if (hidden && JSON.parse(hidden)) {
   282         return true;
   283       }
   284     }
   285     return false;
   286   },
   288   isAliveAndVisible: function isAliveAndVisible(aAccessible, aIsOnScreen) {
   289     if (!aAccessible) {
   290       return false;
   291     }
   293     try {
   294       let state = this.getState(aAccessible);
   295       if (state.contains(States.DEFUNCT) || state.contains(States.INVISIBLE) ||
   296           (aIsOnScreen && state.contains(States.OFFSCREEN)) ||
   297           Utils.inHiddenSubtree(aAccessible)) {
   298         return false;
   299       }
   300     } catch (x) {
   301       return false;
   302     }
   304     return true;
   305   },
   307   matchAttributeValue: function matchAttributeValue(aAttributeValue, values) {
   308     let attrSet = new Set(aAttributeValue.split(' '));
   309     for (let value of values) {
   310       if (attrSet.has(value)) {
   311         return value;
   312       }
   313     }
   314   },
   316   getLandmarkName: function getLandmarkName(aAccessible) {
   317     const landmarks = [
   318       'banner',
   319       'complementary',
   320       'contentinfo',
   321       'main',
   322       'navigation',
   323       'search'
   324     ];
   325     let roles = this.getAttributes(aAccessible)['xml-roles'];
   326     if (!roles) {
   327       return;
   328     }
   330     // Looking up a role that would match a landmark.
   331     return this.matchAttributeValue(roles, landmarks);
   332   },
   334   getEmbeddedControl: function getEmbeddedControl(aLabel) {
   335     if (aLabel) {
   336       let relation = aLabel.getRelationByType(Relations.LABEL_FOR);
   337       for (let i = 0; i < relation.targetsCount; i++) {
   338         let target = relation.getTarget(i);
   339         if (target.parent === aLabel) {
   340           return target;
   341         }
   342       }
   343     }
   345     return null;
   346   }
   347 };
   349 /**
   350  * State object used internally to process accessible's states.
   351  * @param {Number} aBase     Base state.
   352  * @param {Number} aExtended Extended state.
   353  */
   354 function State(aBase, aExtended) {
   355   this.base = aBase;
   356   this.extended = aExtended;
   357 }
   359 State.prototype = {
   360   contains: function State_contains(other) {
   361     return !!(this.base & other.base || this.extended & other.extended);
   362   },
   363   toString: function State_toString() {
   364     let stateStrings = Utils.AccRetrieval.
   365       getStringStates(this.base, this.extended);
   366     let statesArray = new Array(stateStrings.length);
   367     for (let i = 0; i < statesArray.length; i++) {
   368       statesArray[i] = stateStrings.item(i);
   369     }
   370     return '[' + statesArray.join(', ') + ']';
   371   }
   372 };
   374 this.Logger = {
   375   DEBUG: 0,
   376   INFO: 1,
   377   WARNING: 2,
   378   ERROR: 3,
   379   _LEVEL_NAMES: ['DEBUG', 'INFO', 'WARNING', 'ERROR'],
   381   logLevel: 1, // INFO;
   383   test: false,
   385   log: function log(aLogLevel) {
   386     if (aLogLevel < this.logLevel)
   387       return;
   389     let args = Array.prototype.slice.call(arguments, 1);
   390     let message = (typeof(args[0]) === 'function' ? args[0]() : args).join(' ');
   391     message = '[' + Utils.ScriptName + '] ' + this._LEVEL_NAMES[aLogLevel] +
   392       ' ' + message + '\n';
   393     dump(message);
   394     // Note: used for testing purposes. If |this.test| is true, also log to
   395     // the console service.
   396     if (this.test) {
   397       try {
   398         Services.console.logStringMessage(message);
   399       } catch (ex) {
   400         // There was an exception logging to the console service.
   401       }
   402     }
   403   },
   405   info: function info() {
   406     this.log.apply(
   407       this, [this.INFO].concat(Array.prototype.slice.call(arguments)));
   408   },
   410   debug: function debug() {
   411     this.log.apply(
   412       this, [this.DEBUG].concat(Array.prototype.slice.call(arguments)));
   413   },
   415   warning: function warning() {
   416     this.log.apply(
   417       this, [this.WARNING].concat(Array.prototype.slice.call(arguments)));
   418   },
   420   error: function error() {
   421     this.log.apply(
   422       this, [this.ERROR].concat(Array.prototype.slice.call(arguments)));
   423   },
   425   logException: function logException(
   426     aException, aErrorMessage = 'An exception has occured') {
   427     try {
   428       let stackMessage = '';
   429       if (aException.stack) {
   430         stackMessage = '  ' + aException.stack.replace(/\n/g, '\n  ');
   431       } else if (aException.location) {
   432         let frame = aException.location;
   433         let stackLines = [];
   434         while (frame && frame.lineNumber) {
   435           stackLines.push(
   436             '  ' + frame.name + '@' + frame.filename + ':' + frame.lineNumber);
   437           frame = frame.caller;
   438         }
   439         stackMessage = stackLines.join('\n');
   440       } else {
   441         stackMessage = '(' + aException.fileName + ':' + aException.lineNumber + ')';
   442       }
   443       this.error(aErrorMessage + ':\n ' +
   444                  aException.message + '\n' +
   445                  stackMessage);
   446     } catch (x) {
   447       this.error(x);
   448     }
   449   },
   451   accessibleToString: function accessibleToString(aAccessible) {
   452     let str = '[ defunct ]';
   453     try {
   454       str = '[ ' + Utils.AccRetrieval.getStringRole(aAccessible.role) +
   455         ' | ' + aAccessible.name + ' ]';
   456     } catch (x) {
   457     }
   459     return str;
   460   },
   462   eventToString: function eventToString(aEvent) {
   463     let str = Utils.AccRetrieval.getStringEventType(aEvent.eventType);
   464     if (aEvent.eventType == Events.STATE_CHANGE) {
   465       let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
   466       let stateStrings = event.isExtraState ?
   467         Utils.AccRetrieval.getStringStates(0, event.state) :
   468         Utils.AccRetrieval.getStringStates(event.state, 0);
   469       str += ' (' + stateStrings.item(0) + ')';
   470     }
   472     if (aEvent.eventType == Events.VIRTUALCURSOR_CHANGED) {
   473       let event = aEvent.QueryInterface(
   474         Ci.nsIAccessibleVirtualCursorChangeEvent);
   475       let pivot = aEvent.accessible.QueryInterface(
   476         Ci.nsIAccessibleDocument).virtualCursor;
   477       str += ' (' + this.accessibleToString(event.oldAccessible) + ' -> ' +
   478 	this.accessibleToString(pivot.position) + ')';
   479     }
   481     return str;
   482   },
   484   statesToString: function statesToString(aAccessible) {
   485     return Utils.getState(aAccessible).toString();
   486   },
   488   dumpTree: function dumpTree(aLogLevel, aRootAccessible) {
   489     if (aLogLevel < this.logLevel)
   490       return;
   492     this._dumpTreeInternal(aLogLevel, aRootAccessible, 0);
   493   },
   495   _dumpTreeInternal: function _dumpTreeInternal(aLogLevel, aAccessible, aIndent) {
   496     let indentStr = '';
   497     for (var i=0; i < aIndent; i++)
   498       indentStr += ' ';
   499     this.log(aLogLevel, indentStr,
   500              this.accessibleToString(aAccessible),
   501              '(' + this.statesToString(aAccessible) + ')');
   502     for (var i=0; i < aAccessible.childCount; i++)
   503       this._dumpTreeInternal(aLogLevel, aAccessible.getChildAt(i), aIndent + 1);
   504     }
   505 };
   507 /**
   508  * PivotContext: An object that generates and caches context information
   509  * for a given accessible and its relationship with another accessible.
   510  *
   511  * If the given accessible is a label for a nested control, then this
   512  * context will represent the nested control instead of the label.
   513  * With the exception of bounds calculation, which will use the containing
   514  * label. In this case the |accessible| field would be the embedded control,
   515  * and the |accessibleForBounds| field would be the label.
   516  */
   517 this.PivotContext = function PivotContext(aAccessible, aOldAccessible,
   518   aStartOffset, aEndOffset, aIgnoreAncestry = false,
   519   aIncludeInvisible = false) {
   520   this._accessible = aAccessible;
   521   this._nestedControl = Utils.getEmbeddedControl(aAccessible);
   522   this._oldAccessible =
   523     this._isDefunct(aOldAccessible) ? null : aOldAccessible;
   524   this.startOffset = aStartOffset;
   525   this.endOffset = aEndOffset;
   526   this._ignoreAncestry = aIgnoreAncestry;
   527   this._includeInvisible = aIncludeInvisible;
   528 }
   530 PivotContext.prototype = {
   531   get accessible() {
   532     // If the current pivot accessible has a nested control,
   533     // make this context use it publicly.
   534     return this._nestedControl || this._accessible;
   535   },
   537   get oldAccessible() {
   538     return this._oldAccessible;
   539   },
   541   get isNestedControl() {
   542     return !!this._nestedControl;
   543   },
   545   get accessibleForBounds() {
   546     return this._accessible;
   547   },
   549   get textAndAdjustedOffsets() {
   550     if (this.startOffset === -1 && this.endOffset === -1) {
   551       return null;
   552     }
   554     if (!this._textAndAdjustedOffsets) {
   555       let result = {startOffset: this.startOffset,
   556                     endOffset: this.endOffset,
   557                     text: this._accessible.QueryInterface(Ci.nsIAccessibleText).
   558                           getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT)};
   559       let hypertextAcc = this._accessible.QueryInterface(Ci.nsIAccessibleHyperText);
   561       // Iterate through the links in backwards order so text replacements don't
   562       // affect the offsets of links yet to be processed.
   563       for (let i = hypertextAcc.linkCount - 1; i >= 0; i--) {
   564         let link = hypertextAcc.getLinkAt(i);
   565         let linkText = '';
   566         if (link instanceof Ci.nsIAccessibleText) {
   567           linkText = link.QueryInterface(Ci.nsIAccessibleText).
   568                           getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT);
   569         }
   571         let start = link.startIndex;
   572         let end = link.endIndex;
   573         for (let offset of ['startOffset', 'endOffset']) {
   574           if (this[offset] >= end) {
   575             result[offset] += linkText.length - (end - start);
   576           }
   577         }
   578         result.text = result.text.substring(0, start) + linkText +
   579                       result.text.substring(end);
   580       }
   582       this._textAndAdjustedOffsets = result;
   583     }
   585     return this._textAndAdjustedOffsets;
   586   },
   588   /**
   589    * Get a list of |aAccessible|'s ancestry up to the root.
   590    * @param  {nsIAccessible} aAccessible.
   591    * @return {Array} Ancestry list.
   592    */
   593   _getAncestry: function _getAncestry(aAccessible) {
   594     let ancestry = [];
   595     let parent = aAccessible;
   596     try {
   597       while (parent && (parent = parent.parent)) {
   598        ancestry.push(parent);
   599       }
   600     } catch (x) {
   601       // A defunct accessible will raise an exception geting parent.
   602       Logger.debug('Failed to get parent:', x);
   603     }
   604     return ancestry.reverse();
   605   },
   607   /**
   608    * A list of the old accessible's ancestry.
   609    */
   610   get oldAncestry() {
   611     if (!this._oldAncestry) {
   612       if (!this._oldAccessible || this._ignoreAncestry) {
   613         this._oldAncestry = [];
   614       } else {
   615         this._oldAncestry = this._getAncestry(this._oldAccessible);
   616         this._oldAncestry.push(this._oldAccessible);
   617       }
   618     }
   619     return this._oldAncestry;
   620   },
   622   /**
   623    * A list of the current accessible's ancestry.
   624    */
   625   get currentAncestry() {
   626     if (!this._currentAncestry) {
   627       this._currentAncestry = this._ignoreAncestry ? [] :
   628         this._getAncestry(this.accessible);
   629     }
   630     return this._currentAncestry;
   631   },
   633   /*
   634    * This is a list of the accessible's ancestry up to the common ancestor
   635    * of the accessible and the old accessible. It is useful for giving the
   636    * user context as to where they are in the heirarchy.
   637    */
   638   get newAncestry() {
   639     if (!this._newAncestry) {
   640       this._newAncestry = this._ignoreAncestry ? [] : [currentAncestor for (
   641         [index, currentAncestor] of Iterator(this.currentAncestry)) if (
   642           currentAncestor !== this.oldAncestry[index])];
   643     }
   644     return this._newAncestry;
   645   },
   647   /*
   648    * Traverse the accessible's subtree in pre or post order.
   649    * It only includes the accessible's visible chidren.
   650    * Note: needSubtree is a function argument that can be used to determine
   651    * whether aAccessible's subtree is required.
   652    */
   653   _traverse: function _traverse(aAccessible, aPreorder, aStop) {
   654     if (aStop && aStop(aAccessible)) {
   655       return;
   656     }
   657     let child = aAccessible.firstChild;
   658     while (child) {
   659       let include;
   660       if (this._includeInvisible) {
   661         include = true;
   662       } else {
   663         include = !(Utils.getState(child).contains(States.INVISIBLE));
   664       }
   665       if (include) {
   666         if (aPreorder) {
   667           yield child;
   668           [yield node for (node of this._traverse(child, aPreorder, aStop))];
   669         } else {
   670           [yield node for (node of this._traverse(child, aPreorder, aStop))];
   671           yield child;
   672         }
   673       }
   674       child = child.nextSibling;
   675     }
   676   },
   678   /*
   679    * A subtree generator function, used to generate a flattened
   680    * list of the accessible's subtree in pre or post order.
   681    * It only includes the accessible's visible chidren.
   682    * @param {boolean} aPreorder A flag for traversal order. If true, traverse
   683    * in preorder; if false, traverse in postorder.
   684    * @param {function} aStop An optional function, indicating whether subtree
   685    * traversal should stop.
   686    */
   687   subtreeGenerator: function subtreeGenerator(aPreorder, aStop) {
   688     return this._traverse(this.accessible, aPreorder, aStop);
   689   },
   691   getCellInfo: function getCellInfo(aAccessible) {
   692     if (!this._cells) {
   693       this._cells = new WeakMap();
   694     }
   696     let domNode = aAccessible.DOMNode;
   697     if (this._cells.has(domNode)) {
   698       return this._cells.get(domNode);
   699     }
   701     let cellInfo = {};
   702     let getAccessibleCell = function getAccessibleCell(aAccessible) {
   703       if (!aAccessible) {
   704         return null;
   705       }
   706       if ([Roles.CELL, Roles.COLUMNHEADER, Roles.ROWHEADER].indexOf(
   707         aAccessible.role) < 0) {
   708           return null;
   709       }
   710       try {
   711         return aAccessible.QueryInterface(Ci.nsIAccessibleTableCell);
   712       } catch (x) {
   713         Logger.logException(x);
   714         return null;
   715       }
   716     };
   717     let getHeaders = function getHeaders(aHeaderCells) {
   718       let enumerator = aHeaderCells.enumerate();
   719       while (enumerator.hasMoreElements()) {
   720         yield enumerator.getNext().QueryInterface(Ci.nsIAccessible).name;
   721       }
   722     };
   724     cellInfo.current = getAccessibleCell(aAccessible);
   726     if (!cellInfo.current) {
   727       Logger.warning(aAccessible,
   728         'does not support nsIAccessibleTableCell interface.');
   729       this._cells.set(domNode, null);
   730       return null;
   731     }
   733     let table = cellInfo.current.table;
   734     if (table.isProbablyForLayout()) {
   735       this._cells.set(domNode, null);
   736       return null;
   737     }
   739     cellInfo.previous = null;
   740     let oldAncestry = this.oldAncestry.reverse();
   741     let ancestor = oldAncestry.shift();
   742     while (!cellInfo.previous && ancestor) {
   743       let cell = getAccessibleCell(ancestor);
   744       if (cell && cell.table === table) {
   745         cellInfo.previous = cell;
   746       }
   747       ancestor = oldAncestry.shift();
   748     }
   750     if (cellInfo.previous) {
   751       cellInfo.rowChanged = cellInfo.current.rowIndex !==
   752         cellInfo.previous.rowIndex;
   753       cellInfo.columnChanged = cellInfo.current.columnIndex !==
   754         cellInfo.previous.columnIndex;
   755     } else {
   756       cellInfo.rowChanged = true;
   757       cellInfo.columnChanged = true;
   758     }
   760     cellInfo.rowExtent = cellInfo.current.rowExtent;
   761     cellInfo.columnExtent = cellInfo.current.columnExtent;
   762     cellInfo.columnIndex = cellInfo.current.columnIndex;
   763     cellInfo.rowIndex = cellInfo.current.rowIndex;
   765     cellInfo.columnHeaders = [];
   766     if (cellInfo.columnChanged && cellInfo.current.role !==
   767       Roles.COLUMNHEADER) {
   768       cellInfo.columnHeaders = [headers for (headers of getHeaders(
   769         cellInfo.current.columnHeaderCells))];
   770     }
   771     cellInfo.rowHeaders = [];
   772     if (cellInfo.rowChanged && cellInfo.current.role === Roles.CELL) {
   773       cellInfo.rowHeaders = [headers for (headers of getHeaders(
   774         cellInfo.current.rowHeaderCells))];
   775     }
   777     this._cells.set(domNode, cellInfo);
   778     return cellInfo;
   779   },
   781   get bounds() {
   782     if (!this._bounds) {
   783       this._bounds = Utils.getBounds(this.accessibleForBounds);
   784     }
   786     return this._bounds.clone();
   787   },
   789   _isDefunct: function _isDefunct(aAccessible) {
   790     try {
   791       return Utils.getState(aAccessible).contains(States.DEFUNCT);
   792     } catch (x) {
   793       return true;
   794     }
   795   }
   796 };
   798 this.PrefCache = function PrefCache(aName, aCallback, aRunCallbackNow) {
   799   this.name = aName;
   800   this.callback = aCallback;
   802   let branch = Services.prefs;
   803   this.value = this._getValue(branch);
   805   if (this.callback && aRunCallbackNow) {
   806     try {
   807       this.callback(this.name, this.value);
   808     } catch (x) {
   809       Logger.logException(x);
   810     }
   811   }
   813   branch.addObserver(aName, this, true);
   814 };
   816 PrefCache.prototype = {
   817   _getValue: function _getValue(aBranch) {
   818     try {
   819       if (!this.type) {
   820         this.type = aBranch.getPrefType(this.name);
   821       }
   822       switch (this.type) {
   823         case Ci.nsIPrefBranch.PREF_STRING:
   824           return aBranch.getCharPref(this.name);
   825         case Ci.nsIPrefBranch.PREF_INT:
   826           return aBranch.getIntPref(this.name);
   827         case Ci.nsIPrefBranch.PREF_BOOL:
   828           return aBranch.getBoolPref(this.name);
   829         default:
   830           return null;
   831       }
   832     } catch (x) {
   833       // Pref does not exist.
   834       return null;
   835     }
   836   },
   838   observe: function observe(aSubject, aTopic, aData) {
   839     this.value = this._getValue(aSubject.QueryInterface(Ci.nsIPrefBranch));
   840     if (this.callback) {
   841       try {
   842         this.callback(this.name, this.value);
   843       } catch (x) {
   844         Logger.logException(x);
   845       }
   846     }
   847   },
   849   QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
   850                                           Ci.nsISupportsWeakReference])
   851 };
   853 this.SettingCache = function SettingCache(aName, aCallback, aOptions = {}) {
   854   this.value = aOptions.defaultValue;
   855   let runCallback = () => {
   856     if (aCallback) {
   857       aCallback(aName, this.value);
   858       if (aOptions.callbackOnce) {
   859         runCallback = () => {};
   860       }
   861     }
   862   };
   864   let settings = Utils.win.navigator.mozSettings;
   865   if (!settings) {
   866     if (aOptions.callbackNow) {
   867       runCallback();
   868     }
   869     return;
   870   }
   873   let lock = settings.createLock();
   874   let req = lock.get(aName);
   876   req.addEventListener('success', () => {
   877     this.value = req.result[aName] == undefined ? aOptions.defaultValue : req.result[aName];
   878     if (aOptions.callbackNow) {
   879       runCallback();
   880     }
   881   });
   883   settings.addObserver(aName,
   884                        (evt) => {
   885                          this.value = evt.settingValue;
   886                          runCallback();
   887                        });
   888 };

mercurial