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 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
44 */ | 44 */ |
45 Output = function() { | 45 Output = function() { |
46 // TODO(dtseng): Include braille specific rules. | 46 // TODO(dtseng): Include braille specific rules. |
47 /** @type {!cvox.Spannable} */ | 47 /** @type {!cvox.Spannable} */ |
48 this.buffer_ = new cvox.Spannable(); | 48 this.buffer_ = new cvox.Spannable(); |
49 /** @type {!cvox.Spannable} */ | 49 /** @type {!cvox.Spannable} */ |
50 this.brailleBuffer_ = new cvox.Spannable(); | 50 this.brailleBuffer_ = new cvox.Spannable(); |
51 /** @type {!Array.<Object>} */ | 51 /** @type {!Array.<Object>} */ |
52 this.locations_ = []; | 52 this.locations_ = []; |
53 /** @type {function()} */ | 53 /** @type {function()} */ |
54 this.speechStartCallback_ = function() {}; | 54 this.speechStartCallback_; |
55 /** @type {function()} */ | 55 /** @type {function()} */ |
56 this.speechEndCallback_ = function() {}; | 56 this.speechEndCallback_; |
57 /** @type {function()} */ | 57 /** @type {function()} */ |
58 this.speechInterruptedCallback_ = function() {}; | 58 this.speechInterruptedCallback_; |
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}} |
63 */ | 63 */ |
64 this.formatOptions_ = {speech: true, braille: false, location: true}; | 64 this.formatOptions_ = {speech: true, braille: false, location: true}; |
| 65 |
| 66 /** |
| 67 * Speech properties to apply to the entire output. |
| 68 * @type {!Object.<string, *>} |
| 69 */ |
| 70 this.speechProperties_ = {}; |
65 }; | 71 }; |
66 | 72 |
67 /** | 73 /** |
68 * Delimiter to use between output values. | 74 * Delimiter to use between output values. |
69 * @type {string} | 75 * @type {string} |
70 */ | 76 */ |
71 Output.SPACE = ' '; | 77 Output.SPACE = ' '; |
72 | 78 |
73 /** | 79 /** |
74 * Rules specifying format of AutomationNodes for output. | 80 * Rules specifying format of AutomationNodes for output. |
75 * @type {!Object.<string, Object.<string, Object.<string, string>>>} | 81 * @type {!Object.<string, Object.<string, Object.<string, string>>>} |
76 */ | 82 */ |
77 Output.RULES = { | 83 Output.RULES = { |
78 navigate: { | 84 navigate: { |
79 'default': { | 85 'default': { |
80 speak: '$name $role $value', | 86 speak: '$name $role $value', |
81 braille: '' | 87 braille: '' |
82 }, | 88 }, |
83 alert: { | 89 alert: { |
84 speak: '@aria_role_alert $name $earcon(ALERT_NONMODAL)' | 90 speak: '!doNotInterrupt ' + |
| 91 '@aria_role_alert $name $earcon(ALERT_NONMODAL) $descendants' |
85 }, | 92 }, |
86 button: { | 93 button: { |
87 speak: '$name $earcon(BUTTON, @tag_button)' | 94 speak: '$name $earcon(BUTTON, @tag_button)' |
88 }, | 95 }, |
89 checkBox: { | 96 checkBox: { |
90 speak: '$or($checked, @describe_checkbox_checked($name), ' + | 97 speak: '$or($checked, @describe_checkbox_checked($name), ' + |
91 '@describe_checkbox_unchecked($name)) ' + | 98 '@describe_checkbox_unchecked($name)) ' + |
92 '$or($checked, ' + | 99 '$or($checked, ' + |
93 '$earcon(CHECK_ON, @input_type_checkbox), ' + | 100 '$earcon(CHECK_ON, @input_type_checkbox), ' + |
94 '$earcon(CHECK_OFF, @input_type_checkbox))' | 101 '$earcon(CHECK_OFF, @input_type_checkbox))' |
(...skipping 205 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
300 | 307 |
301 /** | 308 /** |
302 * Executes all specified output. | 309 * Executes all specified output. |
303 */ | 310 */ |
304 go: function() { | 311 go: function() { |
305 // Speech. | 312 // Speech. |
306 var buff = this.buffer_; | 313 var buff = this.buffer_; |
307 | 314 |
308 var onEvent = function(evt) { | 315 var onEvent = function(evt) { |
309 switch (evt.type) { | 316 switch (evt.type) { |
310 case 'start': this.speechStartCallback_(); break; | 317 case 'start': |
311 case 'end': this.speechEndCallback_(); break; | 318 this.speechStartCallback_(); |
312 case 'interrupted': this.speechInterruptedCallback_(); break; | 319 break; |
| 320 case 'end': |
| 321 this.speechEndCallback_(); |
| 322 break; |
| 323 case 'interrupted': |
| 324 this.speechInterruptedCallback_ && this.speechInterruptedCallback_(); |
| 325 break; |
313 } | 326 } |
314 }.bind(this); | 327 }.bind(this); |
315 | 328 |
316 if (buff.toString()) { | 329 if (buff.toString()) { |
| 330 if (this.speechStartCallback_ || |
| 331 this.speechEndCallback_ || |
| 332 this.speechInterruptedCallback_) |
| 333 this.speechProperties_['onEvent'] = onEvent; |
| 334 |
317 cvox.ChromeVox.tts.speak( | 335 cvox.ChromeVox.tts.speak( |
318 buff.toString(), cvox.QueueMode.FLUSH, {onEvent: onEvent}); | 336 buff.toString(), cvox.QueueMode.FLUSH, this.speechProperties_); |
319 } | 337 } |
320 | 338 |
321 var actions = buff.getSpansInstanceOf(Output.Action); | 339 var actions = buff.getSpansInstanceOf(Output.Action); |
322 if (actions) { | 340 if (actions) { |
323 actions.forEach(function(a) { | 341 actions.forEach(function(a) { |
324 a.run(); | 342 a.run(); |
325 }); | 343 }); |
326 } | 344 } |
327 | 345 |
328 // Braille. | 346 // Braille. |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
424 return; | 442 return; |
425 | 443 |
426 // All possible tokens based on prefix. | 444 // All possible tokens based on prefix. |
427 if (prefix == '$') { | 445 if (prefix == '$') { |
428 options.annotation = token; | 446 options.annotation = token; |
429 if (token == 'role') { | 447 if (token == 'role') { |
430 // Non-localized role and state obtained by default. | 448 // Non-localized role and state obtained by default. |
431 this.addToSpannable_(buff, node.role, options); | 449 this.addToSpannable_(buff, node.role, options); |
432 } else if (token == 'value') { | 450 } else if (token == 'value') { |
433 var text = node.attributes.value; | 451 var text = node.attributes.value; |
434 var offset = buff.getLength(); | 452 if (text) { |
435 if (node.attributes.textSelStart !== undefined) { | 453 var offset = buff.getLength(); |
436 options.annotation = new Output.SelectionSpan( | 454 if (node.attributes.textSelStart !== undefined) { |
437 node.attributes.textSelStart, | 455 options.annotation = new Output.SelectionSpan( |
438 node.attributes.textSelEnd); | 456 node.attributes.textSelStart, |
| 457 node.attributes.textSelEnd); |
| 458 } |
| 459 } else if (node.role == chrome.automation.RoleType.staticText) { |
| 460 // TODO(dtseng): Remove once Blink treats staticText values as |
| 461 // names. |
| 462 text = node.attributes.name; |
439 } | 463 } |
440 this.addToSpannable_(buff, text, options); | 464 this.addToSpannable_(buff, text, options); |
441 } else if (token == 'indexInParent') { | 465 } else if (token == 'indexInParent') { |
442 this.addToSpannable_(buff, node.indexInParent + 1); | 466 this.addToSpannable_(buff, node.indexInParent + 1); |
443 } else if (token == 'parentChildCount') { | 467 } else if (token == 'parentChildCount') { |
444 if (node.parent) | 468 if (node.parent) |
445 this.addToSpannable_(buff, node.parent.children.length); | 469 this.addToSpannable_(buff, node.parent.children.length); |
446 } else if (token == 'state') { | 470 } else if (token == 'state') { |
447 Object.getOwnPropertyNames(node.state).forEach(function(s) { | 471 Object.getOwnPropertyNames(node.state).forEach(function(s) { |
448 this.addToSpannable_(buff, s, options); | 472 this.addToSpannable_(buff, s, options); |
449 }.bind(this)); | 473 }.bind(this)); |
450 } else if (token == 'find') { | 474 } else if (token == 'find') { |
451 // Find takes two arguments: JSON query string and format string. | 475 // Find takes two arguments: JSON query string and format string. |
452 if (tree.firstChild) { | 476 if (tree.firstChild) { |
453 var jsonQuery = tree.firstChild.value; | 477 var jsonQuery = tree.firstChild.value; |
454 node = node.find( | 478 node = node.find( |
455 /** @type {Object}*/(JSON.parse(jsonQuery))); | 479 /** @type {Object}*/(JSON.parse(jsonQuery))); |
456 var formatString = tree.firstChild.nextSibling; | 480 var formatString = tree.firstChild.nextSibling; |
457 if (node) | 481 if (node) |
458 this.format_(node, formatString, buff); | 482 this.format_(node, formatString, buff); |
459 } | 483 } |
| 484 } else if (token == 'descendants') { |
| 485 if (AutomationPredicate.leaf(node)) |
| 486 return; |
| 487 |
| 488 // Construct a range to the leftmost and rightmost leaves. |
| 489 var leftmost = AutomationUtil.findNodePre( |
| 490 node, Dir.FORWARD, AutomationPredicate.leaf); |
| 491 var rightmost = AutomationUtil.findNodePre( |
| 492 node, Dir.BACKWARD, AutomationPredicate.leaf); |
| 493 if (!leftmost || !rightmost) |
| 494 return; |
| 495 |
| 496 var subrange = new cursors.Range( |
| 497 new cursors.Cursor(leftmost, 0), |
| 498 new cursors.Cursor(rightmost, 0)); |
| 499 this.range_(subrange, null, 'navigate', buff); |
460 } else if (node.attributes[token]) { | 500 } else if (node.attributes[token]) { |
461 this.addToSpannable_(buff, node.attributes[token], options); | 501 this.addToSpannable_(buff, node.attributes[token], options); |
462 } else if (node.state[token]) { | 502 } else if (node.state[token]) { |
463 this.addToSpannable_(buff, token, options); | 503 this.addToSpannable_(buff, token, options); |
464 } else if (tree.firstChild) { | 504 } else if (tree.firstChild) { |
465 // Custom functions. | 505 // Custom functions. |
466 if (token == 'or') { | 506 if (token == 'or') { |
467 var cond = tree.firstChild; | 507 var cond = tree.firstChild; |
468 var attrib = cond.value.slice(1); | 508 var attrib = cond.value.slice(1); |
469 if (node.attributes[attrib] || node.state[attrib]) | 509 if (node.attributes[attrib] || node.state[attrib]) |
(...skipping 29 matching lines...) Expand all Loading... |
499 } | 539 } |
500 var msg = cvox.ChromeVox.msgs.getMsg(msgId, msgArgs); | 540 var msg = cvox.ChromeVox.msgs.getMsg(msgId, msgArgs); |
501 try { | 541 try { |
502 if (this.formatOptions_.braille) | 542 if (this.formatOptions_.braille) |
503 msg = cvox.ChromeVox.msgs.getMsg(msgId + '_brl', msgArgs) || msg; | 543 msg = cvox.ChromeVox.msgs.getMsg(msgId + '_brl', msgArgs) || msg; |
504 } catch(e) {} | 544 } catch(e) {} |
505 | 545 |
506 if (msg) { | 546 if (msg) { |
507 this.addToSpannable_(buff, msg, options); | 547 this.addToSpannable_(buff, msg, options); |
508 } | 548 } |
| 549 } else if (prefix == '!') { |
| 550 this.speechProperties_[token] = true; |
509 } | 551 } |
510 }.bind(this)); | 552 }.bind(this)); |
511 }, | 553 }, |
512 | 554 |
513 /** | 555 /** |
514 * @param {!cursors.Range} range | 556 * @param {!cursors.Range} range |
515 * @param {cursors.Range} prevRange | 557 * @param {cursors.Range} prevRange |
516 * @param {chrome.automation.EventType|string} type | 558 * @param {chrome.automation.EventType|string} type |
517 * @param {!cvox.Spannable} rangeBuff | 559 * @param {!cvox.Spannable} rangeBuff |
518 * @private | 560 * @private |
(...skipping 187 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
706 } | 748 } |
707 | 749 |
708 if (currentNode != root) | 750 if (currentNode != root) |
709 throw 'Unbalanced parenthesis.'; | 751 throw 'Unbalanced parenthesis.'; |
710 | 752 |
711 return root; | 753 return root; |
712 } | 754 } |
713 }; | 755 }; |
714 | 756 |
715 }); // goog.scope | 757 }); // goog.scope |
OLD | NEW |