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/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
{
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
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
;
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());
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
,
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
,
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
,
118 suggested_key_prefs
.release());
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
;
147 BrowserContextKeyedAPIFactory
<CommandService
>*
148 CommandService::GetFactoryInstance() {
149 return g_factory
.Pointer();
153 CommandService
* CommandService::Get(content::BrowserContext
* context
) {
154 return BrowserContextKeyedAPIFactory
<CommandService
>::Get(context
);
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());
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
,
177 bool* active
) const {
178 return GetExtensionActionCommand(
179 extension_id
, type
, command
, active
, BROWSER_ACTION
);
182 bool CommandService::GetPageActionCommand(const std::string
& extension_id
,
185 bool* active
) const {
186 return GetExtensionActionCommand(
187 extension_id
, type
, command
, active
, PAGE_ACTION
);
190 bool CommandService::GetNamedCommands(const std::string
& extension_id
,
193 CommandMap
* command_map
) const {
194 const ExtensionSet
& extensions
=
195 ExtensionRegistry::Get(profile_
)->enabled_extensions();
196 const Extension
* extension
= extensions
.GetByID(extension_id
);
199 command_map
->clear();
200 const CommandMap
* commands
= CommandsInfo::GetNamedCommands(extension
);
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
)
214 Command command
= iter
->second
;
215 if (scope
!= ANY_SCOPE
&& ((scope
== GLOBAL
) != saved_command
.global()))
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
,
234 if (accelerator
.key_code() == ui::VKEY_UNKNOWN
)
237 // Nothing needs to be done if the existing command is the same as the desired
239 Command existing_command
= FindCommandByName(extension_id
, command_name
);
240 if (existing_command
.accelerator() == accelerator
&&
241 existing_command
.global() == global
)
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
,
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.
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
> >(
314 void CommandService::OnExtensionWillBeInstalled(
315 content::BrowserContext
* browser_context
,
316 const Extension
* extension
,
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
332 if (reason
== extensions::UNINSTALL_REASON_COMPONENT_REMOVED
)
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
,
356 Command command
= FindCommandByName(extension_id
, command_name
);
357 if (global
== command
.global())
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
);
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();
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
)
381 std::string command_name
;
382 item
->GetString(kCommandName
, &command_name
);
383 if (command
!= command_name
)
385 // Format stored in Preferences is: "Platform:Shortcut[:ExtensionId]".
386 std::string shortcut
= it
.key();
387 if (!IsForCurrentPlatform(shortcut
))
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(),
403 bool CommandService::GetSuggestedExtensionCommand(
404 const std::string
& extension_id
,
405 const ui::Accelerator
& accelerator
,
407 ExtensionCommandType
* command_type
) const {
408 const Extension
* extension
=
409 ExtensionRegistry::Get(profile_
)
410 ->GetExtensionById(extension_id
, ExtensionRegistry::ENABLED
);
413 Command prospective_command
;
414 CommandMap command_map
;
415 if (GetBrowserActionCommand(extension_id
,
416 CommandService::SUGGESTED
,
417 &prospective_command
,
419 accelerator
== prospective_command
.accelerator()) {
421 *command
= prospective_command
;
423 *command_type
= BROWSER_ACTION
;
425 } else if (GetPageActionCommand(extension_id
,
426 CommandService::SUGGESTED
,
427 &prospective_command
,
429 accelerator
== prospective_command
.accelerator()) {
431 *command
= prospective_command
;
433 *command_type
= PAGE_ACTION
;
435 } else if (GetNamedCommands(extension_id
,
436 CommandService::SUGGESTED
,
437 CommandService::REGULAR
,
439 for (CommandMap::const_iterator it
= command_map
.begin();
440 it
!= command_map
.end();
442 if (accelerator
== it
->second
.accelerator()) {
444 *command
= it
->second
;
446 *command_type
= NAMED
;
454 bool CommandService::RequestsBookmarkShortcutOverride(
455 const Extension
* extension
) const {
456 return RemovesBookmarkShortcut(extension
) &&
457 GetSuggestedExtensionCommand(
459 chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE
),
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
,
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() ==
518 !IsCommandShortcutUserModified(
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
,
530 !CommandsInfo::GetPageActionCommand(extension
) &&
531 !IsCommandShortcutUserModified(
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
);
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(),
555 command
.command_name(),
556 false, // Overwriting not allowed.
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(),
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(),
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()))
590 // Media Keys are non-exclusive, so allow auto-assigning them.
591 if (Command::IsMediaKey(command
.accelerator()))
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
))
604 // Global shortcuts are restricted to (Ctrl|Command)+Shift+[0-9].
605 #if defined OS_MACOSX
606 if (!command
.accelerator().IsCmdDown())
609 if (!command
.accelerator().IsCtrlDown())
612 if (!command
.accelerator().IsShiftDown())
614 return (command
.accelerator().key_code() >= ui::VKEY_0
&&
615 command
.accelerator().key_code() <= ui::VKEY_9
);
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_
));
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
);
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(
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(
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(
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(),
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() ==
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(),
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(),
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
,
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.
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(),
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();
812 // Removal of keybinding preference should be limited to current platform.
813 if (!IsForCurrentPlatform(it
.key()))
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.
826 item
->GetString(kCommandName
, &command
);
827 if (!command_name
.empty() && command_name
!= command
)
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
) {
853 OnExtensionCommandRemoved(extension_id
, removed_command
));
857 bool CommandService::GetExtensionActionCommand(
858 const std::string
& extension_id
,
859 QueryType query_type
,
862 ExtensionCommandType action_type
) const {
863 const ExtensionSet
& extensions
=
864 ExtensionRegistry::Get(profile_
)->enabled_extensions();
865 const Extension
* extension
= extensions
.GetByID(extension_id
);
871 const Command
* requested_command
= NULL
;
872 switch (action_type
) {
874 requested_command
= CommandsInfo::GetBrowserActionCommand(extension
);
877 requested_command
= CommandsInfo::GetPageActionCommand(extension
);
883 if (!requested_command
)
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();
892 if (query_type
== SUGGESTED
) {
894 (requested_command
->accelerator().key_code() != ui::VKEY_UNKNOWN
&&
895 requested_command
->accelerator() == shortcut_assigned
);
897 *active
= (shortcut_assigned
.key_code() != ui::VKEY_UNKNOWN
);
901 if (query_type
== ACTIVE
&& shortcut_assigned
.key_code() == ui::VKEY_UNKNOWN
)
904 *command
= *requested_command
;
905 if (query_type
!= SUGGESTED
&&
906 shortcut_assigned
.key_code() != ui::VKEY_UNKNOWN
)
907 command
->set_accelerator(shortcut_assigned
);
914 BrowserContextKeyedAPIFactory
<CommandService
>::DeclareFactoryDependencies() {
915 DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance());
918 } // namespace extensions