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