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::StartsWithASCII(key
, Command::CommandPlatform() + ":", true);
75 std::string
StripCurrentPlatform(const std::string
& key
) {
76 DCHECK(IsForCurrentPlatform(key
));
77 std::string result
= key
;
78 ReplaceFirstSubstringAfterOffset(&result
, 0, Command::CommandPlatform() + ":",
83 void SetInitialBindingsHaveBeenAssigned(
84 ExtensionPrefs
* prefs
, const std::string
& extension_id
) {
85 prefs
->UpdateExtensionPref(extension_id
, kInitialBindingsHaveBeenAssigned
,
86 new base::FundamentalValue(true));
89 bool InitialBindingsHaveBeenAssigned(
90 const ExtensionPrefs
* prefs
, const std::string
& extension_id
) {
91 bool assigned
= false;
92 if (!prefs
|| !prefs
->ReadPrefAsBoolean(extension_id
,
93 kInitialBindingsHaveBeenAssigned
,
100 // Merge |suggested_key_prefs| into the saved preferences for the extension. We
101 // merge rather than overwrite to preserve existing was_assigned preferences.
102 void MergeSuggestedKeyPrefs(
103 const std::string
& extension_id
,
104 ExtensionPrefs
* extension_prefs
,
105 scoped_ptr
<base::DictionaryValue
> suggested_key_prefs
) {
106 const base::DictionaryValue
* current_prefs
;
107 if (extension_prefs
->ReadPrefAsDictionary(extension_id
,
110 scoped_ptr
<base::DictionaryValue
> new_prefs(current_prefs
->DeepCopy());
111 new_prefs
->MergeDictionary(suggested_key_prefs
.get());
112 suggested_key_prefs
.reset(new_prefs
.release());
115 extension_prefs
->UpdateExtensionPref(extension_id
,
117 suggested_key_prefs
.release());
123 void CommandService::RegisterProfilePrefs(
124 user_prefs::PrefRegistrySyncable
* registry
) {
125 registry
->RegisterDictionaryPref(
126 prefs::kExtensionCommands
,
127 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF
);
130 CommandService::CommandService(content::BrowserContext
* context
)
131 : profile_(Profile::FromBrowserContext(context
)),
132 extension_registry_observer_(this) {
133 ExtensionFunctionRegistry::GetInstance()->
134 RegisterFunction
<GetAllCommandsFunction
>();
136 extension_registry_observer_
.Add(ExtensionRegistry::Get(profile_
));
139 CommandService::~CommandService() {
142 static base::LazyInstance
<BrowserContextKeyedAPIFactory
<CommandService
> >
143 g_factory
= LAZY_INSTANCE_INITIALIZER
;
146 BrowserContextKeyedAPIFactory
<CommandService
>*
147 CommandService::GetFactoryInstance() {
148 return g_factory
.Pointer();
152 CommandService
* CommandService::Get(content::BrowserContext
* context
) {
153 return BrowserContextKeyedAPIFactory
<CommandService
>::Get(context
);
157 bool CommandService::RemovesBookmarkShortcut(const Extension
* extension
) {
158 return UIOverrides::RemovesBookmarkShortcut(extension
) &&
159 (extension
->permissions_data()->HasAPIPermission(
160 APIPermission::kBookmarkManagerPrivate
) ||
161 FeatureSwitch::enable_override_bookmarks_ui()->IsEnabled());
165 bool CommandService::RemovesBookmarkOpenPagesShortcut(
166 const Extension
* extension
) {
167 return UIOverrides::RemovesBookmarkOpenPagesShortcut(extension
) &&
168 (extension
->permissions_data()->HasAPIPermission(
169 APIPermission::kBookmarkManagerPrivate
) ||
170 FeatureSwitch::enable_override_bookmarks_ui()->IsEnabled());
173 bool CommandService::GetBrowserActionCommand(const std::string
& extension_id
,
176 bool* active
) const {
177 return GetExtensionActionCommand(
178 extension_id
, type
, command
, active
, BROWSER_ACTION
);
181 bool CommandService::GetPageActionCommand(const std::string
& extension_id
,
184 bool* active
) const {
185 return GetExtensionActionCommand(
186 extension_id
, type
, command
, active
, PAGE_ACTION
);
189 bool CommandService::GetNamedCommands(const std::string
& extension_id
,
192 CommandMap
* command_map
) const {
193 const ExtensionSet
& extensions
=
194 ExtensionRegistry::Get(profile_
)->enabled_extensions();
195 const Extension
* extension
= extensions
.GetByID(extension_id
);
198 command_map
->clear();
199 const CommandMap
* commands
= CommandsInfo::GetNamedCommands(extension
);
203 for (CommandMap::const_iterator iter
= commands
->begin();
204 iter
!= commands
->end(); ++iter
) {
205 // Look up to see if the user has overridden how the command should work.
206 Command saved_command
=
207 FindCommandByName(extension_id
, iter
->second
.command_name());
208 ui::Accelerator shortcut_assigned
= saved_command
.accelerator();
210 if (type
== ACTIVE
&& shortcut_assigned
.key_code() == ui::VKEY_UNKNOWN
)
213 Command command
= iter
->second
;
214 if (scope
!= ANY_SCOPE
&& ((scope
== GLOBAL
) != saved_command
.global()))
217 if (type
!= SUGGESTED
&& shortcut_assigned
.key_code() != ui::VKEY_UNKNOWN
)
218 command
.set_accelerator(shortcut_assigned
);
219 command
.set_global(saved_command
.global());
221 (*command_map
)[iter
->second
.command_name()] = command
;
224 return !command_map
->empty();
227 bool CommandService::AddKeybindingPref(
228 const ui::Accelerator
& accelerator
,
229 const std::string
& extension_id
,
230 const std::string
& command_name
,
231 bool allow_overrides
,
233 if (accelerator
.key_code() == ui::VKEY_UNKNOWN
)
236 // Nothing needs to be done if the existing command is the same as the desired
238 Command existing_command
= FindCommandByName(extension_id
, command_name
);
239 if (existing_command
.accelerator() == accelerator
&&
240 existing_command
.global() == global
)
243 // Media Keys are allowed to be used by named command only.
244 DCHECK(!Command::IsMediaKey(accelerator
) ||
245 (command_name
!= manifest_values::kPageActionCommandEvent
&&
246 command_name
!= manifest_values::kBrowserActionCommandEvent
));
248 DictionaryPrefUpdate
updater(profile_
->GetPrefs(),
249 prefs::kExtensionCommands
);
250 base::DictionaryValue
* bindings
= updater
.Get();
252 std::string key
= GetPlatformKeybindingKeyForAccelerator(accelerator
,
255 if (bindings
->HasKey(key
)) {
256 if (!allow_overrides
)
257 return false; // Already taken.
259 // If the shortcut has been assigned to another command, it should be
260 // removed before overriding, so that |ExtensionKeybindingRegistry| can get
261 // a chance to do clean-up.
262 const base::DictionaryValue
* item
= NULL
;
263 bindings
->GetDictionary(key
, &item
);
264 std::string old_extension_id
;
265 std::string old_command_name
;
266 item
->GetString(kExtension
, &old_extension_id
);
267 item
->GetString(kCommandName
, &old_command_name
);
268 RemoveKeybindingPrefs(old_extension_id
, old_command_name
);
271 // If the command that is taking a new shortcut already has a shortcut, remove
272 // it before assigning the new one.
273 if (existing_command
.accelerator().key_code() != ui::VKEY_UNKNOWN
)
274 RemoveKeybindingPrefs(extension_id
, command_name
);
276 // Set the keybinding pref.
277 base::DictionaryValue
* keybinding
= new base::DictionaryValue();
278 keybinding
->SetString(kExtension
, extension_id
);
279 keybinding
->SetString(kCommandName
, command_name
);
280 keybinding
->SetBoolean(kGlobal
, global
);
282 bindings
->Set(key
, keybinding
);
284 // Set the was_assigned pref for the suggested key.
285 scoped_ptr
<base::DictionaryValue
> command_keys(new base::DictionaryValue
);
286 command_keys
->SetBoolean(kSuggestedKeyWasAssigned
, true);
287 scoped_ptr
<base::DictionaryValue
> suggested_key_prefs(
288 new base::DictionaryValue
);
289 suggested_key_prefs
->Set(command_name
, command_keys
.release());
290 MergeSuggestedKeyPrefs(extension_id
,
291 ExtensionPrefs::Get(profile_
),
292 suggested_key_prefs
.Pass());
294 // Fetch the newly-updated command, and notify the observers.
298 OnExtensionCommandAdded(extension_id
,
299 FindCommandByName(extension_id
, command_name
)));
301 // TODO(devlin): Deprecate this notification in favor of the observers.
302 std::pair
<const std::string
, const std::string
> details
=
303 std::make_pair(extension_id
, command_name
);
304 content::NotificationService::current()->Notify(
305 extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED
,
306 content::Source
<Profile
>(profile_
),
307 content::Details
<std::pair
<const std::string
, const std::string
> >(
313 void CommandService::OnExtensionWillBeInstalled(
314 content::BrowserContext
* browser_context
,
315 const Extension
* extension
,
318 const std::string
& old_name
) {
319 UpdateKeybindings(extension
);
322 void CommandService::OnExtensionUninstalled(
323 content::BrowserContext
* browser_context
,
324 const Extension
* extension
,
325 extensions::UninstallReason reason
) {
326 // Adding a component extensions will only trigger install the first time on a
327 // clean profile or on a version increase (see
328 // ComponentLoader::AddComponentExtension). It will, however, always trigger
329 // an uninstall on removal. See http://crbug.com/458612. Isolate this case and
331 if (reason
== extensions::UNINSTALL_REASON_COMPONENT_REMOVED
)
334 RemoveKeybindingPrefs(extension
->id(), std::string());
337 void CommandService::UpdateKeybindingPrefs(const std::string
& extension_id
,
338 const std::string
& command_name
,
339 const std::string
& keystroke
) {
340 Command command
= FindCommandByName(extension_id
, command_name
);
342 // The extension command might be assigned another shortcut. Remove that
343 // shortcut before proceeding.
344 RemoveKeybindingPrefs(extension_id
, command_name
);
346 ui::Accelerator accelerator
=
347 Command::StringToAccelerator(keystroke
, command_name
);
348 AddKeybindingPref(accelerator
, extension_id
, command_name
,
349 true, command
.global());
352 bool CommandService::SetScope(const std::string
& extension_id
,
353 const std::string
& command_name
,
355 Command command
= FindCommandByName(extension_id
, command_name
);
356 if (global
== command
.global())
359 // Pre-existing shortcuts must be removed before proceeding because the
360 // handlers for global and non-global extensions are not one and the same.
361 RemoveKeybindingPrefs(extension_id
, command_name
);
362 AddKeybindingPref(command
.accelerator(), extension_id
,
363 command_name
, true, global
);
367 Command
CommandService::FindCommandByName(const std::string
& extension_id
,
368 const std::string
& command
) const {
369 const base::DictionaryValue
* bindings
=
370 profile_
->GetPrefs()->GetDictionary(prefs::kExtensionCommands
);
371 for (base::DictionaryValue::Iterator
it(*bindings
); !it
.IsAtEnd();
373 const base::DictionaryValue
* item
= NULL
;
374 it
.value().GetAsDictionary(&item
);
376 std::string extension
;
377 item
->GetString(kExtension
, &extension
);
378 if (extension
!= extension_id
)
380 std::string command_name
;
381 item
->GetString(kCommandName
, &command_name
);
382 if (command
!= command_name
)
384 // Format stored in Preferences is: "Platform:Shortcut[:ExtensionId]".
385 std::string shortcut
= it
.key();
386 if (!IsForCurrentPlatform(shortcut
))
389 item
->GetBoolean(kGlobal
, &global
);
391 std::vector
<std::string
> tokens
;
392 base::SplitString(shortcut
, ':', &tokens
);
393 CHECK(tokens
.size() >= 2);
394 shortcut
= tokens
[1];
396 return Command(command_name
, base::string16(), shortcut
, global
);
402 bool CommandService::GetSuggestedExtensionCommand(
403 const std::string
& extension_id
,
404 const ui::Accelerator
& accelerator
,
406 ExtensionCommandType
* command_type
) const {
407 const Extension
* extension
=
408 ExtensionRegistry::Get(profile_
)
409 ->GetExtensionById(extension_id
, ExtensionRegistry::ENABLED
);
412 Command prospective_command
;
413 CommandMap command_map
;
414 if (GetBrowserActionCommand(extension_id
,
415 CommandService::SUGGESTED
,
416 &prospective_command
,
418 accelerator
== prospective_command
.accelerator()) {
420 *command
= prospective_command
;
422 *command_type
= BROWSER_ACTION
;
424 } else if (GetPageActionCommand(extension_id
,
425 CommandService::SUGGESTED
,
426 &prospective_command
,
428 accelerator
== prospective_command
.accelerator()) {
430 *command
= prospective_command
;
432 *command_type
= PAGE_ACTION
;
434 } else if (GetNamedCommands(extension_id
,
435 CommandService::SUGGESTED
,
436 CommandService::REGULAR
,
438 for (CommandMap::const_iterator it
= command_map
.begin();
439 it
!= command_map
.end();
441 if (accelerator
== it
->second
.accelerator()) {
443 *command
= it
->second
;
445 *command_type
= NAMED
;
453 bool CommandService::RequestsBookmarkShortcutOverride(
454 const Extension
* extension
) const {
455 return RemovesBookmarkShortcut(extension
) &&
456 GetSuggestedExtensionCommand(
458 chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE
),
463 void CommandService::AddObserver(Observer
* observer
) {
464 observers_
.AddObserver(observer
);
467 void CommandService::RemoveObserver(Observer
* observer
) {
468 observers_
.RemoveObserver(observer
);
471 void CommandService::UpdateKeybindings(const Extension
* extension
) {
472 const ExtensionSet
& extensions
=
473 ExtensionRegistry::Get(profile_
)->enabled_extensions();
474 // The extension is not added to the profile by this point on first install,
475 // so don't try to check for existing keybindings.
476 if (extensions
.GetByID(extension
->id()))
477 RemoveRelinquishedKeybindings(extension
);
478 AssignKeybindings(extension
);
479 UpdateExtensionSuggestedCommandPrefs(extension
);
480 RemoveDefunctExtensionSuggestedCommandPrefs(extension
);
483 void CommandService::RemoveRelinquishedKeybindings(const Extension
* extension
) {
484 // Remove keybindings if they have been removed by the extension and the user
485 // has not modified them.
486 CommandMap existing_command_map
;
487 if (GetNamedCommands(extension
->id(),
488 CommandService::ACTIVE
,
489 CommandService::REGULAR
,
490 &existing_command_map
)) {
491 const CommandMap
* new_command_map
=
492 CommandsInfo::GetNamedCommands(extension
);
493 for (CommandMap::const_iterator it
= existing_command_map
.begin();
494 it
!= existing_command_map
.end(); ++it
) {
495 std::string command_name
= it
->first
;
496 if (new_command_map
->find(command_name
) == new_command_map
->end() &&
497 !IsCommandShortcutUserModified(extension
, command_name
)) {
498 RemoveKeybindingPrefs(extension
->id(), command_name
);
503 Command existing_browser_action_command
;
504 const Command
* new_browser_action_command
=
505 CommandsInfo::GetBrowserActionCommand(extension
);
506 if (GetBrowserActionCommand(extension
->id(),
507 CommandService::ACTIVE
,
508 &existing_browser_action_command
,
510 // The browser action command may be defaulted to an unassigned
511 // accelerator if a browser action is specified by the extension but a
512 // keybinding is not declared. See
513 // CommandsHandler::MaybeSetBrowserActionDefault.
514 (!new_browser_action_command
||
515 new_browser_action_command
->accelerator().key_code() ==
517 !IsCommandShortcutUserModified(
519 existing_browser_action_command
.command_name())) {
520 RemoveKeybindingPrefs(extension
->id(),
521 existing_browser_action_command
.command_name());
524 Command existing_page_action_command
;
525 if (GetPageActionCommand(extension
->id(),
526 CommandService::ACTIVE
,
527 &existing_page_action_command
,
529 !CommandsInfo::GetPageActionCommand(extension
) &&
530 !IsCommandShortcutUserModified(
532 existing_page_action_command
.command_name())) {
533 RemoveKeybindingPrefs(extension
->id(),
534 existing_page_action_command
.command_name());
538 void CommandService::AssignKeybindings(const Extension
* extension
) {
539 const CommandMap
* commands
= CommandsInfo::GetNamedCommands(extension
);
543 ExtensionPrefs
* extension_prefs
= ExtensionPrefs::Get(profile_
);
544 // TODO(wittman): remove use of this pref after M37 hits stable.
545 if (!InitialBindingsHaveBeenAssigned(extension_prefs
, extension
->id()))
546 SetInitialBindingsHaveBeenAssigned(extension_prefs
, extension
->id());
548 for (CommandMap::const_iterator iter
= commands
->begin();
549 iter
!= commands
->end(); ++iter
) {
550 const Command command
= iter
->second
;
551 if (CanAutoAssign(command
, extension
)) {
552 AddKeybindingPref(command
.accelerator(),
554 command
.command_name(),
555 false, // Overwriting not allowed.
560 const Command
* browser_action_command
=
561 CommandsInfo::GetBrowserActionCommand(extension
);
562 if (browser_action_command
&&
563 CanAutoAssign(*browser_action_command
, extension
)) {
564 AddKeybindingPref(browser_action_command
->accelerator(),
566 browser_action_command
->command_name(),
567 false, // Overwriting not allowed.
568 false); // Not global.
571 const Command
* page_action_command
=
572 CommandsInfo::GetPageActionCommand(extension
);
573 if (page_action_command
&& CanAutoAssign(*page_action_command
, extension
)) {
574 AddKeybindingPref(page_action_command
->accelerator(),
576 page_action_command
->command_name(),
577 false, // Overwriting not allowed.
578 false); // Not global.
582 bool CommandService::CanAutoAssign(const Command
&command
,
583 const Extension
* extension
) {
584 // Extensions are allowed to auto-assign updated keys if the user has not
585 // changed from the previous value.
586 if (IsCommandShortcutUserModified(extension
, command
.command_name()))
589 // Media Keys are non-exclusive, so allow auto-assigning them.
590 if (Command::IsMediaKey(command
.accelerator()))
593 if (command
.global()) {
594 using namespace extensions
;
595 if (command
.command_name() == manifest_values::kBrowserActionCommandEvent
||
596 command
.command_name() == manifest_values::kPageActionCommandEvent
)
597 return false; // Browser and page actions are not global in nature.
599 if (extension
->permissions_data()->HasAPIPermission(
600 APIPermission::kCommandsAccessibility
))
603 // Global shortcuts are restricted to (Ctrl|Command)+Shift+[0-9].
604 #if defined OS_MACOSX
605 if (!command
.accelerator().IsCmdDown())
608 if (!command
.accelerator().IsCtrlDown())
611 if (!command
.accelerator().IsShiftDown())
613 return (command
.accelerator().key_code() >= ui::VKEY_0
&&
614 command
.accelerator().key_code() <= ui::VKEY_9
);
616 // Not a global command, check if Chrome shortcut and whether
617 // we can override it.
618 if (command
.accelerator() ==
619 chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE
) &&
620 CommandService::RemovesBookmarkShortcut(extension
)) {
621 // If this check fails it either means we have an API to override a
622 // key that isn't a ChromeAccelerator (and the API can therefore be
623 // deprecated) or the IsChromeAccelerator isn't consistently
624 // returning true for all accelerators.
625 DCHECK(chrome::IsChromeAccelerator(command
.accelerator(), profile_
));
629 return !chrome::IsChromeAccelerator(command
.accelerator(), profile_
);
633 void CommandService::UpdateExtensionSuggestedCommandPrefs(
634 const Extension
* extension
) {
635 scoped_ptr
<base::DictionaryValue
> suggested_key_prefs(
636 new base::DictionaryValue
);
638 const CommandMap
* commands
= CommandsInfo::GetNamedCommands(extension
);
640 for (CommandMap::const_iterator iter
= commands
->begin();
641 iter
!= commands
->end(); ++iter
) {
642 const Command command
= iter
->second
;
643 scoped_ptr
<base::DictionaryValue
> command_keys(new base::DictionaryValue
);
644 command_keys
->SetString(
646 Command::AcceleratorToString(command
.accelerator()));
647 suggested_key_prefs
->Set(command
.command_name(), command_keys
.release());
651 const Command
* browser_action_command
=
652 CommandsInfo::GetBrowserActionCommand(extension
);
653 // The browser action command may be defaulted to an unassigned accelerator if
654 // a browser action is specified by the extension but a keybinding is not
655 // declared. See CommandsHandler::MaybeSetBrowserActionDefault.
656 if (browser_action_command
&&
657 browser_action_command
->accelerator().key_code() != ui::VKEY_UNKNOWN
) {
658 scoped_ptr
<base::DictionaryValue
> command_keys(new base::DictionaryValue
);
659 command_keys
->SetString(
661 Command::AcceleratorToString(browser_action_command
->accelerator()));
662 suggested_key_prefs
->Set(browser_action_command
->command_name(),
663 command_keys
.release());
666 const Command
* page_action_command
=
667 CommandsInfo::GetPageActionCommand(extension
);
668 if (page_action_command
) {
669 scoped_ptr
<base::DictionaryValue
> command_keys(new base::DictionaryValue
);
670 command_keys
->SetString(
672 Command::AcceleratorToString(page_action_command
->accelerator()));
673 suggested_key_prefs
->Set(page_action_command
->command_name(),
674 command_keys
.release());
677 // Merge into current prefs, if present.
678 MergeSuggestedKeyPrefs(extension
->id(),
679 ExtensionPrefs::Get(profile_
),
680 suggested_key_prefs
.Pass());
683 void CommandService::RemoveDefunctExtensionSuggestedCommandPrefs(
684 const Extension
* extension
) {
685 ExtensionPrefs
* extension_prefs
= ExtensionPrefs::Get(profile_
);
686 const base::DictionaryValue
* current_prefs
= NULL
;
687 extension_prefs
->ReadPrefAsDictionary(extension
->id(),
692 scoped_ptr
<base::DictionaryValue
> suggested_key_prefs(
693 current_prefs
->DeepCopy());
694 const CommandMap
* named_commands
=
695 CommandsInfo::GetNamedCommands(extension
);
696 const Command
* browser_action_command
=
697 CommandsInfo::GetBrowserActionCommand(extension
);
698 for (base::DictionaryValue::Iterator
it(*current_prefs
);
699 !it
.IsAtEnd(); it
.Advance()) {
700 if (it
.key() == manifest_values::kBrowserActionCommandEvent
) {
701 // The browser action command may be defaulted to an unassigned
702 // accelerator if a browser action is specified by the extension but a
703 // keybinding is not declared. See
704 // CommandsHandler::MaybeSetBrowserActionDefault.
705 if (!browser_action_command
||
706 browser_action_command
->accelerator().key_code() ==
708 suggested_key_prefs
->Remove(it
.key(), NULL
);
710 } else if (it
.key() == manifest_values::kPageActionCommandEvent
) {
711 if (!CommandsInfo::GetPageActionCommand(extension
))
712 suggested_key_prefs
->Remove(it
.key(), NULL
);
713 } else if (named_commands
) {
714 if (named_commands
->find(it
.key()) == named_commands
->end())
715 suggested_key_prefs
->Remove(it
.key(), NULL
);
719 extension_prefs
->UpdateExtensionPref(extension
->id(),
721 suggested_key_prefs
.release());
725 bool CommandService::IsCommandShortcutUserModified(
726 const Extension
* extension
,
727 const std::string
& command_name
) {
728 // Get the previous suggested key, if any.
729 ui::Accelerator suggested_key
;
730 bool suggested_key_was_assigned
= false;
731 ExtensionPrefs
* extension_prefs
= ExtensionPrefs::Get(profile_
);
732 const base::DictionaryValue
* commands_prefs
= NULL
;
733 const base::DictionaryValue
* suggested_key_prefs
= NULL
;
734 if (extension_prefs
->ReadPrefAsDictionary(extension
->id(),
737 commands_prefs
->GetDictionary(command_name
, &suggested_key_prefs
)) {
738 std::string suggested_key_string
;
739 if (suggested_key_prefs
->GetString(kSuggestedKey
, &suggested_key_string
)) {
740 suggested_key
= Command::StringToAccelerator(suggested_key_string
,
744 suggested_key_prefs
->GetBoolean(kSuggestedKeyWasAssigned
,
745 &suggested_key_was_assigned
);
748 // Get the active shortcut from the prefs, if any.
749 Command active_command
= FindCommandByName(extension
->id(), command_name
);
751 return suggested_key_was_assigned
?
752 active_command
.accelerator() != suggested_key
:
753 active_command
.accelerator().key_code() != ui::VKEY_UNKNOWN
;
756 bool CommandService::IsKeybindingChanging(const Extension
* extension
,
757 const std::string
& command_name
) {
758 // Get the new assigned command, if any.
760 if (command_name
== manifest_values::kBrowserActionCommandEvent
) {
761 new_command
= *CommandsInfo::GetBrowserActionCommand(extension
);
762 } else if (command_name
== manifest_values::kPageActionCommandEvent
) {
763 new_command
= *CommandsInfo::GetPageActionCommand(extension
);
764 } else { // This is a named command.
765 const CommandMap
* named_commands
=
766 CommandsInfo::GetNamedCommands(extension
);
767 if (named_commands
) {
768 CommandMap::const_iterator loc
= named_commands
->find(command_name
);
769 if (loc
!= named_commands
->end())
770 new_command
= loc
->second
;
774 return Command::StringToAccelerator(
775 GetSuggestedKeyPref(extension
, command_name
), command_name
) !=
776 new_command
.accelerator();
779 std::string
CommandService::GetSuggestedKeyPref(
780 const Extension
* extension
,
781 const std::string
& command_name
) {
782 // Get the previous suggested key, if any.
783 ui::Accelerator suggested_key
;
784 ExtensionPrefs
* extension_prefs
= ExtensionPrefs::Get(profile_
);
785 const base::DictionaryValue
* commands_prefs
= NULL
;
786 if (extension_prefs
->ReadPrefAsDictionary(extension
->id(),
789 const base::DictionaryValue
* suggested_key_prefs
= NULL
;
790 std::string suggested_key
;
791 if (commands_prefs
->GetDictionary(command_name
, &suggested_key_prefs
) &&
792 suggested_key_prefs
->GetString(kSuggestedKey
, &suggested_key
)) {
793 return suggested_key
;
797 return std::string();
800 void CommandService::RemoveKeybindingPrefs(const std::string
& extension_id
,
801 const std::string
& command_name
) {
802 DictionaryPrefUpdate
updater(profile_
->GetPrefs(),
803 prefs::kExtensionCommands
);
804 base::DictionaryValue
* bindings
= updater
.Get();
806 typedef std::vector
<std::string
> KeysToRemove
;
807 KeysToRemove keys_to_remove
;
808 std::vector
<Command
> removed_commands
;
809 for (base::DictionaryValue::Iterator
it(*bindings
); !it
.IsAtEnd();
811 // Removal of keybinding preference should be limited to current platform.
812 if (!IsForCurrentPlatform(it
.key()))
815 const base::DictionaryValue
* item
= NULL
;
816 it
.value().GetAsDictionary(&item
);
818 std::string extension
;
819 item
->GetString(kExtension
, &extension
);
821 if (extension
== extension_id
) {
822 // If |command_name| is specified, delete only that command. Otherwise,
823 // delete all commands.
825 item
->GetString(kCommandName
, &command
);
826 if (!command_name
.empty() && command_name
!= command
)
829 removed_commands
.push_back(FindCommandByName(extension_id
, command
));
830 keys_to_remove
.push_back(it
.key());
834 for (KeysToRemove::const_iterator it
= keys_to_remove
.begin();
835 it
!= keys_to_remove
.end(); ++it
) {
836 std::string key
= *it
;
837 bindings
->Remove(key
, NULL
);
839 // TODO(devlin): Deprecate this notification in favor of the observers.
840 ExtensionCommandRemovedDetails
details(extension_id
, command_name
,
841 StripCurrentPlatform(key
));
842 content::NotificationService::current()->Notify(
843 extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED
,
844 content::Source
<Profile
>(profile_
),
845 content::Details
<ExtensionCommandRemovedDetails
>(&details
));
848 for (const Command
& removed_command
: removed_commands
) {
852 OnExtensionCommandRemoved(extension_id
, removed_command
));
856 bool CommandService::GetExtensionActionCommand(
857 const std::string
& extension_id
,
858 QueryType query_type
,
861 ExtensionCommandType action_type
) const {
862 const ExtensionSet
& extensions
=
863 ExtensionRegistry::Get(profile_
)->enabled_extensions();
864 const Extension
* extension
= extensions
.GetByID(extension_id
);
870 const Command
* requested_command
= NULL
;
871 switch (action_type
) {
873 requested_command
= CommandsInfo::GetBrowserActionCommand(extension
);
876 requested_command
= CommandsInfo::GetPageActionCommand(extension
);
882 if (!requested_command
)
885 // Look up to see if the user has overridden how the command should work.
886 Command saved_command
=
887 FindCommandByName(extension_id
, requested_command
->command_name());
888 ui::Accelerator shortcut_assigned
= saved_command
.accelerator();
891 if (query_type
== SUGGESTED
) {
893 (requested_command
->accelerator().key_code() != ui::VKEY_UNKNOWN
&&
894 requested_command
->accelerator() == shortcut_assigned
);
896 *active
= (shortcut_assigned
.key_code() != ui::VKEY_UNKNOWN
);
900 if (query_type
== ACTIVE
&& shortcut_assigned
.key_code() == ui::VKEY_UNKNOWN
)
903 *command
= *requested_command
;
904 if (query_type
!= SUGGESTED
&&
905 shortcut_assigned
.key_code() != ui::VKEY_UNKNOWN
)
906 command
->set_accelerator(shortcut_assigned
);
913 BrowserContextKeyedAPIFactory
<CommandService
>::DeclareFactoryDependencies() {
914 DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance());
917 } // namespace extensions