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 35 namespace extensions { 36 namespace { 37 38 const char kExtension[] = "extension"; 39 const char kCommandName[] = "command_name"; 40 const char kGlobal[] = "global"; 41 42 // A preference that stores keybinding state associated with extension commands. 43 const char kCommands[] = "commands"; 44 45 // Preference key name for saving the extension-suggested key. 46 const char kSuggestedKey[] = "suggested_key"; 47 48 // Preference key name for saving whether the extension-suggested key was 49 // actually assigned. 50 const char kSuggestedKeyWasAssigned[] = "was_assigned"; 51 52 // A preference that indicates that the initial keybindings for the given 53 // extension have been set. 54 const char kInitialBindingsHaveBeenAssigned[] = "initial_keybindings_set"; 55 56 std::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 71 bool IsForCurrentPlatform(const std::string& key) { 72 return StartsWithASCII(key, Command::CommandPlatform() + ":", true); 73 } 74 75 void SetInitialBindingsHaveBeenAssigned( 76 ExtensionPrefs* prefs, const std::string& extension_id) { 77 prefs->UpdateExtensionPref(extension_id, kInitialBindingsHaveBeenAssigned, 78 new base::FundamentalValue(true)); 79 } 80 81 bool 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. 94 void 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 ¤t_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 115 void CommandService::RegisterProfilePrefs( 116 user_prefs::PrefRegistrySyncable* registry) { 117 registry->RegisterDictionaryPref( 118 prefs::kExtensionCommands, 119 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); 120 } 121 122 CommandService::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 131 CommandService::~CommandService() { 132 } 133 134 static base::LazyInstance<BrowserContextKeyedAPIFactory<CommandService> > 135 g_factory = LAZY_INSTANCE_INITIALIZER; 136 137 // static 138 BrowserContextKeyedAPIFactory<CommandService>* 139 CommandService::GetFactoryInstance() { 140 return g_factory.Pointer(); 141 } 142 143 // static 144 CommandService* CommandService::Get(content::BrowserContext* context) { 145 return BrowserContextKeyedAPIFactory<CommandService>::Get(context); 146 } 147 148 // static 149 bool 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 157 bool 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 165 bool 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 173 bool 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 181 bool 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 219 bool 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 297 void 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 306 void CommandService::OnExtensionUninstalled( 307 content::BrowserContext* browser_context, 308 const Extension* extension, 309 extensions::UninstallReason reason) { 310 RemoveKeybindingPrefs(extension->id(), std::string()); 311 } 312 313 void 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 328 bool 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 343 Command 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 378 bool 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 430 bool 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 440 void 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 452 void 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 507 void 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 551 bool 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 602 void 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 652 void 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 ¤t_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 694 bool 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 725 bool 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 748 std::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 769 void 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 818 bool 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 866 template <> 867 void 868 BrowserContextKeyedAPIFactory<CommandService>::DeclareFactoryDependencies() { 869 DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance()); 870 } 871 872 } // namespace extensions 873