Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(440)

Side by Side Diff: chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js

Issue 1060913004: Revert of Split spoken feedback up on a per format rule basis. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 28 matching lines...) Expand all
39 * @ prefix: used to substitute a message. Note the ability to specify params to 39 * @ prefix: used to substitute a message. Note the ability to specify params to
40 * the message. For example, '@tag_html' '@selected_index($text_sel_start, 40 * the message. For example, '@tag_html' '@selected_index($text_sel_start,
41 * $text_sel_end'). 41 * $text_sel_end').
42 * = suffix: used to specify substitution only if not previously appended. 42 * = suffix: used to specify substitution only if not previously appended.
43 * For example, $name= would insert the name attribute only if no name 43 * For example, $name= would insert the name attribute only if no name
44 * attribute had been inserted previously. 44 * attribute had been inserted previously.
45 * @constructor 45 * @constructor
46 */ 46 */
47 Output = function() { 47 Output = function() {
48 // TODO(dtseng): Include braille specific rules. 48 // TODO(dtseng): Include braille specific rules.
49 /** @type {!Array<cvox.Spannable>} */ 49 /** @type {!cvox.Spannable} */
50 this.buffer_ = []; 50 this.buffer_ = new cvox.Spannable();
51 /** @type {!Array<cvox.Spannable>} */ 51 /** @type {!cvox.Spannable} */
52 this.brailleBuffer_ = []; 52 this.brailleBuffer_ = new cvox.Spannable();
53 /** @type {!Array<Object>} */ 53 /** @type {!Array<Object>} */
54 this.locations_ = []; 54 this.locations_ = [];
55 /** @type {function()} */ 55 /** @type {function()} */
56 this.speechStartCallback_; 56 this.speechStartCallback_;
57 /** @type {function()} */ 57 /** @type {function()} */
58 this.speechEndCallback_; 58 this.speechEndCallback_;
59 59
60 /** 60 /**
61 * Current global options. 61 * Current global options.
62 * @type {{speech: boolean, braille: boolean, location: boolean}} 62 * @type {{speech: boolean, braille: boolean, location: boolean}}
(...skipping 20 matching lines...) Expand all
83 */ 83 */
84 Output.ROLE_INFO_ = { 84 Output.ROLE_INFO_ = {
85 alert: { 85 alert: {
86 msgId: 'aria_role_alert', 86 msgId: 'aria_role_alert',
87 earcon: 'ALERT_NONMODAL', 87 earcon: 'ALERT_NONMODAL',
88 }, 88 },
89 button: { 89 button: {
90 msgId: 'tag_button', 90 msgId: 'tag_button',
91 earcon: 'BUTTON' 91 earcon: 'BUTTON'
92 }, 92 },
93 checkBox: { 93 checkbox: {
94 msgId: 'input_type_checkbox' 94 msgId: 'input_type_checkbox'
95 }, 95 },
96 dialog: {
97 msgId: 'dialog'
98 },
99 heading: { 96 heading: {
100 msgId: 'aria_role_heading', 97 msgId: 'aria_role_heading',
101 }, 98 },
102 link: { 99 link: {
103 msgId: 'tag_link', 100 msgId: 'tag_link',
104 earcon: 'LINK' 101 earcon: 'LINK'
105 }, 102 },
106 listItem: { 103 listItem: {
107 msgId: 'ARIA_ROLE_LISTITEM', 104 msgId: 'ARIA_ROLE_LISTITEM',
108 earcon: 'list_item' 105 earcon: 'list_item'
(...skipping 13 matching lines...) Expand all
122 }, 119 },
123 textField: { 120 textField: {
124 msgId: 'input_type_text', 121 msgId: 'input_type_text',
125 earcon: 'EDITABLE_TEXT' 122 earcon: 'EDITABLE_TEXT'
126 }, 123 },
127 toolbar: { 124 toolbar: {
128 msgId: 'aria_role_toolbar' 125 msgId: 'aria_role_toolbar'
129 } 126 }
130 }; 127 };
131 128
132 /**
133 * Metadata about supported automation states.
134 * @const {!Object<string,
135 * {on: {msgId: string, earconId: string},
136 * off: {msgId: string, earconId: string}}>}
137 * @private
138 */
139 Output.STATE_INFO_ = {
140 checked: {
141 on: {
142 earconId: 'CHECK_ON',
143 msgId: 'checkbox_checked_state'
144 },
145 off: {
146 earconId: 'CHECK_OFF',
147 msgId: 'checkbox_unchecked_state'
148 }
149 }
150 };
151
152 /** 129 /**
153 * Rules specifying format of AutomationNodes for output. 130 * Rules specifying format of AutomationNodes for output.
154 * @type {!Object<string, Object<string, Object<string, string>>>} 131 * @type {!Object<string, Object<string, Object<string, string>>>}
155 */ 132 */
156 Output.RULES = { 133 Output.RULES = {
157 navigate: { 134 navigate: {
158 'default': { 135 'default': {
159 speak: '$name $value $role', 136 speak: '$name $value $role',
160 braille: '' 137 braille: ''
161 }, 138 },
162 alert: { 139 alert: {
163 speak: '!doNotInterrupt $role $descendants' 140 speak: '!doNotInterrupt ' +
141 '@aria_role_alert $name $earcon(ALERT_NONMODAL) $descendants'
164 }, 142 },
165 checkBox: { 143 checkBox: {
166 speak: '$name $role $checked' 144 speak: '$if($checked, @describe_checkbox_checked($name), ' +
145 '@describe_checkbox_unchecked($name)) ' +
146 '$if($checked, ' +
147 '$earcon(CHECK_ON, @input_type_checkbox), ' +
148 '$earcon(CHECK_OFF, @input_type_checkbox))'
167 }, 149 },
168 dialog: { 150 dialog: {
169 enter: '$name $role' 151 enter: '$name $role'
170 }, 152 },
171 heading: { 153 heading: {
172 enter: '@aria_role_heading', 154 enter: '@aria_role_heading',
173 speak: '@aria_role_heading $name=' 155 speak: '@aria_role_heading $name='
174 }, 156 },
175 inlineTextBox: { 157 inlineTextBox: {
176 speak: '$value=' 158 speak: '$value='
177 }, 159 },
178 link: { 160 link: {
179 enter: '$name $visited $role', 161 enter: '$name= $visited $earcon(LINK, @tag_link)=',
180 stay: '$name= $visited $role', 162 stay: '$name= $visited @tag_link',
181 speak: '$name= $visited $role' 163 speak: '$name= $visited $earcon(LINK, @tag_link)='
182 }, 164 },
183 list: { 165 list: {
184 enter: '@aria_role_list @list_with_items($parentChildCount)' 166 enter: '@aria_role_list @list_with_items($parentChildCount)'
185 }, 167 },
186 listItem: { 168 listItem: {
187 enter: '$role' 169 enter: '$role'
188 }, 170 },
189 menuItem: { 171 menuItem: {
190 speak: '$if($haspopup, @describe_menu_item_with_submenu($name), ' + 172 speak: '$if($haspopup, @describe_menu_item_with_submenu($name), ' +
191 '@describe_menu_item($name)) ' + 173 '@describe_menu_item($name)) ' +
192 '@describe_index($indexInParent, $parentChildCount)' 174 '@describe_index($indexInParent, $parentChildCount)'
193 }, 175 },
194 menuListOption: { 176 menuListOption: {
195 speak: '$name $value @aria_role_menuitem ' + 177 speak: '$name $value @aria_role_menuitem ' +
196 '@describe_index($indexInParent, $parentChildCount)' 178 '@describe_index($indexInParent, $parentChildCount)'
197 }, 179 },
198 paragraph: { 180 paragraph: {
199 speak: '$value' 181 speak: '$value'
200 }, 182 },
201 popUpButton: { 183 popUpButton: {
202 speak: '$value $name $role @aria_has_popup ' + 184 speak: '$value $name @tag_button @aria_has_popup $earcon(LISTBOX) ' +
203 '$if($collapsed, @aria_expanded_false, @aria_expanded_true)' 185 '$if($collapsed, @aria_expanded_false, @aria_expanded_true)'
204 }, 186 },
205 radioButton: { 187 radioButton: {
206 speak: '$if($checked, @describe_radio_selected($name), ' + 188 speak: '$if($checked, @describe_radio_selected($name), ' +
207 '@describe_radio_unselected($name))' 189 '@describe_radio_unselected($name)) ' +
190 '$if($checked, ' +
191 '$earcon(CHECK_ON, @input_type_radio), ' +
192 '$earcon(CHECK_OFF, @input_type_radio))'
208 }, 193 },
209 slider: { 194 slider: {
210 speak: '@describe_slider($value, $name)' 195 speak: '@describe_slider($value, $name)'
211 }, 196 },
212 staticText: { 197 staticText: {
213 speak: '$value $name' 198 speak: '$value $name'
214 }, 199 },
215 tab: { 200 tab: {
216 speak: '@describe_tab($name)' 201 speak: '@describe_tab($name)'
217 }, 202 },
218 toolbar: { 203 toolbar: {
219 enter: '$name $role' 204 enter: '$name $role'
220 }, 205 },
221 window: { 206 window: {
222 enter: '$name', 207 enter: '$name',
223 speak: '@describe_window($name) $earcon(OBJECT_OPEN)' 208 speak: '@describe_window($name) $earcon(OBJECT_OPEN)'
224 } 209 }
225 }, 210 },
226 menuStart: { 211 menuStart: {
227 'default': { 212 'default': {
228 speak: '@chrome_menu_opened($name) $earcon(OBJECT_OPEN)' 213 speak: '@chrome_menu_opened($name) $role $earcon(OBJECT_OPEN)'
229 } 214 }
230 }, 215 },
231 menuEnd: { 216 menuEnd: {
232 'default': { 217 'default': {
233 speak: '@chrome_menu_closed $earcon(OBJECT_CLOSE)' 218 speak: '$earcon(OBJECT_CLOSE)'
234 } 219 }
235 }, 220 },
236 menuListValueChanged: { 221 menuListValueChanged: {
237 'default': { 222 'default': {
238 speak: '$value $name ' + 223 speak: '$value $name ' +
239 '$find({"state": {"selected": true, "invisible": false}}, ' + 224 '$find({"state": {"selected": true, "invisible": false}}, ' +
240 '@describe_index($indexInParent, $parentChildCount)) ' 225 '@describe_index($indexInParent, $parentChildCount)) '
241 } 226 }
242 }, 227 },
243 alert: { 228 alert: {
244 default: { 229 default: {
245 speak: '!doNotInterrupt ' + 230 speak: '!doNotInterrupt ' +
246 '@aria_role_alert $name $earcon(ALERT_NONMODAL) $descendants' 231 '@aria_role_alert $name $earcon(ALERT_NONMODAL) $descendants'
247 } 232 }
248 } 233 }
249 }; 234 };
250 235
251 /** 236 /**
237 * Alias equivalent attributes.
238 * @type {!Object<string, string>}
239 */
240 Output.ATTRIBUTE_ALIAS = {
241 name: 'value',
242 value: 'name'
243 };
244
245 /**
252 * Custom actions performed while rendering an output string. 246 * Custom actions performed while rendering an output string.
253 * @param {function()} action 247 * @param {function()} action
254 * @constructor 248 * @constructor
255 */ 249 */
256 Output.Action = function(action) { 250 Output.Action = function(action) {
257 this.action_ = action; 251 this.action_ = action;
258 }; 252 };
259 253
260 Output.Action.prototype = { 254 Output.Action.prototype = {
261 run: function() { 255 run: function() {
262 this.action_(); 256 this.action_();
263 } 257 }
264 }; 258 };
265 259
266 /** 260 /**
267 * Action to play a earcon.
268 * @param {string} earconId
269 * @constructor
270 * @extends {Output.Action}
271 */
272 Output.EarconAction = function(earconId) {
273 Output.Action.call(this, function() {
274 cvox.ChromeVox.earcons.playEarcon(
275 cvox.AbstractEarcons[earconId]);
276 });
277 };
278
279 Output.EarconAction.prototype = {
280 __proto__: Output.Action.prototype
281 };
282
283 /**
284 * Annotation for selection. 261 * Annotation for selection.
285 * @param {number} startIndex 262 * @param {number} startIndex
286 * @param {number} endIndex 263 * @param {number} endIndex
287 * @constructor 264 * @constructor
288 */ 265 */
289 Output.SelectionSpan = function(startIndex, endIndex) { 266 Output.SelectionSpan = function(startIndex, endIndex) {
290 // TODO(dtseng): Direction lost below; should preserve for braille panning. 267 // TODO(dtseng): Direction lost below; should preserve for braille panning.
291 this.startIndex = startIndex < endIndex ? startIndex : endIndex; 268 this.startIndex = startIndex < endIndex ? startIndex : endIndex;
292 this.endIndex = endIndex > startIndex ? endIndex : startIndex; 269 this.endIndex = endIndex > startIndex ? endIndex : startIndex;
293 }; 270 };
294 271
295 /** 272 /**
296 * Possible events handled by ChromeVox internally. 273 * Possible events handled by ChromeVox internally.
297 * @enum {string} 274 * @enum {string}
298 */ 275 */
299 Output.EventType = { 276 Output.EventType = {
300 NAVIGATE: 'navigate' 277 NAVIGATE: 'navigate'
301 }; 278 };
302 279
303 Output.prototype = { 280 Output.prototype = {
304 /** 281 /**
305 * Gets the output buffer for speech. 282 * Gets the output buffer for speech.
306 * @return {!cvox.Spannable} 283 * @return {!cvox.Spannable}
307 */ 284 */
308 toSpannable: function() { 285 getBuffer: function() {
309 return this.buffer_.reduce(function(prev, cur) { 286 return this.buffer_;
310 prev.append(cur);
311 return prev;
312 }, new cvox.Spannable());
313 }, 287 },
314 288
315 /** 289 /**
316 * Specify ranges for speech. 290 * Specify ranges for speech.
317 * @param {!cursors.Range} range 291 * @param {!cursors.Range} range
318 * @param {cursors.Range} prevRange 292 * @param {cursors.Range} prevRange
319 * @param {chrome.automation.EventType|Output.EventType} type 293 * @param {chrome.automation.EventType|Output.EventType} type
320 * @return {!Output} 294 * @return {!Output}
321 */ 295 */
322 withSpeech: function(range, prevRange, type) { 296 withSpeech: function(range, prevRange, type) {
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
383 onSpeechEnd: function(callback) { 357 onSpeechEnd: function(callback) {
384 this.speechEndCallback_ = callback; 358 this.speechEndCallback_ = callback;
385 return this; 359 return this;
386 }, 360 },
387 361
388 /** 362 /**
389 * Executes all specified output. 363 * Executes all specified output.
390 */ 364 */
391 go: function() { 365 go: function() {
392 // Speech. 366 // Speech.
393 var queueMode = cvox.QueueMode.FLUSH; 367 var buff = this.buffer_;
394 this.buffer_.forEach(function(buff, i, a) { 368 if (buff.toString()) {
395 if (buff.toString()) { 369 if (this.speechStartCallback_)
396 if (this.speechStartCallback_ && i == 0) 370 this.speechProperties_['startCallback'] = this.speechStartCallback_;
397 this.speechProperties_['startCallback'] = this.speechStartCallback_; 371 if (this.speechEndCallback_) {
398 else 372 this.speechProperties_['endCallback'] = this.speechEndCallback_;
399 this.speechProperties_['startCallback'] = null;
400 if (this.speechEndCallback_ && i == a.length - 1)
401 this.speechProperties_['endCallback'] = this.speechEndCallback_;
402 else
403 this.speechProperties_['endCallback'] = null;
404 cvox.ChromeVox.tts.speak(
405 buff.toString(), queueMode, this.speechProperties_);
406 queueMode = cvox.QueueMode.QUEUE;
407 } 373 }
408 var actions = buff.getSpansInstanceOf(Output.Action); 374
409 if (actions) { 375 cvox.ChromeVox.tts.speak(
410 actions.forEach(function(a) { 376 buff.toString(), cvox.QueueMode.FLUSH, this.speechProperties_);
411 a.run(); 377 }
412 }); 378
413 } 379 var actions = buff.getSpansInstanceOf(Output.Action);
414 }.bind(this)); 380 if (actions) {
381 actions.forEach(function(a) {
382 a.run();
383 });
384 }
415 385
416 // Braille. 386 // Braille.
417 var buff = this.brailleBuffer_.reduce(function(prev, cur) {
418 if (prev.getLength() > 0 && cur.getLength() > 0)
419 prev.append(Output.SPACE);
420 prev.append(cur);
421 return prev;
422 }, new cvox.Spannable());
423
424 var selSpan = 387 var selSpan =
425 buff.getSpanInstanceOf(Output.SelectionSpan); 388 this.brailleBuffer_.getSpanInstanceOf(Output.SelectionSpan);
426 var startIndex = -1, endIndex = -1; 389 var startIndex = -1, endIndex = -1;
427 if (selSpan) { 390 if (selSpan) {
428 // Casts ok, since the span is known to be in the spannable. 391 // Casts ok, since the span is known to be in the spannable.
429 var valueStart = 392 var valueStart =
430 /** @type {number} */ (buff.getSpanStart(selSpan)); 393 /** @type {number} */ (this.brailleBuffer_.getSpanStart(selSpan));
431 var valueEnd = 394 var valueEnd =
432 /** @type {number} */ (buff.getSpanEnd(selSpan)); 395 /** @type {number} */ (this.brailleBuffer_.getSpanEnd(selSpan));
433 startIndex = valueStart + selSpan.startIndex; 396 startIndex = valueStart + selSpan.startIndex;
434 endIndex = valueStart + selSpan.endIndex; 397 endIndex = valueStart + selSpan.endIndex;
435 buff.setSpan(new cvox.ValueSpan(0), 398 this.brailleBuffer_.setSpan(new cvox.ValueSpan(0),
436 valueStart, valueEnd); 399 valueStart, valueEnd);
437 buff.setSpan(new cvox.ValueSelectionSpan(), 400 this.brailleBuffer_.setSpan(new cvox.ValueSelectionSpan(),
438 startIndex, endIndex); 401 startIndex, endIndex);
439 } 402 }
440 403
441 var output = new cvox.NavBraille({ 404 var output = new cvox.NavBraille({
442 text: buff, 405 text: this.brailleBuffer_,
443 startIndex: startIndex, 406 startIndex: startIndex,
444 endIndex: endIndex 407 endIndex: endIndex
445 }); 408 });
446 409
447 if (this.brailleBuffer_) 410 if (this.brailleBuffer_)
448 cvox.ChromeVox.braille.write(output); 411 cvox.ChromeVox.braille.write(output);
449 412
450 // Display. 413 // Display.
451 chrome.accessibilityPrivate.setFocusRing(this.locations_); 414 chrome.accessibilityPrivate.setFocusRing(this.locations_);
452 }, 415 },
453 416
454 /** 417 /**
455 * Renders the given range using optional context previous range and event 418 * Renders the given range using optional context previous range and event
456 * type. 419 * type.
457 * @param {!cursors.Range} range 420 * @param {!cursors.Range} range
458 * @param {cursors.Range} prevRange 421 * @param {cursors.Range} prevRange
459 * @param {chrome.automation.EventType|string} type 422 * @param {chrome.automation.EventType|string} type
460 * @param {!Array<cvox.Spannable>} buff Buffer to receive rendered output. 423 * @param {!cvox.Spannable} buff Buffer to receive rendered output.
461 * @private 424 * @private
462 */ 425 */
463 render_: function(range, prevRange, type, buff) { 426 render_: function(range, prevRange, type, buff) {
464 if (range.isSubNode()) 427 if (range.isSubNode())
465 this.subNode_(range, prevRange, type, buff); 428 this.subNode_(range, prevRange, type, buff);
466 else 429 else
467 this.range_(range, prevRange, type, buff); 430 this.range_(range, prevRange, type, buff);
468 }, 431 },
469 432
470 /** 433 /**
471 * Format the node given the format specifier. 434 * Format the node given the format specifier.
472 * @param {chrome.automation.AutomationNode} node 435 * @param {chrome.automation.AutomationNode} node
473 * @param {string|!Object} format The output format either specified as an 436 * @param {string|!Object} format The output format either specified as an
474 * output template string or a parsed output format tree. 437 * output template string or a parsed output format tree.
475 * @param {!Array<cvox.Spannable>} buff Buffer to receive rendered output. 438 * @param {!cvox.Spannable} buff Buffer to receive rendered output.
476 * @param {!Object=} opt_exclude A set of attributes to exclude. 439 * @param {!Object=} opt_exclude A set of attributes to exclude.
477 * @private 440 * @private
478 */ 441 */
479 format_: function(node, format, buff, opt_exclude) { 442 format_: function(node, format, buff, opt_exclude) {
480 opt_exclude = opt_exclude || {}; 443 opt_exclude = opt_exclude || {};
481 var tokens = []; 444 var tokens = [];
482 var args = null; 445 var args = null;
483 446
484 // Hacky way to support args. 447 // Hacky way to support args.
485 if (typeof(format) == 'string') { 448 if (typeof(format) == 'string') {
(...skipping 13 matching lines...) Expand all
499 if (typeof(token) == 'string') 462 if (typeof(token) == 'string')
500 tree = this.createParseTree(token); 463 tree = this.createParseTree(token);
501 else 464 else
502 tree = token; 465 tree = token;
503 466
504 // Obtain the operator token. 467 // Obtain the operator token.
505 token = tree.value; 468 token = tree.value;
506 469
507 // Set suffix options. 470 // Set suffix options.
508 var options = {}; 471 var options = {};
509 options.annotation = []; 472 options.ifEmpty = token[token.length - 1] == '=';
510 options.isUnique = token[token.length - 1] == '='; 473 if (options.ifEmpty)
511 if (options.isUnique)
512 token = token.substring(0, token.length - 1); 474 token = token.substring(0, token.length - 1);
513 475
514 // Process token based on prefix. 476 // Process token based on prefix.
515 var prefix = token[0]; 477 var prefix = token[0];
516 token = token.slice(1); 478 token = token.slice(1);
517 479
518 if (opt_exclude[token]) 480 if (opt_exclude[token])
519 return; 481 return;
520 482
521 // All possible tokens based on prefix. 483 // All possible tokens based on prefix.
522 if (prefix == '$') { 484 if (prefix == '$') {
485 options.annotation = token;
523 if (token == 'value') { 486 if (token == 'value') {
524 var text = node.attributes.value; 487 var text = node.attributes.value;
525 if (text !== undefined) { 488 if (text !== undefined) {
489 var offset = buff.getLength();
526 if (node.attributes.textSelStart !== undefined) { 490 if (node.attributes.textSelStart !== undefined) {
527 options.annotation.push(new Output.SelectionSpan( 491 options.annotation = new Output.SelectionSpan(
528 node.attributes.textSelStart, 492 node.attributes.textSelStart,
529 node.attributes.textSelEnd)); 493 node.attributes.textSelEnd);
530 } 494 }
495 } else if (node.role == chrome.automation.RoleType.staticText) {
496 // TODO(dtseng): Remove once Blink treats staticText values as
497 // names.
498 text = node.attributes.name;
531 } 499 }
532 // Annotate this as a name so we don't duplicate names from ancestors. 500 this.addToSpannable_(buff, text, options);
533 if (node.role == chrome.automation.RoleType.inlineTextBox)
534 token = 'name';
535 options.annotation.push(token);
536 this.append_(buff, text, options);
537 } else if (token == 'indexInParent') { 501 } else if (token == 'indexInParent') {
538 options.annotation.push(token); 502 this.addToSpannable_(buff, node.indexInParent + 1);
539 this.append_(buff, node.indexInParent + 1);
540 } else if (token == 'parentChildCount') { 503 } else if (token == 'parentChildCount') {
541 options.annotation.push(token);
542 if (node.parent) 504 if (node.parent)
543 this.append_(buff, node.parent.children.length); 505 this.addToSpannable_(buff, node.parent.children.length);
544 } else if (token == 'state') { 506 } else if (token == 'state') {
545 options.annotation.push(token);
546 Object.getOwnPropertyNames(node.state).forEach(function(s) { 507 Object.getOwnPropertyNames(node.state).forEach(function(s) {
547 this.append_(buff, s, options); 508 this.addToSpannable_(buff, s, options);
548 }.bind(this)); 509 }.bind(this));
549 } else if (token == 'find') { 510 } else if (token == 'find') {
550 // Find takes two arguments: JSON query string and format string. 511 // Find takes two arguments: JSON query string and format string.
551 if (tree.firstChild) { 512 if (tree.firstChild) {
552 var jsonQuery = tree.firstChild.value; 513 var jsonQuery = tree.firstChild.value;
553 node = node.find( 514 node = node.find(
554 /** @type {Object}*/(JSON.parse(jsonQuery))); 515 /** @type {Object}*/(JSON.parse(jsonQuery)));
555 var formatString = tree.firstChild.nextSibling; 516 var formatString = tree.firstChild.nextSibling;
556 if (node) 517 if (node)
557 this.format_(node, formatString, buff); 518 this.format_(node, formatString, buff);
558 } 519 }
559 } else if (token == 'descendants') { 520 } else if (token == 'descendants') {
560 if (AutomationPredicate.leaf(node)) 521 if (AutomationPredicate.leaf(node))
561 return; 522 return;
562 523
563 // Construct a range to the leftmost and rightmost leaves. 524 // Construct a range to the leftmost and rightmost leaves.
564 var leftmost = AutomationUtil.findNodePre( 525 var leftmost = AutomationUtil.findNodePre(
565 node, Dir.FORWARD, AutomationPredicate.leaf); 526 node, Dir.FORWARD, AutomationPredicate.leaf);
566 var rightmost = AutomationUtil.findNodePre( 527 var rightmost = AutomationUtil.findNodePre(
567 node, Dir.BACKWARD, AutomationPredicate.leaf); 528 node, Dir.BACKWARD, AutomationPredicate.leaf);
568 if (!leftmost || !rightmost) 529 if (!leftmost || !rightmost)
569 return; 530 return;
570 531
571 var subrange = new cursors.Range( 532 var subrange = new cursors.Range(
572 new cursors.Cursor(leftmost, 0), 533 new cursors.Cursor(leftmost, 0),
573 new cursors.Cursor(rightmost, 0)); 534 new cursors.Cursor(rightmost, 0));
574 this.range_(subrange, null, 'navigate', buff); 535 this.range_(subrange, null, 'navigate', buff);
575 } else if (token == 'role') { 536 } else if (token == 'role') {
576 options.annotation.push(token);
577 var msg = node.role; 537 var msg = node.role;
578 var earconId = null; 538 var earconId = null;
579 var info = Output.ROLE_INFO_[node.role]; 539 var info = Output.ROLE_INFO_[node.role];
580 if (info) { 540 if (info) {
581 if (this.formatOptions_.braille) 541 if (this.formatOptions_.braille)
582 msg = cvox.ChromeVox.msgs.getMsg(info.msgId + '_brl'); 542 msg = cvox.ChromeVox.msgs.getMsg(info.msgId + '_brl');
583 else 543 else
584 msg = cvox.ChromeVox.msgs.getMsg(info.msgId); 544 msg = cvox.ChromeVox.msgs.getMsg(info.msgId);
585 earconId = info.earcon; 545 earconId = info.earcon;
586 } else { 546 } else {
587 console.error('Missing role info for ' + node.role); 547 console.error('Missing role info for ' + node.role);
588 } 548 }
589 if (earconId) 549 if (earconId) {
590 options.annotation.push(new Output.EarconAction(earconId)); 550 options.annotation = new Output.Action(function() {
591 this.append_(buff, msg, options); 551 cvox.ChromeVox.earcons.playEarcon(
592 } else if (node.attributes[token] !== undefined) { 552 cvox.AbstractEarcons[earconId]);
593 options.annotation.push(token); 553 });
594 this.append_(buff, node.attributes[token], options); 554 }
595 } else if (Output.STATE_INFO_[token]) { 555 this.addToSpannable_(buff, msg, options);
596 options.annotation.push('state'); 556 } else if (node.attributes[token]) {
597 var stateInfo = Output.STATE_INFO_[token]; 557 this.addToSpannable_(buff, node.attributes[token], options);
598 var resolvedInfo = node.state[token] ? stateInfo.on : stateInfo.off; 558 } else if (node.state[token]) {
599 options.annotation.push( 559 this.addToSpannable_(buff, token, options);
600 new Output.EarconAction(resolvedInfo.earconId));
601 var msgId =
602 this.formatOptions_.braille ? resolvedInfo.msgId + '_brl' :
603 resolvedInfo.msgId;
604 var msg = cvox.ChromeVox.msgs.getMsg(msgId);
605 this.append_(buff, msg, options);
606 } else if (tree.firstChild) { 560 } else if (tree.firstChild) {
607 // Custom functions. 561 // Custom functions.
608 if (token == 'if') { 562 if (token == 'if') {
609 var cond = tree.firstChild; 563 var cond = tree.firstChild;
610 var attrib = cond.value.slice(1); 564 var attrib = cond.value.slice(1);
611 if (node.attributes[attrib] || node.state[attrib]) 565 if (node.attributes[attrib] || node.state[attrib])
612 this.format_(node, cond.nextSibling, buff); 566 this.format_(node, cond.nextSibling, buff);
613 else 567 else
614 this.format_(node, cond.nextSibling.nextSibling, buff); 568 this.format_(node, cond.nextSibling.nextSibling, buff);
615 } else if (token == 'earcon') { 569 } else if (token == 'earcon') {
616 // Assumes there's existing output in our buffer. 570 var contentBuff = new cvox.Spannable();
617 var lastBuff = buff[buff.length - 1]; 571 if (tree.firstChild.nextSibling)
618 if (!lastBuff) 572 this.format_(node, tree.firstChild.nextSibling, contentBuff);
619 return; 573 options.annotation = new Output.Action(function() {
620 574 cvox.ChromeVox.earcons.playEarcon(
621 lastBuff.setSpan( 575 cvox.AbstractEarcons[tree.firstChild.value]);
622 new Output.EarconAction(tree.firstChild.value), 0, 0); 576 });
577 this.addToSpannable_(buff, contentBuff, options);
623 } 578 }
624 } 579 }
625 } else if (prefix == '@') { 580 } else if (prefix == '@') {
626 var msgId = token; 581 var msgId = token;
627 var msgArgs = []; 582 var msgArgs = [];
628 var curMsg = tree.firstChild; 583 var curMsg = tree.firstChild;
629 584
630 while (curMsg) { 585 while (curMsg) {
631 var arg = curMsg.value; 586 var arg = curMsg.value;
632 if (arg[0] != '$') { 587 if (arg[0] != '$') {
633 console.error('Unexpected value: ' + arg); 588 console.error('Unexpected value: ' + arg);
634 return; 589 return;
635 } 590 }
636 var msgBuff = []; 591 var msgBuff = new cvox.Spannable();
637 this.format_(node, arg, msgBuff); 592 this.format_(node, arg, msgBuff);
638 msgArgs = msgArgs.concat(msgBuff); 593 msgArgs.push(msgBuff.toString());
639 curMsg = curMsg.nextSibling; 594 curMsg = curMsg.nextSibling;
640 } 595 }
641 var msg = cvox.ChromeVox.msgs.getMsg(msgId, msgArgs); 596 var msg = cvox.ChromeVox.msgs.getMsg(msgId, msgArgs);
642 try { 597 try {
643 if (this.formatOptions_.braille) 598 if (this.formatOptions_.braille)
644 msg = cvox.ChromeVox.msgs.getMsg(msgId + '_brl', msgArgs) || msg; 599 msg = cvox.ChromeVox.msgs.getMsg(msgId + '_brl', msgArgs) || msg;
645 } catch(e) {} 600 } catch(e) {}
646 601
647 if (msg) { 602 if (msg) {
648 this.append_(buff, msg, options); 603 this.addToSpannable_(buff, msg, options);
649 } 604 }
650 } else if (prefix == '!') { 605 } else if (prefix == '!') {
651 this.speechProperties_[token] = true; 606 this.speechProperties_[token] = true;
652 } 607 }
653 }.bind(this)); 608 }.bind(this));
654 }, 609 },
655 610
656 /** 611 /**
657 * @param {!cursors.Range} range 612 * @param {!cursors.Range} range
658 * @param {cursors.Range} prevRange 613 * @param {cursors.Range} prevRange
659 * @param {chrome.automation.EventType|string} type 614 * @param {chrome.automation.EventType|string} type
660 * @param {!Array<cvox.Spannable>} rangeBuff 615 * @param {!cvox.Spannable} rangeBuff
661 * @private 616 * @private
662 */ 617 */
663 range_: function(range, prevRange, type, rangeBuff) { 618 range_: function(range, prevRange, type, rangeBuff) {
664 if (!prevRange) 619 if (!prevRange)
665 prevRange = cursors.Range.fromNode(range.getStart().getNode().root); 620 prevRange = cursors.Range.fromNode(range.getStart().getNode().root);
666 621
667 var cursor = range.getStart(); 622 var cursor = range.getStart();
668 var prevNode = prevRange.getStart().getNode(); 623 var prevNode = prevRange.getStart().getNode();
669 624
670 var formatNodeAndAncestors = function(node, prevNode) { 625 var formatNodeAndAncestors = function(node, prevNode) {
671 var buff = []; 626 var buff = new cvox.Spannable();
672 this.ancestry_(node, prevNode, type, buff); 627 this.ancestry_(node, prevNode, type, buff);
673 this.node_(node, prevNode, type, buff); 628 this.node_(node, prevNode, type, buff);
674 if (this.formatOptions_.location) 629 if (this.formatOptions_.location)
675 this.locations_.push(node.location); 630 this.locations_.push(node.location);
676 return buff; 631 return buff;
677 }.bind(this); 632 }.bind(this);
678 633
679 while (cursor.getNode() != range.getEnd().getNode()) { 634 while (cursor.getNode() != range.getEnd().getNode()) {
680 var node = cursor.getNode(); 635 var node = cursor.getNode();
681 rangeBuff.push.apply(rangeBuff, formatNodeAndAncestors(node, prevNode)); 636 this.addToSpannable_(
637 rangeBuff, formatNodeAndAncestors(node, prevNode));
682 prevNode = node; 638 prevNode = node;
683 cursor = cursor.move(cursors.Unit.NODE, 639 cursor = cursor.move(cursors.Unit.NODE,
684 cursors.Movement.DIRECTIONAL, 640 cursors.Movement.DIRECTIONAL,
685 Dir.FORWARD); 641 Dir.FORWARD);
686 } 642 }
687 var lastNode = range.getEnd().getNode(); 643 var lastNode = range.getEnd().getNode();
688 rangeBuff.push.apply(rangeBuff, formatNodeAndAncestors(lastNode, prevNode)); 644 this.addToSpannable_(rangeBuff, formatNodeAndAncestors(lastNode, prevNode));
689 }, 645 },
690 646
691 /** 647 /**
692 * @param {!chrome.automation.AutomationNode} node 648 * @param {!chrome.automation.AutomationNode} node
693 * @param {!chrome.automation.AutomationNode} prevNode 649 * @param {!chrome.automation.AutomationNode} prevNode
694 * @param {chrome.automation.EventType|string} type 650 * @param {chrome.automation.EventType|string} type
695 * @param {!Array<cvox.Spannable>} buff 651 * @param {!cvox.Spannable} buff
696 * @param {!Object=} opt_exclude A list of attributes to exclude from 652 * @param {!Object=} opt_exclude A list of attributes to exclude from
697 * processing. 653 * processing.
698 * @private 654 * @private
699 */ 655 */
700 ancestry_: function(node, prevNode, type, buff, opt_exclude) { 656 ancestry_: function(node, prevNode, type, buff, opt_exclude) {
701 opt_exclude = opt_exclude || {}; 657 opt_exclude = opt_exclude || {};
702 var prevUniqueAncestors = 658 var prevUniqueAncestors =
703 AutomationUtil.getUniqueAncestors(node, prevNode); 659 AutomationUtil.getUniqueAncestors(node, prevNode);
704 var uniqueAncestors = AutomationUtil.getUniqueAncestors(prevNode, node); 660 var uniqueAncestors = AutomationUtil.getUniqueAncestors(prevNode, node);
705 661
706 // First, look up the event type's format block. 662 // First, look up the event type's format block.
707 // Navigate is the default event. 663 // Navigate is the default event.
708 var eventBlock = Output.RULES[type] || Output.RULES['navigate']; 664 var eventBlock = Output.RULES[type] || Output.RULES['navigate'];
709 665
710 for (var i = 0, formatPrevNode; 666 for (var i = 0, formatPrevNode;
711 (formatPrevNode = prevUniqueAncestors[i]); 667 (formatPrevNode = prevUniqueAncestors[i]);
712 i++) { 668 i++) {
713 var roleBlock = eventBlock[formatPrevNode.role] || eventBlock['default']; 669 var roleBlock = eventBlock[formatPrevNode.role] || eventBlock['default'];
714 if (roleBlock.leave) 670 if (roleBlock.leave)
715 this.format_(formatPrevNode, roleBlock.leave, buff, opt_exclude); 671 this.format_(formatPrevNode, roleBlock.leave, buff, opt_exclude);
716 } 672 }
717 673
718 var enterOutputs = []; 674 var enterOutput = [];
719 var enterRole = {}; 675 var enterRole = {};
720 for (var j = uniqueAncestors.length - 2, formatNode; 676 for (var j = uniqueAncestors.length - 2, formatNode;
721 (formatNode = uniqueAncestors[j]); 677 (formatNode = uniqueAncestors[j]);
722 j--) { 678 j--) {
723 var roleBlock = eventBlock[formatNode.role] || eventBlock['default']; 679 var roleBlock = eventBlock[formatNode.role] || eventBlock['default'];
724 if (roleBlock.enter) { 680 if (roleBlock.enter) {
725 if (enterRole[formatNode.role]) 681 if (enterRole[formatNode.role])
726 continue; 682 continue;
727 enterRole[formatNode.role] = true; 683 enterRole[formatNode.role] = true;
728 var tempBuff = []; 684 var tempBuff = new cvox.Spannable('');
729 this.format_(formatNode, roleBlock.enter, tempBuff, opt_exclude); 685 this.format_(formatNode, roleBlock.enter, tempBuff, opt_exclude);
730 enterOutputs.unshift(tempBuff); 686 enterOutput.unshift(tempBuff);
731 } 687 }
732 if (formatNode.role == 'window')
733 break;
734 } 688 }
735 enterOutputs.forEach(function(b) { 689 enterOutput.forEach(function(c) {
736 buff.push.apply(buff, b); 690 this.addToSpannable_(buff, c);
737 }); 691 }.bind(this));
738 692
739 if (!opt_exclude.stay) { 693 if (!opt_exclude.stay) {
740 var commonFormatNode = uniqueAncestors[0]; 694 var commonFormatNode = uniqueAncestors[0];
741 while (commonFormatNode && commonFormatNode.parent) { 695 while (commonFormatNode && commonFormatNode.parent) {
742 commonFormatNode = commonFormatNode.parent; 696 commonFormatNode = commonFormatNode.parent;
743 var roleBlock = 697 var roleBlock =
744 eventBlock[commonFormatNode.role] || eventBlock['default']; 698 eventBlock[commonFormatNode.role] || eventBlock['default'];
745 if (roleBlock.stay) 699 if (roleBlock.stay)
746 this.format_(commonFormatNode, roleBlock.stay, buff, opt_exclude); 700 this.format_(commonFormatNode, roleBlock.stay, buff, opt_exclude);
747 } 701 }
748 } 702 }
749 }, 703 },
750 704
751 /** 705 /**
752 * @param {!chrome.automation.AutomationNode} node 706 * @param {!chrome.automation.AutomationNode} node
753 * @param {!chrome.automation.AutomationNode} prevNode 707 * @param {!chrome.automation.AutomationNode} prevNode
754 * @param {chrome.automation.EventType|string} type 708 * @param {chrome.automation.EventType|string} type
755 * @param {!Array<cvox.Spannable>} buff 709 * @param {!cvox.Spannable} buff
756 * @private 710 * @private
757 */ 711 */
758 node_: function(node, prevNode, type, buff) { 712 node_: function(node, prevNode, type, buff) {
759 // Navigate is the default event. 713 // Navigate is the default event.
760 var eventBlock = Output.RULES[type] || Output.RULES['navigate']; 714 var eventBlock = Output.RULES[type] || Output.RULES['navigate'];
761 var roleBlock = eventBlock[node.role] || eventBlock['default']; 715 var roleBlock = eventBlock[node.role] || eventBlock['default'];
762 var speakFormat = roleBlock.speak || eventBlock['default'].speak; 716 var speakFormat = roleBlock.speak || eventBlock['default'].speak;
763 this.format_(node, speakFormat, buff); 717 this.format_(node, speakFormat, buff);
764 }, 718 },
765 719
766 /** 720 /**
767 * @param {!cursors.Range} range 721 * @param {!cursors.Range} range
768 * @param {cursors.Range} prevRange 722 * @param {cursors.Range} prevRange
769 * @param {chrome.automation.EventType|string} type 723 * @param {chrome.automation.EventType|string} type
770 * @param {!Array<cvox.Spannable>} buff 724 * @param {!cvox.Spannable} buff
771 * @private 725 * @private
772 */ 726 */
773 subNode_: function(range, prevRange, type, buff) { 727 subNode_: function(range, prevRange, type, buff) {
774 if (!prevRange) 728 if (!prevRange)
775 prevRange = range; 729 prevRange = range;
776 var dir = cursors.Range.getDirection(prevRange, range); 730 var dir = cursors.Range.getDirection(prevRange, range);
777 var prevNode = prevRange.getBound(dir).getNode(); 731 var prevNode = prevRange.getBound(dir).getNode();
778 this.ancestry_( 732 this.ancestry_(
779 range.getStart().getNode(), prevNode, type, buff, 733 range.getStart().getNode(), prevNode, type, buff,
780 {stay: true, name: true, value: true}); 734 {stay: true, name: true, value: true});
781 var startIndex = range.getStart().getIndex(); 735 var startIndex = range.getStart().getIndex();
782 var endIndex = range.getEnd().getIndex(); 736 var endIndex = range.getEnd().getIndex();
783 if (startIndex === endIndex) 737 if (startIndex === endIndex)
784 endIndex++; 738 endIndex++;
785 this.append_( 739 this.addToSpannable_(
786 buff, range.getStart().getText().substring(startIndex, endIndex)); 740 buff, range.getStart().getText().substring(startIndex, endIndex));
787 }, 741 },
788 742
789 /** 743 /**
790 * Appends output to the |buff|. 744 * Adds to the given buffer with proper delimiters added.
791 * @param {!Array<cvox.Spannable>} buff 745 * @param {!cvox.Spannable} spannable
792 * @param {string|!cvox.Spannable} value 746 * @param {string|!cvox.Spannable} value
793 * @param {{isUnique: (boolean|undefined), 747 * @param {{ifEmpty: boolean,
794 * annotation: !Array<*>}=} opt_options 748 * annotation: (string|Output.Action|undefined)}=} opt_options
795 */ 749 */
796 append_: function(buff, value, opt_options) { 750 addToSpannable_: function(spannable, value, opt_options) {
797 opt_options = opt_options || {isUnique: false, annotation: []}; 751 opt_options = opt_options || {ifEmpty: false, annotation: undefined};
798 752 if ((!value || value.length == 0) && !opt_options.annotation)
799 // Reject empty values without annotations.
800 if ((!value || value.length == 0) && opt_options.annotation.length == 0)
801 return; 753 return;
802 754
803 var spannableToAdd = new cvox.Spannable(value); 755 var spannableToAdd = new cvox.Spannable(value, opt_options.annotation);
804 opt_options.annotation.forEach(function(a) { 756 if (spannable.getLength() == 0) {
805 spannableToAdd.setSpan(a, 0, spannableToAdd.getLength()); 757 spannable.append(spannableToAdd);
806 });
807
808 // Early return if the buffer is empty.
809 if (buff.length == 0) {
810 buff.push(spannableToAdd);
811 return; 758 return;
812 } 759 }
813 760
814 // |isUnique| specifies an annotation that cannot be duplicated. 761 if (opt_options.ifEmpty &&
815 if (opt_options.isUnique) { 762 opt_options.annotation &&
816 var alreadyAnnotated = buff.some(function(s) { 763 (spannable.getSpanStart(opt_options.annotation) != undefined ||
817 return opt_options.annotation.some(function(annotation) { 764 spannable.getSpanStart(
818 return s.getSpanStart(annotation) != undefined; 765 Output.ATTRIBUTE_ALIAS[opt_options.annotation]) != undefined))
819 }); 766 return;
820 });
821 if (alreadyAnnotated)
822 return;
823 }
824 767
825 buff.push(spannableToAdd); 768 var prefixed = new cvox.Spannable(Output.SPACE);
769 prefixed.append(spannableToAdd);
770 spannable.append(prefixed);
826 }, 771 },
827 772
828 /** 773 /**
829 * Parses the token containing a custom function and returns a tree. 774 * Parses the token containing a custom function and returns a tree.
830 * @param {string} inputStr 775 * @param {string} inputStr
831 * @return {Object} 776 * @return {Object}
832 */ 777 */
833 createParseTree: function(inputStr) { 778 createParseTree: function(inputStr) {
834 var root = {value: ''}; 779 var root = {value: ''};
835 var currentNode = root; 780 var currentNode = root;
(...skipping 23 matching lines...) Expand all
859 } 804 }
860 805
861 if (currentNode != root) 806 if (currentNode != root)
862 throw 'Unbalanced parenthesis.'; 807 throw 'Unbalanced parenthesis.';
863 808
864 return root; 809 return root;
865 } 810 }
866 }; 811 };
867 812
868 }); // goog.scope 813 }); // goog.scope
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698