Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(878)

Unified Diff: chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js

Issue 2150623002: Refactor: Extract a KeyboardHandler and CommandHandler from Background (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix test. Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js
new file mode 100644
index 0000000000000000000000000000000000000000..a12746031dc1b2101ed3beaa8174a5a0da17b8e4
--- /dev/null
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js
@@ -0,0 +1,552 @@
+// Copyright 2016 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 ChromeVox commands.
+ */
+
+goog.provide('CommandHandler');
+
+goog.require('ChromeVoxState');
+goog.require('Output');
+goog.require('cvox.ChromeVoxBackground');
+
+goog.scope(function() {
+var AutomationNode = chrome.automation.AutomationNode;
+var Dir = constants.Dir;
+var EventType = chrome.automation.EventType;
+var RoleType = chrome.automation.RoleType;
+
+/**
+ * Handles ChromeVox Next commands.
+ * @param {string} command
+ * @return {boolean} True if the command should propagate.
+ */
+CommandHandler.onCommand = function(command) {
+ // Check for loss of focus which results in us invalidating our current
+ // range. Note this call is synchronis.
+ chrome.automation.getFocus(function(focusedNode) {
+ var cur = ChromeVoxState.instance.currentRange;
+ if (cur && !cur.isValid()) {
+ ChromeVoxState.instance.setCurrentRange(
+ cursors.Range.fromNode(focusedNode));
+ }
+
+ if (!focusedNode)
+ ChromeVoxState.instance.setCurrentRange(null);
+ });
+
+ // These commands don't require a current range and work in all modes.
+ switch (command) {
+ case 'speakTimeAndDate':
+ chrome.automation.getDesktop(function(d) {
+ // First, try speaking the on-screen time.
+ var allTime = d.findAll({role: RoleType.time});
+ allTime.filter(function(t) { return t.root.role == RoleType.desktop; });
+
+ var timeString = '';
+ allTime.forEach(function(t) {
+ if (t.name) timeString = t.name;
+ });
+ if (timeString) {
+ cvox.ChromeVox.tts.speak(timeString, cvox.QueueMode.FLUSH);
+ } else {
+ // Fallback to the old way of speaking time.
+ var output = new Output();
+ var dateTime = new Date();
+ output
+ .withString(
+ dateTime.toLocaleTimeString() + ', ' +
+ dateTime.toLocaleDateString())
+ .go();
+ }
+ });
+ return false;
+ case 'showOptionsPage':
+ chrome.runtime.openOptionsPage();
+ break;
+ case 'toggleChromeVox':
+ if (cvox.ChromeVox.isChromeOS)
+ return false;
+
+ cvox.ChromeVox.isActive = !cvox.ChromeVox.isActive;
+ if (!cvox.ChromeVox.isActive) {
+ var msg = Msgs.getMsg('chromevox_inactive');
+ cvox.ChromeVox.tts.speak(msg, cvox.QueueMode.FLUSH);
+ return false;
+ }
+ break;
+ case 'toggleStickyMode':
+ cvox.ChromeVoxBackground.setPref(
+ 'sticky', !cvox.ChromeVox.isStickyPrefOn, true);
+
+ if (cvox.ChromeVox.isStickyPrefOn)
+ chrome.accessibilityPrivate.setKeyboardListener(true, true);
+ else
+ chrome.accessibilityPrivate.setKeyboardListener(true, false);
+ return false;
+ case 'passThroughMode':
+ cvox.ChromeVox.passThroughMode = true;
+ cvox.ChromeVox.tts.speak(
+ Msgs.getMsg('pass_through_key'), cvox.QueueMode.QUEUE);
+ return true;
+ case 'showKbExplorerPage':
+ var explorerPage = {url: 'chromevox/background/kbexplorer.html'};
+ chrome.tabs.create(explorerPage);
+ break;
+ case 'decreaseTtsRate':
+ CommandHandler.increaseOrDecreaseSpeechProperty_(
+ cvox.AbstractTts.RATE, false);
+ return false;
+ case 'increaseTtsRate':
+ CommandHandler.increaseOrDecreaseSpeechProperty_(
+ cvox.AbstractTts.RATE, true);
+ return false;
+ case 'decreaseTtsPitch':
+ CommandHandler.increaseOrDecreaseSpeechProperty_(
+ cvox.AbstractTts.PITCH, false);
+ return false;
+ case 'increaseTtsPitch':
+ CommandHandler.increaseOrDecreaseSpeechProperty_(
+ cvox.AbstractTts.PITCH, true);
+ return false;
+ case 'decreaseTtsVolume':
+ CommandHandler.increaseOrDecreaseSpeechProperty_(
+ cvox.AbstractTts.VOLUME, false);
+ return false;
+ case 'increaseTtsVolume':
+ CommandHandler.increaseOrDecreaseSpeechProperty_(
+ cvox.AbstractTts.VOLUME, true);
+ return false;
+ case 'stopSpeech':
+ cvox.ChromeVox.tts.stop();
+ ChromeVoxState.isReadingContinuously = false;
+ return false;
+ case 'toggleEarcons':
+ cvox.AbstractEarcons.enabled = !cvox.AbstractEarcons.enabled;
+ var announce = cvox.AbstractEarcons.enabled ? Msgs.getMsg('earcons_on') :
+ Msgs.getMsg('earcons_off');
+ cvox.ChromeVox.tts.speak(
+ announce, cvox.QueueMode.FLUSH,
+ cvox.AbstractTts.PERSONALITY_ANNOTATION);
+ return false;
+ case 'cycleTypingEcho':
+ cvox.ChromeVox.typingEcho =
+ cvox.TypingEcho.cycle(cvox.ChromeVox.typingEcho);
+ var announce = '';
+ switch (cvox.ChromeVox.typingEcho) {
+ case cvox.TypingEcho.CHARACTER:
+ announce = Msgs.getMsg('character_echo');
+ break;
+ case cvox.TypingEcho.WORD:
+ announce = Msgs.getMsg('word_echo');
+ break;
+ case cvox.TypingEcho.CHARACTER_AND_WORD:
+ announce = Msgs.getMsg('character_and_word_echo');
+ break;
+ case cvox.TypingEcho.NONE:
+ announce = Msgs.getMsg('none_echo');
+ break;
+ }
+ cvox.ChromeVox.tts.speak(
+ announce, cvox.QueueMode.FLUSH,
+ cvox.AbstractTts.PERSONALITY_ANNOTATION);
+ return false;
+ case 'cyclePunctuationEcho':
+ cvox.ChromeVox.tts.speak(
+ Msgs.getMsg(ChromeVoxState.backgroundTts.cyclePunctuationEcho()),
+ cvox.QueueMode.FLUSH);
+ return false;
+ case 'reportIssue':
+ var url = Background.ISSUE_URL;
+ var description = {};
+ description['Mode'] = ChromeVoxState.instance.mode;
+ description['Version'] = chrome.app.getDetails().version;
+ description['Reproduction Steps'] = '%0a1.%0a2.%0a3.';
+ for (var key in description)
+ url += key + ':%20' + description[key] + '%0a';
+ chrome.tabs.create({url: url});
+ return false;
+ case 'toggleBrailleCaptions':
+ cvox.BrailleCaptionsBackground.setActive(
+ !cvox.BrailleCaptionsBackground.isEnabled());
+ return false;
+ case 'toggleChromeVoxVersion':
+ if (!ChromeVoxState.instance.toggleNext())
+ return false;
+ if (ChromeVoxState.instance.currentRange) {
+ ChromeVoxState.instance.navigateToRange(
+ ChromeVoxState.instance.currentRange);
+ }
+ break;
+ case 'showNextUpdatePage':
+ (new PanelCommand(PanelCommandType.TUTORIAL)).send();
+ return false;
+ default:
+ break;
+ }
+
+ // Require a current range.
+ if (!ChromeVoxState.instance.currentRange_)
+ return true;
+
+ // Next/compat commands hereafter.
+ if (ChromeVoxState.instance.mode == ChromeVoxMode.CLASSIC) return true;
+
+ var current = ChromeVoxState.instance.currentRange_;
+ var dir = Dir.FORWARD;
+ var pred = null;
+ var predErrorMsg = undefined;
+ var speechProps = {};
+ switch (command) {
+ case 'nextCharacter':
+ speechProps['phoneticCharacters'] = true;
+ current = current.move(cursors.Unit.CHARACTER, Dir.FORWARD);
+ break;
+ case 'previousCharacter':
+ speechProps['phoneticCharacters'] = true;
+ current = current.move(cursors.Unit.CHARACTER, Dir.BACKWARD);
+ break;
+ case 'nextWord':
+ current = current.move(cursors.Unit.WORD, Dir.FORWARD);
+ break;
+ case 'previousWord':
+ current = current.move(cursors.Unit.WORD, Dir.BACKWARD);
+ break;
+ case 'forward':
+ case 'nextLine':
+ current = current.move(cursors.Unit.LINE, Dir.FORWARD);
+ break;
+ case 'backward':
+ case 'previousLine':
+ current = current.move(cursors.Unit.LINE, Dir.BACKWARD);
+ break;
+ case 'nextButton':
+ dir = Dir.FORWARD;
+ pred = AutomationPredicate.button;
+ predErrorMsg = 'no_next_button';
+ break;
+ case 'previousButton':
+ dir = Dir.BACKWARD;
+ pred = AutomationPredicate.button;
+ predErrorMsg = 'no_previous_button';
+ break;
+ case 'nextCheckbox':
+ dir = Dir.FORWARD;
+ pred = AutomationPredicate.checkBox;
+ predErrorMsg = 'no_next_checkbox';
+ break;
+ case 'previousCheckbox':
+ dir = Dir.BACKWARD;
+ pred = AutomationPredicate.checkBox;
+ predErrorMsg = 'no_previous_checkbox';
+ break;
+ case 'nextComboBox':
+ dir = Dir.FORWARD;
+ pred = AutomationPredicate.comboBox;
+ predErrorMsg = 'no_next_combo_box';
+ break;
+ case 'previousComboBox':
+ dir = Dir.BACKWARD;
+ pred = AutomationPredicate.comboBox;
+ predErrorMsg = 'no_previous_combo_box';
+ break;
+ case 'nextEditText':
+ dir = Dir.FORWARD;
+ pred = AutomationPredicate.editText;
+ predErrorMsg = 'no_next_edit_text';
+ break;
+ case 'previousEditText':
+ dir = Dir.BACKWARD;
+ pred = AutomationPredicate.editText;
+ predErrorMsg = 'no_previous_edit_text';
+ break;
+ case 'nextFormField':
+ dir = Dir.FORWARD;
+ pred = AutomationPredicate.formField;
+ predErrorMsg = 'no_next_form_field';
+ break;
+ case 'previousFormField':
+ dir = Dir.BACKWARD;
+ pred = AutomationPredicate.formField;
+ predErrorMsg = 'no_previous_form_field';
+ break;
+ case 'nextHeading':
+ dir = Dir.FORWARD;
+ pred = AutomationPredicate.heading;
+ predErrorMsg = 'no_next_heading';
+ break;
+ case 'previousHeading':
+ dir = Dir.BACKWARD;
+ pred = AutomationPredicate.heading;
+ predErrorMsg = 'no_previous_heading';
+ break;
+ case 'nextLink':
+ dir = Dir.FORWARD;
+ pred = AutomationPredicate.link;
+ predErrorMsg = 'no_next_link';
+ break;
+ case 'previousLink':
+ dir = Dir.BACKWARD;
+ pred = AutomationPredicate.link;
+ predErrorMsg = 'no_previous_link';
+ break;
+ case 'nextTable':
+ dir = Dir.FORWARD;
+ pred = AutomationPredicate.table;
+ predErrorMsg = 'no_next_table';
+ break;
+ case 'previousTable':
+ dir = Dir.BACKWARD;
+ pred = AutomationPredicate.table;
+ predErrorMsg = 'no_previous_table';
+ break;
+ case 'nextVisitedLink':
+ dir = Dir.FORWARD;
+ pred = AutomationPredicate.visitedLink;
+ predErrorMsg = 'no_next_visited_link';
+ break;
+ case 'previousVisitedLink':
+ dir = Dir.BACKWARD;
+ pred = AutomationPredicate.visitedLink;
+ predErrorMsg = 'no_previous_visited_link';
+ break;
+ case 'right':
+ case 'nextObject':
+ current = current.move(cursors.Unit.DOM_NODE, Dir.FORWARD);
+ break;
+ case 'left':
+ case 'previousObject':
+ current = current.move(cursors.Unit.DOM_NODE, Dir.BACKWARD);
+ break;
+ case 'jumpToTop':
+ var node = AutomationUtil.findNodePost(
+ current.start.node.root, Dir.FORWARD, AutomationPredicate.leaf);
+ if (node)
+ current = cursors.Range.fromNode(node);
+ break;
+ case 'jumpToBottom':
+ var node = AutomationUtil.findNodePost(
+ current.start.node.root, Dir.BACKWARD, AutomationPredicate.leaf);
+ if (node)
+ current = cursors.Range.fromNode(node);
+ break;
+ case 'forceClickOnCurrentItem':
+ if (ChromeVoxState.instance.currentRange_) {
+ var actionNode = ChromeVoxState.instance.currentRange_.start.node;
+ if (actionNode.role == RoleType.inlineTextBox)
+ actionNode = actionNode.parent;
+ actionNode.doDefault();
+ }
+ // Skip all other processing; if focus changes, we should get an event
+ // for that.
+ return false;
+ case 'readFromHere':
+ ChromeVoxState.isReadingContinuously = true;
+ var continueReading = function() {
+ if (!ChromeVoxState.isReadingContinuously ||
+ !ChromeVoxState.instance.currentRange_)
+ return;
+
+ var prevRange = ChromeVoxState.instance.currentRange_;
+ var newRange =
+ ChromeVoxState.instance.currentRange_.move(
+ cursors.Unit.DOM_NODE, Dir.FORWARD);
+
+ // Stop if we've wrapped back to the document.
+ var maybeDoc = newRange.start.node;
+ if (maybeDoc.role == RoleType.rootWebArea &&
+ maybeDoc.parent.root.role == RoleType.desktop) {
+ ChromeVoxState.isReadingContinuously = false;
+ return;
+ }
+
+ ChromeVoxState.instance.setCurrentRange(newRange);
+
+ new Output()
+ .withRichSpeechAndBraille(ChromeVoxState.instance.currentRange_,
+ prevRange,
+ Output.EventType.NAVIGATE)
+ .onSpeechEnd(continueReading)
+ .go();
+ }.bind(this);
+
+ new Output()
+ .withRichSpeechAndBraille(ChromeVoxState.instance.currentRange_,
+ null,
+ Output.EventType.NAVIGATE)
+ .onSpeechEnd(continueReading)
+ .go();
+
+ return false;
+ case 'contextMenu':
+ if (ChromeVoxState.instance.currentRange_) {
+ var actionNode = ChromeVoxState.instance.currentRange_.start.node;
+ if (actionNode.role == RoleType.inlineTextBox)
+ actionNode = actionNode.parent;
+ actionNode.showContextMenu();
+ return false;
+ }
+ break;
+ case 'toggleKeyboardHelp':
+ ChromeVoxState.instance.startExcursion();
+ (new PanelCommand(PanelCommandType.OPEN_MENUS)).send();
+ return false;
+ case 'showHeadingsList':
+ ChromeVoxState.instance.startExcursion();
+ (new PanelCommand(PanelCommandType.OPEN_MENUS, 'role_heading')).send();
+ return false;
+ case 'showFormsList':
+ ChromeVoxState.instance.startExcursion();
+ (new PanelCommand(PanelCommandType.OPEN_MENUS, 'role_form')).send();
+ return false;
+ case 'showLandmarksList':
+ ChromeVoxState.instance.startExcursion();
+ (new PanelCommand(PanelCommandType.OPEN_MENUS, 'role_landmark')).send();
+ return false;
+ case 'showLinksList':
+ ChromeVoxState.instance.startExcursion();
+ (new PanelCommand(PanelCommandType.OPEN_MENUS, 'role_link')).send();
+ return false;
+ case 'showTablesList':
+ ChromeVoxState.instance.startExcursion();
+ (new PanelCommand(PanelCommandType.OPEN_MENUS, 'table_strategy')).send();
+ return false;
+ case 'toggleSearchWidget':
+ (new PanelCommand(PanelCommandType.SEARCH)).send();
+ return false;
+ case 'readCurrentTitle':
+ var target = ChromeVoxState.instance.currentRange_.start.node;
+ var output = new Output();
+
+ if (target.root.role == RoleType.rootWebArea) {
+ // Web.
+ target = target.root;
+ output.withString(target.name || target.docUrl);
+ } else {
+ // Views.
+ while (target.role != RoleType.window) target = target.parent;
+ if (target)
+ output.withString(target.name || '');
+ }
+ output.go();
+ return false;
+ case 'readCurrentURL':
+ var output = new Output();
+ var target = ChromeVoxState.instance.currentRange_.start.node.root;
+ output.withString(target.docUrl || '').go();
+ return false;
+ case 'copy':
+ var textarea = document.createElement('textarea');
+ document.body.appendChild(textarea);
+ textarea.focus();
+ document.execCommand('paste');
+ var clipboardContent = textarea.value;
+ textarea.remove();
+ cvox.ChromeVox.tts.speak(
+ Msgs.getMsg('copy', [clipboardContent]), cvox.QueueMode.FLUSH);
+ ChromeVoxState.instance.pageSel_ = null;
+ return true;
+ case 'toggleSelection':
+ if (!ChromeVoxState.instance.pageSel_) {
+ ChromeVoxState.instance.pageSel_ = ChromeVoxState.instance.currentRange;
+ } else {
+ var root = ChromeVoxState.instance.currentRange_.start.node.root;
+ if (root && root.anchorObject && root.focusObject) {
+ var sel = new cursors.Range(
+ new cursors.Cursor(root.anchorObject, root.anchorOffset),
+ new cursors.Cursor(root.focusObject, root.focusOffset));
+ var o = new Output()
+ .format('@end_selection')
+ .withSpeechAndBraille(sel, sel, Output.EventType.NAVIGATE)
+ .go();
+ }
+ ChromeVoxState.instance.pageSel_ = null;
+ return false;
+ }
+ break;
+ default:
+ return true;
+ }
+
+ if (pred) {
+ var bound = current.getBound(dir).node;
+ if (bound) {
+ var node = AutomationUtil.findNextNode(
+ bound, dir, pred, {skipInitialAncestry: true});
+
+ if (node) {
+ node = AutomationUtil.findNodePre(
+ node, Dir.FORWARD, AutomationPredicate.object) ||
+ node;
+ }
+
+ if (node) {
+ current = cursors.Range.fromNode(node);
+ } else {
+ if (predErrorMsg) {
+ cvox.ChromeVox.tts.speak(
+ Msgs.getMsg(predErrorMsg), cvox.QueueMode.FLUSH);
+ }
+ return false;
+ }
+ }
+ }
+
+ if (current)
+ ChromeVoxState.instance.navigateToRange(current, undefined, speechProps);
+
+ return false;
+};
+
+/**
+ * React to mode changes.
+ * @param {ChromeVoxMode} newMode
+ * @param {ChromeVoxMode?} oldMode
+ */
+CommandHandler.onModeChanged = function(newMode, oldMode) {
+ // Previously uninitialized.
+ if (!oldMode)
+ cvox.ChromeVoxKbHandler.commandHandler = CommandHandler.onCommand;
+
+ var hasListener =
+ chrome.commands.onCommand.hasListener(CommandHandler.onCommand);
+ if (newMode == ChromeVoxMode.CLASSIC && hasListener)
+ chrome.commands.onCommand.removeListener(CommandHandler.onCommand);
+ else if (newMode == ChromeVoxMode.CLASSIC && !hasListener)
+ chrome.commands.onCommand.addListener(CommandHandler.onCommand);
+};
+
+/**
+ * Increase or decrease a speech property and make an announcement.
+ * @param {string} propertyName The name of the property to change.
+ * @param {boolean} increase If true, increases the property value by one
+ * step size, otherwise decreases.
+ * @private
+ */
+CommandHandler.increaseOrDecreaseSpeechProperty_ =
+ function(propertyName, increase) {
+ cvox.ChromeVox.tts.increaseOrDecreaseProperty(propertyName, increase);
+ var announcement;
+ var valueAsPercent = Math.round(
+ cvox.ChromeVox.tts.propertyToPercentage(propertyName) * 100);
+ switch (propertyName) {
+ case cvox.AbstractTts.RATE:
+ announcement = Msgs.getMsg('announce_rate', [valueAsPercent]);
+ break;
+ case cvox.AbstractTts.PITCH:
+ announcement = Msgs.getMsg('announce_pitch', [valueAsPercent]);
+ break;
+ case cvox.AbstractTts.VOLUME:
+ announcement = Msgs.getMsg('announce_volume', [valueAsPercent]);
+ break;
+ }
+ if (announcement) {
+ cvox.ChromeVox.tts.speak(
+ announcement, cvox.QueueMode.FLUSH,
+ cvox.AbstractTts.PERSONALITY_ANNOTATION);
+ }
+};
+
+}); // goog.scope

Powered by Google App Engine
This is Rietveld 408576698