blob: a31e637a84511a407ac0f0b5cd98db52a3edab54 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview High level commands that the user can invoke using hotkeys.
*
* Usage:
* If you are here, you probably want to add a new user command. Here are some
* general steps to get you started.
* - Go to command_store.js, where all static data about a command lives. Follow
* the instructions there.
* - Add the logic of the command to doCommand_ below. Try to reuse or group
* your command with related commands.
*/
goog.provide('cvox.ChromeVoxUserCommands');
goog.require('cvox.BrailleKeyCommand');
goog.require('cvox.BrailleOverlayWidget');
goog.require('cvox.ChromeVox');
goog.require('cvox.CommandStore');
goog.require('cvox.ConsoleTts');
goog.require('cvox.ContextMenuWidget');
goog.require('cvox.DomPredicates');
goog.require('cvox.DomUtil');
goog.require('cvox.FocusUtil');
goog.require('cvox.KeyboardHelpWidget');
goog.require('cvox.NodeSearchWidget');
goog.require('cvox.PlatformUtil');
goog.require('cvox.SearchWidget');
goog.require('cvox.SelectWidget');
goog.require('cvox.TypingEcho');
goog.require('cvox.UserEventDetail');
goog.require('goog.object');
/**
* Initializes commands map.
* Initializes global members.
* @private
*/
cvox.ChromeVoxUserCommands.init_ = function() {
if (cvox.ChromeVoxUserCommands.commands) {
return;
} else {
cvox.ChromeVoxUserCommands.commands = {};
}
for (var cmd in cvox.CommandStore.CMD_WHITELIST) {
cvox.ChromeVoxUserCommands.commands[cmd] =
cvox.ChromeVoxUserCommands.createCommand_(cmd);
}
};
/**
* @type {!Object.<string, function(Object=): boolean>}
*/
cvox.ChromeVoxUserCommands.commands;
/**
* @type {boolean}
* TODO (clchen, dmazzoni): Implement syncing on click to avoid needing this.
*/
cvox.ChromeVoxUserCommands.wasMouseClicked = false;
/**
* @type {boolean} Flag to set whether or not certain user commands will be
* first dispatched to the underlying web page. Some commands (such as finding
* the next/prev structural element) may be better implemented by the web app
* than by ChromeVox.
*
* By default, this is enabled; however, for testing, we usually disable this to
* reduce flakiness caused by event timing issues.
*
* TODO (clchen, dtseng): Fix testing framework so that we don't need to turn
* this feature off at all.
*/
cvox.ChromeVoxUserCommands.enableCommandDispatchingToPage = true;
/**
* Handles any tab navigation by putting focus at the user's position.
* This function will create dummy nodes if there is nothing that is focusable
* at the current position.
* TODO (adu): This function is too long. We need to break it up into smaller
* helper functions.
* @return {boolean} True if default action should be taken.
* @private
*/
cvox.ChromeVoxUserCommands.handleTabAction_ = function() {
cvox.ChromeVox.tts.stop();
// If we are tabbing from an invalid location, prevent the default action.
// We pass the isFocusable function as a predicate to specify we only want to
// revert to focusable nodes.
if (!cvox.ChromeVox.navigationManager.resolve(cvox.DomUtil.isFocusable)) {
cvox.ChromeVox.navigationManager.setFocus();
return false;
}
// If the user is already focused on anything, nothing more needs to be done.
if (document.activeElement != document.body) {
return true;
}
// Try to find something reasonable to focus on.
// Use selection if it exists because it means that the user has probably
// clicked with their mouse and we should respect their position.
// If there is no selection, then use the last known position based on
// NavigationManager's currentNode.
var anchorNode = null;
var focusNode = null;
var sel = window.getSelection();
if (!cvox.ChromeVoxUserCommands.wasMouseClicked) {
sel = null;
} else {
cvox.ChromeVoxUserCommands.wasMouseClicked = false;
}
if (sel == null || sel.anchorNode == null || sel.focusNode == null) {
anchorNode = cvox.ChromeVox.navigationManager.getCurrentNode();
focusNode = cvox.ChromeVox.navigationManager.getCurrentNode();
} else {
anchorNode = sel.anchorNode;
focusNode = sel.focusNode;
}
// See if we can set focus to either anchorNode or focusNode.
// If not, try the parents. Otherwise give up and create a dummy span.
if (anchorNode == null || focusNode == null) {
return true;
}
if (cvox.DomUtil.isFocusable(anchorNode)) {
anchorNode.focus();
return true;
}
if (cvox.DomUtil.isFocusable(focusNode)) {
focusNode.focus();
return true;
}
if (cvox.DomUtil.isFocusable(anchorNode.parentNode)) {
anchorNode.parentNode.focus();
return true;
}
if (cvox.DomUtil.isFocusable(focusNode.parentNode)) {
focusNode.parentNode.focus();
return true;
}
// Insert and focus a dummy span immediately before the current position
// so that the default tab action will start off as close to the user's
// current position as possible.
var bestGuess = anchorNode;
var dummySpan = cvox.ChromeVoxUserCommands.createTabDummySpan_();
bestGuess.parentNode.insertBefore(dummySpan, bestGuess);
dummySpan.focus();
return true;
};
/**
* If a lingering tab dummy span exists, remove it.
*/
cvox.ChromeVoxUserCommands.removeTabDummySpan = function() {
// Break the following line to get around a Chromium js linter warning.
// TODO(plundblad): Find a better solution.
var previousDummySpan = document.
getElementById('ChromeVoxTabDummySpan');
if (previousDummySpan && document.activeElement != previousDummySpan) {
previousDummySpan.parentNode.removeChild(previousDummySpan);
}
};
/**
* Create a new tab dummy span.
* @return {Element} The dummy span element to be inserted.
* @private
*/
cvox.ChromeVoxUserCommands.createTabDummySpan_ = function() {
var span = document.createElement('span');
span.id = 'ChromeVoxTabDummySpan';
span.tabIndex = -1;
return span;
};
/**
* @param {string} cmd The programmatic command name.
* @return {function(Object=): boolean} The callable command taking an optional
* args dictionary.
* @private
*/
cvox.ChromeVoxUserCommands.createCommand_ = function(cmd) {
return goog.bind(function(opt_kwargs) {
var cmdStruct = cvox.ChromeVoxUserCommands.lookupCommand_(cmd, opt_kwargs);
return cvox.ChromeVoxUserCommands.dispatchCommand_(cmdStruct);
}, cvox.ChromeVoxUserCommands);
};
/**
* @param {Object} cmdStruct The command to do.
* @return {boolean} False to prevent the default action. True otherwise.
* @private
*/
cvox.ChromeVoxUserCommands.dispatchCommand_ = function(cmdStruct) {
if (cvox.Widget.isActive()) {
return true;
}
if (!cvox.PlatformUtil.matchesPlatform(cmdStruct.platformFilter) ||
(cmdStruct.skipInput && cvox.FocusUtil.isFocusInTextInputField())) {
return true;
}
// Handle dispatching public command events
if (cvox.ChromeVoxUserCommands.enableCommandDispatchingToPage &&
(cvox.UserEventDetail.JUMP_COMMANDS.indexOf(cmdStruct.command) != -1)) {
var detail = new cvox.UserEventDetail({command: cmdStruct.command});
var evt = detail.createEventObject();
var currentNode = cvox.ChromeVox.navigationManager.getCurrentNode();
if (!currentNode) {
currentNode = document.body;
}
currentNode.dispatchEvent(evt);
return false;
}
// Not a public command; act on this command directly.
return cvox.ChromeVoxUserCommands.doCommand_(cmdStruct);
};
/**
* @param {Object} cmdStruct The command to do.
* @return {boolean} False to prevent the default action. True otherwise.
* @private
*/
cvox.ChromeVoxUserCommands.doCommand_ = function(cmdStruct) {
if (cvox.Widget.isActive()) {
return true;
}
if (!cvox.PlatformUtil.matchesPlatform(cmdStruct.platformFilter) ||
(cmdStruct.skipInput && cvox.FocusUtil.isFocusInTextInputField())) {
return true;
}
if (cmdStruct.disallowOOBE && document.URL.match(/^chrome:\/\/oobe/i)) {
return true;
}
var cmd = cmdStruct.command;
if (!cmdStruct.allowEvents) {
cvox.ChromeVoxEventSuspender.enterSuspendEvents();
}
if (cmdStruct.disallowContinuation) {
cvox.ChromeVox.navigationManager.stopReading(true);
}
if (cmdStruct.forward) {
cvox.ChromeVox.navigationManager.setReversed(false);
} else if (cmdStruct.backward) {
cvox.ChromeVox.navigationManager.setReversed(true);
}
if (cmdStruct.findNext) {
cmd = 'find';
cmdStruct.announce = true;
}
var errorMsg = '';
var prefixMsg = '';
var ret = false;
switch (cmd) {
case 'handleTab':
case 'handleTabPrev':
ret = cvox.ChromeVoxUserCommands.handleTabAction_();
break;
case 'forward':
case 'backward':
ret = !cvox.ChromeVox.navigationManager.navigate();
break;
case 'right':
case 'left':
cvox.ChromeVox.navigationManager.subnavigate();
break;
case 'find':
if (!cmdStruct.findNext) {
throw 'Invalid find command.';
}
var NodeInfoStruct =
cvox.CommandStore.NODE_INFO_MAP[cmdStruct.findNext];
var predicateName = NodeInfoStruct.predicate;
var predicate = cvox.DomPredicates[predicateName];
var error = '';
var wrap = '';
if (cmdStruct.forward) {
wrap = cvox.ChromeVox.msgs.getMsg('wrapped_to_top');
error = cvox.ChromeVox.msgs.getMsg(NodeInfoStruct.forwardError);
} else if (cmdStruct.backward) {
wrap = cvox.ChromeVox.msgs.getMsg('wrapped_to_bottom');
error = cvox.ChromeVox.msgs.getMsg(NodeInfoStruct.backwardError);
}
var found = null;
var status = cmdStruct.status || cvox.UserEventDetail.Status.PENDING;
var resultNode = cmdStruct.resultNode || null;
switch (status) {
case cvox.UserEventDetail.Status.SUCCESS:
if (resultNode) {
cvox.ChromeVox.navigationManager.updateSelToArbitraryNode(
resultNode, true);
}
break;
case cvox.UserEventDetail.Status.FAILURE:
prefixMsg = error;
break;
default:
found = cvox.ChromeVox.navigationManager.findNext(
predicate, predicateName);
if (!found) {
cvox.ChromeVox.navigationManager.saveSel();
prefixMsg = wrap;
cvox.ChromeVox.navigationManager.syncToBeginning();
cvox.ChromeVox.earcons.playEarcon(cvox.AbstractEarcons.WRAP);
found = cvox.ChromeVox.navigationManager.findNext(
predicate, predicateName, true);
if (!found) {
prefixMsg = error;
cvox.ChromeVox.navigationManager.restoreSel();
}
}
break;
}
// NavigationManager performs announcement inside of frames when finding.
if (found && found.start.node.tagName == 'IFRAME') {
cmdStruct.announce = false;
}
break;
// TODO(stoarca): Bad naming. Should be less instead of previous.
case 'previousGranularity':
cvox.ChromeVox.navigationManager.makeLessGranular(true);
prefixMsg = cvox.ChromeVox.navigationManager.getGranularityMsg();
break;
case 'nextGranularity':
cvox.ChromeVox.navigationManager.makeMoreGranular(true);
prefixMsg = cvox.ChromeVox.navigationManager.getGranularityMsg();
break;
case 'previousCharacter':
cvox.ChromeVox.navigationManager.navigate(false,
cvox.NavigationShifter.GRANULARITIES.CHARACTER);
break;
case 'nextCharacter':
cvox.ChromeVox.navigationManager.navigate(false,
cvox.NavigationShifter.GRANULARITIES.CHARACTER);
break;
case 'previousWord':
cvox.ChromeVox.navigationManager.navigate(false,
cvox.NavigationShifter.GRANULARITIES.WORD);
break;
case 'nextWord':
cvox.ChromeVox.navigationManager.navigate(false,
cvox.NavigationShifter.GRANULARITIES.WORD);
break;
case 'previousSentence':
cvox.ChromeVox.navigationManager.navigate(false,
cvox.NavigationShifter.GRANULARITIES.SENTENCE);
break;
case 'nextSentence':
cvox.ChromeVox.navigationManager.navigate(false,
cvox.NavigationShifter.GRANULARITIES.SENTENCE);
break;
case 'previousLine':
cvox.ChromeVox.navigationManager.navigate(false,
cvox.NavigationShifter.GRANULARITIES.LINE);
break;
case 'nextLine':
cvox.ChromeVox.navigationManager.navigate(false,
cvox.NavigationShifter.GRANULARITIES.LINE);
break;
case 'previousObject':
cvox.ChromeVox.navigationManager.navigate(false,
cvox.NavigationShifter.GRANULARITIES.OBJECT);
break;
case 'nextObject':
cvox.ChromeVox.navigationManager.navigate(false,
cvox.NavigationShifter.GRANULARITIES.OBJECT);
break;
case 'previousGroup':
cvox.ChromeVox.navigationManager.navigate(false,
cvox.NavigationShifter.GRANULARITIES.GROUP);
break;
case 'nextGroup':
cvox.ChromeVox.navigationManager.navigate(false,
cvox.NavigationShifter.GRANULARITIES.GROUP);
break;
case 'previousRow':
case 'previousCol':
// Fold these commands to their "next" equivalents since we already set
// isReversed above.
cmd = cmd == 'previousRow' ? 'nextRow' : 'nextCol';
case 'nextRow':
case 'nextCol':
cvox.ChromeVox.navigationManager.performAction('enterShifterSilently');
cvox.ChromeVox.navigationManager.performAction(cmd);
break;
case 'moveToStartOfLine':
case 'moveToEndOfLine':
cvox.ChromeVox.navigationManager.setGranularity(
cvox.NavigationShifter.GRANULARITIES.LINE);
cvox.ChromeVox.navigationManager.sync();
cvox.ChromeVox.navigationManager.collapseSelection();
break;
case 'readFromHere':
cvox.ChromeVox.navigationManager.setGranularity(
cvox.NavigationShifter.GRANULARITIES.OBJECT, true, true);
cvox.ChromeVox.navigationManager.startReading(
cvox.QueueMode.FLUSH);
break;
case 'cycleTypingEcho':
cvox.ChromeVox.host.sendToBackgroundPage({
'target': 'Prefs',
'action': 'setPref',
'pref': 'typingEcho',
'value': cvox.TypingEcho.cycle(cvox.ChromeVox.typingEcho),
'announce': true
});
break;
case 'jumpToTop':
case cvox.BrailleKeyCommand.TOP:
cvox.ChromeVox.navigationManager.syncToBeginning();
break;
case 'jumpToBottom':
case cvox.BrailleKeyCommand.BOTTOM:
cvox.ChromeVox.navigationManager.syncToBeginning();
break;
case 'stopSpeech':
cvox.ChromeVox.navigationManager.stopReading(true);
break;
case 'toggleKeyboardHelp':
cvox.KeyboardHelpWidget.getInstance().toggle();
break;
case 'help':
cvox.ChromeVox.tts.stop();
cvox.ChromeVox.host.sendToBackgroundPage({
'target': 'HelpDocs',
'action': 'open'
});
break;
case 'contextMenu':
// Move this logic to a central dispatching class if it grows any bigger.
var node = cvox.ChromeVox.navigationManager.getCurrentNode();
if (node.tagName == 'SELECT' && !node.multiple) {
new cvox.SelectWidget(node).show();
} else {
var contextMenuWidget = new cvox.ContextMenuWidget();
contextMenuWidget.toggle();
}
break;
case 'showBookmarkManager':
// TODO(stoarca): Should this have tts.stop()??
cvox.ChromeVox.host.sendToBackgroundPage({
'target': 'BookmarkManager',
'action': 'open'
});
break;
case 'showOptionsPage':
cvox.ChromeVox.tts.stop();
cvox.ChromeVox.host.sendToBackgroundPage({
'target': 'Options',
'action': 'open'
});
break;
case 'showKbExplorerPage':
cvox.ChromeVox.tts.stop();
cvox.ChromeVox.host.sendToBackgroundPage({
'target': 'KbExplorer',
'action': 'open'
});
break;
case 'readLinkURL':
var activeElement = document.activeElement;
var currentSelectionAnchor = window.getSelection().anchorNode;
var url = '';
if (activeElement.tagName == 'A') {
url = cvox.DomUtil.getLinkURL(activeElement);
} else if (currentSelectionAnchor) {
url = cvox.DomUtil.getLinkURL(currentSelectionAnchor.parentNode);
}
if (url != '') {
cvox.ChromeVox.tts.speak(url, cvox.QueueMode.QUEUE);
} else {
cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg('no_url_found'),
cvox.QueueMode.QUEUE);
}
break;
case 'readCurrentTitle':
cvox.ChromeVox.tts.speak(document.title, cvox.QueueMode.QUEUE);
break;
case 'readCurrentURL':
cvox.ChromeVox.tts.speak(document.URL, cvox.QueueMode.QUEUE);
break;
case 'performDefaultAction':
if (cvox.DomPredicates.linkPredicate([document.activeElement])) {
if (cvox.DomUtil.isInternalLink(document.activeElement)) {
// First, sync our selection to the destination of the internal link.
cvox.DomUtil.syncInternalLink(document.activeElement);
// Now, sync our selection based on the current granularity.
cvox.ChromeVox.navigationManager.sync();
// Announce this new selection.
cmdStruct.announce = true;
}
}
break;
case 'forceClickOnCurrentItem':
prefixMsg = cvox.ChromeVox.msgs.getMsg('element_clicked');
var targetNode = cvox.ChromeVox.navigationManager.getCurrentNode();
cvox.DomUtil.clickElem(targetNode, false, false);
break;
case 'forceDoubleClickOnCurrentItem':
prefixMsg = cvox.ChromeVox.msgs.getMsg('element_double_clicked');
var targetNode = cvox.ChromeVox.navigationManager.getCurrentNode();
cvox.DomUtil.clickElem(targetNode, false, false, true);
break;
case 'toggleChromeVox':
cvox.ChromeVox.host.sendToBackgroundPage({
'target': 'Prefs',
'action': 'setPref',
'pref': 'active',
'value': !cvox.ChromeVox.isActive
});
break;
case 'fullyDescribe':
var descs = cvox.ChromeVox.navigationManager.getFullDescription();
cvox.ChromeVox.navigationManager.speakDescriptionArray(
descs,
cvox.QueueMode.FLUSH,
null);
break;
case 'speakTimeAndDate':
var dateTime = new Date();
cvox.ChromeVox.tts.speak(
dateTime.toLocaleTimeString() + ', ' + dateTime.toLocaleDateString(),
cvox.QueueMode.QUEUE);
break;
case 'toggleSelection':
var selState = cvox.ChromeVox.navigationManager.togglePageSel();
prefixMsg = cvox.ChromeVox.msgs.getMsg(
selState ? 'begin_selection' : 'end_selection');
break;
case 'startHistoryRecording':
cvox.History.getInstance().startRecording();
break;
case 'stopHistoryRecording':
cvox.History.getInstance().stopRecording();
break;
case 'enableConsoleTts':
cvox.ConsoleTts.getInstance().setEnabled(true);
break;
case 'toggleBrailleCaptions':
cvox.ChromeVox.host.sendToBackgroundPage({
'target': 'Prefs',
'action': 'setPref',
'pref': 'brailleCaptions',
'value': !cvox.BrailleOverlayWidget.getInstance().isActive()
});
break;
// Table actions.
case 'goToFirstCell':
case 'goToLastCell':
case 'goToRowFirstCell':
case 'goToRowLastCell':
case 'goToColFirstCell':
case 'goToColLastCell':
case 'announceHeaders':
case 'speakTableLocation':
case 'exitShifterContent':
if (!cvox.DomPredicates.tablePredicate(cvox.DomUtil.getAncestors(
cvox.ChromeVox.navigationManager.getCurrentNode()))) {
errorMsg = 'not_inside_table';
} else if (!cvox.ChromeVox.navigationManager.performAction(cmd)) {
errorMsg = 'not_in_table_mode';
}
break;
// Generic actions.
case 'enterShifter':
case 'exitShifter':
cvox.ChromeVox.navigationManager.performAction(cmd);
break;
// TODO(stoarca): Code repetition.
case 'decreaseTtsRate':
// TODO(stoarca): This function name is way too long.
cvox.ChromeVox.tts.increaseOrDecreaseProperty(
cvox.AbstractTts.RATE, false);
break;
case 'increaseTtsRate':
cvox.ChromeVox.tts.increaseOrDecreaseProperty(
cvox.AbstractTts.RATE, true);
break;
case 'decreaseTtsPitch':
cvox.ChromeVox.tts.increaseOrDecreaseProperty(
cvox.AbstractTts.PITCH, false);
break;
case 'increaseTtsPitch':
cvox.ChromeVox.tts.increaseOrDecreaseProperty(
cvox.AbstractTts.PITCH, true);
break;
case 'decreaseTtsVolume':
cvox.ChromeVox.tts.increaseOrDecreaseProperty(
cvox.AbstractTts.VOLUME, false);
break;
case 'increaseTtsVolume':
cvox.ChromeVox.tts.increaseOrDecreaseProperty(
cvox.AbstractTts.VOLUME, true);
break;
case 'cyclePunctuationEcho':
cvox.ChromeVox.host.sendToBackgroundPage({
'target': 'TTS',
'action': 'cyclePunctuationEcho'
});
break;
case 'toggleStickyMode':
cvox.ChromeVox.host.sendToBackgroundPage({
'target': 'Prefs',
'action': 'setPref',
'pref': 'sticky',
'value': !cvox.ChromeVox.isStickyPrefOn,
'announce': true
});
break;
case 'toggleKeyPrefix':
cvox.ChromeVox.keyPrefixOn = !cvox.ChromeVox.keyPrefixOn;
break;
case 'passThroughMode':
cvox.ChromeVox.passThroughMode = true;
cvox.ChromeVox.tts.speak(
cvox.ChromeVox.msgs.getMsg('pass_through_key'), cvox.QueueMode.QUEUE);
break;
case 'toggleSearchWidget':
cvox.SearchWidget.getInstance().toggle();
break;
case 'toggleEarcons':
prefixMsg = cvox.ChromeVox.earcons.toggle() ?
cvox.ChromeVox.msgs.getMsg('earcons_on') :
cvox.ChromeVox.msgs.getMsg('earcons_off');
break;
case 'showHeadingsList':
case 'showLinksList':
case 'showFormsList':
case 'showTablesList':
case 'showLandmarksList':
if (!cmdStruct.nodeList) {
break;
}
var nodeListStruct =
cvox.CommandStore.NODE_INFO_MAP[cmdStruct.nodeList];
cvox.NodeSearchWidget.create(nodeListStruct.typeMsg,
cvox.DomPredicates[nodeListStruct.predicate]).show();
break;
case 'openLongDesc':
var currentNode = cvox.ChromeVox.navigationManager.getCurrentNode();
if (cvox.DomUtil.hasLongDesc(currentNode)) {
cvox.ChromeVox.host.sendToBackgroundPage({
'target': 'OpenTab',
'url': currentNode.longDesc // Use .longDesc instead of getAttribute
// since we want Chrome to convert the
// longDesc to an absolute URL.
});
} else {
cvox.ChromeVox.tts.speak(
cvox.ChromeVox.msgs.getMsg('no_long_desc'),
cvox.QueueMode.FLUSH,
cvox.AbstractTts.PERSONALITY_ANNOTATION);
}
break;
case 'pauseAllMedia':
var videos = document.getElementsByTagName('VIDEO');
for (var i = 0, mediaElem; mediaElem = videos[i]; i++) {
mediaElem.pause();
}
var audios = document.getElementsByTagName('AUDIO');
for (var i = 0, mediaElem; mediaElem = audios[i]; i++) {
mediaElem.pause();
}
break;
// Math specific commands.
case 'toggleSemantics':
if (cvox.TraverseMath.toggleSemantic()) {
cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg('semantics_on'),
cvox.QueueMode.QUEUE);
} else {
cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg('semantics_off'),
cvox.QueueMode.QUEUE);
}
break;
// Braille specific commands.
case cvox.BrailleKeyCommand.ROUTING:
var braille = cmdStruct.content;
if (braille) {
cvox.BrailleUtil.click(braille, cmdStruct.event.displayPosition);
}
break;
case cvox.BrailleKeyCommand.PAN_LEFT:
case cvox.BrailleKeyCommand.LINE_UP:
case cvox.BrailleKeyCommand.PAN_RIGHT:
case cvox.BrailleKeyCommand.LINE_DOWN:
// TODO(dtseng, plundblad): This needs to sync to the last pan position
// after line up/pan left and move the display to the far right on the
// line in case the synced to node is longer than one display line.
// Should also work with all widgets.
cvox.ChromeVox.navigationManager.navigate(false,
cvox.NavigationShifter.GRANULARITIES.LINE);
break;
case 'debug':
// TODO(stoarca): This doesn't belong here.
break;
case 'nop':
break;
default:
throw 'Command behavior not defined: ' + cmd;
}
if (errorMsg != '') {
cvox.ChromeVox.tts.speak(
cvox.ChromeVox.msgs.getMsg(errorMsg),
cvox.QueueMode.FLUSH,
cvox.AbstractTts.PERSONALITY_ANNOTATION);
} else if (cvox.ChromeVox.navigationManager.isReading()) {
if (cmdStruct.disallowContinuation) {
cvox.ChromeVox.navigationManager.stopReading(true);
} else if (cmd != 'readFromHere') {
cvox.ChromeVox.navigationManager.skip();
}
} else {
if (cmdStruct.announce) {
cvox.ChromeVox.navigationManager.finishNavCommand(prefixMsg);
}
}
if (!cmdStruct.allowEvents) {
cvox.ChromeVoxEventSuspender.exitSuspendEvents();
}
return !!cmdStruct.doDefault || ret;
};
/**
* Default handler for public user commands that are dispatched to the web app
* first so that the web developer can handle these commands instead of
* ChromeVox if they decide they can do a better job than the default algorithm.
*
* @param {Object} cvoxUserEvent The cvoxUserEvent to handle.
*/
cvox.ChromeVoxUserCommands.handleChromeVoxUserEvent = function(cvoxUserEvent) {
var detail = new cvox.UserEventDetail(cvoxUserEvent.detail);
if (detail.command) {
cvox.ChromeVoxUserCommands.doCommand_(
cvox.ChromeVoxUserCommands.lookupCommand_(detail.command, detail));
}
};
/**
* Returns an object containing information about the given command.
* @param {string} cmd The name of the command.
* @param {Object=} opt_kwargs Optional key values to add to the command
* structure.
* @return {Object} A key value mapping.
* @private
*/
cvox.ChromeVoxUserCommands.lookupCommand_ = function(cmd, opt_kwargs) {
var cmdStruct = cvox.CommandStore.CMD_WHITELIST[cmd];
if (!cmdStruct) {
throw 'Invalid command: ' + cmd;
}
cmdStruct = goog.object.clone(cmdStruct);
cmdStruct.command = cmd;
if (opt_kwargs) {
goog.object.extend(cmdStruct, opt_kwargs);
}
return cmdStruct;
};
cvox.ChromeVoxUserCommands.init_();