user_commands.js revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
145afe016bed87b9c6946184709058b39ede3f77ajwong@chromium.org// Copyright 2014 The Chromium Authors. All rights reserved. 245afe016bed87b9c6946184709058b39ede3f77ajwong@chromium.org// Use of this source code is governed by a BSD-style license that can be 345afe016bed87b9c6946184709058b39ede3f77ajwong@chromium.org// found in the LICENSE file. 445afe016bed87b9c6946184709058b39ede3f77ajwong@chromium.org 545afe016bed87b9c6946184709058b39ede3f77ajwong@chromium.org/** 645afe016bed87b9c6946184709058b39ede3f77ajwong@chromium.org * @fileoverview High level commands that the user can invoke using hotkeys. 745afe016bed87b9c6946184709058b39ede3f77ajwong@chromium.org * 845afe016bed87b9c6946184709058b39ede3f77ajwong@chromium.org * Usage: 945afe016bed87b9c6946184709058b39ede3f77ajwong@chromium.org * If you are here, you probably want to add a new user command. Here are some 1045afe016bed87b9c6946184709058b39ede3f77ajwong@chromium.org * general steps to get you started. 1145afe016bed87b9c6946184709058b39ede3f77ajwong@chromium.org * - Go to command_store.js, where all static data about a command lives. Follow 1245afe016bed87b9c6946184709058b39ede3f77ajwong@chromium.org * the instructions there. 1345afe016bed87b9c6946184709058b39ede3f77ajwong@chromium.org * - Add the logic of the command to doCommand_ below. Try to reuse or group 1445afe016bed87b9c6946184709058b39ede3f77ajwong@chromium.org * your command with related commands. 1545afe016bed87b9c6946184709058b39ede3f77ajwong@chromium.org */ 1645afe016bed87b9c6946184709058b39ede3f77ajwong@chromium.org 1745afe016bed87b9c6946184709058b39ede3f77ajwong@chromium.org 1845afe016bed87b9c6946184709058b39ede3f77ajwong@chromium.orggoog.provide('cvox.ChromeVoxUserCommands'); 19 20goog.require('cvox.BrailleKeyCommand'); 21goog.require('cvox.BrailleOverlayWidget'); 22goog.require('cvox.ChromeVox'); 23goog.require('cvox.CommandStore'); 24goog.require('cvox.ConsoleTts'); 25goog.require('cvox.ContextMenuWidget'); 26goog.require('cvox.DomPredicates'); 27goog.require('cvox.DomUtil'); 28goog.require('cvox.FocusUtil'); 29goog.require('cvox.KeyboardHelpWidget'); 30goog.require('cvox.NodeSearchWidget'); 31goog.require('cvox.PlatformUtil'); 32goog.require('cvox.SearchWidget'); 33goog.require('cvox.SelectWidget'); 34goog.require('cvox.TypingEcho'); 35goog.require('cvox.UserEventDetail'); 36goog.require('goog.object'); 37 38 39/** 40 * Initializes commands map. 41 * Initializes global members. 42 * @private 43 */ 44cvox.ChromeVoxUserCommands.init_ = function() { 45 if (cvox.ChromeVoxUserCommands.commands) { 46 return; 47 } else { 48 cvox.ChromeVoxUserCommands.commands = {}; 49 } 50 for (var cmd in cvox.CommandStore.CMD_WHITELIST) { 51 cvox.ChromeVoxUserCommands.commands[cmd] = 52 cvox.ChromeVoxUserCommands.createCommand_(cmd); 53 } 54}; 55 56 57/** 58 * @type {!Object.<string, function(Object=): boolean>} 59 */ 60cvox.ChromeVoxUserCommands.commands; 61 62 63/** 64 * @type {boolean} 65 * TODO (clchen, dmazzoni): Implement syncing on click to avoid needing this. 66 */ 67cvox.ChromeVoxUserCommands.wasMouseClicked = false; 68 69 70/** 71 * @type {boolean} Flag to set whether or not certain user commands will be 72 * first dispatched to the underlying web page. Some commands (such as finding 73 * the next/prev structural element) may be better implemented by the web app 74 * than by ChromeVox. 75 * 76 * By default, this is enabled; however, for testing, we usually disable this to 77 * reduce flakiness caused by event timing issues. 78 * 79 * TODO (clchen, dtseng): Fix testing framework so that we don't need to turn 80 * this feature off at all. 81 */ 82cvox.ChromeVoxUserCommands.enableCommandDispatchingToPage = true; 83 84 85/** 86 * Handles any tab navigation by putting focus at the user's position. 87 * This function will create dummy nodes if there is nothing that is focusable 88 * at the current position. 89 * TODO (adu): This function is too long. We need to break it up into smaller 90 * helper functions. 91 * @return {boolean} True if default action should be taken. 92 * @private 93 */ 94cvox.ChromeVoxUserCommands.handleTabAction_ = function() { 95 cvox.ChromeVox.tts.stop(); 96 97 // If we are tabbing from an invalid location, prevent the default action. 98 // We pass the isFocusable function as a predicate to specify we only want to 99 // revert to focusable nodes. 100 if (!cvox.ChromeVox.navigationManager.resolve(cvox.DomUtil.isFocusable)) { 101 cvox.ChromeVox.navigationManager.setFocus(); 102 return false; 103 } 104 105 // If the user is already focused on a link or control, 106 // nothing more needs to be done. 107 var isLinkControl = cvox.ChromeVoxUserCommands.isFocusedOnLinkControl_(); 108 if (isLinkControl) { 109 return true; 110 } 111 112 // Try to find something reasonable to focus on. 113 // Use selection if it exists because it means that the user has probably 114 // clicked with their mouse and we should respect their position. 115 // If there is no selection, then use the last known position based on 116 // NavigationManager's currentNode. 117 var anchorNode = null; 118 var focusNode = null; 119 var sel = window.getSelection(); 120 if (!cvox.ChromeVoxUserCommands.wasMouseClicked) { 121 sel = null; 122 } else { 123 cvox.ChromeVoxUserCommands.wasMouseClicked = false; 124 } 125 if (sel == null || sel.anchorNode == null || sel.focusNode == null) { 126 anchorNode = cvox.ChromeVox.navigationManager.getCurrentNode(); 127 focusNode = cvox.ChromeVox.navigationManager.getCurrentNode(); 128 } else { 129 anchorNode = sel.anchorNode; 130 focusNode = sel.focusNode; 131 } 132 133 // See if we can set focus to either anchorNode or focusNode. 134 // If not, try the parents. Otherwise give up and create a dummy span. 135 if (anchorNode == null || focusNode == null) { 136 return true; 137 } 138 if (cvox.DomUtil.isFocusable(anchorNode)) { 139 anchorNode.focus(); 140 return true; 141 } 142 if (cvox.DomUtil.isFocusable(focusNode)) { 143 focusNode.focus(); 144 return true; 145 } 146 if (cvox.DomUtil.isFocusable(anchorNode.parentNode)) { 147 anchorNode.parentNode.focus(); 148 return true; 149 } 150 if (cvox.DomUtil.isFocusable(focusNode.parentNode)) { 151 focusNode.parentNode.focus(); 152 return true; 153 } 154 155 // Insert and focus a dummy span immediately before the current position 156 // so that the default tab action will start off as close to the user's 157 // current position as possible. 158 var bestGuess = anchorNode; 159 var dummySpan = cvox.ChromeVoxUserCommands.createTabDummySpan_(); 160 bestGuess.parentNode.insertBefore(dummySpan, bestGuess); 161 dummySpan.focus(); 162 return true; 163}; 164 165 166/** 167 * @return {boolean} True if we are focused on a link or any other control. 168 * @private 169 */ 170cvox.ChromeVoxUserCommands.isFocusedOnLinkControl_ = function() { 171 var tagName = 'A'; 172 if ((document.activeElement.tagName == tagName) || 173 cvox.DomUtil.isControl(document.activeElement)) { 174 return true; 175 } 176 return false; 177}; 178 179 180/** 181 * If a lingering tab dummy span exists, remove it. 182 */ 183cvox.ChromeVoxUserCommands.removeTabDummySpan = function() { 184 // Break the following line to get around a Chromium js linter warning. 185 // TODO(plundblad): Find a better solution. 186 var previousDummySpan = document. 187 getElementById('ChromeVoxTabDummySpan'); 188 if (previousDummySpan && document.activeElement != previousDummySpan) { 189 previousDummySpan.parentNode.removeChild(previousDummySpan); 190 } 191}; 192 193 194/** 195 * Create a new tab dummy span. 196 * @return {Element} The dummy span element to be inserted. 197 * @private 198 */ 199cvox.ChromeVoxUserCommands.createTabDummySpan_ = function() { 200 var span = document.createElement('span'); 201 span.id = 'ChromeVoxTabDummySpan'; 202 span.tabIndex = -1; 203 return span; 204}; 205 206 207/** 208 * @param {string} cmd The programmatic command name. 209 * @return {function(Object=): boolean} The callable command taking an optional 210 * args dictionary. 211 * @private 212 */ 213cvox.ChromeVoxUserCommands.createCommand_ = function(cmd) { 214 return goog.bind(function(opt_kwargs) { 215 var cmdStruct = cvox.ChromeVoxUserCommands.lookupCommand_(cmd, opt_kwargs); 216 return cvox.ChromeVoxUserCommands.dispatchCommand_(cmdStruct); 217 }, cvox.ChromeVoxUserCommands); 218}; 219 220 221/** 222 * @param {Object} cmdStruct The command to do. 223 * @return {boolean} False to prevent the default action. True otherwise. 224 * @private 225 */ 226cvox.ChromeVoxUserCommands.dispatchCommand_ = function(cmdStruct) { 227 if (cvox.Widget.isActive()) { 228 return true; 229 } 230 if (!cvox.PlatformUtil.matchesPlatform(cmdStruct.platformFilter) || 231 (cmdStruct.skipInput && cvox.FocusUtil.isFocusInTextInputField())) { 232 return true; 233 } 234 // Handle dispatching public command events 235 if (cvox.ChromeVoxUserCommands.enableCommandDispatchingToPage && 236 (cvox.UserEventDetail.JUMP_COMMANDS.indexOf(cmdStruct.command) != -1)) { 237 var detail = new cvox.UserEventDetail({command: cmdStruct.command}); 238 var evt = detail.createEventObject(); 239 var currentNode = cvox.ChromeVox.navigationManager.getCurrentNode(); 240 if (!currentNode) { 241 currentNode = document.body; 242 } 243 currentNode.dispatchEvent(evt); 244 return false; 245 } 246 // Not a public command; act on this command directly. 247 return cvox.ChromeVoxUserCommands.doCommand_(cmdStruct); 248}; 249 250 251/** 252 * @param {Object} cmdStruct The command to do. 253 * @return {boolean} False to prevent the default action. True otherwise. 254 * @private 255 */ 256cvox.ChromeVoxUserCommands.doCommand_ = function(cmdStruct) { 257 if (cvox.Widget.isActive()) { 258 return true; 259 } 260 261 if (!cvox.PlatformUtil.matchesPlatform(cmdStruct.platformFilter) || 262 (cmdStruct.skipInput && cvox.FocusUtil.isFocusInTextInputField())) { 263 return true; 264 } 265 266 if (cmdStruct.disallowOOBE && document.URL.match(/^chrome:\/\/oobe/i)) { 267 return true; 268 } 269 270 var cmd = cmdStruct.command; 271 272 if (!cmdStruct.allowEvents) { 273 cvox.ChromeVoxEventSuspender.enterSuspendEvents(); 274 } 275 276 if (cmdStruct.disallowContinuation) { 277 cvox.ChromeVox.navigationManager.stopReading(true); 278 } 279 280 if (cmdStruct.forward) { 281 cvox.ChromeVox.navigationManager.setReversed(false); 282 } else if (cmdStruct.backward) { 283 cvox.ChromeVox.navigationManager.setReversed(true); 284 } 285 286 if (cmdStruct.findNext) { 287 cmd = 'find'; 288 cmdStruct.announce = true; 289 } 290 291 var errorMsg = ''; 292 var prefixMsg = ''; 293 var ret = false; 294 switch (cmd) { 295 case 'handleTab': 296 case 'handleTabPrev': 297 ret = cvox.ChromeVoxUserCommands.handleTabAction_(); 298 break; 299 case 'forward': 300 case 'backward': 301 ret = !cvox.ChromeVox.navigationManager.navigate(); 302 break; 303 case 'right': 304 case 'left': 305 cvox.ChromeVox.navigationManager.subnavigate(); 306 break; 307 case 'find': 308 if (!cmdStruct.findNext) { 309 throw 'Invalid find command.'; 310 } 311 var NodeInfoStruct = 312 cvox.CommandStore.NODE_INFO_MAP[cmdStruct.findNext]; 313 var predicateName = NodeInfoStruct.predicate; 314 var predicate = cvox.DomPredicates[predicateName]; 315 var error = ''; 316 var wrap = ''; 317 if (cmdStruct.forward) { 318 wrap = cvox.ChromeVox.msgs.getMsg('wrapped_to_top'); 319 error = cvox.ChromeVox.msgs.getMsg(NodeInfoStruct.forwardError); 320 } else if (cmdStruct.backward) { 321 wrap = cvox.ChromeVox.msgs.getMsg('wrapped_to_bottom'); 322 error = cvox.ChromeVox.msgs.getMsg(NodeInfoStruct.backwardError); 323 } 324 var found = null; 325 var status = cmdStruct.status || cvox.UserEventDetail.Status.PENDING; 326 var resultNode = cmdStruct.resultNode || null; 327 switch (status) { 328 case cvox.UserEventDetail.Status.SUCCESS: 329 if (resultNode) { 330 cvox.ChromeVox.navigationManager.updateSelToArbitraryNode( 331 resultNode, true); 332 } 333 break; 334 case cvox.UserEventDetail.Status.FAILURE: 335 prefixMsg = error; 336 break; 337 default: 338 found = cvox.ChromeVox.navigationManager.findNext( 339 predicate, predicateName); 340 if (!found) { 341 cvox.ChromeVox.navigationManager.saveSel(); 342 prefixMsg = wrap; 343 cvox.ChromeVox.navigationManager.syncToBeginning(); 344 cvox.ChromeVox.earcons.playEarcon(cvox.AbstractEarcons.WRAP); 345 found = cvox.ChromeVox.navigationManager.findNext( 346 predicate, predicateName, true); 347 if (!found) { 348 prefixMsg = error; 349 cvox.ChromeVox.navigationManager.restoreSel(); 350 } 351 } 352 break; 353 } 354 // NavigationManager performs announcement inside of frames when finding. 355 if (found && found.start.node.tagName == 'IFRAME') { 356 cmdStruct.announce = false; 357 } 358 break; 359 // TODO(stoarca): Bad naming. Should be less instead of previous. 360 case 'previousGranularity': 361 cvox.ChromeVox.navigationManager.makeLessGranular(true); 362 prefixMsg = cvox.ChromeVox.navigationManager.getGranularityMsg(); 363 break; 364 case 'nextGranularity': 365 cvox.ChromeVox.navigationManager.makeMoreGranular(true); 366 prefixMsg = cvox.ChromeVox.navigationManager.getGranularityMsg(); 367 break; 368 369 case 'previousCharacter': 370 cvox.ChromeVox.navigationManager.navigate(false, 371 cvox.NavigationShifter.GRANULARITIES.CHARACTER); 372 break; 373 case 'nextCharacter': 374 cvox.ChromeVox.navigationManager.navigate(false, 375 cvox.NavigationShifter.GRANULARITIES.CHARACTER); 376 break; 377 378 case 'previousWord': 379 cvox.ChromeVox.navigationManager.navigate(false, 380 cvox.NavigationShifter.GRANULARITIES.WORD); 381 break; 382 case 'nextWord': 383 cvox.ChromeVox.navigationManager.navigate(false, 384 cvox.NavigationShifter.GRANULARITIES.WORD); 385 break; 386 387 case 'previousSentence': 388 cvox.ChromeVox.navigationManager.navigate(false, 389 cvox.NavigationShifter.GRANULARITIES.SENTENCE); 390 break; 391 case 'nextSentence': 392 cvox.ChromeVox.navigationManager.navigate(false, 393 cvox.NavigationShifter.GRANULARITIES.SENTENCE); 394 break; 395 396 case 'previousLine': 397 cvox.ChromeVox.navigationManager.navigate(false, 398 cvox.NavigationShifter.GRANULARITIES.LINE); 399 break; 400 case 'nextLine': 401 cvox.ChromeVox.navigationManager.navigate(false, 402 cvox.NavigationShifter.GRANULARITIES.LINE); 403 break; 404 405 case 'previousObject': 406 cvox.ChromeVox.navigationManager.navigate(false, 407 cvox.NavigationShifter.GRANULARITIES.OBJECT); 408 break; 409 case 'nextObject': 410 cvox.ChromeVox.navigationManager.navigate(false, 411 cvox.NavigationShifter.GRANULARITIES.OBJECT); 412 break; 413 414 case 'previousGroup': 415 cvox.ChromeVox.navigationManager.navigate(false, 416 cvox.NavigationShifter.GRANULARITIES.GROUP); 417 break; 418 case 'nextGroup': 419 cvox.ChromeVox.navigationManager.navigate(false, 420 cvox.NavigationShifter.GRANULARITIES.GROUP); 421 break; 422 423 case 'previousRow': 424 case 'previousCol': 425 // Fold these commands to their "next" equivalents since we already set 426 // isReversed above. 427 cmd = cmd == 'previousRow' ? 'nextRow' : 'nextCol'; 428 case 'nextRow': 429 case 'nextCol': 430 cvox.ChromeVox.navigationManager.performAction('enterShifterSilently'); 431 cvox.ChromeVox.navigationManager.performAction(cmd); 432 break; 433 434 case 'moveToStartOfLine': 435 case 'moveToEndOfLine': 436 cvox.ChromeVox.navigationManager.setGranularity( 437 cvox.NavigationShifter.GRANULARITIES.LINE); 438 cvox.ChromeVox.navigationManager.sync(); 439 cvox.ChromeVox.navigationManager.collapseSelection(); 440 break; 441 442 case 'readFromHere': 443 cvox.ChromeVox.navigationManager.setGranularity( 444 cvox.NavigationShifter.GRANULARITIES.OBJECT, true, true); 445 cvox.ChromeVox.navigationManager.startReading( 446 cvox.AbstractTts.QUEUE_MODE_FLUSH); 447 break; 448 case 'cycleTypingEcho': 449 cvox.ChromeVox.host.sendToBackgroundPage({ 450 'target': 'Prefs', 451 'action': 'setPref', 452 'pref': 'typingEcho', 453 'value': cvox.TypingEcho.cycle(cvox.ChromeVox.typingEcho), 454 'announce': true 455 }); 456 break; 457 case 'jumpToTop': 458 case cvox.BrailleKeyCommand.TOP: 459 cvox.ChromeVox.navigationManager.syncToBeginning(); 460 break; 461 case 'jumpToBottom': 462 case cvox.BrailleKeyCommand.BOTTOM: 463 cvox.ChromeVox.navigationManager.syncToBeginning(); 464 break; 465 case 'stopSpeech': 466 cvox.ChromeVox.navigationManager.stopReading(true); 467 break; 468 case 'toggleKeyboardHelp': 469 cvox.KeyboardHelpWidget.getInstance().toggle(); 470 break; 471 case 'help': 472 cvox.ChromeVox.tts.stop(); 473 cvox.ChromeVox.host.sendToBackgroundPage({ 474 'target': 'HelpDocs', 475 'action': 'open' 476 }); 477 break; 478 case 'contextMenu': 479 // Move this logic to a central dispatching class if it grows any bigger. 480 var node = cvox.ChromeVox.navigationManager.getCurrentNode(); 481 if (node.tagName == 'SELECT' && !node.multiple) { 482 new cvox.SelectWidget(node).show(); 483 } else { 484 var contextMenuWidget = new cvox.ContextMenuWidget(); 485 contextMenuWidget.toggle(); 486 } 487 break; 488 case 'showBookmarkManager': 489 // TODO(stoarca): Should this have tts.stop()?? 490 cvox.ChromeVox.host.sendToBackgroundPage({ 491 'target': 'BookmarkManager', 492 'action': 'open' 493 }); 494 break; 495 case 'showOptionsPage': 496 cvox.ChromeVox.tts.stop(); 497 cvox.ChromeVox.host.sendToBackgroundPage({ 498 'target': 'Options', 499 'action': 'open' 500 }); 501 break; 502 case 'showKbExplorerPage': 503 cvox.ChromeVox.tts.stop(); 504 cvox.ChromeVox.host.sendToBackgroundPage({ 505 'target': 'KbExplorer', 506 'action': 'open' 507 }); 508 break; 509 case 'readLinkURL': 510 var activeElement = document.activeElement; 511 var currentSelectionAnchor = window.getSelection().anchorNode; 512 513 var url = ''; 514 if (activeElement.tagName == 'A') { 515 url = cvox.DomUtil.getLinkURL(activeElement); 516 } else if (currentSelectionAnchor) { 517 url = cvox.DomUtil.getLinkURL(currentSelectionAnchor.parentNode); 518 } 519 520 if (url != '') { 521 cvox.ChromeVox.tts.speak(url); 522 } else { 523 cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg('no_url_found')); 524 } 525 break; 526 case 'readCurrentTitle': 527 cvox.ChromeVox.tts.speak(document.title); 528 break; 529 case 'readCurrentURL': 530 cvox.ChromeVox.tts.speak(document.URL); 531 break; 532 case 'performDefaultAction': 533 if (cvox.DomPredicates.linkPredicate([document.activeElement])) { 534 if (cvox.DomUtil.isInternalLink(document.activeElement)) { 535 // First, sync our selection to the destination of the internal link. 536 cvox.DomUtil.syncInternalLink(document.activeElement); 537 // Now, sync our selection based on the current granularity. 538 cvox.ChromeVox.navigationManager.sync(); 539 // Announce this new selection. 540 cmdStruct.announce = true; 541 } 542 } 543 break; 544 case 'forceClickOnCurrentItem': 545 prefixMsg = cvox.ChromeVox.msgs.getMsg('element_clicked'); 546 var targetNode = cvox.ChromeVox.navigationManager.getCurrentNode(); 547 cvox.DomUtil.clickElem(targetNode, false, false); 548 break; 549 case 'forceDoubleClickOnCurrentItem': 550 prefixMsg = cvox.ChromeVox.msgs.getMsg('element_double_clicked'); 551 var targetNode = cvox.ChromeVox.navigationManager.getCurrentNode(); 552 cvox.DomUtil.clickElem(targetNode, false, false, true); 553 break; 554 case 'toggleChromeVox': 555 cvox.ChromeVox.host.sendToBackgroundPage({ 556 'target': 'Prefs', 557 'action': 'setPref', 558 'pref': 'active', 559 'value': !cvox.ChromeVox.isActive 560 }); 561 break; 562 case 'fullyDescribe': 563 var descs = cvox.ChromeVox.navigationManager.getFullDescription(); 564 cvox.ChromeVox.navigationManager.speakDescriptionArray( 565 descs, 566 cvox.AbstractTts.QUEUE_MODE_FLUSH, 567 null); 568 break; 569 case 'speakTimeAndDate': 570 var dateTime = new Date(); 571 cvox.ChromeVox.tts.speak( 572 dateTime.toLocaleTimeString() + ', ' + dateTime.toLocaleDateString()); 573 break; 574 case 'toggleSelection': 575 var selState = cvox.ChromeVox.navigationManager.togglePageSel(); 576 prefixMsg = cvox.ChromeVox.msgs.getMsg( 577 selState ? 'begin_selection' : 'end_selection'); 578 break; 579 case 'startHistoryRecording': 580 cvox.History.getInstance().startRecording(); 581 break; 582 case 'stopHistoryRecording': 583 cvox.History.getInstance().stopRecording(); 584 break; 585 case 'enableConsoleTts': 586 cvox.ConsoleTts.getInstance().setEnabled(true); 587 break; 588 case 'toggleBrailleCaptions': 589 cvox.ChromeVox.host.sendToBackgroundPage({ 590 'target': 'Prefs', 591 'action': 'setPref', 592 'pref': 'brailleCaptions', 593 'value': !cvox.BrailleOverlayWidget.getInstance().isActive() 594 }); 595 break; 596 597 // Table actions. 598 case 'goToFirstCell': 599 case 'goToLastCell': 600 case 'goToRowFirstCell': 601 case 'goToRowLastCell': 602 case 'goToColFirstCell': 603 case 'goToColLastCell': 604 case 'announceHeaders': 605 case 'speakTableLocation': 606 case 'exitShifterContent': 607 if (!cvox.DomPredicates.tablePredicate(cvox.DomUtil.getAncestors( 608 cvox.ChromeVox.navigationManager.getCurrentNode())) || 609 !cvox.ChromeVox.navigationManager.performAction(cmd)) { 610 errorMsg = 'not_inside_table'; 611 } 612 break; 613 614 // Generic actions. 615 case 'enterShifter': 616 case 'exitShifter': 617 cvox.ChromeVox.navigationManager.performAction(cmd); 618 break; 619 // TODO(stoarca): Code repetition. 620 case 'decreaseTtsRate': 621 // TODO(stoarca): This function name is way too long. 622 cvox.ChromeVox.tts.increaseOrDecreaseProperty( 623 cvox.AbstractTts.RATE, false); 624 break; 625 case 'increaseTtsRate': 626 cvox.ChromeVox.tts.increaseOrDecreaseProperty( 627 cvox.AbstractTts.RATE, true); 628 break; 629 case 'decreaseTtsPitch': 630 cvox.ChromeVox.tts.increaseOrDecreaseProperty( 631 cvox.AbstractTts.PITCH, false); 632 break; 633 case 'increaseTtsPitch': 634 cvox.ChromeVox.tts.increaseOrDecreaseProperty( 635 cvox.AbstractTts.PITCH, true); 636 break; 637 case 'decreaseTtsVolume': 638 cvox.ChromeVox.tts.increaseOrDecreaseProperty( 639 cvox.AbstractTts.VOLUME, false); 640 break; 641 case 'increaseTtsVolume': 642 cvox.ChromeVox.tts.increaseOrDecreaseProperty( 643 cvox.AbstractTts.VOLUME, true); 644 break; 645 case 'cyclePunctuationEcho': 646 cvox.ChromeVox.host.sendToBackgroundPage({ 647 'target': 'TTS', 648 'action': 'cyclePunctuationEcho' 649 }); 650 break; 651 652 case 'toggleStickyMode': 653 cvox.ChromeVox.host.sendToBackgroundPage({ 654 'target': 'Prefs', 655 'action': 'setPref', 656 'pref': 'sticky', 657 'value': !cvox.ChromeVox.isStickyPrefOn, 658 'announce': true 659 }); 660 break; 661 case 'toggleKeyPrefix': 662 cvox.ChromeVox.keyPrefixOn = !cvox.ChromeVox.keyPrefixOn; 663 break; 664 case 'passThroughMode': 665 cvox.ChromeVox.passThroughMode = true; 666 cvox.ChromeVox.tts.speak( 667 cvox.ChromeVox.msgs.getMsg('pass_through_key')); 668 break; 669 case 'toggleSearchWidget': 670 cvox.SearchWidget.getInstance().toggle(); 671 break; 672 673 case 'toggleEarcons': 674 prefixMsg = cvox.ChromeVox.earcons.toggle() ? 675 cvox.ChromeVox.msgs.getMsg('earcons_on') : 676 cvox.ChromeVox.msgs.getMsg('earcons_off'); 677 break; 678 679 case 'showHeadingsList': 680 case 'showLinksList': 681 case 'showFormsList': 682 case 'showTablesList': 683 case 'showLandmarksList': 684 if (!cmdStruct.nodeList) { 685 break; 686 } 687 var nodeListStruct = 688 cvox.CommandStore.NODE_INFO_MAP[cmdStruct.nodeList]; 689 690 cvox.NodeSearchWidget.create(nodeListStruct.typeMsg, 691 cvox.DomPredicates[nodeListStruct.predicate]).show(); 692 break; 693 694 case 'openLongDesc': 695 var currentNode = cvox.ChromeVox.navigationManager.getCurrentNode(); 696 if (cvox.DomUtil.hasLongDesc(currentNode)) { 697 cvox.ChromeVox.host.sendToBackgroundPage({ 698 'target': 'OpenTab', 699 'url': currentNode.longDesc // Use .longDesc instead of getAttribute 700 // since we want Chrome to convert the 701 // longDesc to an absolute URL. 702 }); 703 } else { 704 cvox.ChromeVox.tts.speak( 705 cvox.ChromeVox.msgs.getMsg('no_long_desc'), 706 cvox.AbstractTts.QUEUE_MODE_FLUSH, 707 cvox.AbstractTts.PERSONALITY_ANNOTATION); 708 } 709 break; 710 711 case 'pauseAllMedia': 712 var videos = document.getElementsByTagName('VIDEO'); 713 for (var i = 0, mediaElem; mediaElem = videos[i]; i++) { 714 mediaElem.pause(); 715 } 716 var audios = document.getElementsByTagName('AUDIO'); 717 for (var i = 0, mediaElem; mediaElem = audios[i]; i++) { 718 mediaElem.pause(); 719 } 720 break; 721 722 // Math specific commands. 723 case 'toggleSemantics': 724 if (cvox.TraverseMath.toggleSemantic()) { 725 cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg('semantics_on')); 726 } else { 727 cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg('semantics_off')); 728 } 729 break; 730 731 // Braille specific commands. 732 case cvox.BrailleKeyCommand.ROUTING: 733 var braille = cmdStruct.content; 734 if (braille) { 735 cvox.BrailleUtil.click(braille, cmdStruct.event.displayPosition); 736 } 737 break; 738 case cvox.BrailleKeyCommand.PAN_LEFT: 739 case cvox.BrailleKeyCommand.LINE_UP: 740 case cvox.BrailleKeyCommand.PAN_RIGHT: 741 case cvox.BrailleKeyCommand.LINE_DOWN: 742 // TODO(dtseng, plundblad): This needs to sync to the last pan position 743 // after line up/pan left and move the display to the far right on the 744 // line in case the synced to node is longer than one display line. 745 // Should also work with all widgets. 746 cvox.ChromeVox.navigationManager.navigate(false, 747 cvox.NavigationShifter.GRANULARITIES.LINE); 748 break; 749 750 case 'debug': 751 // TODO(stoarca): This doesn't belong here. 752 break; 753 754 case 'nop': 755 break; 756 default: 757 throw 'Command behavior not defined: ' + cmd; 758 } 759 760 if (errorMsg != '') { 761 cvox.ChromeVox.tts.speak( 762 cvox.ChromeVox.msgs.getMsg(errorMsg), 763 cvox.AbstractTts.QUEUE_MODE_FLUSH, 764 cvox.AbstractTts.PERSONALITY_ANNOTATION); 765 } else if (cvox.ChromeVox.navigationManager.isReading()) { 766 if (cmdStruct.disallowContinuation) { 767 cvox.ChromeVox.navigationManager.stopReading(true); 768 } else if (cmd != 'readFromHere') { 769 cvox.ChromeVox.navigationManager.skip(); 770 } 771 } else { 772 if (cmdStruct.announce) { 773 cvox.ChromeVox.navigationManager.finishNavCommand(prefixMsg); 774 } 775 } 776 if (!cmdStruct.allowEvents) { 777 cvox.ChromeVoxEventSuspender.exitSuspendEvents(); 778 } 779 return !!cmdStruct.doDefault || ret; 780}; 781 782 783/** 784 * Default handler for public user commands that are dispatched to the web app 785 * first so that the web developer can handle these commands instead of 786 * ChromeVox if they decide they can do a better job than the default algorithm. 787 * 788 * @param {Object} cvoxUserEvent The cvoxUserEvent to handle. 789 */ 790cvox.ChromeVoxUserCommands.handleChromeVoxUserEvent = function(cvoxUserEvent) { 791 var detail = new cvox.UserEventDetail(cvoxUserEvent.detail); 792 if (detail.command) { 793 cvox.ChromeVoxUserCommands.doCommand_( 794 cvox.ChromeVoxUserCommands.lookupCommand_(detail.command, detail)); 795 } 796}; 797 798 799/** 800 * Returns an object containing information about the given command. 801 * @param {string} cmd The name of the command. 802 * @param {Object=} opt_kwargs Optional key values to add to the command 803 * structure. 804 * @return {Object} A key value mapping. 805 * @private 806 */ 807cvox.ChromeVoxUserCommands.lookupCommand_ = function(cmd, opt_kwargs) { 808 var cmdStruct = cvox.CommandStore.CMD_WHITELIST[cmd]; 809 if (!cmdStruct) { 810 throw 'Invalid command: ' + cmd; 811 } 812 cmdStruct = goog.object.clone(cmdStruct); 813 cmdStruct.command = cmd; 814 if (opt_kwargs) { 815 goog.object.extend(cmdStruct, opt_kwargs); 816 } 817 return cmdStruct; 818}; 819 820 821cvox.ChromeVoxUserCommands.init_(); 822