Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / extensions / api / commands / command_service.cc
blob5ff87113ecf42f457c2a7786281829ec947d1b49
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"
7 #include <vector>
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/extensions/api/commands/commands.h"
16 #include "chrome/browser/extensions/extension_commands_global_registry.h"
17 #include "chrome/browser/extensions/extension_keybinding_registry.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/ui/accelerator_utils.h"
20 #include "chrome/common/extensions/api/commands/commands_handler.h"
21 #include "chrome/common/extensions/manifest_handlers/ui_overrides_handler.h"
22 #include "chrome/common/pref_names.h"
23 #include "components/pref_registry/pref_registry_syncable.h"
24 #include "content/public/browser/notification_details.h"
25 #include "content/public/browser/notification_service.h"
26 #include "extensions/browser/extension_function_registry.h"
27 #include "extensions/browser/extension_prefs.h"
28 #include "extensions/browser/extension_registry.h"
29 #include "extensions/browser/extension_system.h"
30 #include "extensions/browser/notification_types.h"
31 #include "extensions/common/feature_switch.h"
32 #include "extensions/common/manifest_constants.h"
33 #include "extensions/common/permissions/permissions_data.h"
35 namespace extensions {
36 namespace {
38 const char kExtension[] = "extension";
39 const char kCommandName[] = "command_name";
40 const char kGlobal[] = "global";
42 // A preference that stores keybinding state associated with extension commands.
43 const char kCommands[] = "commands";
45 // Preference key name for saving the extension-suggested key.
46 const char kSuggestedKey[] = "suggested_key";
48 // Preference key name for saving whether the extension-suggested key was
49 // actually assigned.
50 const char kSuggestedKeyWasAssigned[] = "was_assigned";
52 // A preference that indicates that the initial keybindings for the given
53 // extension have been set.
54 const char kInitialBindingsHaveBeenAssigned[] = "initial_keybindings_set";
56 std::string GetPlatformKeybindingKeyForAccelerator(
57 const ui::Accelerator& accelerator, const std::string& extension_id) {
58 std::string key = Command::CommandPlatform() + ":" +
59 Command::AcceleratorToString(accelerator);
61 // Media keys have a 1-to-many relationship with targets, unlike regular
62 // shortcut (1-to-1 relationship). That means two or more extensions can
63 // register for the same media key so the extension ID needs to be added to
64 // the key to make sure the key is unique.
65 if (Command::IsMediaKey(accelerator))
66 key += ":" + extension_id;
68 return key;
71 bool IsForCurrentPlatform(const std::string& key) {
72 return base::StartsWith(key, Command::CommandPlatform() + ":",
73 base::CompareCase::SENSITIVE);
76 std::string StripCurrentPlatform(const std::string& key) {
77 DCHECK(IsForCurrentPlatform(key));
78 std::string result = key;
79 base::ReplaceFirstSubstringAfterOffset(
80 &result, 0, Command::CommandPlatform() + ":", base::StringPiece());
81 return result;
84 void SetInitialBindingsHaveBeenAssigned(
85 ExtensionPrefs* prefs, const std::string& extension_id) {
86 prefs->UpdateExtensionPref(extension_id, kInitialBindingsHaveBeenAssigned,
87 new base::FundamentalValue(true));
90 bool InitialBindingsHaveBeenAssigned(
91 const ExtensionPrefs* prefs, const std::string& extension_id) {
92 bool assigned = false;
93 if (!prefs || !prefs->ReadPrefAsBoolean(extension_id,
94 kInitialBindingsHaveBeenAssigned,
95 &assigned))
96 return false;
98 return assigned;
101 // Merge |suggested_key_prefs| into the saved preferences for the extension. We
102 // merge rather than overwrite to preserve existing was_assigned preferences.
103 void MergeSuggestedKeyPrefs(
104 const std::string& extension_id,
105 ExtensionPrefs* extension_prefs,
106 scoped_ptr<base::DictionaryValue> suggested_key_prefs) {
107 const base::DictionaryValue* current_prefs;
108 if (extension_prefs->ReadPrefAsDictionary(extension_id,
109 kCommands,
110 &current_prefs)) {
111 scoped_ptr<base::DictionaryValue> new_prefs(current_prefs->DeepCopy());
112 new_prefs->MergeDictionary(suggested_key_prefs.get());
113 suggested_key_prefs.reset(new_prefs.release());
116 extension_prefs->UpdateExtensionPref(extension_id,
117 kCommands,
118 suggested_key_prefs.release());
121 } // namespace
123 // static
124 void CommandService::RegisterProfilePrefs(
125 user_prefs::PrefRegistrySyncable* registry) {
126 registry->RegisterDictionaryPref(
127 prefs::kExtensionCommands,
128 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
131 CommandService::CommandService(content::BrowserContext* context)
132 : profile_(Profile::FromBrowserContext(context)),
133 extension_registry_observer_(this) {
134 ExtensionFunctionRegistry::GetInstance()->
135 RegisterFunction<GetAllCommandsFunction>();
137 extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
140 CommandService::~CommandService() {
143 static base::LazyInstance<BrowserContextKeyedAPIFactory<CommandService> >
144 g_factory = LAZY_INSTANCE_INITIALIZER;
146 // static
147 BrowserContextKeyedAPIFactory<CommandService>*
148 CommandService::GetFactoryInstance() {
149 return g_factory.Pointer();
152 // static
153 CommandService* CommandService::Get(content::BrowserContext* context) {
154 return BrowserContextKeyedAPIFactory<CommandService>::Get(context);
157 // static
158 bool CommandService::RemovesBookmarkShortcut(const Extension* extension) {
159 return UIOverrides::RemovesBookmarkShortcut(extension) &&
160 (extension->permissions_data()->HasAPIPermission(
161 APIPermission::kBookmarkManagerPrivate) ||
162 FeatureSwitch::enable_override_bookmarks_ui()->IsEnabled());
165 // static
166 bool CommandService::RemovesBookmarkOpenPagesShortcut(
167 const Extension* extension) {
168 return UIOverrides::RemovesBookmarkOpenPagesShortcut(extension) &&
169 (extension->permissions_data()->HasAPIPermission(
170 APIPermission::kBookmarkManagerPrivate) ||
171 FeatureSwitch::enable_override_bookmarks_ui()->IsEnabled());
174 bool CommandService::GetBrowserActionCommand(const std::string& extension_id,
175 QueryType type,
176 Command* command,
177 bool* active) const {
178 return GetExtensionActionCommand(
179 extension_id, type, command, active, BROWSER_ACTION);
182 bool CommandService::GetPageActionCommand(const std::string& extension_id,
183 QueryType type,
184 Command* command,
185 bool* active) const {
186 return GetExtensionActionCommand(
187 extension_id, type, command, active, PAGE_ACTION);
190 bool CommandService::GetNamedCommands(const std::string& extension_id,
191 QueryType type,
192 CommandScope scope,
193 CommandMap* command_map) const {
194 const ExtensionSet& extensions =
195 ExtensionRegistry::Get(profile_)->enabled_extensions();
196 const Extension* extension = extensions.GetByID(extension_id);
197 CHECK(extension);
199 command_map->clear();
200 const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
201 if (!commands)
202 return false;
204 for (CommandMap::const_iterator iter = commands->begin();
205 iter != commands->end(); ++iter) {
206 // Look up to see if the user has overridden how the command should work.
207 Command saved_command =
208 FindCommandByName(extension_id, iter->second.command_name());
209 ui::Accelerator shortcut_assigned = saved_command.accelerator();
211 if (type == ACTIVE && shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
212 continue;
214 Command command = iter->second;
215 if (scope != ANY_SCOPE && ((scope == GLOBAL) != saved_command.global()))
216 continue;
218 if (type != SUGGESTED && shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
219 command.set_accelerator(shortcut_assigned);
220 command.set_global(saved_command.global());
222 (*command_map)[iter->second.command_name()] = command;
225 return !command_map->empty();
228 bool CommandService::AddKeybindingPref(
229 const ui::Accelerator& accelerator,
230 const std::string& extension_id,
231 const std::string& command_name,
232 bool allow_overrides,
233 bool global) {
234 if (accelerator.key_code() == ui::VKEY_UNKNOWN)
235 return false;
237 // Nothing needs to be done if the existing command is the same as the desired
238 // new one.
239 Command existing_command = FindCommandByName(extension_id, command_name);
240 if (existing_command.accelerator() == accelerator &&
241 existing_command.global() == global)
242 return true;
244 // Media Keys are allowed to be used by named command only.
245 DCHECK(!Command::IsMediaKey(accelerator) ||
246 (command_name != manifest_values::kPageActionCommandEvent &&
247 command_name != manifest_values::kBrowserActionCommandEvent));
249 DictionaryPrefUpdate updater(profile_->GetPrefs(),
250 prefs::kExtensionCommands);
251 base::DictionaryValue* bindings = updater.Get();
253 std::string key = GetPlatformKeybindingKeyForAccelerator(accelerator,
254 extension_id);
256 if (bindings->HasKey(key)) {
257 if (!allow_overrides)
258 return false; // Already taken.
260 // If the shortcut has been assigned to another command, it should be
261 // removed before overriding, so that |ExtensionKeybindingRegistry| can get
262 // a chance to do clean-up.
263 const base::DictionaryValue* item = NULL;
264 bindings->GetDictionary(key, &item);
265 std::string old_extension_id;
266 std::string old_command_name;
267 item->GetString(kExtension, &old_extension_id);
268 item->GetString(kCommandName, &old_command_name);
269 RemoveKeybindingPrefs(old_extension_id, old_command_name);
272 // If the command that is taking a new shortcut already has a shortcut, remove
273 // it before assigning the new one.
274 if (existing_command.accelerator().key_code() != ui::VKEY_UNKNOWN)
275 RemoveKeybindingPrefs(extension_id, command_name);
277 // Set the keybinding pref.
278 base::DictionaryValue* keybinding = new base::DictionaryValue();
279 keybinding->SetString(kExtension, extension_id);
280 keybinding->SetString(kCommandName, command_name);
281 keybinding->SetBoolean(kGlobal, global);
283 bindings->Set(key, keybinding);
285 // Set the was_assigned pref for the suggested key.
286 scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
287 command_keys->SetBoolean(kSuggestedKeyWasAssigned, true);
288 scoped_ptr<base::DictionaryValue> suggested_key_prefs(
289 new base::DictionaryValue);
290 suggested_key_prefs->Set(command_name, command_keys.release());
291 MergeSuggestedKeyPrefs(extension_id,
292 ExtensionPrefs::Get(profile_),
293 suggested_key_prefs.Pass());
295 // Fetch the newly-updated command, and notify the observers.
296 FOR_EACH_OBSERVER(
297 Observer,
298 observers_,
299 OnExtensionCommandAdded(extension_id,
300 FindCommandByName(extension_id, command_name)));
302 // TODO(devlin): Deprecate this notification in favor of the observers.
303 std::pair<const std::string, const std::string> details =
304 std::make_pair(extension_id, command_name);
305 content::NotificationService::current()->Notify(
306 extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED,
307 content::Source<Profile>(profile_),
308 content::Details<std::pair<const std::string, const std::string> >(
309 &details));
311 return true;
314 void CommandService::OnExtensionWillBeInstalled(
315 content::BrowserContext* browser_context,
316 const Extension* extension,
317 bool is_update,
318 bool from_ephemeral,
319 const std::string& old_name) {
320 UpdateKeybindings(extension);
323 void CommandService::OnExtensionUninstalled(
324 content::BrowserContext* browser_context,
325 const Extension* extension,
326 extensions::UninstallReason reason) {
327 // Adding a component extensions will only trigger install the first time on a
328 // clean profile or on a version increase (see
329 // ComponentLoader::AddComponentExtension). It will, however, always trigger
330 // an uninstall on removal. See http://crbug.com/458612. Isolate this case and
331 // ignore it.
332 if (reason == extensions::UNINSTALL_REASON_COMPONENT_REMOVED)
333 return;
335 RemoveKeybindingPrefs(extension->id(), std::string());
338 void CommandService::UpdateKeybindingPrefs(const std::string& extension_id,
339 const std::string& command_name,
340 const std::string& keystroke) {
341 Command command = FindCommandByName(extension_id, command_name);
343 // The extension command might be assigned another shortcut. Remove that
344 // shortcut before proceeding.
345 RemoveKeybindingPrefs(extension_id, command_name);
347 ui::Accelerator accelerator =
348 Command::StringToAccelerator(keystroke, command_name);
349 AddKeybindingPref(accelerator, extension_id, command_name,
350 true, command.global());
353 bool CommandService::SetScope(const std::string& extension_id,
354 const std::string& command_name,
355 bool global) {
356 Command command = FindCommandByName(extension_id, command_name);
357 if (global == command.global())
358 return false;
360 // Pre-existing shortcuts must be removed before proceeding because the
361 // handlers for global and non-global extensions are not one and the same.
362 RemoveKeybindingPrefs(extension_id, command_name);
363 AddKeybindingPref(command.accelerator(), extension_id,
364 command_name, true, global);
365 return true;
368 Command CommandService::FindCommandByName(const std::string& extension_id,
369 const std::string& command) const {
370 const base::DictionaryValue* bindings =
371 profile_->GetPrefs()->GetDictionary(prefs::kExtensionCommands);
372 for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
373 it.Advance()) {
374 const base::DictionaryValue* item = NULL;
375 it.value().GetAsDictionary(&item);
377 std::string extension;
378 item->GetString(kExtension, &extension);
379 if (extension != extension_id)
380 continue;
381 std::string command_name;
382 item->GetString(kCommandName, &command_name);
383 if (command != command_name)
384 continue;
385 // Format stored in Preferences is: "Platform:Shortcut[:ExtensionId]".
386 std::string shortcut = it.key();
387 if (!IsForCurrentPlatform(shortcut))
388 continue;
389 bool global = false;
390 item->GetBoolean(kGlobal, &global);
392 std::vector<base::StringPiece> tokens = base::SplitStringPiece(
393 shortcut, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
394 CHECK(tokens.size() >= 2);
396 return Command(command_name, base::string16(), tokens[1].as_string(),
397 global);
400 return Command();
403 bool CommandService::GetSuggestedExtensionCommand(
404 const std::string& extension_id,
405 const ui::Accelerator& accelerator,
406 Command* command,
407 ExtensionCommandType* command_type) const {
408 const Extension* extension =
409 ExtensionRegistry::Get(profile_)
410 ->GetExtensionById(extension_id, ExtensionRegistry::ENABLED);
411 CHECK(extension);
413 Command prospective_command;
414 CommandMap command_map;
415 if (GetBrowserActionCommand(extension_id,
416 CommandService::SUGGESTED,
417 &prospective_command,
418 nullptr) &&
419 accelerator == prospective_command.accelerator()) {
420 if (command)
421 *command = prospective_command;
422 if (command_type)
423 *command_type = BROWSER_ACTION;
424 return true;
425 } else if (GetPageActionCommand(extension_id,
426 CommandService::SUGGESTED,
427 &prospective_command,
428 nullptr) &&
429 accelerator == prospective_command.accelerator()) {
430 if (command)
431 *command = prospective_command;
432 if (command_type)
433 *command_type = PAGE_ACTION;
434 return true;
435 } else if (GetNamedCommands(extension_id,
436 CommandService::SUGGESTED,
437 CommandService::REGULAR,
438 &command_map)) {
439 for (CommandMap::const_iterator it = command_map.begin();
440 it != command_map.end();
441 ++it) {
442 if (accelerator == it->second.accelerator()) {
443 if (command)
444 *command = it->second;
445 if (command_type)
446 *command_type = NAMED;
447 return true;
451 return false;
454 bool CommandService::RequestsBookmarkShortcutOverride(
455 const Extension* extension) const {
456 return RemovesBookmarkShortcut(extension) &&
457 GetSuggestedExtensionCommand(
458 extension->id(),
459 chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE),
460 NULL,
461 NULL);
464 void CommandService::AddObserver(Observer* observer) {
465 observers_.AddObserver(observer);
468 void CommandService::RemoveObserver(Observer* observer) {
469 observers_.RemoveObserver(observer);
472 void CommandService::UpdateKeybindings(const Extension* extension) {
473 const ExtensionSet& extensions =
474 ExtensionRegistry::Get(profile_)->enabled_extensions();
475 // The extension is not added to the profile by this point on first install,
476 // so don't try to check for existing keybindings.
477 if (extensions.GetByID(extension->id()))
478 RemoveRelinquishedKeybindings(extension);
479 AssignKeybindings(extension);
480 UpdateExtensionSuggestedCommandPrefs(extension);
481 RemoveDefunctExtensionSuggestedCommandPrefs(extension);
484 void CommandService::RemoveRelinquishedKeybindings(const Extension* extension) {
485 // Remove keybindings if they have been removed by the extension and the user
486 // has not modified them.
487 CommandMap existing_command_map;
488 if (GetNamedCommands(extension->id(),
489 CommandService::ACTIVE,
490 CommandService::REGULAR,
491 &existing_command_map)) {
492 const CommandMap* new_command_map =
493 CommandsInfo::GetNamedCommands(extension);
494 for (CommandMap::const_iterator it = existing_command_map.begin();
495 it != existing_command_map.end(); ++it) {
496 std::string command_name = it->first;
497 if (new_command_map->find(command_name) == new_command_map->end() &&
498 !IsCommandShortcutUserModified(extension, command_name)) {
499 RemoveKeybindingPrefs(extension->id(), command_name);
504 Command existing_browser_action_command;
505 const Command* new_browser_action_command =
506 CommandsInfo::GetBrowserActionCommand(extension);
507 if (GetBrowserActionCommand(extension->id(),
508 CommandService::ACTIVE,
509 &existing_browser_action_command,
510 NULL) &&
511 // The browser action command may be defaulted to an unassigned
512 // accelerator if a browser action is specified by the extension but a
513 // keybinding is not declared. See
514 // CommandsHandler::MaybeSetBrowserActionDefault.
515 (!new_browser_action_command ||
516 new_browser_action_command->accelerator().key_code() ==
517 ui::VKEY_UNKNOWN) &&
518 !IsCommandShortcutUserModified(
519 extension,
520 existing_browser_action_command.command_name())) {
521 RemoveKeybindingPrefs(extension->id(),
522 existing_browser_action_command.command_name());
525 Command existing_page_action_command;
526 if (GetPageActionCommand(extension->id(),
527 CommandService::ACTIVE,
528 &existing_page_action_command,
529 NULL) &&
530 !CommandsInfo::GetPageActionCommand(extension) &&
531 !IsCommandShortcutUserModified(
532 extension,
533 existing_page_action_command.command_name())) {
534 RemoveKeybindingPrefs(extension->id(),
535 existing_page_action_command.command_name());
539 void CommandService::AssignKeybindings(const Extension* extension) {
540 const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
541 if (!commands)
542 return;
544 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
545 // TODO(wittman): remove use of this pref after M37 hits stable.
546 if (!InitialBindingsHaveBeenAssigned(extension_prefs, extension->id()))
547 SetInitialBindingsHaveBeenAssigned(extension_prefs, extension->id());
549 for (CommandMap::const_iterator iter = commands->begin();
550 iter != commands->end(); ++iter) {
551 const Command command = iter->second;
552 if (CanAutoAssign(command, extension)) {
553 AddKeybindingPref(command.accelerator(),
554 extension->id(),
555 command.command_name(),
556 false, // Overwriting not allowed.
557 command.global());
561 const Command* browser_action_command =
562 CommandsInfo::GetBrowserActionCommand(extension);
563 if (browser_action_command &&
564 CanAutoAssign(*browser_action_command, extension)) {
565 AddKeybindingPref(browser_action_command->accelerator(),
566 extension->id(),
567 browser_action_command->command_name(),
568 false, // Overwriting not allowed.
569 false); // Not global.
572 const Command* page_action_command =
573 CommandsInfo::GetPageActionCommand(extension);
574 if (page_action_command && CanAutoAssign(*page_action_command, extension)) {
575 AddKeybindingPref(page_action_command->accelerator(),
576 extension->id(),
577 page_action_command->command_name(),
578 false, // Overwriting not allowed.
579 false); // Not global.
583 bool CommandService::CanAutoAssign(const Command &command,
584 const Extension* extension) {
585 // Extensions are allowed to auto-assign updated keys if the user has not
586 // changed from the previous value.
587 if (IsCommandShortcutUserModified(extension, command.command_name()))
588 return false;
590 // Media Keys are non-exclusive, so allow auto-assigning them.
591 if (Command::IsMediaKey(command.accelerator()))
592 return true;
594 if (command.global()) {
595 using namespace extensions;
596 if (command.command_name() == manifest_values::kBrowserActionCommandEvent ||
597 command.command_name() == manifest_values::kPageActionCommandEvent)
598 return false; // Browser and page actions are not global in nature.
600 if (extension->permissions_data()->HasAPIPermission(
601 APIPermission::kCommandsAccessibility))
602 return true;
604 // Global shortcuts are restricted to (Ctrl|Command)+Shift+[0-9].
605 #if defined OS_MACOSX
606 if (!command.accelerator().IsCmdDown())
607 return false;
608 #else
609 if (!command.accelerator().IsCtrlDown())
610 return false;
611 #endif
612 if (!command.accelerator().IsShiftDown())
613 return false;
614 return (command.accelerator().key_code() >= ui::VKEY_0 &&
615 command.accelerator().key_code() <= ui::VKEY_9);
616 } else {
617 // Not a global command, check if Chrome shortcut and whether
618 // we can override it.
619 if (command.accelerator() ==
620 chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE) &&
621 CommandService::RemovesBookmarkShortcut(extension)) {
622 // If this check fails it either means we have an API to override a
623 // key that isn't a ChromeAccelerator (and the API can therefore be
624 // deprecated) or the IsChromeAccelerator isn't consistently
625 // returning true for all accelerators.
626 DCHECK(chrome::IsChromeAccelerator(command.accelerator(), profile_));
627 return true;
630 return !chrome::IsChromeAccelerator(command.accelerator(), profile_);
634 void CommandService::UpdateExtensionSuggestedCommandPrefs(
635 const Extension* extension) {
636 scoped_ptr<base::DictionaryValue> suggested_key_prefs(
637 new base::DictionaryValue);
639 const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
640 if (commands) {
641 for (CommandMap::const_iterator iter = commands->begin();
642 iter != commands->end(); ++iter) {
643 const Command command = iter->second;
644 scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
645 command_keys->SetString(
646 kSuggestedKey,
647 Command::AcceleratorToString(command.accelerator()));
648 suggested_key_prefs->Set(command.command_name(), command_keys.release());
652 const Command* browser_action_command =
653 CommandsInfo::GetBrowserActionCommand(extension);
654 // The browser action command may be defaulted to an unassigned accelerator if
655 // a browser action is specified by the extension but a keybinding is not
656 // declared. See CommandsHandler::MaybeSetBrowserActionDefault.
657 if (browser_action_command &&
658 browser_action_command->accelerator().key_code() != ui::VKEY_UNKNOWN) {
659 scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
660 command_keys->SetString(
661 kSuggestedKey,
662 Command::AcceleratorToString(browser_action_command->accelerator()));
663 suggested_key_prefs->Set(browser_action_command->command_name(),
664 command_keys.release());
667 const Command* page_action_command =
668 CommandsInfo::GetPageActionCommand(extension);
669 if (page_action_command) {
670 scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
671 command_keys->SetString(
672 kSuggestedKey,
673 Command::AcceleratorToString(page_action_command->accelerator()));
674 suggested_key_prefs->Set(page_action_command->command_name(),
675 command_keys.release());
678 // Merge into current prefs, if present.
679 MergeSuggestedKeyPrefs(extension->id(),
680 ExtensionPrefs::Get(profile_),
681 suggested_key_prefs.Pass());
684 void CommandService::RemoveDefunctExtensionSuggestedCommandPrefs(
685 const Extension* extension) {
686 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
687 const base::DictionaryValue* current_prefs = NULL;
688 extension_prefs->ReadPrefAsDictionary(extension->id(),
689 kCommands,
690 &current_prefs);
692 if (current_prefs) {
693 scoped_ptr<base::DictionaryValue> suggested_key_prefs(
694 current_prefs->DeepCopy());
695 const CommandMap* named_commands =
696 CommandsInfo::GetNamedCommands(extension);
697 const Command* browser_action_command =
698 CommandsInfo::GetBrowserActionCommand(extension);
699 for (base::DictionaryValue::Iterator it(*current_prefs);
700 !it.IsAtEnd(); it.Advance()) {
701 if (it.key() == manifest_values::kBrowserActionCommandEvent) {
702 // The browser action command may be defaulted to an unassigned
703 // accelerator if a browser action is specified by the extension but a
704 // keybinding is not declared. See
705 // CommandsHandler::MaybeSetBrowserActionDefault.
706 if (!browser_action_command ||
707 browser_action_command->accelerator().key_code() ==
708 ui::VKEY_UNKNOWN) {
709 suggested_key_prefs->Remove(it.key(), NULL);
711 } else if (it.key() == manifest_values::kPageActionCommandEvent) {
712 if (!CommandsInfo::GetPageActionCommand(extension))
713 suggested_key_prefs->Remove(it.key(), NULL);
714 } else if (named_commands) {
715 if (named_commands->find(it.key()) == named_commands->end())
716 suggested_key_prefs->Remove(it.key(), NULL);
720 extension_prefs->UpdateExtensionPref(extension->id(),
721 kCommands,
722 suggested_key_prefs.release());
726 bool CommandService::IsCommandShortcutUserModified(
727 const Extension* extension,
728 const std::string& command_name) {
729 // Get the previous suggested key, if any.
730 ui::Accelerator suggested_key;
731 bool suggested_key_was_assigned = false;
732 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
733 const base::DictionaryValue* commands_prefs = NULL;
734 const base::DictionaryValue* suggested_key_prefs = NULL;
735 if (extension_prefs->ReadPrefAsDictionary(extension->id(),
736 kCommands,
737 &commands_prefs) &&
738 commands_prefs->GetDictionary(command_name, &suggested_key_prefs)) {
739 std::string suggested_key_string;
740 if (suggested_key_prefs->GetString(kSuggestedKey, &suggested_key_string)) {
741 suggested_key = Command::StringToAccelerator(suggested_key_string,
742 command_name);
745 suggested_key_prefs->GetBoolean(kSuggestedKeyWasAssigned,
746 &suggested_key_was_assigned);
749 // Get the active shortcut from the prefs, if any.
750 Command active_command = FindCommandByName(extension->id(), command_name);
752 return suggested_key_was_assigned ?
753 active_command.accelerator() != suggested_key :
754 active_command.accelerator().key_code() != ui::VKEY_UNKNOWN;
757 bool CommandService::IsKeybindingChanging(const Extension* extension,
758 const std::string& command_name) {
759 // Get the new assigned command, if any.
760 Command new_command;
761 if (command_name == manifest_values::kBrowserActionCommandEvent) {
762 new_command = *CommandsInfo::GetBrowserActionCommand(extension);
763 } else if (command_name == manifest_values::kPageActionCommandEvent) {
764 new_command = *CommandsInfo::GetPageActionCommand(extension);
765 } else { // This is a named command.
766 const CommandMap* named_commands =
767 CommandsInfo::GetNamedCommands(extension);
768 if (named_commands) {
769 CommandMap::const_iterator loc = named_commands->find(command_name);
770 if (loc != named_commands->end())
771 new_command = loc->second;
775 return Command::StringToAccelerator(
776 GetSuggestedKeyPref(extension, command_name), command_name) !=
777 new_command.accelerator();
780 std::string CommandService::GetSuggestedKeyPref(
781 const Extension* extension,
782 const std::string& command_name) {
783 // Get the previous suggested key, if any.
784 ui::Accelerator suggested_key;
785 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
786 const base::DictionaryValue* commands_prefs = NULL;
787 if (extension_prefs->ReadPrefAsDictionary(extension->id(),
788 kCommands,
789 &commands_prefs)) {
790 const base::DictionaryValue* suggested_key_prefs = NULL;
791 std::string suggested_key;
792 if (commands_prefs->GetDictionary(command_name, &suggested_key_prefs) &&
793 suggested_key_prefs->GetString(kSuggestedKey, &suggested_key)) {
794 return suggested_key;
798 return std::string();
801 void CommandService::RemoveKeybindingPrefs(const std::string& extension_id,
802 const std::string& command_name) {
803 DictionaryPrefUpdate updater(profile_->GetPrefs(),
804 prefs::kExtensionCommands);
805 base::DictionaryValue* bindings = updater.Get();
807 typedef std::vector<std::string> KeysToRemove;
808 KeysToRemove keys_to_remove;
809 std::vector<Command> removed_commands;
810 for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
811 it.Advance()) {
812 // Removal of keybinding preference should be limited to current platform.
813 if (!IsForCurrentPlatform(it.key()))
814 continue;
816 const base::DictionaryValue* item = NULL;
817 it.value().GetAsDictionary(&item);
819 std::string extension;
820 item->GetString(kExtension, &extension);
822 if (extension == extension_id) {
823 // If |command_name| is specified, delete only that command. Otherwise,
824 // delete all commands.
825 std::string command;
826 item->GetString(kCommandName, &command);
827 if (!command_name.empty() && command_name != command)
828 continue;
830 removed_commands.push_back(FindCommandByName(extension_id, command));
831 keys_to_remove.push_back(it.key());
835 for (KeysToRemove::const_iterator it = keys_to_remove.begin();
836 it != keys_to_remove.end(); ++it) {
837 std::string key = *it;
838 bindings->Remove(key, NULL);
840 // TODO(devlin): Deprecate this notification in favor of the observers.
841 ExtensionCommandRemovedDetails details(extension_id, command_name,
842 StripCurrentPlatform(key));
843 content::NotificationService::current()->Notify(
844 extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED,
845 content::Source<Profile>(profile_),
846 content::Details<ExtensionCommandRemovedDetails>(&details));
849 for (const Command& removed_command : removed_commands) {
850 FOR_EACH_OBSERVER(
851 Observer,
852 observers_,
853 OnExtensionCommandRemoved(extension_id, removed_command));
857 bool CommandService::GetExtensionActionCommand(
858 const std::string& extension_id,
859 QueryType query_type,
860 Command* command,
861 bool* active,
862 ExtensionCommandType action_type) const {
863 const ExtensionSet& extensions =
864 ExtensionRegistry::Get(profile_)->enabled_extensions();
865 const Extension* extension = extensions.GetByID(extension_id);
866 CHECK(extension);
868 if (active)
869 *active = false;
871 const Command* requested_command = NULL;
872 switch (action_type) {
873 case BROWSER_ACTION:
874 requested_command = CommandsInfo::GetBrowserActionCommand(extension);
875 break;
876 case PAGE_ACTION:
877 requested_command = CommandsInfo::GetPageActionCommand(extension);
878 break;
879 case NAMED:
880 NOTREACHED();
881 return false;
883 if (!requested_command)
884 return false;
886 // Look up to see if the user has overridden how the command should work.
887 Command saved_command =
888 FindCommandByName(extension_id, requested_command->command_name());
889 ui::Accelerator shortcut_assigned = saved_command.accelerator();
891 if (active) {
892 if (query_type == SUGGESTED) {
893 *active =
894 (requested_command->accelerator().key_code() != ui::VKEY_UNKNOWN &&
895 requested_command->accelerator() == shortcut_assigned);
896 } else {
897 *active = (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN);
901 if (query_type == ACTIVE && shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
902 return false;
904 *command = *requested_command;
905 if (query_type != SUGGESTED &&
906 shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
907 command->set_accelerator(shortcut_assigned);
909 return true;
912 template <>
913 void
914 BrowserContextKeyedAPIFactory<CommandService>::DeclareFactoryDependencies() {
915 DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance());
918 } // namespace extensions