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 |