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