accessible/src/jsat/OutputGenerator.jsm

branch
TOR_BUG_9701
changeset 3
141e0f1194b1
equal deleted inserted replaced
-1:000000000000 0:045ada98e400
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/. */
4
5 'use strict';
6
7 const Cc = Components.classes;
8 const Ci = Components.interfaces;
9 const Cu = Components.utils;
10 const Cr = Components.results;
11
12 const INCLUDE_DESC = 0x01;
13 const INCLUDE_NAME = 0x02;
14 const INCLUDE_VALUE = 0x04;
15 const INCLUDE_CUSTOM = 0x08;
16 const NAME_FROM_SUBTREE_RULE = 0x10;
17 const IGNORE_EXPLICIT_NAME = 0x20;
18
19 const OUTPUT_DESC_FIRST = 0;
20 const OUTPUT_DESC_LAST = 1;
21
22 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
23 XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
24 'resource://gre/modules/accessibility/Utils.jsm');
25 XPCOMUtils.defineLazyModuleGetter(this, 'PrefCache',
26 'resource://gre/modules/accessibility/Utils.jsm');
27 XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
28 'resource://gre/modules/accessibility/Utils.jsm');
29 XPCOMUtils.defineLazyModuleGetter(this, 'PluralForm',
30 'resource://gre/modules/PluralForm.jsm');
31 XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
32 'resource://gre/modules/accessibility/Constants.jsm');
33 XPCOMUtils.defineLazyModuleGetter(this, 'States',
34 'resource://gre/modules/accessibility/Constants.jsm');
35
36 this.EXPORTED_SYMBOLS = ['UtteranceGenerator', 'BrailleGenerator'];
37
38 this.OutputGenerator = {
39
40 defaultOutputOrder: OUTPUT_DESC_LAST,
41
42 /**
43 * Generates output for a PivotContext.
44 * @param {PivotContext} aContext object that generates and caches
45 * context information for a given accessible and its relationship with
46 * another accessible.
47 * @return {Object} An object that neccessarily has an output property which
48 * is an array of strings. Depending on the utterance order,
49 * the strings describe the context for an accessible object either
50 * starting from the accessible's ancestry or accessible's subtree.
51 * The object may also have properties specific to the type of output
52 * generated.
53 */
54 genForContext: function genForContext(aContext) {
55 let output = [];
56 let self = this;
57 let addOutput = function addOutput(aAccessible) {
58 output.push.apply(output, self.genForObject(aAccessible, aContext));
59 };
60 let ignoreSubtree = function ignoreSubtree(aAccessible) {
61 let roleString = Utils.AccRetrieval.getStringRole(aAccessible.role);
62 let nameRule = self.roleRuleMap[roleString] || 0;
63 // Ignore subtree if the name is explicit and the role's name rule is the
64 // NAME_FROM_SUBTREE_RULE.
65 return (((nameRule & INCLUDE_VALUE) && aAccessible.value) ||
66 ((nameRule & NAME_FROM_SUBTREE_RULE) &&
67 (Utils.getAttributes(aAccessible)['explicit-name'] === 'true' &&
68 !(nameRule & IGNORE_EXPLICIT_NAME))));
69 };
70
71 let contextStart = this._getContextStart(aContext);
72
73 if (this.outputOrder === OUTPUT_DESC_FIRST) {
74 contextStart.forEach(addOutput);
75 addOutput(aContext.accessible);
76 [addOutput(node) for
77 (node of aContext.subtreeGenerator(true, ignoreSubtree))];
78 } else {
79 [addOutput(node) for
80 (node of aContext.subtreeGenerator(false, ignoreSubtree))];
81 addOutput(aContext.accessible);
82 contextStart.reverse().forEach(addOutput);
83 }
84
85 // Clean up the white space.
86 let trimmed;
87 output = [trimmed for (word of output) if (trimmed = word.trim())];
88 return {output: output};
89 },
90
91
92 /**
93 * Generates output for an object.
94 * @param {nsIAccessible} aAccessible accessible object to generate output
95 * for.
96 * @param {PivotContext} aContext object that generates and caches
97 * context information for a given accessible and its relationship with
98 * another accessible.
99 * @return {Array} Two string array. The first string describes the object
100 * and its state. The second string is the object's name. Whether the
101 * object's description or it's role is included is determined by
102 * {@link roleRuleMap}.
103 */
104 genForObject: function genForObject(aAccessible, aContext) {
105 let roleString = Utils.AccRetrieval.getStringRole(aAccessible.role);
106 let func = this.objectOutputFunctions[
107 OutputGenerator._getOutputName(roleString)] ||
108 this.objectOutputFunctions.defaultFunc;
109
110 let flags = this.roleRuleMap[roleString] || 0;
111
112 if (aAccessible.childCount == 0)
113 flags |= INCLUDE_NAME;
114
115 return func.apply(this, [aAccessible, roleString,
116 Utils.getState(aAccessible), flags, aContext]);
117 },
118
119 /**
120 * Generates output for an action performed.
121 * @param {nsIAccessible} aAccessible accessible object that the action was
122 * invoked in.
123 * @param {string} aActionName the name of the action, one of the keys in
124 * {@link gActionMap}.
125 * @return {Array} A one string array with the action.
126 */
127 genForAction: function genForAction(aObject, aActionName) {},
128
129 /**
130 * Generates output for an announcement. Basically attempts to localize
131 * the announcement string.
132 * @param {string} aAnnouncement unlocalized announcement.
133 * @return {Array} A one string array with the announcement.
134 */
135 genForAnnouncement: function genForAnnouncement(aAnnouncement) {},
136
137 /**
138 * Generates output for a tab state change.
139 * @param {nsIAccessible} aAccessible accessible object of the tab's attached
140 * document.
141 * @param {string} aTabState the tab state name, see
142 * {@link Presenter.tabStateChanged}.
143 * @return {Array} The tab state utterace.
144 */
145 genForTabStateChange: function genForTabStateChange(aObject, aTabState) {},
146
147 /**
148 * Generates output for announcing entering and leaving editing mode.
149 * @param {aIsEditing} boolean true if we are in editing mode
150 * @return {Array} The mode utterance
151 */
152 genForEditingMode: function genForEditingMode(aIsEditing) {},
153
154 _getContextStart: function getContextStart(aContext) {},
155
156 _addName: function _addName(aOutput, aAccessible, aFlags) {
157 let name;
158 if ((Utils.getAttributes(aAccessible)['explicit-name'] === 'true' &&
159 !(aFlags & IGNORE_EXPLICIT_NAME)) || (aFlags & INCLUDE_NAME)) {
160 name = aAccessible.name;
161 }
162
163 let description = aAccessible.description;
164 if (description) {
165 // Compare against the calculated name unconditionally, regardless of name rule,
166 // so we can make sure we don't speak duplicated descriptions
167 let tmpName = name || aAccessible.name;
168 if (tmpName && (description !== tmpName)) {
169 name = name || '';
170 name = this.outputOrder === OUTPUT_DESC_FIRST ?
171 description + ' - ' + name :
172 name + ' - ' + description;
173 }
174 }
175
176 if (name) {
177 aOutput[this.outputOrder === OUTPUT_DESC_FIRST ?
178 'push' : 'unshift'](name);
179 }
180 },
181
182 /**
183 * Adds a landmark role to the output if available.
184 * @param {Array} aOutput Output array.
185 * @param {nsIAccessible} aAccessible current accessible object.
186 */
187 _addLandmark: function _addLandmark(aOutput, aAccessible) {
188 let landmarkName = Utils.getLandmarkName(aAccessible);
189 if (!landmarkName) {
190 return;
191 }
192
193 let landmark = Utils.stringBundle.GetStringFromName(landmarkName);
194 if (!landmark) {
195 return;
196 }
197
198 aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 'unshift' : 'push'](
199 landmark);
200 },
201
202 /**
203 * Adds an entry type attribute to the description if available.
204 * @param {Array} aDesc Description array.
205 * @param {nsIAccessible} aAccessible current accessible object.
206 * @param {String} aRoleStr aAccessible's role string.
207 */
208 _addType: function _addType(aDesc, aAccessible, aRoleStr) {
209 if (aRoleStr !== 'entry') {
210 return;
211 }
212
213 let typeName = Utils.getAttributes(aAccessible)['text-input-type'];
214 // Ignore the the input type="text" case.
215 if (!typeName || typeName === 'text') {
216 return;
217 }
218 typeName = 'textInputType_' + typeName;
219 try {
220 aDesc.push(Utils.stringBundle.GetStringFromName(typeName));
221 } catch (x) {
222 Logger.warning('Failed to get a string from a bundle for', typeName);
223 }
224 },
225
226 get outputOrder() {
227 if (!this._utteranceOrder) {
228 this._utteranceOrder = new PrefCache('accessibility.accessfu.utterance');
229 }
230 return typeof this._utteranceOrder.value === 'number' ?
231 this._utteranceOrder.value : this.defaultOutputOrder;
232 },
233
234 _getOutputName: function _getOutputName(aName) {
235 return aName.replace(' ', '');
236 },
237
238 _getLocalizedRole: function _getLocalizedRole(aRoleStr) {},
239
240 _getLocalizedState: function _getLocalizedState(aState) {},
241
242 _getPluralFormString: function _getPluralFormString(aString, aCount) {
243 let str = Utils.stringBundle.GetStringFromName(this._getOutputName(aString));
244 str = PluralForm.get(aCount, str);
245 return str.replace('#1', aCount);
246 },
247
248 roleRuleMap: {
249 'menubar': INCLUDE_DESC,
250 'scrollbar': INCLUDE_DESC,
251 'grip': INCLUDE_DESC,
252 'alert': INCLUDE_DESC | INCLUDE_NAME,
253 'menupopup': INCLUDE_DESC,
254 'menuitem': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
255 'tooltip': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
256 'columnheader': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
257 'rowheader': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
258 'column': NAME_FROM_SUBTREE_RULE,
259 'row': NAME_FROM_SUBTREE_RULE,
260 'cell': INCLUDE_DESC | INCLUDE_NAME,
261 'application': INCLUDE_NAME,
262 'document': INCLUDE_NAME,
263 'grouping': INCLUDE_DESC | INCLUDE_NAME,
264 'toolbar': INCLUDE_DESC,
265 'table': INCLUDE_DESC | INCLUDE_NAME,
266 'link': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
267 'helpballoon': NAME_FROM_SUBTREE_RULE,
268 'list': INCLUDE_DESC | INCLUDE_NAME,
269 'listitem': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
270 'outline': INCLUDE_DESC,
271 'outlineitem': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
272 'pagetab': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
273 'graphic': INCLUDE_DESC,
274 'pushbutton': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
275 'checkbutton': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
276 'radiobutton': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
277 'buttondropdown': NAME_FROM_SUBTREE_RULE,
278 'combobox': INCLUDE_DESC,
279 'droplist': INCLUDE_DESC,
280 'progressbar': INCLUDE_DESC | INCLUDE_VALUE,
281 'slider': INCLUDE_DESC | INCLUDE_VALUE,
282 'spinbutton': INCLUDE_DESC | INCLUDE_VALUE,
283 'diagram': INCLUDE_DESC,
284 'animation': INCLUDE_DESC,
285 'equation': INCLUDE_DESC,
286 'buttonmenu': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
287 'buttondropdowngrid': NAME_FROM_SUBTREE_RULE,
288 'pagetablist': INCLUDE_DESC,
289 'canvas': INCLUDE_DESC,
290 'check menu item': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
291 'label': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
292 'password text': INCLUDE_DESC,
293 'popup menu': INCLUDE_DESC,
294 'radio menu item': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
295 'table column header': NAME_FROM_SUBTREE_RULE,
296 'table row header': NAME_FROM_SUBTREE_RULE,
297 'tear off menu item': NAME_FROM_SUBTREE_RULE,
298 'toggle button': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
299 'parent menuitem': NAME_FROM_SUBTREE_RULE,
300 'header': INCLUDE_DESC,
301 'footer': INCLUDE_DESC,
302 'entry': INCLUDE_DESC | INCLUDE_NAME | INCLUDE_VALUE,
303 'caption': INCLUDE_DESC,
304 'document frame': INCLUDE_DESC,
305 'heading': INCLUDE_DESC,
306 'calendar': INCLUDE_DESC | INCLUDE_NAME,
307 'combobox list': INCLUDE_DESC,
308 'combobox option': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
309 'listbox option': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
310 'listbox rich option': NAME_FROM_SUBTREE_RULE,
311 'gridcell': NAME_FROM_SUBTREE_RULE,
312 'check rich option': NAME_FROM_SUBTREE_RULE,
313 'term': NAME_FROM_SUBTREE_RULE,
314 'definition': NAME_FROM_SUBTREE_RULE,
315 'key': NAME_FROM_SUBTREE_RULE,
316 'image map': INCLUDE_DESC,
317 'option': INCLUDE_DESC,
318 'listbox': INCLUDE_DESC,
319 'definitionlist': INCLUDE_DESC | INCLUDE_NAME,
320 'dialog': INCLUDE_DESC | INCLUDE_NAME,
321 'chrome window': IGNORE_EXPLICIT_NAME,
322 'app root': IGNORE_EXPLICIT_NAME },
323
324 objectOutputFunctions: {
325 _generateBaseOutput: function _generateBaseOutput(aAccessible, aRoleStr, aState, aFlags) {
326 let output = [];
327
328 if (aFlags & INCLUDE_DESC) {
329 let desc = this._getLocalizedState(aState);
330 let roleStr = this._getLocalizedRole(aRoleStr);
331 if (roleStr) {
332 this._addType(desc, aAccessible, aRoleStr);
333 desc.push(roleStr);
334 }
335 output.push(desc.join(' '));
336 }
337
338 if (aFlags & INCLUDE_VALUE) {
339 let value = aAccessible.value;
340 if (value) {
341 output[this.outputOrder === OUTPUT_DESC_FIRST ?
342 'push' : 'unshift'](value);
343 }
344 }
345
346 this._addName(output, aAccessible, aFlags);
347 this._addLandmark(output, aAccessible);
348
349 return output;
350 },
351
352 label: function label(aAccessible, aRoleStr, aState, aFlags, aContext) {
353 if (aContext.isNestedControl ||
354 aContext.accessible == Utils.getEmbeddedControl(aAccessible)) {
355 // If we are on a nested control, or a nesting label,
356 // we don't need the context.
357 return [];
358 }
359
360 return this.objectOutputFunctions.defaultFunc.apply(this, arguments);
361 },
362
363 entry: function entry(aAccessible, aRoleStr, aState, aFlags) {
364 let rolestr = aState.contains(States.MULTI_LINE) ? 'textarea' : 'entry';
365 return this.objectOutputFunctions.defaultFunc.apply(
366 this, [aAccessible, rolestr, aState, aFlags]);
367 },
368
369 pagetab: function pagetab(aAccessible, aRoleStr, aState, aFlags) {
370 let localizedRole = this._getLocalizedRole(aRoleStr);
371 let itemno = {};
372 let itemof = {};
373 aAccessible.groupPosition({}, itemof, itemno);
374 let output = [];
375 let desc = this._getLocalizedState(aState);
376 desc.push(
377 Utils.stringBundle.formatStringFromName(
378 'objItemOf', [localizedRole, itemno.value, itemof.value], 3));
379 output.push(desc.join(' '));
380
381 this._addName(output, aAccessible, aFlags);
382 this._addLandmark(output, aAccessible);
383
384 return output;
385 },
386
387 table: function table(aAccessible, aRoleStr, aState, aFlags) {
388 let output = [];
389 let table;
390 try {
391 table = aAccessible.QueryInterface(Ci.nsIAccessibleTable);
392 } catch (x) {
393 Logger.logException(x);
394 return output;
395 } finally {
396 // Check if it's a layout table, and bail out if true.
397 // We don't want to speak any table information for layout tables.
398 if (table.isProbablyForLayout()) {
399 return output;
400 }
401 let tableColumnInfo = this._getPluralFormString('tableColumnInfo',
402 table.columnCount);
403 let tableRowInfo = this._getPluralFormString('tableRowInfo',
404 table.rowCount);
405 output.push(Utils.stringBundle.formatStringFromName(
406 this._getOutputName('tableInfo'), [this._getLocalizedRole(aRoleStr),
407 tableColumnInfo, tableRowInfo], 3));
408 this._addName(output, aAccessible, aFlags);
409 this._addLandmark(output, aAccessible);
410 return output;
411 }
412 }
413 }
414 };
415
416 /**
417 * Generates speech utterances from objects, actions and state changes.
418 * An utterance is an array of strings.
419 *
420 * It should not be assumed that flattening an utterance array would create a
421 * gramatically correct sentence. For example, {@link genForObject} might
422 * return: ['graphic', 'Welcome to my home page'].
423 * Each string element in an utterance should be gramatically correct in itself.
424 * Another example from {@link genForObject}: ['list item 2 of 5', 'Alabama'].
425 *
426 * An utterance is ordered from the least to the most important. Speaking the
427 * last string usually makes sense, but speaking the first often won't.
428 * For example {@link genForAction} might return ['button', 'clicked'] for a
429 * clicked event. Speaking only 'clicked' makes sense. Speaking 'button' does
430 * not.
431 */
432 this.UtteranceGenerator = {
433 __proto__: OutputGenerator,
434
435 gActionMap: {
436 jump: 'jumpAction',
437 press: 'pressAction',
438 check: 'checkAction',
439 uncheck: 'uncheckAction',
440 select: 'selectAction',
441 unselect: 'unselectAction',
442 open: 'openAction',
443 close: 'closeAction',
444 switch: 'switchAction',
445 click: 'clickAction',
446 collapse: 'collapseAction',
447 expand: 'expandAction',
448 activate: 'activateAction',
449 cycle: 'cycleAction'
450 },
451
452 //TODO: May become more verbose in the future.
453 genForAction: function genForAction(aObject, aActionName) {
454 return [Utils.stringBundle.GetStringFromName(this.gActionMap[aActionName])];
455 },
456
457 genForLiveRegion: function genForLiveRegion(aContext, aIsHide, aModifiedText) {
458 let utterance = [];
459 if (aIsHide) {
460 utterance.push(Utils.stringBundle.GetStringFromName('hidden'));
461 }
462 return utterance.concat(
463 aModifiedText || this.genForContext(aContext).output);
464 },
465
466 genForAnnouncement: function genForAnnouncement(aAnnouncement) {
467 try {
468 return [Utils.stringBundle.GetStringFromName(aAnnouncement)];
469 } catch (x) {
470 return [aAnnouncement];
471 }
472 },
473
474 genForTabStateChange: function genForTabStateChange(aObject, aTabState) {
475 switch (aTabState) {
476 case 'newtab':
477 return [Utils.stringBundle.GetStringFromName('tabNew')];
478 case 'loading':
479 return [Utils.stringBundle.GetStringFromName('tabLoading')];
480 case 'loaded':
481 return [aObject.name || '',
482 Utils.stringBundle.GetStringFromName('tabLoaded')];
483 case 'loadstopped':
484 return [Utils.stringBundle.GetStringFromName('tabLoadStopped')];
485 case 'reload':
486 return [Utils.stringBundle.GetStringFromName('tabReload')];
487 default:
488 return [];
489 }
490 },
491
492 genForEditingMode: function genForEditingMode(aIsEditing) {
493 return [Utils.stringBundle.GetStringFromName(
494 aIsEditing ? 'editingMode' : 'navigationMode')];
495 },
496
497 objectOutputFunctions: {
498
499 __proto__: OutputGenerator.objectOutputFunctions,
500
501 defaultFunc: function defaultFunc(aAccessible, aRoleStr, aState, aFlags) {
502 return this.objectOutputFunctions._generateBaseOutput.apply(this, arguments);
503 },
504
505 heading: function heading(aAccessible, aRoleStr, aState, aFlags) {
506 let level = {};
507 aAccessible.groupPosition(level, {}, {});
508 let utterance =
509 [Utils.stringBundle.formatStringFromName(
510 'headingLevel', [level.value], 1)];
511
512 this._addName(utterance, aAccessible, aFlags);
513 this._addLandmark(utterance, aAccessible);
514
515 return utterance;
516 },
517
518 listitem: function listitem(aAccessible, aRoleStr, aState, aFlags) {
519 let itemno = {};
520 let itemof = {};
521 aAccessible.groupPosition({}, itemof, itemno);
522 let utterance = [];
523 if (itemno.value == 1) // Start of list
524 utterance.push(Utils.stringBundle.GetStringFromName('listStart'));
525 else if (itemno.value == itemof.value) // last item
526 utterance.push(Utils.stringBundle.GetStringFromName('listEnd'));
527
528 this._addName(utterance, aAccessible, aFlags);
529 this._addLandmark(utterance, aAccessible);
530
531 return utterance;
532 },
533
534 list: function list(aAccessible, aRoleStr, aState, aFlags) {
535 return this._getListUtterance
536 (aAccessible, aRoleStr, aFlags, aAccessible.childCount);
537 },
538
539 definitionlist: function definitionlist(aAccessible, aRoleStr, aState, aFlags) {
540 return this._getListUtterance
541 (aAccessible, aRoleStr, aFlags, aAccessible.childCount / 2);
542 },
543
544 application: function application(aAccessible, aRoleStr, aState, aFlags) {
545 // Don't utter location of applications, it gets tiring.
546 if (aAccessible.name != aAccessible.DOMNode.location)
547 return this.objectOutputFunctions.defaultFunc.apply(this,
548 [aAccessible, aRoleStr, aState, aFlags]);
549
550 return [];
551 },
552
553 cell: function cell(aAccessible, aRoleStr, aState, aFlags, aContext) {
554 let utterance = [];
555 let cell = aContext.getCellInfo(aAccessible);
556 if (cell) {
557 let desc = [];
558 let addCellChanged = function addCellChanged(aDesc, aChanged, aString, aIndex) {
559 if (aChanged) {
560 aDesc.push(Utils.stringBundle.formatStringFromName(aString,
561 [aIndex + 1], 1));
562 }
563 };
564 let addExtent = function addExtent(aDesc, aExtent, aString) {
565 if (aExtent > 1) {
566 aDesc.push(Utils.stringBundle.formatStringFromName(aString,
567 [aExtent], 1));
568 }
569 };
570 let addHeaders = function addHeaders(aDesc, aHeaders) {
571 if (aHeaders.length > 0) {
572 aDesc.push.apply(aDesc, aHeaders);
573 }
574 };
575
576 addCellChanged(desc, cell.columnChanged, 'columnInfo', cell.columnIndex);
577 addCellChanged(desc, cell.rowChanged, 'rowInfo', cell.rowIndex);
578
579 addExtent(desc, cell.columnExtent, 'spansColumns');
580 addExtent(desc, cell.rowExtent, 'spansRows');
581
582 addHeaders(desc, cell.columnHeaders);
583 addHeaders(desc, cell.rowHeaders);
584
585 utterance.push(desc.join(' '));
586 }
587
588 this._addName(utterance, aAccessible, aFlags);
589 this._addLandmark(utterance, aAccessible);
590
591 return utterance;
592 },
593
594 columnheader: function columnheader() {
595 return this.objectOutputFunctions.cell.apply(this, arguments);
596 },
597
598 rowheader: function rowheader() {
599 return this.objectOutputFunctions.cell.apply(this, arguments);
600 }
601 },
602
603 _getContextStart: function _getContextStart(aContext) {
604 return aContext.newAncestry;
605 },
606
607 _getLocalizedRole: function _getLocalizedRole(aRoleStr) {
608 try {
609 return Utils.stringBundle.GetStringFromName(
610 this._getOutputName(aRoleStr));
611 } catch (x) {
612 return '';
613 }
614 },
615
616 _getLocalizedState: function _getLocalizedState(aState) {
617 let stateUtterances = [];
618
619 if (aState.contains(States.UNAVAILABLE)) {
620 stateUtterances.push(
621 Utils.stringBundle.GetStringFromName('stateUnavailable'));
622 }
623
624 // Don't utter this in Jelly Bean, we let TalkBack do it for us there.
625 // This is because we expose the checked information on the node itself.
626 // XXX: this means the checked state is always appended to the end, regardless
627 // of the utterance ordering preference.
628 if ((Utils.AndroidSdkVersion < 16 || Utils.MozBuildApp === 'browser') &&
629 aState.contains(States.CHECKABLE)) {
630 let statetr = aState.contains(States.CHECKED) ?
631 'stateChecked' : 'stateNotChecked';
632 stateUtterances.push(Utils.stringBundle.GetStringFromName(statetr));
633 }
634
635 if (aState.contains(States.PRESSED)) {
636 stateUtterances.push(
637 Utils.stringBundle.GetStringFromName('statePressed'));
638 }
639
640 if (aState.contains(States.EXPANDABLE)) {
641 let statetr = aState.contains(States.EXPANDED) ?
642 'stateExpanded' : 'stateCollapsed';
643 stateUtterances.push(Utils.stringBundle.GetStringFromName(statetr));
644 }
645
646 if (aState.contains(States.REQUIRED)) {
647 stateUtterances.push(
648 Utils.stringBundle.GetStringFromName('stateRequired'));
649 }
650
651 if (aState.contains(States.TRAVERSED)) {
652 stateUtterances.push(
653 Utils.stringBundle.GetStringFromName('stateTraversed'));
654 }
655
656 if (aState.contains(States.HASPOPUP)) {
657 stateUtterances.push(
658 Utils.stringBundle.GetStringFromName('stateHasPopup'));
659 }
660
661 if (aState.contains(States.SELECTED)) {
662 stateUtterances.push(
663 Utils.stringBundle.GetStringFromName('stateSelected'));
664 }
665
666 return stateUtterances;
667 },
668
669 _getListUtterance: function _getListUtterance(aAccessible, aRoleStr, aFlags, aItemCount) {
670 let desc = [];
671 let roleStr = this._getLocalizedRole(aRoleStr);
672 if (roleStr) {
673 desc.push(roleStr);
674 }
675 desc.push(this._getPluralFormString('listItemsCount', aItemCount));
676 let utterance = [desc.join(' ')];
677
678 this._addName(utterance, aAccessible, aFlags);
679 this._addLandmark(utterance, aAccessible);
680
681 return utterance;
682 }
683 };
684
685
686 this.BrailleGenerator = {
687 __proto__: OutputGenerator,
688
689 genForContext: function genForContext(aContext) {
690 let output = OutputGenerator.genForContext.apply(this, arguments);
691
692 let acc = aContext.accessible;
693
694 // add the static text indicating a list item; do this for both listitems or
695 // direct first children of listitems, because these are both common browsing
696 // scenarios
697 let addListitemIndicator = function addListitemIndicator(indicator = '*') {
698 output.output.unshift(indicator);
699 };
700
701 if (acc.indexInParent === 1 &&
702 acc.parent.role == Roles.LISTITEM &&
703 acc.previousSibling.role == Roles.STATICTEXT) {
704 if (acc.parent.parent && acc.parent.parent.DOMNode &&
705 acc.parent.parent.DOMNode.nodeName == 'UL') {
706 addListitemIndicator();
707 } else {
708 addListitemIndicator(acc.previousSibling.name.trim());
709 }
710 } else if (acc.role == Roles.LISTITEM && acc.firstChild &&
711 acc.firstChild.role == Roles.STATICTEXT) {
712 if (acc.parent.DOMNode.nodeName == 'UL') {
713 addListitemIndicator();
714 } else {
715 addListitemIndicator(acc.firstChild.name.trim());
716 }
717 }
718
719 if (acc instanceof Ci.nsIAccessibleText) {
720 output.endOffset = this.outputOrder === OUTPUT_DESC_FIRST ?
721 output.output.join(' ').length : acc.characterCount;
722 output.startOffset = output.endOffset - acc.characterCount;
723 }
724
725 return output;
726 },
727
728 objectOutputFunctions: {
729
730 __proto__: OutputGenerator.objectOutputFunctions,
731
732 defaultFunc: function defaultFunc(aAccessible, aRoleStr, aState, aFlags) {
733 return this.objectOutputFunctions._generateBaseOutput.apply(this, arguments);
734 },
735
736 listitem: function listitem(aAccessible, aRoleStr, aState, aFlags) {
737 let braille = [];
738
739 this._addName(braille, aAccessible, aFlags);
740 this._addLandmark(braille, aAccessible);
741
742 return braille;
743 },
744
745 cell: function cell(aAccessible, aRoleStr, aState, aFlags, aContext) {
746 let braille = [];
747 let cell = aContext.getCellInfo(aAccessible);
748 if (cell) {
749 let desc = [];
750 let addHeaders = function addHeaders(aDesc, aHeaders) {
751 if (aHeaders.length > 0) {
752 aDesc.push.apply(aDesc, aHeaders);
753 }
754 };
755
756 desc.push(Utils.stringBundle.formatStringFromName(
757 this._getOutputName('cellInfo'), [cell.columnIndex + 1,
758 cell.rowIndex + 1], 2));
759
760 addHeaders(desc, cell.columnHeaders);
761 addHeaders(desc, cell.rowHeaders);
762 braille.push(desc.join(' '));
763 }
764
765 this._addName(braille, aAccessible, aFlags);
766 this._addLandmark(braille, aAccessible);
767 return braille;
768 },
769
770 columnheader: function columnheader() {
771 return this.objectOutputFunctions.cell.apply(this, arguments);
772 },
773
774 rowheader: function rowheader() {
775 return this.objectOutputFunctions.cell.apply(this, arguments);
776 },
777
778 statictext: function statictext(aAccessible, aRoleStr, aState, aFlags) {
779 // Since we customize the list bullet's output, we add the static
780 // text from the first node in each listitem, so skip it here.
781 if (aAccessible.parent.role == Roles.LISTITEM) {
782 return [];
783 }
784
785 return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
786 },
787
788 _useStateNotRole: function _useStateNotRole(aAccessible, aRoleStr, aState, aFlags) {
789 let braille = [];
790
791 let desc = this._getLocalizedState(aState, aAccessible.role);
792 braille.push(desc.join(' '));
793
794 this._addName(braille, aAccessible, aFlags);
795 this._addLandmark(braille, aAccessible);
796
797 return braille;
798 },
799
800 checkbutton: function checkbutton(aAccessible, aRoleStr, aState, aFlags) {
801 return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
802 },
803
804 radiobutton: function radiobutton(aAccessible, aRoleStr, aState, aFlags) {
805 return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
806 },
807
808 togglebutton: function radiobutton(aAccessible, aRoleStr, aState, aFlags) {
809 return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
810 }
811 },
812
813 _getContextStart: function _getContextStart(aContext) {
814 if (aContext.accessible.parent.role == Roles.LINK) {
815 return [aContext.accessible.parent];
816 }
817
818 return [];
819 },
820
821 _getOutputName: function _getOutputName(aName) {
822 return OutputGenerator._getOutputName(aName) + 'Abbr';
823 },
824
825 _getLocalizedRole: function _getLocalizedRole(aRoleStr) {
826 try {
827 return Utils.stringBundle.GetStringFromName(
828 this._getOutputName(aRoleStr));
829 } catch (x) {
830 try {
831 return Utils.stringBundle.GetStringFromName(
832 OutputGenerator._getOutputName(aRoleStr));
833 } catch (y) {
834 return '';
835 }
836 }
837 },
838
839 _getLocalizedState: function _getLocalizedState(aState, aRole) {
840 let stateBraille = [];
841
842 let getResultMarker = function getResultMarker(aMarker) {
843 // aMarker is a simple boolean.
844 let resultMarker = [];
845 resultMarker.push('(');
846 resultMarker.push(aMarker ? 'x' : ' ');
847 resultMarker.push(')');
848
849 return resultMarker.join('');
850 };
851
852 if (aState.contains(States.CHECKABLE)) {
853 stateBraille.push(getResultMarker(aState.contains(States.CHECKED)));
854 }
855
856 if (aRole === Roles.TOGGLE_BUTTON) {
857 stateBraille.push(getResultMarker(aState.contains(States.PRESSED)));
858 }
859
860 return stateBraille;
861 }
862
863 };

mercurial