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.

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

mercurial