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    if (extension->permissions_data()->HasAPIPermission(
569            APIPermission::kCommandsAccessibility))
570      return true;
571
572    // Global shortcuts are restricted to (Ctrl|Command)+Shift+[0-9].
573#if defined OS_MACOSX
574    if (!command.accelerator().IsCmdDown())
575      return false;
576#else
577    if (!command.accelerator().IsCtrlDown())
578      return false;
579#endif
580    if (!command.accelerator().IsShiftDown())
581      return false;
582    return (command.accelerator().key_code() >= ui::VKEY_0 &&
583            command.accelerator().key_code() <= ui::VKEY_9);
584  } else {
585    // Not a global command, check if Chrome shortcut and whether
586    // we can override it.
587    if (command.accelerator() ==
588        chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE) &&
589        CommandService::RemovesBookmarkShortcut(extension)) {
590      // If this check fails it either means we have an API to override a
591      // key that isn't a ChromeAccelerator (and the API can therefore be
592      // deprecated) or the IsChromeAccelerator isn't consistently
593      // returning true for all accelerators.
594      DCHECK(chrome::IsChromeAccelerator(command.accelerator(), profile_));
595      return true;
596    }
597
598    return !chrome::IsChromeAccelerator(command.accelerator(), profile_);
599  }
600}
601
602void CommandService::UpdateExtensionSuggestedCommandPrefs(
603    const Extension* extension) {
604  scoped_ptr<base::DictionaryValue> suggested_key_prefs(
605      new base::DictionaryValue);
606
607  const CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
608  if (commands) {
609    for (CommandMap::const_iterator iter = commands->begin();
610         iter != commands->end(); ++iter) {
611      const Command command = iter->second;
612      scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
613      command_keys->SetString(
614          kSuggestedKey,
615          Command::AcceleratorToString(command.accelerator()));
616      suggested_key_prefs->Set(command.command_name(), command_keys.release());
617    }
618  }
619
620  const Command* browser_action_command =
621      CommandsInfo::GetBrowserActionCommand(extension);
622  // The browser action command may be defaulted to an unassigned accelerator if
623  // a browser action is specified by the extension but a keybinding is not
624  // declared. See CommandsHandler::MaybeSetBrowserActionDefault.
625  if (browser_action_command &&
626      browser_action_command->accelerator().key_code() != ui::VKEY_UNKNOWN) {
627    scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
628    command_keys->SetString(
629        kSuggestedKey,
630        Command::AcceleratorToString(browser_action_command->accelerator()));
631    suggested_key_prefs->Set(browser_action_command->command_name(),
632                             command_keys.release());
633  }
634
635  const Command* page_action_command =
636      CommandsInfo::GetPageActionCommand(extension);
637  if (page_action_command) {
638    scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue);
639    command_keys->SetString(
640        kSuggestedKey,
641        Command::AcceleratorToString(page_action_command->accelerator()));
642    suggested_key_prefs->Set(page_action_command->command_name(),
643               command_keys.release());
644  }
645
646  // Merge into current prefs, if present.
647  MergeSuggestedKeyPrefs(extension->id(),
648                         ExtensionPrefs::Get(profile_),
649                         suggested_key_prefs.Pass());
650}
651
652void CommandService::RemoveDefunctExtensionSuggestedCommandPrefs(
653    const Extension* extension) {
654  ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
655  const base::DictionaryValue* current_prefs = NULL;
656  extension_prefs->ReadPrefAsDictionary(extension->id(),
657                                        kCommands,
658                                        &current_prefs);
659
660  if (current_prefs) {
661    scoped_ptr<base::DictionaryValue> suggested_key_prefs(
662        current_prefs->DeepCopy());
663    const CommandMap* named_commands =
664        CommandsInfo::GetNamedCommands(extension);
665    const Command* browser_action_command =
666        CommandsInfo::GetBrowserActionCommand(extension);
667    for (base::DictionaryValue::Iterator it(*current_prefs);
668         !it.IsAtEnd(); it.Advance()) {
669      if (it.key() == manifest_values::kBrowserActionCommandEvent) {
670        // The browser action command may be defaulted to an unassigned
671        // accelerator if a browser action is specified by the extension but a
672        // keybinding is not declared. See
673        // CommandsHandler::MaybeSetBrowserActionDefault.
674        if (!browser_action_command ||
675            browser_action_command->accelerator().key_code() ==
676                ui::VKEY_UNKNOWN) {
677          suggested_key_prefs->Remove(it.key(), NULL);
678        }
679      } else if (it.key() == manifest_values::kPageActionCommandEvent) {
680        if (!CommandsInfo::GetPageActionCommand(extension))
681          suggested_key_prefs->Remove(it.key(), NULL);
682      } else if (named_commands) {
683        if (named_commands->find(it.key()) == named_commands->end())
684          suggested_key_prefs->Remove(it.key(), NULL);
685      }
686    }
687
688    extension_prefs->UpdateExtensionPref(extension->id(),
689                                         kCommands,
690                                         suggested_key_prefs.release());
691  }
692}
693
694bool CommandService::IsCommandShortcutUserModified(
695    const Extension* extension,
696    const std::string& command_name) {
697  // Get the previous suggested key, if any.
698  ui::Accelerator suggested_key;
699  bool suggested_key_was_assigned = false;
700  ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
701  const base::DictionaryValue* commands_prefs = NULL;
702  const base::DictionaryValue* suggested_key_prefs = NULL;
703  if (extension_prefs->ReadPrefAsDictionary(extension->id(),
704                                            kCommands,
705                                            &commands_prefs) &&
706      commands_prefs->GetDictionary(command_name, &suggested_key_prefs)) {
707    std::string suggested_key_string;
708    if (suggested_key_prefs->GetString(kSuggestedKey, &suggested_key_string)) {
709      suggested_key = Command::StringToAccelerator(suggested_key_string,
710                                                   command_name);
711    }
712
713    suggested_key_prefs->GetBoolean(kSuggestedKeyWasAssigned,
714                                    &suggested_key_was_assigned);
715  }
716
717  // Get the active shortcut from the prefs, if any.
718  Command active_command = FindCommandByName(extension->id(), command_name);
719
720  return suggested_key_was_assigned ?
721      active_command.accelerator() != suggested_key :
722      active_command.accelerator().key_code() != ui::VKEY_UNKNOWN;
723}
724
725bool CommandService::IsKeybindingChanging(const Extension* extension,
726                                          const std::string& command_name) {
727  // Get the new assigned command, if any.
728  Command new_command;
729  if (command_name == manifest_values::kBrowserActionCommandEvent) {
730    new_command = *CommandsInfo::GetBrowserActionCommand(extension);
731  } else if (command_name == manifest_values::kPageActionCommandEvent) {
732    new_command = *CommandsInfo::GetPageActionCommand(extension);
733  } else {  // This is a named command.
734    const CommandMap* named_commands =
735        CommandsInfo::GetNamedCommands(extension);
736    if (named_commands) {
737      CommandMap::const_iterator loc = named_commands->find(command_name);
738      if (loc != named_commands->end())
739        new_command = loc->second;
740    }
741  }
742
743  return Command::StringToAccelerator(
744      GetSuggestedKeyPref(extension, command_name), command_name) !=
745      new_command.accelerator();
746}
747
748std::string CommandService::GetSuggestedKeyPref(
749    const Extension* extension,
750    const std::string& command_name) {
751  // Get the previous suggested key, if any.
752  ui::Accelerator suggested_key;
753  ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
754  const base::DictionaryValue* commands_prefs = NULL;
755  if (extension_prefs->ReadPrefAsDictionary(extension->id(),
756                                            kCommands,
757                                            &commands_prefs)) {
758    const base::DictionaryValue* suggested_key_prefs = NULL;
759    std::string suggested_key;
760    if (commands_prefs->GetDictionary(command_name, &suggested_key_prefs) &&
761        suggested_key_prefs->GetString(kSuggestedKey, &suggested_key)) {
762      return suggested_key;
763    }
764  }
765
766  return std::string();
767}
768
769void CommandService::RemoveKeybindingPrefs(const std::string& extension_id,
770                                           const std::string& command_name) {
771  DictionaryPrefUpdate updater(profile_->GetPrefs(),
772                               prefs::kExtensionCommands);
773  base::DictionaryValue* bindings = updater.Get();
774
775  typedef std::vector<std::string> KeysToRemove;
776  KeysToRemove keys_to_remove;
777  for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd();
778       it.Advance()) {
779    // Removal of keybinding preference should be limited to current platform.
780    if (!IsForCurrentPlatform(it.key()))
781      continue;
782
783    const base::DictionaryValue* item = NULL;
784    it.value().GetAsDictionary(&item);
785
786    std::string extension;
787    item->GetString(kExtension, &extension);
788
789    if (extension == extension_id) {
790      // If |command_name| is specified, delete only that command. Otherwise,
791      // delete all commands.
792      if (!command_name.empty()) {
793        std::string command;
794        item->GetString(kCommandName, &command);
795        if (command_name != command)
796          continue;
797      }
798
799      keys_to_remove.push_back(it.key());
800    }
801  }
802
803  for (KeysToRemove::const_iterator it = keys_to_remove.begin();
804       it != keys_to_remove.end(); ++it) {
805    std::string key = *it;
806    bindings->Remove(key, NULL);
807
808    std::pair<const std::string, const std::string> details =
809        std::make_pair(extension_id, command_name);
810    content::NotificationService::current()->Notify(
811        extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED,
812        content::Source<Profile>(profile_),
813        content::Details<std::pair<const std::string, const std::string> >(
814            &details));
815  }
816}
817
818bool CommandService::GetExtensionActionCommand(
819    const std::string& extension_id,
820    QueryType query_type,
821    Command* command,
822    bool* active,
823    ExtensionCommandType action_type) const {
824  const ExtensionSet& extensions =
825      ExtensionRegistry::Get(profile_)->enabled_extensions();
826  const Extension* extension = extensions.GetByID(extension_id);
827  CHECK(extension);
828
829  if (active)
830    *active = false;
831
832  const Command* requested_command = NULL;
833  switch (action_type) {
834    case BROWSER_ACTION:
835      requested_command = CommandsInfo::GetBrowserActionCommand(extension);
836      break;
837    case PAGE_ACTION:
838      requested_command = CommandsInfo::GetPageActionCommand(extension);
839      break;
840    case NAMED:
841      NOTREACHED();
842      return false;
843  }
844  if (!requested_command)
845    return false;
846
847  // Look up to see if the user has overridden how the command should work.
848  Command saved_command =
849      FindCommandByName(extension_id, requested_command->command_name());
850  ui::Accelerator shortcut_assigned = saved_command.accelerator();
851
852  if (active)
853    *active = (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN);
854
855  if (query_type == ACTIVE_ONLY &&
856      shortcut_assigned.key_code() == ui::VKEY_UNKNOWN)
857    return false;
858
859  *command = *requested_command;
860  if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN)
861    command->set_accelerator(shortcut_assigned);
862
863  return true;
864}
865
866template <>
867void
868BrowserContextKeyedAPIFactory<CommandService>::DeclareFactoryDependencies() {
869  DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance());
870}
871
872}  // namespace extensions
873