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/string_number_conversions.h"
9 #include "base/string_split.h"
10 #include "base/string_util.h"
11 #include "base/values.h"
12 #include "chrome/common/extensions/extension.h"
13 #include "chrome/common/extensions/extension_error_utils.h"
14 #include "chrome/common/extensions/extension_manifest_constants.h"
15 #include "grit/generated_resources.h"
16 #include "ui/base/l10n/l10n_util.h"
18 namespace errors
= extension_manifest_errors
;
19 namespace keys
= extension_manifest_keys
;
20 namespace values
= extension_manifest_values
;
24 static const char kMissing
[] = "Missing";
26 static const char kCommandKeyNotSupported
[] =
27 "Command key is not supported. Note: Ctrl means Command on Mac";
30 ui::Accelerator
ParseImpl(const std::string
& accelerator
,
31 const std::string
& platform_key
,
34 if (platform_key
!= values::kKeybindingPlatformWin
&&
35 platform_key
!= values::kKeybindingPlatformMac
&&
36 platform_key
!= values::kKeybindingPlatformChromeOs
&&
37 platform_key
!= values::kKeybindingPlatformLinux
&&
38 platform_key
!= values::kKeybindingPlatformDefault
) {
39 *error
= ExtensionErrorUtils::FormatErrorMessageUTF16(
40 errors::kInvalidKeyBindingUnknownPlatform
,
41 base::IntToString(index
),
43 return ui::Accelerator();
46 std::vector
<std::string
> tokens
;
47 base::SplitString(accelerator
, '+', &tokens
);
48 if (tokens
.size() < 2 || tokens
.size() > 3) {
49 *error
= ExtensionErrorUtils::FormatErrorMessageUTF16(
50 errors::kInvalidKeyBinding
,
51 base::IntToString(index
),
54 return ui::Accelerator();
57 // Now, parse it into an accelerator.
58 int modifiers
= ui::EF_NONE
;
59 ui::KeyboardCode key
= ui::VKEY_UNKNOWN
;
60 for (size_t i
= 0; i
< tokens
.size(); i
++) {
61 if (tokens
[i
] == "Ctrl") {
62 modifiers
|= ui::EF_CONTROL_DOWN
;
63 } else if (tokens
[i
] == "Command") {
64 if (platform_key
== "mac") {
65 // Either the developer specified Command+foo in the manifest for Mac or
66 // they specified Ctrl and it got normalized to Command (to get Ctrl on
67 // Mac the developer has to specify MacCtrl). Therefore we treat this
69 modifiers
|= ui::EF_COMMAND_DOWN
;
70 #if defined(OS_MACOSX)
71 } else if (platform_key
== "default") {
72 // If we see "Command+foo" in the Default section it can mean two
73 // things, depending on the platform:
74 // The developer specified "Ctrl+foo" for Default and it got normalized
75 // on Mac to "Command+foo". This is fine. Treat it as Command.
76 modifiers
|= ui::EF_COMMAND_DOWN
;
79 // No other platform supports Command.
80 key
= ui::VKEY_UNKNOWN
;
83 } else if (tokens
[i
] == "Alt") {
84 modifiers
|= ui::EF_ALT_DOWN
;
85 } else if (tokens
[i
] == "Shift") {
86 modifiers
|= ui::EF_SHIFT_DOWN
;
87 } else if (tokens
[i
].size() == 1) {
88 if (key
!= ui::VKEY_UNKNOWN
) {
89 // Multiple key assignments.
90 key
= ui::VKEY_UNKNOWN
;
93 if (tokens
[i
][0] >= 'A' && tokens
[i
][0] <= 'Z') {
94 key
= static_cast<ui::KeyboardCode
>(ui::VKEY_A
+ (tokens
[i
][0] - 'A'));
95 } else if (tokens
[i
][0] >= '0' && tokens
[i
][0] <= '9') {
96 key
= static_cast<ui::KeyboardCode
>(ui::VKEY_0
+ (tokens
[i
][0] - '0'));
98 key
= ui::VKEY_UNKNOWN
;
102 *error
= ExtensionErrorUtils::FormatErrorMessageUTF16(
103 errors::kInvalidKeyBinding
,
104 base::IntToString(index
),
107 return ui::Accelerator();
110 bool command
= (modifiers
& ui::EF_COMMAND_DOWN
) != 0;
111 bool ctrl
= (modifiers
& ui::EF_CONTROL_DOWN
) != 0;
112 bool alt
= (modifiers
& ui::EF_ALT_DOWN
) != 0;
113 bool shift
= (modifiers
& ui::EF_SHIFT_DOWN
) != 0;
115 // We support Ctrl+foo, Alt+foo, Ctrl+Shift+foo, Alt+Shift+foo, but not
116 // Ctrl+Alt+foo and not Shift+foo either. For a more detailed reason why we
117 // don't support Ctrl+Alt+foo see this article:
118 // http://blogs.msdn.com/b/oldnewthing/archive/2004/03/29/101121.aspx.
119 // On Mac Command can also be used in combination with Shift or on its own,
121 if (key
== ui::VKEY_UNKNOWN
|| (ctrl
&& alt
) || (command
&& alt
) ||
122 (shift
&& !ctrl
&& !alt
&& !command
)) {
123 *error
= ExtensionErrorUtils::FormatErrorMessageUTF16(
124 errors::kInvalidKeyBinding
,
125 base::IntToString(index
),
128 return ui::Accelerator();
131 return ui::Accelerator(key
, modifiers
);
134 // For Mac, we convert "Ctrl" to "Command" and "MacCtrl" to "Ctrl". Other
135 // platforms leave the shortcut untouched.
136 std::string
NormalizeShortcutSuggestion(const std::string
& suggestion
,
137 const std::string
& platform
) {
138 bool normalize
= false;
139 if (platform
== "mac") {
141 } else if (platform
== "default") {
142 #if defined(OS_MACOSX)
151 std::vector
<std::string
> tokens
;
152 base::SplitString(suggestion
, '+', &tokens
);
153 for (size_t i
= 0; i
< tokens
.size(); i
++) {
154 if (tokens
[i
] == "Ctrl")
155 tokens
[i
] = "Command";
156 else if (tokens
[i
] == "MacCtrl")
159 return JoinString(tokens
, '+');
164 namespace extensions
{
166 Command::Command() {}
168 Command::Command(const std::string
& command_name
,
169 const string16
& description
,
170 const std::string
& accelerator
)
171 : command_name_(command_name
),
172 description_(description
) {
174 accelerator_
= ParseImpl(accelerator
, CommandPlatform(), 0, &error
);
177 Command::~Command() {}
180 std::string
Command::CommandPlatform() {
182 return values::kKeybindingPlatformWin
;
183 #elif defined(OS_MACOSX)
184 return values::kKeybindingPlatformMac
;
185 #elif defined(OS_CHROMEOS)
186 return values::kKeybindingPlatformChromeOs
;
187 #elif defined(OS_LINUX)
188 return values::kKeybindingPlatformLinux
;
195 ui::Accelerator
Command::StringToAccelerator(const std::string
& accelerator
) {
198 ui::Accelerator parsed
=
199 ParseImpl(accelerator
, Command::CommandPlatform(), 0, &error
);
203 bool Command::Parse(DictionaryValue
* command
,
204 const std::string
& command_name
,
207 DCHECK(!command_name
.empty());
209 // We'll build up a map of platform-to-shortcut suggestions.
210 typedef std::map
<const std::string
, std::string
> SuggestionMap
;
211 SuggestionMap suggestions
;
213 // First try to parse the |suggested_key| as a dictionary.
214 DictionaryValue
* suggested_key_dict
;
215 if (command
->GetDictionary(keys::kSuggestedKey
, &suggested_key_dict
)) {
216 DictionaryValue::key_iterator iter
= suggested_key_dict
->begin_keys();
217 for ( ; iter
!= suggested_key_dict
->end_keys(); ++iter
) {
218 // For each item in the dictionary, extract the platforms specified.
219 std::string suggested_key_string
;
220 if (suggested_key_dict
->GetString(*iter
, &suggested_key_string
) &&
221 !suggested_key_string
.empty()) {
222 // Found a platform, add it to the suggestions list.
223 suggestions
[*iter
] = suggested_key_string
;
225 *error
= ExtensionErrorUtils::FormatErrorMessageUTF16(
226 errors::kInvalidKeyBinding
,
227 base::IntToString(index
),
234 // No dictionary was found, fall back to using just a string, so developers
235 // don't have to specify a dictionary if they just want to use one default
236 // for all platforms.
237 std::string suggested_key_string
;
238 if (command
->GetString(keys::kSuggestedKey
, &suggested_key_string
) &&
239 !suggested_key_string
.empty()) {
240 // If only a single string is provided, it must be default for all.
241 suggestions
["default"] = suggested_key_string
;
243 *error
= ExtensionErrorUtils::FormatErrorMessageUTF16(
244 errors::kInvalidKeyBinding
,
245 base::IntToString(index
),
252 // Normalize the suggestions.
253 for (SuggestionMap::iterator iter
= suggestions
.begin();
254 iter
!= suggestions
.end(); ++iter
) {
255 // Before we normalize Ctrl to Command we must detect when the developer
256 // specified Command in the Default section, which will work on Mac after
257 // normalization but only fail on other platforms when they try it out on
258 // other platforms, which is not what we want.
259 if (iter
->first
== "default" &&
260 iter
->second
.find("Command+") != std::string::npos
) {
261 *error
= ExtensionErrorUtils::FormatErrorMessageUTF16(
262 errors::kInvalidKeyBinding
,
263 base::IntToString(index
),
265 kCommandKeyNotSupported
);
269 suggestions
[iter
->first
] = NormalizeShortcutSuggestion(iter
->second
,
273 std::string platform
= CommandPlatform();
274 std::string key
= platform
;
275 if (suggestions
.find(key
) == suggestions
.end())
276 key
= values::kKeybindingPlatformDefault
;
277 if (suggestions
.find(key
) == suggestions
.end()) {
278 *error
= ExtensionErrorUtils::FormatErrorMessageUTF16(
279 errors::kInvalidKeyBindingMissingPlatform
,
280 base::IntToString(index
),
283 return false; // No platform specified and no fallback. Bail.
286 // For developer convenience, we parse all the suggestions (and complain about
287 // errors for platforms other than the current one) but use only what we need.
288 std::map
<const std::string
, std::string
>::const_iterator iter
=
290 for ( ; iter
!= suggestions
.end(); ++iter
) {
291 // Note that we pass iter->first to pretend we are on a platform we're not
293 ui::Accelerator accelerator
=
294 ParseImpl(iter
->second
, iter
->first
, index
, error
);
295 if (accelerator
.key_code() == ui::VKEY_UNKNOWN
) {
296 *error
= ExtensionErrorUtils::FormatErrorMessageUTF16(
297 errors::kInvalidKeyBinding
,
298 base::IntToString(index
),
304 if (iter
->first
== key
) {
305 // This platform is our platform, so grab this key.
306 accelerator_
= accelerator
;
307 command_name_
= command_name
;
310 extension_manifest_values::kPageActionCommandEvent
&&
312 extension_manifest_values::kBrowserActionCommandEvent
&&
314 extension_manifest_values::kScriptBadgeCommandEvent
) {
315 if (!command
->GetString(keys::kDescription
, &description_
) ||
316 description_
.empty()) {
317 *error
= ExtensionErrorUtils::FormatErrorMessageUTF16(
318 errors::kInvalidKeyBindingDescription
,
319 base::IntToString(index
));
328 DictionaryValue
* Command::ToValue(const Extension
* extension
,
330 DictionaryValue
* extension_data
= new DictionaryValue();
332 string16 command_description
;
333 if (command_name() == values::kBrowserActionCommandEvent
||
334 command_name() == values::kPageActionCommandEvent
||
335 command_name() == values::kScriptBadgeCommandEvent
) {
336 command_description
=
337 l10n_util::GetStringUTF16(IDS_EXTENSION_COMMANDS_GENERIC_ACTIVATE
);
339 command_description
= description();
341 extension_data
->SetString("description", command_description
);
342 extension_data
->SetBoolean("active", active
);
343 extension_data
->SetString("keybinding", accelerator().GetShortcutText());
344 extension_data
->SetString("command_name", command_name());
345 extension_data
->SetString("extension_id", extension
->id());
347 return extension_data
;
350 } // namespace extensions