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