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/browser/extensions/api/commands/command_service.h"
9 #include "base/lazy_instance.h"
10 #include "base/prefs/scoped_user_pref_update.h"
11 #include "base/strings/string_split.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/app/chrome_command_ids.h"
15 #include "chrome/browser/chrome_notification_types.h"
16 #include "chrome/browser/extensions/api/commands/commands.h"
17 #include "chrome/browser/extensions/extension_commands_global_registry.h"
18 #include "chrome/browser/extensions/extension_function_registry.h"
19 #include "chrome/browser/extensions/extension_keybinding_registry.h"
20 #include "chrome/browser/extensions/extension_service.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/ui/accelerator_utils.h"
23 #include "chrome/common/extensions/api/commands/commands_handler.h"
24 #include "chrome/common/extensions/manifest_handlers/settings_overrides_handler.h"
25 #include "chrome/common/pref_names.h"
26 #include "components/user_prefs/pref_registry_syncable.h"
27 #include "content/public/browser/notification_details.h"
28 #include "content/public/browser/notification_service.h"
29 #include "extensions/browser/extension_system.h"
30 #include "extensions/common/feature_switch.h"
31 #include "extensions/common/manifest_constants.h"
32 #include "extensions/common/permissions/permissions_data.h"
34 using extensions::Extension;
35 using extensions::ExtensionPrefs;
39 const char kExtension[] = "extension";
40 const char kCommandName[] = "command_name";
41 const char kGlobal[] = "global";
43 // A preference that indicates that the initial keybindings for the given
44 // extension have been set.
45 const char kInitialBindingsHaveBeenAssigned[] = "initial_keybindings_set";
47 std::string GetPlatformKeybindingKeyForAccelerator(
48 const ui::Accelerator& accelerator, const std::string extension_id) {
49 std::string key = extensions::Command::CommandPlatform() + ":" +
50 extensions::Command::AcceleratorToString(accelerator);
52 // Media keys have a 1-to-many relationship with targets, unlike regular
53 // shortcut (1-to-1 relationship). That means two or more extensions can
54 // register for the same media key so the extension ID needs to be added to
55 // the key to make sure the key is unique.
56 if (extensions::CommandService::IsMediaKey(accelerator))
57 key += ":" + extension_id;
62 bool IsForCurrentPlatform(const std::string& key) {
63 return StartsWithASCII(
64 key, extensions::Command::CommandPlatform() + ":", true);
67 void SetInitialBindingsHaveBeenAssigned(
68 ExtensionPrefs* prefs, const std::string& extension_id) {
69 prefs->UpdateExtensionPref(extension_id, kInitialBindingsHaveBeenAssigned,
70 new base::FundamentalValue(true));
73 bool InitialBindingsHaveBeenAssigned(
74 const ExtensionPrefs* prefs, const std::string& extension_id) {
75 bool assigned = false;
76 if (!prefs || !prefs->ReadPrefAsBoolean(extension_id,
77 kInitialBindingsHaveBeenAssigned,
84 // Checks if |extension| is permitted to automatically assign the |accelerator|
86 bool CanAutoAssign(const ui::Accelerator& accelerator,
87 const Extension* extension,
89 bool is_named_command,
91 // Media Keys are non-exclusive, so allow auto-assigning them.
92 if (extensions::CommandService::IsMediaKey(accelerator))
96 if (!is_named_command)
97 return false; // Browser and page actions are not global in nature.
99 // Global shortcuts are restricted to (Ctrl|Command)+Shift+[0-9].
100 #if defined OS_MACOSX
101 if (!accelerator.IsCmdDown())
104 if (!accelerator.IsCtrlDown())
107 if (!accelerator.IsShiftDown())
109 return (accelerator.key_code() >= ui::VKEY_0 &&
110 accelerator.key_code() <= ui::VKEY_9);
112 // Not a global command, check if Chrome shortcut and whether
113 // we can override it.
115 chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE)) {
116 using extensions::SettingsOverrides;
117 using extensions::FeatureSwitch;
118 const SettingsOverrides* settings_overrides =
119 SettingsOverrides::Get(extension);
120 if (settings_overrides &&
121 SettingsOverrides::RemovesBookmarkShortcut(*settings_overrides) &&
122 (extensions::PermissionsData::HasAPIPermission(
124 extensions::APIPermission::kBookmarkManagerPrivate) ||
125 FeatureSwitch::enable_override_bookmarks_ui()->IsEnabled())) {
126 // If this check fails it either means we have an API to override a
127 // key that isn't a ChromeAccelerator (and the API can therefore be
128 // deprecated) or the IsChromeAccelerator isn't consistently
129 // returning true for all accelerators.
130 DCHECK(chrome::IsChromeAccelerator(accelerator, profile));
135 return !chrome::IsChromeAccelerator(accelerator, profile);
141 namespace extensions {
144 void CommandService::RegisterProfilePrefs(
145 user_prefs::PrefRegistrySyncable* registry) {
146 registry->RegisterDictionaryPref(
147 prefs::kExtensionCommands,
148 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
151 CommandService::CommandService(Profile* profile)
152 : profile_(profile) {
153 ExtensionFunctionRegistry::GetInstance()->
154 RegisterFunction<GetAllCommandsFunction>();
156 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED,
157 content::Source<Profile>(profile));
158 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
159 content::Source<Profile>(profile));
162 CommandService::~CommandService() {
165 static base::LazyInstance<ProfileKeyedAPIFactory<CommandService> >
166 g_factory = LAZY_INSTANCE_INITIALIZER;
169 ProfileKeyedAPIFactory<CommandService>* CommandService::GetFactoryInstance() {
170 return g_factory.Pointer();
174 CommandService* CommandService::Get(Profile* profile) {
175 return ProfileKeyedAPIFactory<CommandService>::GetForProfile(profile);
179 bool CommandService::IsMediaKey(const ui::Accelerator& accelerator) {
180 if (accelerator.modifiers() != 0)
183 return (accelerator.key_code() == ui::VKEY_MEDIA_NEXT_TRACK ||
184 accelerator.key_code() == ui::VKEY_MEDIA_PREV_TRACK ||
185 accelerator.key_code() == ui::VKEY_MEDIA_PLAY_PAUSE ||
186 accelerator.key_code() == ui::VKEY_MEDIA_STOP);
189 bool CommandService::GetBrowserActionCommand(
190 const std::string& extension_id,
192 extensions::Command* command,
194 return GetExtensionActionCommand(
195 extension_id, type, command, active, BROWSER_ACTION);
198 bool CommandService::GetPageActionCommand(
199 const std::string& extension_id,
201 extensions::Command* command,
203 return GetExtensionActionCommand(
204 extension_id, type, command, active, PAGE_ACTION);
207 bool CommandService::GetNamedCommands(const std::string& extension_id,
210 extensions::CommandMap* command_map) {
211 ExtensionService* extension_service =
212 ExtensionSystem::Get(profile_)->extension_service();
213 if (!extension_service)
214 return false; // Can occur during testing.
215 const ExtensionSet* extensions = extension_service->extensions();
216 const Extension* extension = extensions->GetByID(extension_id);
219 command_map->clear();
220 const extensions::CommandMap* commands =
221 CommandsInfo::GetNamedCommands(extension);
225 extensions::CommandMap::const_iterator iter = commands->begin();
226 for (; iter != commands->end(); ++iter) {
227 // Look up to see if the user has overridden how the command should work.
228 extensions::Command saved_command =
229 FindCommandByName(extension_id, iter->second.command_name());
230 ui::Accelerator shortcut_assigned = saved_command.accelerator();
232 if (type == ACTIVE_ONLY && shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
235 extensions::Command command = iter->second;
236 if (scope != ANY_SCOPE && ((scope == GLOBAL) != saved_command.global()))
239 if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
240 command.set_accelerator(shortcut_assigned);
241 command.set_global(saved_command.global());
243 (*command_map)[iter->second.command_name()] = command;
249 bool CommandService::AddKeybindingPref(
250 const ui::Accelerator& accelerator,
251 std::string extension_id,
252 std::string command_name,
253 bool allow_overrides,
255 if (accelerator.key_code() == ui::VKEY_UNKNOWN)
258 // Media Keys are allowed to be used by named command only.
259 DCHECK(!IsMediaKey(accelerator) ||
260 (command_name != manifest_values::kPageActionCommandEvent &&
261 command_name != manifest_values::kBrowserActionCommandEvent));
263 DictionaryPrefUpdate updater(profile_->GetPrefs(),
264 prefs::kExtensionCommands);
265 base::DictionaryValue* bindings = updater.Get();
267 std::string key = GetPlatformKeybindingKeyForAccelerator(accelerator,
270 if (bindings->HasKey(key)) {
271 if (!allow_overrides)
272 return false; // Already taken.
274 // If the shortcut has been assigned to another command, it should be
275 // removed before overriding, so that |ExtensionKeybindingRegistry| can get
276 // a chance to do clean-up.
277 const base::DictionaryValue* item = NULL;
278 bindings->GetDictionary(key, &item);
279 std::string old_extension_id;
280 std::string old_command_name;
281 item->GetString(kExtension, &old_extension_id);
282 item->GetString(kCommandName, &old_command_name);
283 RemoveKeybindingPrefs(old_extension_id, old_command_name);
286 base::DictionaryValue* keybinding = new base::DictionaryValue();
287 keybinding->SetString(kExtension, extension_id);
288 keybinding->SetString(kCommandName, command_name);
289 keybinding->SetBoolean(kGlobal, global);
291 bindings->Set(key, keybinding);
293 std::pair<const std::string, const std::string> details =
294 std::make_pair(extension_id, command_name);
295 content::NotificationService::current()->Notify(
296 chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED,
297 content::Source<Profile>(profile_),
299 std::pair<const std::string, const std::string> >(&details));
304 void CommandService::Observe(
306 const content::NotificationSource& source,
307 const content::NotificationDetails& details) {
309 case chrome::NOTIFICATION_EXTENSION_INSTALLED:
310 AssignInitialKeybindings(
311 content::Details<const InstalledExtensionInfo>(details)->extension);
313 case chrome::NOTIFICATION_EXTENSION_UNINSTALLED:
314 RemoveKeybindingPrefs(
315 content::Details<const Extension>(details)->id(),
324 void CommandService::UpdateKeybindingPrefs(const std::string& extension_id,
325 const std::string& command_name,
326 const std::string& keystroke) {
327 extensions::Command command = FindCommandByName(extension_id, command_name);
329 // The extension command might be assigned another shortcut. Remove that
330 // shortcut before proceeding.
331 RemoveKeybindingPrefs(extension_id, command_name);
333 ui::Accelerator accelerator =
334 Command::StringToAccelerator(keystroke, command_name);
335 AddKeybindingPref(accelerator, extension_id, command_name,
336 true, command.global());
339 bool CommandService::SetScope(const std::string& extension_id,
340 const std::string& command_name,
342 extensions::Command command = FindCommandByName(extension_id, command_name);
343 if (global == command.global())
346 // Pre-existing shortcuts must be removed before proceeding because the
347 // handlers for global and non-global extensions are not one and the same.
348 RemoveKeybindingPrefs(extension_id, command_name);
349 AddKeybindingPref(command.accelerator(), extension_id,
350 command_name, true, global);
354 Command CommandService::FindCommandByName(
355 const std::string& extension_id, const std::string& command) {
356 const base::DictionaryValue* bindings =
357 profile_->GetPrefs()->GetDictionary(prefs::kExtensionCommands);
358 for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
360 const base::DictionaryValue* item = NULL;
361 it.value().GetAsDictionary(&item);
363 std::string extension;
364 item->GetString(kExtension, &extension);
365 if (extension != extension_id)
367 std::string command_name;
368 item->GetString(kCommandName, &command_name);
369 if (command != command_name)
371 // Format stored in Preferences is: "Platform:Shortcut[:ExtensionId]".
372 std::string shortcut = it.key();
373 if (!IsForCurrentPlatform(shortcut))
376 if (FeatureSwitch::global_commands()->IsEnabled())
377 item->GetBoolean(kGlobal, &global);
379 std::vector<std::string> tokens;
380 base::SplitString(shortcut, ':', &tokens);
381 CHECK(tokens.size() >= 2);
382 shortcut = tokens[1];
384 return Command(command_name, base::string16(), shortcut, global);
390 void CommandService::AssignInitialKeybindings(const Extension* extension) {
391 const extensions::CommandMap* commands =
392 CommandsInfo::GetNamedCommands(extension);
396 ExtensionService* extension_service =
397 ExtensionSystem::Get(profile_)->extension_service();
398 ExtensionPrefs* extension_prefs = extension_service->extension_prefs();
399 if (InitialBindingsHaveBeenAssigned(extension_prefs, extension->id()))
401 SetInitialBindingsHaveBeenAssigned(extension_prefs, extension->id());
403 extensions::CommandMap::const_iterator iter = commands->begin();
404 for (; iter != commands->end(); ++iter) {
405 const extensions::Command command = iter->second;
406 if (CanAutoAssign(command.accelerator(),
409 true, // Is a named command.
411 AddKeybindingPref(command.accelerator(),
413 command.command_name(),
414 false, // Overwriting not allowed.
419 const extensions::Command* browser_action_command =
420 CommandsInfo::GetBrowserActionCommand(extension);
421 if (browser_action_command &&
422 CanAutoAssign(browser_action_command->accelerator(),
425 false, // Not a named command.
426 false)) { // Not global.
427 AddKeybindingPref(browser_action_command->accelerator(),
429 browser_action_command->command_name(),
430 false, // Overwriting not allowed.
431 false); // Not global.
434 const extensions::Command* page_action_command =
435 CommandsInfo::GetPageActionCommand(extension);
436 if (page_action_command &&
437 CanAutoAssign(page_action_command->accelerator(),
440 false, // Not a named command.
441 false)) { // Not global.
442 AddKeybindingPref(page_action_command->accelerator(),
444 page_action_command->command_name(),
445 false, // Overwriting not allowed.
446 false); // Not global.
450 void CommandService::RemoveKeybindingPrefs(const std::string& extension_id,
451 const std::string& command_name) {
452 DictionaryPrefUpdate updater(profile_->GetPrefs(),
453 prefs::kExtensionCommands);
454 base::DictionaryValue* bindings = updater.Get();
456 typedef std::vector<std::string> KeysToRemove;
457 KeysToRemove keys_to_remove;
458 for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
460 // Removal of keybinding preference should be limited to current platform.
461 if (!IsForCurrentPlatform(it.key()))
464 const base::DictionaryValue* item = NULL;
465 it.value().GetAsDictionary(&item);
467 std::string extension;
468 item->GetString(kExtension, &extension);
470 if (extension == extension_id) {
471 // If |command_name| is specified, delete only that command. Otherwise,
472 // delete all commands.
473 if (!command_name.empty()) {
475 item->GetString(kCommandName, &command);
476 if (command_name != command)
480 keys_to_remove.push_back(it.key());
484 for (KeysToRemove::const_iterator it = keys_to_remove.begin();
485 it != keys_to_remove.end(); ++it) {
486 std::string key = *it;
487 bindings->Remove(key, NULL);
489 std::pair<const std::string, const std::string> details =
490 std::make_pair(extension_id, command_name);
491 content::NotificationService::current()->Notify(
492 chrome::NOTIFICATION_EXTENSION_COMMAND_REMOVED,
493 content::Source<Profile>(profile_),
495 std::pair<const std::string, const std::string> >(&details));
499 bool CommandService::GetExtensionActionCommand(
500 const std::string& extension_id,
501 QueryType query_type,
502 extensions::Command* command,
504 ExtensionActionType action_type) {
505 ExtensionService* service =
506 ExtensionSystem::Get(profile_)->extension_service();
508 return false; // Can happen in tests.
509 const ExtensionSet* extensions = service->extensions();
510 const Extension* extension = extensions->GetByID(extension_id);
516 const extensions::Command* requested_command = NULL;
517 switch (action_type) {
519 requested_command = CommandsInfo::GetBrowserActionCommand(extension);
522 requested_command = CommandsInfo::GetPageActionCommand(extension);
525 if (!requested_command)
528 // Look up to see if the user has overridden how the command should work.
529 extensions::Command saved_command =
530 FindCommandByName(extension_id, requested_command->command_name());
531 ui::Accelerator shortcut_assigned = saved_command.accelerator();
534 *active = (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN);
536 if (query_type == ACTIVE_ONLY &&
537 shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
540 *command = *requested_command;
541 if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
542 command->set_accelerator(shortcut_assigned);
548 void ProfileKeyedAPIFactory<CommandService>::DeclareFactoryDependencies() {
549 DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance());
552 } // namespace extensions