| 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; |
| 33 | 34 |
| 34 /** | 35 /** |
| 35 * An Output object formats a cursors.Range into speech, braille, or both | 36 * An Output object formats a cursors.Range into speech, braille, or both |
| 36 * representations. This is typically a |Spannable|. | 37 * representations. This is typically a |Spannable|. |
| 37 * | 38 * |
| 38 * The translation from Range to these output representations rely upon format | 39 * The translation from Range to these output representations rely upon format |
| 39 * rules which specify how to convert AutomationNode objects into annotated | 40 * rules which specify how to convert AutomationNode objects into annotated |
| 40 * strings. | 41 * strings. |
| 41 * The format of these rules is as follows. | 42 * The format of these rules is as follows. |
| 42 * | 43 * |
| (...skipping 935 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 978 if (prevRange && !prevRange.isValid()) | 979 if (prevRange && !prevRange.isValid()) |
| 979 prevRange = null; | 980 prevRange = null; |
| 980 | 981 |
| 981 // Scan unique ancestors to get the value of |outputContextFirst|. | 982 // Scan unique ancestors to get the value of |outputContextFirst|. |
| 982 var parent = range.start.node; | 983 var parent = range.start.node; |
| 983 var prevParent = prevRange ? prevRange.start.node : parent; | 984 var prevParent = prevRange ? prevRange.start.node : parent; |
| 984 if (!parent || !prevParent) | 985 if (!parent || !prevParent) |
| 985 return; | 986 return; |
| 986 var uniqueAncestors = AutomationUtil.getUniqueAncestors(prevParent, parent); | 987 var uniqueAncestors = AutomationUtil.getUniqueAncestors(prevParent, parent); |
| 987 for (var i = 0; parent = uniqueAncestors[i]; i++) { | 988 for (var i = 0; parent = uniqueAncestors[i]; i++) { |
| 988 if (parent.role == RoleType.window) | 989 if (parent.role == RoleType.WINDOW) |
| 989 break; | 990 break; |
| 990 if (Output.ROLE_INFO_[parent.role] && | 991 if (Output.ROLE_INFO_[parent.role] && |
| 991 Output.ROLE_INFO_[parent.role].outputContextFirst) { | 992 Output.ROLE_INFO_[parent.role].outputContextFirst) { |
| 992 this.outputContextFirst_ = true; | 993 this.outputContextFirst_ = true; |
| 993 break; | 994 break; |
| 994 } | 995 } |
| 995 } | 996 } |
| 996 | 997 |
| 997 if (range.isSubNode()) | 998 if (range.isSubNode()) |
| 998 this.subNode_(range, prevRange, type, buff); | 999 this.subNode_(range, prevRange, type, buff); |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1045 token = token.substring(0, token.length - 1); | 1046 token = token.substring(0, token.length - 1); |
| 1046 | 1047 |
| 1047 // Process token based on prefix. | 1048 // Process token based on prefix. |
| 1048 var prefix = token[0]; | 1049 var prefix = token[0]; |
| 1049 token = token.slice(1); | 1050 token = token.slice(1); |
| 1050 | 1051 |
| 1051 // All possible tokens based on prefix. | 1052 // All possible tokens based on prefix. |
| 1052 if (prefix == '$') { | 1053 if (prefix == '$') { |
| 1053 if (token == 'value') { | 1054 if (token == 'value') { |
| 1054 var text = node.value; | 1055 var text = node.value; |
| 1055 if (!node.state.editable && node.name == text) | 1056 if (!node.state[StateType.EDITABLE] && node.name == text) |
| 1056 return; | 1057 return; |
| 1057 | 1058 |
| 1058 var selectedText = ''; | 1059 var selectedText = ''; |
| 1059 if (text !== undefined) { | 1060 if (text !== undefined) { |
| 1060 if (node.textSelStart !== undefined) { | 1061 if (node.textSelStart !== undefined) { |
| 1061 options.annotation.push(new Output.SelectionSpan( | 1062 options.annotation.push(new Output.SelectionSpan( |
| 1062 node.textSelStart, | 1063 node.textSelStart, |
| 1063 node.textSelEnd)); | 1064 node.textSelEnd)); |
| 1064 | 1065 |
| 1065 selectedText = | 1066 selectedText = |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1137 case 'mixed': | 1138 case 'mixed': |
| 1138 msg = 'aria_checked_mixed'; | 1139 msg = 'aria_checked_mixed'; |
| 1139 break; | 1140 break; |
| 1140 case 'true': | 1141 case 'true': |
| 1141 msg = 'aria_checked_true'; | 1142 msg = 'aria_checked_true'; |
| 1142 break; | 1143 break; |
| 1143 case 'false': | 1144 case 'false': |
| 1144 msg = 'aria_checked_false'; | 1145 msg = 'aria_checked_false'; |
| 1145 break; | 1146 break; |
| 1146 default: | 1147 default: |
| 1147 msg = | 1148 msg = node.state[StateType.CHECKED] ? |
| 1148 node.state.checked ? 'aria_checked_true' : 'aria_checked_false'; | 1149 'aria_checked_true' : 'aria_checked_false'; |
| 1149 } | 1150 } |
| 1150 this.format_(node, '@' + msg, buff); | 1151 this.format_(node, '@' + msg, buff); |
| 1151 } else if (token == 'state') { | 1152 } else if (token == 'state') { |
| 1152 Object.getOwnPropertyNames(node.state).forEach(function(s) { | 1153 Object.getOwnPropertyNames(node.state).forEach(function(s) { |
| 1153 var stateInfo = Output.STATE_INFO_[s]; | 1154 var stateInfo = Output.STATE_INFO_[s]; |
| 1154 if (stateInfo && !stateInfo.isRoleSpecific && stateInfo.on) | 1155 if (stateInfo && !stateInfo.isRoleSpecific && stateInfo.on) |
| 1155 this.format_(node, '@' + stateInfo.on.msgId, buff); | 1156 this.format_(node, '@' + stateInfo.on.msgId, buff); |
| 1156 }.bind(this)); | 1157 }.bind(this)); |
| 1157 } else if (token == 'find') { | 1158 } else if (token == 'find') { |
| 1158 // Find takes two arguments: JSON query string and format string. | 1159 // Find takes two arguments: JSON query string and format string. |
| 1159 if (tree.firstChild) { | 1160 if (tree.firstChild) { |
| 1160 var jsonQuery = tree.firstChild.value; | 1161 var jsonQuery = tree.firstChild.value; |
| 1161 node = node.find( | 1162 node = node.find( |
| 1162 /** @type {Object}*/(JSON.parse(jsonQuery))); | 1163 /** @type {chrome.automation.FindParams}*/( |
| 1164 JSON.parse(jsonQuery))); |
| 1163 var formatString = tree.firstChild.nextSibling; | 1165 var formatString = tree.firstChild.nextSibling; |
| 1164 if (node) | 1166 if (node) |
| 1165 this.format_(node, formatString, buff); | 1167 this.format_(node, formatString, buff); |
| 1166 } | 1168 } |
| 1167 } else if (token == 'descendants') { | 1169 } else if (token == 'descendants') { |
| 1168 if (!node || AutomationPredicate.leafOrStaticText(node)) | 1170 if (!node || AutomationPredicate.leafOrStaticText(node)) |
| 1169 return; | 1171 return; |
| 1170 | 1172 |
| 1171 // Construct a range to the leftmost and rightmost leaves. | 1173 // Construct a range to the leftmost and rightmost leaves. |
| 1172 var leftmost = AutomationUtil.findNodePre( | 1174 var leftmost = AutomationUtil.findNodePre( |
| (...skipping 270 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1443 */ | 1445 */ |
| 1444 ancestry_: function(node, prevNode, type, buff) { | 1446 ancestry_: function(node, prevNode, type, buff) { |
| 1445 // Expects |ancestors| to be ordered from root down to leaf. Outputs in | 1447 // Expects |ancestors| to be ordered from root down to leaf. Outputs in |
| 1446 // reverse; place context first nodes at the end. | 1448 // reverse; place context first nodes at the end. |
| 1447 function byContextFirst(ancestors) { | 1449 function byContextFirst(ancestors) { |
| 1448 var contextFirst = []; | 1450 var contextFirst = []; |
| 1449 var rest = []; | 1451 var rest = []; |
| 1450 for (i = 0; i < ancestors.length - 1; i++) { | 1452 for (i = 0; i < ancestors.length - 1; i++) { |
| 1451 var node = ancestors[i]; | 1453 var node = ancestors[i]; |
| 1452 // Discard ancestors of deepest window. | 1454 // Discard ancestors of deepest window. |
| 1453 if (node.role == RoleType.window) { | 1455 if (node.role == RoleType.WINDOW) { |
| 1454 contextFirst = []; | 1456 contextFirst = []; |
| 1455 rest = []; | 1457 rest = []; |
| 1456 } | 1458 } |
| 1457 if ((Output.ROLE_INFO_[node.role] || {}).outputContextFirst) | 1459 if ((Output.ROLE_INFO_[node.role] || {}).outputContextFirst) |
| 1458 contextFirst.push(node); | 1460 contextFirst.push(node); |
| 1459 else | 1461 else |
| 1460 rest.push(node); | 1462 rest.push(node); |
| 1461 } | 1463 } |
| 1462 return rest.concat(contextFirst.reverse()); | 1464 return rest.concat(contextFirst.reverse()); |
| 1463 } | 1465 } |
| (...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1607 } | 1609 } |
| 1608 } | 1610 } |
| 1609 | 1611 |
| 1610 if (this.outputContextFirst_) | 1612 if (this.outputContextFirst_) |
| 1611 this.ancestry_(node, prevNode, type, buff); | 1613 this.ancestry_(node, prevNode, type, buff); |
| 1612 var earcon = this.findEarcon_(node, prevNode); | 1614 var earcon = this.findEarcon_(node, prevNode); |
| 1613 if (earcon) | 1615 if (earcon) |
| 1614 options.annotation.push(earcon); | 1616 options.annotation.push(earcon); |
| 1615 var text = ''; | 1617 var text = ''; |
| 1616 | 1618 |
| 1617 if (this.formatOptions_.braille && !node.state.editable) { | 1619 if (this.formatOptions_.braille && !node.state[StateType.EDITABLE]) { |
| 1618 // In braille, we almost always want to show the entire contents and | 1620 // In braille, we almost always want to show the entire contents and |
| 1619 // simply place the cursor under the SelectionSpan we set above. | 1621 // simply place the cursor under the SelectionSpan we set above. |
| 1620 text = range.start.getText(); | 1622 text = range.start.getText(); |
| 1621 } else { | 1623 } else { |
| 1622 // This is output for speech or editable braille. | 1624 // This is output for speech or editable braille. |
| 1623 text = range.start.getText().substring(rangeStart, rangeEnd); | 1625 text = range.start.getText().substring(rangeStart, rangeEnd); |
| 1624 } | 1626 } |
| 1625 | 1627 |
| 1626 this.append_(buff, text, options); | 1628 this.append_(buff, text, options); |
| 1627 | 1629 |
| (...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1750 return result; | 1752 return result; |
| 1751 } | 1753 } |
| 1752 | 1754 |
| 1753 // Keep track of if there's an inline node associated with | 1755 // Keep track of if there's an inline node associated with |
| 1754 // |cur|. | 1756 // |cur|. |
| 1755 var hasInlineNode = cur.getSpansInstanceOf(Output.NodeSpan) | 1757 var hasInlineNode = cur.getSpansInstanceOf(Output.NodeSpan) |
| 1756 .some(function(s) { | 1758 .some(function(s) { |
| 1757 if (!s.node) | 1759 if (!s.node) |
| 1758 return false; | 1760 return false; |
| 1759 return s.node.display == 'inline' || | 1761 return s.node.display == 'inline' || |
| 1760 s.node.role == RoleType.inlineTextBox; | 1762 s.node.role == RoleType.INLINE_TEXT_BOX; |
| 1761 }); | 1763 }); |
| 1762 | 1764 |
| 1763 var isName = cur.hasSpan('name'); | 1765 var isName = cur.hasSpan('name'); |
| 1764 | 1766 |
| 1765 // Now, decide whether we should include separators between the previous | 1767 // Now, decide whether we should include separators between the previous |
| 1766 // span and |cur|. | 1768 // span and |cur|. |
| 1767 // Never separate chunks without something already there at this point. | 1769 // Never separate chunks without something already there at this point. |
| 1768 | 1770 |
| 1769 // The only case where we know for certain that a separator is not needed | 1771 // The only case where we know for certain that a separator is not needed |
| 1770 // is when the previous and current values are in-lined and part of the | 1772 // is when the previous and current values are in-lined and part of the |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1847 /** | 1849 /** |
| 1848 * Gets the output buffer for braille. | 1850 * Gets the output buffer for braille. |
| 1849 * @return {!Spannable} | 1851 * @return {!Spannable} |
| 1850 */ | 1852 */ |
| 1851 get brailleOutputForTest() { | 1853 get brailleOutputForTest() { |
| 1852 return this.mergeBraille_(this.brailleBuffer_); | 1854 return this.mergeBraille_(this.brailleBuffer_); |
| 1853 } | 1855 } |
| 1854 }; | 1856 }; |
| 1855 | 1857 |
| 1856 }); // goog.scope | 1858 }); // goog.scope |
| OLD | NEW |