michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: 'use strict'; michael@0: michael@0: const Cu = Components.utils; michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'Services', michael@0: 'resource://gre/modules/Services.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'Rect', michael@0: 'resource://gre/modules/Geometry.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'Roles', michael@0: 'resource://gre/modules/accessibility/Constants.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'Events', michael@0: 'resource://gre/modules/accessibility/Constants.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'Relations', michael@0: 'resource://gre/modules/accessibility/Constants.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'States', michael@0: 'resource://gre/modules/accessibility/Constants.jsm'); michael@0: michael@0: this.EXPORTED_SYMBOLS = ['Utils', 'Logger', 'PivotContext', 'PrefCache', 'SettingCache']; michael@0: michael@0: this.Utils = { michael@0: _buildAppMap: { michael@0: '{3c2e2abc-06d4-11e1-ac3b-374f68613e61}': 'b2g', michael@0: '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': 'browser', michael@0: '{aa3c5121-dab2-40e2-81ca-7ea25febc110}': 'mobile/android', michael@0: '{a23983c0-fd0e-11dc-95ff-0800200c9a66}': 'mobile/xul' michael@0: }, michael@0: michael@0: init: function Utils_init(aWindow) { michael@0: if (this._win) michael@0: // XXX: only supports attaching to one window now. michael@0: throw new Error('Only one top-level window could used with AccessFu'); michael@0: michael@0: this._win = Cu.getWeakReference(aWindow); michael@0: }, michael@0: michael@0: uninit: function Utils_uninit() { michael@0: if (!this._win) { michael@0: return; michael@0: } michael@0: delete this._win; michael@0: }, michael@0: michael@0: get win() { michael@0: if (!this._win) { michael@0: return null; michael@0: } michael@0: return this._win.get(); michael@0: }, michael@0: michael@0: get AccRetrieval() { michael@0: if (!this._AccRetrieval) { michael@0: this._AccRetrieval = Cc['@mozilla.org/accessibleRetrieval;1']. michael@0: getService(Ci.nsIAccessibleRetrieval); michael@0: } michael@0: michael@0: return this._AccRetrieval; michael@0: }, michael@0: michael@0: set MozBuildApp(value) { michael@0: this._buildApp = value; michael@0: }, michael@0: michael@0: get MozBuildApp() { michael@0: if (!this._buildApp) michael@0: this._buildApp = this._buildAppMap[Services.appinfo.ID]; michael@0: return this._buildApp; michael@0: }, michael@0: michael@0: get OS() { michael@0: if (!this._OS) michael@0: this._OS = Services.appinfo.OS; michael@0: return this._OS; michael@0: }, michael@0: michael@0: get widgetToolkit() { michael@0: if (!this._widgetToolkit) michael@0: this._widgetToolkit = Services.appinfo.widgetToolkit; michael@0: return this._widgetToolkit; michael@0: }, michael@0: michael@0: get ScriptName() { michael@0: if (!this._ScriptName) michael@0: this._ScriptName = michael@0: (Services.appinfo.processType == 2) ? 'AccessFuContent' : 'AccessFu'; michael@0: return this._ScriptName; michael@0: }, michael@0: michael@0: get AndroidSdkVersion() { michael@0: if (!this._AndroidSdkVersion) { michael@0: if (Services.appinfo.OS == 'Android') { michael@0: this._AndroidSdkVersion = Services.sysinfo.getPropertyAsInt32('version'); michael@0: } else { michael@0: // Most useful in desktop debugging. michael@0: this._AndroidSdkVersion = 16; michael@0: } michael@0: } michael@0: return this._AndroidSdkVersion; michael@0: }, michael@0: michael@0: set AndroidSdkVersion(value) { michael@0: // When we want to mimic another version. michael@0: this._AndroidSdkVersion = value; michael@0: }, michael@0: michael@0: get BrowserApp() { michael@0: if (!this.win) { michael@0: return null; michael@0: } michael@0: switch (this.MozBuildApp) { michael@0: case 'mobile/android': michael@0: return this.win.BrowserApp; michael@0: case 'browser': michael@0: return this.win.gBrowser; michael@0: case 'b2g': michael@0: return this.win.shell; michael@0: default: michael@0: return null; michael@0: } michael@0: }, michael@0: michael@0: get CurrentBrowser() { michael@0: if (!this.BrowserApp) { michael@0: return null; michael@0: } michael@0: if (this.MozBuildApp == 'b2g') michael@0: return this.BrowserApp.contentBrowser; michael@0: return this.BrowserApp.selectedBrowser; michael@0: }, michael@0: michael@0: get CurrentContentDoc() { michael@0: let browser = this.CurrentBrowser; michael@0: return browser ? browser.contentDocument : null; michael@0: }, michael@0: michael@0: get AllMessageManagers() { michael@0: let messageManagers = []; michael@0: michael@0: for (let i = 0; i < this.win.messageManager.childCount; i++) michael@0: messageManagers.push(this.win.messageManager.getChildAt(i)); michael@0: michael@0: let document = this.CurrentContentDoc; michael@0: michael@0: if (document) { michael@0: let remoteframes = document.querySelectorAll('iframe'); michael@0: michael@0: for (let i = 0; i < remoteframes.length; ++i) { michael@0: let mm = this.getMessageManager(remoteframes[i]); michael@0: if (mm) { michael@0: messageManagers.push(mm); michael@0: } michael@0: } michael@0: michael@0: } michael@0: michael@0: return messageManagers; michael@0: }, michael@0: michael@0: get isContentProcess() { michael@0: delete this.isContentProcess; michael@0: this.isContentProcess = michael@0: Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT; michael@0: return this.isContentProcess; michael@0: }, michael@0: michael@0: get stringBundle() { michael@0: delete this.stringBundle; michael@0: this.stringBundle = Services.strings.createBundle( michael@0: 'chrome://global/locale/AccessFu.properties'); michael@0: return this.stringBundle; michael@0: }, michael@0: michael@0: getMessageManager: function getMessageManager(aBrowser) { michael@0: try { michael@0: return aBrowser.QueryInterface(Ci.nsIFrameLoaderOwner). michael@0: frameLoader.messageManager; michael@0: } catch (x) { michael@0: Logger.logException(x); michael@0: return null; michael@0: } michael@0: }, michael@0: michael@0: getViewport: function getViewport(aWindow) { michael@0: switch (this.MozBuildApp) { michael@0: case 'mobile/android': michael@0: return aWindow.BrowserApp.selectedTab.getViewport(); michael@0: default: michael@0: return null; michael@0: } michael@0: }, michael@0: michael@0: getState: function getState(aAccessibleOrEvent) { michael@0: if (aAccessibleOrEvent instanceof Ci.nsIAccessibleStateChangeEvent) { michael@0: return new State( michael@0: aAccessibleOrEvent.isExtraState ? 0 : aAccessibleOrEvent.state, michael@0: aAccessibleOrEvent.isExtraState ? aAccessibleOrEvent.state : 0); michael@0: } else { michael@0: let state = {}; michael@0: let extState = {}; michael@0: aAccessibleOrEvent.getState(state, extState); michael@0: return new State(state.value, extState.value); michael@0: } michael@0: }, michael@0: michael@0: getAttributes: function getAttributes(aAccessible) { michael@0: let attributes = {}; michael@0: michael@0: if (aAccessible && aAccessible.attributes) { michael@0: let attributesEnum = aAccessible.attributes.enumerate(); michael@0: michael@0: // Populate |attributes| object with |aAccessible|'s attribute key-value michael@0: // pairs. michael@0: while (attributesEnum.hasMoreElements()) { michael@0: let attribute = attributesEnum.getNext().QueryInterface( michael@0: Ci.nsIPropertyElement); michael@0: attributes[attribute.key] = attribute.value; michael@0: } michael@0: } michael@0: michael@0: return attributes; michael@0: }, michael@0: michael@0: getVirtualCursor: function getVirtualCursor(aDocument) { michael@0: let doc = (aDocument instanceof Ci.nsIAccessible) ? aDocument : michael@0: this.AccRetrieval.getAccessibleFor(aDocument); michael@0: michael@0: return doc.QueryInterface(Ci.nsIAccessibleDocument).virtualCursor; michael@0: }, michael@0: michael@0: getBounds: function getBounds(aAccessible) { michael@0: let objX = {}, objY = {}, objW = {}, objH = {}; michael@0: aAccessible.getBounds(objX, objY, objW, objH); michael@0: return new Rect(objX.value, objY.value, objW.value, objH.value); michael@0: }, michael@0: michael@0: getTextBounds: function getTextBounds(aAccessible, aStart, aEnd) { michael@0: let accText = aAccessible.QueryInterface(Ci.nsIAccessibleText); michael@0: let objX = {}, objY = {}, objW = {}, objH = {}; michael@0: accText.getRangeExtents(aStart, aEnd, objX, objY, objW, objH, michael@0: Ci.nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE); michael@0: return new Rect(objX.value, objY.value, objW.value, objH.value); michael@0: }, michael@0: michael@0: /** michael@0: * Get current display DPI. michael@0: */ michael@0: get dpi() { michael@0: delete this.dpi; michael@0: this.dpi = this.win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface( michael@0: Ci.nsIDOMWindowUtils).displayDPI; michael@0: return this.dpi; michael@0: }, michael@0: michael@0: isInSubtree: function isInSubtree(aAccessible, aSubTreeRoot) { michael@0: let acc = aAccessible; michael@0: while (acc) { michael@0: if (acc == aSubTreeRoot) { michael@0: return true; michael@0: } michael@0: michael@0: try { michael@0: acc = acc.parent; michael@0: } catch (x) { michael@0: Logger.debug('Failed to get parent:', x); michael@0: acc = null; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: }, michael@0: michael@0: inHiddenSubtree: function inHiddenSubtree(aAccessible) { michael@0: for (let acc=aAccessible; acc; acc=acc.parent) { michael@0: let hidden = Utils.getAttributes(acc).hidden; michael@0: if (hidden && JSON.parse(hidden)) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: }, michael@0: michael@0: isAliveAndVisible: function isAliveAndVisible(aAccessible, aIsOnScreen) { michael@0: if (!aAccessible) { michael@0: return false; michael@0: } michael@0: michael@0: try { michael@0: let state = this.getState(aAccessible); michael@0: if (state.contains(States.DEFUNCT) || state.contains(States.INVISIBLE) || michael@0: (aIsOnScreen && state.contains(States.OFFSCREEN)) || michael@0: Utils.inHiddenSubtree(aAccessible)) { michael@0: return false; michael@0: } michael@0: } catch (x) { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: }, michael@0: michael@0: matchAttributeValue: function matchAttributeValue(aAttributeValue, values) { michael@0: let attrSet = new Set(aAttributeValue.split(' ')); michael@0: for (let value of values) { michael@0: if (attrSet.has(value)) { michael@0: return value; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: getLandmarkName: function getLandmarkName(aAccessible) { michael@0: const landmarks = [ michael@0: 'banner', michael@0: 'complementary', michael@0: 'contentinfo', michael@0: 'main', michael@0: 'navigation', michael@0: 'search' michael@0: ]; michael@0: let roles = this.getAttributes(aAccessible)['xml-roles']; michael@0: if (!roles) { michael@0: return; michael@0: } michael@0: michael@0: // Looking up a role that would match a landmark. michael@0: return this.matchAttributeValue(roles, landmarks); michael@0: }, michael@0: michael@0: getEmbeddedControl: function getEmbeddedControl(aLabel) { michael@0: if (aLabel) { michael@0: let relation = aLabel.getRelationByType(Relations.LABEL_FOR); michael@0: for (let i = 0; i < relation.targetsCount; i++) { michael@0: let target = relation.getTarget(i); michael@0: if (target.parent === aLabel) { michael@0: return target; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return null; michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * State object used internally to process accessible's states. michael@0: * @param {Number} aBase Base state. michael@0: * @param {Number} aExtended Extended state. michael@0: */ michael@0: function State(aBase, aExtended) { michael@0: this.base = aBase; michael@0: this.extended = aExtended; michael@0: } michael@0: michael@0: State.prototype = { michael@0: contains: function State_contains(other) { michael@0: return !!(this.base & other.base || this.extended & other.extended); michael@0: }, michael@0: toString: function State_toString() { michael@0: let stateStrings = Utils.AccRetrieval. michael@0: getStringStates(this.base, this.extended); michael@0: let statesArray = new Array(stateStrings.length); michael@0: for (let i = 0; i < statesArray.length; i++) { michael@0: statesArray[i] = stateStrings.item(i); michael@0: } michael@0: return '[' + statesArray.join(', ') + ']'; michael@0: } michael@0: }; michael@0: michael@0: this.Logger = { michael@0: DEBUG: 0, michael@0: INFO: 1, michael@0: WARNING: 2, michael@0: ERROR: 3, michael@0: _LEVEL_NAMES: ['DEBUG', 'INFO', 'WARNING', 'ERROR'], michael@0: michael@0: logLevel: 1, // INFO; michael@0: michael@0: test: false, michael@0: michael@0: log: function log(aLogLevel) { michael@0: if (aLogLevel < this.logLevel) michael@0: return; michael@0: michael@0: let args = Array.prototype.slice.call(arguments, 1); michael@0: let message = (typeof(args[0]) === 'function' ? args[0]() : args).join(' '); michael@0: message = '[' + Utils.ScriptName + '] ' + this._LEVEL_NAMES[aLogLevel] + michael@0: ' ' + message + '\n'; michael@0: dump(message); michael@0: // Note: used for testing purposes. If |this.test| is true, also log to michael@0: // the console service. michael@0: if (this.test) { michael@0: try { michael@0: Services.console.logStringMessage(message); michael@0: } catch (ex) { michael@0: // There was an exception logging to the console service. michael@0: } michael@0: } michael@0: }, michael@0: michael@0: info: function info() { michael@0: this.log.apply( michael@0: this, [this.INFO].concat(Array.prototype.slice.call(arguments))); michael@0: }, michael@0: michael@0: debug: function debug() { michael@0: this.log.apply( michael@0: this, [this.DEBUG].concat(Array.prototype.slice.call(arguments))); michael@0: }, michael@0: michael@0: warning: function warning() { michael@0: this.log.apply( michael@0: this, [this.WARNING].concat(Array.prototype.slice.call(arguments))); michael@0: }, michael@0: michael@0: error: function error() { michael@0: this.log.apply( michael@0: this, [this.ERROR].concat(Array.prototype.slice.call(arguments))); michael@0: }, michael@0: michael@0: logException: function logException( michael@0: aException, aErrorMessage = 'An exception has occured') { michael@0: try { michael@0: let stackMessage = ''; michael@0: if (aException.stack) { michael@0: stackMessage = ' ' + aException.stack.replace(/\n/g, '\n '); michael@0: } else if (aException.location) { michael@0: let frame = aException.location; michael@0: let stackLines = []; michael@0: while (frame && frame.lineNumber) { michael@0: stackLines.push( michael@0: ' ' + frame.name + '@' + frame.filename + ':' + frame.lineNumber); michael@0: frame = frame.caller; michael@0: } michael@0: stackMessage = stackLines.join('\n'); michael@0: } else { michael@0: stackMessage = '(' + aException.fileName + ':' + aException.lineNumber + ')'; michael@0: } michael@0: this.error(aErrorMessage + ':\n ' + michael@0: aException.message + '\n' + michael@0: stackMessage); michael@0: } catch (x) { michael@0: this.error(x); michael@0: } michael@0: }, michael@0: michael@0: accessibleToString: function accessibleToString(aAccessible) { michael@0: let str = '[ defunct ]'; michael@0: try { michael@0: str = '[ ' + Utils.AccRetrieval.getStringRole(aAccessible.role) + michael@0: ' | ' + aAccessible.name + ' ]'; michael@0: } catch (x) { michael@0: } michael@0: michael@0: return str; michael@0: }, michael@0: michael@0: eventToString: function eventToString(aEvent) { michael@0: let str = Utils.AccRetrieval.getStringEventType(aEvent.eventType); michael@0: if (aEvent.eventType == Events.STATE_CHANGE) { michael@0: let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent); michael@0: let stateStrings = event.isExtraState ? michael@0: Utils.AccRetrieval.getStringStates(0, event.state) : michael@0: Utils.AccRetrieval.getStringStates(event.state, 0); michael@0: str += ' (' + stateStrings.item(0) + ')'; michael@0: } michael@0: michael@0: if (aEvent.eventType == Events.VIRTUALCURSOR_CHANGED) { michael@0: let event = aEvent.QueryInterface( michael@0: Ci.nsIAccessibleVirtualCursorChangeEvent); michael@0: let pivot = aEvent.accessible.QueryInterface( michael@0: Ci.nsIAccessibleDocument).virtualCursor; michael@0: str += ' (' + this.accessibleToString(event.oldAccessible) + ' -> ' + michael@0: this.accessibleToString(pivot.position) + ')'; michael@0: } michael@0: michael@0: return str; michael@0: }, michael@0: michael@0: statesToString: function statesToString(aAccessible) { michael@0: return Utils.getState(aAccessible).toString(); michael@0: }, michael@0: michael@0: dumpTree: function dumpTree(aLogLevel, aRootAccessible) { michael@0: if (aLogLevel < this.logLevel) michael@0: return; michael@0: michael@0: this._dumpTreeInternal(aLogLevel, aRootAccessible, 0); michael@0: }, michael@0: michael@0: _dumpTreeInternal: function _dumpTreeInternal(aLogLevel, aAccessible, aIndent) { michael@0: let indentStr = ''; michael@0: for (var i=0; i < aIndent; i++) michael@0: indentStr += ' '; michael@0: this.log(aLogLevel, indentStr, michael@0: this.accessibleToString(aAccessible), michael@0: '(' + this.statesToString(aAccessible) + ')'); michael@0: for (var i=0; i < aAccessible.childCount; i++) michael@0: this._dumpTreeInternal(aLogLevel, aAccessible.getChildAt(i), aIndent + 1); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * PivotContext: An object that generates and caches context information michael@0: * for a given accessible and its relationship with another accessible. michael@0: * michael@0: * If the given accessible is a label for a nested control, then this michael@0: * context will represent the nested control instead of the label. michael@0: * With the exception of bounds calculation, which will use the containing michael@0: * label. In this case the |accessible| field would be the embedded control, michael@0: * and the |accessibleForBounds| field would be the label. michael@0: */ michael@0: this.PivotContext = function PivotContext(aAccessible, aOldAccessible, michael@0: aStartOffset, aEndOffset, aIgnoreAncestry = false, michael@0: aIncludeInvisible = false) { michael@0: this._accessible = aAccessible; michael@0: this._nestedControl = Utils.getEmbeddedControl(aAccessible); michael@0: this._oldAccessible = michael@0: this._isDefunct(aOldAccessible) ? null : aOldAccessible; michael@0: this.startOffset = aStartOffset; michael@0: this.endOffset = aEndOffset; michael@0: this._ignoreAncestry = aIgnoreAncestry; michael@0: this._includeInvisible = aIncludeInvisible; michael@0: } michael@0: michael@0: PivotContext.prototype = { michael@0: get accessible() { michael@0: // If the current pivot accessible has a nested control, michael@0: // make this context use it publicly. michael@0: return this._nestedControl || this._accessible; michael@0: }, michael@0: michael@0: get oldAccessible() { michael@0: return this._oldAccessible; michael@0: }, michael@0: michael@0: get isNestedControl() { michael@0: return !!this._nestedControl; michael@0: }, michael@0: michael@0: get accessibleForBounds() { michael@0: return this._accessible; michael@0: }, michael@0: michael@0: get textAndAdjustedOffsets() { michael@0: if (this.startOffset === -1 && this.endOffset === -1) { michael@0: return null; michael@0: } michael@0: michael@0: if (!this._textAndAdjustedOffsets) { michael@0: let result = {startOffset: this.startOffset, michael@0: endOffset: this.endOffset, michael@0: text: this._accessible.QueryInterface(Ci.nsIAccessibleText). michael@0: getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT)}; michael@0: let hypertextAcc = this._accessible.QueryInterface(Ci.nsIAccessibleHyperText); michael@0: michael@0: // Iterate through the links in backwards order so text replacements don't michael@0: // affect the offsets of links yet to be processed. michael@0: for (let i = hypertextAcc.linkCount - 1; i >= 0; i--) { michael@0: let link = hypertextAcc.getLinkAt(i); michael@0: let linkText = ''; michael@0: if (link instanceof Ci.nsIAccessibleText) { michael@0: linkText = link.QueryInterface(Ci.nsIAccessibleText). michael@0: getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT); michael@0: } michael@0: michael@0: let start = link.startIndex; michael@0: let end = link.endIndex; michael@0: for (let offset of ['startOffset', 'endOffset']) { michael@0: if (this[offset] >= end) { michael@0: result[offset] += linkText.length - (end - start); michael@0: } michael@0: } michael@0: result.text = result.text.substring(0, start) + linkText + michael@0: result.text.substring(end); michael@0: } michael@0: michael@0: this._textAndAdjustedOffsets = result; michael@0: } michael@0: michael@0: return this._textAndAdjustedOffsets; michael@0: }, michael@0: michael@0: /** michael@0: * Get a list of |aAccessible|'s ancestry up to the root. michael@0: * @param {nsIAccessible} aAccessible. michael@0: * @return {Array} Ancestry list. michael@0: */ michael@0: _getAncestry: function _getAncestry(aAccessible) { michael@0: let ancestry = []; michael@0: let parent = aAccessible; michael@0: try { michael@0: while (parent && (parent = parent.parent)) { michael@0: ancestry.push(parent); michael@0: } michael@0: } catch (x) { michael@0: // A defunct accessible will raise an exception geting parent. michael@0: Logger.debug('Failed to get parent:', x); michael@0: } michael@0: return ancestry.reverse(); michael@0: }, michael@0: michael@0: /** michael@0: * A list of the old accessible's ancestry. michael@0: */ michael@0: get oldAncestry() { michael@0: if (!this._oldAncestry) { michael@0: if (!this._oldAccessible || this._ignoreAncestry) { michael@0: this._oldAncestry = []; michael@0: } else { michael@0: this._oldAncestry = this._getAncestry(this._oldAccessible); michael@0: this._oldAncestry.push(this._oldAccessible); michael@0: } michael@0: } michael@0: return this._oldAncestry; michael@0: }, michael@0: michael@0: /** michael@0: * A list of the current accessible's ancestry. michael@0: */ michael@0: get currentAncestry() { michael@0: if (!this._currentAncestry) { michael@0: this._currentAncestry = this._ignoreAncestry ? [] : michael@0: this._getAncestry(this.accessible); michael@0: } michael@0: return this._currentAncestry; michael@0: }, michael@0: michael@0: /* michael@0: * This is a list of the accessible's ancestry up to the common ancestor michael@0: * of the accessible and the old accessible. It is useful for giving the michael@0: * user context as to where they are in the heirarchy. michael@0: */ michael@0: get newAncestry() { michael@0: if (!this._newAncestry) { michael@0: this._newAncestry = this._ignoreAncestry ? [] : [currentAncestor for ( michael@0: [index, currentAncestor] of Iterator(this.currentAncestry)) if ( michael@0: currentAncestor !== this.oldAncestry[index])]; michael@0: } michael@0: return this._newAncestry; michael@0: }, michael@0: michael@0: /* michael@0: * Traverse the accessible's subtree in pre or post order. michael@0: * It only includes the accessible's visible chidren. michael@0: * Note: needSubtree is a function argument that can be used to determine michael@0: * whether aAccessible's subtree is required. michael@0: */ michael@0: _traverse: function _traverse(aAccessible, aPreorder, aStop) { michael@0: if (aStop && aStop(aAccessible)) { michael@0: return; michael@0: } michael@0: let child = aAccessible.firstChild; michael@0: while (child) { michael@0: let include; michael@0: if (this._includeInvisible) { michael@0: include = true; michael@0: } else { michael@0: include = !(Utils.getState(child).contains(States.INVISIBLE)); michael@0: } michael@0: if (include) { michael@0: if (aPreorder) { michael@0: yield child; michael@0: [yield node for (node of this._traverse(child, aPreorder, aStop))]; michael@0: } else { michael@0: [yield node for (node of this._traverse(child, aPreorder, aStop))]; michael@0: yield child; michael@0: } michael@0: } michael@0: child = child.nextSibling; michael@0: } michael@0: }, michael@0: michael@0: /* michael@0: * A subtree generator function, used to generate a flattened michael@0: * list of the accessible's subtree in pre or post order. michael@0: * It only includes the accessible's visible chidren. michael@0: * @param {boolean} aPreorder A flag for traversal order. If true, traverse michael@0: * in preorder; if false, traverse in postorder. michael@0: * @param {function} aStop An optional function, indicating whether subtree michael@0: * traversal should stop. michael@0: */ michael@0: subtreeGenerator: function subtreeGenerator(aPreorder, aStop) { michael@0: return this._traverse(this.accessible, aPreorder, aStop); michael@0: }, michael@0: michael@0: getCellInfo: function getCellInfo(aAccessible) { michael@0: if (!this._cells) { michael@0: this._cells = new WeakMap(); michael@0: } michael@0: michael@0: let domNode = aAccessible.DOMNode; michael@0: if (this._cells.has(domNode)) { michael@0: return this._cells.get(domNode); michael@0: } michael@0: michael@0: let cellInfo = {}; michael@0: let getAccessibleCell = function getAccessibleCell(aAccessible) { michael@0: if (!aAccessible) { michael@0: return null; michael@0: } michael@0: if ([Roles.CELL, Roles.COLUMNHEADER, Roles.ROWHEADER].indexOf( michael@0: aAccessible.role) < 0) { michael@0: return null; michael@0: } michael@0: try { michael@0: return aAccessible.QueryInterface(Ci.nsIAccessibleTableCell); michael@0: } catch (x) { michael@0: Logger.logException(x); michael@0: return null; michael@0: } michael@0: }; michael@0: let getHeaders = function getHeaders(aHeaderCells) { michael@0: let enumerator = aHeaderCells.enumerate(); michael@0: while (enumerator.hasMoreElements()) { michael@0: yield enumerator.getNext().QueryInterface(Ci.nsIAccessible).name; michael@0: } michael@0: }; michael@0: michael@0: cellInfo.current = getAccessibleCell(aAccessible); michael@0: michael@0: if (!cellInfo.current) { michael@0: Logger.warning(aAccessible, michael@0: 'does not support nsIAccessibleTableCell interface.'); michael@0: this._cells.set(domNode, null); michael@0: return null; michael@0: } michael@0: michael@0: let table = cellInfo.current.table; michael@0: if (table.isProbablyForLayout()) { michael@0: this._cells.set(domNode, null); michael@0: return null; michael@0: } michael@0: michael@0: cellInfo.previous = null; michael@0: let oldAncestry = this.oldAncestry.reverse(); michael@0: let ancestor = oldAncestry.shift(); michael@0: while (!cellInfo.previous && ancestor) { michael@0: let cell = getAccessibleCell(ancestor); michael@0: if (cell && cell.table === table) { michael@0: cellInfo.previous = cell; michael@0: } michael@0: ancestor = oldAncestry.shift(); michael@0: } michael@0: michael@0: if (cellInfo.previous) { michael@0: cellInfo.rowChanged = cellInfo.current.rowIndex !== michael@0: cellInfo.previous.rowIndex; michael@0: cellInfo.columnChanged = cellInfo.current.columnIndex !== michael@0: cellInfo.previous.columnIndex; michael@0: } else { michael@0: cellInfo.rowChanged = true; michael@0: cellInfo.columnChanged = true; michael@0: } michael@0: michael@0: cellInfo.rowExtent = cellInfo.current.rowExtent; michael@0: cellInfo.columnExtent = cellInfo.current.columnExtent; michael@0: cellInfo.columnIndex = cellInfo.current.columnIndex; michael@0: cellInfo.rowIndex = cellInfo.current.rowIndex; michael@0: michael@0: cellInfo.columnHeaders = []; michael@0: if (cellInfo.columnChanged && cellInfo.current.role !== michael@0: Roles.COLUMNHEADER) { michael@0: cellInfo.columnHeaders = [headers for (headers of getHeaders( michael@0: cellInfo.current.columnHeaderCells))]; michael@0: } michael@0: cellInfo.rowHeaders = []; michael@0: if (cellInfo.rowChanged && cellInfo.current.role === Roles.CELL) { michael@0: cellInfo.rowHeaders = [headers for (headers of getHeaders( michael@0: cellInfo.current.rowHeaderCells))]; michael@0: } michael@0: michael@0: this._cells.set(domNode, cellInfo); michael@0: return cellInfo; michael@0: }, michael@0: michael@0: get bounds() { michael@0: if (!this._bounds) { michael@0: this._bounds = Utils.getBounds(this.accessibleForBounds); michael@0: } michael@0: michael@0: return this._bounds.clone(); michael@0: }, michael@0: michael@0: _isDefunct: function _isDefunct(aAccessible) { michael@0: try { michael@0: return Utils.getState(aAccessible).contains(States.DEFUNCT); michael@0: } catch (x) { michael@0: return true; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: this.PrefCache = function PrefCache(aName, aCallback, aRunCallbackNow) { michael@0: this.name = aName; michael@0: this.callback = aCallback; michael@0: michael@0: let branch = Services.prefs; michael@0: this.value = this._getValue(branch); michael@0: michael@0: if (this.callback && aRunCallbackNow) { michael@0: try { michael@0: this.callback(this.name, this.value); michael@0: } catch (x) { michael@0: Logger.logException(x); michael@0: } michael@0: } michael@0: michael@0: branch.addObserver(aName, this, true); michael@0: }; michael@0: michael@0: PrefCache.prototype = { michael@0: _getValue: function _getValue(aBranch) { michael@0: try { michael@0: if (!this.type) { michael@0: this.type = aBranch.getPrefType(this.name); michael@0: } michael@0: switch (this.type) { michael@0: case Ci.nsIPrefBranch.PREF_STRING: michael@0: return aBranch.getCharPref(this.name); michael@0: case Ci.nsIPrefBranch.PREF_INT: michael@0: return aBranch.getIntPref(this.name); michael@0: case Ci.nsIPrefBranch.PREF_BOOL: michael@0: return aBranch.getBoolPref(this.name); michael@0: default: michael@0: return null; michael@0: } michael@0: } catch (x) { michael@0: // Pref does not exist. michael@0: return null; michael@0: } michael@0: }, michael@0: michael@0: observe: function observe(aSubject, aTopic, aData) { michael@0: this.value = this._getValue(aSubject.QueryInterface(Ci.nsIPrefBranch)); michael@0: if (this.callback) { michael@0: try { michael@0: this.callback(this.name, this.value); michael@0: } catch (x) { michael@0: Logger.logException(x); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver, michael@0: Ci.nsISupportsWeakReference]) michael@0: }; michael@0: michael@0: this.SettingCache = function SettingCache(aName, aCallback, aOptions = {}) { michael@0: this.value = aOptions.defaultValue; michael@0: let runCallback = () => { michael@0: if (aCallback) { michael@0: aCallback(aName, this.value); michael@0: if (aOptions.callbackOnce) { michael@0: runCallback = () => {}; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: let settings = Utils.win.navigator.mozSettings; michael@0: if (!settings) { michael@0: if (aOptions.callbackNow) { michael@0: runCallback(); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: michael@0: let lock = settings.createLock(); michael@0: let req = lock.get(aName); michael@0: michael@0: req.addEventListener('success', () => { michael@0: this.value = req.result[aName] == undefined ? aOptions.defaultValue : req.result[aName]; michael@0: if (aOptions.callbackNow) { michael@0: runCallback(); michael@0: } michael@0: }); michael@0: michael@0: settings.addObserver(aName, michael@0: (evt) => { michael@0: this.value = evt.settingValue; michael@0: runCallback(); michael@0: }); michael@0: };