Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / api / commands / command_service.cc
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.
4
5 #include "chrome/browser/extensions/api/commands/command_service.h"
6
7 #include <vector>
8
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"
34
35 namespace extensions {
36 namespace {
37
38 const char kExtension[] = "extension";
39 const char kCommandName[] = "command_name";
40 const char kGlobal[] = "global";
41
42 // A preference that stores keybinding state associated with extension commands.
43 const char kCommands[] = "commands";
44
45 // Preference key name for saving the extension-suggested key.
46 const char kSuggestedKey[] = "suggested_key";
47
48 // Preference key name for saving whether the extension-suggested key was
49 // actually assigned.
50 const char kSuggestedKeyWasAssigned[] = "was_assigned";
51
52 // A preference that indicates that the initial keybindings for the given
53 // extension have been set.
54 const char kInitialBindingsHaveBeenAssigned[] = "initial_keybindings_set";
55
56 std::string GetPlatformKeybindingKeyForAccelerator(
57     const ui::Accelerator& accelerator, const std::string extension_id) {
58   std::string key = Command::CommandPlatform() + ":" +
59                     Command::AcceleratorToString(accelerator);
60
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;
67
68   return key;
69 }
70
71 bool IsForCurrentPlatform(const std::string& key) {
72   return StartsWithASCII(key, Command::CommandPlatform() + ":", true);
73 }
74
75 void SetInitialBindingsHaveBeenAssigned(
76     ExtensionPrefs* prefs, const std::string& extension_id) {
77   prefs->UpdateExtensionPref(extension_id, kInitialBindingsHaveBeenAssigned,
78                              new base::FundamentalValue(true));
79 }
80
81 bool InitialBindingsHaveBeenAssigned(
82     const ExtensionPrefs* prefs, const std::string& extension_id) {
83   bool assigned = false;
84   if (!prefs || !prefs->ReadPrefAsBoolean(extension_id,
85                                           kInitialBindingsHaveBeenAssigned,
86                                           &assigned))
87     return false;
88
89   return assigned;
90 }
91
92 // Merge |suggested_key_prefs| into the saved preferences for the extension. We
93 // merge rather than overwrite to preserve existing was_assigned preferences.
94 void MergeSuggestedKeyPrefs(
95     const std::string& extension_id,
96     ExtensionPrefs* extension_prefs,
97     scoped_ptr<base::DictionaryValue> suggested_key_prefs) {
98   const base::DictionaryValue* current_prefs;
99   if (extension_prefs->ReadPrefAsDictionary(extension_id,
100                                             kCommands,
101                                             &current_prefs)) {
102     scoped_ptr<base::DictionaryValue> new_prefs(current_prefs->DeepCopy());
103     new_prefs->MergeDictionary(suggested_key_prefs.get());
104     suggested_key_prefs.reset(new_prefs.release());
105   }
106
107   extension_prefs->UpdateExtensionPref(extension_id,
108                                        kCommands,
109                                        suggested_key_prefs.release());
110 }
111
112 }  // namespace
113
114 // static
115 void CommandService::RegisterProfilePrefs(
116     user_prefs::PrefRegistrySyncable* registry) {
117   registry->RegisterDictionaryPref(
118       prefs::kExtensionCommands,
119       user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
120 }
121
122 CommandService::CommandService(content::BrowserContext* context)
123     : profile_(Profile::FromBrowserContext(context)),
124       extension_registry_observer_(this) {
125   ExtensionFunctionRegistry::GetInstance()->
126       RegisterFunction<GetAllCommandsFunction>();
127
128   extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
129 }
130
131 CommandService::~CommandService() {
132 }
133
134 static base::LazyInstance<BrowserContextKeyedAPIFactory<CommandService> >
135     g_factory = LAZY_INSTANCE_INITIALIZER;
136
137 // static
138 BrowserContextKeyedAPIFactory<CommandService>*
139 CommandService::GetFactoryInstance() {
140   return g_factory.Pointer();
141 }
142
143 // static
144 CommandService* CommandService::Get(content::BrowserContext* context) {
145   return BrowserContextKeyedAPIFactory<CommandService>::Get(context);
146 }
147
148 // static
149 bool CommandService::RemovesBookmarkShortcut(const Extension* extension) {
150   return UIOverrides::RemovesBookmarkShortcut(extension) &&
151       (extension->permissions_data()->HasAPIPermission(
152           APIPermission::kBookmarkManagerPrivate) ||
153        FeatureSwitch::enable_override_bookmarks_ui()->IsEnabled());
154 }
155
156 // static
157 bool CommandService::RemovesBookmarkOpenPagesShortcut(
158     const Extension* extension) {
159   return UIOverrides::RemovesBookmarkOpenPagesShortcut(extension) &&
160       (extension->permissions_data()->HasAPIPermission(
161           APIPermission::kBookmarkManagerPrivate) ||
162        FeatureSwitch::enable_override_bookmarks_ui()->IsEnabled());
163 }
164
165 bool CommandService::GetBrowserActionCommand(const std::string& extension_id,
166                                              QueryType type,
167                                              Command* command,
168                                              bool* active) const {
169   return GetExtensionActionCommand(
170       extension_id, type, command, active, BROWSER_ACTION);
171 }
172
173 bool CommandService::GetPageActionCommand(const std::string& extension_id,
174                                           QueryType type,
175                                           Command* command,
176                                           bool* active) const {
177   return GetExtensionActionCommand(
178       extension_id, type, command, active, PAGE_ACTION);
179 }
180
181 bool CommandService::GetNamedCommands(const std::string& extension_id,
182                                       QueryType type,
183                                       CommandScope scope,
184                                       CommandMap* command_map) const {
185   const ExtensionSet& extensions =
186       ExtensionRegistry::Get(profile_)->enabled_extensions();
187   const Extension* extension = extensions.GetByID(extension_id);
188   CHECK(extension);
189
190   command_map->clear();
191   const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
192   if (!commands)
193     return false;
194
195   for (CommandMap::const_iterator iter = commands->begin();
196        iter != commands->end(); ++iter) {
197     // Look up to see if the user has overridden how the command should work.
198     Command saved_command =
199         FindCommandByName(extension_id, iter->second.command_name());
200     ui::Accelerator shortcut_assigned = saved_command.accelerator();
201
202     if (type == ACTIVE_ONLY && shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
203       continue;
204
205     Command command = iter->second;
206     if (scope != ANY_SCOPE && ((scope == GLOBAL) != saved_command.global()))
207       continue;
208
209     if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
210       command.set_accelerator(shortcut_assigned);
211     command.set_global(saved_command.global());
212
213     (*command_map)[iter->second.command_name()] = command;
214   }
215
216   return true;
217 }
218
219 bool CommandService::AddKeybindingPref(
220     const ui::Accelerator& accelerator,
221     std::string extension_id,
222     std::string command_name,
223     bool allow_overrides,
224     bool global) {
225   if (accelerator.key_code() == ui::VKEY_UNKNOWN)
226     return false;
227
228   // Nothing needs to be done if the existing command is the same as the desired
229   // new one.
230   Command existing_command = FindCommandByName(extension_id, command_name);
231   if (existing_command.accelerator() == accelerator &&
232       existing_command.global() == global)
233     return true;
234
235   // Media Keys are allowed to be used by named command only.
236   DCHECK(!Command::IsMediaKey(accelerator) ||
237          (command_name != manifest_values::kPageActionCommandEvent &&
238           command_name != manifest_values::kBrowserActionCommandEvent));
239
240   DictionaryPrefUpdate updater(profile_->GetPrefs(),
241                                prefs::kExtensionCommands);
242   base::DictionaryValue* bindings = updater.Get();
243
244   std::string key = GetPlatformKeybindingKeyForAccelerator(accelerator,
245                                                            extension_id);
246
247   if (bindings->HasKey(key)) {
248     if (!allow_overrides)
249       return false;  // Already taken.
250
251     // If the shortcut has been assigned to another command, it should be
252     // removed before overriding, so that |ExtensionKeybindingRegistry| can get
253     // a chance to do clean-up.
254     const base::DictionaryValue* item = NULL;
255     bindings->GetDictionary(key, &item);
256     std::string old_extension_id;
257     std::string old_command_name;
258     item->GetString(kExtension, &old_extension_id);
259     item->GetString(kCommandName, &old_command_name);
260     RemoveKeybindingPrefs(old_extension_id, old_command_name);
261   }
262
263   // If the command that is taking a new shortcut already has a shortcut, remove
264   // it before assigning the new one.
265   if (existing_command.accelerator().key_code() != ui::VKEY_UNKNOWN)
266     RemoveKeybindingPrefs(extension_id, command_name);
267
268   // Set the keybinding pref.
269   base::DictionaryValue* keybinding = new base::DictionaryValue();
270   keybinding->SetString(kExtension, extension_id);
271   keybinding->SetString(kCommandName, command_name);
272   keybinding->SetBoolean(kGlobal, global);
273
274   bindings->Set(key, keybinding);
275
276   // Set the was_assigned pref for the suggested key.
277   scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
278   command_keys->SetBoolean(kSuggestedKeyWasAssigned, true);
279   scoped_ptr<base::DictionaryValue> suggested_key_prefs(
280       new base::DictionaryValue);
281   suggested_key_prefs->Set(command_name, command_keys.release());
282   MergeSuggestedKeyPrefs(extension_id,
283                          ExtensionPrefs::Get(profile_),
284                          suggested_key_prefs.Pass());
285
286   std::pair<const std::string, const std::string> details =
287       std::make_pair(extension_id, command_name);
288   content::NotificationService::current()->Notify(
289       extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED,
290       content::Source<Profile>(profile_),
291       content::Details<std::pair<const std::string, const std::string> >(
292           &details));
293
294   return true;
295 }
296
297 void CommandService::OnExtensionWillBeInstalled(
298     content::BrowserContext* browser_context,
299     const Extension* extension,
300     bool is_update,
301     bool from_ephemeral,
302     const std::string& old_name) {
303   UpdateKeybindings(extension);
304 }
305
306 void CommandService::OnExtensionUninstalled(
307     content::BrowserContext* browser_context,
308     const Extension* extension,
309     extensions::UninstallReason reason) {
310   RemoveKeybindingPrefs(extension->id(), std::string());
311 }
312
313 void CommandService::UpdateKeybindingPrefs(const std::string& extension_id,
314                                            const std::string& command_name,
315                                            const std::string& keystroke) {
316   Command command = FindCommandByName(extension_id, command_name);
317
318   // The extension command might be assigned another shortcut. Remove that
319   // shortcut before proceeding.
320   RemoveKeybindingPrefs(extension_id, command_name);
321
322   ui::Accelerator accelerator =
323       Command::StringToAccelerator(keystroke, command_name);
324   AddKeybindingPref(accelerator, extension_id, command_name,
325                     true, command.global());
326 }
327
328 bool CommandService::SetScope(const std::string& extension_id,
329                               const std::string& command_name,
330                               bool global) {
331   Command command = FindCommandByName(extension_id, command_name);
332   if (global == command.global())
333     return false;
334
335   // Pre-existing shortcuts must be removed before proceeding because the
336   // handlers for global and non-global extensions are not one and the same.
337   RemoveKeybindingPrefs(extension_id, command_name);
338   AddKeybindingPref(command.accelerator(), extension_id,
339                     command_name, true, global);
340   return true;
341 }
342
343 Command CommandService::FindCommandByName(const std::string& extension_id,
344                                           const std::string& command) const {
345   const base::DictionaryValue* bindings =
346       profile_->GetPrefs()->GetDictionary(prefs::kExtensionCommands);
347   for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
348        it.Advance()) {
349     const base::DictionaryValue* item = NULL;
350     it.value().GetAsDictionary(&item);
351
352     std::string extension;
353     item->GetString(kExtension, &extension);
354     if (extension != extension_id)
355       continue;
356     std::string command_name;
357     item->GetString(kCommandName, &command_name);
358     if (command != command_name)
359       continue;
360     // Format stored in Preferences is: "Platform:Shortcut[:ExtensionId]".
361     std::string shortcut = it.key();
362     if (!IsForCurrentPlatform(shortcut))
363       continue;
364     bool global = false;
365     item->GetBoolean(kGlobal, &global);
366
367     std::vector<std::string> tokens;
368     base::SplitString(shortcut, ':', &tokens);
369     CHECK(tokens.size() >= 2);
370     shortcut = tokens[1];
371
372     return Command(command_name, base::string16(), shortcut, global);
373   }
374
375   return Command();
376 }
377
378 bool CommandService::GetBoundExtensionCommand(
379     const std::string& extension_id,
380     const ui::Accelerator& accelerator,
381     Command* command,
382     ExtensionCommandType* command_type) const {
383   const Extension* extension =
384       ExtensionRegistry::Get(profile_)
385           ->GetExtensionById(extension_id, ExtensionRegistry::ENABLED);
386   CHECK(extension);
387
388   Command prospective_command;
389   CommandMap command_map;
390   bool active = false;
391   if (GetBrowserActionCommand(extension_id,
392                               CommandService::ACTIVE_ONLY,
393                               &prospective_command,
394                               &active) &&
395       active && accelerator == prospective_command.accelerator()) {
396     if (command)
397       *command = prospective_command;
398     if (command_type)
399       *command_type = BROWSER_ACTION;
400     return true;
401   } else if (GetPageActionCommand(extension_id,
402                                   CommandService::ACTIVE_ONLY,
403                                   &prospective_command,
404                                   &active) &&
405              active && accelerator == prospective_command.accelerator()) {
406     if (command)
407       *command = prospective_command;
408     if (command_type)
409       *command_type = PAGE_ACTION;
410     return true;
411   } else if (GetNamedCommands(extension_id,
412                               CommandService::ACTIVE_ONLY,
413                               CommandService::REGULAR,
414                               &command_map)) {
415     for (CommandMap::const_iterator it = command_map.begin();
416          it != command_map.end();
417          ++it) {
418       if (accelerator == it->second.accelerator()) {
419         if (command)
420           *command = it->second;
421         if (command_type)
422           *command_type = NAMED;
423         return true;
424       }
425     }
426   }
427   return false;
428 }
429
430 bool CommandService::OverridesBookmarkShortcut(
431     const Extension* extension) const {
432   return RemovesBookmarkShortcut(extension) &&
433       GetBoundExtensionCommand(
434           extension->id(),
435           chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE),
436           NULL,
437           NULL);
438 }
439
440 void CommandService::UpdateKeybindings(const Extension* extension) {
441   const ExtensionSet& extensions =
442       ExtensionRegistry::Get(profile_)->enabled_extensions();
443   // The extension is not added to the profile by this point on first install,
444   // so don't try to check for existing keybindings.
445   if (extensions.GetByID(extension->id()))
446     RemoveRelinquishedKeybindings(extension);
447   AssignKeybindings(extension);
448   UpdateExtensionSuggestedCommandPrefs(extension);
449   RemoveDefunctExtensionSuggestedCommandPrefs(extension);
450 }
451
452 void CommandService::RemoveRelinquishedKeybindings(const Extension* extension) {
453   // Remove keybindings if they have been removed by the extension and the user
454   // has not modified them.
455   CommandMap existing_command_map;
456   if (GetNamedCommands(extension->id(),
457                        CommandService::ACTIVE_ONLY,
458                        CommandService::REGULAR,
459                        &existing_command_map)) {
460     const CommandMap* new_command_map =
461         CommandsInfo::GetNamedCommands(extension);
462     for (CommandMap::const_iterator it = existing_command_map.begin();
463          it != existing_command_map.end(); ++it) {
464       std::string command_name = it->first;
465       if (new_command_map->find(command_name) == new_command_map->end() &&
466           !IsCommandShortcutUserModified(extension, command_name)) {
467         RemoveKeybindingPrefs(extension->id(), command_name);
468       }
469     }
470   }
471
472   Command existing_browser_action_command;
473   const Command* new_browser_action_command =
474       CommandsInfo::GetBrowserActionCommand(extension);
475   if (GetBrowserActionCommand(extension->id(),
476                               CommandService::ACTIVE_ONLY,
477                               &existing_browser_action_command,
478                               NULL) &&
479       // The browser action command may be defaulted to an unassigned
480       // accelerator if a browser action is specified by the extension but a
481       // keybinding is not declared. See
482       // CommandsHandler::MaybeSetBrowserActionDefault.
483       (!new_browser_action_command ||
484        new_browser_action_command->accelerator().key_code() ==
485            ui::VKEY_UNKNOWN) &&
486       !IsCommandShortcutUserModified(
487           extension,
488           existing_browser_action_command.command_name())) {
489     RemoveKeybindingPrefs(extension->id(),
490                           existing_browser_action_command.command_name());
491   }
492
493   Command existing_page_action_command;
494   if (GetPageActionCommand(extension->id(),
495                            CommandService::ACTIVE_ONLY,
496                            &existing_page_action_command,
497                            NULL) &&
498       !CommandsInfo::GetPageActionCommand(extension) &&
499       !IsCommandShortcutUserModified(
500           extension,
501           existing_page_action_command.command_name())) {
502     RemoveKeybindingPrefs(extension->id(),
503                           existing_page_action_command.command_name());
504   }
505 }
506
507 void CommandService::AssignKeybindings(const Extension* extension) {
508   const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
509   if (!commands)
510     return;
511
512   ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
513   // TODO(wittman): remove use of this pref after M37 hits stable.
514   if (!InitialBindingsHaveBeenAssigned(extension_prefs, extension->id()))
515     SetInitialBindingsHaveBeenAssigned(extension_prefs, extension->id());
516
517   for (CommandMap::const_iterator iter = commands->begin();
518        iter != commands->end(); ++iter) {
519     const Command command = iter->second;
520     if (CanAutoAssign(command, extension)) {
521       AddKeybindingPref(command.accelerator(),
522                         extension->id(),
523                         command.command_name(),
524                         false,  // Overwriting not allowed.
525                         command.global());
526     }
527   }
528
529   const Command* browser_action_command =
530       CommandsInfo::GetBrowserActionCommand(extension);
531   if (browser_action_command &&
532       CanAutoAssign(*browser_action_command, extension)) {
533     AddKeybindingPref(browser_action_command->accelerator(),
534                       extension->id(),
535                       browser_action_command->command_name(),
536                       false,   // Overwriting not allowed.
537                       false);  // Not global.
538   }
539
540   const Command* page_action_command =
541       CommandsInfo::GetPageActionCommand(extension);
542   if (page_action_command && CanAutoAssign(*page_action_command, extension)) {
543     AddKeybindingPref(page_action_command->accelerator(),
544                       extension->id(),
545                       page_action_command->command_name(),
546                       false,   // Overwriting not allowed.
547                       false);  // Not global.
548   }
549 }
550
551 bool CommandService::CanAutoAssign(const Command &command,
552                                    const Extension* extension) {
553   // Media Keys are non-exclusive, so allow auto-assigning them.
554   if (Command::IsMediaKey(command.accelerator()))
555     return true;
556
557   // Extensions are allowed to auto-assign updated keys if the user has not
558   // changed from the previous value.
559   if (IsCommandShortcutUserModified(extension, command.command_name()))
560     return false;
561
562   if (command.global()) {
563     using namespace extensions;
564     if (command.command_name() == manifest_values::kBrowserActionCommandEvent ||
565         command.command_name() == manifest_values::kPageActionCommandEvent)
566       return false;  // Browser and page actions are not global in nature.
567
568     // Global shortcuts are restricted to (Ctrl|Command)+Shift+[0-9].
569 #if defined OS_MACOSX
570     if (!command.accelerator().IsCmdDown())
571       return false;
572 #else
573     if (!command.accelerator().IsCtrlDown())
574       return false;
575 #endif
576     if (!command.accelerator().IsShiftDown())
577       return false;
578     return (command.accelerator().key_code() >= ui::VKEY_0 &&
579             command.accelerator().key_code() <= ui::VKEY_9);
580   } else {
581     // Not a global command, check if Chrome shortcut and whether
582     // we can override it.
583     if (command.accelerator() ==
584         chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE) &&
585         CommandService::RemovesBookmarkShortcut(extension)) {
586       // If this check fails it either means we have an API to override a
587       // key that isn't a ChromeAccelerator (and the API can therefore be
588       // deprecated) or the IsChromeAccelerator isn't consistently
589       // returning true for all accelerators.
590       DCHECK(chrome::IsChromeAccelerator(command.accelerator(), profile_));
591       return true;
592     }
593
594     return !chrome::IsChromeAccelerator(command.accelerator(), profile_);
595   }
596 }
597
598 void CommandService::UpdateExtensionSuggestedCommandPrefs(
599     const Extension* extension) {
600   scoped_ptr<base::DictionaryValue> suggested_key_prefs(
601       new base::DictionaryValue);
602
603   const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
604   if (commands) {
605     for (CommandMap::const_iterator iter = commands->begin();
606          iter != commands->end(); ++iter) {
607       const Command command = iter->second;
608       scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
609       command_keys->SetString(
610           kSuggestedKey,
611           Command::AcceleratorToString(command.accelerator()));
612       suggested_key_prefs->Set(command.command_name(), command_keys.release());
613     }
614   }
615
616   const Command* browser_action_command =
617       CommandsInfo::GetBrowserActionCommand(extension);
618   // The browser action command may be defaulted to an unassigned accelerator if
619   // a browser action is specified by the extension but a keybinding is not
620   // declared. See CommandsHandler::MaybeSetBrowserActionDefault.
621   if (browser_action_command &&
622       browser_action_command->accelerator().key_code() != ui::VKEY_UNKNOWN) {
623     scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
624     command_keys->SetString(
625         kSuggestedKey,
626         Command::AcceleratorToString(browser_action_command->accelerator()));
627     suggested_key_prefs->Set(browser_action_command->command_name(),
628                              command_keys.release());
629   }
630
631   const Command* page_action_command =
632       CommandsInfo::GetPageActionCommand(extension);
633   if (page_action_command) {
634     scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
635     command_keys->SetString(
636         kSuggestedKey,
637         Command::AcceleratorToString(page_action_command->accelerator()));
638     suggested_key_prefs->Set(page_action_command->command_name(),
639                command_keys.release());
640   }
641
642   // Merge into current prefs, if present.
643   MergeSuggestedKeyPrefs(extension->id(),
644                          ExtensionPrefs::Get(profile_),
645                          suggested_key_prefs.Pass());
646 }
647
648 void CommandService::RemoveDefunctExtensionSuggestedCommandPrefs(
649     const Extension* extension) {
650   ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
651   const base::DictionaryValue* current_prefs = NULL;
652   extension_prefs->ReadPrefAsDictionary(extension->id(),
653                                         kCommands,
654                                         &current_prefs);
655
656   if (current_prefs) {
657     scoped_ptr<base::DictionaryValue> suggested_key_prefs(
658         current_prefs->DeepCopy());
659     const CommandMap* named_commands =
660         CommandsInfo::GetNamedCommands(extension);
661     const Command* browser_action_command =
662         CommandsInfo::GetBrowserActionCommand(extension);
663     for (base::DictionaryValue::Iterator it(*current_prefs);
664          !it.IsAtEnd(); it.Advance()) {
665       if (it.key() == manifest_values::kBrowserActionCommandEvent) {
666         // The browser action command may be defaulted to an unassigned
667         // accelerator if a browser action is specified by the extension but a
668         // keybinding is not declared. See
669         // CommandsHandler::MaybeSetBrowserActionDefault.
670         if (!browser_action_command ||
671             browser_action_command->accelerator().key_code() ==
672                 ui::VKEY_UNKNOWN) {
673           suggested_key_prefs->Remove(it.key(), NULL);
674         }
675       } else if (it.key() == manifest_values::kPageActionCommandEvent) {
676         if (!CommandsInfo::GetPageActionCommand(extension))
677           suggested_key_prefs->Remove(it.key(), NULL);
678       } else if (named_commands) {
679         if (named_commands->find(it.key()) == named_commands->end())
680           suggested_key_prefs->Remove(it.key(), NULL);
681       }
682     }
683
684     extension_prefs->UpdateExtensionPref(extension->id(),
685                                          kCommands,
686                                          suggested_key_prefs.release());
687   }
688 }
689
690 bool CommandService::IsCommandShortcutUserModified(
691     const Extension* extension,
692     const std::string& command_name) {
693   // Get the previous suggested key, if any.
694   ui::Accelerator suggested_key;
695   bool suggested_key_was_assigned = false;
696   ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
697   const base::DictionaryValue* commands_prefs = NULL;
698   const base::DictionaryValue* suggested_key_prefs = NULL;
699   if (extension_prefs->ReadPrefAsDictionary(extension->id(),
700                                             kCommands,
701                                             &commands_prefs) &&
702       commands_prefs->GetDictionary(command_name, &suggested_key_prefs)) {
703     std::string suggested_key_string;
704     if (suggested_key_prefs->GetString(kSuggestedKey, &suggested_key_string)) {
705       suggested_key = Command::StringToAccelerator(suggested_key_string,
706                                                    command_name);
707     }
708
709     suggested_key_prefs->GetBoolean(kSuggestedKeyWasAssigned,
710                                     &suggested_key_was_assigned);
711   }
712
713   // Get the active shortcut from the prefs, if any.
714   Command active_command = FindCommandByName(extension->id(), command_name);
715
716   return suggested_key_was_assigned ?
717       active_command.accelerator() != suggested_key :
718       active_command.accelerator().key_code() != ui::VKEY_UNKNOWN;
719 }
720
721 bool CommandService::IsKeybindingChanging(const Extension* extension,
722                                           const std::string& command_name) {
723   // Get the new assigned command, if any.
724   Command new_command;
725   if (command_name == manifest_values::kBrowserActionCommandEvent) {
726     new_command = *CommandsInfo::GetBrowserActionCommand(extension);
727   } else if (command_name == manifest_values::kPageActionCommandEvent) {
728     new_command = *CommandsInfo::GetPageActionCommand(extension);
729   } else {  // This is a named command.
730     const CommandMap* named_commands =
731         CommandsInfo::GetNamedCommands(extension);
732     if (named_commands) {
733       CommandMap::const_iterator loc = named_commands->find(command_name);
734       if (loc != named_commands->end())
735         new_command = loc->second;
736     }
737   }
738
739   return Command::StringToAccelerator(
740       GetSuggestedKeyPref(extension, command_name), command_name) !=
741       new_command.accelerator();
742 }
743
744 std::string CommandService::GetSuggestedKeyPref(
745     const Extension* extension,
746     const std::string& command_name) {
747   // Get the previous suggested key, if any.
748   ui::Accelerator suggested_key;
749   ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
750   const base::DictionaryValue* commands_prefs = NULL;
751   if (extension_prefs->ReadPrefAsDictionary(extension->id(),
752                                             kCommands,
753                                             &commands_prefs)) {
754     const base::DictionaryValue* suggested_key_prefs = NULL;
755     std::string suggested_key;
756     if (commands_prefs->GetDictionary(command_name, &suggested_key_prefs) &&
757         suggested_key_prefs->GetString(kSuggestedKey, &suggested_key)) {
758       return suggested_key;
759     }
760   }
761
762   return std::string();
763 }
764
765 void CommandService::RemoveKeybindingPrefs(const std::string& extension_id,
766                                            const std::string& command_name) {
767   DictionaryPrefUpdate updater(profile_->GetPrefs(),
768                                prefs::kExtensionCommands);
769   base::DictionaryValue* bindings = updater.Get();
770
771   typedef std::vector<std::string> KeysToRemove;
772   KeysToRemove keys_to_remove;
773   for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
774        it.Advance()) {
775     // Removal of keybinding preference should be limited to current platform.
776     if (!IsForCurrentPlatform(it.key()))
777       continue;
778
779     const base::DictionaryValue* item = NULL;
780     it.value().GetAsDictionary(&item);
781
782     std::string extension;
783     item->GetString(kExtension, &extension);
784
785     if (extension == extension_id) {
786       // If |command_name| is specified, delete only that command. Otherwise,
787       // delete all commands.
788       if (!command_name.empty()) {
789         std::string command;
790         item->GetString(kCommandName, &command);
791         if (command_name != command)
792           continue;
793       }
794
795       keys_to_remove.push_back(it.key());
796     }
797   }
798
799   for (KeysToRemove::const_iterator it = keys_to_remove.begin();
800        it != keys_to_remove.end(); ++it) {
801     std::string key = *it;
802     bindings->Remove(key, NULL);
803
804     std::pair<const std::string, const std::string> details =
805         std::make_pair(extension_id, command_name);
806     content::NotificationService::current()->Notify(
807         extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED,
808         content::Source<Profile>(profile_),
809         content::Details<std::pair<const std::string, const std::string> >(
810             &details));
811   }
812 }
813
814 bool CommandService::GetExtensionActionCommand(
815     const std::string& extension_id,
816     QueryType query_type,
817     Command* command,
818     bool* active,
819     ExtensionCommandType action_type) const {
820   const ExtensionSet& extensions =
821       ExtensionRegistry::Get(profile_)->enabled_extensions();
822   const Extension* extension = extensions.GetByID(extension_id);
823   CHECK(extension);
824
825   if (active)
826     *active = false;
827
828   const Command* requested_command = NULL;
829   switch (action_type) {
830     case BROWSER_ACTION:
831       requested_command = CommandsInfo::GetBrowserActionCommand(extension);
832       break;
833     case PAGE_ACTION:
834       requested_command = CommandsInfo::GetPageActionCommand(extension);
835       break;
836     case NAMED:
837       NOTREACHED();
838       return false;
839   }
840   if (!requested_command)
841     return false;
842
843   // Look up to see if the user has overridden how the command should work.
844   Command saved_command =
845       FindCommandByName(extension_id, requested_command->command_name());
846   ui::Accelerator shortcut_assigned = saved_command.accelerator();
847
848   if (active)
849     *active = (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN);
850
851   if (query_type == ACTIVE_ONLY &&
852       shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
853     return false;
854
855   *command = *requested_command;
856   if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
857     command->set_accelerator(shortcut_assigned);
858
859   return true;
860 }
861
862 template <>
863 void
864 BrowserContextKeyedAPIFactory<CommandService>::DeclareFactoryDependencies() {
865   DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance());
866 }
867
868 }  // namespace extensions