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