Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / chromeos / chromevox / chromevox / injected / user_commands.js
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.
4
5 /**
6  * @fileoverview High level commands that the user can invoke using hotkeys.
7  *
8  * Usage:
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.
15  */
16
17
18 goog.provide('cvox.ChromeVoxUserCommands');
19
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');
37
38
39 /**
40  * Initializes commands map.
41  * Initializes global members.
42  * @private
43  */
44 cvox.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  */
60 cvox.ChromeVoxUserCommands.commands;
61
62
63 /**
64  * @type {boolean}
65  * TODO (clchen, dmazzoni): Implement syncing on click to avoid needing this.
66  */
67 cvox.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  */
82 cvox.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  */
94 cvox.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  */
170 cvox.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  */
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);
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  */
199 cvox.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  */
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);
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  */
226 cvox.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  */
256 cvox.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.QueueMode.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, cvox.QueueMode.QUEUE);
522       } else {
523         cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg('no_url_found'),
524                                  cvox.QueueMode.QUEUE);
525       }
526       break;
527     case 'readCurrentTitle':
528       cvox.ChromeVox.tts.speak(document.title, cvox.QueueMode.QUEUE);
529       break;
530     case 'readCurrentURL':
531       cvox.ChromeVox.tts.speak(document.URL, cvox.QueueMode.QUEUE);
532       break;
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;
542         }
543       }
544       break;
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);
549       break;
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);
554       break;
555     case 'toggleChromeVox':
556       cvox.ChromeVox.host.sendToBackgroundPage({
557         'target': 'Prefs',
558         'action': 'setPref',
559         'pref': 'active',
560         'value': !cvox.ChromeVox.isActive
561       });
562       break;
563     case 'fullyDescribe':
564       var descs = cvox.ChromeVox.navigationManager.getFullDescription();
565       cvox.ChromeVox.navigationManager.speakDescriptionArray(
566           descs,
567           cvox.QueueMode.FLUSH,
568           null);
569       break;
570     case 'speakTimeAndDate':
571       var dateTime = new Date();
572       cvox.ChromeVox.tts.speak(
573           dateTime.toLocaleTimeString() + ', ' + dateTime.toLocaleDateString(),
574           cvox.QueueMode.QUEUE);
575       break;
576     case 'toggleSelection':
577       var selState = cvox.ChromeVox.navigationManager.togglePageSel();
578       prefixMsg = cvox.ChromeVox.msgs.getMsg(
579           selState ? 'begin_selection' : 'end_selection');
580     break;
581     case 'startHistoryRecording':
582       cvox.History.getInstance().startRecording();
583       break;
584     case 'stopHistoryRecording':
585       cvox.History.getInstance().stopRecording();
586       break;
587     case 'enableConsoleTts':
588       cvox.ConsoleTts.getInstance().setEnabled(true);
589       break;
590     case 'toggleBrailleCaptions':
591       cvox.ChromeVox.host.sendToBackgroundPage({
592         'target': 'Prefs',
593         'action': 'setPref',
594         'pref': 'brailleCaptions',
595         'value': !cvox.BrailleOverlayWidget.getInstance().isActive()
596       });
597       break;
598
599     // Table actions.
600     case 'goToFirstCell':
601     case 'goToLastCell':
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';
613       }
614       break;
615
616     // Generic actions.
617     case 'enterShifter':
618     case 'exitShifter':
619       cvox.ChromeVox.navigationManager.performAction(cmd);
620       break;
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);
626       break;
627     case 'increaseTtsRate':
628       cvox.ChromeVox.tts.increaseOrDecreaseProperty(
629           cvox.AbstractTts.RATE, true);
630       break;
631     case 'decreaseTtsPitch':
632       cvox.ChromeVox.tts.increaseOrDecreaseProperty(
633           cvox.AbstractTts.PITCH, false);
634       break;
635     case 'increaseTtsPitch':
636       cvox.ChromeVox.tts.increaseOrDecreaseProperty(
637           cvox.AbstractTts.PITCH, true);
638       break;
639     case 'decreaseTtsVolume':
640       cvox.ChromeVox.tts.increaseOrDecreaseProperty(
641           cvox.AbstractTts.VOLUME, false);
642       break;
643     case 'increaseTtsVolume':
644       cvox.ChromeVox.tts.increaseOrDecreaseProperty(
645           cvox.AbstractTts.VOLUME, true);
646       break;
647       case 'cyclePunctuationEcho':
648         cvox.ChromeVox.host.sendToBackgroundPage({
649             'target': 'TTS',
650             'action': 'cyclePunctuationEcho'
651           });
652         break;
653
654     case 'toggleStickyMode':
655       cvox.ChromeVox.host.sendToBackgroundPage({
656         'target': 'Prefs',
657         'action': 'setPref',
658         'pref': 'sticky',
659         'value': !cvox.ChromeVox.isStickyPrefOn,
660         'announce': true
661       });
662       break;
663     case 'toggleKeyPrefix':
664       cvox.ChromeVox.keyPrefixOn = !cvox.ChromeVox.keyPrefixOn;
665       break;
666     case 'passThroughMode':
667       cvox.ChromeVox.passThroughMode = true;
668       cvox.ChromeVox.tts.speak(
669           cvox.ChromeVox.msgs.getMsg('pass_through_key'), cvox.QueueMode.QUEUE);
670       break;
671     case 'toggleSearchWidget':
672       cvox.SearchWidget.getInstance().toggle();
673       break;
674
675     case 'toggleEarcons':
676       prefixMsg = cvox.ChromeVox.earcons.toggle() ?
677           cvox.ChromeVox.msgs.getMsg('earcons_on') :
678               cvox.ChromeVox.msgs.getMsg('earcons_off');
679       break;
680
681     case 'showHeadingsList':
682     case 'showLinksList':
683     case 'showFormsList':
684     case 'showTablesList':
685     case 'showLandmarksList':
686       if (!cmdStruct.nodeList) {
687         break;
688       }
689       var nodeListStruct =
690           cvox.CommandStore.NODE_INFO_MAP[cmdStruct.nodeList];
691
692       cvox.NodeSearchWidget.create(nodeListStruct.typeMsg,
693                   cvox.DomPredicates[nodeListStruct.predicate]).show();
694       break;
695
696     case 'openLongDesc':
697       var currentNode = cvox.ChromeVox.navigationManager.getCurrentNode();
698       if (cvox.DomUtil.hasLongDesc(currentNode)) {
699         cvox.ChromeVox.host.sendToBackgroundPage({
700           'target': 'OpenTab',
701           'url': currentNode.longDesc // Use .longDesc instead of getAttribute
702                                       // since we want Chrome to convert the
703                                       // longDesc to an absolute URL.
704         });
705       } else {
706         cvox.ChromeVox.tts.speak(
707           cvox.ChromeVox.msgs.getMsg('no_long_desc'),
708           cvox.QueueMode.FLUSH,
709           cvox.AbstractTts.PERSONALITY_ANNOTATION);
710       }
711       break;
712
713     case 'pauseAllMedia':
714       var videos = document.getElementsByTagName('VIDEO');
715       for (var i = 0, mediaElem; mediaElem = videos[i]; i++) {
716         mediaElem.pause();
717       }
718       var audios = document.getElementsByTagName('AUDIO');
719       for (var i = 0, mediaElem; mediaElem = audios[i]; i++) {
720         mediaElem.pause();
721       }
722       break;
723
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);
729       } else {
730         cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg('semantics_off'),
731                                  cvox.QueueMode.QUEUE);
732       }
733       break;
734
735     // Braille specific commands.
736     case cvox.BrailleKeyCommand.ROUTING:
737       var braille = cmdStruct.content;
738       if (braille) {
739         cvox.BrailleUtil.click(braille, cmdStruct.event.displayPosition);
740       }
741       break;
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);
752       break;
753
754     case 'debug':
755       // TODO(stoarca): This doesn't belong here.
756       break;
757
758     case 'nop':
759       break;
760     default:
761       throw 'Command behavior not defined: ' + cmd;
762   }
763
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();
774     }
775   } else {
776     if (cmdStruct.announce) {
777       cvox.ChromeVox.navigationManager.finishNavCommand(prefixMsg);
778     }
779   }
780   if (!cmdStruct.allowEvents) {
781     cvox.ChromeVoxEventSuspender.exitSuspendEvents();
782   }
783   return !!cmdStruct.doDefault || ret;
784 };
785
786
787 /**
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.
791  *
792  * @param {Object} cvoxUserEvent The cvoxUserEvent to handle.
793  */
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));
799   }
800 };
801
802
803 /**
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
807  * structure.
808  * @return {Object} A key value mapping.
809  * @private
810  */
811 cvox.ChromeVoxUserCommands.lookupCommand_ = function(cmd, opt_kwargs) {
812   var cmdStruct = cvox.CommandStore.CMD_WHITELIST[cmd];
813   if (!cmdStruct) {
814     throw 'Invalid command: ' + cmd;
815   }
816   cmdStruct = goog.object.clone(cmdStruct);
817   cmdStruct.command = cmd;
818   if (opt_kwargs) {
819     goog.object.extend(cmdStruct, opt_kwargs);
820   }
821   return cmdStruct;
822 };
823
824
825 cvox.ChromeVoxUserCommands.init_();