Move StartsWith[ASCII] to base namespace.
[chromium-blink-merge.git] / chrome / browser / extensions / api / commands / command_service.cc
blob45be93b6823a8acbec5949112fb36b5b33c42adb
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/extensions/api/commands/command_service.h"
7 #include <vector>
9 #include "base/lazy_instance.h"
10 #include "base/prefs/scoped_user_pref_update.h"
11 #include "base/strings/string_split.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/app/chrome_command_ids.h"
15 #include "chrome/browser/extensions/api/commands/commands.h"
16 #include "chrome/browser/extensions/extension_commands_global_registry.h"
17 #include "chrome/browser/extensions/extension_keybinding_registry.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/ui/accelerator_utils.h"
20 #include "chrome/common/extensions/api/commands/commands_handler.h"
21 #include "chrome/common/extensions/manifest_handlers/ui_overrides_handler.h"
22 #include "chrome/common/pref_names.h"
23 #include "components/pref_registry/pref_registry_syncable.h"
24 #include "content/public/browser/notification_details.h"
25 #include "content/public/browser/notification_service.h"
26 #include "extensions/browser/extension_function_registry.h"
27 #include "extensions/browser/extension_prefs.h"
28 #include "extensions/browser/extension_registry.h"
29 #include "extensions/browser/extension_system.h"
30 #include "extensions/browser/notification_types.h"
31 #include "extensions/common/feature_switch.h"
32 #include "extensions/common/manifest_constants.h"
33 #include "extensions/common/permissions/permissions_data.h"
35 namespace extensions {
36 namespace {
38 const char kExtension[] = "extension";
39 const char kCommandName[] = "command_name";
40 const char kGlobal[] = "global";
42 // A preference that stores keybinding state associated with extension commands.
43 const char kCommands[] = "commands";
45 // Preference key name for saving the extension-suggested key.
46 const char kSuggestedKey[] = "suggested_key";
48 // Preference key name for saving whether the extension-suggested key was
49 // actually assigned.
50 const char kSuggestedKeyWasAssigned[] = "was_assigned";
52 // A preference that indicates that the initial keybindings for the given
53 // extension have been set.
54 const char kInitialBindingsHaveBeenAssigned[] = "initial_keybindings_set";
56 std::string GetPlatformKeybindingKeyForAccelerator(
57 const ui::Accelerator& accelerator, const std::string& extension_id) {
58 std::string key = Command::CommandPlatform() + ":" +
59 Command::AcceleratorToString(accelerator);
61 // Media keys have a 1-to-many relationship with targets, unlike regular
62 // shortcut (1-to-1 relationship). That means two or more extensions can
63 // register for the same media key so the extension ID needs to be added to
64 // the key to make sure the key is unique.
65 if (Command::IsMediaKey(accelerator))
66 key += ":" + extension_id;
68 return key;
71 bool IsForCurrentPlatform(const std::string& key) {
72 return base::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() + ":",
79 "");
80 return result;
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,
94 &assigned))
95 return false;
97 return assigned;
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,
108 kCommands,
109 &current_prefs)) {
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,
116 kCommands,
117 suggested_key_prefs.release());
120 } // namespace
122 // static
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;
145 // static
146 BrowserContextKeyedAPIFactory<CommandService>*
147 CommandService::GetFactoryInstance() {
148 return g_factory.Pointer();
151 // static
152 CommandService* CommandService::Get(content::BrowserContext* context) {
153 return BrowserContextKeyedAPIFactory<CommandService>::Get(context);
156 // static
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());
164 // static
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,
174 QueryType type,
175 Command* command,
176 bool* active) const {
177 return GetExtensionActionCommand(
178 extension_id, type, command, active, BROWSER_ACTION);
181 bool CommandService::GetPageActionCommand(const std::string& extension_id,
182 QueryType type,
183 Command* command,
184 bool* active) const {
185 return GetExtensionActionCommand(
186 extension_id, type, command, active, PAGE_ACTION);
189 bool CommandService::GetNamedCommands(const std::string& extension_id,
190 QueryType type,
191 CommandScope scope,
192 CommandMap* command_map) const {
193 const ExtensionSet& extensions =
194 ExtensionRegistry::Get(profile_)->enabled_extensions();
195 const Extension* extension = extensions.GetByID(extension_id);
196 CHECK(extension);
198 command_map->clear();
199 const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
200 if (!commands)
201 return false;
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)
211 continue;
213 Command command = iter->second;
214 if (scope != ANY_SCOPE && ((scope == GLOBAL) != saved_command.global()))
215 continue;
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,
232 bool global) {
233 if (accelerator.key_code() == ui::VKEY_UNKNOWN)
234 return false;
236 // Nothing needs to be done if the existing command is the same as the desired
237 // new one.
238 Command existing_command = FindCommandByName(extension_id, command_name);
239 if (existing_command.accelerator() == accelerator &&
240 existing_command.global() == global)
241 return true;
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,
253 extension_id);
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.
295 FOR_EACH_OBSERVER(
296 Observer,
297 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> >(
308 &details));
310 return true;
313 void CommandService::OnExtensionWillBeInstalled(
314 content::BrowserContext* browser_context,
315 const Extension* extension,
316 bool is_update,
317 bool from_ephemeral,
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
330 // ignore it.
331 if (reason == extensions::UNINSTALL_REASON_COMPONENT_REMOVED)
332 return;
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,
354 bool global) {
355 Command command = FindCommandByName(extension_id, command_name);
356 if (global == command.global())
357 return false;
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);
364 return true;
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();
372 it.Advance()) {
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)
379 continue;
380 std::string command_name;
381 item->GetString(kCommandName, &command_name);
382 if (command != command_name)
383 continue;
384 // Format stored in Preferences is: "Platform:Shortcut[:ExtensionId]".
385 std::string shortcut = it.key();
386 if (!IsForCurrentPlatform(shortcut))
387 continue;
388 bool global = false;
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);
399 return Command();
402 bool CommandService::GetSuggestedExtensionCommand(
403 const std::string& extension_id,
404 const ui::Accelerator& accelerator,
405 Command* command,
406 ExtensionCommandType* command_type) const {
407 const Extension* extension =
408 ExtensionRegistry::Get(profile_)
409 ->GetExtensionById(extension_id, ExtensionRegistry::ENABLED);
410 CHECK(extension);
412 Command prospective_command;
413 CommandMap command_map;
414 if (GetBrowserActionCommand(extension_id,
415 CommandService::SUGGESTED,
416 &prospective_command,
417 nullptr) &&
418 accelerator == prospective_command.accelerator()) {
419 if (command)
420 *command = prospective_command;
421 if (command_type)
422 *command_type = BROWSER_ACTION;
423 return true;
424 } else if (GetPageActionCommand(extension_id,
425 CommandService::SUGGESTED,
426 &prospective_command,
427 nullptr) &&
428 accelerator == prospective_command.accelerator()) {
429 if (command)
430 *command = prospective_command;
431 if (command_type)
432 *command_type = PAGE_ACTION;
433 return true;
434 } else if (GetNamedCommands(extension_id,
435 CommandService::SUGGESTED,
436 CommandService::REGULAR,
437 &command_map)) {
438 for (CommandMap::const_iterator it = command_map.begin();
439 it != command_map.end();
440 ++it) {
441 if (accelerator == it->second.accelerator()) {
442 if (command)
443 *command = it->second;
444 if (command_type)
445 *command_type = NAMED;
446 return true;
450 return false;
453 bool CommandService::RequestsBookmarkShortcutOverride(
454 const Extension* extension) const {
455 return RemovesBookmarkShortcut(extension) &&
456 GetSuggestedExtensionCommand(
457 extension->id(),
458 chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE),
459 NULL,
460 NULL);
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,
509 NULL) &&
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() ==
516 ui::VKEY_UNKNOWN) &&
517 !IsCommandShortcutUserModified(
518 extension,
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,
528 NULL) &&
529 !CommandsInfo::GetPageActionCommand(extension) &&
530 !IsCommandShortcutUserModified(
531 extension,
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);
540 if (!commands)
541 return;
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(),
553 extension->id(),
554 command.command_name(),
555 false, // Overwriting not allowed.
556 command.global());
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(),
565 extension->id(),
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(),
575 extension->id(),
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()))
587 return false;
589 // Media Keys are non-exclusive, so allow auto-assigning them.
590 if (Command::IsMediaKey(command.accelerator()))
591 return true;
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))
601 return true;
603 // Global shortcuts are restricted to (Ctrl|Command)+Shift+[0-9].
604 #if defined OS_MACOSX
605 if (!command.accelerator().IsCmdDown())
606 return false;
607 #else
608 if (!command.accelerator().IsCtrlDown())
609 return false;
610 #endif
611 if (!command.accelerator().IsShiftDown())
612 return false;
613 return (command.accelerator().key_code() >= ui::VKEY_0 &&
614 command.accelerator().key_code() <= ui::VKEY_9);
615 } else {
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_));
626 return true;
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);
639 if (commands) {
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(
645 kSuggestedKey,
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(
660 kSuggestedKey,
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(
671 kSuggestedKey,
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(),
688 kCommands,
689 &current_prefs);
691 if (current_prefs) {
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() ==
707 ui::VKEY_UNKNOWN) {
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(),
720 kCommands,
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(),
735 kCommands,
736 &commands_prefs) &&
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,
741 command_name);
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.
759 Command new_command;
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(),
787 kCommands,
788 &commands_prefs)) {
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();
810 it.Advance()) {
811 // Removal of keybinding preference should be limited to current platform.
812 if (!IsForCurrentPlatform(it.key()))
813 continue;
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.
824 std::string command;
825 item->GetString(kCommandName, &command);
826 if (!command_name.empty() && command_name != command)
827 continue;
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) {
849 FOR_EACH_OBSERVER(
850 Observer,
851 observers_,
852 OnExtensionCommandRemoved(extension_id, removed_command));
856 bool CommandService::GetExtensionActionCommand(
857 const std::string& extension_id,
858 QueryType query_type,
859 Command* command,
860 bool* active,
861 ExtensionCommandType action_type) const {
862 const ExtensionSet& extensions =
863 ExtensionRegistry::Get(profile_)->enabled_extensions();
864 const Extension* extension = extensions.GetByID(extension_id);
865 CHECK(extension);
867 if (active)
868 *active = false;
870 const Command* requested_command = NULL;
871 switch (action_type) {
872 case BROWSER_ACTION:
873 requested_command = CommandsInfo::GetBrowserActionCommand(extension);
874 break;
875 case PAGE_ACTION:
876 requested_command = CommandsInfo::GetPageActionCommand(extension);
877 break;
878 case NAMED:
879 NOTREACHED();
880 return false;
882 if (!requested_command)
883 return false;
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();
890 if (active) {
891 if (query_type == SUGGESTED) {
892 *active =
893 (requested_command->accelerator().key_code() != ui::VKEY_UNKNOWN &&
894 requested_command->accelerator() == shortcut_assigned);
895 } else {
896 *active = (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN);
900 if (query_type == ACTIVE && shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
901 return false;
903 *command = *requested_command;
904 if (query_type != SUGGESTED &&
905 shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
906 command->set_accelerator(shortcut_assigned);
908 return true;
911 template <>
912 void
913 BrowserContextKeyedAPIFactory<CommandService>::DeclareFactoryDependencies() {
914 DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance());
917 } // namespace extensions