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