Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(239)

Side by Side Diff: chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js

Issue 2601333002: Update json_schema_compiler to handle the Automation extension API (Closed)
Patch Set: Rebase Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
1044 if (options.isUnique) 1045 if (options.isUnique)
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 (node.textSelStart !== undefined) {
1060 if (node.textSelStart !== undefined) { 1061 options.annotation.push(new Output.SelectionSpan(
1061 options.annotation.push(new Output.SelectionSpan( 1062 node.textSelStart || 0,
1062 node.textSelStart, 1063 node.textSelEnd || 0));
1063 node.textSelEnd));
1064 1064
1065 selectedText = 1065 selectedText =
1066 node.value.substring(node.textSelStart, node.textSelEnd); 1066 node.value.substring(node.textSelStart || 0,
1067 } 1067 node.textSelEnd || 0);
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 (chrome.automation.NameFromType[node.nameFrom] == 1096 if (node.nameFrom == chrome.automation.NameFromType.CONTENTS)
1097 'contents')
1098 return; 1097 return;
1099 1098
1100 options.annotation.push('name'); 1099 options.annotation.push('name');
1101 this.append_(buff, node.name, options); 1100 this.append_(buff, node.name || '', options);
1102 } else if (token == 'nameOrDescendants') { 1101 } else if (token == 'nameOrDescendants') {
1103 options.annotation.push(token); 1102 options.annotation.push(token);
1104 if (node.name) 1103 if (node.name)
1105 this.append_(buff, node.name, options); 1104 this.append_(buff, node.name || '', options);
1106 else 1105 else
1107 this.format_(node, '$descendants', buff); 1106 this.format_(node, '$descendants', buff);
1108 } else if (token == 'description') { 1107 } else if (token == 'description') {
1109 if (node.name == node.description || node.value == node.description) 1108 if (node.name == node.description || node.value == node.description)
1110 return; 1109 return;
1111 options.annotation.push(token); 1110 options.annotation.push(token);
1112 this.append_(buff, node.description, options); 1111 this.append_(buff, node.description || '', options);
1113 } else if (token == 'indexInParent') { 1112 } else if (token == 'indexInParent') {
1114 if (node.parent) { 1113 if (node.parent) {
1115 options.annotation.push(token); 1114 options.annotation.push(token);
1116 var count = 0; 1115 var count = 0;
1117 for (var i = 0, child; child = node.parent.children[i]; i++) { 1116 for (var i = 0, child; child = node.parent.children[i]; i++) {
1118 if (node.role == child.role) 1117 if (node.role == child.role)
1119 count++; 1118 count++;
1120 if (node === child) 1119 if (node === child)
1121 break; 1120 break;
1122 } 1121 }
(...skipping 14 matching lines...) Expand all
1137 case 'mixed': 1136 case 'mixed':
1138 msg = 'aria_checked_mixed'; 1137 msg = 'aria_checked_mixed';
1139 break; 1138 break;
1140 case 'true': 1139 case 'true':
1141 msg = 'aria_checked_true'; 1140 msg = 'aria_checked_true';
1142 break; 1141 break;
1143 case 'false': 1142 case 'false':
1144 msg = 'aria_checked_false'; 1143 msg = 'aria_checked_false';
1145 break; 1144 break;
1146 default: 1145 default:
1147 msg = 1146 msg = node.state[StateType.CHECKED] ?
1148 node.state.checked ? 'aria_checked_true' : 'aria_checked_false'; 1147 'aria_checked_true' : 'aria_checked_false';
1149 } 1148 }
1150 this.format_(node, '@' + msg, buff); 1149 this.format_(node, '@' + msg, buff);
1151 } else if (token == 'state') { 1150 } else if (token == 'state') {
1152 Object.getOwnPropertyNames(node.state).forEach(function(s) { 1151 if (node.state) {
1153 var stateInfo = Output.STATE_INFO_[s]; 1152 Object.getOwnPropertyNames(node.state).forEach(function(s) {
1154 if (stateInfo && !stateInfo.isRoleSpecific && stateInfo.on) 1153 var stateInfo = Output.STATE_INFO_[s];
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 }
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 26 matching lines...) Expand all
1199 var msg = node.role; 1201 var msg = node.role;
1200 var info = Output.ROLE_INFO_[node.role]; 1202 var info = Output.ROLE_INFO_[node.role];
1201 if (info) { 1203 if (info) {
1202 if (this.formatOptions_.braille) 1204 if (this.formatOptions_.braille)
1203 msg = Msgs.getMsg(info.msgId + '_brl'); 1205 msg = Msgs.getMsg(info.msgId + '_brl');
1204 else 1206 else
1205 msg = Msgs.getMsg(info.msgId); 1207 msg = Msgs.getMsg(info.msgId);
1206 } else { 1208 } else {
1207 console.error('Missing role info for ' + node.role); 1209 console.error('Missing role info for ' + node.role);
1208 } 1210 }
1209 this.append_(buff, msg, options); 1211 this.append_(buff, msg || '', options);
1210 } else if (token == 'inputType') { 1212 } else if (token == 'inputType') {
1211 if (!node.inputType) 1213 if (!node.inputType)
1212 return; 1214 return;
1213 options.annotation.push(token); 1215 options.annotation.push(token);
1214 var msgId = Output.INPUT_TYPE_MESSAGE_IDS_[node.inputType] || 1216 var msgId = Output.INPUT_TYPE_MESSAGE_IDS_[node.inputType] ||
1215 'input_type_text'; 1217 'input_type_text';
1216 if (this.formatOptions_.braille) 1218 if (this.formatOptions_.braille)
1217 msgId = msgId + '_brl'; 1219 msgId = msgId + '_brl';
1218 this.append_(buff, Msgs.getMsg(msgId), options); 1220 this.append_(buff, Msgs.getMsg(msgId), options);
1219 } else if (token == 'tableCellRowIndex' || 1221 } else if (token == 'tableCellRowIndex' ||
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
1254 this.append_(buff, value, options); 1256 this.append_(buff, value, options);
1255 } else if (Output.STATE_INFO_[token]) { 1257 } else if (Output.STATE_INFO_[token]) {
1256 options.annotation.push('state'); 1258 options.annotation.push('state');
1257 var stateInfo = Output.STATE_INFO_[token]; 1259 var stateInfo = Output.STATE_INFO_[token];
1258 var resolvedInfo = {}; 1260 var resolvedInfo = {};
1259 resolvedInfo = node.state[token] ? stateInfo.on : stateInfo.off; 1261 resolvedInfo = node.state[token] ? stateInfo.on : stateInfo.off;
1260 if (!resolvedInfo) 1262 if (!resolvedInfo)
1261 return; 1263 return;
1262 if (this.formatOptions_.speech && resolvedInfo.earconId) { 1264 if (this.formatOptions_.speech && resolvedInfo.earconId) {
1263 options.annotation.push( 1265 options.annotation.push(
1264 new Output.EarconAction(resolvedInfo.earconId), node.location); 1266 new Output.EarconAction(resolvedInfo.earconId),
1267 node.location || undefined);
1265 } 1268 }
1266 var msgId = 1269 var msgId =
1267 this.formatOptions_.braille ? resolvedInfo.msgId + '_brl' : 1270 this.formatOptions_.braille ? resolvedInfo.msgId + '_brl' :
1268 resolvedInfo.msgId; 1271 resolvedInfo.msgId;
1269 var msg = Msgs.getMsg(msgId); 1272 var msg = Msgs.getMsg(msgId);
1270 this.append_(buff, msg, options); 1273 this.append_(buff, msg, options);
1271 } else if (tree.firstChild) { 1274 } else if (tree.firstChild) {
1272 // Custom functions. 1275 // Custom functions.
1273 if (token == 'if') { 1276 if (token == 'if') {
1274 var cond = tree.firstChild; 1277 var cond = tree.firstChild;
1275 var attrib = cond.value.slice(1); 1278 var attrib = cond.value.slice(1);
1276 if (node[attrib] || node.state[attrib]) 1279 if (node[attrib] || node.state[attrib])
1277 this.format_(node, cond.nextSibling, buff); 1280 this.format_(node, cond.nextSibling, buff);
1278 else 1281 else
1279 this.format_(node, cond.nextSibling.nextSibling, buff); 1282 this.format_(node, cond.nextSibling.nextSibling, buff);
1280 } else if (token == 'earcon') { 1283 } else if (token == 'earcon') {
1281 // Ignore unless we're generating speech output. 1284 // Ignore unless we're generating speech output.
1282 if (!this.formatOptions_.speech) 1285 if (!this.formatOptions_.speech)
1283 return; 1286 return;
1284 1287
1285 options.annotation.push( 1288 options.annotation.push(
1286 new Output.EarconAction(tree.firstChild.value, node.location)); 1289 new Output.EarconAction(tree.firstChild.value,
1290 node.location || undefined));
1287 this.append_(buff, '', options); 1291 this.append_(buff, '', options);
1288 } else if (token == 'countChildren') { 1292 } else if (token == 'countChildren') {
1289 var role = tree.firstChild.value; 1293 var role = tree.firstChild.value;
1290 var count = node.children.filter(function(e) { 1294 var count = node.children.filter(function(e) {
1291 return e.role == role; 1295 return e.role == role;
1292 }).length; 1296 }).length;
1293 this.append_(buff, String(count)); 1297 this.append_(buff, String(count));
1294 } 1298 }
1295 } 1299 }
1296 } else if (prefix == '@') { 1300 } else if (prefix == '@') {
(...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after
1443 */ 1447 */
1444 ancestry_: function(node, prevNode, type, buff) { 1448 ancestry_: function(node, prevNode, type, buff) {
1445 // Expects |ancestors| to be ordered from root down to leaf. Outputs in 1449 // Expects |ancestors| to be ordered from root down to leaf. Outputs in
1446 // reverse; place context first nodes at the end. 1450 // reverse; place context first nodes at the end.
1447 function byContextFirst(ancestors) { 1451 function byContextFirst(ancestors) {
1448 var contextFirst = []; 1452 var contextFirst = [];
1449 var rest = []; 1453 var rest = [];
1450 for (i = 0; i < ancestors.length - 1; i++) { 1454 for (i = 0; i < ancestors.length - 1; i++) {
1451 var node = ancestors[i]; 1455 var node = ancestors[i];
1452 // Discard ancestors of deepest window. 1456 // Discard ancestors of deepest window.
1453 if (node.role == RoleType.window) { 1457 if (node.role == RoleType.WINDOW) {
1454 contextFirst = []; 1458 contextFirst = [];
1455 rest = []; 1459 rest = [];
1456 } 1460 }
1457 if ((Output.ROLE_INFO_[node.role] || {}).outputContextFirst) 1461 if ((Output.ROLE_INFO_[node.role] || {}).outputContextFirst)
1458 contextFirst.push(node); 1462 contextFirst.push(node);
1459 else 1463 else
1460 rest.push(node); 1464 rest.push(node);
1461 } 1465 }
1462 return rest.concat(contextFirst.reverse()); 1466 return rest.concat(contextFirst.reverse());
1463 } 1467 }
(...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after
1607 } 1611 }
1608 } 1612 }
1609 1613
1610 if (this.outputContextFirst_) 1614 if (this.outputContextFirst_)
1611 this.ancestry_(node, prevNode, type, buff); 1615 this.ancestry_(node, prevNode, type, buff);
1612 var earcon = this.findEarcon_(node, prevNode); 1616 var earcon = this.findEarcon_(node, prevNode);
1613 if (earcon) 1617 if (earcon)
1614 options.annotation.push(earcon); 1618 options.annotation.push(earcon);
1615 var text = ''; 1619 var text = '';
1616 1620
1617 if (this.formatOptions_.braille && !node.state.editable) { 1621 if (this.formatOptions_.braille && !node.state[StateType.EDITABLE]) {
1618 // In braille, we almost always want to show the entire contents and 1622 // In braille, we almost always want to show the entire contents and
1619 // simply place the cursor under the SelectionSpan we set above. 1623 // simply place the cursor under the SelectionSpan we set above.
1620 text = range.start.getText(); 1624 text = range.start.getText();
1621 } else { 1625 } else {
1622 // This is output for speech or editable braille. 1626 // This is output for speech or editable braille.
1623 text = range.start.getText().substring(rangeStart, rangeEnd); 1627 text = range.start.getText().substring(rangeStart, rangeEnd);
1624 } 1628 }
1625 1629
1626 this.append_(buff, text, options); 1630 this.append_(buff, text, options);
1627 1631
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after
1750 return result; 1754 return result;
1751 } 1755 }
1752 1756
1753 // Keep track of if there's an inline node associated with 1757 // Keep track of if there's an inline node associated with
1754 // |cur|. 1758 // |cur|.
1755 var hasInlineNode = cur.getSpansInstanceOf(Output.NodeSpan) 1759 var hasInlineNode = cur.getSpansInstanceOf(Output.NodeSpan)
1756 .some(function(s) { 1760 .some(function(s) {
1757 if (!s.node) 1761 if (!s.node)
1758 return false; 1762 return false;
1759 return s.node.display == 'inline' || 1763 return s.node.display == 'inline' ||
1760 s.node.role == RoleType.inlineTextBox; 1764 s.node.role == RoleType.INLINE_TEXT_BOX;
1761 }); 1765 });
1762 1766
1763 var isName = cur.hasSpan('name'); 1767 var isName = cur.hasSpan('name');
1764 1768
1765 // Now, decide whether we should include separators between the previous 1769 // Now, decide whether we should include separators between the previous
1766 // span and |cur|. 1770 // span and |cur|.
1767 // Never separate chunks without something already there at this point. 1771 // Never separate chunks without something already there at this point.
1768 1772
1769 // The only case where we know for certain that a separator is not needed 1773 // 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 1774 // is when the previous and current values are in-lined and part of the
(...skipping 30 matching lines...) Expand all
1801 var earconFinder = node; 1805 var earconFinder = node;
1802 var ancestors; 1806 var ancestors;
1803 if (opt_prevNode) 1807 if (opt_prevNode)
1804 ancestors = AutomationUtil.getUniqueAncestors(opt_prevNode, node); 1808 ancestors = AutomationUtil.getUniqueAncestors(opt_prevNode, node);
1805 else 1809 else
1806 ancestors = AutomationUtil.getAncestors(node); 1810 ancestors = AutomationUtil.getAncestors(node);
1807 1811
1808 while (earconFinder = ancestors.pop()) { 1812 while (earconFinder = ancestors.pop()) {
1809 var info = Output.ROLE_INFO_[earconFinder.role]; 1813 var info = Output.ROLE_INFO_[earconFinder.role];
1810 if (info && info.earconId) { 1814 if (info && info.earconId) {
1811 return new Output.EarconAction(info.earconId, node.location); 1815 return new Output.EarconAction(info.earconId,
1816 node.location || undefined);
1812 break; 1817 break;
1813 } 1818 }
1814 earconFinder = earconFinder.parent; 1819 earconFinder = earconFinder.parent;
1815 } 1820 }
1816 } 1821 }
1817 return null; 1822 return null;
1818 }, 1823 },
1819 1824
1820 /** 1825 /**
1821 * Gets a human friendly string with the contents of output. 1826 * Gets a human friendly string with the contents of output.
(...skipping 25 matching lines...) Expand all
1847 /** 1852 /**
1848 * Gets the output buffer for braille. 1853 * Gets the output buffer for braille.
1849 * @return {!Spannable} 1854 * @return {!Spannable}
1850 */ 1855 */
1851 get brailleOutputForTest() { 1856 get brailleOutputForTest() {
1852 return this.mergeBraille_(this.brailleBuffer_); 1857 return this.mergeBraille_(this.brailleBuffer_);
1853 } 1858 }
1854 }; 1859 };
1855 1860
1856 }); // goog.scope 1861 }); // goog.scope
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698