• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GetPlatformKeybindingKeyForAccelerator(const ui::Accelerator & accelerator,const std::string extension_id)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 
IsForCurrentPlatform(const std::string & key)71 bool IsForCurrentPlatform(const std::string& key) {
72   return StartsWithASCII(key, Command::CommandPlatform() + ":", true);
73 }
74 
SetInitialBindingsHaveBeenAssigned(ExtensionPrefs * prefs,const std::string & extension_id)75 void SetInitialBindingsHaveBeenAssigned(
76     ExtensionPrefs* prefs, const std::string& extension_id) {
77   prefs->UpdateExtensionPref(extension_id, kInitialBindingsHaveBeenAssigned,
78                              new base::FundamentalValue(true));
79 }
80 
InitialBindingsHaveBeenAssigned(const ExtensionPrefs * prefs,const std::string & extension_id)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.
MergeSuggestedKeyPrefs(const std::string & extension_id,ExtensionPrefs * extension_prefs,scoped_ptr<base::DictionaryValue> suggested_key_prefs)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
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)115 void CommandService::RegisterProfilePrefs(
116     user_prefs::PrefRegistrySyncable* registry) {
117   registry->RegisterDictionaryPref(
118       prefs::kExtensionCommands,
119       user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
120 }
121 
CommandService(content::BrowserContext * context)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 
~CommandService()131 CommandService::~CommandService() {
132 }
133 
134 static base::LazyInstance<BrowserContextKeyedAPIFactory<CommandService> >
135     g_factory = LAZY_INSTANCE_INITIALIZER;
136 
137 // static
138 BrowserContextKeyedAPIFactory<CommandService>*
GetFactoryInstance()139 CommandService::GetFactoryInstance() {
140   return g_factory.Pointer();
141 }
142 
143 // static
Get(content::BrowserContext * context)144 CommandService* CommandService::Get(content::BrowserContext* context) {
145   return BrowserContextKeyedAPIFactory<CommandService>::Get(context);
146 }
147 
148 // static
RemovesBookmarkShortcut(const Extension * extension)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
RemovesBookmarkOpenPagesShortcut(const Extension * extension)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 
GetBrowserActionCommand(const std::string & extension_id,QueryType type,Command * command,bool * active) const165 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 
GetPageActionCommand(const std::string & extension_id,QueryType type,Command * command,bool * active) const173 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 
GetNamedCommands(const std::string & extension_id,QueryType type,CommandScope scope,CommandMap * command_map) const181 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 
AddKeybindingPref(const ui::Accelerator & accelerator,std::string extension_id,std::string command_name,bool allow_overrides,bool global)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 
OnExtensionWillBeInstalled(content::BrowserContext * browser_context,const Extension * extension,bool is_update,bool from_ephemeral,const std::string & old_name)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 
OnExtensionUninstalled(content::BrowserContext * browser_context,const Extension * extension,extensions::UninstallReason reason)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 
UpdateKeybindingPrefs(const std::string & extension_id,const std::string & command_name,const std::string & keystroke)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 
SetScope(const std::string & extension_id,const std::string & command_name,bool global)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 
FindCommandByName(const std::string & extension_id,const std::string & command) const343 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 
GetBoundExtensionCommand(const std::string & extension_id,const ui::Accelerator & accelerator,Command * command,ExtensionCommandType * command_type) const378 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 
OverridesBookmarkShortcut(const Extension * extension) const430 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 
UpdateKeybindings(const Extension * extension)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 
RemoveRelinquishedKeybindings(const Extension * extension)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 
AssignKeybindings(const Extension * extension)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 
CanAutoAssign(const Command & command,const Extension * extension)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     if (extension->permissions_data()->HasAPIPermission(
569             APIPermission::kCommandsAccessibility))
570       return true;
571 
572     // Global shortcuts are restricted to (Ctrl|Command)+Shift+[0-9].
573 #if defined OS_MACOSX
574     if (!command.accelerator().IsCmdDown())
575       return false;
576 #else
577     if (!command.accelerator().IsCtrlDown())
578       return false;
579 #endif
580     if (!command.accelerator().IsShiftDown())
581       return false;
582     return (command.accelerator().key_code() >= ui::VKEY_0 &&
583             command.accelerator().key_code() <= ui::VKEY_9);
584   } else {
585     // Not a global command, check if Chrome shortcut and whether
586     // we can override it.
587     if (command.accelerator() ==
588         chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE) &&
589         CommandService::RemovesBookmarkShortcut(extension)) {
590       // If this check fails it either means we have an API to override a
591       // key that isn't a ChromeAccelerator (and the API can therefore be
592       // deprecated) or the IsChromeAccelerator isn't consistently
593       // returning true for all accelerators.
594       DCHECK(chrome::IsChromeAccelerator(command.accelerator(), profile_));
595       return true;
596     }
597 
598     return !chrome::IsChromeAccelerator(command.accelerator(), profile_);
599   }
600 }
601 
UpdateExtensionSuggestedCommandPrefs(const Extension * extension)602 void CommandService::UpdateExtensionSuggestedCommandPrefs(
603     const Extension* extension) {
604   scoped_ptr<base::DictionaryValue> suggested_key_prefs(
605       new base::DictionaryValue);
606 
607   const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
608   if (commands) {
609     for (CommandMap::const_iterator iter = commands->begin();
610          iter != commands->end(); ++iter) {
611       const Command command = iter->second;
612       scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
613       command_keys->SetString(
614           kSuggestedKey,
615           Command::AcceleratorToString(command.accelerator()));
616       suggested_key_prefs->Set(command.command_name(), command_keys.release());
617     }
618   }
619 
620   const Command* browser_action_command =
621       CommandsInfo::GetBrowserActionCommand(extension);
622   // The browser action command may be defaulted to an unassigned accelerator if
623   // a browser action is specified by the extension but a keybinding is not
624   // declared. See CommandsHandler::MaybeSetBrowserActionDefault.
625   if (browser_action_command &&
626       browser_action_command->accelerator().key_code() != ui::VKEY_UNKNOWN) {
627     scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
628     command_keys->SetString(
629         kSuggestedKey,
630         Command::AcceleratorToString(browser_action_command->accelerator()));
631     suggested_key_prefs->Set(browser_action_command->command_name(),
632                              command_keys.release());
633   }
634 
635   const Command* page_action_command =
636       CommandsInfo::GetPageActionCommand(extension);
637   if (page_action_command) {
638     scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
639     command_keys->SetString(
640         kSuggestedKey,
641         Command::AcceleratorToString(page_action_command->accelerator()));
642     suggested_key_prefs->Set(page_action_command->command_name(),
643                command_keys.release());
644   }
645 
646   // Merge into current prefs, if present.
647   MergeSuggestedKeyPrefs(extension->id(),
648                          ExtensionPrefs::Get(profile_),
649                          suggested_key_prefs.Pass());
650 }
651 
RemoveDefunctExtensionSuggestedCommandPrefs(const Extension * extension)652 void CommandService::RemoveDefunctExtensionSuggestedCommandPrefs(
653     const Extension* extension) {
654   ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
655   const base::DictionaryValue* current_prefs = NULL;
656   extension_prefs->ReadPrefAsDictionary(extension->id(),
657                                         kCommands,
658                                         &current_prefs);
659 
660   if (current_prefs) {
661     scoped_ptr<base::DictionaryValue> suggested_key_prefs(
662         current_prefs->DeepCopy());
663     const CommandMap* named_commands =
664         CommandsInfo::GetNamedCommands(extension);
665     const Command* browser_action_command =
666         CommandsInfo::GetBrowserActionCommand(extension);
667     for (base::DictionaryValue::Iterator it(*current_prefs);
668          !it.IsAtEnd(); it.Advance()) {
669       if (it.key() == manifest_values::kBrowserActionCommandEvent) {
670         // The browser action command may be defaulted to an unassigned
671         // accelerator if a browser action is specified by the extension but a
672         // keybinding is not declared. See
673         // CommandsHandler::MaybeSetBrowserActionDefault.
674         if (!browser_action_command ||
675             browser_action_command->accelerator().key_code() ==
676                 ui::VKEY_UNKNOWN) {
677           suggested_key_prefs->Remove(it.key(), NULL);
678         }
679       } else if (it.key() == manifest_values::kPageActionCommandEvent) {
680         if (!CommandsInfo::GetPageActionCommand(extension))
681           suggested_key_prefs->Remove(it.key(), NULL);
682       } else if (named_commands) {
683         if (named_commands->find(it.key()) == named_commands->end())
684           suggested_key_prefs->Remove(it.key(), NULL);
685       }
686     }
687 
688     extension_prefs->UpdateExtensionPref(extension->id(),
689                                          kCommands,
690                                          suggested_key_prefs.release());
691   }
692 }
693 
IsCommandShortcutUserModified(const Extension * extension,const std::string & command_name)694 bool CommandService::IsCommandShortcutUserModified(
695     const Extension* extension,
696     const std::string& command_name) {
697   // Get the previous suggested key, if any.
698   ui::Accelerator suggested_key;
699   bool suggested_key_was_assigned = false;
700   ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
701   const base::DictionaryValue* commands_prefs = NULL;
702   const base::DictionaryValue* suggested_key_prefs = NULL;
703   if (extension_prefs->ReadPrefAsDictionary(extension->id(),
704                                             kCommands,
705                                             &commands_prefs) &&
706       commands_prefs->GetDictionary(command_name, &suggested_key_prefs)) {
707     std::string suggested_key_string;
708     if (suggested_key_prefs->GetString(kSuggestedKey, &suggested_key_string)) {
709       suggested_key = Command::StringToAccelerator(suggested_key_string,
710                                                    command_name);
711     }
712 
713     suggested_key_prefs->GetBoolean(kSuggestedKeyWasAssigned,
714                                     &suggested_key_was_assigned);
715   }
716 
717   // Get the active shortcut from the prefs, if any.
718   Command active_command = FindCommandByName(extension->id(), command_name);
719 
720   return suggested_key_was_assigned ?
721       active_command.accelerator() != suggested_key :
722       active_command.accelerator().key_code() != ui::VKEY_UNKNOWN;
723 }
724 
IsKeybindingChanging(const Extension * extension,const std::string & command_name)725 bool CommandService::IsKeybindingChanging(const Extension* extension,
726                                           const std::string& command_name) {
727   // Get the new assigned command, if any.
728   Command new_command;
729   if (command_name == manifest_values::kBrowserActionCommandEvent) {
730     new_command = *CommandsInfo::GetBrowserActionCommand(extension);
731   } else if (command_name == manifest_values::kPageActionCommandEvent) {
732     new_command = *CommandsInfo::GetPageActionCommand(extension);
733   } else {  // This is a named command.
734     const CommandMap* named_commands =
735         CommandsInfo::GetNamedCommands(extension);
736     if (named_commands) {
737       CommandMap::const_iterator loc = named_commands->find(command_name);
738       if (loc != named_commands->end())
739         new_command = loc->second;
740     }
741   }
742 
743   return Command::StringToAccelerator(
744       GetSuggestedKeyPref(extension, command_name), command_name) !=
745       new_command.accelerator();
746 }
747 
GetSuggestedKeyPref(const Extension * extension,const std::string & command_name)748 std::string CommandService::GetSuggestedKeyPref(
749     const Extension* extension,
750     const std::string& command_name) {
751   // Get the previous suggested key, if any.
752   ui::Accelerator suggested_key;
753   ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
754   const base::DictionaryValue* commands_prefs = NULL;
755   if (extension_prefs->ReadPrefAsDictionary(extension->id(),
756                                             kCommands,
757                                             &commands_prefs)) {
758     const base::DictionaryValue* suggested_key_prefs = NULL;
759     std::string suggested_key;
760     if (commands_prefs->GetDictionary(command_name, &suggested_key_prefs) &&
761         suggested_key_prefs->GetString(kSuggestedKey, &suggested_key)) {
762       return suggested_key;
763     }
764   }
765 
766   return std::string();
767 }
768 
RemoveKeybindingPrefs(const std::string & extension_id,const std::string & command_name)769 void CommandService::RemoveKeybindingPrefs(const std::string& extension_id,
770                                            const std::string& command_name) {
771   DictionaryPrefUpdate updater(profile_->GetPrefs(),
772                                prefs::kExtensionCommands);
773   base::DictionaryValue* bindings = updater.Get();
774 
775   typedef std::vector<std::string> KeysToRemove;
776   KeysToRemove keys_to_remove;
777   for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
778        it.Advance()) {
779     // Removal of keybinding preference should be limited to current platform.
780     if (!IsForCurrentPlatform(it.key()))
781       continue;
782 
783     const base::DictionaryValue* item = NULL;
784     it.value().GetAsDictionary(&item);
785 
786     std::string extension;
787     item->GetString(kExtension, &extension);
788 
789     if (extension == extension_id) {
790       // If |command_name| is specified, delete only that command. Otherwise,
791       // delete all commands.
792       if (!command_name.empty()) {
793         std::string command;
794         item->GetString(kCommandName, &command);
795         if (command_name != command)
796           continue;
797       }
798 
799       keys_to_remove.push_back(it.key());
800     }
801   }
802 
803   for (KeysToRemove::const_iterator it = keys_to_remove.begin();
804        it != keys_to_remove.end(); ++it) {
805     std::string key = *it;
806     bindings->Remove(key, NULL);
807 
808     std::pair<const std::string, const std::string> details =
809         std::make_pair(extension_id, command_name);
810     content::NotificationService::current()->Notify(
811         extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED,
812         content::Source<Profile>(profile_),
813         content::Details<std::pair<const std::string, const std::string> >(
814             &details));
815   }
816 }
817 
GetExtensionActionCommand(const std::string & extension_id,QueryType query_type,Command * command,bool * active,ExtensionCommandType action_type) const818 bool CommandService::GetExtensionActionCommand(
819     const std::string& extension_id,
820     QueryType query_type,
821     Command* command,
822     bool* active,
823     ExtensionCommandType action_type) const {
824   const ExtensionSet& extensions =
825       ExtensionRegistry::Get(profile_)->enabled_extensions();
826   const Extension* extension = extensions.GetByID(extension_id);
827   CHECK(extension);
828 
829   if (active)
830     *active = false;
831 
832   const Command* requested_command = NULL;
833   switch (action_type) {
834     case BROWSER_ACTION:
835       requested_command = CommandsInfo::GetBrowserActionCommand(extension);
836       break;
837     case PAGE_ACTION:
838       requested_command = CommandsInfo::GetPageActionCommand(extension);
839       break;
840     case NAMED:
841       NOTREACHED();
842       return false;
843   }
844   if (!requested_command)
845     return false;
846 
847   // Look up to see if the user has overridden how the command should work.
848   Command saved_command =
849       FindCommandByName(extension_id, requested_command->command_name());
850   ui::Accelerator shortcut_assigned = saved_command.accelerator();
851 
852   if (active)
853     *active = (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN);
854 
855   if (query_type == ACTIVE_ONLY &&
856       shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
857     return false;
858 
859   *command = *requested_command;
860   if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
861     command->set_accelerator(shortcut_assigned);
862 
863   return true;
864 }
865 
866 template <>
867 void
DeclareFactoryDependencies()868 BrowserContextKeyedAPIFactory<CommandService>::DeclareFactoryDependencies() {
869   DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance());
870 }
871 
872 }  // namespace extensions
873