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