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