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 |