Suppress tabs permission warning if there is already a browsingHistory warning.
[chromium-blink-merge.git] / chrome / common / extensions / command.cc
blobf3ddfb4594885ffa8dfd4b1d80d96e8069bafa28
1 // Copyright (c) 2012 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.
5 #include "chrome/common/extensions/command.h"
7 #include "base/logging.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/strings/string_split.h"
10 #include "base/strings/string_util.h"
11 #include "base/values.h"
12 #include "extensions/common/error_utils.h"
13 #include "extensions/common/extension.h"
14 #include "extensions/common/feature_switch.h"
15 #include "extensions/common/manifest_constants.h"
16 #include "grit/generated_resources.h"
17 #include "ui/base/l10n/l10n_util.h"
19 namespace extensions {
21 namespace errors = manifest_errors;
22 namespace keys = manifest_keys;
23 namespace values = manifest_values;
25 namespace {
27 static const char kMissing[] = "Missing";
29 static const char kCommandKeyNotSupported[] =
30 "Command key is not supported. Note: Ctrl means Command on Mac";
32 bool IsNamedCommand(const std::string& command_name) {
33 return command_name != values::kPageActionCommandEvent &&
34 command_name != values::kBrowserActionCommandEvent;
37 bool DoesRequireModifier(const std::string& accelerator) {
38 return accelerator != values::kKeyMediaNextTrack &&
39 accelerator != values::kKeyMediaPlayPause &&
40 accelerator != values::kKeyMediaPrevTrack &&
41 accelerator != values::kKeyMediaStop;
44 ui::Accelerator ParseImpl(const std::string& accelerator,
45 const std::string& platform_key,
46 int index,
47 bool should_parse_media_keys,
48 base::string16* error) {
49 error->clear();
50 if (platform_key != values::kKeybindingPlatformWin &&
51 platform_key != values::kKeybindingPlatformMac &&
52 platform_key != values::kKeybindingPlatformChromeOs &&
53 platform_key != values::kKeybindingPlatformLinux &&
54 platform_key != values::kKeybindingPlatformDefault) {
55 *error = ErrorUtils::FormatErrorMessageUTF16(
56 errors::kInvalidKeyBindingUnknownPlatform,
57 base::IntToString(index),
58 platform_key);
59 return ui::Accelerator();
62 std::vector<std::string> tokens;
63 base::SplitString(accelerator, '+', &tokens);
64 if (tokens.size() == 0 ||
65 (tokens.size() == 1 && DoesRequireModifier(accelerator)) ||
66 tokens.size() > 3) {
67 *error = ErrorUtils::FormatErrorMessageUTF16(
68 errors::kInvalidKeyBinding,
69 base::IntToString(index),
70 platform_key,
71 accelerator);
72 return ui::Accelerator();
75 // Now, parse it into an accelerator.
76 int modifiers = ui::EF_NONE;
77 ui::KeyboardCode key = ui::VKEY_UNKNOWN;
78 for (size_t i = 0; i < tokens.size(); i++) {
79 if (tokens[i] == values::kKeyCtrl) {
80 modifiers |= ui::EF_CONTROL_DOWN;
81 } else if (tokens[i] == values::kKeyCommand) {
82 if (platform_key == values::kKeybindingPlatformMac) {
83 // Either the developer specified Command+foo in the manifest for Mac or
84 // they specified Ctrl and it got normalized to Command (to get Ctrl on
85 // Mac the developer has to specify MacCtrl). Therefore we treat this
86 // as Command.
87 modifiers |= ui::EF_COMMAND_DOWN;
88 #if defined(OS_MACOSX)
89 } else if (platform_key == values::kKeybindingPlatformDefault) {
90 // If we see "Command+foo" in the Default section it can mean two
91 // things, depending on the platform:
92 // The developer specified "Ctrl+foo" for Default and it got normalized
93 // on Mac to "Command+foo". This is fine. Treat it as Command.
94 modifiers |= ui::EF_COMMAND_DOWN;
95 #endif
96 } else {
97 // No other platform supports Command.
98 key = ui::VKEY_UNKNOWN;
99 break;
101 } else if (tokens[i] == values::kKeyAlt) {
102 modifiers |= ui::EF_ALT_DOWN;
103 } else if (tokens[i] == values::kKeyShift) {
104 modifiers |= ui::EF_SHIFT_DOWN;
105 } else if (tokens[i].size() == 1 || // A-Z, 0-9.
106 tokens[i] == values::kKeyComma ||
107 tokens[i] == values::kKeyPeriod ||
108 tokens[i] == values::kKeyUp ||
109 tokens[i] == values::kKeyDown ||
110 tokens[i] == values::kKeyLeft ||
111 tokens[i] == values::kKeyRight ||
112 tokens[i] == values::kKeyIns ||
113 tokens[i] == values::kKeyDel ||
114 tokens[i] == values::kKeyHome ||
115 tokens[i] == values::kKeyEnd ||
116 tokens[i] == values::kKeyPgUp ||
117 tokens[i] == values::kKeyPgDwn ||
118 tokens[i] == values::kKeyTab ||
119 tokens[i] == values::kKeyMediaNextTrack ||
120 tokens[i] == values::kKeyMediaPlayPause ||
121 tokens[i] == values::kKeyMediaPrevTrack ||
122 tokens[i] == values::kKeyMediaStop) {
123 if (key != ui::VKEY_UNKNOWN) {
124 // Multiple key assignments.
125 key = ui::VKEY_UNKNOWN;
126 break;
129 if (tokens[i] == values::kKeyComma) {
130 key = ui::VKEY_OEM_COMMA;
131 } else if (tokens[i] == values::kKeyPeriod) {
132 key = ui::VKEY_OEM_PERIOD;
133 } else if (tokens[i] == values::kKeyUp) {
134 key = ui::VKEY_UP;
135 } else if (tokens[i] == values::kKeyDown) {
136 key = ui::VKEY_DOWN;
137 } else if (tokens[i] == values::kKeyLeft) {
138 key = ui::VKEY_LEFT;
139 } else if (tokens[i] == values::kKeyRight) {
140 key = ui::VKEY_RIGHT;
141 } else if (tokens[i] == values::kKeyIns) {
142 key = ui::VKEY_INSERT;
143 } else if (tokens[i] == values::kKeyDel) {
144 key = ui::VKEY_DELETE;
145 } else if (tokens[i] == values::kKeyHome) {
146 key = ui::VKEY_HOME;
147 } else if (tokens[i] == values::kKeyEnd) {
148 key = ui::VKEY_END;
149 } else if (tokens[i] == values::kKeyPgUp) {
150 key = ui::VKEY_PRIOR;
151 } else if (tokens[i] == values::kKeyPgDwn) {
152 key = ui::VKEY_NEXT;
153 } else if (tokens[i] == values::kKeyTab) {
154 key = ui::VKEY_TAB;
155 } else if (tokens[i] == values::kKeyMediaNextTrack &&
156 should_parse_media_keys) {
157 key = ui::VKEY_MEDIA_NEXT_TRACK;
158 } else if (tokens[i] == values::kKeyMediaPlayPause &&
159 should_parse_media_keys) {
160 key = ui::VKEY_MEDIA_PLAY_PAUSE;
161 } else if (tokens[i] == values::kKeyMediaPrevTrack &&
162 should_parse_media_keys) {
163 key = ui::VKEY_MEDIA_PREV_TRACK;
164 } else if (tokens[i] == values::kKeyMediaStop &&
165 should_parse_media_keys) {
166 key = ui::VKEY_MEDIA_STOP;
167 } else if (tokens[i].size() == 1 &&
168 tokens[i][0] >= 'A' && tokens[i][0] <= 'Z') {
169 key = static_cast<ui::KeyboardCode>(ui::VKEY_A + (tokens[i][0] - 'A'));
170 } else if (tokens[i].size() == 1 &&
171 tokens[i][0] >= '0' && tokens[i][0] <= '9') {
172 key = static_cast<ui::KeyboardCode>(ui::VKEY_0 + (tokens[i][0] - '0'));
173 } else {
174 key = ui::VKEY_UNKNOWN;
175 break;
177 } else {
178 *error = ErrorUtils::FormatErrorMessageUTF16(
179 errors::kInvalidKeyBinding,
180 base::IntToString(index),
181 platform_key,
182 accelerator);
183 return ui::Accelerator();
187 bool command = (modifiers & ui::EF_COMMAND_DOWN) != 0;
188 bool ctrl = (modifiers & ui::EF_CONTROL_DOWN) != 0;
189 bool alt = (modifiers & ui::EF_ALT_DOWN) != 0;
190 bool shift = (modifiers & ui::EF_SHIFT_DOWN) != 0;
192 // We support Ctrl+foo, Alt+foo, Ctrl+Shift+foo, Alt+Shift+foo, but not
193 // Ctrl+Alt+foo and not Shift+foo either. For a more detailed reason why we
194 // don't support Ctrl+Alt+foo see this article:
195 // http://blogs.msdn.com/b/oldnewthing/archive/2004/03/29/101121.aspx.
196 // On Mac Command can also be used in combination with Shift or on its own,
197 // as a modifier.
198 if (key == ui::VKEY_UNKNOWN || (ctrl && alt) || (command && alt) ||
199 (shift && !ctrl && !alt && !command)) {
200 *error = ErrorUtils::FormatErrorMessageUTF16(
201 errors::kInvalidKeyBinding,
202 base::IntToString(index),
203 platform_key,
204 accelerator);
205 return ui::Accelerator();
208 if ((key == ui::VKEY_MEDIA_NEXT_TRACK ||
209 key == ui::VKEY_MEDIA_PREV_TRACK ||
210 key == ui::VKEY_MEDIA_PLAY_PAUSE ||
211 key == ui::VKEY_MEDIA_STOP) &&
212 (shift || ctrl || alt || command)) {
213 *error = ErrorUtils::FormatErrorMessageUTF16(
214 errors::kInvalidKeyBindingMediaKeyWithModifier,
215 base::IntToString(index),
216 platform_key,
217 accelerator);
218 return ui::Accelerator();
221 return ui::Accelerator(key, modifiers);
224 // For Mac, we convert "Ctrl" to "Command" and "MacCtrl" to "Ctrl". Other
225 // platforms leave the shortcut untouched.
226 std::string NormalizeShortcutSuggestion(const std::string& suggestion,
227 const std::string& platform) {
228 bool normalize = false;
229 if (platform == values::kKeybindingPlatformMac) {
230 normalize = true;
231 } else if (platform == values::kKeybindingPlatformDefault) {
232 #if defined(OS_MACOSX)
233 normalize = true;
234 #endif
237 if (!normalize)
238 return suggestion;
240 std::vector<std::string> tokens;
241 base::SplitString(suggestion, '+', &tokens);
242 for (size_t i = 0; i < tokens.size(); i++) {
243 if (tokens[i] == values::kKeyCtrl)
244 tokens[i] = values::kKeyCommand;
245 else if (tokens[i] == values::kKeyMacCtrl)
246 tokens[i] = values::kKeyCtrl;
248 return JoinString(tokens, '+');
251 } // namespace
253 Command::Command() : global_(false) {}
255 Command::Command(const std::string& command_name,
256 const base::string16& description,
257 const std::string& accelerator,
258 bool global)
259 : command_name_(command_name),
260 description_(description),
261 global_(global) {
262 base::string16 error;
263 accelerator_ = ParseImpl(accelerator, CommandPlatform(), 0,
264 IsNamedCommand(command_name), &error);
267 Command::~Command() {}
269 // static
270 std::string Command::CommandPlatform() {
271 #if defined(OS_WIN)
272 return values::kKeybindingPlatformWin;
273 #elif defined(OS_MACOSX)
274 return values::kKeybindingPlatformMac;
275 #elif defined(OS_CHROMEOS)
276 return values::kKeybindingPlatformChromeOs;
277 #elif defined(OS_LINUX)
278 return values::kKeybindingPlatformLinux;
279 #else
280 return "";
281 #endif
284 // static
285 ui::Accelerator Command::StringToAccelerator(const std::string& accelerator,
286 const std::string& command_name) {
287 base::string16 error;
288 ui::Accelerator parsed =
289 ParseImpl(accelerator, Command::CommandPlatform(), 0,
290 IsNamedCommand(command_name), &error);
291 return parsed;
294 // static
295 std::string Command::AcceleratorToString(const ui::Accelerator& accelerator) {
296 std::string shortcut;
298 // Ctrl and Alt are mutually exclusive.
299 if (accelerator.IsCtrlDown())
300 shortcut += values::kKeyCtrl;
301 else if (accelerator.IsAltDown())
302 shortcut += values::kKeyAlt;
303 if (!shortcut.empty())
304 shortcut += values::kKeySeparator;
306 if (accelerator.IsCmdDown()) {
307 shortcut += values::kKeyCommand;
308 shortcut += values::kKeySeparator;
311 if (accelerator.IsShiftDown()) {
312 shortcut += values::kKeyShift;
313 shortcut += values::kKeySeparator;
316 if (accelerator.key_code() >= ui::VKEY_0 &&
317 accelerator.key_code() <= ui::VKEY_9) {
318 shortcut += '0' + (accelerator.key_code() - ui::VKEY_0);
319 } else if (accelerator.key_code() >= ui::VKEY_A &&
320 accelerator.key_code() <= ui::VKEY_Z) {
321 shortcut += 'A' + (accelerator.key_code() - ui::VKEY_A);
322 } else {
323 switch (accelerator.key_code()) {
324 case ui::VKEY_OEM_COMMA:
325 shortcut += values::kKeyComma;
326 break;
327 case ui::VKEY_OEM_PERIOD:
328 shortcut += values::kKeyPeriod;
329 break;
330 case ui::VKEY_UP:
331 shortcut += values::kKeyUp;
332 break;
333 case ui::VKEY_DOWN:
334 shortcut += values::kKeyDown;
335 break;
336 case ui::VKEY_LEFT:
337 shortcut += values::kKeyLeft;
338 break;
339 case ui::VKEY_RIGHT:
340 shortcut += values::kKeyRight;
341 break;
342 case ui::VKEY_INSERT:
343 shortcut += values::kKeyIns;
344 break;
345 case ui::VKEY_DELETE:
346 shortcut += values::kKeyDel;
347 break;
348 case ui::VKEY_HOME:
349 shortcut += values::kKeyHome;
350 break;
351 case ui::VKEY_END:
352 shortcut += values::kKeyEnd;
353 break;
354 case ui::VKEY_PRIOR:
355 shortcut += values::kKeyPgUp;
356 break;
357 case ui::VKEY_NEXT:
358 shortcut += values::kKeyPgDwn;
359 break;
360 case ui::VKEY_TAB:
361 shortcut += values::kKeyTab;
362 break;
363 case ui::VKEY_MEDIA_NEXT_TRACK:
364 shortcut += values::kKeyMediaNextTrack;
365 break;
366 case ui::VKEY_MEDIA_PLAY_PAUSE:
367 shortcut += values::kKeyMediaPlayPause;
368 break;
369 case ui::VKEY_MEDIA_PREV_TRACK:
370 shortcut += values::kKeyMediaPrevTrack;
371 break;
372 case ui::VKEY_MEDIA_STOP:
373 shortcut += values::kKeyMediaStop;
374 break;
375 default:
376 return "";
379 return shortcut;
382 // static
383 bool Command::IsMediaKey(const ui::Accelerator& accelerator) {
384 if (accelerator.modifiers() != 0)
385 return false;
387 return (accelerator.key_code() == ui::VKEY_MEDIA_NEXT_TRACK ||
388 accelerator.key_code() == ui::VKEY_MEDIA_PREV_TRACK ||
389 accelerator.key_code() == ui::VKEY_MEDIA_PLAY_PAUSE ||
390 accelerator.key_code() == ui::VKEY_MEDIA_STOP);
393 bool Command::Parse(const base::DictionaryValue* command,
394 const std::string& command_name,
395 int index,
396 base::string16* error) {
397 DCHECK(!command_name.empty());
399 base::string16 description;
400 if (IsNamedCommand(command_name)) {
401 if (!command->GetString(keys::kDescription, &description) ||
402 description.empty()) {
403 *error = ErrorUtils::FormatErrorMessageUTF16(
404 errors::kInvalidKeyBindingDescription,
405 base::IntToString(index));
406 return false;
410 // We'll build up a map of platform-to-shortcut suggestions.
411 typedef std::map<const std::string, std::string> SuggestionMap;
412 SuggestionMap suggestions;
414 // First try to parse the |suggested_key| as a dictionary.
415 const base::DictionaryValue* suggested_key_dict;
416 if (command->GetDictionary(keys::kSuggestedKey, &suggested_key_dict)) {
417 for (base::DictionaryValue::Iterator iter(*suggested_key_dict);
418 !iter.IsAtEnd(); iter.Advance()) {
419 // For each item in the dictionary, extract the platforms specified.
420 std::string suggested_key_string;
421 if (iter.value().GetAsString(&suggested_key_string) &&
422 !suggested_key_string.empty()) {
423 // Found a platform, add it to the suggestions list.
424 suggestions[iter.key()] = suggested_key_string;
425 } else {
426 *error = ErrorUtils::FormatErrorMessageUTF16(
427 errors::kInvalidKeyBinding,
428 base::IntToString(index),
429 keys::kSuggestedKey,
430 kMissing);
431 return false;
434 } else {
435 // No dictionary was found, fall back to using just a string, so developers
436 // don't have to specify a dictionary if they just want to use one default
437 // for all platforms.
438 std::string suggested_key_string;
439 if (command->GetString(keys::kSuggestedKey, &suggested_key_string) &&
440 !suggested_key_string.empty()) {
441 // If only a single string is provided, it must be default for all.
442 suggestions[values::kKeybindingPlatformDefault] = suggested_key_string;
443 } else {
444 suggestions[values::kKeybindingPlatformDefault] = "";
448 // Check if this is a global or a regular shortcut.
449 bool global = false;
450 if (FeatureSwitch::global_commands()->IsEnabled())
451 command->GetBoolean(keys::kGlobal, &global);
453 // Normalize the suggestions.
454 for (SuggestionMap::iterator iter = suggestions.begin();
455 iter != suggestions.end(); ++iter) {
456 // Before we normalize Ctrl to Command we must detect when the developer
457 // specified Command in the Default section, which will work on Mac after
458 // normalization but only fail on other platforms when they try it out on
459 // other platforms, which is not what we want.
460 if (iter->first == values::kKeybindingPlatformDefault &&
461 iter->second.find("Command+") != std::string::npos) {
462 *error = ErrorUtils::FormatErrorMessageUTF16(
463 errors::kInvalidKeyBinding,
464 base::IntToString(index),
465 keys::kSuggestedKey,
466 kCommandKeyNotSupported);
467 return false;
470 suggestions[iter->first] = NormalizeShortcutSuggestion(iter->second,
471 iter->first);
474 std::string platform = CommandPlatform();
475 std::string key = platform;
476 if (suggestions.find(key) == suggestions.end())
477 key = values::kKeybindingPlatformDefault;
478 if (suggestions.find(key) == suggestions.end()) {
479 *error = ErrorUtils::FormatErrorMessageUTF16(
480 errors::kInvalidKeyBindingMissingPlatform,
481 base::IntToString(index),
482 keys::kSuggestedKey,
483 platform);
484 return false; // No platform specified and no fallback. Bail.
487 // For developer convenience, we parse all the suggestions (and complain about
488 // errors for platforms other than the current one) but use only what we need.
489 std::map<const std::string, std::string>::const_iterator iter =
490 suggestions.begin();
491 for ( ; iter != suggestions.end(); ++iter) {
492 ui::Accelerator accelerator;
493 if (!iter->second.empty()) {
494 // Note that we pass iter->first to pretend we are on a platform we're not
495 // on.
496 accelerator = ParseImpl(iter->second, iter->first, index,
497 IsNamedCommand(command_name), error);
498 if (accelerator.key_code() == ui::VKEY_UNKNOWN) {
499 if (error->empty()) {
500 *error = ErrorUtils::FormatErrorMessageUTF16(
501 errors::kInvalidKeyBinding,
502 base::IntToString(index),
503 iter->first,
504 iter->second);
506 return false;
510 if (iter->first == key) {
511 // This platform is our platform, so grab this key.
512 accelerator_ = accelerator;
513 command_name_ = command_name;
514 description_ = description;
515 global_ = global;
518 return true;
521 base::DictionaryValue* Command::ToValue(const Extension* extension,
522 bool active) const {
523 base::DictionaryValue* extension_data = new base::DictionaryValue();
525 base::string16 command_description;
526 bool extension_action = false;
527 if (command_name() == values::kBrowserActionCommandEvent ||
528 command_name() == values::kPageActionCommandEvent) {
529 command_description =
530 l10n_util::GetStringUTF16(IDS_EXTENSION_COMMANDS_GENERIC_ACTIVATE);
531 extension_action = true;
532 } else {
533 command_description = description();
535 extension_data->SetString("description", command_description);
536 extension_data->SetBoolean("active", active);
537 extension_data->SetString("keybinding", accelerator().GetShortcutText());
538 extension_data->SetString("command_name", command_name());
539 extension_data->SetString("extension_id", extension->id());
540 extension_data->SetBoolean("global", global());
541 extension_data->SetBoolean("extension_action", extension_action);
543 if (FeatureSwitch::global_commands()->IsEnabled())
544 extension_data->SetBoolean("scope_ui_visible", true);
546 return extension_data;
549 } // namespace extensions