accessible/src/jsat/OutputGenerator.jsm

Wed, 31 Dec 2014 07:16:47 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:16:47 +0100
branch
TOR_BUG_9701
changeset 3
141e0f1194b1
permissions
-rw-r--r--

Revert simplistic fix pending revisit of Mozilla integration attempt.

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

mercurial