command_service.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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/extensions/api/commands/commands.h"
16#include "chrome/browser/extensions/extension_commands_global_registry.h"
17#include "chrome/browser/extensions/extension_keybinding_registry.h"
18#include "chrome/browser/profiles/profile.h"
19#include "chrome/browser/ui/accelerator_utils.h"
20#include "chrome/common/extensions/api/commands/commands_handler.h"
21#include "chrome/common/extensions/manifest_handlers/ui_overrides_handler.h"
22#include "chrome/common/pref_names.h"
23#include "components/pref_registry/pref_registry_syncable.h"
24#include "content/public/browser/notification_details.h"
25#include "content/public/browser/notification_service.h"
26#include "extensions/browser/extension_function_registry.h"
27#include "extensions/browser/extension_prefs.h"
28#include "extensions/browser/extension_registry.h"
29#include "extensions/browser/extension_system.h"
30#include "extensions/browser/notification_types.h"
31#include "extensions/common/feature_switch.h"
32#include "extensions/common/manifest_constants.h"
33#include "extensions/common/permissions/permissions_data.h"
34
35namespace extensions {
36namespace {
37
38const char kExtension[] = "extension";
39const char kCommandName[] = "command_name";
40const char kGlobal[] = "global";
41
42// A preference that stores keybinding state associated with extension commands.
43const char kCommands[] = "commands";
44
45// Preference key name for saving the extension-suggested key.
46const char kSuggestedKey[] = "suggested_key";
47
48// Preference key name for saving whether the extension-suggested key was
49// actually assigned.
50const char kSuggestedKeyWasAssigned[] = "was_assigned";
51
52// A preference that indicates that the initial keybindings for the given
53// extension have been set.
54const char kInitialBindingsHaveBeenAssigned[] = "initial_keybindings_set";
55
56std::string GetPlatformKeybindingKeyForAccelerator(
57    const ui::Accelerator& accelerator, const std::string extension_id) {
58  std::string key = Command::CommandPlatform() + ":" +
59                    Command::AcceleratorToString(accelerator);
60
61  // Media keys have a 1-to-many relationship with targets, unlike regular
62  // shortcut (1-to-1 relationship). That means two or more extensions can
63  // register for the same media key so the extension ID needs to be added to
64  // the key to make sure the key is unique.
65  if (Command::IsMediaKey(accelerator))
66    key += ":" + extension_id;
67
68  return key;
69}
70
71bool IsForCurrentPlatform(const std::string& key) {
72  return StartsWithASCII(key, Command::CommandPlatform() + ":", true);
73}
74
75void SetInitialBindingsHaveBeenAssigned(
76    ExtensionPrefs* prefs, const std::string& extension_id) {
77  prefs->UpdateExtensionPref(extension_id, kInitialBindingsHaveBeenAssigned,
78                             new base::FundamentalValue(true));
79}
80
81bool InitialBindingsHaveBeenAssigned(
82    const ExtensionPrefs* prefs, const std::string& extension_id) {
83  bool assigned = false;
84  if (!prefs || !prefs->ReadPrefAsBoolean(extension_id,
85                                          kInitialBindingsHaveBeenAssigned,
86                                          &assigned))
87    return false;
88
89  return assigned;
90}
91
92// Merge |suggested_key_prefs| into the saved preferences for the extension. We
93// merge rather than overwrite to preserve existing was_assigned preferences.
94void MergeSuggestedKeyPrefs(
95    const std::string& extension_id,
96    ExtensionPrefs* extension_prefs,
97    scoped_ptr<base::DictionaryValue> suggested_key_prefs) {
98  const base::DictionaryValue* current_prefs;
99  if (extension_prefs->ReadPrefAsDictionary(extension_id,
100                                            kCommands,
101                                            &current_prefs)) {
102    scoped_ptr<base::DictionaryValue> new_prefs(current_prefs->DeepCopy());
103    new_prefs->MergeDictionary(suggested_key_prefs.get());
104    suggested_key_prefs.reset(new_prefs.release());
105  }
106
107  extension_prefs->UpdateExtensionPref(extension_id,
108                                       kCommands,
109                                       suggested_key_prefs.release());
110}
111
112}  // namespace
113
114// static
115void CommandService::RegisterProfilePrefs(
116    user_prefs::PrefRegistrySyncable* registry) {
117  registry->RegisterDictionaryPref(
118      prefs::kExtensionCommands,
119      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
120}
121
122CommandService::CommandService(content::BrowserContext* context)
123    : profile_(Profile::FromBrowserContext(context)),
124      extension_registry_observer_(this) {
125  ExtensionFunctionRegistry::GetInstance()->
126      RegisterFunction<GetAllCommandsFunction>();
127
128  extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
129}
130
131CommandService::~CommandService() {
132}
133
134static base::LazyInstance<BrowserContextKeyedAPIFactory<CommandService> >
135    g_factory = LAZY_INSTANCE_INITIALIZER;
136
137// static
138BrowserContextKeyedAPIFactory<CommandService>*
139CommandService::GetFactoryInstance() {
140  return g_factory.Pointer();
141}
142
143// static
144CommandService* CommandService::Get(content::BrowserContext* context) {
145  return BrowserContextKeyedAPIFactory<CommandService>::Get(context);
146}
147
148// static
149bool CommandService::RemovesBookmarkShortcut(const Extension* extension) {
150  return UIOverrides::RemovesBookmarkShortcut(extension) &&
151      (extension->permissions_data()->HasAPIPermission(
152          APIPermission::kBookmarkManagerPrivate) ||
153       FeatureSwitch::enable_override_bookmarks_ui()->IsEnabled());
154}
155
156// static
157bool CommandService::RemovesBookmarkOpenPagesShortcut(
158    const Extension* extension) {
159  return UIOverrides::RemovesBookmarkOpenPagesShortcut(extension) &&
160      (extension->permissions_data()->HasAPIPermission(
161          APIPermission::kBookmarkManagerPrivate) ||
162       FeatureSwitch::enable_override_bookmarks_ui()->IsEnabled());
163}
164
165bool CommandService::GetBrowserActionCommand(const std::string& extension_id,
166                                             QueryType type,
167                                             Command* command,
168                                             bool* active) const {
169  return GetExtensionActionCommand(
170      extension_id, type, command, active, BROWSER_ACTION);
171}
172
173bool CommandService::GetPageActionCommand(const std::string& extension_id,
174                                          QueryType type,
175                                          Command* command,
176                                          bool* active) const {
177  return GetExtensionActionCommand(
178      extension_id, type, command, active, PAGE_ACTION);
179}
180
181bool CommandService::GetNamedCommands(const std::string& extension_id,
182                                      QueryType type,
183                                      CommandScope scope,
184                                      CommandMap* command_map) const {
185  const ExtensionSet& extensions =
186      ExtensionRegistry::Get(profile_)->enabled_extensions();
187  const Extension* extension = extensions.GetByID(extension_id);
188  CHECK(extension);
189
190  command_map->clear();
191  const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
192  if (!commands)
193    return false;
194
195  for (CommandMap::const_iterator iter = commands->begin();
196       iter != commands->end(); ++iter) {
197    // Look up to see if the user has overridden how the command should work.
198    Command saved_command =
199        FindCommandByName(extension_id, iter->second.command_name());
200    ui::Accelerator shortcut_assigned = saved_command.accelerator();
201
202    if (type == ACTIVE_ONLY && shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
203      continue;
204
205    Command command = iter->second;
206    if (scope != ANY_SCOPE && ((scope == GLOBAL) != saved_command.global()))
207      continue;
208
209    if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
210      command.set_accelerator(shortcut_assigned);
211    command.set_global(saved_command.global());
212
213    (*command_map)[iter->second.command_name()] = command;
214  }
215
216  return true;
217}
218
219bool CommandService::AddKeybindingPref(
220    const ui::Accelerator& accelerator,
221    std::string extension_id,
222    std::string command_name,
223    bool allow_overrides,
224    bool global) {
225  if (accelerator.key_code() == ui::VKEY_UNKNOWN)
226    return false;
227
228  // Nothing needs to be done if the existing command is the same as the desired
229  // new one.
230  Command existing_command = FindCommandByName(extension_id, command_name);
231  if (existing_command.accelerator() == accelerator &&
232      existing_command.global() == global)
233    return true;
234
235  // Media Keys are allowed to be used by named command only.
236  DCHECK(!Command::IsMediaKey(accelerator) ||
237         (command_name != manifest_values::kPageActionCommandEvent &&
238          command_name != manifest_values::kBrowserActionCommandEvent));
239
240  DictionaryPrefUpdate updater(profile_->GetPrefs(),
241                               prefs::kExtensionCommands);
242  base::DictionaryValue* bindings = updater.Get();
243
244  std::string key = GetPlatformKeybindingKeyForAccelerator(accelerator,
245                                                           extension_id);
246
247  if (bindings->HasKey(key)) {
248    if (!allow_overrides)
249      return false;  // Already taken.
250
251    // If the shortcut has been assigned to another command, it should be
252    // removed before overriding, so that |ExtensionKeybindingRegistry| can get
253    // a chance to do clean-up.
254    const base::DictionaryValue* item = NULL;
255    bindings->GetDictionary(key, &item);
256    std::string old_extension_id;
257    std::string old_command_name;
258    item->GetString(kExtension, &old_extension_id);
259    item->GetString(kCommandName, &old_command_name);
260    RemoveKeybindingPrefs(old_extension_id, old_command_name);
261  }
262
263  // If the command that is taking a new shortcut already has a shortcut, remove
264  // it before assigning the new one.
265  if (existing_command.accelerator().key_code() != ui::VKEY_UNKNOWN)
266    RemoveKeybindingPrefs(extension_id, command_name);
267
268  // Set the keybinding pref.
269  base::DictionaryValue* keybinding = new base::DictionaryValue();
270  keybinding->SetString(kExtension, extension_id);
271  keybinding->SetString(kCommandName, command_name);
272  keybinding->SetBoolean(kGlobal, global);
273
274  bindings->Set(key, keybinding);
275
276  // Set the was_assigned pref for the suggested key.
277  scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
278  command_keys->SetBoolean(kSuggestedKeyWasAssigned, true);
279  scoped_ptr<base::DictionaryValue> suggested_key_prefs(
280      new base::DictionaryValue);
281  suggested_key_prefs->Set(command_name, command_keys.release());
282  MergeSuggestedKeyPrefs(extension_id,
283                         ExtensionPrefs::Get(profile_),
284                         suggested_key_prefs.Pass());
285
286  std::pair<const std::string, const std::string> details =
287      std::make_pair(extension_id, command_name);
288  content::NotificationService::current()->Notify(
289      extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED,
290      content::Source<Profile>(profile_),
291      content::Details<std::pair<const std::string, const std::string> >(
292          &details));
293
294  return true;
295}
296
297void CommandService::OnExtensionWillBeInstalled(
298    content::BrowserContext* browser_context,
299    const Extension* extension,
300    bool is_update,
301    bool from_ephemeral,
302    const std::string& old_name) {
303  UpdateKeybindings(extension);
304}
305
306void CommandService::OnExtensionUninstalled(
307    content::BrowserContext* browser_context,
308    const Extension* extension,
309    extensions::UninstallReason reason) {
310  RemoveKeybindingPrefs(extension->id(), std::string());
311}
312
313void CommandService::UpdateKeybindingPrefs(const std::string& extension_id,
314                                           const std::string& command_name,
315                                           const std::string& keystroke) {
316  Command command = FindCommandByName(extension_id, command_name);
317
318  // The extension command might be assigned another shortcut. Remove that
319  // shortcut before proceeding.
320  RemoveKeybindingPrefs(extension_id, command_name);
321
322  ui::Accelerator accelerator =
323      Command::StringToAccelerator(keystroke, command_name);
324  AddKeybindingPref(accelerator, extension_id, command_name,
325                    true, command.global());
326}
327
328bool CommandService::SetScope(const std::string& extension_id,
329                              const std::string& command_name,
330                              bool global) {
331  Command command = FindCommandByName(extension_id, command_name);
332  if (global == command.global())
333    return false;
334
335  // Pre-existing shortcuts must be removed before proceeding because the
336  // handlers for global and non-global extensions are not one and the same.
337  RemoveKeybindingPrefs(extension_id, command_name);
338  AddKeybindingPref(command.accelerator(), extension_id,
339                    command_name, true, global);
340  return true;
341}
342
343Command CommandService::FindCommandByName(const std::string& extension_id,
344                                          const std::string& command) const {
345  const base::DictionaryValue* bindings =
346      profile_->GetPrefs()->GetDictionary(prefs::kExtensionCommands);
347  for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
348       it.Advance()) {
349    const base::DictionaryValue* item = NULL;
350    it.value().GetAsDictionary(&item);
351
352    std::string extension;
353    item->GetString(kExtension, &extension);
354    if (extension != extension_id)
355      continue;
356    std::string command_name;
357    item->GetString(kCommandName, &command_name);
358    if (command != command_name)
359      continue;
360    // Format stored in Preferences is: "Platform:Shortcut[:ExtensionId]".
361    std::string shortcut = it.key();
362    if (!IsForCurrentPlatform(shortcut))
363      continue;
364    bool global = false;
365    item->GetBoolean(kGlobal, &global);
366
367    std::vector<std::string> tokens;
368    base::SplitString(shortcut, ':', &tokens);
369    CHECK(tokens.size() >= 2);
370    shortcut = tokens[1];
371
372    return Command(command_name, base::string16(), shortcut, global);
373  }
374
375  return Command();
376}
377
378bool CommandService::GetBoundExtensionCommand(
379    const std::string& extension_id,
380    const ui::Accelerator& accelerator,
381    Command* command,
382    ExtensionCommandType* command_type) const {
383  const Extension* extension =
384      ExtensionRegistry::Get(profile_)
385          ->GetExtensionById(extension_id, ExtensionRegistry::ENABLED);
386  CHECK(extension);
387
388  Command prospective_command;
389  CommandMap command_map;
390  bool active = false;
391  if (GetBrowserActionCommand(extension_id,
392                              CommandService::ACTIVE_ONLY,
393                              &prospective_command,
394                              &active) &&
395      active && accelerator == prospective_command.accelerator()) {
396    if (command)
397      *command = prospective_command;
398    if (command_type)
399      *command_type = BROWSER_ACTION;
400    return true;
401  } else if (GetPageActionCommand(extension_id,
402                                  CommandService::ACTIVE_ONLY,
403                                  &prospective_command,
404                                  &active) &&
405             active && accelerator == prospective_command.accelerator()) {
406    if (command)
407      *command = prospective_command;
408    if (command_type)
409      *command_type = PAGE_ACTION;
410    return true;
411  } else if (GetNamedCommands(extension_id,
412                              CommandService::ACTIVE_ONLY,
413                              CommandService::REGULAR,
414                              &command_map)) {
415    for (CommandMap::const_iterator it = command_map.begin();
416         it != command_map.end();
417         ++it) {
418      if (accelerator == it->second.accelerator()) {
419        if (command)
420          *command = it->second;
421        if (command_type)
422          *command_type = NAMED;
423        return true;
424      }
425    }
426  }
427  return false;
428}
429
430bool CommandService::OverridesBookmarkShortcut(
431    const Extension* extension) const {
432  return RemovesBookmarkShortcut(extension) &&
433      GetBoundExtensionCommand(
434          extension->id(),
435          chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE),
436          NULL,
437          NULL);
438}
439
440void CommandService::UpdateKeybindings(const Extension* extension) {
441  const ExtensionSet& extensions =
442      ExtensionRegistry::Get(profile_)->enabled_extensions();
443  // The extension is not added to the profile by this point on first install,
444  // so don't try to check for existing keybindings.
445  if (extensions.GetByID(extension->id()))
446    RemoveRelinquishedKeybindings(extension);
447  AssignKeybindings(extension);
448  UpdateExtensionSuggestedCommandPrefs(extension);
449  RemoveDefunctExtensionSuggestedCommandPrefs(extension);
450}
451
452void CommandService::RemoveRelinquishedKeybindings(const Extension* extension) {
453  // Remove keybindings if they have been removed by the extension and the user
454  // has not modified them.
455  CommandMap existing_command_map;
456  if (GetNamedCommands(extension->id(),
457                       CommandService::ACTIVE_ONLY,
458                       CommandService::REGULAR,
459                       &existing_command_map)) {
460    const CommandMap* new_command_map =
461        CommandsInfo::GetNamedCommands(extension);
462    for (CommandMap::const_iterator it = existing_command_map.begin();
463         it != existing_command_map.end(); ++it) {
464      std::string command_name = it->first;
465      if (new_command_map->find(command_name) == new_command_map->end() &&
466          !IsCommandShortcutUserModified(extension, command_name)) {
467        RemoveKeybindingPrefs(extension->id(), command_name);
468      }
469    }
470  }
471
472  Command existing_browser_action_command;
473  const Command* new_browser_action_command =
474      CommandsInfo::GetBrowserActionCommand(extension);
475  if (GetBrowserActionCommand(extension->id(),
476                              CommandService::ACTIVE_ONLY,
477                              &existing_browser_action_command,
478                              NULL) &&
479      // The browser action command may be defaulted to an unassigned
480      // accelerator if a browser action is specified by the extension but a
481      // keybinding is not declared. See
482      // CommandsHandler::MaybeSetBrowserActionDefault.
483      (!new_browser_action_command ||
484       new_browser_action_command->accelerator().key_code() ==
485           ui::VKEY_UNKNOWN) &&
486      !IsCommandShortcutUserModified(
487          extension,
488          existing_browser_action_command.command_name())) {
489    RemoveKeybindingPrefs(extension->id(),
490                          existing_browser_action_command.command_name());
491  }
492
493  Command existing_page_action_command;
494  if (GetPageActionCommand(extension->id(),
495                           CommandService::ACTIVE_ONLY,
496                           &existing_page_action_command,
497                           NULL) &&
498      !CommandsInfo::GetPageActionCommand(extension) &&
499      !IsCommandShortcutUserModified(
500          extension,
501          existing_page_action_command.command_name())) {
502    RemoveKeybindingPrefs(extension->id(),
503                          existing_page_action_command.command_name());
504  }
505}
506
507void CommandService::AssignKeybindings(const Extension* extension) {
508  const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
509  if (!commands)
510    return;
511
512  ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
513  // TODO(wittman): remove use of this pref after M37 hits stable.
514  if (!InitialBindingsHaveBeenAssigned(extension_prefs, extension->id()))
515    SetInitialBindingsHaveBeenAssigned(extension_prefs, extension->id());
516
517  for (CommandMap::const_iterator iter = commands->begin();
518       iter != commands->end(); ++iter) {
519    const Command command = iter->second;
520    if (CanAutoAssign(command, extension)) {
521      AddKeybindingPref(command.accelerator(),
522                        extension->id(),
523                        command.command_name(),
524                        false,  // Overwriting not allowed.
525                        command.global());
526    }
527  }
528
529  const Command* browser_action_command =
530      CommandsInfo::GetBrowserActionCommand(extension);
531  if (browser_action_command &&
532      CanAutoAssign(*browser_action_command, extension)) {
533    AddKeybindingPref(browser_action_command->accelerator(),
534                      extension->id(),
535                      browser_action_command->command_name(),
536                      false,   // Overwriting not allowed.
537                      false);  // Not global.
538  }
539
540  const Command* page_action_command =
541      CommandsInfo::GetPageActionCommand(extension);
542  if (page_action_command && CanAutoAssign(*page_action_command, extension)) {
543    AddKeybindingPref(page_action_command->accelerator(),
544                      extension->id(),
545                      page_action_command->command_name(),
546                      false,   // Overwriting not allowed.
547                      false);  // Not global.
548  }
549}
550
551bool CommandService::CanAutoAssign(const Command &command,
552                                   const Extension* extension) {
553  // Media Keys are non-exclusive, so allow auto-assigning them.
554  if (Command::IsMediaKey(command.accelerator()))
555    return true;
556
557  // Extensions are allowed to auto-assign updated keys if the user has not
558  // changed from the previous value.
559  if (IsCommandShortcutUserModified(extension, command.command_name()))
560    return false;
561
562  if (command.global()) {
563    using namespace extensions;
564    if (command.command_name() == manifest_values::kBrowserActionCommandEvent ||
565        command.command_name() == manifest_values::kPageActionCommandEvent)
566      return false;  // Browser and page actions are not global in nature.
567
568    // Global shortcuts are restricted to (Ctrl|Command)+Shift+[0-9].
569#if defined OS_MACOSX
570    if (!command.accelerator().IsCmdDown())
571      return false;
572#else
573    if (!command.accelerator().IsCtrlDown())
574      return false;
575#endif
576    if (!command.accelerator().IsShiftDown())
577      return false;
578    return (command.accelerator().key_code() >= ui::VKEY_0 &&
579            command.accelerator().key_code() <= ui::VKEY_9);
580  } else {
581    // Not a global command, check if Chrome shortcut and whether
582    // we can override it.
583    if (command.accelerator() ==
584        chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE) &&
585        CommandService::RemovesBookmarkShortcut(extension)) {
586      // If this check fails it either means we have an API to override a
587      // key that isn't a ChromeAccelerator (and the API can therefore be
588      // deprecated) or the IsChromeAccelerator isn't consistently
589      // returning true for all accelerators.
590      DCHECK(chrome::IsChromeAccelerator(command.accelerator(), profile_));
591      return true;
592    }
593
594    return !chrome::IsChromeAccelerator(command.accelerator(), profile_);
595  }
596}
597
598void CommandService::UpdateExtensionSuggestedCommandPrefs(
599    const Extension* extension) {
600  scoped_ptr<base::DictionaryValue> suggested_key_prefs(
601      new base::DictionaryValue);
602
603  const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
604  if (commands) {
605    for (CommandMap::const_iterator iter = commands->begin();
606         iter != commands->end(); ++iter) {
607      const Command command = iter->second;
608      scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
609      command_keys->SetString(
610          kSuggestedKey,
611          Command::AcceleratorToString(command.accelerator()));
612      suggested_key_prefs->Set(command.command_name(), command_keys.release());
613    }
614  }
615
616  const Command* browser_action_command =
617      CommandsInfo::GetBrowserActionCommand(extension);
618  // The browser action command may be defaulted to an unassigned accelerator if
619  // a browser action is specified by the extension but a keybinding is not
620  // declared. See CommandsHandler::MaybeSetBrowserActionDefault.
621  if (browser_action_command &&
622      browser_action_command->accelerator().key_code() != ui::VKEY_UNKNOWN) {
623    scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
624    command_keys->SetString(
625        kSuggestedKey,
626        Command::AcceleratorToString(browser_action_command->accelerator()));
627    suggested_key_prefs->Set(browser_action_command->command_name(),
628                             command_keys.release());
629  }
630
631  const Command* page_action_command =
632      CommandsInfo::GetPageActionCommand(extension);
633  if (page_action_command) {
634    scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
635    command_keys->SetString(
636        kSuggestedKey,
637        Command::AcceleratorToString(page_action_command->accelerator()));
638    suggested_key_prefs->Set(page_action_command->command_name(),
639               command_keys.release());
640  }
641
642  // Merge into current prefs, if present.
643  MergeSuggestedKeyPrefs(extension->id(),
644                         ExtensionPrefs::Get(profile_),
645                         suggested_key_prefs.Pass());
646}
647
648void CommandService::RemoveDefunctExtensionSuggestedCommandPrefs(
649    const Extension* extension) {
650  ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
651  const base::DictionaryValue* current_prefs = NULL;
652  extension_prefs->ReadPrefAsDictionary(extension->id(),
653                                        kCommands,
654                                        &current_prefs);
655
656  if (current_prefs) {
657    scoped_ptr<base::DictionaryValue> suggested_key_prefs(
658        current_prefs->DeepCopy());
659    const CommandMap* named_commands =
660        CommandsInfo::GetNamedCommands(extension);
661    const Command* browser_action_command =
662        CommandsInfo::GetBrowserActionCommand(extension);
663    for (base::DictionaryValue::Iterator it(*current_prefs);
664         !it.IsAtEnd(); it.Advance()) {
665      if (it.key() == manifest_values::kBrowserActionCommandEvent) {
666        // The browser action command may be defaulted to an unassigned
667        // accelerator if a browser action is specified by the extension but a
668        // keybinding is not declared. See
669        // CommandsHandler::MaybeSetBrowserActionDefault.
670        if (!browser_action_command ||
671            browser_action_command->accelerator().key_code() ==
672                ui::VKEY_UNKNOWN) {
673          suggested_key_prefs->Remove(it.key(), NULL);
674        }
675      } else if (it.key() == manifest_values::kPageActionCommandEvent) {
676        if (!CommandsInfo::GetPageActionCommand(extension))
677          suggested_key_prefs->Remove(it.key(), NULL);
678      } else if (named_commands) {
679        if (named_commands->find(it.key()) == named_commands->end())
680          suggested_key_prefs->Remove(it.key(), NULL);
681      }
682    }
683
684    extension_prefs->UpdateExtensionPref(extension->id(),
685                                         kCommands,
686                                         suggested_key_prefs.release());
687  }
688}
689
690bool CommandService::IsCommandShortcutUserModified(
691    const Extension* extension,
692    const std::string& command_name) {
693  // Get the previous suggested key, if any.
694  ui::Accelerator suggested_key;
695  bool suggested_key_was_assigned = false;
696  ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
697  const base::DictionaryValue* commands_prefs = NULL;
698  const base::DictionaryValue* suggested_key_prefs = NULL;
699  if (extension_prefs->ReadPrefAsDictionary(extension->id(),
700                                            kCommands,
701                                            &commands_prefs) &&
702      commands_prefs->GetDictionary(command_name, &suggested_key_prefs)) {
703    std::string suggested_key_string;
704    if (suggested_key_prefs->GetString(kSuggestedKey, &suggested_key_string)) {
705      suggested_key = Command::StringToAccelerator(suggested_key_string,
706                                                   command_name);
707    }
708
709    suggested_key_prefs->GetBoolean(kSuggestedKeyWasAssigned,
710                                    &suggested_key_was_assigned);
711  }
712
713  // Get the active shortcut from the prefs, if any.
714  Command active_command = FindCommandByName(extension->id(), command_name);
715
716  return suggested_key_was_assigned ?
717      active_command.accelerator() != suggested_key :
718      active_command.accelerator().key_code() != ui::VKEY_UNKNOWN;
719}
720
721bool CommandService::IsKeybindingChanging(const Extension* extension,
722                                          const std::string& command_name) {
723  // Get the new assigned command, if any.
724  Command new_command;
725  if (command_name == manifest_values::kBrowserActionCommandEvent) {
726    new_command = *CommandsInfo::GetBrowserActionCommand(extension);
727  } else if (command_name == manifest_values::kPageActionCommandEvent) {
728    new_command = *CommandsInfo::GetPageActionCommand(extension);
729  } else {  // This is a named command.
730    const CommandMap* named_commands =
731        CommandsInfo::GetNamedCommands(extension);
732    if (named_commands) {
733      CommandMap::const_iterator loc = named_commands->find(command_name);
734      if (loc != named_commands->end())
735        new_command = loc->second;
736    }
737  }
738
739  return Command::StringToAccelerator(
740      GetSuggestedKeyPref(extension, command_name), command_name) !=
741      new_command.accelerator();
742}
743
744std::string CommandService::GetSuggestedKeyPref(
745    const Extension* extension,
746    const std::string& command_name) {
747  // Get the previous suggested key, if any.
748  ui::Accelerator suggested_key;
749  ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
750  const base::DictionaryValue* commands_prefs = NULL;
751  if (extension_prefs->ReadPrefAsDictionary(extension->id(),
752                                            kCommands,
753                                            &commands_prefs)) {
754    const base::DictionaryValue* suggested_key_prefs = NULL;
755    std::string suggested_key;
756    if (commands_prefs->GetDictionary(command_name, &suggested_key_prefs) &&
757        suggested_key_prefs->GetString(kSuggestedKey, &suggested_key)) {
758      return suggested_key;
759    }
760  }
761
762  return std::string();
763}
764
765void CommandService::RemoveKeybindingPrefs(const std::string& extension_id,
766                                           const std::string& command_name) {
767  DictionaryPrefUpdate updater(profile_->GetPrefs(),
768                               prefs::kExtensionCommands);
769  base::DictionaryValue* bindings = updater.Get();
770
771  typedef std::vector<std::string> KeysToRemove;
772  KeysToRemove keys_to_remove;
773  for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
774       it.Advance()) {
775    // Removal of keybinding preference should be limited to current platform.
776    if (!IsForCurrentPlatform(it.key()))
777      continue;
778
779    const base::DictionaryValue* item = NULL;
780    it.value().GetAsDictionary(&item);
781
782    std::string extension;
783    item->GetString(kExtension, &extension);
784
785    if (extension == extension_id) {
786      // If |command_name| is specified, delete only that command. Otherwise,
787      // delete all commands.
788      if (!command_name.empty()) {
789        std::string command;
790        item->GetString(kCommandName, &command);
791        if (command_name != command)
792          continue;
793      }
794
795      keys_to_remove.push_back(it.key());
796    }
797  }
798
799  for (KeysToRemove::const_iterator it = keys_to_remove.begin();
800       it != keys_to_remove.end(); ++it) {
801    std::string key = *it;
802    bindings->Remove(key, NULL);
803
804    std::pair<const std::string, const std::string> details =
805        std::make_pair(extension_id, command_name);
806    content::NotificationService::current()->Notify(
807        extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED,
808        content::Source<Profile>(profile_),
809        content::Details<std::pair<const std::string, const std::string> >(
810            &details));
811  }
812}
813
814bool CommandService::GetExtensionActionCommand(
815    const std::string& extension_id,
816    QueryType query_type,
817    Command* command,
818    bool* active,
819    ExtensionCommandType action_type) const {
820  const ExtensionSet& extensions =
821      ExtensionRegistry::Get(profile_)->enabled_extensions();
822  const Extension* extension = extensions.GetByID(extension_id);
823  CHECK(extension);
824
825  if (active)
826    *active = false;
827
828  const Command* requested_command = NULL;
829  switch (action_type) {
830    case BROWSER_ACTION:
831      requested_command = CommandsInfo::GetBrowserActionCommand(extension);
832      break;
833    case PAGE_ACTION:
834      requested_command = CommandsInfo::GetPageActionCommand(extension);
835      break;
836    case NAMED:
837      NOTREACHED();
838      return false;
839  }
840  if (!requested_command)
841    return false;
842
843  // Look up to see if the user has overridden how the command should work.
844  Command saved_command =
845      FindCommandByName(extension_id, requested_command->command_name());
846  ui::Accelerator shortcut_assigned = saved_command.accelerator();
847
848  if (active)
849    *active = (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN);
850
851  if (query_type == ACTIVE_ONLY &&
852      shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
853    return false;
854
855  *command = *requested_command;
856  if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
857    command->set_accelerator(shortcut_assigned);
858
859  return true;
860}
861
862template <>
863void
864BrowserContextKeyedAPIFactory<CommandService>::DeclareFactoryDependencies() {
865  DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance());
866}
867
868}  // namespace extensions
869