accessible/src/jsat/Utils.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/accessible/src/jsat/Utils.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,888 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +'use strict';
     1.9 +
    1.10 +const Cu = Components.utils;
    1.11 +const Cc = Components.classes;
    1.12 +const Ci = Components.interfaces;
    1.13 +
    1.14 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.15 +XPCOMUtils.defineLazyModuleGetter(this, 'Services',
    1.16 +  'resource://gre/modules/Services.jsm');
    1.17 +XPCOMUtils.defineLazyModuleGetter(this, 'Rect',
    1.18 +  'resource://gre/modules/Geometry.jsm');
    1.19 +XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
    1.20 +  'resource://gre/modules/accessibility/Constants.jsm');
    1.21 +XPCOMUtils.defineLazyModuleGetter(this, 'Events',
    1.22 +  'resource://gre/modules/accessibility/Constants.jsm');
    1.23 +XPCOMUtils.defineLazyModuleGetter(this, 'Relations',
    1.24 +  'resource://gre/modules/accessibility/Constants.jsm');
    1.25 +XPCOMUtils.defineLazyModuleGetter(this, 'States',
    1.26 +  'resource://gre/modules/accessibility/Constants.jsm');
    1.27 +
    1.28 +this.EXPORTED_SYMBOLS = ['Utils', 'Logger', 'PivotContext', 'PrefCache', 'SettingCache'];
    1.29 +
    1.30 +this.Utils = {
    1.31 +  _buildAppMap: {
    1.32 +    '{3c2e2abc-06d4-11e1-ac3b-374f68613e61}': 'b2g',
    1.33 +    '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': 'browser',
    1.34 +    '{aa3c5121-dab2-40e2-81ca-7ea25febc110}': 'mobile/android',
    1.35 +    '{a23983c0-fd0e-11dc-95ff-0800200c9a66}': 'mobile/xul'
    1.36 +  },
    1.37 +
    1.38 +  init: function Utils_init(aWindow) {
    1.39 +    if (this._win)
    1.40 +      // XXX: only supports attaching to one window now.
    1.41 +      throw new Error('Only one top-level window could used with AccessFu');
    1.42 +
    1.43 +    this._win = Cu.getWeakReference(aWindow);
    1.44 +  },
    1.45 +
    1.46 +  uninit: function Utils_uninit() {
    1.47 +    if (!this._win) {
    1.48 +      return;
    1.49 +    }
    1.50 +    delete this._win;
    1.51 +  },
    1.52 +
    1.53 +  get win() {
    1.54 +    if (!this._win) {
    1.55 +      return null;
    1.56 +    }
    1.57 +    return this._win.get();
    1.58 +  },
    1.59 +
    1.60 +  get AccRetrieval() {
    1.61 +    if (!this._AccRetrieval) {
    1.62 +      this._AccRetrieval = Cc['@mozilla.org/accessibleRetrieval;1'].
    1.63 +        getService(Ci.nsIAccessibleRetrieval);
    1.64 +    }
    1.65 +
    1.66 +    return this._AccRetrieval;
    1.67 +  },
    1.68 +
    1.69 +  set MozBuildApp(value) {
    1.70 +    this._buildApp = value;
    1.71 +  },
    1.72 +
    1.73 +  get MozBuildApp() {
    1.74 +    if (!this._buildApp)
    1.75 +      this._buildApp = this._buildAppMap[Services.appinfo.ID];
    1.76 +    return this._buildApp;
    1.77 +  },
    1.78 +
    1.79 +  get OS() {
    1.80 +    if (!this._OS)
    1.81 +      this._OS = Services.appinfo.OS;
    1.82 +    return this._OS;
    1.83 +  },
    1.84 +
    1.85 +  get widgetToolkit() {
    1.86 +    if (!this._widgetToolkit)
    1.87 +      this._widgetToolkit = Services.appinfo.widgetToolkit;
    1.88 +    return this._widgetToolkit;
    1.89 +  },
    1.90 +
    1.91 +  get ScriptName() {
    1.92 +    if (!this._ScriptName)
    1.93 +      this._ScriptName =
    1.94 +        (Services.appinfo.processType == 2) ? 'AccessFuContent' : 'AccessFu';
    1.95 +    return this._ScriptName;
    1.96 +  },
    1.97 +
    1.98 +  get AndroidSdkVersion() {
    1.99 +    if (!this._AndroidSdkVersion) {
   1.100 +      if (Services.appinfo.OS == 'Android') {
   1.101 +        this._AndroidSdkVersion = Services.sysinfo.getPropertyAsInt32('version');
   1.102 +      } else {
   1.103 +        // Most useful in desktop debugging.
   1.104 +        this._AndroidSdkVersion = 16;
   1.105 +      }
   1.106 +    }
   1.107 +    return this._AndroidSdkVersion;
   1.108 +  },
   1.109 +
   1.110 +  set AndroidSdkVersion(value) {
   1.111 +    // When we want to mimic another version.
   1.112 +    this._AndroidSdkVersion = value;
   1.113 +  },
   1.114 +
   1.115 +  get BrowserApp() {
   1.116 +    if (!this.win) {
   1.117 +      return null;
   1.118 +    }
   1.119 +    switch (this.MozBuildApp) {
   1.120 +      case 'mobile/android':
   1.121 +        return this.win.BrowserApp;
   1.122 +      case 'browser':
   1.123 +        return this.win.gBrowser;
   1.124 +      case 'b2g':
   1.125 +        return this.win.shell;
   1.126 +      default:
   1.127 +        return null;
   1.128 +    }
   1.129 +  },
   1.130 +
   1.131 +  get CurrentBrowser() {
   1.132 +    if (!this.BrowserApp) {
   1.133 +      return null;
   1.134 +    }
   1.135 +    if (this.MozBuildApp == 'b2g')
   1.136 +      return this.BrowserApp.contentBrowser;
   1.137 +    return this.BrowserApp.selectedBrowser;
   1.138 +  },
   1.139 +
   1.140 +  get CurrentContentDoc() {
   1.141 +    let browser = this.CurrentBrowser;
   1.142 +    return browser ? browser.contentDocument : null;
   1.143 +  },
   1.144 +
   1.145 +  get AllMessageManagers() {
   1.146 +    let messageManagers = [];
   1.147 +
   1.148 +    for (let i = 0; i < this.win.messageManager.childCount; i++)
   1.149 +      messageManagers.push(this.win.messageManager.getChildAt(i));
   1.150 +
   1.151 +    let document = this.CurrentContentDoc;
   1.152 +
   1.153 +    if (document) {
   1.154 +      let remoteframes = document.querySelectorAll('iframe');
   1.155 +
   1.156 +      for (let i = 0; i < remoteframes.length; ++i) {
   1.157 +        let mm = this.getMessageManager(remoteframes[i]);
   1.158 +        if (mm) {
   1.159 +          messageManagers.push(mm);
   1.160 +        }
   1.161 +      }
   1.162 +
   1.163 +    }
   1.164 +
   1.165 +    return messageManagers;
   1.166 +  },
   1.167 +
   1.168 +  get isContentProcess() {
   1.169 +    delete this.isContentProcess;
   1.170 +    this.isContentProcess =
   1.171 +      Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
   1.172 +    return this.isContentProcess;
   1.173 +  },
   1.174 +
   1.175 +  get stringBundle() {
   1.176 +    delete this.stringBundle;
   1.177 +    this.stringBundle = Services.strings.createBundle(
   1.178 +      'chrome://global/locale/AccessFu.properties');
   1.179 +    return this.stringBundle;
   1.180 +  },
   1.181 +
   1.182 +  getMessageManager: function getMessageManager(aBrowser) {
   1.183 +    try {
   1.184 +      return aBrowser.QueryInterface(Ci.nsIFrameLoaderOwner).
   1.185 +         frameLoader.messageManager;
   1.186 +    } catch (x) {
   1.187 +      Logger.logException(x);
   1.188 +      return null;
   1.189 +    }
   1.190 +  },
   1.191 +
   1.192 +  getViewport: function getViewport(aWindow) {
   1.193 +    switch (this.MozBuildApp) {
   1.194 +      case 'mobile/android':
   1.195 +        return aWindow.BrowserApp.selectedTab.getViewport();
   1.196 +      default:
   1.197 +        return null;
   1.198 +    }
   1.199 +  },
   1.200 +
   1.201 +  getState: function getState(aAccessibleOrEvent) {
   1.202 +    if (aAccessibleOrEvent instanceof Ci.nsIAccessibleStateChangeEvent) {
   1.203 +      return new State(
   1.204 +        aAccessibleOrEvent.isExtraState ? 0 : aAccessibleOrEvent.state,
   1.205 +        aAccessibleOrEvent.isExtraState ? aAccessibleOrEvent.state : 0);
   1.206 +    } else {
   1.207 +      let state = {};
   1.208 +      let extState = {};
   1.209 +      aAccessibleOrEvent.getState(state, extState);
   1.210 +      return new State(state.value, extState.value);
   1.211 +    }
   1.212 +  },
   1.213 +
   1.214 +  getAttributes: function getAttributes(aAccessible) {
   1.215 +    let attributes = {};
   1.216 +
   1.217 +    if (aAccessible && aAccessible.attributes) {
   1.218 +      let attributesEnum = aAccessible.attributes.enumerate();
   1.219 +
   1.220 +      // Populate |attributes| object with |aAccessible|'s attribute key-value
   1.221 +      // pairs.
   1.222 +      while (attributesEnum.hasMoreElements()) {
   1.223 +        let attribute = attributesEnum.getNext().QueryInterface(
   1.224 +          Ci.nsIPropertyElement);
   1.225 +        attributes[attribute.key] = attribute.value;
   1.226 +      }
   1.227 +    }
   1.228 +
   1.229 +    return attributes;
   1.230 +  },
   1.231 +
   1.232 +  getVirtualCursor: function getVirtualCursor(aDocument) {
   1.233 +    let doc = (aDocument instanceof Ci.nsIAccessible) ? aDocument :
   1.234 +      this.AccRetrieval.getAccessibleFor(aDocument);
   1.235 +
   1.236 +    return doc.QueryInterface(Ci.nsIAccessibleDocument).virtualCursor;
   1.237 +  },
   1.238 +
   1.239 +  getBounds: function getBounds(aAccessible) {
   1.240 +      let objX = {}, objY = {}, objW = {}, objH = {};
   1.241 +      aAccessible.getBounds(objX, objY, objW, objH);
   1.242 +      return new Rect(objX.value, objY.value, objW.value, objH.value);
   1.243 +  },
   1.244 +
   1.245 +  getTextBounds: function getTextBounds(aAccessible, aStart, aEnd) {
   1.246 +      let accText = aAccessible.QueryInterface(Ci.nsIAccessibleText);
   1.247 +      let objX = {}, objY = {}, objW = {}, objH = {};
   1.248 +      accText.getRangeExtents(aStart, aEnd, objX, objY, objW, objH,
   1.249 +                              Ci.nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE);
   1.250 +      return new Rect(objX.value, objY.value, objW.value, objH.value);
   1.251 +  },
   1.252 +
   1.253 +  /**
   1.254 +   * Get current display DPI.
   1.255 +   */
   1.256 +  get dpi() {
   1.257 +    delete this.dpi;
   1.258 +    this.dpi = this.win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(
   1.259 +      Ci.nsIDOMWindowUtils).displayDPI;
   1.260 +    return this.dpi;
   1.261 +  },
   1.262 +
   1.263 +  isInSubtree: function isInSubtree(aAccessible, aSubTreeRoot) {
   1.264 +    let acc = aAccessible;
   1.265 +    while (acc) {
   1.266 +      if (acc == aSubTreeRoot) {
   1.267 +        return true;
   1.268 +      }
   1.269 +
   1.270 +      try {
   1.271 +        acc = acc.parent;
   1.272 +      } catch (x) {
   1.273 +        Logger.debug('Failed to get parent:', x);
   1.274 +        acc = null;
   1.275 +      }
   1.276 +    }
   1.277 +
   1.278 +    return false;
   1.279 +  },
   1.280 +
   1.281 +  inHiddenSubtree: function inHiddenSubtree(aAccessible) {
   1.282 +    for (let acc=aAccessible; acc; acc=acc.parent) {
   1.283 +      let hidden = Utils.getAttributes(acc).hidden;
   1.284 +      if (hidden && JSON.parse(hidden)) {
   1.285 +        return true;
   1.286 +      }
   1.287 +    }
   1.288 +    return false;
   1.289 +  },
   1.290 +
   1.291 +  isAliveAndVisible: function isAliveAndVisible(aAccessible, aIsOnScreen) {
   1.292 +    if (!aAccessible) {
   1.293 +      return false;
   1.294 +    }
   1.295 +
   1.296 +    try {
   1.297 +      let state = this.getState(aAccessible);
   1.298 +      if (state.contains(States.DEFUNCT) || state.contains(States.INVISIBLE) ||
   1.299 +          (aIsOnScreen && state.contains(States.OFFSCREEN)) ||
   1.300 +          Utils.inHiddenSubtree(aAccessible)) {
   1.301 +        return false;
   1.302 +      }
   1.303 +    } catch (x) {
   1.304 +      return false;
   1.305 +    }
   1.306 +
   1.307 +    return true;
   1.308 +  },
   1.309 +
   1.310 +  matchAttributeValue: function matchAttributeValue(aAttributeValue, values) {
   1.311 +    let attrSet = new Set(aAttributeValue.split(' '));
   1.312 +    for (let value of values) {
   1.313 +      if (attrSet.has(value)) {
   1.314 +        return value;
   1.315 +      }
   1.316 +    }
   1.317 +  },
   1.318 +
   1.319 +  getLandmarkName: function getLandmarkName(aAccessible) {
   1.320 +    const landmarks = [
   1.321 +      'banner',
   1.322 +      'complementary',
   1.323 +      'contentinfo',
   1.324 +      'main',
   1.325 +      'navigation',
   1.326 +      'search'
   1.327 +    ];
   1.328 +    let roles = this.getAttributes(aAccessible)['xml-roles'];
   1.329 +    if (!roles) {
   1.330 +      return;
   1.331 +    }
   1.332 +
   1.333 +    // Looking up a role that would match a landmark.
   1.334 +    return this.matchAttributeValue(roles, landmarks);
   1.335 +  },
   1.336 +
   1.337 +  getEmbeddedControl: function getEmbeddedControl(aLabel) {
   1.338 +    if (aLabel) {
   1.339 +      let relation = aLabel.getRelationByType(Relations.LABEL_FOR);
   1.340 +      for (let i = 0; i < relation.targetsCount; i++) {
   1.341 +        let target = relation.getTarget(i);
   1.342 +        if (target.parent === aLabel) {
   1.343 +          return target;
   1.344 +        }
   1.345 +      }
   1.346 +    }
   1.347 +
   1.348 +    return null;
   1.349 +  }
   1.350 +};
   1.351 +
   1.352 +/**
   1.353 + * State object used internally to process accessible's states.
   1.354 + * @param {Number} aBase     Base state.
   1.355 + * @param {Number} aExtended Extended state.
   1.356 + */
   1.357 +function State(aBase, aExtended) {
   1.358 +  this.base = aBase;
   1.359 +  this.extended = aExtended;
   1.360 +}
   1.361 +
   1.362 +State.prototype = {
   1.363 +  contains: function State_contains(other) {
   1.364 +    return !!(this.base & other.base || this.extended & other.extended);
   1.365 +  },
   1.366 +  toString: function State_toString() {
   1.367 +    let stateStrings = Utils.AccRetrieval.
   1.368 +      getStringStates(this.base, this.extended);
   1.369 +    let statesArray = new Array(stateStrings.length);
   1.370 +    for (let i = 0; i < statesArray.length; i++) {
   1.371 +      statesArray[i] = stateStrings.item(i);
   1.372 +    }
   1.373 +    return '[' + statesArray.join(', ') + ']';
   1.374 +  }
   1.375 +};
   1.376 +
   1.377 +this.Logger = {
   1.378 +  DEBUG: 0,
   1.379 +  INFO: 1,
   1.380 +  WARNING: 2,
   1.381 +  ERROR: 3,
   1.382 +  _LEVEL_NAMES: ['DEBUG', 'INFO', 'WARNING', 'ERROR'],
   1.383 +
   1.384 +  logLevel: 1, // INFO;
   1.385 +
   1.386 +  test: false,
   1.387 +
   1.388 +  log: function log(aLogLevel) {
   1.389 +    if (aLogLevel < this.logLevel)
   1.390 +      return;
   1.391 +
   1.392 +    let args = Array.prototype.slice.call(arguments, 1);
   1.393 +    let message = (typeof(args[0]) === 'function' ? args[0]() : args).join(' ');
   1.394 +    message = '[' + Utils.ScriptName + '] ' + this._LEVEL_NAMES[aLogLevel] +
   1.395 +      ' ' + message + '\n';
   1.396 +    dump(message);
   1.397 +    // Note: used for testing purposes. If |this.test| is true, also log to
   1.398 +    // the console service.
   1.399 +    if (this.test) {
   1.400 +      try {
   1.401 +        Services.console.logStringMessage(message);
   1.402 +      } catch (ex) {
   1.403 +        // There was an exception logging to the console service.
   1.404 +      }
   1.405 +    }
   1.406 +  },
   1.407 +
   1.408 +  info: function info() {
   1.409 +    this.log.apply(
   1.410 +      this, [this.INFO].concat(Array.prototype.slice.call(arguments)));
   1.411 +  },
   1.412 +
   1.413 +  debug: function debug() {
   1.414 +    this.log.apply(
   1.415 +      this, [this.DEBUG].concat(Array.prototype.slice.call(arguments)));
   1.416 +  },
   1.417 +
   1.418 +  warning: function warning() {
   1.419 +    this.log.apply(
   1.420 +      this, [this.WARNING].concat(Array.prototype.slice.call(arguments)));
   1.421 +  },
   1.422 +
   1.423 +  error: function error() {
   1.424 +    this.log.apply(
   1.425 +      this, [this.ERROR].concat(Array.prototype.slice.call(arguments)));
   1.426 +  },
   1.427 +
   1.428 +  logException: function logException(
   1.429 +    aException, aErrorMessage = 'An exception has occured') {
   1.430 +    try {
   1.431 +      let stackMessage = '';
   1.432 +      if (aException.stack) {
   1.433 +        stackMessage = '  ' + aException.stack.replace(/\n/g, '\n  ');
   1.434 +      } else if (aException.location) {
   1.435 +        let frame = aException.location;
   1.436 +        let stackLines = [];
   1.437 +        while (frame && frame.lineNumber) {
   1.438 +          stackLines.push(
   1.439 +            '  ' + frame.name + '@' + frame.filename + ':' + frame.lineNumber);
   1.440 +          frame = frame.caller;
   1.441 +        }
   1.442 +        stackMessage = stackLines.join('\n');
   1.443 +      } else {
   1.444 +        stackMessage = '(' + aException.fileName + ':' + aException.lineNumber + ')';
   1.445 +      }
   1.446 +      this.error(aErrorMessage + ':\n ' +
   1.447 +                 aException.message + '\n' +
   1.448 +                 stackMessage);
   1.449 +    } catch (x) {
   1.450 +      this.error(x);
   1.451 +    }
   1.452 +  },
   1.453 +
   1.454 +  accessibleToString: function accessibleToString(aAccessible) {
   1.455 +    let str = '[ defunct ]';
   1.456 +    try {
   1.457 +      str = '[ ' + Utils.AccRetrieval.getStringRole(aAccessible.role) +
   1.458 +        ' | ' + aAccessible.name + ' ]';
   1.459 +    } catch (x) {
   1.460 +    }
   1.461 +
   1.462 +    return str;
   1.463 +  },
   1.464 +
   1.465 +  eventToString: function eventToString(aEvent) {
   1.466 +    let str = Utils.AccRetrieval.getStringEventType(aEvent.eventType);
   1.467 +    if (aEvent.eventType == Events.STATE_CHANGE) {
   1.468 +      let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
   1.469 +      let stateStrings = event.isExtraState ?
   1.470 +        Utils.AccRetrieval.getStringStates(0, event.state) :
   1.471 +        Utils.AccRetrieval.getStringStates(event.state, 0);
   1.472 +      str += ' (' + stateStrings.item(0) + ')';
   1.473 +    }
   1.474 +
   1.475 +    if (aEvent.eventType == Events.VIRTUALCURSOR_CHANGED) {
   1.476 +      let event = aEvent.QueryInterface(
   1.477 +        Ci.nsIAccessibleVirtualCursorChangeEvent);
   1.478 +      let pivot = aEvent.accessible.QueryInterface(
   1.479 +        Ci.nsIAccessibleDocument).virtualCursor;
   1.480 +      str += ' (' + this.accessibleToString(event.oldAccessible) + ' -> ' +
   1.481 +	this.accessibleToString(pivot.position) + ')';
   1.482 +    }
   1.483 +
   1.484 +    return str;
   1.485 +  },
   1.486 +
   1.487 +  statesToString: function statesToString(aAccessible) {
   1.488 +    return Utils.getState(aAccessible).toString();
   1.489 +  },
   1.490 +
   1.491 +  dumpTree: function dumpTree(aLogLevel, aRootAccessible) {
   1.492 +    if (aLogLevel < this.logLevel)
   1.493 +      return;
   1.494 +
   1.495 +    this._dumpTreeInternal(aLogLevel, aRootAccessible, 0);
   1.496 +  },
   1.497 +
   1.498 +  _dumpTreeInternal: function _dumpTreeInternal(aLogLevel, aAccessible, aIndent) {
   1.499 +    let indentStr = '';
   1.500 +    for (var i=0; i < aIndent; i++)
   1.501 +      indentStr += ' ';
   1.502 +    this.log(aLogLevel, indentStr,
   1.503 +             this.accessibleToString(aAccessible),
   1.504 +             '(' + this.statesToString(aAccessible) + ')');
   1.505 +    for (var i=0; i < aAccessible.childCount; i++)
   1.506 +      this._dumpTreeInternal(aLogLevel, aAccessible.getChildAt(i), aIndent + 1);
   1.507 +    }
   1.508 +};
   1.509 +
   1.510 +/**
   1.511 + * PivotContext: An object that generates and caches context information
   1.512 + * for a given accessible and its relationship with another accessible.
   1.513 + *
   1.514 + * If the given accessible is a label for a nested control, then this
   1.515 + * context will represent the nested control instead of the label.
   1.516 + * With the exception of bounds calculation, which will use the containing
   1.517 + * label. In this case the |accessible| field would be the embedded control,
   1.518 + * and the |accessibleForBounds| field would be the label.
   1.519 + */
   1.520 +this.PivotContext = function PivotContext(aAccessible, aOldAccessible,
   1.521 +  aStartOffset, aEndOffset, aIgnoreAncestry = false,
   1.522 +  aIncludeInvisible = false) {
   1.523 +  this._accessible = aAccessible;
   1.524 +  this._nestedControl = Utils.getEmbeddedControl(aAccessible);
   1.525 +  this._oldAccessible =
   1.526 +    this._isDefunct(aOldAccessible) ? null : aOldAccessible;
   1.527 +  this.startOffset = aStartOffset;
   1.528 +  this.endOffset = aEndOffset;
   1.529 +  this._ignoreAncestry = aIgnoreAncestry;
   1.530 +  this._includeInvisible = aIncludeInvisible;
   1.531 +}
   1.532 +
   1.533 +PivotContext.prototype = {
   1.534 +  get accessible() {
   1.535 +    // If the current pivot accessible has a nested control,
   1.536 +    // make this context use it publicly.
   1.537 +    return this._nestedControl || this._accessible;
   1.538 +  },
   1.539 +
   1.540 +  get oldAccessible() {
   1.541 +    return this._oldAccessible;
   1.542 +  },
   1.543 +
   1.544 +  get isNestedControl() {
   1.545 +    return !!this._nestedControl;
   1.546 +  },
   1.547 +
   1.548 +  get accessibleForBounds() {
   1.549 +    return this._accessible;
   1.550 +  },
   1.551 +
   1.552 +  get textAndAdjustedOffsets() {
   1.553 +    if (this.startOffset === -1 && this.endOffset === -1) {
   1.554 +      return null;
   1.555 +    }
   1.556 +
   1.557 +    if (!this._textAndAdjustedOffsets) {
   1.558 +      let result = {startOffset: this.startOffset,
   1.559 +                    endOffset: this.endOffset,
   1.560 +                    text: this._accessible.QueryInterface(Ci.nsIAccessibleText).
   1.561 +                          getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT)};
   1.562 +      let hypertextAcc = this._accessible.QueryInterface(Ci.nsIAccessibleHyperText);
   1.563 +
   1.564 +      // Iterate through the links in backwards order so text replacements don't
   1.565 +      // affect the offsets of links yet to be processed.
   1.566 +      for (let i = hypertextAcc.linkCount - 1; i >= 0; i--) {
   1.567 +        let link = hypertextAcc.getLinkAt(i);
   1.568 +        let linkText = '';
   1.569 +        if (link instanceof Ci.nsIAccessibleText) {
   1.570 +          linkText = link.QueryInterface(Ci.nsIAccessibleText).
   1.571 +                          getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT);
   1.572 +        }
   1.573 +
   1.574 +        let start = link.startIndex;
   1.575 +        let end = link.endIndex;
   1.576 +        for (let offset of ['startOffset', 'endOffset']) {
   1.577 +          if (this[offset] >= end) {
   1.578 +            result[offset] += linkText.length - (end - start);
   1.579 +          }
   1.580 +        }
   1.581 +        result.text = result.text.substring(0, start) + linkText +
   1.582 +                      result.text.substring(end);
   1.583 +      }
   1.584 +
   1.585 +      this._textAndAdjustedOffsets = result;
   1.586 +    }
   1.587 +
   1.588 +    return this._textAndAdjustedOffsets;
   1.589 +  },
   1.590 +
   1.591 +  /**
   1.592 +   * Get a list of |aAccessible|'s ancestry up to the root.
   1.593 +   * @param  {nsIAccessible} aAccessible.
   1.594 +   * @return {Array} Ancestry list.
   1.595 +   */
   1.596 +  _getAncestry: function _getAncestry(aAccessible) {
   1.597 +    let ancestry = [];
   1.598 +    let parent = aAccessible;
   1.599 +    try {
   1.600 +      while (parent && (parent = parent.parent)) {
   1.601 +       ancestry.push(parent);
   1.602 +      }
   1.603 +    } catch (x) {
   1.604 +      // A defunct accessible will raise an exception geting parent.
   1.605 +      Logger.debug('Failed to get parent:', x);
   1.606 +    }
   1.607 +    return ancestry.reverse();
   1.608 +  },
   1.609 +
   1.610 +  /**
   1.611 +   * A list of the old accessible's ancestry.
   1.612 +   */
   1.613 +  get oldAncestry() {
   1.614 +    if (!this._oldAncestry) {
   1.615 +      if (!this._oldAccessible || this._ignoreAncestry) {
   1.616 +        this._oldAncestry = [];
   1.617 +      } else {
   1.618 +        this._oldAncestry = this._getAncestry(this._oldAccessible);
   1.619 +        this._oldAncestry.push(this._oldAccessible);
   1.620 +      }
   1.621 +    }
   1.622 +    return this._oldAncestry;
   1.623 +  },
   1.624 +
   1.625 +  /**
   1.626 +   * A list of the current accessible's ancestry.
   1.627 +   */
   1.628 +  get currentAncestry() {
   1.629 +    if (!this._currentAncestry) {
   1.630 +      this._currentAncestry = this._ignoreAncestry ? [] :
   1.631 +        this._getAncestry(this.accessible);
   1.632 +    }
   1.633 +    return this._currentAncestry;
   1.634 +  },
   1.635 +
   1.636 +  /*
   1.637 +   * This is a list of the accessible's ancestry up to the common ancestor
   1.638 +   * of the accessible and the old accessible. It is useful for giving the
   1.639 +   * user context as to where they are in the heirarchy.
   1.640 +   */
   1.641 +  get newAncestry() {
   1.642 +    if (!this._newAncestry) {
   1.643 +      this._newAncestry = this._ignoreAncestry ? [] : [currentAncestor for (
   1.644 +        [index, currentAncestor] of Iterator(this.currentAncestry)) if (
   1.645 +          currentAncestor !== this.oldAncestry[index])];
   1.646 +    }
   1.647 +    return this._newAncestry;
   1.648 +  },
   1.649 +
   1.650 +  /*
   1.651 +   * Traverse the accessible's subtree in pre or post order.
   1.652 +   * It only includes the accessible's visible chidren.
   1.653 +   * Note: needSubtree is a function argument that can be used to determine
   1.654 +   * whether aAccessible's subtree is required.
   1.655 +   */
   1.656 +  _traverse: function _traverse(aAccessible, aPreorder, aStop) {
   1.657 +    if (aStop && aStop(aAccessible)) {
   1.658 +      return;
   1.659 +    }
   1.660 +    let child = aAccessible.firstChild;
   1.661 +    while (child) {
   1.662 +      let include;
   1.663 +      if (this._includeInvisible) {
   1.664 +        include = true;
   1.665 +      } else {
   1.666 +        include = !(Utils.getState(child).contains(States.INVISIBLE));
   1.667 +      }
   1.668 +      if (include) {
   1.669 +        if (aPreorder) {
   1.670 +          yield child;
   1.671 +          [yield node for (node of this._traverse(child, aPreorder, aStop))];
   1.672 +        } else {
   1.673 +          [yield node for (node of this._traverse(child, aPreorder, aStop))];
   1.674 +          yield child;
   1.675 +        }
   1.676 +      }
   1.677 +      child = child.nextSibling;
   1.678 +    }
   1.679 +  },
   1.680 +
   1.681 +  /*
   1.682 +   * A subtree generator function, used to generate a flattened
   1.683 +   * list of the accessible's subtree in pre or post order.
   1.684 +   * It only includes the accessible's visible chidren.
   1.685 +   * @param {boolean} aPreorder A flag for traversal order. If true, traverse
   1.686 +   * in preorder; if false, traverse in postorder.
   1.687 +   * @param {function} aStop An optional function, indicating whether subtree
   1.688 +   * traversal should stop.
   1.689 +   */
   1.690 +  subtreeGenerator: function subtreeGenerator(aPreorder, aStop) {
   1.691 +    return this._traverse(this.accessible, aPreorder, aStop);
   1.692 +  },
   1.693 +
   1.694 +  getCellInfo: function getCellInfo(aAccessible) {
   1.695 +    if (!this._cells) {
   1.696 +      this._cells = new WeakMap();
   1.697 +    }
   1.698 +
   1.699 +    let domNode = aAccessible.DOMNode;
   1.700 +    if (this._cells.has(domNode)) {
   1.701 +      return this._cells.get(domNode);
   1.702 +    }
   1.703 +
   1.704 +    let cellInfo = {};
   1.705 +    let getAccessibleCell = function getAccessibleCell(aAccessible) {
   1.706 +      if (!aAccessible) {
   1.707 +        return null;
   1.708 +      }
   1.709 +      if ([Roles.CELL, Roles.COLUMNHEADER, Roles.ROWHEADER].indexOf(
   1.710 +        aAccessible.role) < 0) {
   1.711 +          return null;
   1.712 +      }
   1.713 +      try {
   1.714 +        return aAccessible.QueryInterface(Ci.nsIAccessibleTableCell);
   1.715 +      } catch (x) {
   1.716 +        Logger.logException(x);
   1.717 +        return null;
   1.718 +      }
   1.719 +    };
   1.720 +    let getHeaders = function getHeaders(aHeaderCells) {
   1.721 +      let enumerator = aHeaderCells.enumerate();
   1.722 +      while (enumerator.hasMoreElements()) {
   1.723 +        yield enumerator.getNext().QueryInterface(Ci.nsIAccessible).name;
   1.724 +      }
   1.725 +    };
   1.726 +
   1.727 +    cellInfo.current = getAccessibleCell(aAccessible);
   1.728 +
   1.729 +    if (!cellInfo.current) {
   1.730 +      Logger.warning(aAccessible,
   1.731 +        'does not support nsIAccessibleTableCell interface.');
   1.732 +      this._cells.set(domNode, null);
   1.733 +      return null;
   1.734 +    }
   1.735 +
   1.736 +    let table = cellInfo.current.table;
   1.737 +    if (table.isProbablyForLayout()) {
   1.738 +      this._cells.set(domNode, null);
   1.739 +      return null;
   1.740 +    }
   1.741 +
   1.742 +    cellInfo.previous = null;
   1.743 +    let oldAncestry = this.oldAncestry.reverse();
   1.744 +    let ancestor = oldAncestry.shift();
   1.745 +    while (!cellInfo.previous && ancestor) {
   1.746 +      let cell = getAccessibleCell(ancestor);
   1.747 +      if (cell && cell.table === table) {
   1.748 +        cellInfo.previous = cell;
   1.749 +      }
   1.750 +      ancestor = oldAncestry.shift();
   1.751 +    }
   1.752 +
   1.753 +    if (cellInfo.previous) {
   1.754 +      cellInfo.rowChanged = cellInfo.current.rowIndex !==
   1.755 +        cellInfo.previous.rowIndex;
   1.756 +      cellInfo.columnChanged = cellInfo.current.columnIndex !==
   1.757 +        cellInfo.previous.columnIndex;
   1.758 +    } else {
   1.759 +      cellInfo.rowChanged = true;
   1.760 +      cellInfo.columnChanged = true;
   1.761 +    }
   1.762 +
   1.763 +    cellInfo.rowExtent = cellInfo.current.rowExtent;
   1.764 +    cellInfo.columnExtent = cellInfo.current.columnExtent;
   1.765 +    cellInfo.columnIndex = cellInfo.current.columnIndex;
   1.766 +    cellInfo.rowIndex = cellInfo.current.rowIndex;
   1.767 +
   1.768 +    cellInfo.columnHeaders = [];
   1.769 +    if (cellInfo.columnChanged && cellInfo.current.role !==
   1.770 +      Roles.COLUMNHEADER) {
   1.771 +      cellInfo.columnHeaders = [headers for (headers of getHeaders(
   1.772 +        cellInfo.current.columnHeaderCells))];
   1.773 +    }
   1.774 +    cellInfo.rowHeaders = [];
   1.775 +    if (cellInfo.rowChanged && cellInfo.current.role === Roles.CELL) {
   1.776 +      cellInfo.rowHeaders = [headers for (headers of getHeaders(
   1.777 +        cellInfo.current.rowHeaderCells))];
   1.778 +    }
   1.779 +
   1.780 +    this._cells.set(domNode, cellInfo);
   1.781 +    return cellInfo;
   1.782 +  },
   1.783 +
   1.784 +  get bounds() {
   1.785 +    if (!this._bounds) {
   1.786 +      this._bounds = Utils.getBounds(this.accessibleForBounds);
   1.787 +    }
   1.788 +
   1.789 +    return this._bounds.clone();
   1.790 +  },
   1.791 +
   1.792 +  _isDefunct: function _isDefunct(aAccessible) {
   1.793 +    try {
   1.794 +      return Utils.getState(aAccessible).contains(States.DEFUNCT);
   1.795 +    } catch (x) {
   1.796 +      return true;
   1.797 +    }
   1.798 +  }
   1.799 +};
   1.800 +
   1.801 +this.PrefCache = function PrefCache(aName, aCallback, aRunCallbackNow) {
   1.802 +  this.name = aName;
   1.803 +  this.callback = aCallback;
   1.804 +
   1.805 +  let branch = Services.prefs;
   1.806 +  this.value = this._getValue(branch);
   1.807 +
   1.808 +  if (this.callback && aRunCallbackNow) {
   1.809 +    try {
   1.810 +      this.callback(this.name, this.value);
   1.811 +    } catch (x) {
   1.812 +      Logger.logException(x);
   1.813 +    }
   1.814 +  }
   1.815 +
   1.816 +  branch.addObserver(aName, this, true);
   1.817 +};
   1.818 +
   1.819 +PrefCache.prototype = {
   1.820 +  _getValue: function _getValue(aBranch) {
   1.821 +    try {
   1.822 +      if (!this.type) {
   1.823 +        this.type = aBranch.getPrefType(this.name);
   1.824 +      }
   1.825 +      switch (this.type) {
   1.826 +        case Ci.nsIPrefBranch.PREF_STRING:
   1.827 +          return aBranch.getCharPref(this.name);
   1.828 +        case Ci.nsIPrefBranch.PREF_INT:
   1.829 +          return aBranch.getIntPref(this.name);
   1.830 +        case Ci.nsIPrefBranch.PREF_BOOL:
   1.831 +          return aBranch.getBoolPref(this.name);
   1.832 +        default:
   1.833 +          return null;
   1.834 +      }
   1.835 +    } catch (x) {
   1.836 +      // Pref does not exist.
   1.837 +      return null;
   1.838 +    }
   1.839 +  },
   1.840 +
   1.841 +  observe: function observe(aSubject, aTopic, aData) {
   1.842 +    this.value = this._getValue(aSubject.QueryInterface(Ci.nsIPrefBranch));
   1.843 +    if (this.callback) {
   1.844 +      try {
   1.845 +        this.callback(this.name, this.value);
   1.846 +      } catch (x) {
   1.847 +        Logger.logException(x);
   1.848 +      }
   1.849 +    }
   1.850 +  },
   1.851 +
   1.852 +  QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
   1.853 +                                          Ci.nsISupportsWeakReference])
   1.854 +};
   1.855 +
   1.856 +this.SettingCache = function SettingCache(aName, aCallback, aOptions = {}) {
   1.857 +  this.value = aOptions.defaultValue;
   1.858 +  let runCallback = () => {
   1.859 +    if (aCallback) {
   1.860 +      aCallback(aName, this.value);
   1.861 +      if (aOptions.callbackOnce) {
   1.862 +        runCallback = () => {};
   1.863 +      }
   1.864 +    }
   1.865 +  };
   1.866 +
   1.867 +  let settings = Utils.win.navigator.mozSettings;
   1.868 +  if (!settings) {
   1.869 +    if (aOptions.callbackNow) {
   1.870 +      runCallback();
   1.871 +    }
   1.872 +    return;
   1.873 +  }
   1.874 +
   1.875 +
   1.876 +  let lock = settings.createLock();
   1.877 +  let req = lock.get(aName);
   1.878 +
   1.879 +  req.addEventListener('success', () => {
   1.880 +    this.value = req.result[aName] == undefined ? aOptions.defaultValue : req.result[aName];
   1.881 +    if (aOptions.callbackNow) {
   1.882 +      runCallback();
   1.883 +    }
   1.884 +  });
   1.885 +
   1.886 +  settings.addObserver(aName,
   1.887 +                       (evt) => {
   1.888 +                         this.value = evt.settingValue;
   1.889 +                         runCallback();
   1.890 +                       });
   1.891 +};

mercurial