summaryrefslogtreecommitdiffstats
path: root/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/key_map.js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/key_map.js')
-rw-r--r--chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/key_map.js453
1 files changed, 453 insertions, 0 deletions
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/key_map.js b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/key_map.js
new file mode 100644
index 00000000000..c0bd759c6bc
--- /dev/null
+++ b/chromium/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/key_map.js
@@ -0,0 +1,453 @@
+// 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 This class provides a stable interface for initializing,
+ * querying, and modifying a ChromeVox key map.
+ *
+ * An instance contains an object-based bi-directional mapping from key binding
+ * to a function name of a user command (herein simply called a command).
+ * A caller is responsible for providing a JSON keymap (a simple Object key
+ * value structure), which has (key, command) key value pairs.
+ *
+ * Due to execution of user commands within the content script, the function
+ * name of the command is not explicitly checked within the background page via
+ * Closure. Any errors would only be caught at runtime.
+ *
+ * To retrieve static data about user commands, see both cvox.CommandStore and
+ * cvox.UserCommands.
+ */
+
+goog.provide('cvox.KeyMap');
+
+// TODO(dtseng): Only needed for sticky mode.
+goog.require('cvox.KeyUtil');
+goog.require('cvox.PlatformUtil');
+
+/**
+ * @param {Array.<Object.<string,
+ * {command: string, sequence: cvox.KeySequence}>>}
+ * commandsAndKeySequences An array of pairs - KeySequences and commands.
+ * @constructor
+ */
+cvox.KeyMap = function(commandsAndKeySequences) {
+ /**
+ * An array of bindings - commands and KeySequences.
+ * @type {Array.<Object.<string,
+ * {command: string, sequence: cvox.KeySequence}>>}
+ * @private
+ */
+ this.bindings_ = commandsAndKeySequences;
+
+ /**
+ * Maps a command to a key. This optimizes the process of searching for a
+ * key sequence when you already know the command.
+ * @type {Object.<string, cvox.KeySequence>}
+ * @private
+ */
+ this.commandToKey_ = {};
+ this.buildCommandToKey_();
+};
+
+
+/**
+ * Path to dir containing ChromeVox keymap json definitions.
+ * @type {string}
+ * @const
+ */
+cvox.KeyMap.KEYMAP_PATH = 'chromevox/background/keymaps/';
+
+
+/**
+ * An array of available key maps sorted by priority.
+ * (The first map is the default, the last is the least important).
+ * TODO(dtseng): Not really sure this belongs here, but it doesn't seem to be
+ * user configurable, so it doesn't make sense to json-stringify it.
+ * Should have class to siwtch among and manage multiple key maps.
+ * @type {Object.<string, Object.<string, string>>}
+ * @const
+ */
+cvox.KeyMap.AVAILABLE_MAP_INFO = {
+'keymap_classic': {
+ 'file': 'classic_keymap.json'
+ },
+'keymap_flat': {
+ 'file': 'flat_keymap.json'
+ },
+'keymap_experimental': {
+ 'file': 'experimental.json'
+ }
+};
+
+
+/**
+ * The index of the default key map info in cvox.KeyMap.AVAIABLE_KEYMAP_INFO.
+ * @type {number}
+ * @const
+ */
+cvox.KeyMap.DEFAULT_KEYMAP = 0;
+
+
+/**
+ * The number of mappings in the keymap.
+ * @return {number} The number of mappings.
+ */
+cvox.KeyMap.prototype.length = function() {
+ return this.bindings_.length;
+};
+
+
+/**
+ * Returns a copy of all KeySequences in this map.
+ * @return {Array.<cvox.KeySequence>} Array of all keys.
+ */
+cvox.KeyMap.prototype.keys = function() {
+ return this.bindings_.map(function(binding) {
+ return binding.sequence;
+ });
+};
+
+
+/**
+ * Returns a collection of command, KeySequence bindings.
+ * @return {Array.<Object.<string, cvox.KeySequence>>} Array of all command,
+ * key bindings.
+ * @suppress {checkTypes} inconsistent return type
+ * found : (Array.<(Object.<{command: string,
+ * sequence: (cvox.KeySequence|null)}>|null)>|null)
+ * required: (Array.<(Object.<(cvox.KeySequence|null)>|null)>|null)
+ */
+cvox.KeyMap.prototype.bindings = function() {
+ return this.bindings_;
+};
+
+
+/**
+ * This method is called when cvox.KeyMap instances are stringified via
+ * JSON.stringify.
+ * @return {string} The JSON representation of this instance.
+ */
+cvox.KeyMap.prototype.toJSON = function() {
+ return JSON.stringify({bindings: this.bindings_});
+};
+
+
+/**
+ * Writes to local storage.
+ */
+cvox.KeyMap.prototype.toLocalStorage = function() {
+ localStorage['keyBindings'] = this.toJSON();
+};
+
+
+/**
+ * Checks if this key map has a given binding.
+ * @param {string} command The command.
+ * @param {cvox.KeySequence} sequence The key sequence.
+ * @return {boolean} Whether the binding exists.
+ */
+cvox.KeyMap.prototype.hasBinding = function(command, sequence) {
+ if (this.commandToKey_ != null) {
+ return this.commandToKey_[command] == sequence;
+ } else {
+ for (var i = 0; i < this.bindings_.length; i++) {
+ var binding = this.bindings_[i];
+ if (binding.command == command && binding.sequence == sequence) {
+ return true;
+ }
+ }
+ }
+ return false;
+};
+
+
+/**
+ * Checks if this key map has a given command.
+ * @param {string} command The command to check.
+ * @return {boolean} Whether 'command' has a binding.
+ */
+cvox.KeyMap.prototype.hasCommand = function(command) {
+ if (this.commandToKey_ != null) {
+ return this.commandToKey_[command] != undefined;
+ } else {
+ for (var i = 0; i < this.bindings_.length; i++) {
+ var binding = this.bindings_[i];
+ if (binding.command == command) {
+ return true;
+ }
+ }
+ }
+ return false;
+};
+
+
+/**
+ * Checks if this key map has a given key.
+ * @param {cvox.KeySequence} key The key to check.
+ * @return {boolean} Whether 'key' has a binding.
+ */
+cvox.KeyMap.prototype.hasKey = function(key) {
+ for (var i = 0; i < this.bindings_.length; i++) {
+ var binding = this.bindings_[i];
+ if (binding.sequence.equals(key)) {
+ return true;
+ }
+ }
+ return false;
+};
+
+
+/**
+ * Gets a command given a key.
+ * @param {cvox.KeySequence} key The key to query.
+ * @return {?string} The command, if any.
+ */
+cvox.KeyMap.prototype.commandForKey = function(key) {
+ if (key != null) {
+ for (var i = 0; i < this.bindings_.length; i++) {
+ var binding = this.bindings_[i];
+ if (binding.sequence.equals(key)) {
+ return binding.command;
+ }
+ }
+ }
+ return null;
+};
+
+
+/**
+ * Gets a key given a command.
+ * @param {string} command The command to query.
+ * @return {!Array.<cvox.KeySequence>} The keys associated with that command,
+ * if any.
+ */
+cvox.KeyMap.prototype.keyForCommand = function(command) {
+ if (this.commandToKey_ != null) {
+ return [this.commandToKey_[command]];
+ } else {
+ var keySequenceArray = [];
+ for (var i = 0; i < this.bindings_.length; i++) {
+ var binding = this.bindings_[i];
+ if (binding.command == command) {
+ keySequenceArray.push(binding.sequence);
+ }
+ }
+ }
+ return (keySequenceArray.length > 0) ? keySequenceArray : [];
+};
+
+
+/**
+ * Merges an input map with this one. The merge preserves this instance's
+ * mappings. It only adds new bindings if there isn't one already.
+ * If either the incoming binding's command or key exist in this, it will be
+ * ignored.
+ * @param {!cvox.KeyMap} inputMap The map to merge with this.
+ * @return {boolean} True if there were no merge conflicts.
+ */
+cvox.KeyMap.prototype.merge = function(inputMap) {
+ var keys = inputMap.keys();
+ var cleanMerge = true;
+ for (var i = 0; i < keys.length; ++i) {
+ var key = keys[i];
+ var command = inputMap.commandForKey(key);
+ if (command == 'toggleStickyMode') {
+ // TODO(dtseng): More uglyness because of sticky key.
+ continue;
+ } else if (key && command &&
+ !this.hasKey(key) && !this.hasCommand(command)) {
+ this.bind_(command, key);
+ } else {
+ cleanMerge = false;
+ }
+ }
+ return cleanMerge;
+};
+
+
+/**
+ * Changes an existing key binding to a new key. If the key is already bound to
+ * a command, the rebind will fail.
+ * @param {string} command The command to set.
+ * @param {cvox.KeySequence} newKey The new key to assign it to.
+ * @return {boolean} Whether the rebinding succeeds.
+ */
+cvox.KeyMap.prototype.rebind = function(command, newKey) {
+ if (this.hasCommand(command) && !this.hasKey(newKey)) {
+ this.bind_(command, newKey);
+ return true;
+ }
+ return false;
+};
+
+
+/**
+ * Changes a key binding. Any existing bindings to the given key will be
+ * deleted. Use this.rebind to have non-overwrite behavior.
+ * @param {string} command The command to set.
+ * @param {cvox.KeySequence} newKey The new key to assign it to.
+ * @private
+ */
+cvox.KeyMap.prototype.bind_ = function(command, newKey) {
+ // TODO(dtseng): Need unit test to ensure command is valid for every *.json
+ // keymap.
+ var bound = false;
+ for (var i = 0; i < this.bindings_.length; i++) {
+ var binding = this.bindings_[i];
+ if (binding.command == command) {
+ // Replace the key with the new key.
+ delete binding.sequence;
+ binding.sequence = newKey;
+ if (this.commandToKey_ != null) {
+ this.commandToKey_[binding.command] = newKey;
+ }
+ bound = true;
+ }
+ }
+ if (!bound) {
+ var binding = {
+ 'command': command,
+ 'sequence': newKey
+ };
+ this.bindings_.push(binding);
+ this.commandToKey_[binding.command] = binding.sequence;
+ }
+};
+
+
+// TODO(dtseng): Move to a manager class.
+/**
+ * Convenience method for getting a default key map.
+ * @return {!cvox.KeyMap} The default key map.
+ */
+cvox.KeyMap.fromDefaults = function() {
+ return /** @type {!cvox.KeyMap} */ (
+ cvox.KeyMap.fromPath(cvox.KeyMap.KEYMAP_PATH +
+ cvox.KeyMap.AVAILABLE_MAP_INFO['keymap_classic'].file));
+};
+
+
+/**
+ * Convenience method for creating a key map based on a JSON (key, value) Object
+ * where the key is a literal keyboard string and value is a command string.
+ * @param {string} json The JSON.
+ * @return {cvox.KeyMap} The resulting object; null if unable to parse.
+ */
+cvox.KeyMap.fromJSON = function(json) {
+ try {
+ var commandsAndKeySequences =
+ /** @type {Array.<Object.<string,
+ * {command: string, sequence: cvox.KeySequence}>>} */
+ (JSON.parse(json).bindings);
+ commandsAndKeySequences = commandsAndKeySequences.filter(function(value) {
+ return value.sequence.platformFilter === undefined ||
+ cvox.PlatformUtil.matchesPlatform(value.sequence.platformFilter);
+ });
+ } catch (e) {
+ return null;
+ }
+
+ // Validate the type of the commandsAndKeySequences array.
+ if (typeof(commandsAndKeySequences) != 'object') {
+ return null;
+ }
+ for (var i = 0; i < commandsAndKeySequences.length; i++) {
+ if (commandsAndKeySequences[i].command == undefined ||
+ commandsAndKeySequences[i].sequence == undefined) {
+ return null;
+ } else {
+ commandsAndKeySequences[i].sequence = /** @type {cvox.KeySequence} */
+ (cvox.KeySequence.deserialize(commandsAndKeySequences[i].sequence));
+ }
+ }
+ return new cvox.KeyMap(commandsAndKeySequences);
+};
+
+
+/**
+ * Convenience method for creating a map local storage.
+ * @return {cvox.KeyMap} A map that reads from local storage.
+ */
+cvox.KeyMap.fromLocalStorage = function() {
+ if (localStorage['keyBindings']) {
+ return cvox.KeyMap.fromJSON(localStorage['keyBindings']);
+ }
+ return null;
+};
+
+
+/**
+ * Convenience method for creating a cvox.KeyMap based on a path.
+ * Warning: you should only call this within a background page context.
+ * @param {string} path A valid path of the form
+ * chromevox/background/keymaps/*.json.
+ * @return {cvox.KeyMap} A valid KeyMap object; null on error.
+ */
+cvox.KeyMap.fromPath = function(path) {
+ return cvox.KeyMap.fromJSON(cvox.KeyMap.readJSON_(path));
+};
+
+
+/**
+ * Convenience method for getting a currently selected key map.
+ * @return {!cvox.KeyMap} The currently selected key map.
+ */
+cvox.KeyMap.fromCurrentKeyMap = function() {
+ var map = localStorage['currentKeyMap'];
+ if (map && cvox.KeyMap.AVAILABLE_MAP_INFO[map]) {
+ return /** @type {!cvox.KeyMap} */ (cvox.KeyMap.fromPath(
+ cvox.KeyMap.KEYMAP_PATH + cvox.KeyMap.AVAILABLE_MAP_INFO[map].file));
+ } else {
+ return cvox.KeyMap.fromDefaults();
+ }
+};
+
+
+/**
+ * Takes a path to a JSON file and returns a JSON Object.
+ * @param {string} path Contains the path to a JSON file.
+ * @return {string} JSON.
+ * @private
+ * @suppress {missingProperties}
+ */
+cvox.KeyMap.readJSON_ = function(path) {
+ var url = chrome.extension.getURL(path);
+ if (!url) {
+ throw 'Invalid path: ' + path;
+ }
+
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url, false);
+ xhr.send();
+ return xhr.responseText;
+};
+
+
+/**
+ * Resets the default modifier keys.
+ * TODO(dtseng): Move elsewhere when we figure out our localStorage story.
+ */
+cvox.KeyMap.prototype.resetModifier = function() {
+ localStorage['cvoxKey'] = cvox.ChromeVox.modKeyStr;
+};
+
+
+/**
+ * Builds the map of commands to keys.
+ * @private
+ */
+cvox.KeyMap.prototype.buildCommandToKey_ = function() {
+ // TODO (dtseng): What about more than one sequence mapped to the same
+ // command?
+ for (var i = 0; i < this.bindings_.length; i++) {
+ var binding = this.bindings_[i];
+ if (this.commandToKey_[binding.command] != undefined) {
+ // There's at least two key sequences mapped to the same
+ // command. continue.
+ continue;
+ }
+ this.commandToKey_[binding.command] = binding.sequence;
+ }
+};