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