- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / common / extensions / command.cc
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.
4
5 #include "chrome/common/extensions/command.h"
6
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 "chrome/common/extensions/extension.h"
14 #include "chrome/common/extensions/feature_switch.h"
15 #include "extensions/common/error_utils.h"
16 #include "extensions/common/manifest_constants.h"
17 #include "grit/generated_resources.h"
18 #include "ui/base/l10n/l10n_util.h"
19
20 namespace extensions {
21
22 namespace errors = manifest_errors;
23 namespace keys = manifest_keys;
24 namespace values = manifest_values;
25
26 namespace {
27
28 static const char kMissing[] = "Missing";
29
30 static const char kCommandKeyNotSupported[] =
31     "Command key is not supported. Note: Ctrl means Command on Mac";
32
33 bool IsNamedCommand(const std::string& command_name) {
34   return command_name != values::kPageActionCommandEvent &&
35          command_name != values::kBrowserActionCommandEvent &&
36          command_name != values::kScriptBadgeCommandEvent;
37 }
38
39 bool DoesRequireModifier(const std::string& accelerator) {
40   return accelerator != values::kKeyMediaNextTrack &&
41          accelerator != values::kKeyMediaPlayPause &&
42          accelerator != values::kKeyMediaPrevTrack &&
43          accelerator != values::kKeyMediaStop;
44 }
45
46 ui::Accelerator ParseImpl(const std::string& accelerator,
47                           const std::string& platform_key,
48                           int index,
49                           bool should_parse_media_keys,
50                           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();
62   }
63
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();
75   }
76
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;
102       }
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;
129       }
130
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;
178       }
179     } else {
180       *error = ErrorUtils::FormatErrorMessageUTF16(
181           errors::kInvalidKeyBinding,
182           base::IntToString(index),
183           platform_key,
184           accelerator);
185       return ui::Accelerator();
186     }
187   }
188
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;
193
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();
208   }
209
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();
221   }
222
223   return ui::Accelerator(key, modifiers);
224 }
225
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
237   }
238
239   if (!normalize)
240     return suggestion;
241
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;
249   }
250   return JoinString(tokens, '+');
251 }
252
253 }  // namespace
254
255 Command::Command() : global_(false) {}
256
257 Command::Command(const std::string& command_name,
258                  const string16& description,
259                  const std::string& accelerator,
260                  bool global)
261     : command_name_(command_name),
262       description_(description),
263       global_(global) {
264   string16 error;
265   accelerator_ = ParseImpl(accelerator, CommandPlatform(), 0,
266                            IsNamedCommand(command_name), &error);
267 }
268
269 Command::~Command() {}
270
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
284 }
285
286 // static
287 ui::Accelerator Command::StringToAccelerator(const std::string& accelerator,
288                                              const std::string& command_name) {
289   string16 error;
290   ui::Accelerator parsed =
291       ParseImpl(accelerator, Command::CommandPlatform(), 0,
292                 IsNamedCommand(command_name), &error);
293   return parsed;
294 }
295
296 // static
297 std::string Command::AcceleratorToString(const ui::Accelerator& accelerator) {
298   std::string shortcut;
299
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;
307
308   if (accelerator.IsCmdDown()) {
309     shortcut += values::kKeyCommand;
310     shortcut += values::kKeySeparator;
311   }
312
313   if (accelerator.IsShiftDown()) {
314     shortcut += values::kKeyShift;
315     shortcut += values::kKeySeparator;
316   }
317
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 "";
379     }
380   }
381   return shortcut;
382 }
383
384 bool Command::Parse(const base::DictionaryValue* command,
385                     const std::string& command_name,
386                     int index,
387                     string16* error) {
388   DCHECK(!command_name.empty());
389
390   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;
398     }
399   }
400
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;
404
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;
423       }
424     }
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] = "";
436     }
437   }
438
439   // Check if this is a global or a regular shortcut.
440   bool global = false;
441   command->GetBoolean(keys::kGlobal, &global);
442
443   // Normalize the suggestions.
444   for (SuggestionMap::iterator iter = suggestions.begin();
445        iter != suggestions.end(); ++iter) {
446     // Before we normalize Ctrl to Command we must detect when the developer
447     // specified Command in the Default section, which will work on Mac after
448     // normalization but only fail on other platforms when they try it out on
449     // other platforms, which is not what we want.
450     if (iter->first == values::kKeybindingPlatformDefault &&
451         iter->second.find("Command+") != std::string::npos) {
452       *error = ErrorUtils::FormatErrorMessageUTF16(
453           errors::kInvalidKeyBinding,
454           base::IntToString(index),
455           keys::kSuggestedKey,
456           kCommandKeyNotSupported);
457       return false;
458     }
459
460     suggestions[iter->first] = NormalizeShortcutSuggestion(iter->second,
461                                                            iter->first);
462   }
463
464   std::string platform = CommandPlatform();
465   std::string key = platform;
466   if (suggestions.find(key) == suggestions.end())
467     key = values::kKeybindingPlatformDefault;
468   if (suggestions.find(key) == suggestions.end()) {
469     *error = ErrorUtils::FormatErrorMessageUTF16(
470         errors::kInvalidKeyBindingMissingPlatform,
471         base::IntToString(index),
472         keys::kSuggestedKey,
473         platform);
474     return false;  // No platform specified and no fallback. Bail.
475   }
476
477   // For developer convenience, we parse all the suggestions (and complain about
478   // errors for platforms other than the current one) but use only what we need.
479   std::map<const std::string, std::string>::const_iterator iter =
480       suggestions.begin();
481   for ( ; iter != suggestions.end(); ++iter) {
482     ui::Accelerator accelerator;
483     if (!iter->second.empty()) {
484       // Note that we pass iter->first to pretend we are on a platform we're not
485       // on.
486       accelerator = ParseImpl(iter->second, iter->first, index,
487                               IsNamedCommand(command_name), error);
488       if (accelerator.key_code() == ui::VKEY_UNKNOWN) {
489         if (error->empty()) {
490           *error = ErrorUtils::FormatErrorMessageUTF16(
491               errors::kInvalidKeyBinding,
492               base::IntToString(index),
493               iter->first,
494               iter->second);
495         }
496         return false;
497       }
498     }
499
500     if (iter->first == key) {
501       // This platform is our platform, so grab this key.
502       accelerator_ = accelerator;
503       command_name_ = command_name;
504       description_ = description;
505       global_ = global;
506     }
507   }
508   return true;
509 }
510
511 base::DictionaryValue* Command::ToValue(const Extension* extension,
512                                         bool active) const {
513   base::DictionaryValue* extension_data = new base::DictionaryValue();
514
515   string16 command_description;
516   bool extension_action = false;
517   if (command_name() == values::kBrowserActionCommandEvent ||
518       command_name() == values::kPageActionCommandEvent ||
519       command_name() == values::kScriptBadgeCommandEvent) {
520     command_description =
521         l10n_util::GetStringUTF16(IDS_EXTENSION_COMMANDS_GENERIC_ACTIVATE);
522     extension_action = true;
523   } else {
524     command_description = description();
525   }
526   extension_data->SetString("description", command_description);
527   extension_data->SetBoolean("active", active);
528   extension_data->SetString("keybinding", accelerator().GetShortcutText());
529   extension_data->SetString("command_name", command_name());
530   extension_data->SetString("extension_id", extension->id());
531   extension_data->SetBoolean("global", global());
532   extension_data->SetBoolean("extension_action", extension_action);
533
534   if (FeatureSwitch::global_commands()->IsEnabled()) {
535     // TODO(finnur): This is to make sure we don't show the config UI beyond
536     // dev and will be removed when we launch.
537     static bool stable_or_beta =
538         chrome::VersionInfo::GetChannel() >= chrome::VersionInfo::CHANNEL_BETA;
539     extension_data->SetBoolean("scope_ui_visible", !stable_or_beta);
540   }
541
542   return extension_data;
543 }
544
545 }  // namespace extensions