command_service.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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/string_util.h"
9#include "base/strings/utf_string_conversions.h"
10#include "chrome/browser/extensions/api/commands/commands.h"
11#include "chrome/browser/extensions/extension_function_registry.h"
12#include "chrome/browser/extensions/extension_keybinding_registry.h"
13#include "chrome/browser/extensions/extension_service.h"
14#include "chrome/browser/extensions/extension_system.h"
15#include "chrome/browser/prefs/scoped_user_pref_update.h"
16#include "chrome/browser/profiles/profile.h"
17#include "chrome/browser/ui/accelerator_utils.h"
18#include "chrome/common/chrome_notification_types.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                             Value::CreateBooleanValue(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::RegisterUserPrefs(
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  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  DictionaryValue* keybinding = new 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 = Command::StringToAccelerator(keystroke);
220  AddKeybindingPref(accelerator, extension_id, command_name, true);
221}
222
223ui::Accelerator CommandService::FindShortcutForCommand(
224    const std::string& extension_id, const std::string& command) {
225  const DictionaryValue* bindings =
226      profile_->GetPrefs()->GetDictionary(prefs::kExtensionCommands);
227  for (DictionaryValue::Iterator it(*bindings); !it.IsAtEnd(); it.Advance()) {
228    const DictionaryValue* item = NULL;
229    it.value().GetAsDictionary(&item);
230
231    std::string extension;
232    item->GetString(kExtension, &extension);
233    if (extension != extension_id)
234      continue;
235    std::string command_name;
236    item->GetString(kCommandName, &command_name);
237    if (command != command_name)
238      continue;
239
240    std::string shortcut = it.key();
241    if (StartsWithASCII(shortcut, Command::CommandPlatform() + ":", true))
242      shortcut = shortcut.substr(Command::CommandPlatform().length() + 1);
243
244    return Command::StringToAccelerator(shortcut);
245  }
246
247  return ui::Accelerator();
248}
249
250void CommandService::AssignInitialKeybindings(const Extension* extension) {
251  const extensions::CommandMap* commands =
252      CommandsInfo::GetNamedCommands(extension);
253  if (!commands)
254    return;
255
256  ExtensionService* extension_service =
257      ExtensionSystem::Get(profile_)->extension_service();
258  ExtensionPrefs* extension_prefs = extension_service->extension_prefs();
259  if (InitialBindingsHaveBeenAssigned(extension_prefs, extension->id()))
260    return;
261  SetInitialBindingsHaveBeenAssigned(extension_prefs, extension->id());
262
263  extensions::CommandMap::const_iterator iter = commands->begin();
264  for (; iter != commands->end(); ++iter) {
265    if (!chrome::IsChromeAccelerator(
266        iter->second.accelerator(), profile_)) {
267      AddKeybindingPref(iter->second.accelerator(),
268                        extension->id(),
269                        iter->second.command_name(),
270                        false);  // Overwriting not allowed.
271    }
272  }
273
274  const extensions::Command* browser_action_command =
275      CommandsInfo::GetBrowserActionCommand(extension);
276  if (browser_action_command) {
277    if (!chrome::IsChromeAccelerator(
278        browser_action_command->accelerator(), profile_)) {
279      AddKeybindingPref(browser_action_command->accelerator(),
280                        extension->id(),
281                        browser_action_command->command_name(),
282                        false);  // Overwriting not allowed.
283    }
284  }
285
286  const extensions::Command* page_action_command =
287      CommandsInfo::GetPageActionCommand(extension);
288  if (page_action_command) {
289    if (!chrome::IsChromeAccelerator(
290        page_action_command->accelerator(), profile_)) {
291      AddKeybindingPref(page_action_command->accelerator(),
292                        extension->id(),
293                        page_action_command->command_name(),
294                        false);  // Overwriting not allowed.
295    }
296  }
297
298  const extensions::Command* script_badge_command =
299      CommandsInfo::GetScriptBadgeCommand(extension);
300  if (script_badge_command) {
301    if (!chrome::IsChromeAccelerator(
302        script_badge_command->accelerator(), profile_)) {
303      AddKeybindingPref(script_badge_command->accelerator(),
304                        extension->id(),
305                        script_badge_command->command_name(),
306                        false);  // Overwriting not allowed.
307    }
308  }
309}
310
311void CommandService::RemoveKeybindingPrefs(const std::string& extension_id,
312                                           const std::string& command_name) {
313  DictionaryPrefUpdate updater(profile_->GetPrefs(),
314                               prefs::kExtensionCommands);
315  DictionaryValue* bindings = updater.Get();
316
317  typedef std::vector<std::string> KeysToRemove;
318  KeysToRemove keys_to_remove;
319  for (DictionaryValue::Iterator it(*bindings); !it.IsAtEnd(); it.Advance()) {
320    const DictionaryValue* item = NULL;
321    it.value().GetAsDictionary(&item);
322
323    std::string extension;
324    item->GetString(kExtension, &extension);
325
326    if (extension == extension_id) {
327      // If |command_name| is specified, delete only that command. Otherwise,
328      // delete all commands.
329      if (!command_name.empty()) {
330        std::string command;
331        item->GetString(kCommandName, &command);
332        if (command_name != command)
333          continue;
334      }
335
336      keys_to_remove.push_back(it.key());
337    }
338  }
339
340  for (KeysToRemove::const_iterator it = keys_to_remove.begin();
341       it != keys_to_remove.end(); ++it) {
342    std::string key = *it;
343    bindings->Remove(key, NULL);
344
345    std::pair<const std::string, const std::string> details =
346        std::make_pair(extension_id, command_name);
347    content::NotificationService::current()->Notify(
348        chrome::NOTIFICATION_EXTENSION_COMMAND_REMOVED,
349        content::Source<Profile>(profile_),
350        content::Details<
351            std::pair<const std::string, const std::string> >(&details));
352  }
353}
354
355bool CommandService::GetExtensionActionCommand(
356    const std::string& extension_id,
357    QueryType query_type,
358    extensions::Command* command,
359    bool* active,
360    ExtensionActionType action_type) {
361  ExtensionService* service =
362      ExtensionSystem::Get(profile_)->extension_service();
363  if (!service)
364    return false;  // Can happen in tests.
365  const ExtensionSet* extensions = service->extensions();
366  const Extension* extension = extensions->GetByID(extension_id);
367  CHECK(extension);
368
369  if (active)
370    *active = false;
371
372  const extensions::Command* requested_command = NULL;
373  switch (action_type) {
374    case BROWSER_ACTION:
375      requested_command = CommandsInfo::GetBrowserActionCommand(extension);
376      break;
377    case PAGE_ACTION:
378      requested_command = CommandsInfo::GetPageActionCommand(extension);
379      break;
380    case SCRIPT_BADGE:
381      requested_command = CommandsInfo::GetScriptBadgeCommand(extension);
382      break;
383  }
384  if (!requested_command)
385    return false;
386
387  ui::Accelerator shortcut_assigned =
388      FindShortcutForCommand(extension_id, requested_command->command_name());
389
390  if (active)
391    *active = (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN);
392
393  if (query_type == ACTIVE_ONLY &&
394      shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
395    return false;
396
397  *command = *requested_command;
398  if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
399    command->set_accelerator(shortcut_assigned);
400
401  return true;
402}
403
404}  // namespace extensions
405