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