command_service.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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/settings_overrides_handler.h" 23#include "chrome/common/extensions/manifest_handlers/ui_overrides_handler.h" 24#include "chrome/common/pref_names.h" 25#include "components/pref_registry/pref_registry_syncable.h" 26#include "content/public/browser/notification_details.h" 27#include "content/public/browser/notification_service.h" 28#include "extensions/browser/extension_function_registry.h" 29#include "extensions/browser/extension_prefs.h" 30#include "extensions/browser/extension_registry.h" 31#include "extensions/browser/extension_system.h" 32#include "extensions/common/feature_switch.h" 33#include "extensions/common/manifest_constants.h" 34#include "extensions/common/permissions/permissions_data.h" 35 36namespace extensions { 37namespace { 38 39const char kExtension[] = "extension"; 40const char kCommandName[] = "command_name"; 41const char kGlobal[] = "global"; 42 43// A preference that indicates that the initial keybindings for the given 44// extension have been set. 45const char kInitialBindingsHaveBeenAssigned[] = "initial_keybindings_set"; 46 47std::string GetPlatformKeybindingKeyForAccelerator( 48 const ui::Accelerator& accelerator, const std::string extension_id) { 49 std::string key = Command::CommandPlatform() + ":" + 50 Command::AcceleratorToString(accelerator); 51 52 // Media keys have a 1-to-many relationship with targets, unlike regular 53 // shortcut (1-to-1 relationship). That means two or more extensions can 54 // register for the same media key so the extension ID needs to be added to 55 // the key to make sure the key is unique. 56 if (Command::IsMediaKey(accelerator)) 57 key += ":" + extension_id; 58 59 return key; 60} 61 62bool IsForCurrentPlatform(const std::string& key) { 63 return StartsWithASCII(key, Command::CommandPlatform() + ":", true); 64} 65 66void SetInitialBindingsHaveBeenAssigned( 67 ExtensionPrefs* prefs, const std::string& extension_id) { 68 prefs->UpdateExtensionPref(extension_id, kInitialBindingsHaveBeenAssigned, 69 new base::FundamentalValue(true)); 70} 71 72bool InitialBindingsHaveBeenAssigned( 73 const ExtensionPrefs* prefs, const std::string& extension_id) { 74 bool assigned = false; 75 if (!prefs || !prefs->ReadPrefAsBoolean(extension_id, 76 kInitialBindingsHaveBeenAssigned, 77 &assigned)) 78 return false; 79 80 return assigned; 81} 82 83// Checks if |extension| is permitted to automatically assign the |accelerator| 84// key. 85bool CanAutoAssign(const ui::Accelerator& accelerator, 86 const Extension* extension, 87 Profile* profile, 88 bool is_named_command, 89 bool is_global) { 90 // Media Keys are non-exclusive, so allow auto-assigning them. 91 if (Command::IsMediaKey(accelerator)) 92 return true; 93 94 if (is_global) { 95 if (!is_named_command) 96 return false; // Browser and page actions are not global in nature. 97 98 // Global shortcuts are restricted to (Ctrl|Command)+Shift+[0-9]. 99#if defined OS_MACOSX 100 if (!accelerator.IsCmdDown()) 101 return false; 102#else 103 if (!accelerator.IsCtrlDown()) 104 return false; 105#endif 106 if (!accelerator.IsShiftDown()) 107 return false; 108 return (accelerator.key_code() >= ui::VKEY_0 && 109 accelerator.key_code() <= ui::VKEY_9); 110 } else { 111 // Not a global command, check if Chrome shortcut and whether 112 // we can override it. 113 if (accelerator == chrome::GetPrimaryChromeAcceleratorForCommandId( 114 IDC_BOOKMARK_PAGE) && 115 CommandService::RemovesBookmarkShortcut(extension)) { 116 // If this check fails it either means we have an API to override a 117 // key that isn't a ChromeAccelerator (and the API can therefore be 118 // deprecated) or the IsChromeAccelerator isn't consistently 119 // returning true for all accelerators. 120 DCHECK(chrome::IsChromeAccelerator(accelerator, profile)); 121 return true; 122 } 123 124 return !chrome::IsChromeAccelerator(accelerator, profile); 125 } 126} 127 128} // namespace 129 130// static 131void CommandService::RegisterProfilePrefs( 132 user_prefs::PrefRegistrySyncable* registry) { 133 registry->RegisterDictionaryPref( 134 prefs::kExtensionCommands, 135 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); 136} 137 138CommandService::CommandService(content::BrowserContext* context) 139 : profile_(Profile::FromBrowserContext(context)), 140 extension_registry_observer_(this) { 141 ExtensionFunctionRegistry::GetInstance()-> 142 RegisterFunction<GetAllCommandsFunction>(); 143 144 extension_registry_observer_.Add(ExtensionRegistry::Get(profile_)); 145} 146 147CommandService::~CommandService() { 148} 149 150static base::LazyInstance<BrowserContextKeyedAPIFactory<CommandService> > 151 g_factory = LAZY_INSTANCE_INITIALIZER; 152 153// static 154BrowserContextKeyedAPIFactory<CommandService>* 155CommandService::GetFactoryInstance() { 156 return g_factory.Pointer(); 157} 158 159// static 160CommandService* CommandService::Get(content::BrowserContext* context) { 161 return BrowserContextKeyedAPIFactory<CommandService>::Get(context); 162} 163 164// static 165bool CommandService::RemovesBookmarkShortcut(const Extension* extension) { 166 const UIOverrides* ui_overrides = UIOverrides::Get(extension); 167 const SettingsOverrides* settings_overrides = 168 SettingsOverrides::Get(extension); 169 170 return ((settings_overrides && 171 SettingsOverrides::RemovesBookmarkShortcut(*settings_overrides)) || 172 (ui_overrides && 173 UIOverrides::RemovesBookmarkShortcut(*ui_overrides))) && 174 (extension->permissions_data()->HasAPIPermission( 175 APIPermission::kBookmarkManagerPrivate) || 176 FeatureSwitch::enable_override_bookmarks_ui()->IsEnabled()); 177} 178 179// static 180bool CommandService::RemovesBookmarkOpenPagesShortcut( 181 const Extension* extension) { 182 const UIOverrides* ui_overrides = UIOverrides::Get(extension); 183 const SettingsOverrides* settings_overrides = 184 SettingsOverrides::Get(extension); 185 186 return ((settings_overrides && 187 SettingsOverrides::RemovesBookmarkOpenPagesShortcut( 188 *settings_overrides)) || 189 (ui_overrides && 190 UIOverrides::RemovesBookmarkOpenPagesShortcut(*ui_overrides))) && 191 (extension->permissions_data()->HasAPIPermission( 192 APIPermission::kBookmarkManagerPrivate) || 193 FeatureSwitch::enable_override_bookmarks_ui()->IsEnabled()); 194} 195 196bool CommandService::GetBrowserActionCommand(const std::string& extension_id, 197 QueryType type, 198 Command* command, 199 bool* active) const { 200 return GetExtensionActionCommand( 201 extension_id, type, command, active, BROWSER_ACTION); 202} 203 204bool CommandService::GetPageActionCommand(const std::string& extension_id, 205 QueryType type, 206 Command* command, 207 bool* active) const { 208 return GetExtensionActionCommand( 209 extension_id, type, command, active, PAGE_ACTION); 210} 211 212bool CommandService::GetNamedCommands(const std::string& extension_id, 213 QueryType type, 214 CommandScope scope, 215 CommandMap* command_map) const { 216 const ExtensionSet& extensions = 217 ExtensionRegistry::Get(profile_)->enabled_extensions(); 218 const Extension* extension = extensions.GetByID(extension_id); 219 CHECK(extension); 220 221 command_map->clear(); 222 const CommandMap* commands = CommandsInfo::GetNamedCommands(extension); 223 if (!commands) 224 return false; 225 226 CommandMap::const_iterator iter = commands->begin(); 227 for (; iter != commands->end(); ++iter) { 228 // Look up to see if the user has overridden how the command should work. 229 Command saved_command = 230 FindCommandByName(extension_id, iter->second.command_name()); 231 ui::Accelerator shortcut_assigned = saved_command.accelerator(); 232 233 if (type == ACTIVE_ONLY && shortcut_assigned.key_code() == ui::VKEY_UNKNOWN) 234 continue; 235 236 Command command = iter->second; 237 if (scope != ANY_SCOPE && ((scope == GLOBAL) != saved_command.global())) 238 continue; 239 240 if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN) 241 command.set_accelerator(shortcut_assigned); 242 command.set_global(saved_command.global()); 243 244 (*command_map)[iter->second.command_name()] = command; 245 } 246 247 return true; 248} 249 250bool CommandService::AddKeybindingPref( 251 const ui::Accelerator& accelerator, 252 std::string extension_id, 253 std::string command_name, 254 bool allow_overrides, 255 bool global) { 256 if (accelerator.key_code() == ui::VKEY_UNKNOWN) 257 return false; 258 259 // Media Keys are allowed to be used by named command only. 260 DCHECK(!Command::IsMediaKey(accelerator) || 261 (command_name != manifest_values::kPageActionCommandEvent && 262 command_name != manifest_values::kBrowserActionCommandEvent)); 263 264 DictionaryPrefUpdate updater(profile_->GetPrefs(), 265 prefs::kExtensionCommands); 266 base::DictionaryValue* bindings = updater.Get(); 267 268 std::string key = GetPlatformKeybindingKeyForAccelerator(accelerator, 269 extension_id); 270 271 if (bindings->HasKey(key)) { 272 if (!allow_overrides) 273 return false; // Already taken. 274 275 // If the shortcut has been assigned to another command, it should be 276 // removed before overriding, so that |ExtensionKeybindingRegistry| can get 277 // a chance to do clean-up. 278 const base::DictionaryValue* item = NULL; 279 bindings->GetDictionary(key, &item); 280 std::string old_extension_id; 281 std::string old_command_name; 282 item->GetString(kExtension, &old_extension_id); 283 item->GetString(kCommandName, &old_command_name); 284 RemoveKeybindingPrefs(old_extension_id, old_command_name); 285 } 286 287 base::DictionaryValue* keybinding = new base::DictionaryValue(); 288 keybinding->SetString(kExtension, extension_id); 289 keybinding->SetString(kCommandName, command_name); 290 keybinding->SetBoolean(kGlobal, global); 291 292 bindings->Set(key, keybinding); 293 294 std::pair<const std::string, const std::string> details = 295 std::make_pair(extension_id, command_name); 296 content::NotificationService::current()->Notify( 297 chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED, 298 content::Source<Profile>(profile_), 299 content::Details< 300 std::pair<const std::string, const std::string> >(&details)); 301 302 return true; 303} 304 305void CommandService::OnExtensionWillBeInstalled( 306 content::BrowserContext* browser_context, 307 const Extension* extension, 308 bool is_update, 309 bool from_ephemeral, 310 const std::string& old_name) { 311 AssignInitialKeybindings(extension); 312} 313 314void CommandService::OnExtensionUninstalled( 315 content::BrowserContext* browser_context, 316 const Extension* extension) { 317 RemoveKeybindingPrefs(extension->id(), std::string()); 318} 319 320void CommandService::UpdateKeybindingPrefs(const std::string& extension_id, 321 const std::string& command_name, 322 const std::string& keystroke) { 323 Command command = FindCommandByName(extension_id, command_name); 324 325 // The extension command might be assigned another shortcut. Remove that 326 // shortcut before proceeding. 327 RemoveKeybindingPrefs(extension_id, command_name); 328 329 ui::Accelerator accelerator = 330 Command::StringToAccelerator(keystroke, command_name); 331 AddKeybindingPref(accelerator, extension_id, command_name, 332 true, command.global()); 333} 334 335bool CommandService::SetScope(const std::string& extension_id, 336 const std::string& command_name, 337 bool global) { 338 Command command = FindCommandByName(extension_id, command_name); 339 if (global == command.global()) 340 return false; 341 342 // Pre-existing shortcuts must be removed before proceeding because the 343 // handlers for global and non-global extensions are not one and the same. 344 RemoveKeybindingPrefs(extension_id, command_name); 345 AddKeybindingPref(command.accelerator(), extension_id, 346 command_name, true, global); 347 return true; 348} 349 350Command CommandService::FindCommandByName(const std::string& extension_id, 351 const std::string& command) const { 352 const base::DictionaryValue* bindings = 353 profile_->GetPrefs()->GetDictionary(prefs::kExtensionCommands); 354 for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd(); 355 it.Advance()) { 356 const base::DictionaryValue* item = NULL; 357 it.value().GetAsDictionary(&item); 358 359 std::string extension; 360 item->GetString(kExtension, &extension); 361 if (extension != extension_id) 362 continue; 363 std::string command_name; 364 item->GetString(kCommandName, &command_name); 365 if (command != command_name) 366 continue; 367 // Format stored in Preferences is: "Platform:Shortcut[:ExtensionId]". 368 std::string shortcut = it.key(); 369 if (!IsForCurrentPlatform(shortcut)) 370 continue; 371 bool global = false; 372 if (FeatureSwitch::global_commands()->IsEnabled()) 373 item->GetBoolean(kGlobal, &global); 374 375 std::vector<std::string> tokens; 376 base::SplitString(shortcut, ':', &tokens); 377 CHECK(tokens.size() >= 2); 378 shortcut = tokens[1]; 379 380 return Command(command_name, base::string16(), shortcut, global); 381 } 382 383 return Command(); 384} 385 386bool CommandService::GetBoundExtensionCommand( 387 const std::string& extension_id, 388 const ui::Accelerator& accelerator, 389 Command* command, 390 ExtensionCommandType* command_type) const { 391 const Extension* extension = 392 ExtensionRegistry::Get(profile_) 393 ->GetExtensionById(extension_id, ExtensionRegistry::ENABLED); 394 CHECK(extension); 395 396 Command prospective_command; 397 CommandMap command_map; 398 bool active = false; 399 if (GetBrowserActionCommand(extension_id, 400 CommandService::ACTIVE_ONLY, 401 &prospective_command, 402 &active) && 403 active && accelerator == prospective_command.accelerator()) { 404 if (command) 405 *command = prospective_command; 406 if (command_type) 407 *command_type = BROWSER_ACTION; 408 return true; 409 } else if (GetPageActionCommand(extension_id, 410 CommandService::ACTIVE_ONLY, 411 &prospective_command, 412 &active) && 413 active && accelerator == prospective_command.accelerator()) { 414 if (command) 415 *command = prospective_command; 416 if (command_type) 417 *command_type = PAGE_ACTION; 418 return true; 419 } else if (GetNamedCommands(extension_id, 420 CommandService::ACTIVE_ONLY, 421 CommandService::REGULAR, 422 &command_map)) { 423 for (CommandMap::const_iterator it = command_map.begin(); 424 it != command_map.end(); 425 ++it) { 426 if (accelerator == it->second.accelerator()) { 427 if (command) 428 *command = it->second; 429 if (command_type) 430 *command_type = NAMED; 431 return true; 432 } 433 } 434 } 435 return false; 436} 437 438bool CommandService::OverridesBookmarkShortcut( 439 const Extension* extension) const { 440 return RemovesBookmarkShortcut(extension) && 441 GetBoundExtensionCommand( 442 extension->id(), 443 chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE), 444 NULL, 445 NULL); 446} 447 448void CommandService::AssignInitialKeybindings(const Extension* extension) { 449 const CommandMap* commands = CommandsInfo::GetNamedCommands(extension); 450 if (!commands) 451 return; 452 453 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_); 454 if (InitialBindingsHaveBeenAssigned(extension_prefs, extension->id())) 455 return; 456 SetInitialBindingsHaveBeenAssigned(extension_prefs, extension->id()); 457 458 CommandMap::const_iterator iter = commands->begin(); 459 for (; iter != commands->end(); ++iter) { 460 const Command command = iter->second; 461 if (CanAutoAssign(command.accelerator(), 462 extension, 463 profile_, 464 true, // Is a named command. 465 command.global())) { 466 AddKeybindingPref(command.accelerator(), 467 extension->id(), 468 command.command_name(), 469 false, // Overwriting not allowed. 470 command.global()); 471 } 472 } 473 474 const Command* browser_action_command = 475 CommandsInfo::GetBrowserActionCommand(extension); 476 if (browser_action_command && 477 CanAutoAssign(browser_action_command->accelerator(), 478 extension, 479 profile_, 480 false, // Not a named command. 481 false)) { // Not global. 482 AddKeybindingPref(browser_action_command->accelerator(), 483 extension->id(), 484 browser_action_command->command_name(), 485 false, // Overwriting not allowed. 486 false); // Not global. 487 } 488 489 const Command* page_action_command = 490 CommandsInfo::GetPageActionCommand(extension); 491 if (page_action_command && 492 CanAutoAssign(page_action_command->accelerator(), 493 extension, 494 profile_, 495 false, // Not a named command. 496 false)) { // Not global. 497 AddKeybindingPref(page_action_command->accelerator(), 498 extension->id(), 499 page_action_command->command_name(), 500 false, // Overwriting not allowed. 501 false); // Not global. 502 } 503} 504 505void CommandService::RemoveKeybindingPrefs(const std::string& extension_id, 506 const std::string& command_name) { 507 DictionaryPrefUpdate updater(profile_->GetPrefs(), 508 prefs::kExtensionCommands); 509 base::DictionaryValue* bindings = updater.Get(); 510 511 typedef std::vector<std::string> KeysToRemove; 512 KeysToRemove keys_to_remove; 513 for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd(); 514 it.Advance()) { 515 // Removal of keybinding preference should be limited to current platform. 516 if (!IsForCurrentPlatform(it.key())) 517 continue; 518 519 const base::DictionaryValue* item = NULL; 520 it.value().GetAsDictionary(&item); 521 522 std::string extension; 523 item->GetString(kExtension, &extension); 524 525 if (extension == extension_id) { 526 // If |command_name| is specified, delete only that command. Otherwise, 527 // delete all commands. 528 if (!command_name.empty()) { 529 std::string command; 530 item->GetString(kCommandName, &command); 531 if (command_name != command) 532 continue; 533 } 534 535 keys_to_remove.push_back(it.key()); 536 } 537 } 538 539 for (KeysToRemove::const_iterator it = keys_to_remove.begin(); 540 it != keys_to_remove.end(); ++it) { 541 std::string key = *it; 542 bindings->Remove(key, NULL); 543 544 std::pair<const std::string, const std::string> details = 545 std::make_pair(extension_id, command_name); 546 content::NotificationService::current()->Notify( 547 chrome::NOTIFICATION_EXTENSION_COMMAND_REMOVED, 548 content::Source<Profile>(profile_), 549 content::Details< 550 std::pair<const std::string, const std::string> >(&details)); 551 } 552} 553 554bool CommandService::GetExtensionActionCommand( 555 const std::string& extension_id, 556 QueryType query_type, 557 Command* command, 558 bool* active, 559 ExtensionCommandType action_type) const { 560 const ExtensionSet& extensions = 561 ExtensionRegistry::Get(profile_)->enabled_extensions(); 562 const Extension* extension = extensions.GetByID(extension_id); 563 CHECK(extension); 564 565 if (active) 566 *active = false; 567 568 const Command* requested_command = NULL; 569 switch (action_type) { 570 case BROWSER_ACTION: 571 requested_command = CommandsInfo::GetBrowserActionCommand(extension); 572 break; 573 case PAGE_ACTION: 574 requested_command = CommandsInfo::GetPageActionCommand(extension); 575 break; 576 case NAMED: 577 NOTREACHED(); 578 return false; 579 } 580 if (!requested_command) 581 return false; 582 583 // Look up to see if the user has overridden how the command should work. 584 Command saved_command = 585 FindCommandByName(extension_id, requested_command->command_name()); 586 ui::Accelerator shortcut_assigned = saved_command.accelerator(); 587 588 if (active) 589 *active = (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN); 590 591 if (query_type == ACTIVE_ONLY && 592 shortcut_assigned.key_code() == ui::VKEY_UNKNOWN) 593 return false; 594 595 *command = *requested_command; 596 if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN) 597 command->set_accelerator(shortcut_assigned); 598 599 return true; 600} 601 602template <> 603void 604BrowserContextKeyedAPIFactory<CommandService>::DeclareFactoryDependencies() { 605 DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance()); 606} 607 608} // namespace extensions 609