command_service.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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/chrome_notification_types.h"
16#include "chrome/browser/extensions/api/commands/commands.h"
17#include "chrome/browser/extensions/extension_commands_global_registry.h"
18#include "chrome/browser/extensions/extension_keybinding_registry.h"
19#include "chrome/browser/profiles/profile.h"
20#include "chrome/browser/ui/accelerator_utils.h"
21#include "chrome/common/extensions/api/commands/commands_handler.h"
22#include "chrome/common/extensions/manifest_handlers/ui_overrides_handler.h"
23#include "chrome/common/pref_names.h"
24#include "components/pref_registry/pref_registry_syncable.h"
25#include "content/public/browser/notification_details.h"
26#include "content/public/browser/notification_service.h"
27#include "extensions/browser/extension_function_registry.h"
28#include "extensions/browser/extension_prefs.h"
29#include "extensions/browser/extension_registry.h"
30#include "extensions/browser/extension_system.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      chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED,
290      content::Source<Profile>(profile_),
291      content::Details<
292          std::pair<const std::string, const std::string> >(&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  RemoveKeybindingPrefs(extension->id(), std::string());
310}
311
312void CommandService::UpdateKeybindingPrefs(const std::string& extension_id,
313                                           const std::string& command_name,
314                                           const std::string& keystroke) {
315  Command command = FindCommandByName(extension_id, command_name);
316
317  // The extension command might be assigned another shortcut. Remove that
318  // shortcut before proceeding.
319  RemoveKeybindingPrefs(extension_id, command_name);
320
321  ui::Accelerator accelerator =
322      Command::StringToAccelerator(keystroke, command_name);
323  AddKeybindingPref(accelerator, extension_id, command_name,
324                    true, command.global());
325}
326
327bool CommandService::SetScope(const std::string& extension_id,
328                              const std::string& command_name,
329                              bool global) {
330  Command command = FindCommandByName(extension_id, command_name);
331  if (global == command.global())
332    return false;
333
334  // Pre-existing shortcuts must be removed before proceeding because the
335  // handlers for global and non-global extensions are not one and the same.
336  RemoveKeybindingPrefs(extension_id, command_name);
337  AddKeybindingPref(command.accelerator(), extension_id,
338                    command_name, true, global);
339  return true;
340}
341
342Command CommandService::FindCommandByName(const std::string& extension_id,
343                                          const std::string& command) const {
344  const base::DictionaryValue* bindings =
345      profile_->GetPrefs()->GetDictionary(prefs::kExtensionCommands);
346  for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
347       it.Advance()) {
348    const base::DictionaryValue* item = NULL;
349    it.value().GetAsDictionary(&item);
350
351    std::string extension;
352    item->GetString(kExtension, &extension);
353    if (extension != extension_id)
354      continue;
355    std::string command_name;
356    item->GetString(kCommandName, &command_name);
357    if (command != command_name)
358      continue;
359    // Format stored in Preferences is: "Platform:Shortcut[:ExtensionId]".
360    std::string shortcut = it.key();
361    if (!IsForCurrentPlatform(shortcut))
362      continue;
363    bool global = false;
364    item->GetBoolean(kGlobal, &global);
365
366    std::vector<std::string> tokens;
367    base::SplitString(shortcut, ':', &tokens);
368    CHECK(tokens.size() >= 2);
369    shortcut = tokens[1];
370
371    return Command(command_name, base::string16(), shortcut, global);
372  }
373
374  return Command();
375}
376
377bool CommandService::GetBoundExtensionCommand(
378    const std::string& extension_id,
379    const ui::Accelerator& accelerator,
380    Command* command,
381    ExtensionCommandType* command_type) const {
382  const Extension* extension =
383      ExtensionRegistry::Get(profile_)
384          ->GetExtensionById(extension_id, ExtensionRegistry::ENABLED);
385  CHECK(extension);
386
387  Command prospective_command;
388  CommandMap command_map;
389  bool active = false;
390  if (GetBrowserActionCommand(extension_id,
391                              CommandService::ACTIVE_ONLY,
392                              &prospective_command,
393                              &active) &&
394      active && accelerator == prospective_command.accelerator()) {
395    if (command)
396      *command = prospective_command;
397    if (command_type)
398      *command_type = BROWSER_ACTION;
399    return true;
400  } else if (GetPageActionCommand(extension_id,
401                                  CommandService::ACTIVE_ONLY,
402                                  &prospective_command,
403                                  &active) &&
404             active && accelerator == prospective_command.accelerator()) {
405    if (command)
406      *command = prospective_command;
407    if (command_type)
408      *command_type = PAGE_ACTION;
409    return true;
410  } else if (GetNamedCommands(extension_id,
411                              CommandService::ACTIVE_ONLY,
412                              CommandService::REGULAR,
413                              &command_map)) {
414    for (CommandMap::const_iterator it = command_map.begin();
415         it != command_map.end();
416         ++it) {
417      if (accelerator == it->second.accelerator()) {
418        if (command)
419          *command = it->second;
420        if (command_type)
421          *command_type = NAMED;
422        return true;
423      }
424    }
425  }
426  return false;
427}
428
429bool CommandService::OverridesBookmarkShortcut(
430    const Extension* extension) const {
431  return RemovesBookmarkShortcut(extension) &&
432      GetBoundExtensionCommand(
433          extension->id(),
434          chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE),
435          NULL,
436          NULL);
437}
438
439void CommandService::UpdateKeybindings(const Extension* extension) {
440  const ExtensionSet& extensions =
441      ExtensionRegistry::Get(profile_)->enabled_extensions();
442  // The extension is not added to the profile by this point on first install,
443  // so don't try to check for existing keybindings.
444  if (extensions.GetByID(extension->id()))
445    RemoveRelinquishedKeybindings(extension);
446  AssignKeybindings(extension);
447  UpdateExtensionSuggestedCommandPrefs(extension);
448  RemoveDefunctExtensionSuggestedCommandPrefs(extension);
449}
450
451void CommandService::RemoveRelinquishedKeybindings(const Extension* extension) {
452  // Remove keybindings if they have been removed by the extension and the user
453  // has not modified them.
454  CommandMap existing_command_map;
455  if (GetNamedCommands(extension->id(),
456                       CommandService::ACTIVE_ONLY,
457                       CommandService::REGULAR,
458                       &existing_command_map)) {
459    const CommandMap* new_command_map =
460        CommandsInfo::GetNamedCommands(extension);
461    for (CommandMap::const_iterator it = existing_command_map.begin();
462         it != existing_command_map.end(); ++it) {
463      std::string command_name = it->first;
464      if (new_command_map->find(command_name) == new_command_map->end() &&
465          !IsCommandShortcutUserModified(extension, command_name)) {
466        RemoveKeybindingPrefs(extension->id(), command_name);
467      }
468    }
469  }
470
471  Command existing_browser_action_command;
472  const Command* new_browser_action_command =
473      CommandsInfo::GetBrowserActionCommand(extension);
474  if (GetBrowserActionCommand(extension->id(),
475                              CommandService::ACTIVE_ONLY,
476                              &existing_browser_action_command,
477                              NULL) &&
478      // The browser action command may be defaulted to an unassigned
479      // accelerator if a browser action is specified by the extension but a
480      // keybinding is not declared. See
481      // CommandsHandler::MaybeSetBrowserActionDefault.
482      (!new_browser_action_command ||
483       new_browser_action_command->accelerator().key_code() ==
484           ui::VKEY_UNKNOWN) &&
485      !IsCommandShortcutUserModified(
486          extension,
487          existing_browser_action_command.command_name())) {
488    RemoveKeybindingPrefs(extension->id(),
489                          existing_browser_action_command.command_name());
490  }
491
492  Command existing_page_action_command;
493  if (GetPageActionCommand(extension->id(),
494                           CommandService::ACTIVE_ONLY,
495                           &existing_page_action_command,
496                           NULL) &&
497      !CommandsInfo::GetPageActionCommand(extension) &&
498      !IsCommandShortcutUserModified(
499          extension,
500          existing_page_action_command.command_name())) {
501    RemoveKeybindingPrefs(extension->id(),
502                          existing_page_action_command.command_name());
503  }
504}
505
506void CommandService::AssignKeybindings(const Extension* extension) {
507  const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
508  if (!commands)
509    return;
510
511  ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
512  // TODO(wittman): remove use of this pref after M37 hits stable.
513  if (!InitialBindingsHaveBeenAssigned(extension_prefs, extension->id()))
514    SetInitialBindingsHaveBeenAssigned(extension_prefs, extension->id());
515
516  for (CommandMap::const_iterator iter = commands->begin();
517       iter != commands->end(); ++iter) {
518    const Command command = iter->second;
519    if (CanAutoAssign(command, extension)) {
520      AddKeybindingPref(command.accelerator(),
521                        extension->id(),
522                        command.command_name(),
523                        false,  // Overwriting not allowed.
524                        command.global());
525    }
526  }
527
528  const Command* browser_action_command =
529      CommandsInfo::GetBrowserActionCommand(extension);
530  if (browser_action_command &&
531      CanAutoAssign(*browser_action_command, extension)) {
532    AddKeybindingPref(browser_action_command->accelerator(),
533                      extension->id(),
534                      browser_action_command->command_name(),
535                      false,   // Overwriting not allowed.
536                      false);  // Not global.
537  }
538
539  const Command* page_action_command =
540      CommandsInfo::GetPageActionCommand(extension);
541  if (page_action_command && CanAutoAssign(*page_action_command, extension)) {
542    AddKeybindingPref(page_action_command->accelerator(),
543                      extension->id(),
544                      page_action_command->command_name(),
545                      false,   // Overwriting not allowed.
546                      false);  // Not global.
547  }
548}
549
550bool CommandService::CanAutoAssign(const Command &command,
551                                   const Extension* extension) {
552  // Media Keys are non-exclusive, so allow auto-assigning them.
553  if (Command::IsMediaKey(command.accelerator()))
554    return true;
555
556  // Extensions are allowed to auto-assign updated keys if the user has not
557  // changed from the previous value.
558  if (IsCommandShortcutUserModified(extension, command.command_name()))
559    return false;
560
561  if (command.global()) {
562    using namespace extensions;
563    if (command.command_name() == manifest_values::kBrowserActionCommandEvent ||
564        command.command_name() == manifest_values::kPageActionCommandEvent)
565      return false;  // Browser and page actions are not global in nature.
566
567    // Global shortcuts are restricted to (Ctrl|Command)+Shift+[0-9].
568#if defined OS_MACOSX
569    if (!command.accelerator().IsCmdDown())
570      return false;
571#else
572    if (!command.accelerator().IsCtrlDown())
573      return false;
574#endif
575    if (!command.accelerator().IsShiftDown())
576      return false;
577    return (command.accelerator().key_code() >= ui::VKEY_0 &&
578            command.accelerator().key_code() <= ui::VKEY_9);
579  } else {
580    // Not a global command, check if Chrome shortcut and whether
581    // we can override it.
582    if (command.accelerator() ==
583        chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE) &&
584        CommandService::RemovesBookmarkShortcut(extension)) {
585      // If this check fails it either means we have an API to override a
586      // key that isn't a ChromeAccelerator (and the API can therefore be
587      // deprecated) or the IsChromeAccelerator isn't consistently
588      // returning true for all accelerators.
589      DCHECK(chrome::IsChromeAccelerator(command.accelerator(), profile_));
590      return true;
591    }
592
593    return !chrome::IsChromeAccelerator(command.accelerator(), profile_);
594  }
595}
596
597void CommandService::UpdateExtensionSuggestedCommandPrefs(
598    const Extension* extension) {
599  scoped_ptr<base::DictionaryValue> suggested_key_prefs(
600      new base::DictionaryValue);
601
602  const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
603  if (commands) {
604    for (CommandMap::const_iterator iter = commands->begin();
605         iter != commands->end(); ++iter) {
606      const Command command = iter->second;
607      scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
608      command_keys->SetString(
609          kSuggestedKey,
610          Command::AcceleratorToString(command.accelerator()));
611      suggested_key_prefs->Set(command.command_name(), command_keys.release());
612    }
613  }
614
615  const Command* browser_action_command =
616      CommandsInfo::GetBrowserActionCommand(extension);
617  // The browser action command may be defaulted to an unassigned accelerator if
618  // a browser action is specified by the extension but a keybinding is not
619  // declared. See CommandsHandler::MaybeSetBrowserActionDefault.
620  if (browser_action_command &&
621      browser_action_command->accelerator().key_code() != ui::VKEY_UNKNOWN) {
622    scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
623    command_keys->SetString(
624        kSuggestedKey,
625        Command::AcceleratorToString(browser_action_command->accelerator()));
626    suggested_key_prefs->Set(browser_action_command->command_name(),
627                             command_keys.release());
628  }
629
630  const Command* page_action_command =
631      CommandsInfo::GetPageActionCommand(extension);
632  if (page_action_command) {
633    scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
634    command_keys->SetString(
635        kSuggestedKey,
636        Command::AcceleratorToString(page_action_command->accelerator()));
637    suggested_key_prefs->Set(page_action_command->command_name(),
638               command_keys.release());
639  }
640
641  // Merge into current prefs, if present.
642  MergeSuggestedKeyPrefs(extension->id(),
643                         ExtensionPrefs::Get(profile_),
644                         suggested_key_prefs.Pass());
645}
646
647void CommandService::RemoveDefunctExtensionSuggestedCommandPrefs(
648    const Extension* extension) {
649  ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
650  const base::DictionaryValue* current_prefs = NULL;
651  extension_prefs->ReadPrefAsDictionary(extension->id(),
652                                        kCommands,
653                                        &current_prefs);
654
655  if (current_prefs) {
656    scoped_ptr<base::DictionaryValue> suggested_key_prefs(
657        current_prefs->DeepCopy());
658    const CommandMap* named_commands =
659        CommandsInfo::GetNamedCommands(extension);
660    const Command* browser_action_command =
661        CommandsInfo::GetBrowserActionCommand(extension);
662    for (base::DictionaryValue::Iterator it(*current_prefs);
663         !it.IsAtEnd(); it.Advance()) {
664      if (it.key() == manifest_values::kBrowserActionCommandEvent) {
665        // The browser action command may be defaulted to an unassigned
666        // accelerator if a browser action is specified by the extension but a
667        // keybinding is not declared. See
668        // CommandsHandler::MaybeSetBrowserActionDefault.
669        if (!browser_action_command ||
670            browser_action_command->accelerator().key_code() ==
671                ui::VKEY_UNKNOWN) {
672          suggested_key_prefs->Remove(it.key(), NULL);
673        }
674      } else if (it.key() == manifest_values::kPageActionCommandEvent) {
675        if (!CommandsInfo::GetPageActionCommand(extension))
676          suggested_key_prefs->Remove(it.key(), NULL);
677      } else if (named_commands) {
678        if (named_commands->find(it.key()) == named_commands->end())
679          suggested_key_prefs->Remove(it.key(), NULL);
680      }
681    }
682
683    extension_prefs->UpdateExtensionPref(extension->id(),
684                                         kCommands,
685                                         suggested_key_prefs.release());
686  }
687}
688
689bool CommandService::IsCommandShortcutUserModified(
690    const Extension* extension,
691    const std::string& command_name) {
692  // Get the previous suggested key, if any.
693  ui::Accelerator suggested_key;
694  bool suggested_key_was_assigned = false;
695  ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
696  const base::DictionaryValue* commands_prefs = NULL;
697  const base::DictionaryValue* suggested_key_prefs = NULL;
698  if (extension_prefs->ReadPrefAsDictionary(extension->id(),
699                                            kCommands,
700                                            &commands_prefs) &&
701      commands_prefs->GetDictionary(command_name, &suggested_key_prefs)) {
702    std::string suggested_key_string;
703    if (suggested_key_prefs->GetString(kSuggestedKey, &suggested_key_string)) {
704      suggested_key = Command::StringToAccelerator(suggested_key_string,
705                                                   command_name);
706    }
707
708    suggested_key_prefs->GetBoolean(kSuggestedKeyWasAssigned,
709                                    &suggested_key_was_assigned);
710  }
711
712  // Get the active shortcut from the prefs, if any.
713  Command active_command = FindCommandByName(extension->id(), command_name);
714
715  return suggested_key_was_assigned ?
716      active_command.accelerator() != suggested_key :
717      active_command.accelerator().key_code() != ui::VKEY_UNKNOWN;
718}
719
720bool CommandService::IsKeybindingChanging(const Extension* extension,
721                                          const std::string& command_name) {
722  // Get the new assigned command, if any.
723  Command new_command;
724  if (command_name == manifest_values::kBrowserActionCommandEvent) {
725    new_command = *CommandsInfo::GetBrowserActionCommand(extension);
726  } else if (command_name == manifest_values::kPageActionCommandEvent) {
727    new_command = *CommandsInfo::GetPageActionCommand(extension);
728  } else {  // This is a named command.
729    const CommandMap* named_commands =
730        CommandsInfo::GetNamedCommands(extension);
731    if (named_commands) {
732      CommandMap::const_iterator loc = named_commands->find(command_name);
733      if (loc != named_commands->end())
734        new_command = loc->second;
735    }
736  }
737
738  return Command::StringToAccelerator(
739      GetSuggestedKeyPref(extension, command_name), command_name) !=
740      new_command.accelerator();
741}
742
743std::string CommandService::GetSuggestedKeyPref(
744    const Extension* extension,
745    const std::string& command_name) {
746  // Get the previous suggested key, if any.
747  ui::Accelerator suggested_key;
748  ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
749  const base::DictionaryValue* commands_prefs = NULL;
750  if (extension_prefs->ReadPrefAsDictionary(extension->id(),
751                                            kCommands,
752                                            &commands_prefs)) {
753    const base::DictionaryValue* suggested_key_prefs = NULL;
754    std::string suggested_key;
755    if (commands_prefs->GetDictionary(command_name, &suggested_key_prefs) &&
756        suggested_key_prefs->GetString(kSuggestedKey, &suggested_key)) {
757      return suggested_key;
758    }
759  }
760
761  return std::string();
762}
763
764void CommandService::RemoveKeybindingPrefs(const std::string& extension_id,
765                                           const std::string& command_name) {
766  DictionaryPrefUpdate updater(profile_->GetPrefs(),
767                               prefs::kExtensionCommands);
768  base::DictionaryValue* bindings = updater.Get();
769
770  typedef std::vector<std::string> KeysToRemove;
771  KeysToRemove keys_to_remove;
772  for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
773       it.Advance()) {
774    // Removal of keybinding preference should be limited to current platform.
775    if (!IsForCurrentPlatform(it.key()))
776      continue;
777
778    const base::DictionaryValue* item = NULL;
779    it.value().GetAsDictionary(&item);
780
781    std::string extension;
782    item->GetString(kExtension, &extension);
783
784    if (extension == extension_id) {
785      // If |command_name| is specified, delete only that command. Otherwise,
786      // delete all commands.
787      if (!command_name.empty()) {
788        std::string command;
789        item->GetString(kCommandName, &command);
790        if (command_name != command)
791          continue;
792      }
793
794      keys_to_remove.push_back(it.key());
795    }
796  }
797
798  for (KeysToRemove::const_iterator it = keys_to_remove.begin();
799       it != keys_to_remove.end(); ++it) {
800    std::string key = *it;
801    bindings->Remove(key, NULL);
802
803    std::pair<const std::string, const std::string> details =
804        std::make_pair(extension_id, command_name);
805    content::NotificationService::current()->Notify(
806        chrome::NOTIFICATION_EXTENSION_COMMAND_REMOVED,
807        content::Source<Profile>(profile_),
808        content::Details<
809            std::pair<const std::string, const std::string> >(&details));
810  }
811}
812
813bool CommandService::GetExtensionActionCommand(
814    const std::string& extension_id,
815    QueryType query_type,
816    Command* command,
817    bool* active,
818    ExtensionCommandType action_type) const {
819  const ExtensionSet& extensions =
820      ExtensionRegistry::Get(profile_)->enabled_extensions();
821  const Extension* extension = extensions.GetByID(extension_id);
822  CHECK(extension);
823
824  if (active)
825    *active = false;
826
827  const Command* requested_command = NULL;
828  switch (action_type) {
829    case BROWSER_ACTION:
830      requested_command = CommandsInfo::GetBrowserActionCommand(extension);
831      break;
832    case PAGE_ACTION:
833      requested_command = CommandsInfo::GetPageActionCommand(extension);
834      break;
835    case NAMED:
836      NOTREACHED();
837      return false;
838  }
839  if (!requested_command)
840    return false;
841
842  // Look up to see if the user has overridden how the command should work.
843  Command saved_command =
844      FindCommandByName(extension_id, requested_command->command_name());
845  ui::Accelerator shortcut_assigned = saved_command.accelerator();
846
847  if (active)
848    *active = (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN);
849
850  if (query_type == ACTIVE_ONLY &&
851      shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
852    return false;
853
854  *command = *requested_command;
855  if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
856    command->set_accelerator(shortcut_assigned);
857
858  return true;
859}
860
861template <>
862void
863BrowserContextKeyedAPIFactory<CommandService>::DeclareFactoryDependencies() {
864  DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance());
865}
866
867}  // namespace extensions
868