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
3 // found in the LICENSE file.
6 * @fileoverview High level commands that the user can invoke using hotkeys.
9 * If you are here, you probably want to add a new user command. Here are some
10 * general steps to get you started.
11 * - Go to command_store.js, where all static data about a command lives. Follow
12 * the instructions there.
13 * - Add the logic of the command to doCommand_ below. Try to reuse or group
14 * your command with related commands.
18 goog.provide('cvox.ChromeVoxUserCommands');
20 goog.require('cvox.BrailleKeyCommand');
21 goog.require('cvox.BrailleOverlayWidget');
22 goog.require('cvox.ChromeVox');
23 goog.require('cvox.CommandStore');
24 goog.require('cvox.ConsoleTts');
25 goog.require('cvox.ContextMenuWidget');
26 goog.require('cvox.DomPredicates');
27 goog.require('cvox.DomUtil');
28 goog.require('cvox.FocusUtil');
29 goog.require('cvox.KeyboardHelpWidget');
30 goog.require('cvox.NodeSearchWidget');
31 goog.require('cvox.PlatformUtil');
32 goog.require('cvox.SearchWidget');
33 goog.require('cvox.SelectWidget');
34 goog.require('cvox.TypingEcho');
35 goog.require('cvox.UserEventDetail');
36 goog.require('goog.object');
40 * Initializes commands map.
41 * Initializes global members.
44 cvox.ChromeVoxUserCommands.init_ = function() {
45 if (cvox.ChromeVoxUserCommands.commands) {
48 cvox.ChromeVoxUserCommands.commands = {};
50 for (var cmd in cvox.CommandStore.CMD_WHITELIST) {
51 cvox.ChromeVoxUserCommands.commands[cmd] =
52 cvox.ChromeVoxUserCommands.createCommand_(cmd);
58 * @type {!Object.<string, function(Object=): boolean>}
60 cvox.ChromeVoxUserCommands.commands;
65 * TODO (clchen, dmazzoni): Implement syncing on click to avoid needing this.
67 cvox.ChromeVoxUserCommands.wasMouseClicked = false;
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
76 * By default, this is enabled; however, for testing, we usually disable this to
77 * reduce flakiness caused by event timing issues.
79 * TODO (clchen, dtseng): Fix testing framework so that we don't need to turn
80 * this feature off at all.
82 cvox.ChromeVoxUserCommands.enableCommandDispatchingToPage = true;
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
91 * @return {boolean} True if default action should be taken.
94 cvox.ChromeVoxUserCommands.handleTabAction_ = function() {
95 cvox.ChromeVox.tts.stop();
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();
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_();
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) {
123 cvox.ChromeVoxUserCommands.wasMouseClicked = false;
125 if (sel == null || sel.anchorNode == null || sel.focusNode == null) {
126 anchorNode = cvox.ChromeVox.navigationManager.getCurrentNode();
127 focusNode = cvox.ChromeVox.navigationManager.getCurrentNode();
129 anchorNode = sel.anchorNode;
130 focusNode = sel.focusNode;
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) {
138 if (cvox.DomUtil.isFocusable(anchorNode)) {
142 if (cvox.DomUtil.isFocusable(focusNode)) {
146 if (cvox.DomUtil.isFocusable(anchorNode.parentNode)) {
147 anchorNode.parentNode.focus();
150 if (cvox.DomUtil.isFocusable(focusNode.parentNode)) {
151 focusNode.parentNode.focus();
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);
167 * @return {boolean} True if we are focused on a link or any other control.
170 cvox.ChromeVoxUserCommands.isFocusedOnLinkControl_ = function() {
172 if ((document.activeElement.tagName == tagName) ||
173 cvox.DomUtil.isControl(document.activeElement)) {
181 * If a lingering tab dummy span exists, remove it.
183 cvox.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);
195 * Create a new tab dummy span.
196 * @return {Element} The dummy span element to be inserted.
199 cvox.ChromeVoxUserCommands.createTabDummySpan_ = function() {
200 var span = document.createElement('span');
201 span.id = 'ChromeVoxTabDummySpan';
208 * @param {string} cmd The programmatic command name.
209 * @return {function(Object=): boolean} The callable command taking an optional
213 cvox.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);
222 * @param {Object} cmdStruct The command to do.
223 * @return {boolean} False to prevent the default action. True otherwise.
226 cvox.ChromeVoxUserCommands.dispatchCommand_ = function(cmdStruct) {
227 if (cvox.Widget.isActive()) {
230 if (!cvox.PlatformUtil.matchesPlatform(cmdStruct.platformFilter) ||
231 (cmdStruct.skipInput && cvox.FocusUtil.isFocusInTextInputField())) {
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();
241 currentNode = document.body;
243 currentNode.dispatchEvent(evt);
246 // Not a public command; act on this command directly.
247 return cvox.ChromeVoxUserCommands.doCommand_(cmdStruct);
252 * @param {Object} cmdStruct The command to do.
253 * @return {boolean} False to prevent the default action. True otherwise.
256 cvox.ChromeVoxUserCommands.doCommand_ = function(cmdStruct) {
257 if (cvox.Widget.isActive()) {
261 if (!cvox.PlatformUtil.matchesPlatform(cmdStruct.platformFilter) ||
262 (cmdStruct.skipInput && cvox.FocusUtil.isFocusInTextInputField())) {
266 if (cmdStruct.disallowOOBE && document.URL.match(/^chrome:\/\/oobe/i)) {
270 var cmd = cmdStruct.command;
272 if (!cmdStruct.allowEvents) {
273 cvox.ChromeVoxEventSuspender.enterSuspendEvents();
276 if (cmdStruct.disallowContinuation) {
277 cvox.ChromeVox.navigationManager.stopReading(true);
280 if (cmdStruct.forward) {
281 cvox.ChromeVox.navigationManager.setReversed(false);
282 } else if (cmdStruct.backward) {
283 cvox.ChromeVox.navigationManager.setReversed(true);
286 if (cmdStruct.findNext) {
288 cmdStruct.announce = true;
296 case 'handleTabPrev':
297 ret = cvox.ChromeVoxUserCommands.handleTabAction_();
301 ret = !cvox.ChromeVox.navigationManager.navigate();
305 cvox.ChromeVox.navigationManager.subnavigate();
308 if (!cmdStruct.findNext) {
309 throw 'Invalid find command.';
312 cvox.CommandStore.NODE_INFO_MAP[cmdStruct.findNext];
313 var predicateName = NodeInfoStruct.predicate;
314 var predicate = cvox.DomPredicates[predicateName];
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);
325 var status = cmdStruct.status || cvox.UserEventDetail.Status.PENDING;
326 var resultNode = cmdStruct.resultNode || null;
328 case cvox.UserEventDetail.Status.SUCCESS:
330 cvox.ChromeVox.navigationManager.updateSelToArbitraryNode(
334 case cvox.UserEventDetail.Status.FAILURE:
338 found = cvox.ChromeVox.navigationManager.findNext(
339 predicate, predicateName);
341 cvox.ChromeVox.navigationManager.saveSel();
343 cvox.ChromeVox.navigationManager.syncToBeginning();
344 cvox.ChromeVox.earcons.playEarcon(cvox.AbstractEarcons.WRAP);
345 found = cvox.ChromeVox.navigationManager.findNext(
346 predicate, predicateName, true);
349 cvox.ChromeVox.navigationManager.restoreSel();
354 // NavigationManager performs announcement inside of frames when finding.
355 if (found && found.start.node.tagName == 'IFRAME') {
356 cmdStruct.announce = false;
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();
364 case 'nextGranularity':
365 cvox.ChromeVox.navigationManager.makeMoreGranular(true);
366 prefixMsg = cvox.ChromeVox.navigationManager.getGranularityMsg();
369 case 'previousCharacter':
370 cvox.ChromeVox.navigationManager.navigate(false,
371 cvox.NavigationShifter.GRANULARITIES.CHARACTER);
373 case 'nextCharacter':
374 cvox.ChromeVox.navigationManager.navigate(false,
375 cvox.NavigationShifter.GRANULARITIES.CHARACTER);
379 cvox.ChromeVox.navigationManager.navigate(false,
380 cvox.NavigationShifter.GRANULARITIES.WORD);
383 cvox.ChromeVox.navigationManager.navigate(false,
384 cvox.NavigationShifter.GRANULARITIES.WORD);
387 case 'previousSentence':
388 cvox.ChromeVox.navigationManager.navigate(false,
389 cvox.NavigationShifter.GRANULARITIES.SENTENCE);
392 cvox.ChromeVox.navigationManager.navigate(false,
393 cvox.NavigationShifter.GRANULARITIES.SENTENCE);
397 cvox.ChromeVox.navigationManager.navigate(false,
398 cvox.NavigationShifter.GRANULARITIES.LINE);
401 cvox.ChromeVox.navigationManager.navigate(false,
402 cvox.NavigationShifter.GRANULARITIES.LINE);
405 case 'previousObject':
406 cvox.ChromeVox.navigationManager.navigate(false,
407 cvox.NavigationShifter.GRANULARITIES.OBJECT);
410 cvox.ChromeVox.navigationManager.navigate(false,
411 cvox.NavigationShifter.GRANULARITIES.OBJECT);
414 case 'previousGroup':
415 cvox.ChromeVox.navigationManager.navigate(false,
416 cvox.NavigationShifter.GRANULARITIES.GROUP);
419 cvox.ChromeVox.navigationManager.navigate(false,
420 cvox.NavigationShifter.GRANULARITIES.GROUP);
425 // Fold these commands to their "next" equivalents since we already set
427 cmd = cmd == 'previousRow' ? 'nextRow' : 'nextCol';
430 cvox.ChromeVox.navigationManager.performAction('enterShifterSilently');
431 cvox.ChromeVox.navigationManager.performAction(cmd);
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();
443 cvox.ChromeVox.navigationManager.setGranularity(
444 cvox.NavigationShifter.GRANULARITIES.OBJECT, true, true);
445 cvox.ChromeVox.navigationManager.startReading(
446 cvox.QueueMode.FLUSH);
448 case 'cycleTypingEcho':
449 cvox.ChromeVox.host.sendToBackgroundPage({
452 'pref': 'typingEcho',
453 'value': cvox.TypingEcho.cycle(cvox.ChromeVox.typingEcho),
458 case cvox.BrailleKeyCommand.TOP:
459 cvox.ChromeVox.navigationManager.syncToBeginning();
462 case cvox.BrailleKeyCommand.BOTTOM:
463 cvox.ChromeVox.navigationManager.syncToBeginning();
466 cvox.ChromeVox.navigationManager.stopReading(true);
468 case 'toggleKeyboardHelp':
469 cvox.KeyboardHelpWidget.getInstance().toggle();
472 cvox.ChromeVox.tts.stop();
473 cvox.ChromeVox.host.sendToBackgroundPage({
474 'target': 'HelpDocs',
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();
484 var contextMenuWidget = new cvox.ContextMenuWidget();
485 contextMenuWidget.toggle();
488 case 'showBookmarkManager':
489 // TODO(stoarca): Should this have tts.stop()??
490 cvox.ChromeVox.host.sendToBackgroundPage({
491 'target': 'BookmarkManager',
495 case 'showOptionsPage':
496 cvox.ChromeVox.tts.stop();
497 cvox.ChromeVox.host.sendToBackgroundPage({
502 case 'showKbExplorerPage':
503 cvox.ChromeVox.tts.stop();
504 cvox.ChromeVox.host.sendToBackgroundPage({
505 'target': 'KbExplorer',
510 var activeElement = document.activeElement;
511 var currentSelectionAnchor = window.getSelection().anchorNode;
514 if (activeElement.tagName == 'A') {
515 url = cvox.DomUtil.getLinkURL(activeElement);
516 } else if (currentSelectionAnchor) {
517 url = cvox.DomUtil.getLinkURL(currentSelectionAnchor.parentNode);
521 cvox.ChromeVox.tts.speak(url, cvox.QueueMode.QUEUE);
523 cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg('no_url_found'),
524 cvox.QueueMode.QUEUE);
527 case 'readCurrentTitle':
528 cvox.ChromeVox.tts.speak(document.title, cvox.QueueMode.QUEUE);
530 case 'readCurrentURL':
531 cvox.ChromeVox.tts.speak(document.URL, cvox.QueueMode.QUEUE);
533 case 'performDefaultAction':
534 if (cvox.DomPredicates.linkPredicate([document.activeElement])) {
535 if (cvox.DomUtil.isInternalLink(document.activeElement)) {
536 // First, sync our selection to the destination of the internal link.
537 cvox.DomUtil.syncInternalLink(document.activeElement);
538 // Now, sync our selection based on the current granularity.
539 cvox.ChromeVox.navigationManager.sync();
540 // Announce this new selection.
541 cmdStruct.announce = true;
545 case 'forceClickOnCurrentItem':
546 prefixMsg = cvox.ChromeVox.msgs.getMsg('element_clicked');
547 var targetNode = cvox.ChromeVox.navigationManager.getCurrentNode();
548 cvox.DomUtil.clickElem(targetNode, false, false);
550 case 'forceDoubleClickOnCurrentItem':
551 prefixMsg = cvox.ChromeVox.msgs.getMsg('element_double_clicked');
552 var targetNode = cvox.ChromeVox.navigationManager.getCurrentNode();
553 cvox.DomUtil.clickElem(targetNode, false, false, true);
555 case 'toggleChromeVox':
556 cvox.ChromeVox.host.sendToBackgroundPage({
560 'value': !cvox.ChromeVox.isActive
563 case 'fullyDescribe':
564 var descs = cvox.ChromeVox.navigationManager.getFullDescription();
565 cvox.ChromeVox.navigationManager.speakDescriptionArray(
567 cvox.QueueMode.FLUSH,
570 case 'speakTimeAndDate':
571 var dateTime = new Date();
572 cvox.ChromeVox.tts.speak(
573 dateTime.toLocaleTimeString() + ', ' + dateTime.toLocaleDateString(),
574 cvox.QueueMode.QUEUE);
576 case 'toggleSelection':
577 var selState = cvox.ChromeVox.navigationManager.togglePageSel();
578 prefixMsg = cvox.ChromeVox.msgs.getMsg(
579 selState ? 'begin_selection' : 'end_selection');
581 case 'startHistoryRecording':
582 cvox.History.getInstance().startRecording();
584 case 'stopHistoryRecording':
585 cvox.History.getInstance().stopRecording();
587 case 'enableConsoleTts':
588 cvox.ConsoleTts.getInstance().setEnabled(true);
590 case 'toggleBrailleCaptions':
591 cvox.ChromeVox.host.sendToBackgroundPage({
594 'pref': 'brailleCaptions',
595 'value': !cvox.BrailleOverlayWidget.getInstance().isActive()
600 case 'goToFirstCell':
602 case 'goToRowFirstCell':
603 case 'goToRowLastCell':
604 case 'goToColFirstCell':
605 case 'goToColLastCell':
606 case 'announceHeaders':
607 case 'speakTableLocation':
608 case 'exitShifterContent':
609 if (!cvox.DomPredicates.tablePredicate(cvox.DomUtil.getAncestors(
610 cvox.ChromeVox.navigationManager.getCurrentNode())) ||
611 !cvox.ChromeVox.navigationManager.performAction(cmd)) {
612 errorMsg = 'not_inside_table';
619 cvox.ChromeVox.navigationManager.performAction(cmd);
621 // TODO(stoarca): Code repetition.
622 case 'decreaseTtsRate':
623 // TODO(stoarca): This function name is way too long.
624 cvox.ChromeVox.tts.increaseOrDecreaseProperty(
625 cvox.AbstractTts.RATE, false);
627 case 'increaseTtsRate':
628 cvox.ChromeVox.tts.increaseOrDecreaseProperty(
629 cvox.AbstractTts.RATE, true);
631 case 'decreaseTtsPitch':
632 cvox.ChromeVox.tts.increaseOrDecreaseProperty(
633 cvox.AbstractTts.PITCH, false);
635 case 'increaseTtsPitch':
636 cvox.ChromeVox.tts.increaseOrDecreaseProperty(
637 cvox.AbstractTts.PITCH, true);
639 case 'decreaseTtsVolume':
640 cvox.ChromeVox.tts.increaseOrDecreaseProperty(
641 cvox.AbstractTts.VOLUME, false);
643 case 'increaseTtsVolume':
644 cvox.ChromeVox.tts.increaseOrDecreaseProperty(
645 cvox.AbstractTts.VOLUME, true);
647 case 'cyclePunctuationEcho':
648 cvox.ChromeVox.host.sendToBackgroundPage({
650 'action': 'cyclePunctuationEcho'
654 case 'toggleStickyMode':
655 cvox.ChromeVox.host.sendToBackgroundPage({
659 'value': !cvox.ChromeVox.isStickyPrefOn,
663 case 'toggleKeyPrefix':
664 cvox.ChromeVox.keyPrefixOn = !cvox.ChromeVox.keyPrefixOn;
666 case 'passThroughMode':
667 cvox.ChromeVox.passThroughMode = true;
668 cvox.ChromeVox.tts.speak(
669 cvox.ChromeVox.msgs.getMsg('pass_through_key'), cvox.QueueMode.QUEUE);
671 case 'toggleSearchWidget':
672 cvox.SearchWidget.getInstance().toggle();
675 case 'toggleEarcons':
676 prefixMsg = cvox.ChromeVox.earcons.toggle() ?
677 cvox.ChromeVox.msgs.getMsg('earcons_on') :
678 cvox.ChromeVox.msgs.getMsg('earcons_off');
681 case 'showHeadingsList':
682 case 'showLinksList':
683 case 'showFormsList':
684 case 'showTablesList':
685 case 'showLandmarksList':
686 if (!cmdStruct.nodeList) {
690 cvox.CommandStore.NODE_INFO_MAP[cmdStruct.nodeList];
692 cvox.NodeSearchWidget.create(nodeListStruct.typeMsg,
693 cvox.DomPredicates[nodeListStruct.predicate]).show();
697 var currentNode = cvox.ChromeVox.navigationManager.getCurrentNode();
698 if (cvox.DomUtil.hasLongDesc(currentNode)) {
699 cvox.ChromeVox.host.sendToBackgroundPage({
701 'url': currentNode.longDesc // Use .longDesc instead of getAttribute
702 // since we want Chrome to convert the
703 // longDesc to an absolute URL.
706 cvox.ChromeVox.tts.speak(
707 cvox.ChromeVox.msgs.getMsg('no_long_desc'),
708 cvox.QueueMode.FLUSH,
709 cvox.AbstractTts.PERSONALITY_ANNOTATION);
713 case 'pauseAllMedia':
714 var videos = document.getElementsByTagName('VIDEO');
715 for (var i = 0, mediaElem; mediaElem = videos[i]; i++) {
718 var audios = document.getElementsByTagName('AUDIO');
719 for (var i = 0, mediaElem; mediaElem = audios[i]; i++) {
724 // Math specific commands.
725 case 'toggleSemantics':
726 if (cvox.TraverseMath.toggleSemantic()) {
727 cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg('semantics_on'),
728 cvox.QueueMode.QUEUE);
730 cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg('semantics_off'),
731 cvox.QueueMode.QUEUE);
735 // Braille specific commands.
736 case cvox.BrailleKeyCommand.ROUTING:
737 var braille = cmdStruct.content;
739 cvox.BrailleUtil.click(braille, cmdStruct.event.displayPosition);
742 case cvox.BrailleKeyCommand.PAN_LEFT:
743 case cvox.BrailleKeyCommand.LINE_UP:
744 case cvox.BrailleKeyCommand.PAN_RIGHT:
745 case cvox.BrailleKeyCommand.LINE_DOWN:
746 // TODO(dtseng, plundblad): This needs to sync to the last pan position
747 // after line up/pan left and move the display to the far right on the
748 // line in case the synced to node is longer than one display line.
749 // Should also work with all widgets.
750 cvox.ChromeVox.navigationManager.navigate(false,
751 cvox.NavigationShifter.GRANULARITIES.LINE);
755 // TODO(stoarca): This doesn't belong here.
761 throw 'Command behavior not defined: ' + cmd;
764 if (errorMsg != '') {
765 cvox.ChromeVox.tts.speak(
766 cvox.ChromeVox.msgs.getMsg(errorMsg),
767 cvox.QueueMode.FLUSH,
768 cvox.AbstractTts.PERSONALITY_ANNOTATION);
769 } else if (cvox.ChromeVox.navigationManager.isReading()) {
770 if (cmdStruct.disallowContinuation) {
771 cvox.ChromeVox.navigationManager.stopReading(true);
772 } else if (cmd != 'readFromHere') {
773 cvox.ChromeVox.navigationManager.skip();
776 if (cmdStruct.announce) {
777 cvox.ChromeVox.navigationManager.finishNavCommand(prefixMsg);
780 if (!cmdStruct.allowEvents) {
781 cvox.ChromeVoxEventSuspender.exitSuspendEvents();
783 return !!cmdStruct.doDefault || ret;
788 * Default handler for public user commands that are dispatched to the web app
789 * first so that the web developer can handle these commands instead of
790 * ChromeVox if they decide they can do a better job than the default algorithm.
792 * @param {Object} cvoxUserEvent The cvoxUserEvent to handle.
794 cvox.ChromeVoxUserCommands.handleChromeVoxUserEvent = function(cvoxUserEvent) {
795 var detail = new cvox.UserEventDetail(cvoxUserEvent.detail);
796 if (detail.command) {
797 cvox.ChromeVoxUserCommands.doCommand_(
798 cvox.ChromeVoxUserCommands.lookupCommand_(detail.command, detail));
804 * Returns an object containing information about the given command.
805 * @param {string} cmd The name of the command.
806 * @param {Object=} opt_kwargs Optional key values to add to the command
808 * @return {Object} A key value mapping.
811 cvox.ChromeVoxUserCommands.lookupCommand_ = function(cmd, opt_kwargs) {
812 var cmdStruct = cvox.CommandStore.CMD_WHITELIST[cmd];
814 throw 'Invalid command: ' + cmd;
816 cmdStruct = goog.object.clone(cmdStruct);
817 cmdStruct.command = cmd;
819 goog.object.extend(cmdStruct, opt_kwargs);
825 cvox.ChromeVoxUserCommands.init_();