1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/accessible/src/jsat/OutputGenerator.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,863 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +'use strict'; 1.9 + 1.10 +const Cc = Components.classes; 1.11 +const Ci = Components.interfaces; 1.12 +const Cu = Components.utils; 1.13 +const Cr = Components.results; 1.14 + 1.15 +const INCLUDE_DESC = 0x01; 1.16 +const INCLUDE_NAME = 0x02; 1.17 +const INCLUDE_VALUE = 0x04; 1.18 +const INCLUDE_CUSTOM = 0x08; 1.19 +const NAME_FROM_SUBTREE_RULE = 0x10; 1.20 +const IGNORE_EXPLICIT_NAME = 0x20; 1.21 + 1.22 +const OUTPUT_DESC_FIRST = 0; 1.23 +const OUTPUT_DESC_LAST = 1; 1.24 + 1.25 +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); 1.26 +XPCOMUtils.defineLazyModuleGetter(this, 'Utils', 1.27 + 'resource://gre/modules/accessibility/Utils.jsm'); 1.28 +XPCOMUtils.defineLazyModuleGetter(this, 'PrefCache', 1.29 + 'resource://gre/modules/accessibility/Utils.jsm'); 1.30 +XPCOMUtils.defineLazyModuleGetter(this, 'Logger', 1.31 + 'resource://gre/modules/accessibility/Utils.jsm'); 1.32 +XPCOMUtils.defineLazyModuleGetter(this, 'PluralForm', 1.33 + 'resource://gre/modules/PluralForm.jsm'); 1.34 +XPCOMUtils.defineLazyModuleGetter(this, 'Roles', 1.35 + 'resource://gre/modules/accessibility/Constants.jsm'); 1.36 +XPCOMUtils.defineLazyModuleGetter(this, 'States', 1.37 + 'resource://gre/modules/accessibility/Constants.jsm'); 1.38 + 1.39 +this.EXPORTED_SYMBOLS = ['UtteranceGenerator', 'BrailleGenerator']; 1.40 + 1.41 +this.OutputGenerator = { 1.42 + 1.43 + defaultOutputOrder: OUTPUT_DESC_LAST, 1.44 + 1.45 + /** 1.46 + * Generates output for a PivotContext. 1.47 + * @param {PivotContext} aContext object that generates and caches 1.48 + * context information for a given accessible and its relationship with 1.49 + * another accessible. 1.50 + * @return {Object} An object that neccessarily has an output property which 1.51 + * is an array of strings. Depending on the utterance order, 1.52 + * the strings describe the context for an accessible object either 1.53 + * starting from the accessible's ancestry or accessible's subtree. 1.54 + * The object may also have properties specific to the type of output 1.55 + * generated. 1.56 + */ 1.57 + genForContext: function genForContext(aContext) { 1.58 + let output = []; 1.59 + let self = this; 1.60 + let addOutput = function addOutput(aAccessible) { 1.61 + output.push.apply(output, self.genForObject(aAccessible, aContext)); 1.62 + }; 1.63 + let ignoreSubtree = function ignoreSubtree(aAccessible) { 1.64 + let roleString = Utils.AccRetrieval.getStringRole(aAccessible.role); 1.65 + let nameRule = self.roleRuleMap[roleString] || 0; 1.66 + // Ignore subtree if the name is explicit and the role's name rule is the 1.67 + // NAME_FROM_SUBTREE_RULE. 1.68 + return (((nameRule & INCLUDE_VALUE) && aAccessible.value) || 1.69 + ((nameRule & NAME_FROM_SUBTREE_RULE) && 1.70 + (Utils.getAttributes(aAccessible)['explicit-name'] === 'true' && 1.71 + !(nameRule & IGNORE_EXPLICIT_NAME)))); 1.72 + }; 1.73 + 1.74 + let contextStart = this._getContextStart(aContext); 1.75 + 1.76 + if (this.outputOrder === OUTPUT_DESC_FIRST) { 1.77 + contextStart.forEach(addOutput); 1.78 + addOutput(aContext.accessible); 1.79 + [addOutput(node) for 1.80 + (node of aContext.subtreeGenerator(true, ignoreSubtree))]; 1.81 + } else { 1.82 + [addOutput(node) for 1.83 + (node of aContext.subtreeGenerator(false, ignoreSubtree))]; 1.84 + addOutput(aContext.accessible); 1.85 + contextStart.reverse().forEach(addOutput); 1.86 + } 1.87 + 1.88 + // Clean up the white space. 1.89 + let trimmed; 1.90 + output = [trimmed for (word of output) if (trimmed = word.trim())]; 1.91 + return {output: output}; 1.92 + }, 1.93 + 1.94 + 1.95 + /** 1.96 + * Generates output for an object. 1.97 + * @param {nsIAccessible} aAccessible accessible object to generate output 1.98 + * for. 1.99 + * @param {PivotContext} aContext object that generates and caches 1.100 + * context information for a given accessible and its relationship with 1.101 + * another accessible. 1.102 + * @return {Array} Two string array. The first string describes the object 1.103 + * and its state. The second string is the object's name. Whether the 1.104 + * object's description or it's role is included is determined by 1.105 + * {@link roleRuleMap}. 1.106 + */ 1.107 + genForObject: function genForObject(aAccessible, aContext) { 1.108 + let roleString = Utils.AccRetrieval.getStringRole(aAccessible.role); 1.109 + let func = this.objectOutputFunctions[ 1.110 + OutputGenerator._getOutputName(roleString)] || 1.111 + this.objectOutputFunctions.defaultFunc; 1.112 + 1.113 + let flags = this.roleRuleMap[roleString] || 0; 1.114 + 1.115 + if (aAccessible.childCount == 0) 1.116 + flags |= INCLUDE_NAME; 1.117 + 1.118 + return func.apply(this, [aAccessible, roleString, 1.119 + Utils.getState(aAccessible), flags, aContext]); 1.120 + }, 1.121 + 1.122 + /** 1.123 + * Generates output for an action performed. 1.124 + * @param {nsIAccessible} aAccessible accessible object that the action was 1.125 + * invoked in. 1.126 + * @param {string} aActionName the name of the action, one of the keys in 1.127 + * {@link gActionMap}. 1.128 + * @return {Array} A one string array with the action. 1.129 + */ 1.130 + genForAction: function genForAction(aObject, aActionName) {}, 1.131 + 1.132 + /** 1.133 + * Generates output for an announcement. Basically attempts to localize 1.134 + * the announcement string. 1.135 + * @param {string} aAnnouncement unlocalized announcement. 1.136 + * @return {Array} A one string array with the announcement. 1.137 + */ 1.138 + genForAnnouncement: function genForAnnouncement(aAnnouncement) {}, 1.139 + 1.140 + /** 1.141 + * Generates output for a tab state change. 1.142 + * @param {nsIAccessible} aAccessible accessible object of the tab's attached 1.143 + * document. 1.144 + * @param {string} aTabState the tab state name, see 1.145 + * {@link Presenter.tabStateChanged}. 1.146 + * @return {Array} The tab state utterace. 1.147 + */ 1.148 + genForTabStateChange: function genForTabStateChange(aObject, aTabState) {}, 1.149 + 1.150 + /** 1.151 + * Generates output for announcing entering and leaving editing mode. 1.152 + * @param {aIsEditing} boolean true if we are in editing mode 1.153 + * @return {Array} The mode utterance 1.154 + */ 1.155 + genForEditingMode: function genForEditingMode(aIsEditing) {}, 1.156 + 1.157 + _getContextStart: function getContextStart(aContext) {}, 1.158 + 1.159 + _addName: function _addName(aOutput, aAccessible, aFlags) { 1.160 + let name; 1.161 + if ((Utils.getAttributes(aAccessible)['explicit-name'] === 'true' && 1.162 + !(aFlags & IGNORE_EXPLICIT_NAME)) || (aFlags & INCLUDE_NAME)) { 1.163 + name = aAccessible.name; 1.164 + } 1.165 + 1.166 + let description = aAccessible.description; 1.167 + if (description) { 1.168 + // Compare against the calculated name unconditionally, regardless of name rule, 1.169 + // so we can make sure we don't speak duplicated descriptions 1.170 + let tmpName = name || aAccessible.name; 1.171 + if (tmpName && (description !== tmpName)) { 1.172 + name = name || ''; 1.173 + name = this.outputOrder === OUTPUT_DESC_FIRST ? 1.174 + description + ' - ' + name : 1.175 + name + ' - ' + description; 1.176 + } 1.177 + } 1.178 + 1.179 + if (name) { 1.180 + aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 1.181 + 'push' : 'unshift'](name); 1.182 + } 1.183 + }, 1.184 + 1.185 + /** 1.186 + * Adds a landmark role to the output if available. 1.187 + * @param {Array} aOutput Output array. 1.188 + * @param {nsIAccessible} aAccessible current accessible object. 1.189 + */ 1.190 + _addLandmark: function _addLandmark(aOutput, aAccessible) { 1.191 + let landmarkName = Utils.getLandmarkName(aAccessible); 1.192 + if (!landmarkName) { 1.193 + return; 1.194 + } 1.195 + 1.196 + let landmark = Utils.stringBundle.GetStringFromName(landmarkName); 1.197 + if (!landmark) { 1.198 + return; 1.199 + } 1.200 + 1.201 + aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 'unshift' : 'push']( 1.202 + landmark); 1.203 + }, 1.204 + 1.205 + /** 1.206 + * Adds an entry type attribute to the description if available. 1.207 + * @param {Array} aDesc Description array. 1.208 + * @param {nsIAccessible} aAccessible current accessible object. 1.209 + * @param {String} aRoleStr aAccessible's role string. 1.210 + */ 1.211 + _addType: function _addType(aDesc, aAccessible, aRoleStr) { 1.212 + if (aRoleStr !== 'entry') { 1.213 + return; 1.214 + } 1.215 + 1.216 + let typeName = Utils.getAttributes(aAccessible)['text-input-type']; 1.217 + // Ignore the the input type="text" case. 1.218 + if (!typeName || typeName === 'text') { 1.219 + return; 1.220 + } 1.221 + typeName = 'textInputType_' + typeName; 1.222 + try { 1.223 + aDesc.push(Utils.stringBundle.GetStringFromName(typeName)); 1.224 + } catch (x) { 1.225 + Logger.warning('Failed to get a string from a bundle for', typeName); 1.226 + } 1.227 + }, 1.228 + 1.229 + get outputOrder() { 1.230 + if (!this._utteranceOrder) { 1.231 + this._utteranceOrder = new PrefCache('accessibility.accessfu.utterance'); 1.232 + } 1.233 + return typeof this._utteranceOrder.value === 'number' ? 1.234 + this._utteranceOrder.value : this.defaultOutputOrder; 1.235 + }, 1.236 + 1.237 + _getOutputName: function _getOutputName(aName) { 1.238 + return aName.replace(' ', ''); 1.239 + }, 1.240 + 1.241 + _getLocalizedRole: function _getLocalizedRole(aRoleStr) {}, 1.242 + 1.243 + _getLocalizedState: function _getLocalizedState(aState) {}, 1.244 + 1.245 + _getPluralFormString: function _getPluralFormString(aString, aCount) { 1.246 + let str = Utils.stringBundle.GetStringFromName(this._getOutputName(aString)); 1.247 + str = PluralForm.get(aCount, str); 1.248 + return str.replace('#1', aCount); 1.249 + }, 1.250 + 1.251 + roleRuleMap: { 1.252 + 'menubar': INCLUDE_DESC, 1.253 + 'scrollbar': INCLUDE_DESC, 1.254 + 'grip': INCLUDE_DESC, 1.255 + 'alert': INCLUDE_DESC | INCLUDE_NAME, 1.256 + 'menupopup': INCLUDE_DESC, 1.257 + 'menuitem': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 1.258 + 'tooltip': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 1.259 + 'columnheader': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 1.260 + 'rowheader': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 1.261 + 'column': NAME_FROM_SUBTREE_RULE, 1.262 + 'row': NAME_FROM_SUBTREE_RULE, 1.263 + 'cell': INCLUDE_DESC | INCLUDE_NAME, 1.264 + 'application': INCLUDE_NAME, 1.265 + 'document': INCLUDE_NAME, 1.266 + 'grouping': INCLUDE_DESC | INCLUDE_NAME, 1.267 + 'toolbar': INCLUDE_DESC, 1.268 + 'table': INCLUDE_DESC | INCLUDE_NAME, 1.269 + 'link': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 1.270 + 'helpballoon': NAME_FROM_SUBTREE_RULE, 1.271 + 'list': INCLUDE_DESC | INCLUDE_NAME, 1.272 + 'listitem': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 1.273 + 'outline': INCLUDE_DESC, 1.274 + 'outlineitem': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 1.275 + 'pagetab': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 1.276 + 'graphic': INCLUDE_DESC, 1.277 + 'pushbutton': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 1.278 + 'checkbutton': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 1.279 + 'radiobutton': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 1.280 + 'buttondropdown': NAME_FROM_SUBTREE_RULE, 1.281 + 'combobox': INCLUDE_DESC, 1.282 + 'droplist': INCLUDE_DESC, 1.283 + 'progressbar': INCLUDE_DESC | INCLUDE_VALUE, 1.284 + 'slider': INCLUDE_DESC | INCLUDE_VALUE, 1.285 + 'spinbutton': INCLUDE_DESC | INCLUDE_VALUE, 1.286 + 'diagram': INCLUDE_DESC, 1.287 + 'animation': INCLUDE_DESC, 1.288 + 'equation': INCLUDE_DESC, 1.289 + 'buttonmenu': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 1.290 + 'buttondropdowngrid': NAME_FROM_SUBTREE_RULE, 1.291 + 'pagetablist': INCLUDE_DESC, 1.292 + 'canvas': INCLUDE_DESC, 1.293 + 'check menu item': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 1.294 + 'label': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 1.295 + 'password text': INCLUDE_DESC, 1.296 + 'popup menu': INCLUDE_DESC, 1.297 + 'radio menu item': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 1.298 + 'table column header': NAME_FROM_SUBTREE_RULE, 1.299 + 'table row header': NAME_FROM_SUBTREE_RULE, 1.300 + 'tear off menu item': NAME_FROM_SUBTREE_RULE, 1.301 + 'toggle button': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 1.302 + 'parent menuitem': NAME_FROM_SUBTREE_RULE, 1.303 + 'header': INCLUDE_DESC, 1.304 + 'footer': INCLUDE_DESC, 1.305 + 'entry': INCLUDE_DESC | INCLUDE_NAME | INCLUDE_VALUE, 1.306 + 'caption': INCLUDE_DESC, 1.307 + 'document frame': INCLUDE_DESC, 1.308 + 'heading': INCLUDE_DESC, 1.309 + 'calendar': INCLUDE_DESC | INCLUDE_NAME, 1.310 + 'combobox list': INCLUDE_DESC, 1.311 + 'combobox option': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 1.312 + 'listbox option': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE, 1.313 + 'listbox rich option': NAME_FROM_SUBTREE_RULE, 1.314 + 'gridcell': NAME_FROM_SUBTREE_RULE, 1.315 + 'check rich option': NAME_FROM_SUBTREE_RULE, 1.316 + 'term': NAME_FROM_SUBTREE_RULE, 1.317 + 'definition': NAME_FROM_SUBTREE_RULE, 1.318 + 'key': NAME_FROM_SUBTREE_RULE, 1.319 + 'image map': INCLUDE_DESC, 1.320 + 'option': INCLUDE_DESC, 1.321 + 'listbox': INCLUDE_DESC, 1.322 + 'definitionlist': INCLUDE_DESC | INCLUDE_NAME, 1.323 + 'dialog': INCLUDE_DESC | INCLUDE_NAME, 1.324 + 'chrome window': IGNORE_EXPLICIT_NAME, 1.325 + 'app root': IGNORE_EXPLICIT_NAME }, 1.326 + 1.327 + objectOutputFunctions: { 1.328 + _generateBaseOutput: function _generateBaseOutput(aAccessible, aRoleStr, aState, aFlags) { 1.329 + let output = []; 1.330 + 1.331 + if (aFlags & INCLUDE_DESC) { 1.332 + let desc = this._getLocalizedState(aState); 1.333 + let roleStr = this._getLocalizedRole(aRoleStr); 1.334 + if (roleStr) { 1.335 + this._addType(desc, aAccessible, aRoleStr); 1.336 + desc.push(roleStr); 1.337 + } 1.338 + output.push(desc.join(' ')); 1.339 + } 1.340 + 1.341 + if (aFlags & INCLUDE_VALUE) { 1.342 + let value = aAccessible.value; 1.343 + if (value) { 1.344 + output[this.outputOrder === OUTPUT_DESC_FIRST ? 1.345 + 'push' : 'unshift'](value); 1.346 + } 1.347 + } 1.348 + 1.349 + this._addName(output, aAccessible, aFlags); 1.350 + this._addLandmark(output, aAccessible); 1.351 + 1.352 + return output; 1.353 + }, 1.354 + 1.355 + label: function label(aAccessible, aRoleStr, aState, aFlags, aContext) { 1.356 + if (aContext.isNestedControl || 1.357 + aContext.accessible == Utils.getEmbeddedControl(aAccessible)) { 1.358 + // If we are on a nested control, or a nesting label, 1.359 + // we don't need the context. 1.360 + return []; 1.361 + } 1.362 + 1.363 + return this.objectOutputFunctions.defaultFunc.apply(this, arguments); 1.364 + }, 1.365 + 1.366 + entry: function entry(aAccessible, aRoleStr, aState, aFlags) { 1.367 + let rolestr = aState.contains(States.MULTI_LINE) ? 'textarea' : 'entry'; 1.368 + return this.objectOutputFunctions.defaultFunc.apply( 1.369 + this, [aAccessible, rolestr, aState, aFlags]); 1.370 + }, 1.371 + 1.372 + pagetab: function pagetab(aAccessible, aRoleStr, aState, aFlags) { 1.373 + let localizedRole = this._getLocalizedRole(aRoleStr); 1.374 + let itemno = {}; 1.375 + let itemof = {}; 1.376 + aAccessible.groupPosition({}, itemof, itemno); 1.377 + let output = []; 1.378 + let desc = this._getLocalizedState(aState); 1.379 + desc.push( 1.380 + Utils.stringBundle.formatStringFromName( 1.381 + 'objItemOf', [localizedRole, itemno.value, itemof.value], 3)); 1.382 + output.push(desc.join(' ')); 1.383 + 1.384 + this._addName(output, aAccessible, aFlags); 1.385 + this._addLandmark(output, aAccessible); 1.386 + 1.387 + return output; 1.388 + }, 1.389 + 1.390 + table: function table(aAccessible, aRoleStr, aState, aFlags) { 1.391 + let output = []; 1.392 + let table; 1.393 + try { 1.394 + table = aAccessible.QueryInterface(Ci.nsIAccessibleTable); 1.395 + } catch (x) { 1.396 + Logger.logException(x); 1.397 + return output; 1.398 + } finally { 1.399 + // Check if it's a layout table, and bail out if true. 1.400 + // We don't want to speak any table information for layout tables. 1.401 + if (table.isProbablyForLayout()) { 1.402 + return output; 1.403 + } 1.404 + let tableColumnInfo = this._getPluralFormString('tableColumnInfo', 1.405 + table.columnCount); 1.406 + let tableRowInfo = this._getPluralFormString('tableRowInfo', 1.407 + table.rowCount); 1.408 + output.push(Utils.stringBundle.formatStringFromName( 1.409 + this._getOutputName('tableInfo'), [this._getLocalizedRole(aRoleStr), 1.410 + tableColumnInfo, tableRowInfo], 3)); 1.411 + this._addName(output, aAccessible, aFlags); 1.412 + this._addLandmark(output, aAccessible); 1.413 + return output; 1.414 + } 1.415 + } 1.416 + } 1.417 +}; 1.418 + 1.419 +/** 1.420 + * Generates speech utterances from objects, actions and state changes. 1.421 + * An utterance is an array of strings. 1.422 + * 1.423 + * It should not be assumed that flattening an utterance array would create a 1.424 + * gramatically correct sentence. For example, {@link genForObject} might 1.425 + * return: ['graphic', 'Welcome to my home page']. 1.426 + * Each string element in an utterance should be gramatically correct in itself. 1.427 + * Another example from {@link genForObject}: ['list item 2 of 5', 'Alabama']. 1.428 + * 1.429 + * An utterance is ordered from the least to the most important. Speaking the 1.430 + * last string usually makes sense, but speaking the first often won't. 1.431 + * For example {@link genForAction} might return ['button', 'clicked'] for a 1.432 + * clicked event. Speaking only 'clicked' makes sense. Speaking 'button' does 1.433 + * not. 1.434 + */ 1.435 +this.UtteranceGenerator = { 1.436 + __proto__: OutputGenerator, 1.437 + 1.438 + gActionMap: { 1.439 + jump: 'jumpAction', 1.440 + press: 'pressAction', 1.441 + check: 'checkAction', 1.442 + uncheck: 'uncheckAction', 1.443 + select: 'selectAction', 1.444 + unselect: 'unselectAction', 1.445 + open: 'openAction', 1.446 + close: 'closeAction', 1.447 + switch: 'switchAction', 1.448 + click: 'clickAction', 1.449 + collapse: 'collapseAction', 1.450 + expand: 'expandAction', 1.451 + activate: 'activateAction', 1.452 + cycle: 'cycleAction' 1.453 + }, 1.454 + 1.455 + //TODO: May become more verbose in the future. 1.456 + genForAction: function genForAction(aObject, aActionName) { 1.457 + return [Utils.stringBundle.GetStringFromName(this.gActionMap[aActionName])]; 1.458 + }, 1.459 + 1.460 + genForLiveRegion: function genForLiveRegion(aContext, aIsHide, aModifiedText) { 1.461 + let utterance = []; 1.462 + if (aIsHide) { 1.463 + utterance.push(Utils.stringBundle.GetStringFromName('hidden')); 1.464 + } 1.465 + return utterance.concat( 1.466 + aModifiedText || this.genForContext(aContext).output); 1.467 + }, 1.468 + 1.469 + genForAnnouncement: function genForAnnouncement(aAnnouncement) { 1.470 + try { 1.471 + return [Utils.stringBundle.GetStringFromName(aAnnouncement)]; 1.472 + } catch (x) { 1.473 + return [aAnnouncement]; 1.474 + } 1.475 + }, 1.476 + 1.477 + genForTabStateChange: function genForTabStateChange(aObject, aTabState) { 1.478 + switch (aTabState) { 1.479 + case 'newtab': 1.480 + return [Utils.stringBundle.GetStringFromName('tabNew')]; 1.481 + case 'loading': 1.482 + return [Utils.stringBundle.GetStringFromName('tabLoading')]; 1.483 + case 'loaded': 1.484 + return [aObject.name || '', 1.485 + Utils.stringBundle.GetStringFromName('tabLoaded')]; 1.486 + case 'loadstopped': 1.487 + return [Utils.stringBundle.GetStringFromName('tabLoadStopped')]; 1.488 + case 'reload': 1.489 + return [Utils.stringBundle.GetStringFromName('tabReload')]; 1.490 + default: 1.491 + return []; 1.492 + } 1.493 + }, 1.494 + 1.495 + genForEditingMode: function genForEditingMode(aIsEditing) { 1.496 + return [Utils.stringBundle.GetStringFromName( 1.497 + aIsEditing ? 'editingMode' : 'navigationMode')]; 1.498 + }, 1.499 + 1.500 + objectOutputFunctions: { 1.501 + 1.502 + __proto__: OutputGenerator.objectOutputFunctions, 1.503 + 1.504 + defaultFunc: function defaultFunc(aAccessible, aRoleStr, aState, aFlags) { 1.505 + return this.objectOutputFunctions._generateBaseOutput.apply(this, arguments); 1.506 + }, 1.507 + 1.508 + heading: function heading(aAccessible, aRoleStr, aState, aFlags) { 1.509 + let level = {}; 1.510 + aAccessible.groupPosition(level, {}, {}); 1.511 + let utterance = 1.512 + [Utils.stringBundle.formatStringFromName( 1.513 + 'headingLevel', [level.value], 1)]; 1.514 + 1.515 + this._addName(utterance, aAccessible, aFlags); 1.516 + this._addLandmark(utterance, aAccessible); 1.517 + 1.518 + return utterance; 1.519 + }, 1.520 + 1.521 + listitem: function listitem(aAccessible, aRoleStr, aState, aFlags) { 1.522 + let itemno = {}; 1.523 + let itemof = {}; 1.524 + aAccessible.groupPosition({}, itemof, itemno); 1.525 + let utterance = []; 1.526 + if (itemno.value == 1) // Start of list 1.527 + utterance.push(Utils.stringBundle.GetStringFromName('listStart')); 1.528 + else if (itemno.value == itemof.value) // last item 1.529 + utterance.push(Utils.stringBundle.GetStringFromName('listEnd')); 1.530 + 1.531 + this._addName(utterance, aAccessible, aFlags); 1.532 + this._addLandmark(utterance, aAccessible); 1.533 + 1.534 + return utterance; 1.535 + }, 1.536 + 1.537 + list: function list(aAccessible, aRoleStr, aState, aFlags) { 1.538 + return this._getListUtterance 1.539 + (aAccessible, aRoleStr, aFlags, aAccessible.childCount); 1.540 + }, 1.541 + 1.542 + definitionlist: function definitionlist(aAccessible, aRoleStr, aState, aFlags) { 1.543 + return this._getListUtterance 1.544 + (aAccessible, aRoleStr, aFlags, aAccessible.childCount / 2); 1.545 + }, 1.546 + 1.547 + application: function application(aAccessible, aRoleStr, aState, aFlags) { 1.548 + // Don't utter location of applications, it gets tiring. 1.549 + if (aAccessible.name != aAccessible.DOMNode.location) 1.550 + return this.objectOutputFunctions.defaultFunc.apply(this, 1.551 + [aAccessible, aRoleStr, aState, aFlags]); 1.552 + 1.553 + return []; 1.554 + }, 1.555 + 1.556 + cell: function cell(aAccessible, aRoleStr, aState, aFlags, aContext) { 1.557 + let utterance = []; 1.558 + let cell = aContext.getCellInfo(aAccessible); 1.559 + if (cell) { 1.560 + let desc = []; 1.561 + let addCellChanged = function addCellChanged(aDesc, aChanged, aString, aIndex) { 1.562 + if (aChanged) { 1.563 + aDesc.push(Utils.stringBundle.formatStringFromName(aString, 1.564 + [aIndex + 1], 1)); 1.565 + } 1.566 + }; 1.567 + let addExtent = function addExtent(aDesc, aExtent, aString) { 1.568 + if (aExtent > 1) { 1.569 + aDesc.push(Utils.stringBundle.formatStringFromName(aString, 1.570 + [aExtent], 1)); 1.571 + } 1.572 + }; 1.573 + let addHeaders = function addHeaders(aDesc, aHeaders) { 1.574 + if (aHeaders.length > 0) { 1.575 + aDesc.push.apply(aDesc, aHeaders); 1.576 + } 1.577 + }; 1.578 + 1.579 + addCellChanged(desc, cell.columnChanged, 'columnInfo', cell.columnIndex); 1.580 + addCellChanged(desc, cell.rowChanged, 'rowInfo', cell.rowIndex); 1.581 + 1.582 + addExtent(desc, cell.columnExtent, 'spansColumns'); 1.583 + addExtent(desc, cell.rowExtent, 'spansRows'); 1.584 + 1.585 + addHeaders(desc, cell.columnHeaders); 1.586 + addHeaders(desc, cell.rowHeaders); 1.587 + 1.588 + utterance.push(desc.join(' ')); 1.589 + } 1.590 + 1.591 + this._addName(utterance, aAccessible, aFlags); 1.592 + this._addLandmark(utterance, aAccessible); 1.593 + 1.594 + return utterance; 1.595 + }, 1.596 + 1.597 + columnheader: function columnheader() { 1.598 + return this.objectOutputFunctions.cell.apply(this, arguments); 1.599 + }, 1.600 + 1.601 + rowheader: function rowheader() { 1.602 + return this.objectOutputFunctions.cell.apply(this, arguments); 1.603 + } 1.604 + }, 1.605 + 1.606 + _getContextStart: function _getContextStart(aContext) { 1.607 + return aContext.newAncestry; 1.608 + }, 1.609 + 1.610 + _getLocalizedRole: function _getLocalizedRole(aRoleStr) { 1.611 + try { 1.612 + return Utils.stringBundle.GetStringFromName( 1.613 + this._getOutputName(aRoleStr)); 1.614 + } catch (x) { 1.615 + return ''; 1.616 + } 1.617 + }, 1.618 + 1.619 + _getLocalizedState: function _getLocalizedState(aState) { 1.620 + let stateUtterances = []; 1.621 + 1.622 + if (aState.contains(States.UNAVAILABLE)) { 1.623 + stateUtterances.push( 1.624 + Utils.stringBundle.GetStringFromName('stateUnavailable')); 1.625 + } 1.626 + 1.627 + // Don't utter this in Jelly Bean, we let TalkBack do it for us there. 1.628 + // This is because we expose the checked information on the node itself. 1.629 + // XXX: this means the checked state is always appended to the end, regardless 1.630 + // of the utterance ordering preference. 1.631 + if ((Utils.AndroidSdkVersion < 16 || Utils.MozBuildApp === 'browser') && 1.632 + aState.contains(States.CHECKABLE)) { 1.633 + let statetr = aState.contains(States.CHECKED) ? 1.634 + 'stateChecked' : 'stateNotChecked'; 1.635 + stateUtterances.push(Utils.stringBundle.GetStringFromName(statetr)); 1.636 + } 1.637 + 1.638 + if (aState.contains(States.PRESSED)) { 1.639 + stateUtterances.push( 1.640 + Utils.stringBundle.GetStringFromName('statePressed')); 1.641 + } 1.642 + 1.643 + if (aState.contains(States.EXPANDABLE)) { 1.644 + let statetr = aState.contains(States.EXPANDED) ? 1.645 + 'stateExpanded' : 'stateCollapsed'; 1.646 + stateUtterances.push(Utils.stringBundle.GetStringFromName(statetr)); 1.647 + } 1.648 + 1.649 + if (aState.contains(States.REQUIRED)) { 1.650 + stateUtterances.push( 1.651 + Utils.stringBundle.GetStringFromName('stateRequired')); 1.652 + } 1.653 + 1.654 + if (aState.contains(States.TRAVERSED)) { 1.655 + stateUtterances.push( 1.656 + Utils.stringBundle.GetStringFromName('stateTraversed')); 1.657 + } 1.658 + 1.659 + if (aState.contains(States.HASPOPUP)) { 1.660 + stateUtterances.push( 1.661 + Utils.stringBundle.GetStringFromName('stateHasPopup')); 1.662 + } 1.663 + 1.664 + if (aState.contains(States.SELECTED)) { 1.665 + stateUtterances.push( 1.666 + Utils.stringBundle.GetStringFromName('stateSelected')); 1.667 + } 1.668 + 1.669 + return stateUtterances; 1.670 + }, 1.671 + 1.672 + _getListUtterance: function _getListUtterance(aAccessible, aRoleStr, aFlags, aItemCount) { 1.673 + let desc = []; 1.674 + let roleStr = this._getLocalizedRole(aRoleStr); 1.675 + if (roleStr) { 1.676 + desc.push(roleStr); 1.677 + } 1.678 + desc.push(this._getPluralFormString('listItemsCount', aItemCount)); 1.679 + let utterance = [desc.join(' ')]; 1.680 + 1.681 + this._addName(utterance, aAccessible, aFlags); 1.682 + this._addLandmark(utterance, aAccessible); 1.683 + 1.684 + return utterance; 1.685 + } 1.686 +}; 1.687 + 1.688 + 1.689 +this.BrailleGenerator = { 1.690 + __proto__: OutputGenerator, 1.691 + 1.692 + genForContext: function genForContext(aContext) { 1.693 + let output = OutputGenerator.genForContext.apply(this, arguments); 1.694 + 1.695 + let acc = aContext.accessible; 1.696 + 1.697 + // add the static text indicating a list item; do this for both listitems or 1.698 + // direct first children of listitems, because these are both common browsing 1.699 + // scenarios 1.700 + let addListitemIndicator = function addListitemIndicator(indicator = '*') { 1.701 + output.output.unshift(indicator); 1.702 + }; 1.703 + 1.704 + if (acc.indexInParent === 1 && 1.705 + acc.parent.role == Roles.LISTITEM && 1.706 + acc.previousSibling.role == Roles.STATICTEXT) { 1.707 + if (acc.parent.parent && acc.parent.parent.DOMNode && 1.708 + acc.parent.parent.DOMNode.nodeName == 'UL') { 1.709 + addListitemIndicator(); 1.710 + } else { 1.711 + addListitemIndicator(acc.previousSibling.name.trim()); 1.712 + } 1.713 + } else if (acc.role == Roles.LISTITEM && acc.firstChild && 1.714 + acc.firstChild.role == Roles.STATICTEXT) { 1.715 + if (acc.parent.DOMNode.nodeName == 'UL') { 1.716 + addListitemIndicator(); 1.717 + } else { 1.718 + addListitemIndicator(acc.firstChild.name.trim()); 1.719 + } 1.720 + } 1.721 + 1.722 + if (acc instanceof Ci.nsIAccessibleText) { 1.723 + output.endOffset = this.outputOrder === OUTPUT_DESC_FIRST ? 1.724 + output.output.join(' ').length : acc.characterCount; 1.725 + output.startOffset = output.endOffset - acc.characterCount; 1.726 + } 1.727 + 1.728 + return output; 1.729 + }, 1.730 + 1.731 + objectOutputFunctions: { 1.732 + 1.733 + __proto__: OutputGenerator.objectOutputFunctions, 1.734 + 1.735 + defaultFunc: function defaultFunc(aAccessible, aRoleStr, aState, aFlags) { 1.736 + return this.objectOutputFunctions._generateBaseOutput.apply(this, arguments); 1.737 + }, 1.738 + 1.739 + listitem: function listitem(aAccessible, aRoleStr, aState, aFlags) { 1.740 + let braille = []; 1.741 + 1.742 + this._addName(braille, aAccessible, aFlags); 1.743 + this._addLandmark(braille, aAccessible); 1.744 + 1.745 + return braille; 1.746 + }, 1.747 + 1.748 + cell: function cell(aAccessible, aRoleStr, aState, aFlags, aContext) { 1.749 + let braille = []; 1.750 + let cell = aContext.getCellInfo(aAccessible); 1.751 + if (cell) { 1.752 + let desc = []; 1.753 + let addHeaders = function addHeaders(aDesc, aHeaders) { 1.754 + if (aHeaders.length > 0) { 1.755 + aDesc.push.apply(aDesc, aHeaders); 1.756 + } 1.757 + }; 1.758 + 1.759 + desc.push(Utils.stringBundle.formatStringFromName( 1.760 + this._getOutputName('cellInfo'), [cell.columnIndex + 1, 1.761 + cell.rowIndex + 1], 2)); 1.762 + 1.763 + addHeaders(desc, cell.columnHeaders); 1.764 + addHeaders(desc, cell.rowHeaders); 1.765 + braille.push(desc.join(' ')); 1.766 + } 1.767 + 1.768 + this._addName(braille, aAccessible, aFlags); 1.769 + this._addLandmark(braille, aAccessible); 1.770 + return braille; 1.771 + }, 1.772 + 1.773 + columnheader: function columnheader() { 1.774 + return this.objectOutputFunctions.cell.apply(this, arguments); 1.775 + }, 1.776 + 1.777 + rowheader: function rowheader() { 1.778 + return this.objectOutputFunctions.cell.apply(this, arguments); 1.779 + }, 1.780 + 1.781 + statictext: function statictext(aAccessible, aRoleStr, aState, aFlags) { 1.782 + // Since we customize the list bullet's output, we add the static 1.783 + // text from the first node in each listitem, so skip it here. 1.784 + if (aAccessible.parent.role == Roles.LISTITEM) { 1.785 + return []; 1.786 + } 1.787 + 1.788 + return this.objectOutputFunctions._useStateNotRole.apply(this, arguments); 1.789 + }, 1.790 + 1.791 + _useStateNotRole: function _useStateNotRole(aAccessible, aRoleStr, aState, aFlags) { 1.792 + let braille = []; 1.793 + 1.794 + let desc = this._getLocalizedState(aState, aAccessible.role); 1.795 + braille.push(desc.join(' ')); 1.796 + 1.797 + this._addName(braille, aAccessible, aFlags); 1.798 + this._addLandmark(braille, aAccessible); 1.799 + 1.800 + return braille; 1.801 + }, 1.802 + 1.803 + checkbutton: function checkbutton(aAccessible, aRoleStr, aState, aFlags) { 1.804 + return this.objectOutputFunctions._useStateNotRole.apply(this, arguments); 1.805 + }, 1.806 + 1.807 + radiobutton: function radiobutton(aAccessible, aRoleStr, aState, aFlags) { 1.808 + return this.objectOutputFunctions._useStateNotRole.apply(this, arguments); 1.809 + }, 1.810 + 1.811 + togglebutton: function radiobutton(aAccessible, aRoleStr, aState, aFlags) { 1.812 + return this.objectOutputFunctions._useStateNotRole.apply(this, arguments); 1.813 + } 1.814 + }, 1.815 + 1.816 + _getContextStart: function _getContextStart(aContext) { 1.817 + if (aContext.accessible.parent.role == Roles.LINK) { 1.818 + return [aContext.accessible.parent]; 1.819 + } 1.820 + 1.821 + return []; 1.822 + }, 1.823 + 1.824 + _getOutputName: function _getOutputName(aName) { 1.825 + return OutputGenerator._getOutputName(aName) + 'Abbr'; 1.826 + }, 1.827 + 1.828 + _getLocalizedRole: function _getLocalizedRole(aRoleStr) { 1.829 + try { 1.830 + return Utils.stringBundle.GetStringFromName( 1.831 + this._getOutputName(aRoleStr)); 1.832 + } catch (x) { 1.833 + try { 1.834 + return Utils.stringBundle.GetStringFromName( 1.835 + OutputGenerator._getOutputName(aRoleStr)); 1.836 + } catch (y) { 1.837 + return ''; 1.838 + } 1.839 + } 1.840 + }, 1.841 + 1.842 + _getLocalizedState: function _getLocalizedState(aState, aRole) { 1.843 + let stateBraille = []; 1.844 + 1.845 + let getResultMarker = function getResultMarker(aMarker) { 1.846 + // aMarker is a simple boolean. 1.847 + let resultMarker = []; 1.848 + resultMarker.push('('); 1.849 + resultMarker.push(aMarker ? 'x' : ' '); 1.850 + resultMarker.push(')'); 1.851 + 1.852 + return resultMarker.join(''); 1.853 + }; 1.854 + 1.855 + if (aState.contains(States.CHECKABLE)) { 1.856 + stateBraille.push(getResultMarker(aState.contains(States.CHECKED))); 1.857 + } 1.858 + 1.859 + if (aRole === Roles.TOGGLE_BUTTON) { 1.860 + stateBraille.push(getResultMarker(aState.contains(States.PRESSED))); 1.861 + } 1.862 + 1.863 + return stateBraille; 1.864 + } 1.865 + 1.866 +};