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