Add ENABLE_MEDIA_ROUTER define to builds other than Android and iOS.
[chromium-blink-merge.git] / chrome / browser / extensions / api / commands / command_service.cc
blobb254503521779fd5ea920bf9684ea30ff807aa93
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 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 std::string extension_id,
230 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 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> >(
300 &details));
302 return true;
305 void CommandService::OnExtensionWillBeInstalled(
306 content::BrowserContext* browser_context,
307 const Extension* extension,
308 bool is_update,
309 bool from_ephemeral,
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
322 // ignore it.
323 if (reason == extensions::UNINSTALL_REASON_COMPONENT_REMOVED)
324 return;
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,
346 bool global) {
347 Command command = FindCommandByName(extension_id, command_name);
348 if (global == command.global())
349 return false;
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);
356 return true;
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();
364 it.Advance()) {
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)
371 continue;
372 std::string command_name;
373 item->GetString(kCommandName, &command_name);
374 if (command != command_name)
375 continue;
376 // Format stored in Preferences is: "Platform:Shortcut[:ExtensionId]".
377 std::string shortcut = it.key();
378 if (!IsForCurrentPlatform(shortcut))
379 continue;
380 bool global = false;
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);
391 return Command();
394 bool CommandService::GetSuggestedExtensionCommand(
395 const std::string& extension_id,
396 const ui::Accelerator& accelerator,
397 Command* command,
398 ExtensionCommandType* command_type) const {
399 const Extension* extension =
400 ExtensionRegistry::Get(profile_)
401 ->GetExtensionById(extension_id, ExtensionRegistry::ENABLED);
402 CHECK(extension);
404 Command prospective_command;
405 CommandMap command_map;
406 if (GetBrowserActionCommand(extension_id,
407 CommandService::SUGGESTED,
408 &prospective_command,
409 nullptr) &&
410 accelerator == prospective_command.accelerator()) {
411 if (command)
412 *command = prospective_command;
413 if (command_type)
414 *command_type = BROWSER_ACTION;
415 return true;
416 } else if (GetPageActionCommand(extension_id,
417 CommandService::SUGGESTED,
418 &prospective_command,
419 nullptr) &&
420 accelerator == prospective_command.accelerator()) {
421 if (command)
422 *command = prospective_command;
423 if (command_type)
424 *command_type = PAGE_ACTION;
425 return true;
426 } else if (GetNamedCommands(extension_id,
427 CommandService::SUGGESTED,
428 CommandService::REGULAR,
429 &command_map)) {
430 for (CommandMap::const_iterator it = command_map.begin();
431 it != command_map.end();
432 ++it) {
433 if (accelerator == it->second.accelerator()) {
434 if (command)
435 *command = it->second;
436 if (command_type)
437 *command_type = NAMED;
438 return true;
442 return false;
445 bool CommandService::RequestsBookmarkShortcutOverride(
446 const Extension* extension) const {
447 return RemovesBookmarkShortcut(extension) &&
448 GetSuggestedExtensionCommand(
449 extension->id(),
450 chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE),
451 NULL,
452 NULL);
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,
493 NULL) &&
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() ==
500 ui::VKEY_UNKNOWN) &&
501 !IsCommandShortcutUserModified(
502 extension,
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,
512 NULL) &&
513 !CommandsInfo::GetPageActionCommand(extension) &&
514 !IsCommandShortcutUserModified(
515 extension,
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);
524 if (!commands)
525 return;
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(),
537 extension->id(),
538 command.command_name(),
539 false, // Overwriting not allowed.
540 command.global());
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(),
549 extension->id(),
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(),
559 extension->id(),
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()))
571 return false;
573 // Media Keys are non-exclusive, so allow auto-assigning them.
574 if (Command::IsMediaKey(command.accelerator()))
575 return true;
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))
585 return true;
587 // Global shortcuts are restricted to (Ctrl|Command)+Shift+[0-9].
588 #if defined OS_MACOSX
589 if (!command.accelerator().IsCmdDown())
590 return false;
591 #else
592 if (!command.accelerator().IsCtrlDown())
593 return false;
594 #endif
595 if (!command.accelerator().IsShiftDown())
596 return false;
597 return (command.accelerator().key_code() >= ui::VKEY_0 &&
598 command.accelerator().key_code() <= ui::VKEY_9);
599 } else {
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_));
610 return true;
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);
623 if (commands) {
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(
629 kSuggestedKey,
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(
644 kSuggestedKey,
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(
655 kSuggestedKey,
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(),
672 kCommands,
673 &current_prefs);
675 if (current_prefs) {
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() ==
691 ui::VKEY_UNKNOWN) {
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(),
704 kCommands,
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(),
719 kCommands,
720 &commands_prefs) &&
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,
725 command_name);
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.
743 Command new_command;
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(),
771 kCommands,
772 &commands_prefs)) {
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();
793 it.Advance()) {
794 // Removal of keybinding preference should be limited to current platform.
795 if (!IsForCurrentPlatform(it.key()))
796 continue;
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()) {
808 std::string command;
809 item->GetString(kCommandName, &command);
810 if (command_name != command)
811 continue;
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,
835 Command* command,
836 bool* active,
837 ExtensionCommandType action_type) const {
838 const ExtensionSet& extensions =
839 ExtensionRegistry::Get(profile_)->enabled_extensions();
840 const Extension* extension = extensions.GetByID(extension_id);
841 CHECK(extension);
843 if (active)
844 *active = false;
846 const Command* requested_command = NULL;
847 switch (action_type) {
848 case BROWSER_ACTION:
849 requested_command = CommandsInfo::GetBrowserActionCommand(extension);
850 break;
851 case PAGE_ACTION:
852 requested_command = CommandsInfo::GetPageActionCommand(extension);
853 break;
854 case NAMED:
855 NOTREACHED();
856 return false;
858 if (!requested_command)
859 return false;
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();
866 if (active) {
867 if (query_type == SUGGESTED) {
868 *active =
869 (requested_command->accelerator().key_code() != ui::VKEY_UNKNOWN &&
870 requested_command->accelerator() == shortcut_assigned);
871 } else {
872 *active = (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN);
876 if (query_type == ACTIVE && shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
877 return false;
879 *command = *requested_command;
880 if (query_type != SUGGESTED &&
881 shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
882 command->set_accelerator(shortcut_assigned);
884 return true;
887 template <>
888 void
889 BrowserContextKeyedAPIFactory<CommandService>::DeclareFactoryDependencies() {
890 DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance());
893 } // namespace extensions