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 +};