command_service.cc revision 58537e28ecd584eab876aee8be7156509866d23a
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 "base/lazy_instance.h"
8#include "base/strings/string_util.h"
9#include "base/strings/utf_string_conversions.h"
10#include "chrome/browser/chrome_notification_types.h"
11#include "chrome/browser/extensions/api/commands/commands.h"
12#include "chrome/browser/extensions/extension_function_registry.h"
13#include "chrome/browser/extensions/extension_keybinding_registry.h"
14#include "chrome/browser/extensions/extension_service.h"
15#include "chrome/browser/extensions/extension_system.h"
16#include "chrome/browser/prefs/scoped_user_pref_update.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/browser/ui/accelerator_utils.h"
19#include "chrome/common/extensions/api/commands/commands_handler.h"
20#include "chrome/common/pref_names.h"
21#include "components/user_prefs/pref_registry_syncable.h"
22#include "content/public/browser/notification_details.h"
23#include "content/public/browser/notification_service.h"
24
25using extensions::Extension;
26using extensions::ExtensionPrefs;
27
28namespace {
29
30const char kExtension[] = "extension";
31const char kCommandName[] = "command_name";
32
33// A preference that indicates that the initial keybindings for the given
34// extension have been set.
35const char kInitialBindingsHaveBeenAssigned[] = "initial_keybindings_set";
36
37std::string GetPlatformKeybindingKeyForAccelerator(
38    const ui::Accelerator& accelerator) {
39  return extensions::Command::CommandPlatform() + ":" +
40         extensions::Command::AcceleratorToString(accelerator);
41}
42
43void SetInitialBindingsHaveBeenAssigned(
44    ExtensionPrefs* prefs, const std::string& extension_id) {
45  prefs->UpdateExtensionPref(extension_id, kInitialBindingsHaveBeenAssigned,
46                             new base::FundamentalValue(true));
47}
48
49bool InitialBindingsHaveBeenAssigned(
50    const ExtensionPrefs* prefs, const std::string& extension_id) {
51  bool assigned = false;
52  if (!prefs || !prefs->ReadPrefAsBoolean(extension_id,
53                                          kInitialBindingsHaveBeenAssigned,
54                                          &assigned))
55    return false;
56
57  return assigned;
58}
59
60}  // namespace
61
62namespace extensions {
63
64// static
65void CommandService::RegisterProfilePrefs(
66    user_prefs::PrefRegistrySyncable* registry) {
67  registry->RegisterDictionaryPref(
68      prefs::kExtensionCommands,
69      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
70}
71
72CommandService::CommandService(Profile* profile)
73    : profile_(profile) {
74  ExtensionFunctionRegistry::GetInstance()->
75      RegisterFunction<GetAllCommandsFunction>();
76
77  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED,
78      content::Source<Profile>(profile));
79  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
80      content::Source<Profile>(profile));
81}
82
83CommandService::~CommandService() {
84}
85
86static base::LazyInstance<ProfileKeyedAPIFactory<CommandService> >
87g_factory = LAZY_INSTANCE_INITIALIZER;
88
89// static
90ProfileKeyedAPIFactory<CommandService>* CommandService::GetFactoryInstance() {
91  return &g_factory.Get();
92}
93
94// static
95CommandService* CommandService::Get(Profile* profile) {
96  return ProfileKeyedAPIFactory<CommandService>::GetForProfile(profile);
97}
98
99bool CommandService::GetBrowserActionCommand(
100    const std::string& extension_id,
101    QueryType type,
102    extensions::Command* command,
103    bool* active) {
104  return GetExtensionActionCommand(
105      extension_id, type, command, active, BROWSER_ACTION);
106}
107
108bool CommandService::GetPageActionCommand(
109    const std::string& extension_id,
110    QueryType type,
111    extensions::Command* command,
112    bool* active) {
113  return GetExtensionActionCommand(
114      extension_id, type, command, active, PAGE_ACTION);
115}
116
117bool CommandService::GetScriptBadgeCommand(
118    const std::string& extension_id,
119    QueryType type,
120    extensions::Command* command,
121    bool* active) {
122  return GetExtensionActionCommand(
123      extension_id, type, command, active, SCRIPT_BADGE);
124}
125
126bool CommandService::GetNamedCommands(const std::string& extension_id,
127                                      QueryType type,
128                                      extensions::CommandMap* command_map) {
129  const ExtensionSet* extensions =
130      ExtensionSystem::Get(profile_)->extension_service()->extensions();
131  const Extension* extension = extensions->GetByID(extension_id);
132  CHECK(extension);
133
134  command_map->clear();
135  const extensions::CommandMap* commands =
136      CommandsInfo::GetNamedCommands(extension);
137  if (!commands)
138    return false;
139
140  extensions::CommandMap::const_iterator iter = commands->begin();
141  for (; iter != commands->end(); ++iter) {
142    ui::Accelerator shortcut_assigned =
143        FindShortcutForCommand(extension_id, iter->second.command_name());
144
145    if (type == ACTIVE_ONLY && shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
146      continue;
147
148    extensions::Command command = iter->second;
149    if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
150      command.set_accelerator(shortcut_assigned);
151
152    (*command_map)[iter->second.command_name()] = command;
153  }
154
155  return true;
156}
157
158bool CommandService::AddKeybindingPref(
159    const ui::Accelerator& accelerator,
160    std::string extension_id,
161    std::string command_name,
162    bool allow_overrides) {
163  if (accelerator.key_code() == ui::VKEY_UNKNOWN)
164    return false;
165
166  DictionaryPrefUpdate updater(profile_->GetPrefs(),
167                               prefs::kExtensionCommands);
168  base::DictionaryValue* bindings = updater.Get();
169
170  std::string key = GetPlatformKeybindingKeyForAccelerator(accelerator);
171
172  if (!allow_overrides && bindings->HasKey(key))
173    return false;  // Already taken.
174
175  base::DictionaryValue* keybinding = new base::DictionaryValue();
176  keybinding->SetString(kExtension, extension_id);
177  keybinding->SetString(kCommandName, command_name);
178
179  bindings->Set(key, keybinding);
180
181  std::pair<const std::string, const std::string> details =
182      std::make_pair(extension_id, command_name);
183  content::NotificationService::current()->Notify(
184      chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED,
185      content::Source<Profile>(profile_),
186      content::Details<
187          std::pair<const std::string, const std::string> >(&details));
188
189  return true;
190}
191
192void CommandService::Observe(
193    int type,
194    const content::NotificationSource& source,
195    const content::NotificationDetails& details) {
196  switch (type) {
197    case chrome::NOTIFICATION_EXTENSION_INSTALLED:
198      AssignInitialKeybindings(
199          content::Details<const InstalledExtensionInfo>(details)->extension);
200      break;
201    case chrome::NOTIFICATION_EXTENSION_UNINSTALLED:
202      RemoveKeybindingPrefs(
203          content::Details<const Extension>(details)->id(),
204          std::string());
205      break;
206    default:
207      NOTREACHED();
208      break;
209  }
210}
211
212void CommandService::UpdateKeybindingPrefs(const std::string& extension_id,
213                                           const std::string& command_name,
214                                           const std::string& keystroke) {
215  // The extension command might be assigned another shortcut. Remove that
216  // shortcut before proceeding.
217  RemoveKeybindingPrefs(extension_id, command_name);
218
219  ui::Accelerator accelerator =
220      Command::StringToAccelerator(keystroke, command_name);
221  AddKeybindingPref(accelerator, extension_id, command_name, true);
222}
223
224ui::Accelerator CommandService::FindShortcutForCommand(
225    const std::string& extension_id, const std::string& command) {
226  const base::DictionaryValue* bindings =
227      profile_->GetPrefs()->GetDictionary(prefs::kExtensionCommands);
228  for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
229       it.Advance()) {
230    const base::DictionaryValue* item = NULL;
231    it.value().GetAsDictionary(&item);
232
233    std::string extension;
234    item->GetString(kExtension, &extension);
235    if (extension != extension_id)
236      continue;
237    std::string command_name;
238    item->GetString(kCommandName, &command_name);
239    if (command != command_name)
240      continue;
241
242    std::string shortcut = it.key();
243    if (StartsWithASCII(shortcut, Command::CommandPlatform() + ":", true))
244      shortcut = shortcut.substr(Command::CommandPlatform().length() + 1);
245
246    return Command::StringToAccelerator(shortcut, command_name);
247  }
248
249  return ui::Accelerator();
250}
251
252void CommandService::AssignInitialKeybindings(const Extension* extension) {
253  const extensions::CommandMap* commands =
254      CommandsInfo::GetNamedCommands(extension);
255  if (!commands)
256    return;
257
258  ExtensionService* extension_service =
259      ExtensionSystem::Get(profile_)->extension_service();
260  ExtensionPrefs* extension_prefs = extension_service->extension_prefs();
261  if (InitialBindingsHaveBeenAssigned(extension_prefs, extension->id()))
262    return;
263  SetInitialBindingsHaveBeenAssigned(extension_prefs, extension->id());
264
265  extensions::CommandMap::const_iterator iter = commands->begin();
266  for (; iter != commands->end(); ++iter) {
267    if (!chrome::IsChromeAccelerator(
268        iter->second.accelerator(), profile_)) {
269      AddKeybindingPref(iter->second.accelerator(),
270                        extension->id(),
271                        iter->second.command_name(),
272                        false);  // Overwriting not allowed.
273    }
274  }
275
276  const extensions::Command* browser_action_command =
277      CommandsInfo::GetBrowserActionCommand(extension);
278  if (browser_action_command) {
279    if (!chrome::IsChromeAccelerator(
280        browser_action_command->accelerator(), profile_)) {
281      AddKeybindingPref(browser_action_command->accelerator(),
282                        extension->id(),
283                        browser_action_command->command_name(),
284                        false);  // Overwriting not allowed.
285    }
286  }
287
288  const extensions::Command* page_action_command =
289      CommandsInfo::GetPageActionCommand(extension);
290  if (page_action_command) {
291    if (!chrome::IsChromeAccelerator(
292        page_action_command->accelerator(), profile_)) {
293      AddKeybindingPref(page_action_command->accelerator(),
294                        extension->id(),
295                        page_action_command->command_name(),
296                        false);  // Overwriting not allowed.
297    }
298  }
299
300  const extensions::Command* script_badge_command =
301      CommandsInfo::GetScriptBadgeCommand(extension);
302  if (script_badge_command) {
303    if (!chrome::IsChromeAccelerator(
304        script_badge_command->accelerator(), profile_)) {
305      AddKeybindingPref(script_badge_command->accelerator(),
306                        extension->id(),
307                        script_badge_command->command_name(),
308                        false);  // Overwriting not allowed.
309    }
310  }
311}
312
313void CommandService::RemoveKeybindingPrefs(const std::string& extension_id,
314                                           const std::string& command_name) {
315  DictionaryPrefUpdate updater(profile_->GetPrefs(),
316                               prefs::kExtensionCommands);
317  base::DictionaryValue* bindings = updater.Get();
318
319  typedef std::vector<std::string> KeysToRemove;
320  KeysToRemove keys_to_remove;
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
329    if (extension == extension_id) {
330      // If |command_name| is specified, delete only that command. Otherwise,
331      // delete all commands.
332      if (!command_name.empty()) {
333        std::string command;
334        item->GetString(kCommandName, &command);
335        if (command_name != command)
336          continue;
337      }
338
339      keys_to_remove.push_back(it.key());
340    }
341  }
342
343  for (KeysToRemove::const_iterator it = keys_to_remove.begin();
344       it != keys_to_remove.end(); ++it) {
345    std::string key = *it;
346    bindings->Remove(key, NULL);
347
348    std::pair<const std::string, const std::string> details =
349        std::make_pair(extension_id, command_name);
350    content::NotificationService::current()->Notify(
351        chrome::NOTIFICATION_EXTENSION_COMMAND_REMOVED,
352        content::Source<Profile>(profile_),
353        content::Details<
354            std::pair<const std::string, const std::string> >(&details));
355  }
356}
357
358bool CommandService::GetExtensionActionCommand(
359    const std::string& extension_id,
360    QueryType query_type,
361    extensions::Command* command,
362    bool* active,
363    ExtensionActionType action_type) {
364  ExtensionService* service =
365      ExtensionSystem::Get(profile_)->extension_service();
366  if (!service)
367    return false;  // Can happen in tests.
368  const ExtensionSet* extensions = service->extensions();
369  const Extension* extension = extensions->GetByID(extension_id);
370  CHECK(extension);
371
372  if (active)
373    *active = false;
374
375  const extensions::Command* requested_command = NULL;
376  switch (action_type) {
377    case BROWSER_ACTION:
378      requested_command = CommandsInfo::GetBrowserActionCommand(extension);
379      break;
380    case PAGE_ACTION:
381      requested_command = CommandsInfo::GetPageActionCommand(extension);
382      break;
383    case SCRIPT_BADGE:
384      requested_command = CommandsInfo::GetScriptBadgeCommand(extension);
385      break;
386  }
387  if (!requested_command)
388    return false;
389
390  ui::Accelerator shortcut_assigned =
391      FindShortcutForCommand(extension_id, requested_command->command_name());
392
393  if (active)
394    *active = (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN);
395
396  if (query_type == ACTIVE_ONLY &&
397      shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
398    return false;
399
400  *command = *requested_command;
401  if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
402    command->set_accelerator(shortcut_assigned);
403
404  return true;
405}
406
407}  // namespace extensions
408