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