summaryrefslogtreecommitdiff
path: root/chromium/chrome/common/extensions/command.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/common/extensions/command.cc')
-rw-r--r--chromium/chrome/common/extensions/command.cc542
1 files changed, 542 insertions, 0 deletions
diff --git a/chromium/chrome/common/extensions/command.cc b/chromium/chrome/common/extensions/command.cc
new file mode 100644
index 00000000000..aba488f8c64
--- /dev/null
+++ b/chromium/chrome/common/extensions/command.cc
@@ -0,0 +1,542 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/command.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+#include "ui/base/accelerators/media_keys_listener.h"
+
+namespace extensions {
+
+namespace errors = manifest_errors;
+namespace keys = manifest_keys;
+namespace values = manifest_values;
+
+namespace {
+
+static const char kMissing[] = "Missing";
+
+static const char kCommandKeyNotSupported[] =
+ "Command key is not supported. Note: Ctrl means Command on Mac";
+
+#if defined(OS_CHROMEOS)
+// ChromeOS supports an additional modifier 'Search', which can result in longer
+// sequences.
+static const int kMaxTokenSize = 4;
+#else
+static const int kMaxTokenSize = 3;
+#endif // OS_CHROMEOS
+
+Command::Type GetCommandType(const std::string& command_name) {
+ if (command_name == values::kPageActionCommandEvent)
+ return Command::Type::kPageAction;
+ if (command_name == values::kBrowserActionCommandEvent)
+ return Command::Type::kBrowserAction;
+ return Command::Type::kNamed;
+}
+
+bool IsNamedCommand(const std::string& command_name) {
+ return GetCommandType(command_name) == Command::Type::kNamed;
+}
+
+bool DoesRequireModifier(const std::string& accelerator) {
+ return accelerator != values::kKeyMediaNextTrack &&
+ accelerator != values::kKeyMediaPlayPause &&
+ accelerator != values::kKeyMediaPrevTrack &&
+ accelerator != values::kKeyMediaStop;
+}
+
+// Parse an |accelerator| for a given platform (specified by |platform_key|) and
+// return the result as a ui::Accelerator if successful, or VKEY_UNKNOWN if not.
+// |index| is used when constructing an |error| messages to show which command
+// in the manifest is failing and |should_parse_media_keys| specifies whether
+// media keys are to be considered for parsing.
+// Note: If the parsing rules here are changed, make sure to update the
+// corresponding extension_command_list.js validation, which validates the user
+// input for chrome://extensions/configureCommands.
+ui::Accelerator ParseImpl(const std::string& accelerator,
+ const std::string& platform_key,
+ int index,
+ bool should_parse_media_keys,
+ base::string16* error) {
+ error->clear();
+ if (platform_key != values::kKeybindingPlatformWin &&
+ platform_key != values::kKeybindingPlatformMac &&
+ platform_key != values::kKeybindingPlatformChromeOs &&
+ platform_key != values::kKeybindingPlatformLinux &&
+ platform_key != values::kKeybindingPlatformDefault) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidKeyBindingUnknownPlatform, base::NumberToString(index),
+ platform_key);
+ return ui::Accelerator();
+ }
+
+ std::vector<std::string> tokens = base::SplitString(
+ accelerator, "+", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ if (tokens.size() == 0 ||
+ (tokens.size() == 1 && DoesRequireModifier(accelerator)) ||
+ tokens.size() > kMaxTokenSize) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidKeyBinding,
+ base::NumberToString(index),
+ platform_key, accelerator);
+ return ui::Accelerator();
+ }
+
+ // Now, parse it into an accelerator.
+ int modifiers = ui::EF_NONE;
+ ui::KeyboardCode key = ui::VKEY_UNKNOWN;
+ for (size_t i = 0; i < tokens.size(); i++) {
+ if (tokens[i] == values::kKeyCtrl) {
+ modifiers |= ui::EF_CONTROL_DOWN;
+ } else if (tokens[i] == values::kKeyCommand) {
+ if (platform_key == values::kKeybindingPlatformMac) {
+ // Either the developer specified Command+foo in the manifest for Mac or
+ // they specified Ctrl and it got normalized to Command (to get Ctrl on
+ // Mac the developer has to specify MacCtrl). Therefore we treat this
+ // as Command.
+ modifiers |= ui::EF_COMMAND_DOWN;
+#if defined(OS_MACOSX)
+ } else if (platform_key == values::kKeybindingPlatformDefault) {
+ // If we see "Command+foo" in the Default section it can mean two
+ // things, depending on the platform:
+ // The developer specified "Ctrl+foo" for Default and it got normalized
+ // on Mac to "Command+foo". This is fine. Treat it as Command.
+ modifiers |= ui::EF_COMMAND_DOWN;
+#endif
+ } else {
+ // No other platform supports Command.
+ key = ui::VKEY_UNKNOWN;
+ break;
+ }
+ } else if (tokens[i] == values::kKeySearch) {
+ // Search is a special modifier only on ChromeOS and maps to 'Command'.
+ if (platform_key == values::kKeybindingPlatformChromeOs) {
+ modifiers |= ui::EF_COMMAND_DOWN;
+ } else {
+ // No other platform supports Search.
+ key = ui::VKEY_UNKNOWN;
+ break;
+ }
+ } else if (tokens[i] == values::kKeyAlt) {
+ modifiers |= ui::EF_ALT_DOWN;
+ } else if (tokens[i] == values::kKeyShift) {
+ modifiers |= ui::EF_SHIFT_DOWN;
+ } else if (tokens[i].size() == 1 || // A-Z, 0-9.
+ tokens[i] == values::kKeyComma ||
+ tokens[i] == values::kKeyPeriod ||
+ tokens[i] == values::kKeyUp ||
+ tokens[i] == values::kKeyDown ||
+ tokens[i] == values::kKeyLeft ||
+ tokens[i] == values::kKeyRight ||
+ tokens[i] == values::kKeyIns ||
+ tokens[i] == values::kKeyDel ||
+ tokens[i] == values::kKeyHome ||
+ tokens[i] == values::kKeyEnd ||
+ tokens[i] == values::kKeyPgUp ||
+ tokens[i] == values::kKeyPgDwn ||
+ tokens[i] == values::kKeySpace ||
+ tokens[i] == values::kKeyTab ||
+ tokens[i] == values::kKeyMediaNextTrack ||
+ tokens[i] == values::kKeyMediaPlayPause ||
+ tokens[i] == values::kKeyMediaPrevTrack ||
+ tokens[i] == values::kKeyMediaStop) {
+ if (key != ui::VKEY_UNKNOWN) {
+ // Multiple key assignments.
+ key = ui::VKEY_UNKNOWN;
+ break;
+ }
+
+ if (tokens[i] == values::kKeyComma) {
+ key = ui::VKEY_OEM_COMMA;
+ } else if (tokens[i] == values::kKeyPeriod) {
+ key = ui::VKEY_OEM_PERIOD;
+ } else if (tokens[i] == values::kKeyUp) {
+ key = ui::VKEY_UP;
+ } else if (tokens[i] == values::kKeyDown) {
+ key = ui::VKEY_DOWN;
+ } else if (tokens[i] == values::kKeyLeft) {
+ key = ui::VKEY_LEFT;
+ } else if (tokens[i] == values::kKeyRight) {
+ key = ui::VKEY_RIGHT;
+ } else if (tokens[i] == values::kKeyIns) {
+ key = ui::VKEY_INSERT;
+ } else if (tokens[i] == values::kKeyDel) {
+ key = ui::VKEY_DELETE;
+ } else if (tokens[i] == values::kKeyHome) {
+ key = ui::VKEY_HOME;
+ } else if (tokens[i] == values::kKeyEnd) {
+ key = ui::VKEY_END;
+ } else if (tokens[i] == values::kKeyPgUp) {
+ key = ui::VKEY_PRIOR;
+ } else if (tokens[i] == values::kKeyPgDwn) {
+ key = ui::VKEY_NEXT;
+ } else if (tokens[i] == values::kKeySpace) {
+ key = ui::VKEY_SPACE;
+ } else if (tokens[i] == values::kKeyTab) {
+ key = ui::VKEY_TAB;
+ } else if (tokens[i] == values::kKeyMediaNextTrack &&
+ should_parse_media_keys) {
+ key = ui::VKEY_MEDIA_NEXT_TRACK;
+ } else if (tokens[i] == values::kKeyMediaPlayPause &&
+ should_parse_media_keys) {
+ key = ui::VKEY_MEDIA_PLAY_PAUSE;
+ } else if (tokens[i] == values::kKeyMediaPrevTrack &&
+ should_parse_media_keys) {
+ key = ui::VKEY_MEDIA_PREV_TRACK;
+ } else if (tokens[i] == values::kKeyMediaStop &&
+ should_parse_media_keys) {
+ key = ui::VKEY_MEDIA_STOP;
+ } else if (tokens[i].size() == 1 && base::IsAsciiUpper(tokens[i][0])) {
+ key = static_cast<ui::KeyboardCode>(ui::VKEY_A + (tokens[i][0] - 'A'));
+ } else if (tokens[i].size() == 1 && base::IsAsciiDigit(tokens[i][0])) {
+ key = static_cast<ui::KeyboardCode>(ui::VKEY_0 + (tokens[i][0] - '0'));
+ } else {
+ key = ui::VKEY_UNKNOWN;
+ break;
+ }
+ } else {
+ *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidKeyBinding,
+ base::NumberToString(index),
+ platform_key, accelerator);
+ return ui::Accelerator();
+ }
+ }
+
+ bool command = (modifiers & ui::EF_COMMAND_DOWN) != 0;
+ bool ctrl = (modifiers & ui::EF_CONTROL_DOWN) != 0;
+ bool alt = (modifiers & ui::EF_ALT_DOWN) != 0;
+ bool shift = (modifiers & ui::EF_SHIFT_DOWN) != 0;
+
+ // We support Ctrl+foo, Alt+foo, Ctrl+Shift+foo, Alt+Shift+foo, but not
+ // Ctrl+Alt+foo and not Shift+foo either. For a more detailed reason why we
+ // don't support Ctrl+Alt+foo see this article:
+ // http://blogs.msdn.com/b/oldnewthing/archive/2004/03/29/101121.aspx.
+ // On Mac Command can also be used in combination with Shift or on its own,
+ // as a modifier.
+ if (key == ui::VKEY_UNKNOWN || (ctrl && alt) || (command && alt) ||
+ (shift && !ctrl && !alt && !command)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidKeyBinding,
+ base::NumberToString(index),
+ platform_key, accelerator);
+ return ui::Accelerator();
+ }
+
+ if (ui::MediaKeysListener::IsMediaKeycode(key) &&
+ (shift || ctrl || alt || command)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidKeyBindingMediaKeyWithModifier,
+ base::NumberToString(index), platform_key, accelerator);
+ return ui::Accelerator();
+ }
+
+ return ui::Accelerator(key, modifiers);
+}
+
+// For Mac, we convert "Ctrl" to "Command" and "MacCtrl" to "Ctrl". Other
+// platforms leave the shortcut untouched.
+std::string NormalizeShortcutSuggestion(const std::string& suggestion,
+ const std::string& platform) {
+ bool normalize = false;
+ if (platform == values::kKeybindingPlatformMac) {
+ normalize = true;
+ } else if (platform == values::kKeybindingPlatformDefault) {
+#if defined(OS_MACOSX)
+ normalize = true;
+#endif
+ }
+
+ if (!normalize)
+ return suggestion;
+
+ std::vector<base::StringPiece> tokens = base::SplitStringPiece(
+ suggestion, "+", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ for (size_t i = 0; i < tokens.size(); i++) {
+ if (tokens[i] == values::kKeyCtrl)
+ tokens[i] = values::kKeyCommand;
+ else if (tokens[i] == values::kKeyMacCtrl)
+ tokens[i] = values::kKeyCtrl;
+ }
+ return base::JoinString(tokens, "+");
+}
+
+} // namespace
+
+Command::Command() : global_(false), type_(Type::kNamed) {}
+
+Command::Command(const std::string& command_name,
+ const base::string16& description,
+ const std::string& accelerator,
+ bool global)
+ : command_name_(command_name),
+ description_(description),
+ global_(global),
+ type_(GetCommandType(command_name)) {
+ base::string16 error;
+ accelerator_ = ParseImpl(accelerator, CommandPlatform(), 0,
+ type_ == Type::kNamed, &error);
+}
+
+Command::Command(const Command& other) = default;
+
+Command::~Command() {}
+
+// static
+std::string Command::CommandPlatform() {
+#if defined(OS_WIN)
+ return values::kKeybindingPlatformWin;
+#elif defined(OS_MACOSX)
+ return values::kKeybindingPlatformMac;
+#elif defined(OS_CHROMEOS)
+ return values::kKeybindingPlatformChromeOs;
+#elif defined(OS_LINUX)
+ return values::kKeybindingPlatformLinux;
+#else
+ return "";
+#endif
+}
+
+// static
+ui::Accelerator Command::StringToAccelerator(const std::string& accelerator,
+ const std::string& command_name) {
+ base::string16 error;
+ ui::Accelerator parsed =
+ ParseImpl(accelerator, Command::CommandPlatform(), 0,
+ IsNamedCommand(command_name), &error);
+ return parsed;
+}
+
+// static
+std::string Command::AcceleratorToString(const ui::Accelerator& accelerator) {
+ std::string shortcut;
+
+ // Ctrl and Alt are mutually exclusive.
+ if (accelerator.IsCtrlDown())
+ shortcut += values::kKeyCtrl;
+ else if (accelerator.IsAltDown())
+ shortcut += values::kKeyAlt;
+ if (!shortcut.empty())
+ shortcut += values::kKeySeparator;
+
+ if (accelerator.IsCmdDown()) {
+#if defined(OS_CHROMEOS)
+ // Chrome OS treats the Search key like the Command key.
+ shortcut += values::kKeySearch;
+#else
+ shortcut += values::kKeyCommand;
+#endif
+ shortcut += values::kKeySeparator;
+ }
+
+ if (accelerator.IsShiftDown()) {
+ shortcut += values::kKeyShift;
+ shortcut += values::kKeySeparator;
+ }
+
+ if (accelerator.key_code() >= ui::VKEY_0 &&
+ accelerator.key_code() <= ui::VKEY_9) {
+ shortcut += '0' + (accelerator.key_code() - ui::VKEY_0);
+ } else if (accelerator.key_code() >= ui::VKEY_A &&
+ accelerator.key_code() <= ui::VKEY_Z) {
+ shortcut += 'A' + (accelerator.key_code() - ui::VKEY_A);
+ } else {
+ switch (accelerator.key_code()) {
+ case ui::VKEY_OEM_COMMA:
+ shortcut += values::kKeyComma;
+ break;
+ case ui::VKEY_OEM_PERIOD:
+ shortcut += values::kKeyPeriod;
+ break;
+ case ui::VKEY_UP:
+ shortcut += values::kKeyUp;
+ break;
+ case ui::VKEY_DOWN:
+ shortcut += values::kKeyDown;
+ break;
+ case ui::VKEY_LEFT:
+ shortcut += values::kKeyLeft;
+ break;
+ case ui::VKEY_RIGHT:
+ shortcut += values::kKeyRight;
+ break;
+ case ui::VKEY_INSERT:
+ shortcut += values::kKeyIns;
+ break;
+ case ui::VKEY_DELETE:
+ shortcut += values::kKeyDel;
+ break;
+ case ui::VKEY_HOME:
+ shortcut += values::kKeyHome;
+ break;
+ case ui::VKEY_END:
+ shortcut += values::kKeyEnd;
+ break;
+ case ui::VKEY_PRIOR:
+ shortcut += values::kKeyPgUp;
+ break;
+ case ui::VKEY_NEXT:
+ shortcut += values::kKeyPgDwn;
+ break;
+ case ui::VKEY_SPACE:
+ shortcut += values::kKeySpace;
+ break;
+ case ui::VKEY_TAB:
+ shortcut += values::kKeyTab;
+ break;
+ case ui::VKEY_MEDIA_NEXT_TRACK:
+ shortcut += values::kKeyMediaNextTrack;
+ break;
+ case ui::VKEY_MEDIA_PLAY_PAUSE:
+ shortcut += values::kKeyMediaPlayPause;
+ break;
+ case ui::VKEY_MEDIA_PREV_TRACK:
+ shortcut += values::kKeyMediaPrevTrack;
+ break;
+ case ui::VKEY_MEDIA_STOP:
+ shortcut += values::kKeyMediaStop;
+ break;
+ default:
+ return "";
+ }
+ }
+ return shortcut;
+}
+
+// static
+bool Command::IsMediaKey(const ui::Accelerator& accelerator) {
+ if (accelerator.modifiers() != 0)
+ return false;
+
+ return ui::MediaKeysListener::IsMediaKeycode(accelerator.key_code());
+}
+
+bool Command::Parse(const base::DictionaryValue* command,
+ const std::string& command_name,
+ int index,
+ base::string16* error) {
+ DCHECK(!command_name.empty());
+
+ base::string16 description;
+ if (IsNamedCommand(command_name)) {
+ if (!command->GetString(keys::kDescription, &description) ||
+ description.empty()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidKeyBindingDescription, base::NumberToString(index));
+ return false;
+ }
+ }
+
+ // We'll build up a map of platform-to-shortcut suggestions.
+ typedef std::map<const std::string, std::string> SuggestionMap;
+ SuggestionMap suggestions;
+
+ // First try to parse the |suggested_key| as a dictionary.
+ const base::DictionaryValue* suggested_key_dict;
+ if (command->GetDictionary(keys::kSuggestedKey, &suggested_key_dict)) {
+ for (base::DictionaryValue::Iterator iter(*suggested_key_dict);
+ !iter.IsAtEnd(); iter.Advance()) {
+ // For each item in the dictionary, extract the platforms specified.
+ std::string suggested_key_string;
+ if (iter.value().GetAsString(&suggested_key_string) &&
+ !suggested_key_string.empty()) {
+ // Found a platform, add it to the suggestions list.
+ suggestions[iter.key()] = suggested_key_string;
+ } else {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidKeyBinding, base::NumberToString(index),
+ keys::kSuggestedKey, kMissing);
+ return false;
+ }
+ }
+ } else {
+ // No dictionary was found, fall back to using just a string, so developers
+ // don't have to specify a dictionary if they just want to use one default
+ // for all platforms.
+ std::string suggested_key_string;
+ if (command->GetString(keys::kSuggestedKey, &suggested_key_string) &&
+ !suggested_key_string.empty()) {
+ // If only a single string is provided, it must be default for all.
+ suggestions[values::kKeybindingPlatformDefault] = suggested_key_string;
+ } else {
+ suggestions[values::kKeybindingPlatformDefault] = "";
+ }
+ }
+
+ // Check if this is a global or a regular shortcut.
+ bool global = false;
+ command->GetBoolean(keys::kGlobal, &global);
+
+ // Normalize the suggestions.
+ for (auto iter = suggestions.begin(); iter != suggestions.end(); ++iter) {
+ // Before we normalize Ctrl to Command we must detect when the developer
+ // specified Command in the Default section, which will work on Mac after
+ // normalization but only fail on other platforms when they try it out on
+ // other platforms, which is not what we want.
+ if (iter->first == values::kKeybindingPlatformDefault &&
+ iter->second.find("Command+") != std::string::npos) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidKeyBinding, base::NumberToString(index),
+ keys::kSuggestedKey, kCommandKeyNotSupported);
+ return false;
+ }
+
+ suggestions[iter->first] = NormalizeShortcutSuggestion(iter->second,
+ iter->first);
+ }
+
+ std::string platform = CommandPlatform();
+ std::string key = platform;
+ if (suggestions.find(key) == suggestions.end())
+ key = values::kKeybindingPlatformDefault;
+ if (suggestions.find(key) == suggestions.end()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidKeyBindingMissingPlatform, base::NumberToString(index),
+ keys::kSuggestedKey, platform);
+ return false; // No platform specified and no fallback. Bail.
+ }
+
+ // For developer convenience, we parse all the suggestions (and complain about
+ // errors for platforms other than the current one) but use only what we need.
+ std::map<const std::string, std::string>::const_iterator iter =
+ suggestions.begin();
+ for ( ; iter != suggestions.end(); ++iter) {
+ ui::Accelerator accelerator;
+ if (!iter->second.empty()) {
+ // Note that we pass iter->first to pretend we are on a platform we're not
+ // on.
+ accelerator = ParseImpl(iter->second, iter->first, index,
+ IsNamedCommand(command_name), error);
+ if (accelerator.key_code() == ui::VKEY_UNKNOWN) {
+ if (error->empty()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidKeyBinding, base::NumberToString(index),
+ iter->first, iter->second);
+ }
+ return false;
+ }
+ }
+
+ if (iter->first == key) {
+ // This platform is our platform, so grab this key.
+ accelerator_ = accelerator;
+ command_name_ = command_name;
+ description_ = description;
+ global_ = global;
+ type_ = GetCommandType(command_name);
+ }
+ }
+ return true;
+}
+
+} // namespace extensions