| 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 239 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 250 navigation: { | 250 navigation: { |
| 251 msgId: 'role_navigation', | 251 msgId: 'role_navigation', |
| 252 inherits: 'abstractContainer' | 252 inherits: 'abstractContainer' |
| 253 }, | 253 }, |
| 254 note: { | 254 note: { |
| 255 msgId: 'role_note', | 255 msgId: 'role_note', |
| 256 inherits: 'abstractContainer' | 256 inherits: 'abstractContainer' |
| 257 }, | 257 }, |
| 258 popUpButton: { | 258 popUpButton: { |
| 259 msgId: 'role_button', | 259 msgId: 'role_button', |
| 260 earconId: 'POP_UP_BUTTON' |
| 260 }, | 261 }, |
| 261 radioButton: { | 262 radioButton: { |
| 262 msgId: 'role_radio' | 263 msgId: 'role_radio' |
| 263 }, | 264 }, |
| 264 radioGroup: { | 265 radioGroup: { |
| 265 msgId: 'role_radiogroup', | 266 msgId: 'role_radiogroup', |
| 266 }, | 267 }, |
| 267 rowHeader: { | 268 rowHeader: { |
| 268 msgId: 'role_rowheader', | 269 msgId: 'role_rowheader', |
| 269 inherits: 'abstractContainer' | 270 inherits: 'abstractContainer' |
| (...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 389 * Rules specifying format of AutomationNodes for output. | 390 * Rules specifying format of AutomationNodes for output. |
| 390 * @type {!Object<Object<Object<string>>>} | 391 * @type {!Object<Object<Object<string>>>} |
| 391 */ | 392 */ |
| 392 Output.RULES = { | 393 Output.RULES = { |
| 393 navigate: { | 394 navigate: { |
| 394 'default': { | 395 'default': { |
| 395 speak: '$name $value $role $description', | 396 speak: '$name $value $role $description', |
| 396 braille: '' | 397 braille: '' |
| 397 }, | 398 }, |
| 398 abstractContainer: { | 399 abstractContainer: { |
| 399 enter: '$name $role $description', | 400 enter: '$nameFromNode $role $description', |
| 400 leave: '@exited_container($role)' | 401 leave: '@exited_container($role)' |
| 401 }, | 402 }, |
| 402 alert: { | 403 alert: { |
| 403 speak: '!doNotInterrupt $role $descendants' | 404 speak: '!doNotInterrupt $role $descendants' |
| 404 }, | 405 }, |
| 405 alertDialog: { | 406 alertDialog: { |
| 406 enter: '$name $role $description $descendants' | 407 enter: '$nameFromNode $role $description $descendants' |
| 407 }, | 408 }, |
| 408 cell: { | 409 cell: { |
| 409 enter: '@column_granularity $tableCellColumnIndex' | 410 enter: '@column_granularity $tableCellColumnIndex' |
| 410 }, | 411 }, |
| 411 checkBox: { | 412 checkBox: { |
| 412 speak: '$if($checked, $earcon(CHECK_ON), $earcon(CHECK_OFF)) ' + | 413 speak: '$if($checked, $earcon(CHECK_ON), $earcon(CHECK_OFF)) ' + |
| 413 '$name $role $checked $description' | 414 '$name $role $checked $description' |
| 414 }, | 415 }, |
| 415 dialog: { | 416 dialog: { |
| 416 enter: '$name $role $description' | 417 enter: '$nameFromNode $role $description' |
| 417 }, | 418 }, |
| 418 div: { | 419 div: { |
| 419 enter: '$name', | 420 enter: '$nameFromNode', |
| 420 speak: '$name $description' | 421 speak: '$name $description' |
| 421 }, | 422 }, |
| 422 grid: { | 423 grid: { |
| 423 enter: '$name $role $description' | 424 enter: '$nameFromNode $role $description' |
| 424 }, | 425 }, |
| 425 heading: { | 426 heading: { |
| 426 enter: '@tag_h+$hierarchicalLevel', | 427 enter: '@tag_h+$hierarchicalLevel', |
| 427 speak: '!relativePitch(hierarchicalLevel)' + | 428 speak: '!relativePitch(hierarchicalLevel)' + |
| 428 ' $nameOrDescendants= @tag_h+$hierarchicalLevel' | 429 ' $nameOrDescendants= @tag_h+$hierarchicalLevel' |
| 429 }, | 430 }, |
| 430 inlineTextBox: { | 431 inlineTextBox: { |
| 431 speak: '$name=' | 432 speak: '$name=' |
| 432 }, | 433 }, |
| 433 link: { | 434 link: { |
| 434 enter: '$name= $if($visited, @visited_link, $role)', | 435 enter: '$nameFromNode $if($visited, @visited_link, $role)', |
| 435 speak: '$name= $if($visited, @visited_link, $role) $description' | 436 speak: '$name= $if($visited, @visited_link, $role) $description' |
| 436 }, | 437 }, |
| 437 list: { | 438 list: { |
| 438 enter: '$role @@list_with_items($countChildren(listItem))' | 439 enter: '$role @@list_with_items($countChildren(listItem))' |
| 439 }, | 440 }, |
| 440 listBox: { | 441 listBox: { |
| 441 enter: '$name $role @@list_with_items($countChildren(listBoxOption)) ' + | 442 enter: '$nameFromNode ' + |
| 443 '$role @@list_with_items($countChildren(listBoxOption)) ' + |
| 442 '$description' | 444 '$description' |
| 443 }, | 445 }, |
| 444 listBoxOption: { | 446 listBoxOption: { |
| 445 speak: '$name $role @describe_index($indexInParent, $parentChildCount) ' + | 447 speak: '$name $role @describe_index($indexInParent, $parentChildCount) ' + |
| 446 '$description' | 448 '$description' |
| 447 }, | 449 }, |
| 448 listItem: { | 450 listItem: { |
| 449 enter: '$role' | 451 enter: '$role' |
| 450 }, | 452 }, |
| 451 menu: { | 453 menu: { |
| 452 enter: '$name $role', | 454 enter: '$name $role', |
| 453 speak: '$name $role @@list_with_items($countChildren(menuItem))' | 455 speak: '$name $role @@list_with_items($countChildren(menuItem))' |
| 454 }, | 456 }, |
| 455 menuItem: { | 457 menuItem: { |
| 456 speak: '$name $role $if($haspopup, @has_submenu) ' + | 458 speak: '$name $role $if($haspopup, @has_submenu) ' + |
| 457 '@describe_index($indexInParent, $parentChildCount) ' + | 459 '@describe_index($indexInParent, $parentChildCount) ' + |
| 458 '$description' | 460 '$description' |
| 459 }, | 461 }, |
| 460 menuListOption: { | 462 menuListOption: { |
| 461 speak: '$name @role_menuitem ' + | 463 speak: '$name @role_menuitem ' + |
| 462 '@describe_index($indexInParent, $parentChildCount) $description' | 464 '@describe_index($indexInParent, $parentChildCount) $description' |
| 463 }, | 465 }, |
| 464 paragraph: { | 466 paragraph: { |
| 465 speak: '$descendants' | 467 speak: '$descendants' |
| 466 }, | 468 }, |
| 467 popUpButton: { | 469 popUpButton: { |
| 468 speak: '$earcon(POP_UP_BUTTON) $value $name $role @aria_has_popup ' + | 470 speak: '$value $name $role @aria_has_popup ' + |
| 469 '$if($collapsed, @aria_expanded_false, @aria_expanded_true) ' + | 471 '$if($collapsed, @aria_expanded_false, @aria_expanded_true) ' + |
| 470 '$description' | 472 '$description' |
| 471 }, | 473 }, |
| 472 radioButton: { | 474 radioButton: { |
| 473 speak: '$if($checked, @describe_radio_selected($name), ' + | 475 speak: '$if($checked, @describe_radio_selected($name), ' + |
| 474 '@describe_radio_unselected($name)) $description' | 476 '@describe_radio_unselected($name)) $description' |
| 475 }, | 477 }, |
| 476 radioGroup: { | 478 radioGroup: { |
| 477 enter: '$name $role $description' | 479 enter: '$name $role $description' |
| 478 }, | 480 }, |
| (...skipping 437 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 916 else | 918 else |
| 917 this.range_(range, prevRange, type, buff); | 919 this.range_(range, prevRange, type, buff); |
| 918 }, | 920 }, |
| 919 | 921 |
| 920 /** | 922 /** |
| 921 * Format the node given the format specifier. | 923 * Format the node given the format specifier. |
| 922 * @param {AutomationNode} node | 924 * @param {AutomationNode} node |
| 923 * @param {string|!Object} format The output format either specified as an | 925 * @param {string|!Object} format The output format either specified as an |
| 924 * output template string or a parsed output format tree. | 926 * output template string or a parsed output format tree. |
| 925 * @param {!Array<Spannable>} buff Buffer to receive rendered output. | 927 * @param {!Array<Spannable>} buff Buffer to receive rendered output. |
| 928 * @param {!AutomationNode=} opt_prevNode |
| 926 * @private | 929 * @private |
| 927 */ | 930 */ |
| 928 format_: function(node, format, buff) { | 931 format_: function(node, format, buff, opt_prevNode) { |
| 929 var tokens = []; | 932 var tokens = []; |
| 930 var args = null; | 933 var args = null; |
| 931 | 934 |
| 932 // Hacky way to support args. | 935 // Hacky way to support args. |
| 933 if (typeof(format) == 'string') { | 936 if (typeof(format) == 'string') { |
| 934 format = format.replace(/([,:])\W/g, '$1'); | 937 format = format.replace(/([,:])\W/g, '$1'); |
| 935 tokens = format.split(' '); | 938 tokens = format.split(' '); |
| 936 } else { | 939 } else { |
| 937 tokens = [format]; | 940 tokens = [format]; |
| 938 } | 941 } |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 977 if (node.textSelStart !== undefined) { | 980 if (node.textSelStart !== undefined) { |
| 978 options.annotation.push(new Output.SelectionSpan( | 981 options.annotation.push(new Output.SelectionSpan( |
| 979 node.textSelStart, | 982 node.textSelStart, |
| 980 node.textSelEnd)); | 983 node.textSelEnd)); |
| 981 } | 984 } |
| 982 } | 985 } |
| 983 options.annotation.push(token); | 986 options.annotation.push(token); |
| 984 this.append_(buff, text, options); | 987 this.append_(buff, text, options); |
| 985 } else if (token == 'name') { | 988 } else if (token == 'name') { |
| 986 options.annotation.push(token); | 989 options.annotation.push(token); |
| 987 var earcon = node ? this.findEarcon_(node) : null; | 990 var earcon = node ? this.findEarcon_(node, opt_prevNode) : null; |
| 988 if (earcon) | 991 if (earcon) |
| 989 options.annotation.push(earcon); | 992 options.annotation.push(earcon); |
| 990 this.append_(buff, node.name, options); | 993 this.append_(buff, node.name, options); |
| 994 } else if (token == 'nameFromNode') { |
| 995 if (chrome.automation.NameFromType[node.nameFrom] == |
| 996 'nameFromContents') |
| 997 return; |
| 998 |
| 999 options.annotation.push(token); |
| 1000 this.append_(buff, node.name, options); |
| 991 } else if (token == 'nameOrDescendants') { | 1001 } else if (token == 'nameOrDescendants') { |
| 992 options.annotation.push(token); | 1002 options.annotation.push(token); |
| 993 if (node.name) | 1003 if (node.name) |
| 994 this.append_(buff, node.name, options); | 1004 this.append_(buff, node.name, options); |
| 995 else | 1005 else |
| 996 this.format_(node, '$descendants', buff); | 1006 this.format_(node, '$descendants', buff); |
| 997 } else if (token == 'indexInParent') { | 1007 } else if (token == 'indexInParent') { |
| 998 options.annotation.push(token); | 1008 options.annotation.push(token); |
| 999 this.append_(buff, String(node.indexInParent + 1)); | 1009 this.append_(buff, String(node.indexInParent + 1)); |
| 1000 } else if (token == 'parentChildCount') { | 1010 } else if (token == 'parentChildCount') { |
| (...skipping 296 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1297 for (var i = 0, formatPrevNode; | 1307 for (var i = 0, formatPrevNode; |
| 1298 (formatPrevNode = prevUniqueAncestors[i]); | 1308 (formatPrevNode = prevUniqueAncestors[i]); |
| 1299 i++) { | 1309 i++) { |
| 1300 // This prevents very repetitive announcements. | 1310 // This prevents very repetitive announcements. |
| 1301 if (enteredRoleSet[formatPrevNode.role] || | 1311 if (enteredRoleSet[formatPrevNode.role] || |
| 1302 localStorage['useVerboseMode'] == 'false') | 1312 localStorage['useVerboseMode'] == 'false') |
| 1303 continue; | 1313 continue; |
| 1304 | 1314 |
| 1305 var roleBlock = getMergedRoleBlock(formatPrevNode.role); | 1315 var roleBlock = getMergedRoleBlock(formatPrevNode.role); |
| 1306 if (roleBlock.leave && localStorage['useVerboseMode'] == 'true') | 1316 if (roleBlock.leave && localStorage['useVerboseMode'] == 'true') |
| 1307 this.format_(formatPrevNode, roleBlock.leave, buff); | 1317 this.format_(formatPrevNode, roleBlock.leave, buff, prevNode); |
| 1308 } | 1318 } |
| 1309 | 1319 |
| 1310 var enterOutputs = []; | 1320 var enterOutputs = []; |
| 1311 var enterRole = {}; | 1321 var enterRole = {}; |
| 1312 for (var j = uniqueAncestors.length - 2, formatNode; | 1322 for (var j = uniqueAncestors.length - 2, formatNode; |
| 1313 (formatNode = uniqueAncestors[j]); | 1323 (formatNode = uniqueAncestors[j]); |
| 1314 j--) { | 1324 j--) { |
| 1315 var roleBlock = getMergedRoleBlock(formatNode.role); | 1325 var roleBlock = getMergedRoleBlock(formatNode.role); |
| 1316 if (roleBlock.enter) { | 1326 if (roleBlock.enter) { |
| 1317 if (enterRole[formatNode.role]) | 1327 if (enterRole[formatNode.role]) |
| 1318 continue; | 1328 continue; |
| 1319 enterRole[formatNode.role] = true; | 1329 enterRole[formatNode.role] = true; |
| 1320 this.format_(formatNode, roleBlock.enter, buff); | 1330 this.format_(formatNode, roleBlock.enter, buff, prevNode); |
| 1321 } | 1331 } |
| 1322 if (formatNode.role == 'window') | 1332 if (formatNode.role == 'window') |
| 1323 break; | 1333 break; |
| 1324 } | 1334 } |
| 1325 }, | 1335 }, |
| 1326 | 1336 |
| 1327 /** | 1337 /** |
| 1328 * @param {!AutomationNode} node | 1338 * @param {!AutomationNode} node |
| 1329 * @param {!AutomationNode} prevNode | 1339 * @param {!AutomationNode} prevNode |
| 1330 * @param {EventType|Output.EventType} type | 1340 * @param {EventType|Output.EventType} type |
| 1331 * @param {!Array<Spannable>} buff | 1341 * @param {!Array<Spannable>} buff |
| 1332 * @private | 1342 * @private |
| 1333 */ | 1343 */ |
| 1334 node_: function(node, prevNode, type, buff) { | 1344 node_: function(node, prevNode, type, buff) { |
| 1335 // Navigate is the default event. | 1345 // Navigate is the default event. |
| 1336 var eventBlock = Output.RULES[type] || Output.RULES['navigate']; | 1346 var eventBlock = Output.RULES[type] || Output.RULES['navigate']; |
| 1337 var roleBlock = eventBlock[node.role] || {}; | 1347 var roleBlock = eventBlock[node.role] || {}; |
| 1338 var parentRole = (Output.ROLE_INFO_[node.role] || {}).inherits; | 1348 var parentRole = (Output.ROLE_INFO_[node.role] || {}).inherits; |
| 1339 var parentRoleBlock = eventBlock[parentRole || ''] || {}; | 1349 var parentRoleBlock = eventBlock[parentRole || ''] || {}; |
| 1340 var speakFormat = roleBlock.speak || | 1350 var speakFormat = roleBlock.speak || |
| 1341 parentRoleBlock.speak || | 1351 parentRoleBlock.speak || |
| 1342 eventBlock['default'].speak; | 1352 eventBlock['default'].speak; |
| 1343 | 1353 |
| 1344 this.format_(node, speakFormat, buff); | 1354 this.format_(node, speakFormat, buff, prevNode); |
| 1345 }, | 1355 }, |
| 1346 | 1356 |
| 1347 /** | 1357 /** |
| 1348 * @param {!cursors.Range} range | 1358 * @param {!cursors.Range} range |
| 1349 * @param {cursors.Range} prevRange | 1359 * @param {cursors.Range} prevRange |
| 1350 * @param {EventType|Output.EventType} type | 1360 * @param {EventType|Output.EventType} type |
| 1351 * @param {!Array<Spannable>} buff | 1361 * @param {!Array<Spannable>} buff |
| 1352 * @private | 1362 * @private |
| 1353 */ | 1363 */ |
| 1354 subNode_: function(range, prevRange, type, buff) { | 1364 subNode_: function(range, prevRange, type, buff) { |
| (...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1537 return result; | 1547 return result; |
| 1538 }, | 1548 }, |
| 1539 | 1549 |
| 1540 /** | 1550 /** |
| 1541 * Find the earcon for a given node (including ancestry). | 1551 * Find the earcon for a given node (including ancestry). |
| 1542 * @param {!AutomationNode} node | 1552 * @param {!AutomationNode} node |
| 1543 * @param {!AutomationNode=} opt_prevNode | 1553 * @param {!AutomationNode=} opt_prevNode |
| 1544 * @return {Output.Action} | 1554 * @return {Output.Action} |
| 1545 */ | 1555 */ |
| 1546 findEarcon_: function(node, opt_prevNode) { | 1556 findEarcon_: function(node, opt_prevNode) { |
| 1557 if (node === opt_prevNode) |
| 1558 return null; |
| 1559 |
| 1547 if (this.formatOptions_.speech) { | 1560 if (this.formatOptions_.speech) { |
| 1548 var earconFinder = node; | 1561 var earconFinder = node; |
| 1549 var ancestors; | 1562 var ancestors; |
| 1550 if (opt_prevNode) { | 1563 if (opt_prevNode) |
| 1551 // Don't include the node itself. | |
| 1552 ancestors = AutomationUtil.getUniqueAncestors(opt_prevNode, node); | 1564 ancestors = AutomationUtil.getUniqueAncestors(opt_prevNode, node); |
| 1553 ancestors.pop(); | 1565 else |
| 1554 } else { | |
| 1555 ancestors = AutomationUtil.getAncestors(node); | 1566 ancestors = AutomationUtil.getAncestors(node); |
| 1556 } | |
| 1557 | 1567 |
| 1558 while (earconFinder = ancestors.pop()) { | 1568 while (earconFinder = ancestors.pop()) { |
| 1559 var info = Output.ROLE_INFO_[earconFinder.role]; | 1569 var info = Output.ROLE_INFO_[earconFinder.role]; |
| 1560 if (info && info.earconId) { | 1570 if (info && info.earconId) { |
| 1561 return new Output.EarconAction(info.earconId); | 1571 return new Output.EarconAction(info.earconId); |
| 1562 break; | 1572 break; |
| 1563 } | 1573 } |
| 1564 earconFinder = earconFinder.parent; | 1574 earconFinder = earconFinder.parent; |
| 1565 } | 1575 } |
| 1566 } | 1576 } |
| 1567 return null; | 1577 return null; |
| 1568 } | 1578 } |
| 1569 }; | 1579 }; |
| 1570 | 1580 |
| 1571 }); // goog.scope | 1581 }); // goog.scope |
| OLD | NEW |