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 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 std::string extension_id
,
230 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 std::pair
<const std::string
, const std::string
> details
=
295 std::make_pair(extension_id
, command_name
);
296 content::NotificationService::current()->Notify(
297 extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED
,
298 content::Source
<Profile
>(profile_
),
299 content::Details
<std::pair
<const std::string
, const std::string
> >(
305 void CommandService::OnExtensionWillBeInstalled(
306 content::BrowserContext
* browser_context
,
307 const Extension
* extension
,
310 const std::string
& old_name
) {
311 UpdateKeybindings(extension
);
314 void CommandService::OnExtensionUninstalled(
315 content::BrowserContext
* browser_context
,
316 const Extension
* extension
,
317 extensions::UninstallReason reason
) {
318 // Adding a component extensions will only trigger install the first time on a
319 // clean profile or on a version increase (see
320 // ComponentLoader::AddComponentExtension). It will, however, always trigger
321 // an uninstall on removal. See http://crbug.com/458612. Isolate this case and
323 if (reason
== extensions::UNINSTALL_REASON_COMPONENT_REMOVED
)
326 RemoveKeybindingPrefs(extension
->id(), std::string());
329 void CommandService::UpdateKeybindingPrefs(const std::string
& extension_id
,
330 const std::string
& command_name
,
331 const std::string
& keystroke
) {
332 Command command
= FindCommandByName(extension_id
, command_name
);
334 // The extension command might be assigned another shortcut. Remove that
335 // shortcut before proceeding.
336 RemoveKeybindingPrefs(extension_id
, command_name
);
338 ui::Accelerator accelerator
=
339 Command::StringToAccelerator(keystroke
, command_name
);
340 AddKeybindingPref(accelerator
, extension_id
, command_name
,
341 true, command
.global());
344 bool CommandService::SetScope(const std::string
& extension_id
,
345 const std::string
& command_name
,
347 Command command
= FindCommandByName(extension_id
, command_name
);
348 if (global
== command
.global())
351 // Pre-existing shortcuts must be removed before proceeding because the
352 // handlers for global and non-global extensions are not one and the same.
353 RemoveKeybindingPrefs(extension_id
, command_name
);
354 AddKeybindingPref(command
.accelerator(), extension_id
,
355 command_name
, true, global
);
359 Command
CommandService::FindCommandByName(const std::string
& extension_id
,
360 const std::string
& command
) const {
361 const base::DictionaryValue
* bindings
=
362 profile_
->GetPrefs()->GetDictionary(prefs::kExtensionCommands
);
363 for (base::DictionaryValue::Iterator
it(*bindings
); !it
.IsAtEnd();
365 const base::DictionaryValue
* item
= NULL
;
366 it
.value().GetAsDictionary(&item
);
368 std::string extension
;
369 item
->GetString(kExtension
, &extension
);
370 if (extension
!= extension_id
)
372 std::string command_name
;
373 item
->GetString(kCommandName
, &command_name
);
374 if (command
!= command_name
)
376 // Format stored in Preferences is: "Platform:Shortcut[:ExtensionId]".
377 std::string shortcut
= it
.key();
378 if (!IsForCurrentPlatform(shortcut
))
381 item
->GetBoolean(kGlobal
, &global
);
383 std::vector
<std::string
> tokens
;
384 base::SplitString(shortcut
, ':', &tokens
);
385 CHECK(tokens
.size() >= 2);
386 shortcut
= tokens
[1];
388 return Command(command_name
, base::string16(), shortcut
, global
);
394 bool CommandService::GetSuggestedExtensionCommand(
395 const std::string
& extension_id
,
396 const ui::Accelerator
& accelerator
,
398 ExtensionCommandType
* command_type
) const {
399 const Extension
* extension
=
400 ExtensionRegistry::Get(profile_
)
401 ->GetExtensionById(extension_id
, ExtensionRegistry::ENABLED
);
404 Command prospective_command
;
405 CommandMap command_map
;
406 if (GetBrowserActionCommand(extension_id
,
407 CommandService::SUGGESTED
,
408 &prospective_command
,
410 accelerator
== prospective_command
.accelerator()) {
412 *command
= prospective_command
;
414 *command_type
= BROWSER_ACTION
;
416 } else if (GetPageActionCommand(extension_id
,
417 CommandService::SUGGESTED
,
418 &prospective_command
,
420 accelerator
== prospective_command
.accelerator()) {
422 *command
= prospective_command
;
424 *command_type
= PAGE_ACTION
;
426 } else if (GetNamedCommands(extension_id
,
427 CommandService::SUGGESTED
,
428 CommandService::REGULAR
,
430 for (CommandMap::const_iterator it
= command_map
.begin();
431 it
!= command_map
.end();
433 if (accelerator
== it
->second
.accelerator()) {
435 *command
= it
->second
;
437 *command_type
= NAMED
;
445 bool CommandService::RequestsBookmarkShortcutOverride(
446 const Extension
* extension
) const {
447 return RemovesBookmarkShortcut(extension
) &&
448 GetSuggestedExtensionCommand(
450 chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE
),
455 void CommandService::UpdateKeybindings(const Extension
* extension
) {
456 const ExtensionSet
& extensions
=
457 ExtensionRegistry::Get(profile_
)->enabled_extensions();
458 // The extension is not added to the profile by this point on first install,
459 // so don't try to check for existing keybindings.
460 if (extensions
.GetByID(extension
->id()))
461 RemoveRelinquishedKeybindings(extension
);
462 AssignKeybindings(extension
);
463 UpdateExtensionSuggestedCommandPrefs(extension
);
464 RemoveDefunctExtensionSuggestedCommandPrefs(extension
);
467 void CommandService::RemoveRelinquishedKeybindings(const Extension
* extension
) {
468 // Remove keybindings if they have been removed by the extension and the user
469 // has not modified them.
470 CommandMap existing_command_map
;
471 if (GetNamedCommands(extension
->id(),
472 CommandService::ACTIVE
,
473 CommandService::REGULAR
,
474 &existing_command_map
)) {
475 const CommandMap
* new_command_map
=
476 CommandsInfo::GetNamedCommands(extension
);
477 for (CommandMap::const_iterator it
= existing_command_map
.begin();
478 it
!= existing_command_map
.end(); ++it
) {
479 std::string command_name
= it
->first
;
480 if (new_command_map
->find(command_name
) == new_command_map
->end() &&
481 !IsCommandShortcutUserModified(extension
, command_name
)) {
482 RemoveKeybindingPrefs(extension
->id(), command_name
);
487 Command existing_browser_action_command
;
488 const Command
* new_browser_action_command
=
489 CommandsInfo::GetBrowserActionCommand(extension
);
490 if (GetBrowserActionCommand(extension
->id(),
491 CommandService::ACTIVE
,
492 &existing_browser_action_command
,
494 // The browser action command may be defaulted to an unassigned
495 // accelerator if a browser action is specified by the extension but a
496 // keybinding is not declared. See
497 // CommandsHandler::MaybeSetBrowserActionDefault.
498 (!new_browser_action_command
||
499 new_browser_action_command
->accelerator().key_code() ==
501 !IsCommandShortcutUserModified(
503 existing_browser_action_command
.command_name())) {
504 RemoveKeybindingPrefs(extension
->id(),
505 existing_browser_action_command
.command_name());
508 Command existing_page_action_command
;
509 if (GetPageActionCommand(extension
->id(),
510 CommandService::ACTIVE
,
511 &existing_page_action_command
,
513 !CommandsInfo::GetPageActionCommand(extension
) &&
514 !IsCommandShortcutUserModified(
516 existing_page_action_command
.command_name())) {
517 RemoveKeybindingPrefs(extension
->id(),
518 existing_page_action_command
.command_name());
522 void CommandService::AssignKeybindings(const Extension
* extension
) {
523 const CommandMap
* commands
= CommandsInfo::GetNamedCommands(extension
);
527 ExtensionPrefs
* extension_prefs
= ExtensionPrefs::Get(profile_
);
528 // TODO(wittman): remove use of this pref after M37 hits stable.
529 if (!InitialBindingsHaveBeenAssigned(extension_prefs
, extension
->id()))
530 SetInitialBindingsHaveBeenAssigned(extension_prefs
, extension
->id());
532 for (CommandMap::const_iterator iter
= commands
->begin();
533 iter
!= commands
->end(); ++iter
) {
534 const Command command
= iter
->second
;
535 if (CanAutoAssign(command
, extension
)) {
536 AddKeybindingPref(command
.accelerator(),
538 command
.command_name(),
539 false, // Overwriting not allowed.
544 const Command
* browser_action_command
=
545 CommandsInfo::GetBrowserActionCommand(extension
);
546 if (browser_action_command
&&
547 CanAutoAssign(*browser_action_command
, extension
)) {
548 AddKeybindingPref(browser_action_command
->accelerator(),
550 browser_action_command
->command_name(),
551 false, // Overwriting not allowed.
552 false); // Not global.
555 const Command
* page_action_command
=
556 CommandsInfo::GetPageActionCommand(extension
);
557 if (page_action_command
&& CanAutoAssign(*page_action_command
, extension
)) {
558 AddKeybindingPref(page_action_command
->accelerator(),
560 page_action_command
->command_name(),
561 false, // Overwriting not allowed.
562 false); // Not global.
566 bool CommandService::CanAutoAssign(const Command
&command
,
567 const Extension
* extension
) {
568 // Extensions are allowed to auto-assign updated keys if the user has not
569 // changed from the previous value.
570 if (IsCommandShortcutUserModified(extension
, command
.command_name()))
573 // Media Keys are non-exclusive, so allow auto-assigning them.
574 if (Command::IsMediaKey(command
.accelerator()))
577 if (command
.global()) {
578 using namespace extensions
;
579 if (command
.command_name() == manifest_values::kBrowserActionCommandEvent
||
580 command
.command_name() == manifest_values::kPageActionCommandEvent
)
581 return false; // Browser and page actions are not global in nature.
583 if (extension
->permissions_data()->HasAPIPermission(
584 APIPermission::kCommandsAccessibility
))
587 // Global shortcuts are restricted to (Ctrl|Command)+Shift+[0-9].
588 #if defined OS_MACOSX
589 if (!command
.accelerator().IsCmdDown())
592 if (!command
.accelerator().IsCtrlDown())
595 if (!command
.accelerator().IsShiftDown())
597 return (command
.accelerator().key_code() >= ui::VKEY_0
&&
598 command
.accelerator().key_code() <= ui::VKEY_9
);
600 // Not a global command, check if Chrome shortcut and whether
601 // we can override it.
602 if (command
.accelerator() ==
603 chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE
) &&
604 CommandService::RemovesBookmarkShortcut(extension
)) {
605 // If this check fails it either means we have an API to override a
606 // key that isn't a ChromeAccelerator (and the API can therefore be
607 // deprecated) or the IsChromeAccelerator isn't consistently
608 // returning true for all accelerators.
609 DCHECK(chrome::IsChromeAccelerator(command
.accelerator(), profile_
));
613 return !chrome::IsChromeAccelerator(command
.accelerator(), profile_
);
617 void CommandService::UpdateExtensionSuggestedCommandPrefs(
618 const Extension
* extension
) {
619 scoped_ptr
<base::DictionaryValue
> suggested_key_prefs(
620 new base::DictionaryValue
);
622 const CommandMap
* commands
= CommandsInfo::GetNamedCommands(extension
);
624 for (CommandMap::const_iterator iter
= commands
->begin();
625 iter
!= commands
->end(); ++iter
) {
626 const Command command
= iter
->second
;
627 scoped_ptr
<base::DictionaryValue
> command_keys(new base::DictionaryValue
);
628 command_keys
->SetString(
630 Command::AcceleratorToString(command
.accelerator()));
631 suggested_key_prefs
->Set(command
.command_name(), command_keys
.release());
635 const Command
* browser_action_command
=
636 CommandsInfo::GetBrowserActionCommand(extension
);
637 // The browser action command may be defaulted to an unassigned accelerator if
638 // a browser action is specified by the extension but a keybinding is not
639 // declared. See CommandsHandler::MaybeSetBrowserActionDefault.
640 if (browser_action_command
&&
641 browser_action_command
->accelerator().key_code() != ui::VKEY_UNKNOWN
) {
642 scoped_ptr
<base::DictionaryValue
> command_keys(new base::DictionaryValue
);
643 command_keys
->SetString(
645 Command::AcceleratorToString(browser_action_command
->accelerator()));
646 suggested_key_prefs
->Set(browser_action_command
->command_name(),
647 command_keys
.release());
650 const Command
* page_action_command
=
651 CommandsInfo::GetPageActionCommand(extension
);
652 if (page_action_command
) {
653 scoped_ptr
<base::DictionaryValue
> command_keys(new base::DictionaryValue
);
654 command_keys
->SetString(
656 Command::AcceleratorToString(page_action_command
->accelerator()));
657 suggested_key_prefs
->Set(page_action_command
->command_name(),
658 command_keys
.release());
661 // Merge into current prefs, if present.
662 MergeSuggestedKeyPrefs(extension
->id(),
663 ExtensionPrefs::Get(profile_
),
664 suggested_key_prefs
.Pass());
667 void CommandService::RemoveDefunctExtensionSuggestedCommandPrefs(
668 const Extension
* extension
) {
669 ExtensionPrefs
* extension_prefs
= ExtensionPrefs::Get(profile_
);
670 const base::DictionaryValue
* current_prefs
= NULL
;
671 extension_prefs
->ReadPrefAsDictionary(extension
->id(),
676 scoped_ptr
<base::DictionaryValue
> suggested_key_prefs(
677 current_prefs
->DeepCopy());
678 const CommandMap
* named_commands
=
679 CommandsInfo::GetNamedCommands(extension
);
680 const Command
* browser_action_command
=
681 CommandsInfo::GetBrowserActionCommand(extension
);
682 for (base::DictionaryValue::Iterator
it(*current_prefs
);
683 !it
.IsAtEnd(); it
.Advance()) {
684 if (it
.key() == manifest_values::kBrowserActionCommandEvent
) {
685 // The browser action command may be defaulted to an unassigned
686 // accelerator if a browser action is specified by the extension but a
687 // keybinding is not declared. See
688 // CommandsHandler::MaybeSetBrowserActionDefault.
689 if (!browser_action_command
||
690 browser_action_command
->accelerator().key_code() ==
692 suggested_key_prefs
->Remove(it
.key(), NULL
);
694 } else if (it
.key() == manifest_values::kPageActionCommandEvent
) {
695 if (!CommandsInfo::GetPageActionCommand(extension
))
696 suggested_key_prefs
->Remove(it
.key(), NULL
);
697 } else if (named_commands
) {
698 if (named_commands
->find(it
.key()) == named_commands
->end())
699 suggested_key_prefs
->Remove(it
.key(), NULL
);
703 extension_prefs
->UpdateExtensionPref(extension
->id(),
705 suggested_key_prefs
.release());
709 bool CommandService::IsCommandShortcutUserModified(
710 const Extension
* extension
,
711 const std::string
& command_name
) {
712 // Get the previous suggested key, if any.
713 ui::Accelerator suggested_key
;
714 bool suggested_key_was_assigned
= false;
715 ExtensionPrefs
* extension_prefs
= ExtensionPrefs::Get(profile_
);
716 const base::DictionaryValue
* commands_prefs
= NULL
;
717 const base::DictionaryValue
* suggested_key_prefs
= NULL
;
718 if (extension_prefs
->ReadPrefAsDictionary(extension
->id(),
721 commands_prefs
->GetDictionary(command_name
, &suggested_key_prefs
)) {
722 std::string suggested_key_string
;
723 if (suggested_key_prefs
->GetString(kSuggestedKey
, &suggested_key_string
)) {
724 suggested_key
= Command::StringToAccelerator(suggested_key_string
,
728 suggested_key_prefs
->GetBoolean(kSuggestedKeyWasAssigned
,
729 &suggested_key_was_assigned
);
732 // Get the active shortcut from the prefs, if any.
733 Command active_command
= FindCommandByName(extension
->id(), command_name
);
735 return suggested_key_was_assigned
?
736 active_command
.accelerator() != suggested_key
:
737 active_command
.accelerator().key_code() != ui::VKEY_UNKNOWN
;
740 bool CommandService::IsKeybindingChanging(const Extension
* extension
,
741 const std::string
& command_name
) {
742 // Get the new assigned command, if any.
744 if (command_name
== manifest_values::kBrowserActionCommandEvent
) {
745 new_command
= *CommandsInfo::GetBrowserActionCommand(extension
);
746 } else if (command_name
== manifest_values::kPageActionCommandEvent
) {
747 new_command
= *CommandsInfo::GetPageActionCommand(extension
);
748 } else { // This is a named command.
749 const CommandMap
* named_commands
=
750 CommandsInfo::GetNamedCommands(extension
);
751 if (named_commands
) {
752 CommandMap::const_iterator loc
= named_commands
->find(command_name
);
753 if (loc
!= named_commands
->end())
754 new_command
= loc
->second
;
758 return Command::StringToAccelerator(
759 GetSuggestedKeyPref(extension
, command_name
), command_name
) !=
760 new_command
.accelerator();
763 std::string
CommandService::GetSuggestedKeyPref(
764 const Extension
* extension
,
765 const std::string
& command_name
) {
766 // Get the previous suggested key, if any.
767 ui::Accelerator suggested_key
;
768 ExtensionPrefs
* extension_prefs
= ExtensionPrefs::Get(profile_
);
769 const base::DictionaryValue
* commands_prefs
= NULL
;
770 if (extension_prefs
->ReadPrefAsDictionary(extension
->id(),
773 const base::DictionaryValue
* suggested_key_prefs
= NULL
;
774 std::string suggested_key
;
775 if (commands_prefs
->GetDictionary(command_name
, &suggested_key_prefs
) &&
776 suggested_key_prefs
->GetString(kSuggestedKey
, &suggested_key
)) {
777 return suggested_key
;
781 return std::string();
784 void CommandService::RemoveKeybindingPrefs(const std::string
& extension_id
,
785 const std::string
& command_name
) {
786 DictionaryPrefUpdate
updater(profile_
->GetPrefs(),
787 prefs::kExtensionCommands
);
788 base::DictionaryValue
* bindings
= updater
.Get();
790 typedef std::vector
<std::string
> KeysToRemove
;
791 KeysToRemove keys_to_remove
;
792 for (base::DictionaryValue::Iterator
it(*bindings
); !it
.IsAtEnd();
794 // Removal of keybinding preference should be limited to current platform.
795 if (!IsForCurrentPlatform(it
.key()))
798 const base::DictionaryValue
* item
= NULL
;
799 it
.value().GetAsDictionary(&item
);
801 std::string extension
;
802 item
->GetString(kExtension
, &extension
);
804 if (extension
== extension_id
) {
805 // If |command_name| is specified, delete only that command. Otherwise,
806 // delete all commands.
807 if (!command_name
.empty()) {
809 item
->GetString(kCommandName
, &command
);
810 if (command_name
!= command
)
814 keys_to_remove
.push_back(it
.key());
818 for (KeysToRemove::const_iterator it
= keys_to_remove
.begin();
819 it
!= keys_to_remove
.end(); ++it
) {
820 std::string key
= *it
;
821 bindings
->Remove(key
, NULL
);
823 ExtensionCommandRemovedDetails
details(extension_id
, command_name
,
824 StripCurrentPlatform(key
));
825 content::NotificationService::current()->Notify(
826 extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED
,
827 content::Source
<Profile
>(profile_
),
828 content::Details
<ExtensionCommandRemovedDetails
>(&details
));
832 bool CommandService::GetExtensionActionCommand(
833 const std::string
& extension_id
,
834 QueryType query_type
,
837 ExtensionCommandType action_type
) const {
838 const ExtensionSet
& extensions
=
839 ExtensionRegistry::Get(profile_
)->enabled_extensions();
840 const Extension
* extension
= extensions
.GetByID(extension_id
);
846 const Command
* requested_command
= NULL
;
847 switch (action_type
) {
849 requested_command
= CommandsInfo::GetBrowserActionCommand(extension
);
852 requested_command
= CommandsInfo::GetPageActionCommand(extension
);
858 if (!requested_command
)
861 // Look up to see if the user has overridden how the command should work.
862 Command saved_command
=
863 FindCommandByName(extension_id
, requested_command
->command_name());
864 ui::Accelerator shortcut_assigned
= saved_command
.accelerator();
867 if (query_type
== SUGGESTED
) {
869 (requested_command
->accelerator().key_code() != ui::VKEY_UNKNOWN
&&
870 requested_command
->accelerator() == shortcut_assigned
);
872 *active
= (shortcut_assigned
.key_code() != ui::VKEY_UNKNOWN
);
876 if (query_type
== ACTIVE
&& shortcut_assigned
.key_code() == ui::VKEY_UNKNOWN
)
879 *command
= *requested_command
;
880 if (query_type
!= SUGGESTED
&&
881 shortcut_assigned
.key_code() != ui::VKEY_UNKNOWN
)
882 command
->set_accelerator(shortcut_assigned
);
889 BrowserContextKeyedAPIFactory
<CommandService
>::DeclareFactoryDependencies() {
890 DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance());
893 } // namespace extensions