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 12 matching lines...) Expand all Loading... |
23 goog.require('cvox.TtsCategory'); | 23 goog.require('cvox.TtsCategory'); |
24 goog.require('cvox.ValueSelectionSpan'); | 24 goog.require('cvox.ValueSelectionSpan'); |
25 goog.require('cvox.ValueSpan'); | 25 goog.require('cvox.ValueSpan'); |
26 goog.require('goog.i18n.MessageFormat'); | 26 goog.require('goog.i18n.MessageFormat'); |
27 | 27 |
28 goog.scope(function() { | 28 goog.scope(function() { |
29 var AutomationNode = chrome.automation.AutomationNode; | 29 var AutomationNode = chrome.automation.AutomationNode; |
30 var Dir = constants.Dir; | 30 var Dir = constants.Dir; |
31 var EventType = chrome.automation.EventType; | 31 var EventType = chrome.automation.EventType; |
32 var RoleType = chrome.automation.RoleType; | 32 var RoleType = chrome.automation.RoleType; |
33 var StateType = chrome.automation.StateType; | |
34 | 33 |
35 /** | 34 /** |
36 * An Output object formats a cursors.Range into speech, braille, or both | 35 * An Output object formats a cursors.Range into speech, braille, or both |
37 * representations. This is typically a |Spannable|. | 36 * representations. This is typically a |Spannable|. |
38 * | 37 * |
39 * The translation from Range to these output representations rely upon format | 38 * The translation from Range to these output representations rely upon format |
40 * rules which specify how to convert AutomationNode objects into annotated | 39 * rules which specify how to convert AutomationNode objects into annotated |
41 * strings. | 40 * strings. |
42 * The format of these rules is as follows. | 41 * The format of these rules is as follows. |
43 * | 42 * |
(...skipping 935 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
979 if (prevRange && !prevRange.isValid()) | 978 if (prevRange && !prevRange.isValid()) |
980 prevRange = null; | 979 prevRange = null; |
981 | 980 |
982 // Scan unique ancestors to get the value of |outputContextFirst|. | 981 // Scan unique ancestors to get the value of |outputContextFirst|. |
983 var parent = range.start.node; | 982 var parent = range.start.node; |
984 var prevParent = prevRange ? prevRange.start.node : parent; | 983 var prevParent = prevRange ? prevRange.start.node : parent; |
985 if (!parent || !prevParent) | 984 if (!parent || !prevParent) |
986 return; | 985 return; |
987 var uniqueAncestors = AutomationUtil.getUniqueAncestors(prevParent, parent); | 986 var uniqueAncestors = AutomationUtil.getUniqueAncestors(prevParent, parent); |
988 for (var i = 0; parent = uniqueAncestors[i]; i++) { | 987 for (var i = 0; parent = uniqueAncestors[i]; i++) { |
989 if (parent.role == RoleType.WINDOW) | 988 if (parent.role == RoleType.window) |
990 break; | 989 break; |
991 if (Output.ROLE_INFO_[parent.role] && | 990 if (Output.ROLE_INFO_[parent.role] && |
992 Output.ROLE_INFO_[parent.role].outputContextFirst) { | 991 Output.ROLE_INFO_[parent.role].outputContextFirst) { |
993 this.outputContextFirst_ = true; | 992 this.outputContextFirst_ = true; |
994 break; | 993 break; |
995 } | 994 } |
996 } | 995 } |
997 | 996 |
998 if (range.isSubNode()) | 997 if (range.isSubNode()) |
999 this.subNode_(range, prevRange, type, buff); | 998 this.subNode_(range, prevRange, type, buff); |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1045 if (options.isUnique) | 1044 if (options.isUnique) |
1046 token = token.substring(0, token.length - 1); | 1045 token = token.substring(0, token.length - 1); |
1047 | 1046 |
1048 // Process token based on prefix. | 1047 // Process token based on prefix. |
1049 var prefix = token[0]; | 1048 var prefix = token[0]; |
1050 token = token.slice(1); | 1049 token = token.slice(1); |
1051 | 1050 |
1052 // All possible tokens based on prefix. | 1051 // All possible tokens based on prefix. |
1053 if (prefix == '$') { | 1052 if (prefix == '$') { |
1054 if (token == 'value') { | 1053 if (token == 'value') { |
1055 var text = node.value || ''; | 1054 var text = node.value; |
1056 if (!node.state[StateType.EDITABLE] && node.name == text) | 1055 if (!node.state.editable && node.name == text) |
1057 return; | 1056 return; |
1058 | 1057 |
1059 var selectedText = ''; | 1058 var selectedText = ''; |
1060 if (node.textSelStart !== undefined) { | 1059 if (text !== undefined) { |
1061 options.annotation.push(new Output.SelectionSpan( | 1060 if (node.textSelStart !== undefined) { |
1062 node.textSelStart || 0, | 1061 options.annotation.push(new Output.SelectionSpan( |
1063 node.textSelEnd || 0)); | 1062 node.textSelStart, |
| 1063 node.textSelEnd)); |
1064 | 1064 |
1065 selectedText = | 1065 selectedText = |
1066 node.value.substring(node.textSelStart || 0, | 1066 node.value.substring(node.textSelStart, node.textSelEnd); |
1067 node.textSelEnd || 0); | 1067 } |
1068 } | 1068 } |
1069 options.annotation.push(token); | 1069 options.annotation.push(token); |
1070 if (selectedText && !this.formatOptions_.braille) { | 1070 if (selectedText && !this.formatOptions_.braille) { |
1071 this.append_(buff, selectedText, options); | 1071 this.append_(buff, selectedText, options); |
1072 this.append_(buff, Msgs.getMsg('selected')); | 1072 this.append_(buff, Msgs.getMsg('selected')); |
1073 } else { | 1073 } else { |
1074 this.append_(buff, text, options); | 1074 this.append_(buff, text, options); |
1075 } | 1075 } |
1076 } else if (token == 'name') { | 1076 } else if (token == 'name') { |
1077 options.annotation.push(token); | 1077 options.annotation.push(token); |
1078 var earcon = node ? this.findEarcon_(node, opt_prevNode) : null; | 1078 var earcon = node ? this.findEarcon_(node, opt_prevNode) : null; |
1079 if (earcon) | 1079 if (earcon) |
1080 options.annotation.push(earcon); | 1080 options.annotation.push(earcon); |
1081 this.append_(buff, node.name || '', options); | 1081 this.append_(buff, node.name, options); |
1082 } else if (token == 'urlFilename') { | 1082 } else if (token == 'urlFilename') { |
1083 options.annotation.push('name'); | 1083 options.annotation.push('name'); |
1084 var url = node.url || ''; | 1084 var url = node.url; |
1085 var filename = ''; | 1085 var filename = ''; |
1086 if (url.substring(0, 4) != 'data') { | 1086 if (url.substring(0, 4) != 'data') { |
1087 filename = | 1087 filename = |
1088 url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.')); | 1088 url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.')); |
1089 | 1089 |
1090 // Hack to not speak the filename if it's ridiculously long. | 1090 // Hack to not speak the filename if it's ridiculously long. |
1091 if (filename.length >= 30) | 1091 if (filename.length >= 30) |
1092 filename = filename.substring(0, 16) + '...'; | 1092 filename = filename.substring(0, 16) + '...'; |
1093 } | 1093 } |
1094 this.append_(buff, filename, options); | 1094 this.append_(buff, filename, options); |
1095 } else if (token == 'nameFromNode') { | 1095 } else if (token == 'nameFromNode') { |
1096 if (node.nameFrom == chrome.automation.NameFromType.CONTENTS) | 1096 if (chrome.automation.NameFromType[node.nameFrom] == |
| 1097 'contents') |
1097 return; | 1098 return; |
1098 | 1099 |
1099 options.annotation.push('name'); | 1100 options.annotation.push('name'); |
1100 this.append_(buff, node.name || '', options); | 1101 this.append_(buff, node.name, options); |
1101 } else if (token == 'nameOrDescendants') { | 1102 } else if (token == 'nameOrDescendants') { |
1102 options.annotation.push(token); | 1103 options.annotation.push(token); |
1103 if (node.name) | 1104 if (node.name) |
1104 this.append_(buff, node.name || '', options); | 1105 this.append_(buff, node.name, options); |
1105 else | 1106 else |
1106 this.format_(node, '$descendants', buff); | 1107 this.format_(node, '$descendants', buff); |
1107 } else if (token == 'description') { | 1108 } else if (token == 'description') { |
1108 if (node.name == node.description || node.value == node.description) | 1109 if (node.name == node.description || node.value == node.description) |
1109 return; | 1110 return; |
1110 options.annotation.push(token); | 1111 options.annotation.push(token); |
1111 this.append_(buff, node.description || '', options); | 1112 this.append_(buff, node.description, options); |
1112 } else if (token == 'indexInParent') { | 1113 } else if (token == 'indexInParent') { |
1113 if (node.parent) { | 1114 if (node.parent) { |
1114 options.annotation.push(token); | 1115 options.annotation.push(token); |
1115 var count = 0; | 1116 var count = 0; |
1116 for (var i = 0, child; child = node.parent.children[i]; i++) { | 1117 for (var i = 0, child; child = node.parent.children[i]; i++) { |
1117 if (node.role == child.role) | 1118 if (node.role == child.role) |
1118 count++; | 1119 count++; |
1119 if (node === child) | 1120 if (node === child) |
1120 break; | 1121 break; |
1121 } | 1122 } |
(...skipping 14 matching lines...) Expand all Loading... |
1136 case 'mixed': | 1137 case 'mixed': |
1137 msg = 'aria_checked_mixed'; | 1138 msg = 'aria_checked_mixed'; |
1138 break; | 1139 break; |
1139 case 'true': | 1140 case 'true': |
1140 msg = 'aria_checked_true'; | 1141 msg = 'aria_checked_true'; |
1141 break; | 1142 break; |
1142 case 'false': | 1143 case 'false': |
1143 msg = 'aria_checked_false'; | 1144 msg = 'aria_checked_false'; |
1144 break; | 1145 break; |
1145 default: | 1146 default: |
1146 msg = node.state[StateType.CHECKED] ? | 1147 msg = |
1147 'aria_checked_true' : 'aria_checked_false'; | 1148 node.state.checked ? 'aria_checked_true' : 'aria_checked_false'; |
1148 } | 1149 } |
1149 this.format_(node, '@' + msg, buff); | 1150 this.format_(node, '@' + msg, buff); |
1150 } else if (token == 'state') { | 1151 } else if (token == 'state') { |
1151 if (node.state) { | 1152 Object.getOwnPropertyNames(node.state).forEach(function(s) { |
1152 Object.getOwnPropertyNames(node.state).forEach(function(s) { | 1153 var stateInfo = Output.STATE_INFO_[s]; |
1153 var stateInfo = Output.STATE_INFO_[s]; | 1154 if (stateInfo && !stateInfo.isRoleSpecific && stateInfo.on) |
1154 if (stateInfo && !stateInfo.isRoleSpecific && stateInfo.on) | |
1155 this.format_(node, '@' + stateInfo.on.msgId, buff); | 1155 this.format_(node, '@' + stateInfo.on.msgId, buff); |
1156 }.bind(this)); | 1156 }.bind(this)); |
1157 } | |
1158 } else if (token == 'find') { | 1157 } else if (token == 'find') { |
1159 // Find takes two arguments: JSON query string and format string. | 1158 // Find takes two arguments: JSON query string and format string. |
1160 if (tree.firstChild) { | 1159 if (tree.firstChild) { |
1161 var jsonQuery = tree.firstChild.value; | 1160 var jsonQuery = tree.firstChild.value; |
1162 node = node.find( | 1161 node = node.find( |
1163 /** @type {chrome.automation.FindParams}*/( | 1162 /** @type {Object}*/(JSON.parse(jsonQuery))); |
1164 JSON.parse(jsonQuery))); | |
1165 var formatString = tree.firstChild.nextSibling; | 1163 var formatString = tree.firstChild.nextSibling; |
1166 if (node) | 1164 if (node) |
1167 this.format_(node, formatString, buff); | 1165 this.format_(node, formatString, buff); |
1168 } | 1166 } |
1169 } else if (token == 'descendants') { | 1167 } else if (token == 'descendants') { |
1170 if (!node || AutomationPredicate.leafOrStaticText(node)) | 1168 if (!node || AutomationPredicate.leafOrStaticText(node)) |
1171 return; | 1169 return; |
1172 | 1170 |
1173 // Construct a range to the leftmost and rightmost leaves. | 1171 // Construct a range to the leftmost and rightmost leaves. |
1174 var leftmost = AutomationUtil.findNodePre( | 1172 var leftmost = AutomationUtil.findNodePre( |
(...skipping 26 matching lines...) Expand all Loading... |
1201 var msg = node.role; | 1199 var msg = node.role; |
1202 var info = Output.ROLE_INFO_[node.role]; | 1200 var info = Output.ROLE_INFO_[node.role]; |
1203 if (info) { | 1201 if (info) { |
1204 if (this.formatOptions_.braille) | 1202 if (this.formatOptions_.braille) |
1205 msg = Msgs.getMsg(info.msgId + '_brl'); | 1203 msg = Msgs.getMsg(info.msgId + '_brl'); |
1206 else | 1204 else |
1207 msg = Msgs.getMsg(info.msgId); | 1205 msg = Msgs.getMsg(info.msgId); |
1208 } else { | 1206 } else { |
1209 console.error('Missing role info for ' + node.role); | 1207 console.error('Missing role info for ' + node.role); |
1210 } | 1208 } |
1211 this.append_(buff, msg || '', options); | 1209 this.append_(buff, msg, options); |
1212 } else if (token == 'inputType') { | 1210 } else if (token == 'inputType') { |
1213 if (!node.inputType) | 1211 if (!node.inputType) |
1214 return; | 1212 return; |
1215 options.annotation.push(token); | 1213 options.annotation.push(token); |
1216 var msgId = Output.INPUT_TYPE_MESSAGE_IDS_[node.inputType] || | 1214 var msgId = Output.INPUT_TYPE_MESSAGE_IDS_[node.inputType] || |
1217 'input_type_text'; | 1215 'input_type_text'; |
1218 if (this.formatOptions_.braille) | 1216 if (this.formatOptions_.braille) |
1219 msgId = msgId + '_brl'; | 1217 msgId = msgId + '_brl'; |
1220 this.append_(buff, Msgs.getMsg(msgId), options); | 1218 this.append_(buff, Msgs.getMsg(msgId), options); |
1221 } else if (token == 'tableCellRowIndex' || | 1219 } else if (token == 'tableCellRowIndex' || |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1256 this.append_(buff, value, options); | 1254 this.append_(buff, value, options); |
1257 } else if (Output.STATE_INFO_[token]) { | 1255 } else if (Output.STATE_INFO_[token]) { |
1258 options.annotation.push('state'); | 1256 options.annotation.push('state'); |
1259 var stateInfo = Output.STATE_INFO_[token]; | 1257 var stateInfo = Output.STATE_INFO_[token]; |
1260 var resolvedInfo = {}; | 1258 var resolvedInfo = {}; |
1261 resolvedInfo = node.state[token] ? stateInfo.on : stateInfo.off; | 1259 resolvedInfo = node.state[token] ? stateInfo.on : stateInfo.off; |
1262 if (!resolvedInfo) | 1260 if (!resolvedInfo) |
1263 return; | 1261 return; |
1264 if (this.formatOptions_.speech && resolvedInfo.earconId) { | 1262 if (this.formatOptions_.speech && resolvedInfo.earconId) { |
1265 options.annotation.push( | 1263 options.annotation.push( |
1266 new Output.EarconAction(resolvedInfo.earconId), | 1264 new Output.EarconAction(resolvedInfo.earconId), node.location); |
1267 node.location || undefined); | |
1268 } | 1265 } |
1269 var msgId = | 1266 var msgId = |
1270 this.formatOptions_.braille ? resolvedInfo.msgId + '_brl' : | 1267 this.formatOptions_.braille ? resolvedInfo.msgId + '_brl' : |
1271 resolvedInfo.msgId; | 1268 resolvedInfo.msgId; |
1272 var msg = Msgs.getMsg(msgId); | 1269 var msg = Msgs.getMsg(msgId); |
1273 this.append_(buff, msg, options); | 1270 this.append_(buff, msg, options); |
1274 } else if (tree.firstChild) { | 1271 } else if (tree.firstChild) { |
1275 // Custom functions. | 1272 // Custom functions. |
1276 if (token == 'if') { | 1273 if (token == 'if') { |
1277 var cond = tree.firstChild; | 1274 var cond = tree.firstChild; |
1278 var attrib = cond.value.slice(1); | 1275 var attrib = cond.value.slice(1); |
1279 if (node[attrib] || node.state[attrib]) | 1276 if (node[attrib] || node.state[attrib]) |
1280 this.format_(node, cond.nextSibling, buff); | 1277 this.format_(node, cond.nextSibling, buff); |
1281 else | 1278 else |
1282 this.format_(node, cond.nextSibling.nextSibling, buff); | 1279 this.format_(node, cond.nextSibling.nextSibling, buff); |
1283 } else if (token == 'earcon') { | 1280 } else if (token == 'earcon') { |
1284 // Ignore unless we're generating speech output. | 1281 // Ignore unless we're generating speech output. |
1285 if (!this.formatOptions_.speech) | 1282 if (!this.formatOptions_.speech) |
1286 return; | 1283 return; |
1287 | 1284 |
1288 options.annotation.push( | 1285 options.annotation.push( |
1289 new Output.EarconAction(tree.firstChild.value, | 1286 new Output.EarconAction(tree.firstChild.value, node.location)); |
1290 node.location || undefined)); | |
1291 this.append_(buff, '', options); | 1287 this.append_(buff, '', options); |
1292 } else if (token == 'countChildren') { | 1288 } else if (token == 'countChildren') { |
1293 var role = tree.firstChild.value; | 1289 var role = tree.firstChild.value; |
1294 var count = node.children.filter(function(e) { | 1290 var count = node.children.filter(function(e) { |
1295 return e.role == role; | 1291 return e.role == role; |
1296 }).length; | 1292 }).length; |
1297 this.append_(buff, String(count)); | 1293 this.append_(buff, String(count)); |
1298 } | 1294 } |
1299 } | 1295 } |
1300 } else if (prefix == '@') { | 1296 } else if (prefix == '@') { |
(...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1447 */ | 1443 */ |
1448 ancestry_: function(node, prevNode, type, buff) { | 1444 ancestry_: function(node, prevNode, type, buff) { |
1449 // Expects |ancestors| to be ordered from root down to leaf. Outputs in | 1445 // Expects |ancestors| to be ordered from root down to leaf. Outputs in |
1450 // reverse; place context first nodes at the end. | 1446 // reverse; place context first nodes at the end. |
1451 function byContextFirst(ancestors) { | 1447 function byContextFirst(ancestors) { |
1452 var contextFirst = []; | 1448 var contextFirst = []; |
1453 var rest = []; | 1449 var rest = []; |
1454 for (i = 0; i < ancestors.length - 1; i++) { | 1450 for (i = 0; i < ancestors.length - 1; i++) { |
1455 var node = ancestors[i]; | 1451 var node = ancestors[i]; |
1456 // Discard ancestors of deepest window. | 1452 // Discard ancestors of deepest window. |
1457 if (node.role == RoleType.WINDOW) { | 1453 if (node.role == RoleType.window) { |
1458 contextFirst = []; | 1454 contextFirst = []; |
1459 rest = []; | 1455 rest = []; |
1460 } | 1456 } |
1461 if ((Output.ROLE_INFO_[node.role] || {}).outputContextFirst) | 1457 if ((Output.ROLE_INFO_[node.role] || {}).outputContextFirst) |
1462 contextFirst.push(node); | 1458 contextFirst.push(node); |
1463 else | 1459 else |
1464 rest.push(node); | 1460 rest.push(node); |
1465 } | 1461 } |
1466 return rest.concat(contextFirst.reverse()); | 1462 return rest.concat(contextFirst.reverse()); |
1467 } | 1463 } |
(...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1611 } | 1607 } |
1612 } | 1608 } |
1613 | 1609 |
1614 if (this.outputContextFirst_) | 1610 if (this.outputContextFirst_) |
1615 this.ancestry_(node, prevNode, type, buff); | 1611 this.ancestry_(node, prevNode, type, buff); |
1616 var earcon = this.findEarcon_(node, prevNode); | 1612 var earcon = this.findEarcon_(node, prevNode); |
1617 if (earcon) | 1613 if (earcon) |
1618 options.annotation.push(earcon); | 1614 options.annotation.push(earcon); |
1619 var text = ''; | 1615 var text = ''; |
1620 | 1616 |
1621 if (this.formatOptions_.braille && !node.state[StateType.EDITABLE]) { | 1617 if (this.formatOptions_.braille && !node.state.editable) { |
1622 // In braille, we almost always want to show the entire contents and | 1618 // In braille, we almost always want to show the entire contents and |
1623 // simply place the cursor under the SelectionSpan we set above. | 1619 // simply place the cursor under the SelectionSpan we set above. |
1624 text = range.start.getText(); | 1620 text = range.start.getText(); |
1625 } else { | 1621 } else { |
1626 // This is output for speech or editable braille. | 1622 // This is output for speech or editable braille. |
1627 text = range.start.getText().substring(rangeStart, rangeEnd); | 1623 text = range.start.getText().substring(rangeStart, rangeEnd); |
1628 } | 1624 } |
1629 | 1625 |
1630 this.append_(buff, text, options); | 1626 this.append_(buff, text, options); |
1631 | 1627 |
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1754 return result; | 1750 return result; |
1755 } | 1751 } |
1756 | 1752 |
1757 // Keep track of if there's an inline node associated with | 1753 // Keep track of if there's an inline node associated with |
1758 // |cur|. | 1754 // |cur|. |
1759 var hasInlineNode = cur.getSpansInstanceOf(Output.NodeSpan) | 1755 var hasInlineNode = cur.getSpansInstanceOf(Output.NodeSpan) |
1760 .some(function(s) { | 1756 .some(function(s) { |
1761 if (!s.node) | 1757 if (!s.node) |
1762 return false; | 1758 return false; |
1763 return s.node.display == 'inline' || | 1759 return s.node.display == 'inline' || |
1764 s.node.role == RoleType.INLINE_TEXT_BOX; | 1760 s.node.role == RoleType.inlineTextBox; |
1765 }); | 1761 }); |
1766 | 1762 |
1767 var isName = cur.hasSpan('name'); | 1763 var isName = cur.hasSpan('name'); |
1768 | 1764 |
1769 // Now, decide whether we should include separators between the previous | 1765 // Now, decide whether we should include separators between the previous |
1770 // span and |cur|. | 1766 // span and |cur|. |
1771 // Never separate chunks without something already there at this point. | 1767 // Never separate chunks without something already there at this point. |
1772 | 1768 |
1773 // The only case where we know for certain that a separator is not needed | 1769 // The only case where we know for certain that a separator is not needed |
1774 // is when the previous and current values are in-lined and part of the | 1770 // is when the previous and current values are in-lined and part of the |
(...skipping 30 matching lines...) Expand all Loading... |
1805 var earconFinder = node; | 1801 var earconFinder = node; |
1806 var ancestors; | 1802 var ancestors; |
1807 if (opt_prevNode) | 1803 if (opt_prevNode) |
1808 ancestors = AutomationUtil.getUniqueAncestors(opt_prevNode, node); | 1804 ancestors = AutomationUtil.getUniqueAncestors(opt_prevNode, node); |
1809 else | 1805 else |
1810 ancestors = AutomationUtil.getAncestors(node); | 1806 ancestors = AutomationUtil.getAncestors(node); |
1811 | 1807 |
1812 while (earconFinder = ancestors.pop()) { | 1808 while (earconFinder = ancestors.pop()) { |
1813 var info = Output.ROLE_INFO_[earconFinder.role]; | 1809 var info = Output.ROLE_INFO_[earconFinder.role]; |
1814 if (info && info.earconId) { | 1810 if (info && info.earconId) { |
1815 return new Output.EarconAction(info.earconId, | 1811 return new Output.EarconAction(info.earconId, node.location); |
1816 node.location || undefined); | |
1817 break; | 1812 break; |
1818 } | 1813 } |
1819 earconFinder = earconFinder.parent; | 1814 earconFinder = earconFinder.parent; |
1820 } | 1815 } |
1821 } | 1816 } |
1822 return null; | 1817 return null; |
1823 }, | 1818 }, |
1824 | 1819 |
1825 /** | 1820 /** |
1826 * Gets a human friendly string with the contents of output. | 1821 * Gets a human friendly string with the contents of output. |
(...skipping 25 matching lines...) Expand all Loading... |
1852 /** | 1847 /** |
1853 * Gets the output buffer for braille. | 1848 * Gets the output buffer for braille. |
1854 * @return {!Spannable} | 1849 * @return {!Spannable} |
1855 */ | 1850 */ |
1856 get brailleOutputForTest() { | 1851 get brailleOutputForTest() { |
1857 return this.mergeBraille_(this.brailleBuffer_); | 1852 return this.mergeBraille_(this.brailleBuffer_); |
1858 } | 1853 } |
1859 }; | 1854 }; |
1860 | 1855 |
1861 }); // goog.scope | 1856 }); // goog.scope |
OLD | NEW |