| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * @fileoverview Provides output services for ChromeVox. | 6 * @fileoverview Provides output services for ChromeVox. |
| 7 */ | 7 */ |
| 8 | 8 |
| 9 goog.provide('Output'); | 9 goog.provide('Output'); |
| 10 goog.provide('Output.EventType'); | 10 goog.provide('Output.EventType'); |
| (...skipping 26 matching lines...) Expand all Loading... |
| 37 * $text_sel_end'). | 37 * $text_sel_end'). |
| 38 * = suffix: used to specify substitution only if not previously appended. | 38 * = suffix: used to specify substitution only if not previously appended. |
| 39 * For example, $name= would insert the name attribute only if no name | 39 * For example, $name= would insert the name attribute only if no name |
| 40 * attribute had been inserted previously. | 40 * attribute had been inserted previously. |
| 41 * @param {!cursors.Range} range | 41 * @param {!cursors.Range} range |
| 42 * @param {cursors.Range} prevRange | 42 * @param {cursors.Range} prevRange |
| 43 * @param {chrome.automation.EventType|Output.EventType} type | 43 * @param {chrome.automation.EventType|Output.EventType} type |
| 44 * @constructor | 44 * @constructor |
| 45 */ | 45 */ |
| 46 Output = function(range, prevRange, type) { | 46 Output = function(range, prevRange, type) { |
| 47 // TODO(dtseng): Include brailleBuffer once we add rules for braille. | 47 // TODO(dtseng): Include braille specific rules. |
| 48 /** @type {!cvox.Spannable} */ | 48 /** @type {!cvox.Spannable} */ |
| 49 this.buffer_ = new cvox.Spannable(); | 49 this.buffer_ = new cvox.Spannable(); |
| 50 /** @type {!cvox.Spannable} */ |
| 51 this.brailleBuffer_ = new cvox.Spannable(); |
| 52 /** @type {!Array.<Object>} */ |
| 50 this.locations_ = []; | 53 this.locations_ = []; |
| 51 | 54 |
| 55 /** |
| 56 * Current global options. |
| 57 * @type {{speech: boolean, braille: boolean, location: boolean}} |
| 58 */ |
| 59 this.formatOptions_ = {speech: true, braille: false, location: true}; |
| 60 |
| 52 this.render_(range, prevRange, type); | 61 this.render_(range, prevRange, type); |
| 53 this.handleSpeech(); | 62 this.handleSpeech(); |
| 54 this.handleBraille(); | 63 this.handleBraille(); |
| 55 this.handleDisplay(); | 64 this.handleDisplay(); |
| 56 }; | 65 }; |
| 57 | 66 |
| 58 /** | 67 /** |
| 59 * Delimiter to use between output values. | 68 * Delimiter to use between output values. |
| 60 * @type {string} | 69 * @type {string} |
| 61 */ | 70 */ |
| 62 Output.SPACE = ' '; | 71 Output.SPACE = ' '; |
| 63 | 72 |
| 64 /** | 73 /** |
| 65 * Rules specifying format of AutomationNodes for output. | 74 * Rules specifying format of AutomationNodes for output. |
| 66 * @type {!Object.<string, Object.<string, Object.<string, string>>>} | 75 * @type {!Object.<string, Object.<string, Object.<string, string>>>} |
| 67 */ | 76 */ |
| 68 Output.RULES = { | 77 Output.RULES = { |
| 69 navigate: { | 78 navigate: { |
| 70 'default': { | 79 'default': { |
| 71 speak: '$name $role $value', | 80 speak: '$name $role $value', |
| 72 braille: '' | 81 braille: '' |
| 73 }, | 82 }, |
| 83 alert: { |
| 84 speak: '@aria_role_alert $name @earcon(ALERT_NONMODAL)' |
| 85 }, |
| 74 button: { | 86 button: { |
| 75 speak: '$name $earcon(BUTTON, @tag_button)' | 87 speak: '$name $earcon(BUTTON, @tag_button)' |
| 76 }, | 88 }, |
| 77 checkBox: { | 89 checkBox: { |
| 78 speak: '$or($checked, @describe_checkbox_checked($name), ' + | 90 speak: '$or($checked, @describe_checkbox_checked($name), ' + |
| 79 '@describe_checkbox_unchecked($name)) ' + | 91 '@describe_checkbox_unchecked($name)) ' + |
| 80 '$or($checked, ' + | 92 '$or($checked, ' + |
| 81 '$earcon(CHECK_ON, @input_type_checkbox), ' + | 93 '$earcon(CHECK_ON, @input_type_checkbox), ' + |
| 82 '$earcon(CHECK_OFF, @input_type_checkbox))' | 94 '$earcon(CHECK_OFF, @input_type_checkbox))' |
| 83 }, | 95 }, |
| 84 heading: { | 96 heading: { |
| 85 enter: '@aria_role_heading', | 97 enter: '@aria_role_heading', |
| 86 speak: '@aria_role_heading $name=' | 98 speak: '@aria_role_heading $name=' |
| 87 }, | 99 }, |
| 88 inlineTextBox: { | 100 inlineTextBox: { |
| 89 speak: '$value=' | 101 speak: '$value=' |
| 90 }, | 102 }, |
| 91 link: { | 103 link: { |
| 92 enter: '$name= $visited $earcon(LINK, @tag_link)=', | 104 enter: '$name= $visited $earcon(LINK, @tag_link)=', |
| 93 stay: '$name= $visited @tag_link', | 105 stay: '$name= $visited @tag_link', |
| 94 speak: '$name= $visited $earcon(LINK, @tag_link)=' | 106 speak: '$name= $visited $earcon(LINK, @tag_link)=' |
| 95 }, | 107 }, |
| 96 list: { | 108 list: { |
| 97 enter: '$role' | 109 enter: '$role' |
| 98 }, | 110 }, |
| 99 listItem: { | 111 listItem: { |
| 100 enter: '$role' | 112 enter: '$role' |
| 101 }, | 113 }, |
| 114 menuItem: { |
| 115 speak: '$or($haspopup, @describe_menu_item_with_submenu($name), ' + |
| 116 '@describe_menu_item($name))' |
| 117 }, |
| 118 menuListOption: { |
| 119 speak: '$name $value @aria_role_menuitem ' + |
| 120 '@describe_index($indexInParent, $parentChildCount)' |
| 121 }, |
| 102 paragraph: { | 122 paragraph: { |
| 103 speak: '$value' | 123 speak: '$value' |
| 104 }, | 124 }, |
| 125 popUpButton: { |
| 126 speak: '$value $name @tag_button @aria_has_popup $earcon(LISTBOX)' |
| 127 }, |
| 128 radioButton: { |
| 129 speak: '$or($checked, @describe_radio_selected($name), ' + |
| 130 '@describe_radio_unselected($name)) ' + |
| 131 '$or($checked, ' + |
| 132 '$earcon(CHECK_ON, @input_type_radio), ' + |
| 133 '$earcon(CHECK_OFF, @input_type_radio))' |
| 134 }, |
| 135 slider: { |
| 136 speak: '@describe_slider($value, $name)' |
| 137 }, |
| 105 staticText: { | 138 staticText: { |
| 106 speak: '$value' | 139 speak: '$value' |
| 107 }, | 140 }, |
| 108 textBox: { | 141 textBox: { |
| 109 speak: '$name $value $earcon(EDITABLE_TEXT, @input_type_text)' | 142 speak: '$name $value $earcon(EDITABLE_TEXT, @input_type_text)' |
| 110 }, | 143 }, |
| 144 tab: { |
| 145 speak: '@describe_tab($name)' |
| 146 }, |
| 111 textField: { | 147 textField: { |
| 112 speak: '$name $value $earcon(EDITABLE_TEXT, @input_type_text)' | 148 speak: '$name $value $earcon(EDITABLE_TEXT, @input_type_text)' |
| 113 }, | 149 }, |
| 114 window: { | 150 window: { |
| 115 enter: '$name $role= $earcon(OBJECT_OPEN)' | 151 speak: '@describe_window($name) $earcon(OBJECT_OPEN)' |
| 116 // TODO(dtseng): A leave event is not reliable because views does not fire | |
| 117 // focus events after closing windows. | |
| 118 } | 152 } |
| 119 }, | 153 }, |
| 120 menuStart: { | 154 menuStart: { |
| 121 'default': { | 155 'default': { |
| 122 speak: '@chrome_menu_opened($name) $role $earcon(OBJECT_OPEN)' | 156 speak: '@chrome_menu_opened($name) $role $earcon(OBJECT_OPEN)' |
| 123 } | 157 } |
| 124 }, | 158 }, |
| 125 menuEnd: { | 159 menuEnd: { |
| 126 'default': { | 160 'default': { |
| 127 speak: '$earcon(OBJECT_CLOSE)' | 161 speak: '$earcon(OBJECT_CLOSE)' |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 188 a.run(); | 222 a.run(); |
| 189 }); | 223 }); |
| 190 } | 224 } |
| 191 }, | 225 }, |
| 192 | 226 |
| 193 /** | 227 /** |
| 194 * Handles output to braille. | 228 * Handles output to braille. |
| 195 */ | 229 */ |
| 196 handleBraille: function() { | 230 handleBraille: function() { |
| 197 cvox.ChromeVox.braille.write( | 231 cvox.ChromeVox.braille.write( |
| 198 cvox.NavBraille.fromText(this.buffer_.toString())); | 232 cvox.NavBraille.fromText(this.brailleBuffer_.toString())); |
| 199 }, | 233 }, |
| 200 | 234 |
| 201 /** | 235 /** |
| 202 * Handles output to visual display. | 236 * Handles output to visual display. |
| 203 */ | 237 */ |
| 204 handleDisplay: function() { | 238 handleDisplay: function() { |
| 205 chrome.accessibilityPrivate.setFocusRing(this.locations_); | 239 chrome.accessibilityPrivate.setFocusRing(this.locations_); |
| 206 }, | 240 }, |
| 207 | 241 |
| 208 /** | 242 /** |
| 209 * Renders the given range using optional context previous range and event | 243 * Renders the given range using optional context previous range and event |
| 210 * type. | 244 * type. |
| 211 * @param {!cursors.Range} range | 245 * @param {!cursors.Range} range |
| 212 * @param {cursors.Range} prevRange | 246 * @param {cursors.Range} prevRange |
| 213 * @param {chrome.automation.EventType|string} type | 247 * @param {chrome.automation.EventType|string} type |
| 214 * @private | 248 * @private |
| 215 */ | 249 */ |
| 216 render_: function(range, prevRange, type) { | 250 render_: function(range, prevRange, type) { |
| 217 var buff = new cvox.Spannable(); | 251 var buff = new cvox.Spannable(); |
| 252 var brailleBuff = new cvox.Spannable(); |
| 218 if (range.isSubNode()) | 253 if (range.isSubNode()) |
| 219 this.subNode_(range, prevRange, type, buff); | 254 this.subNode_(range, prevRange, type, buff); |
| 220 else | 255 else |
| 221 this.range_(range, prevRange, type, buff); | 256 this.range_(range, prevRange, type, buff); |
| 222 | 257 |
| 258 this.formatOptions_.braille = true; |
| 259 this.formatOptions_.location = false; |
| 260 if (range.isSubNode()) |
| 261 this.subNode_(range, prevRange, type, brailleBuff); |
| 262 else |
| 263 this.range_(range, prevRange, type, brailleBuff); |
| 264 |
| 223 this.buffer_ = buff; | 265 this.buffer_ = buff; |
| 266 this.brailleBuffer_ = brailleBuff; |
| 224 }, | 267 }, |
| 225 | 268 |
| 226 /** | 269 /** |
| 227 * Format the node given the format specifier. | 270 * Format the node given the format specifier. |
| 228 * @param {!chrome.automation.AutomationNode} node | 271 * @param {!chrome.automation.AutomationNode} node |
| 229 * @param {string|!Object} format The output format either specified as an | 272 * @param {string|!Object} format The output format either specified as an |
| 230 * output template string or a parsed output format tree. | 273 * output template string or a parsed output format tree. |
| 231 * @param {!Object=} opt_exclude A set of attributes to exclude. | 274 * @param {!Object=} opt_exclude A set of attributes to exclude. |
| 232 * @private | 275 * @private |
| 233 */ | 276 */ |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 271 | 314 |
| 272 if (opt_exclude[token]) | 315 if (opt_exclude[token]) |
| 273 return; | 316 return; |
| 274 | 317 |
| 275 // All possible tokens based on prefix. | 318 // All possible tokens based on prefix. |
| 276 if (prefix == '$') { | 319 if (prefix == '$') { |
| 277 options.annotation = token; | 320 options.annotation = token; |
| 278 if (token == 'role') { | 321 if (token == 'role') { |
| 279 // Non-localized role and state obtained by default. | 322 // Non-localized role and state obtained by default. |
| 280 this.addToSpannable_(buff, node.role, options); | 323 this.addToSpannable_(buff, node.role, options); |
| 324 } else if (token == 'indexInParent') { |
| 325 this.addToSpannable_(buff, node.indexInParent + 1); |
| 326 } else if (token == 'parentChildCount') { |
| 327 if (node.parent()) |
| 328 this.addToSpannable_(buff, node.parent().children().length); |
| 281 } else if (token == 'state') { | 329 } else if (token == 'state') { |
| 282 Object.getOwnPropertyNames(node.state).forEach(function(s) { | 330 Object.getOwnPropertyNames(node.state).forEach(function(s) { |
| 283 this.addToSpannable_(buff, s, options); | 331 this.addToSpannable_(buff, s, options); |
| 284 }.bind(this)); | 332 }.bind(this)); |
| 285 } else if (node.attributes[token]) { | 333 } else if (node.attributes[token]) { |
| 286 this.addToSpannable_(buff, node.attributes[token], options); | 334 this.addToSpannable_(buff, node.attributes[token], options); |
| 287 } else if (node.state[token]) { | 335 } else if (node.state[token]) { |
| 288 this.addToSpannable_(buff, token, options); | 336 this.addToSpannable_(buff, token, options); |
| 289 } else if (tree.firstChild) { | 337 } else if (tree.firstChild) { |
| 290 // Custom functions. | 338 // Custom functions. |
| (...skipping 19 matching lines...) Expand all Loading... |
| 310 var msgId = token; | 358 var msgId = token; |
| 311 var msgArgs = []; | 359 var msgArgs = []; |
| 312 var curMsg = tree.firstChild; | 360 var curMsg = tree.firstChild; |
| 313 | 361 |
| 314 while (curMsg) { | 362 while (curMsg) { |
| 315 var arg = curMsg.value; | 363 var arg = curMsg.value; |
| 316 if (arg[0] != '$') { | 364 if (arg[0] != '$') { |
| 317 console.error('Unexpected value: ' + arg); | 365 console.error('Unexpected value: ' + arg); |
| 318 return; | 366 return; |
| 319 } | 367 } |
| 320 arg = arg.slice(1); | 368 var msgBuff = new cvox.Spannable(); |
| 321 if (!node.attributes[arg]) | 369 this.format_(node, arg, msgBuff); |
| 322 msgArgs.push(''); | 370 msgArgs.push(msgBuff.toString()); |
| 323 else | |
| 324 msgArgs.push(node.attributes[arg]); | |
| 325 curMsg = curMsg.nextSibling; | 371 curMsg = curMsg.nextSibling; |
| 326 } | 372 } |
| 373 var msg = cvox.ChromeVox.msgs.getMsg(msgId, msgArgs); |
| 374 try { |
| 375 if (this.formatOptions_.braille) |
| 376 msg = cvox.ChromeVox.msgs.getMsg(msgId + '_brl', msgArgs) || msg; |
| 377 } catch(e) {} |
| 327 | 378 |
| 328 var msg = cvox.ChromeVox.msgs.getMsg(msgId, msgArgs); | |
| 329 if (msg) { | 379 if (msg) { |
| 330 this.addToSpannable_(buff, msg, options); | 380 this.addToSpannable_(buff, msg, options); |
| 331 } | 381 } |
| 332 } | 382 } |
| 333 }.bind(this)); | 383 }.bind(this)); |
| 334 }, | 384 }, |
| 335 | 385 |
| 336 /** | 386 /** |
| 337 * @param {!cursors.Range} range | 387 * @param {!cursors.Range} range |
| 338 * @param {cursors.Range} prevRange | 388 * @param {cursors.Range} prevRange |
| 339 * @param {chrome.automation.EventType|string} type | 389 * @param {chrome.automation.EventType|string} type |
| 340 * @param {!cvox.Spannable} rangeBuff | 390 * @param {!cvox.Spannable} rangeBuff |
| 341 * @private | 391 * @private |
| 342 */ | 392 */ |
| 343 range_: function(range, prevRange, type, rangeBuff) { | 393 range_: function(range, prevRange, type, rangeBuff) { |
| 344 if (!prevRange) | 394 if (!prevRange) |
| 345 prevRange = range; | 395 prevRange = range; |
| 346 | 396 |
| 347 var cursor = range.getStart(); | 397 var cursor = range.getStart(); |
| 348 var prevNode = prevRange.getStart().getNode(); | 398 var prevNode = prevRange.getStart().getNode(); |
| 349 | 399 |
| 350 var formatNodeAndAncestors = function(node, prevNode) { | 400 var formatNodeAndAncestors = function(node, prevNode) { |
| 351 var buff = new cvox.Spannable(); | 401 var buff = new cvox.Spannable(); |
| 352 this.ancestry_(node, prevNode, type, buff); | 402 this.ancestry_(node, prevNode, type, buff); |
| 353 this.node_(node, prevNode, type, buff); | 403 this.node_(node, prevNode, type, buff); |
| 354 this.locations_.push(node.location); | 404 if (this.formatOptions_.location) |
| 405 this.locations_.push(node.location); |
| 355 return buff; | 406 return buff; |
| 356 }.bind(this); | 407 }.bind(this); |
| 357 | 408 |
| 358 while (cursor.getNode() != range.getEnd().getNode()) { | 409 while (cursor.getNode() != range.getEnd().getNode()) { |
| 359 var node = cursor.getNode(); | 410 var node = cursor.getNode(); |
| 360 this.addToSpannable_( | 411 this.addToSpannable_( |
| 361 rangeBuff, formatNodeAndAncestors(node, prevNode)); | 412 rangeBuff, formatNodeAndAncestors(node, prevNode)); |
| 362 prevNode = node; | 413 prevNode = node; |
| 363 cursor = cursor.move(cursors.Unit.NODE, | 414 cursor = cursor.move(cursors.Unit.NODE, |
| 364 cursors.Movement.DIRECTIONAL, | 415 cursors.Movement.DIRECTIONAL, |
| (...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 510 } | 561 } |
| 511 | 562 |
| 512 if (currentNode != root) | 563 if (currentNode != root) |
| 513 throw 'Unbalanced parenthesis.'; | 564 throw 'Unbalanced parenthesis.'; |
| 514 | 565 |
| 515 return root; | 566 return root; |
| 516 } | 567 } |
| 517 }; | 568 }; |
| 518 | 569 |
| 519 }); // goog.scope | 570 }); // goog.scope |
| OLD | NEW |