Chromium Code Reviews| 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 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 99 dialog: { | 99 dialog: { |
| 100 msgId: 'dialog' | 100 msgId: 'dialog' |
| 101 }, | 101 }, |
| 102 heading: { | 102 heading: { |
| 103 msgId: 'aria_role_heading', | 103 msgId: 'aria_role_heading', |
| 104 }, | 104 }, |
| 105 link: { | 105 link: { |
| 106 msgId: 'tag_link', | 106 msgId: 'tag_link', |
| 107 earcon: 'LINK' | 107 earcon: 'LINK' |
| 108 }, | 108 }, |
| 109 listBox: { | |
| 110 msgId: 'aria_role_listbox', | |
| 111 earcon: 'LISTBOX' | |
| 112 }, | |
| 113 listBoxOption: { | |
| 114 msgId: 'aria_role_listitem', | |
| 115 earcon: 'LIST_ITEM' | |
| 116 }, | |
| 109 listItem: { | 117 listItem: { |
| 110 msgId: 'ARIA_ROLE_LISTITEM', | 118 msgId: 'ARIA_ROLE_LISTITEM', |
| 111 earcon: 'list_item' | 119 earcon: 'list_item' |
| 112 }, | 120 }, |
| 121 menu: { | |
| 122 msgId: 'aria_role_menu' | |
| 123 }, | |
| 124 menuItem: { | |
| 125 msgId: 'aria_role_menuitem' | |
| 126 }, | |
| 113 menuListOption: { | 127 menuListOption: { |
| 114 msgId: 'aria_role_menuitem' | 128 msgId: 'aria_role_menuitem' |
| 115 }, | 129 }, |
| 130 menuListPopup: { | |
| 131 msgId: 'aria_role_menu' | |
| 132 }, | |
| 116 popUpButton: { | 133 popUpButton: { |
| 117 msgId: 'tag_button' | 134 msgId: 'tag_button' |
| 118 }, | 135 }, |
| 119 radioButton: { | 136 radioButton: { |
| 120 msgId: 'input_type_radio' | 137 msgId: 'input_type_radio' |
| 121 }, | 138 }, |
| 122 spinButton: { | 139 spinButton: { |
| 123 msgId: 'aria_role_combobox', | 140 msgId: 'aria_role_combobox', |
| 124 earcon: 'LISTBOX' | 141 earcon: 'LISTBOX' |
| 125 }, | 142 }, |
| 126 textBox: { | 143 textBox: { |
| 127 msgId: 'input_type_text', | 144 msgId: 'input_type_text', |
| 128 earcon: 'EDITABLE_TEXT' | 145 earcon: 'EDITABLE_TEXT' |
| 129 }, | 146 }, |
| 130 textField: { | 147 textField: { |
| 131 msgId: 'input_type_text', | 148 msgId: 'input_type_text', |
| 132 earcon: 'EDITABLE_TEXT' | 149 earcon: 'EDITABLE_TEXT' |
| 133 }, | 150 }, |
| 134 time: { | 151 time: { |
| 135 msgId: 'tag_time' | 152 msgId: 'tag_time' |
| 136 }, | 153 }, |
| 137 toolbar: { | 154 toolbar: { |
| 138 msgId: 'aria_role_toolbar' | 155 msgId: 'aria_role_toolbar' |
| 156 }, | |
| 157 tree: { | |
| 158 msgId: 'aria_role_tree' | |
| 159 }, | |
| 160 treeItem: { | |
| 161 msgId: 'aria_role_treeitem' | |
| 139 } | 162 } |
| 140 }; | 163 }; |
| 141 | 164 |
| 142 /** | 165 /** |
| 143 * Metadata about supported automation states. | 166 * Metadata about supported automation states. |
| 144 * @const {!Object<string, | 167 * @const {!Object<string, |
| 145 * {on: {msgId: string, earconId: string}, | 168 * {on: {msgId: string, earconId: string}, |
| 146 * off: {msgId: string, earconId: string}}>} | 169 * off: {msgId: string, earconId: string}}>} |
| 147 * @private | 170 * @private |
| 148 */ | 171 */ |
| 149 Output.STATE_INFO_ = { | 172 Output.STATE_INFO_ = { |
| 150 checked: { | 173 checked: { |
| 151 on: { | 174 on: { |
| 152 earconId: 'CHECK_ON', | 175 earconId: 'CHECK_ON', |
| 153 msgId: 'checkbox_checked_state' | 176 msgId: 'checkbox_checked_state' |
| 154 }, | 177 }, |
| 155 off: { | 178 off: { |
| 156 earconId: 'CHECK_OFF', | 179 earconId: 'CHECK_OFF', |
| 157 msgId: 'checkbox_unchecked_state' | 180 msgId: 'checkbox_unchecked_state' |
| 181 }, | |
| 182 omitted: { | |
| 183 earconId: 'CHECK_OFF', | |
| 184 msgId: 'checkbox_unchecked_state' | |
| 185 } | |
| 186 }, | |
| 187 collapsed: { | |
| 188 on: { | |
| 189 msgId: 'aria_expanded_false' | |
| 190 }, | |
| 191 off: { | |
| 192 msgId: 'aria_expanded_true' | |
| 193 } | |
| 194 }, | |
| 195 expanded: { | |
| 196 on: { | |
| 197 msgId: 'aria_expanded_true' | |
| 198 }, | |
| 199 off: { | |
| 200 msgId: 'aria_expanded_false' | |
| 158 } | 201 } |
| 159 } | 202 } |
| 160 }; | 203 }; |
| 161 | 204 |
| 162 /** | 205 /** |
| 163 * Rules specifying format of AutomationNodes for output. | 206 * Rules specifying format of AutomationNodes for output. |
| 164 * @type {!Object<string, Object<string, Object<string, string>>>} | 207 * @type {!Object<string, Object<string, Object<string, string>>>} |
| 165 */ | 208 */ |
| 166 Output.RULES = { | 209 Output.RULES = { |
| 167 navigate: { | 210 navigate: { |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 188 }, | 231 }, |
| 189 inlineTextBox: { | 232 inlineTextBox: { |
| 190 speak: '$value=' | 233 speak: '$value=' |
| 191 }, | 234 }, |
| 192 link: { | 235 link: { |
| 193 enter: '$name $visited $role', | 236 enter: '$name $visited $role', |
| 194 stay: '$name= $visited $role', | 237 stay: '$name= $visited $role', |
| 195 speak: '$name= $visited $role' | 238 speak: '$name= $visited $role' |
| 196 }, | 239 }, |
| 197 list: { | 240 list: { |
| 198 enter: '@aria_role_list @list_with_items($parentChildCount)' | 241 enter: '$role @list_with_items($countChildren(listItem))' |
| 242 }, | |
| 243 listBox: { | |
| 244 enter: '$name $role @list_with_items($countChildren(listBoxOption))' | |
| 245 }, | |
| 246 listBoxOption: { | |
| 247 speak: '$name $role @describe_index($indexInParent, $parentChildCount)' | |
| 199 }, | 248 }, |
| 200 listItem: { | 249 listItem: { |
| 201 enter: '$role' | 250 enter: '$role' |
|
dmazzoni
2015/04/28 18:47:40
Should this one have describe_index too?
David Tseng
2015/04/28 19:06:33
No afaik because these are "normal" listitems (e.g
dmazzoni
2015/04/28 19:13:26
Maybe when we add support for extra verbose "help"
David Tseng
2015/04/28 20:02:51
Agreed on verbosity. I think most screen readers d
| |
| 202 }, | 251 }, |
| 252 menu: { | |
| 253 enter: '$name $role @list_with_items($countChildren(menuItem))' | |
| 254 }, | |
| 203 menuItem: { | 255 menuItem: { |
| 204 speak: '$if($haspopup, @describe_menu_item_with_submenu($name), ' + | 256 speak: '$if($haspopup, @describe_menu_item_with_submenu($name), ' + |
| 205 '@describe_menu_item($name)) ' + | 257 '@describe_menu_item($name)) ' + |
| 206 '@describe_index($indexInParent, $parentChildCount)' | 258 '@describe_index($indexInParent, $parentChildCount)' |
| 207 }, | 259 }, |
| 208 menuListOption: { | 260 menuListOption: { |
|
dmazzoni
2015/04/28 18:47:40
Please add menuList also - it's <select> without m
| |
| 209 speak: '$name $value @aria_role_menuitem ' + | 261 speak: '$name $value @aria_role_menuitem ' + |
| 210 '@describe_index($indexInParent, $parentChildCount)' | 262 '@describe_index($indexInParent, $parentChildCount)' |
| 211 }, | 263 }, |
| 212 paragraph: { | 264 paragraph: { |
| 213 speak: '$value' | 265 speak: '$value' |
| 214 }, | 266 }, |
| 215 popUpButton: { | 267 popUpButton: { |
| 216 speak: '$value $name $role @aria_has_popup ' + | 268 speak: '$value $name $role @aria_has_popup ' + |
| 217 '$if($collapsed, @aria_expanded_false, @aria_expanded_true)' | 269 '$if($collapsed, @aria_expanded_false, @aria_expanded_true)' |
| 218 }, | 270 }, |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 235 '$earcon(EDITABLE_TEXT)', | 287 '$earcon(EDITABLE_TEXT)', |
| 236 braille: '' | 288 braille: '' |
| 237 }, | 289 }, |
| 238 time: { | 290 time: { |
| 239 enter: '$name $role', | 291 enter: '$name $role', |
| 240 leave: '@exited_container($role)' | 292 leave: '@exited_container($role)' |
| 241 }, | 293 }, |
| 242 toolbar: { | 294 toolbar: { |
| 243 enter: '$name $role' | 295 enter: '$name $role' |
| 244 }, | 296 }, |
| 297 tree: { | |
| 298 enter: '$name $role @list_with_items($countChildren(treeItem))' | |
| 299 }, | |
| 300 treeItem: { | |
| 301 enter: '$role $expanded $collapsed ' + | |
| 302 '@describe_index($indexInParent, $parentChildCount) ' + | |
| 303 '@describe_depth($hierarchicalLevel)' | |
| 304 }, | |
| 245 window: { | 305 window: { |
| 246 enter: '$name', | 306 enter: '$name', |
| 247 speak: '@describe_window($name) $earcon(OBJECT_OPEN)' | 307 speak: '@describe_window($name) $earcon(OBJECT_OPEN)' |
| 248 } | 308 } |
| 249 }, | 309 }, |
| 250 menuStart: { | 310 menuStart: { |
| 251 'default': { | 311 'default': { |
| 252 speak: '@chrome_menu_opened($name) $earcon(OBJECT_OPEN)' | 312 speak: '@chrome_menu_opened($name) $earcon(OBJECT_OPEN)' |
| 253 } | 313 } |
| 254 }, | 314 }, |
| (...skipping 366 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 621 msg = cvox.ChromeVox.msgs.getMsg(info.msgId); | 681 msg = cvox.ChromeVox.msgs.getMsg(info.msgId); |
| 622 earconId = info.earcon; | 682 earconId = info.earcon; |
| 623 } else { | 683 } else { |
| 624 console.error('Missing role info for ' + node.role); | 684 console.error('Missing role info for ' + node.role); |
| 625 } | 685 } |
| 626 if (earconId) | 686 if (earconId) |
| 627 options.annotation.push(new Output.EarconAction(earconId)); | 687 options.annotation.push(new Output.EarconAction(earconId)); |
| 628 this.append_(buff, msg, options); | 688 this.append_(buff, msg, options); |
| 629 } else if (node.attributes[token] !== undefined) { | 689 } else if (node.attributes[token] !== undefined) { |
| 630 options.annotation.push(token); | 690 options.annotation.push(token); |
| 631 this.append_(buff, node.attributes[token], options); | 691 var value = node.attributes[token]; |
| 692 if (typeof value == 'number') | |
| 693 value = String(value); | |
| 694 this.append_(buff, value, options); | |
| 632 } else if (Output.STATE_INFO_[token]) { | 695 } else if (Output.STATE_INFO_[token]) { |
| 633 options.annotation.push('state'); | 696 options.annotation.push('state'); |
| 634 var stateInfo = Output.STATE_INFO_[token]; | 697 var stateInfo = Output.STATE_INFO_[token]; |
| 635 var resolvedInfo = node.state[token] ? stateInfo.on : stateInfo.off; | 698 var resolvedInfo = {}; |
| 636 options.annotation.push( | 699 if (node.state[token] === undefined) |
| 637 new Output.EarconAction(resolvedInfo.earconId)); | 700 resolvedInfo = stateInfo.omitted; |
| 701 else | |
| 702 resolvedInfo = node.state[token] ? stateInfo.on : stateInfo.off; | |
| 703 if (!resolvedInfo) | |
| 704 return; | |
| 705 if (resolvedInfo.earconId) { | |
| 706 options.annotation.push( | |
| 707 new Output.EarconAction(resolvedInfo.earconId)); | |
| 708 } | |
| 638 var msgId = | 709 var msgId = |
| 639 this.formatOptions_.braille ? resolvedInfo.msgId + '_brl' : | 710 this.formatOptions_.braille ? resolvedInfo.msgId + '_brl' : |
| 640 resolvedInfo.msgId; | 711 resolvedInfo.msgId; |
| 641 var msg = cvox.ChromeVox.msgs.getMsg(msgId); | 712 var msg = cvox.ChromeVox.msgs.getMsg(msgId); |
| 642 this.append_(buff, msg, options); | 713 this.append_(buff, msg, options); |
| 643 } else if (tree.firstChild) { | 714 } else if (tree.firstChild) { |
| 644 // Custom functions. | 715 // Custom functions. |
| 645 if (token == 'if') { | 716 if (token == 'if') { |
| 646 var cond = tree.firstChild; | 717 var cond = tree.firstChild; |
| 647 var attrib = cond.value.slice(1); | 718 var attrib = cond.value.slice(1); |
| 648 if (node.attributes[attrib] || node.state[attrib]) | 719 if (node.attributes[attrib] || node.state[attrib]) |
| 649 this.format_(node, cond.nextSibling, buff); | 720 this.format_(node, cond.nextSibling, buff); |
| 650 else | 721 else |
| 651 this.format_(node, cond.nextSibling.nextSibling, buff); | 722 this.format_(node, cond.nextSibling.nextSibling, buff); |
| 652 } else if (token == 'earcon') { | 723 } else if (token == 'earcon') { |
| 653 // Assumes there's existing output in our buffer. | 724 // Assumes there's existing output in our buffer. |
| 654 var lastBuff = buff[buff.length - 1]; | 725 var lastBuff = buff[buff.length - 1]; |
| 655 if (!lastBuff) | 726 if (!lastBuff) |
| 656 return; | 727 return; |
| 657 | 728 |
| 658 lastBuff.setSpan( | 729 lastBuff.setSpan( |
| 659 new Output.EarconAction(tree.firstChild.value), 0, 0); | 730 new Output.EarconAction(tree.firstChild.value), 0, 0); |
| 731 } else if (token == 'countChildren') { | |
| 732 var role = tree.firstChild.value; | |
| 733 var count = node.children.filter(function(e) { | |
| 734 return e.role == role; | |
| 735 }).length; | |
| 736 this.append_(buff, String(count)); | |
| 660 } | 737 } |
| 661 } | 738 } |
| 662 } else if (prefix == '@') { | 739 } else if (prefix == '@') { |
| 663 // Tokens can have substitutions. | 740 // Tokens can have substitutions. |
| 664 var pieces = token.split('+'); | 741 var pieces = token.split('+'); |
| 665 token = pieces.reduce(function(prev, cur) { | 742 token = pieces.reduce(function(prev, cur) { |
| 666 var lookup = cur; | 743 var lookup = cur; |
| 667 if (cur[0] == '$') | 744 if (cur[0] == '$') |
| 668 lookup = node.attributes[cur.slice(1)]; | 745 lookup = node.attributes[cur.slice(1)]; |
| 669 return prev + lookup; | 746 return prev + lookup; |
| 670 }.bind(this), ''); | 747 }.bind(this), ''); |
| 671 var msgId = token; | 748 var msgId = token; |
| 672 var msgArgs = []; | 749 var msgArgs = []; |
| 673 var curMsg = tree.firstChild; | 750 var curMsg = tree.firstChild; |
| 674 | 751 |
| 675 while (curMsg) { | 752 while (curMsg) { |
| 676 var arg = curMsg.value; | 753 var arg = curMsg.value; |
| 677 if (arg[0] != '$') { | 754 if (arg[0] != '$') { |
| 678 console.error('Unexpected value: ' + arg); | 755 console.error('Unexpected value: ' + arg); |
| 679 return; | 756 return; |
| 680 } | 757 } |
| 681 var msgBuff = []; | 758 var msgBuff = []; |
| 682 this.format_(node, arg, msgBuff); | 759 this.format_(node, curMsg, msgBuff); |
| 683 msgArgs = msgArgs.concat(msgBuff); | 760 msgArgs = msgArgs.concat(msgBuff); |
| 684 curMsg = curMsg.nextSibling; | 761 curMsg = curMsg.nextSibling; |
| 685 } | 762 } |
| 686 var msg = cvox.ChromeVox.msgs.getMsg(msgId, msgArgs); | 763 var msg = cvox.ChromeVox.msgs.getMsg(msgId, msgArgs); |
| 687 try { | 764 try { |
| 688 if (this.formatOptions_.braille) | 765 if (this.formatOptions_.braille) |
| 689 msg = cvox.ChromeVox.msgs.getMsg(msgId + '_brl', msgArgs) || msg; | 766 msg = cvox.ChromeVox.msgs.getMsg(msgId + '_brl', msgArgs) || msg; |
| 690 } catch(e) {} | 767 } catch(e) {} |
| 691 | 768 |
| 692 if (msg) { | 769 if (msg) { |
| (...skipping 211 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 904 } | 981 } |
| 905 | 982 |
| 906 if (currentNode != root) | 983 if (currentNode != root) |
| 907 throw 'Unbalanced parenthesis.'; | 984 throw 'Unbalanced parenthesis.'; |
| 908 | 985 |
| 909 return root; | 986 return root; |
| 910 } | 987 } |
| 911 }; | 988 }; |
| 912 | 989 |
| 913 }); // goog.scope | 990 }); // goog.scope |
| OLD | NEW |