• 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/browser/chrome_notification_types.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_function_registry.h"
18 #include "chrome/browser/extensions/extension_keybinding_registry.h"
19 #include "chrome/browser/extensions/extension_service.h"
20 #include "chrome/browser/extensions/extension_system.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/ui/accelerator_utils.h"
23 #include "chrome/common/extensions/api/commands/commands_handler.h"
24 #include "chrome/common/pref_names.h"
25 #include "components/user_prefs/pref_registry_syncable.h"
26 #include "content/public/browser/notification_details.h"
27 #include "content/public/browser/notification_service.h"
28 #include "extensions/common/feature_switch.h"
29 #include "extensions/common/manifest_constants.h"
30 
31 using extensions::Extension;
32 using extensions::ExtensionPrefs;
33 
34 namespace {
35 
36 const char kExtension[] = "extension";
37 const char kCommandName[] = "command_name";
38 const char kGlobal[] = "global";
39 
40 // A preference that indicates that the initial keybindings for the given
41 // extension have been set.
42 const char kInitialBindingsHaveBeenAssigned[] = "initial_keybindings_set";
43 
GetPlatformKeybindingKeyForAccelerator(const ui::Accelerator & accelerator,const std::string extension_id)44 std::string GetPlatformKeybindingKeyForAccelerator(
45     const ui::Accelerator& accelerator, const std::string extension_id) {
46   std::string key = extensions::Command::CommandPlatform() + ":" +
47                     extensions::Command::AcceleratorToString(accelerator);
48 
49   // Media keys have a 1-to-many relationship with targets, unlike regular
50   // shortcut (1-to-1 relationship). That means two or more extensions can
51   // register for the same media key so the extension ID needs to be added to
52   // the key to make sure the key is unique.
53   if (extensions::CommandService::IsMediaKey(accelerator))
54     key += ":" + extension_id;
55 
56   return key;
57 }
58 
IsForCurrentPlatform(const std::string & key)59 bool IsForCurrentPlatform(const std::string& key) {
60   return StartsWithASCII(
61       key, extensions::Command::CommandPlatform() + ":", true);
62 }
63 
SetInitialBindingsHaveBeenAssigned(ExtensionPrefs * prefs,const std::string & extension_id)64 void SetInitialBindingsHaveBeenAssigned(
65     ExtensionPrefs* prefs, const std::string& extension_id) {
66   prefs->UpdateExtensionPref(extension_id, kInitialBindingsHaveBeenAssigned,
67                              new base::FundamentalValue(true));
68 }
69 
InitialBindingsHaveBeenAssigned(const ExtensionPrefs * prefs,const std::string & extension_id)70 bool InitialBindingsHaveBeenAssigned(
71     const ExtensionPrefs* prefs, const std::string& extension_id) {
72   bool assigned = false;
73   if (!prefs || !prefs->ReadPrefAsBoolean(extension_id,
74                                           kInitialBindingsHaveBeenAssigned,
75                                           &assigned))
76     return false;
77 
78   return assigned;
79 }
80 
IsWhitelistedGlobalShortcut(const extensions::Command & command)81 bool IsWhitelistedGlobalShortcut(const extensions::Command& command) {
82   if (!command.global())
83     return true;
84   if (!command.accelerator().IsCtrlDown())
85     return false;
86   if (!command.accelerator().IsShiftDown())
87     return false;
88   return (command.accelerator().key_code() >= ui::VKEY_0 &&
89           command.accelerator().key_code() <= ui::VKEY_9);
90 }
91 
92 }  // namespace
93 
94 namespace extensions {
95 
96 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)97 void CommandService::RegisterProfilePrefs(
98     user_prefs::PrefRegistrySyncable* registry) {
99   registry->RegisterDictionaryPref(
100       prefs::kExtensionCommands,
101       user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
102 }
103 
CommandService(Profile * profile)104 CommandService::CommandService(Profile* profile)
105     : profile_(profile) {
106   ExtensionFunctionRegistry::GetInstance()->
107       RegisterFunction<GetAllCommandsFunction>();
108 
109   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED,
110       content::Source<Profile>(profile));
111   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
112       content::Source<Profile>(profile));
113 }
114 
~CommandService()115 CommandService::~CommandService() {
116 }
117 
118 static base::LazyInstance<ProfileKeyedAPIFactory<CommandService> >
119 g_factory = LAZY_INSTANCE_INITIALIZER;
120 
121 // static
GetFactoryInstance()122 ProfileKeyedAPIFactory<CommandService>* CommandService::GetFactoryInstance() {
123   return &g_factory.Get();
124 }
125 
126 // static
Get(Profile * profile)127 CommandService* CommandService::Get(Profile* profile) {
128   return ProfileKeyedAPIFactory<CommandService>::GetForProfile(profile);
129 }
130 
131 // static
IsMediaKey(const ui::Accelerator & accelerator)132 bool CommandService::IsMediaKey(const ui::Accelerator& accelerator) {
133   if (accelerator.modifiers() != 0)
134     return false;
135 
136   return (accelerator.key_code() == ui::VKEY_MEDIA_NEXT_TRACK ||
137           accelerator.key_code() == ui::VKEY_MEDIA_PREV_TRACK ||
138           accelerator.key_code() == ui::VKEY_MEDIA_PLAY_PAUSE ||
139           accelerator.key_code() == ui::VKEY_MEDIA_STOP);
140 }
141 
GetBrowserActionCommand(const std::string & extension_id,QueryType type,extensions::Command * command,bool * active)142 bool CommandService::GetBrowserActionCommand(
143     const std::string& extension_id,
144     QueryType type,
145     extensions::Command* command,
146     bool* active) {
147   return GetExtensionActionCommand(
148       extension_id, type, command, active, BROWSER_ACTION);
149 }
150 
GetPageActionCommand(const std::string & extension_id,QueryType type,extensions::Command * command,bool * active)151 bool CommandService::GetPageActionCommand(
152     const std::string& extension_id,
153     QueryType type,
154     extensions::Command* command,
155     bool* active) {
156   return GetExtensionActionCommand(
157       extension_id, type, command, active, PAGE_ACTION);
158 }
159 
GetScriptBadgeCommand(const std::string & extension_id,QueryType type,extensions::Command * command,bool * active)160 bool CommandService::GetScriptBadgeCommand(
161     const std::string& extension_id,
162     QueryType type,
163     extensions::Command* command,
164     bool* active) {
165   return GetExtensionActionCommand(
166       extension_id, type, command, active, SCRIPT_BADGE);
167 }
168 
GetNamedCommands(const std::string & extension_id,QueryType type,CommandScope scope,extensions::CommandMap * command_map)169 bool CommandService::GetNamedCommands(const std::string& extension_id,
170                                       QueryType type,
171                                       CommandScope scope,
172                                       extensions::CommandMap* command_map) {
173   ExtensionService* extension_service =
174       ExtensionSystem::Get(profile_)->extension_service();
175   if (!extension_service)
176     return false;  // Can occur during testing.
177   const ExtensionSet* extensions = extension_service->extensions();
178   const Extension* extension = extensions->GetByID(extension_id);
179   CHECK(extension);
180 
181   command_map->clear();
182   const extensions::CommandMap* commands =
183       CommandsInfo::GetNamedCommands(extension);
184   if (!commands)
185     return false;
186 
187   extensions::CommandMap::const_iterator iter = commands->begin();
188   for (; iter != commands->end(); ++iter) {
189     // Look up to see if the user has overridden how the command should work.
190     extensions::Command saved_command =
191         FindCommandByName(extension_id, iter->second.command_name());
192     ui::Accelerator shortcut_assigned = saved_command.accelerator();
193 
194     if (type == ACTIVE_ONLY && shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
195       continue;
196 
197     extensions::Command command = iter->second;
198     if (scope != ANY_SCOPE && ((scope == GLOBAL) != saved_command.global()))
199       continue;
200 
201     if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
202       command.set_accelerator(shortcut_assigned);
203     command.set_global(saved_command.global());
204 
205     (*command_map)[iter->second.command_name()] = command;
206   }
207 
208   return true;
209 }
210 
AddKeybindingPref(const ui::Accelerator & accelerator,std::string extension_id,std::string command_name,bool allow_overrides,bool global)211 bool CommandService::AddKeybindingPref(
212     const ui::Accelerator& accelerator,
213     std::string extension_id,
214     std::string command_name,
215     bool allow_overrides,
216     bool global) {
217   if (accelerator.key_code() == ui::VKEY_UNKNOWN)
218     return false;
219 
220   // Media Keys are allowed to be used by named command only.
221   DCHECK(!IsMediaKey(accelerator) ||
222          (command_name != manifest_values::kPageActionCommandEvent &&
223           command_name != manifest_values::kBrowserActionCommandEvent &&
224           command_name != manifest_values::kScriptBadgeCommandEvent));
225 
226   DictionaryPrefUpdate updater(profile_->GetPrefs(),
227                                prefs::kExtensionCommands);
228   base::DictionaryValue* bindings = updater.Get();
229 
230   std::string key = GetPlatformKeybindingKeyForAccelerator(accelerator,
231                                                            extension_id);
232 
233   if (bindings->HasKey(key)) {
234     if (!allow_overrides)
235       return false;  // Already taken.
236 
237     // If the shortcut has been assigned to another command, it should be
238     // removed before overriding, so that |ExtensionKeybindingRegistry| can get
239     // a chance to do clean-up.
240     const base::DictionaryValue* item = NULL;
241     bindings->GetDictionary(key, &item);
242     std::string old_extension_id;
243     std::string old_command_name;
244     item->GetString(kExtension, &old_extension_id);
245     item->GetString(kCommandName, &old_command_name);
246     RemoveKeybindingPrefs(old_extension_id, old_command_name);
247   }
248 
249   base::DictionaryValue* keybinding = new base::DictionaryValue();
250   keybinding->SetString(kExtension, extension_id);
251   keybinding->SetString(kCommandName, command_name);
252   keybinding->SetBoolean(kGlobal, global);
253 
254   bindings->Set(key, keybinding);
255 
256   std::pair<const std::string, const std::string> details =
257       std::make_pair(extension_id, command_name);
258   content::NotificationService::current()->Notify(
259       chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED,
260       content::Source<Profile>(profile_),
261       content::Details<
262           std::pair<const std::string, const std::string> >(&details));
263 
264   return true;
265 }
266 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)267 void CommandService::Observe(
268     int type,
269     const content::NotificationSource& source,
270     const content::NotificationDetails& details) {
271   switch (type) {
272     case chrome::NOTIFICATION_EXTENSION_INSTALLED:
273       AssignInitialKeybindings(
274           content::Details<const InstalledExtensionInfo>(details)->extension);
275       break;
276     case chrome::NOTIFICATION_EXTENSION_UNINSTALLED:
277       RemoveKeybindingPrefs(
278           content::Details<const Extension>(details)->id(),
279           std::string());
280       break;
281     default:
282       NOTREACHED();
283       break;
284   }
285 }
286 
UpdateKeybindingPrefs(const std::string & extension_id,const std::string & command_name,const std::string & keystroke)287 void CommandService::UpdateKeybindingPrefs(const std::string& extension_id,
288                                            const std::string& command_name,
289                                            const std::string& keystroke) {
290   extensions::Command command = FindCommandByName(extension_id, command_name);
291 
292   // The extension command might be assigned another shortcut. Remove that
293   // shortcut before proceeding.
294   RemoveKeybindingPrefs(extension_id, command_name);
295 
296   ui::Accelerator accelerator =
297       Command::StringToAccelerator(keystroke, command_name);
298   AddKeybindingPref(accelerator, extension_id, command_name,
299                     true, command.global());
300 }
301 
SetScope(const std::string & extension_id,const std::string & command_name,bool global)302 bool CommandService::SetScope(const std::string& extension_id,
303                               const std::string& command_name,
304                               bool global) {
305   extensions::Command command = FindCommandByName(extension_id, command_name);
306   if (global == command.global())
307     return false;
308 
309   // Pre-existing shortcuts must be removed before proceeding because the
310   // handlers for global and non-global extensions are not one and the same.
311   RemoveKeybindingPrefs(extension_id, command_name);
312   AddKeybindingPref(command.accelerator(), extension_id,
313                     command_name, true, global);
314   return true;
315 }
316 
FindCommandByName(const std::string & extension_id,const std::string & command)317 Command CommandService::FindCommandByName(
318     const std::string& extension_id, const std::string& command) {
319   const base::DictionaryValue* bindings =
320       profile_->GetPrefs()->GetDictionary(prefs::kExtensionCommands);
321   for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
322        it.Advance()) {
323     const base::DictionaryValue* item = NULL;
324     it.value().GetAsDictionary(&item);
325 
326     std::string extension;
327     item->GetString(kExtension, &extension);
328     if (extension != extension_id)
329       continue;
330     std::string command_name;
331     item->GetString(kCommandName, &command_name);
332     if (command != command_name)
333       continue;
334     // Format stored in Preferences is: "Platform:Shortcut[:ExtensionId]".
335     std::string shortcut = it.key();
336     if (!IsForCurrentPlatform(shortcut))
337       continue;
338     bool global = false;
339     if (FeatureSwitch::global_commands()->IsEnabled())
340       item->GetBoolean(kGlobal, &global);
341 
342     std::vector<std::string> tokens;
343     base::SplitString(shortcut, ':', &tokens);
344     CHECK(tokens.size() >= 2);
345     shortcut = tokens[1];
346 
347     return Command(command_name, base::string16(), shortcut, global);
348   }
349 
350   return Command();
351 }
352 
AssignInitialKeybindings(const Extension * extension)353 void CommandService::AssignInitialKeybindings(const Extension* extension) {
354   const extensions::CommandMap* commands =
355       CommandsInfo::GetNamedCommands(extension);
356   if (!commands)
357     return;
358 
359   ExtensionService* extension_service =
360       ExtensionSystem::Get(profile_)->extension_service();
361   ExtensionPrefs* extension_prefs = extension_service->extension_prefs();
362   if (InitialBindingsHaveBeenAssigned(extension_prefs, extension->id()))
363     return;
364   SetInitialBindingsHaveBeenAssigned(extension_prefs, extension->id());
365 
366   extensions::CommandMap::const_iterator iter = commands->begin();
367   for (; iter != commands->end(); ++iter) {
368     // Make sure registered Chrome shortcuts cannot be automatically assigned
369     // (overwritten) by extension developers. Media keys are an exception here.
370     if ((!chrome::IsChromeAccelerator(iter->second.accelerator(), profile_) &&
371         IsWhitelistedGlobalShortcut(iter->second)) ||
372         extensions::CommandService::IsMediaKey(iter->second.accelerator())) {
373       AddKeybindingPref(iter->second.accelerator(),
374                         extension->id(),
375                         iter->second.command_name(),
376                         false,  // Overwriting not allowed.
377                         iter->second.global());
378     }
379   }
380 
381   const extensions::Command* browser_action_command =
382       CommandsInfo::GetBrowserActionCommand(extension);
383   if (browser_action_command) {
384     if (!chrome::IsChromeAccelerator(
385         browser_action_command->accelerator(), profile_)) {
386       AddKeybindingPref(browser_action_command->accelerator(),
387                         extension->id(),
388                         browser_action_command->command_name(),
389                         false,   // Overwriting not allowed.
390                         false);  // Browser actions can't be global.
391     }
392   }
393 
394   const extensions::Command* page_action_command =
395       CommandsInfo::GetPageActionCommand(extension);
396   if (page_action_command) {
397     if (!chrome::IsChromeAccelerator(
398         page_action_command->accelerator(), profile_)) {
399       AddKeybindingPref(page_action_command->accelerator(),
400                         extension->id(),
401                         page_action_command->command_name(),
402                         false,   // Overwriting not allowed.
403                         false);  // Page actions can't be global.
404     }
405   }
406 
407   const extensions::Command* script_badge_command =
408       CommandsInfo::GetScriptBadgeCommand(extension);
409   if (script_badge_command) {
410     if (!chrome::IsChromeAccelerator(
411         script_badge_command->accelerator(), profile_)) {
412       AddKeybindingPref(script_badge_command->accelerator(),
413                         extension->id(),
414                         script_badge_command->command_name(),
415                         false,   // Overwriting not allowed.
416                         false);  // Script badges can't be global.
417     }
418   }
419 }
420 
RemoveKeybindingPrefs(const std::string & extension_id,const std::string & command_name)421 void CommandService::RemoveKeybindingPrefs(const std::string& extension_id,
422                                            const std::string& command_name) {
423   DictionaryPrefUpdate updater(profile_->GetPrefs(),
424                                prefs::kExtensionCommands);
425   base::DictionaryValue* bindings = updater.Get();
426 
427   typedef std::vector<std::string> KeysToRemove;
428   KeysToRemove keys_to_remove;
429   for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
430        it.Advance()) {
431     // Removal of keybinding preference should be limited to current platform.
432     if (!IsForCurrentPlatform(it.key()))
433       continue;
434 
435     const base::DictionaryValue* item = NULL;
436     it.value().GetAsDictionary(&item);
437 
438     std::string extension;
439     item->GetString(kExtension, &extension);
440 
441     if (extension == extension_id) {
442       // If |command_name| is specified, delete only that command. Otherwise,
443       // delete all commands.
444       if (!command_name.empty()) {
445         std::string command;
446         item->GetString(kCommandName, &command);
447         if (command_name != command)
448           continue;
449       }
450 
451       keys_to_remove.push_back(it.key());
452     }
453   }
454 
455   for (KeysToRemove::const_iterator it = keys_to_remove.begin();
456        it != keys_to_remove.end(); ++it) {
457     std::string key = *it;
458     bindings->Remove(key, NULL);
459 
460     std::pair<const std::string, const std::string> details =
461         std::make_pair(extension_id, command_name);
462     content::NotificationService::current()->Notify(
463         chrome::NOTIFICATION_EXTENSION_COMMAND_REMOVED,
464         content::Source<Profile>(profile_),
465         content::Details<
466             std::pair<const std::string, const std::string> >(&details));
467   }
468 }
469 
GetExtensionActionCommand(const std::string & extension_id,QueryType query_type,extensions::Command * command,bool * active,ExtensionActionType action_type)470 bool CommandService::GetExtensionActionCommand(
471     const std::string& extension_id,
472     QueryType query_type,
473     extensions::Command* command,
474     bool* active,
475     ExtensionActionType action_type) {
476   ExtensionService* service =
477       ExtensionSystem::Get(profile_)->extension_service();
478   if (!service)
479     return false;  // Can happen in tests.
480   const ExtensionSet* extensions = service->extensions();
481   const Extension* extension = extensions->GetByID(extension_id);
482   CHECK(extension);
483 
484   if (active)
485     *active = false;
486 
487   const extensions::Command* requested_command = NULL;
488   switch (action_type) {
489     case BROWSER_ACTION:
490       requested_command = CommandsInfo::GetBrowserActionCommand(extension);
491       break;
492     case PAGE_ACTION:
493       requested_command = CommandsInfo::GetPageActionCommand(extension);
494       break;
495     case SCRIPT_BADGE:
496       requested_command = CommandsInfo::GetScriptBadgeCommand(extension);
497       break;
498   }
499   if (!requested_command)
500     return false;
501 
502   // Look up to see if the user has overridden how the command should work.
503   extensions::Command saved_command =
504       FindCommandByName(extension_id, requested_command->command_name());
505   ui::Accelerator shortcut_assigned = saved_command.accelerator();
506 
507   if (active)
508     *active = (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN);
509 
510   if (query_type == ACTIVE_ONLY &&
511       shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
512     return false;
513 
514   *command = *requested_command;
515   if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
516     command->set_accelerator(shortcut_assigned);
517 
518   return true;
519 }
520 
521 template <>
DeclareFactoryDependencies()522 void ProfileKeyedAPIFactory<CommandService>::DeclareFactoryDependencies() {
523   DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance());
524 }
525 
526 }  // namespace extensions
527