Extract SIGPIPE ignoring code to a common place.
[chromium-blink-merge.git] / chrome / common / extensions / command.cc
blob1b2ec9d90c5c8f54256cf1ada5eb04feeff0bce0
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;
22 namespace {
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,
32 int index,
33 string16* error) {
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),
42 platform_key);
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),
52 platform_key,
53 accelerator);
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
68 // as Command.
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;
77 #endif
78 } else {
79 // No other platform supports Command.
80 key = ui::VKEY_UNKNOWN;
81 break;
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;
91 break;
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'));
97 } else {
98 key = ui::VKEY_UNKNOWN;
99 break;
101 } else {
102 *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
103 errors::kInvalidKeyBinding,
104 base::IntToString(index),
105 platform_key,
106 accelerator);
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,
120 // as a modifier.
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),
126 platform_key,
127 accelerator);
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") {
140 normalize = true;
141 } else if (platform == "default") {
142 #if defined(OS_MACOSX)
143 normalize = true;
144 #endif
147 if (!normalize)
148 return suggestion;
150 std::string key;
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")
157 tokens[i] = "Ctrl";
159 return JoinString(tokens, '+');
162 } // namespace
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) {
173 string16 error;
174 accelerator_ = ParseImpl(accelerator, CommandPlatform(), 0, &error);
177 Command::~Command() {}
179 // static
180 std::string Command::CommandPlatform() {
181 #if defined(OS_WIN)
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;
189 #else
190 return "";
191 #endif
194 // static
195 ui::Accelerator Command::StringToAccelerator(const std::string& accelerator) {
196 string16 error;
197 Command command;
198 ui::Accelerator parsed =
199 ParseImpl(accelerator, Command::CommandPlatform(), 0, &error);
200 return parsed;
203 bool Command::Parse(DictionaryValue* command,
204 const std::string& command_name,
205 int index,
206 string16* error) {
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;
224 } else {
225 *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
226 errors::kInvalidKeyBinding,
227 base::IntToString(index),
228 keys::kSuggestedKey,
229 kMissing);
230 return false;
233 } else {
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;
242 } else {
243 *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
244 errors::kInvalidKeyBinding,
245 base::IntToString(index),
246 keys::kSuggestedKey,
247 kMissing);
248 return false;
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),
264 keys::kSuggestedKey,
265 kCommandKeyNotSupported);
266 return false;
269 suggestions[iter->first] = NormalizeShortcutSuggestion(iter->second,
270 iter->first);
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),
281 keys::kSuggestedKey,
282 platform);
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 =
289 suggestions.begin();
290 for ( ; iter != suggestions.end(); ++iter) {
291 // Note that we pass iter->first to pretend we are on a platform we're not
292 // on.
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),
299 iter->first,
300 iter->second);
301 return false;
304 if (iter->first == key) {
305 // This platform is our platform, so grab this key.
306 accelerator_ = accelerator;
307 command_name_ = command_name;
309 if (command_name !=
310 extension_manifest_values::kPageActionCommandEvent &&
311 command_name !=
312 extension_manifest_values::kBrowserActionCommandEvent &&
313 command_name !=
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));
320 return false;
325 return true;
328 DictionaryValue* Command::ToValue(const Extension* extension,
329 bool active) const {
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);
338 } else {
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