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/ui_overrides_handler.h" 23 #include "chrome/common/pref_names.h" 24 #include "components/pref_registry/pref_registry_syncable.h" 25 #include "content/public/browser/notification_details.h" 26 #include "content/public/browser/notification_service.h" 27 #include "extensions/browser/extension_function_registry.h" 28 #include "extensions/browser/extension_prefs.h" 29 #include "extensions/browser/extension_registry.h" 30 #include "extensions/browser/extension_system.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 chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED, 290 content::Source<Profile>(profile_), 291 content::Details< 292 std::pair<const std::string, const std::string> >(&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 RemoveKeybindingPrefs(extension->id(), std::string()); 310 } 311 312 void CommandService::UpdateKeybindingPrefs(const std::string& extension_id, 313 const std::string& command_name, 314 const std::string& keystroke) { 315 Command command = FindCommandByName(extension_id, command_name); 316 317 // The extension command might be assigned another shortcut. Remove that 318 // shortcut before proceeding. 319 RemoveKeybindingPrefs(extension_id, command_name); 320 321 ui::Accelerator accelerator = 322 Command::StringToAccelerator(keystroke, command_name); 323 AddKeybindingPref(accelerator, extension_id, command_name, 324 true, command.global()); 325 } 326 327 bool CommandService::SetScope(const std::string& extension_id, 328 const std::string& command_name, 329 bool global) { 330 Command command = FindCommandByName(extension_id, command_name); 331 if (global == command.global()) 332 return false; 333 334 // Pre-existing shortcuts must be removed before proceeding because the 335 // handlers for global and non-global extensions are not one and the same. 336 RemoveKeybindingPrefs(extension_id, command_name); 337 AddKeybindingPref(command.accelerator(), extension_id, 338 command_name, true, global); 339 return true; 340 } 341 342 Command CommandService::FindCommandByName(const std::string& extension_id, 343 const std::string& command) const { 344 const base::DictionaryValue* bindings = 345 profile_->GetPrefs()->GetDictionary(prefs::kExtensionCommands); 346 for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd(); 347 it.Advance()) { 348 const base::DictionaryValue* item = NULL; 349 it.value().GetAsDictionary(&item); 350 351 std::string extension; 352 item->GetString(kExtension, &extension); 353 if (extension != extension_id) 354 continue; 355 std::string command_name; 356 item->GetString(kCommandName, &command_name); 357 if (command != command_name) 358 continue; 359 // Format stored in Preferences is: "Platform:Shortcut[:ExtensionId]". 360 std::string shortcut = it.key(); 361 if (!IsForCurrentPlatform(shortcut)) 362 continue; 363 bool global = false; 364 if (FeatureSwitch::global_commands()->IsEnabled()) 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 // Global shortcuts are restricted to (Ctrl|Command)+Shift+[0-9]. 569 #if defined OS_MACOSX 570 if (!command.accelerator().IsCmdDown()) 571 return false; 572 #else 573 if (!command.accelerator().IsCtrlDown()) 574 return false; 575 #endif 576 if (!command.accelerator().IsShiftDown()) 577 return false; 578 return (command.accelerator().key_code() >= ui::VKEY_0 && 579 command.accelerator().key_code() <= ui::VKEY_9); 580 } else { 581 // Not a global command, check if Chrome shortcut and whether 582 // we can override it. 583 if (command.accelerator() == 584 chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE) && 585 CommandService::RemovesBookmarkShortcut(extension)) { 586 // If this check fails it either means we have an API to override a 587 // key that isn't a ChromeAccelerator (and the API can therefore be 588 // deprecated) or the IsChromeAccelerator isn't consistently 589 // returning true for all accelerators. 590 DCHECK(chrome::IsChromeAccelerator(command.accelerator(), profile_)); 591 return true; 592 } 593 594 return !chrome::IsChromeAccelerator(command.accelerator(), profile_); 595 } 596 } 597 598 void CommandService::UpdateExtensionSuggestedCommandPrefs( 599 const Extension* extension) { 600 scoped_ptr<base::DictionaryValue> suggested_key_prefs( 601 new base::DictionaryValue); 602 603 const CommandMap* commands = CommandsInfo::GetNamedCommands(extension); 604 if (commands) { 605 for (CommandMap::const_iterator iter = commands->begin(); 606 iter != commands->end(); ++iter) { 607 const Command command = iter->second; 608 scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue); 609 command_keys->SetString( 610 kSuggestedKey, 611 Command::AcceleratorToString(command.accelerator())); 612 suggested_key_prefs->Set(command.command_name(), command_keys.release()); 613 } 614 } 615 616 const Command* browser_action_command = 617 CommandsInfo::GetBrowserActionCommand(extension); 618 // The browser action command may be defaulted to an unassigned accelerator if 619 // a browser action is specified by the extension but a keybinding is not 620 // declared. See CommandsHandler::MaybeSetBrowserActionDefault. 621 if (browser_action_command && 622 browser_action_command->accelerator().key_code() != ui::VKEY_UNKNOWN) { 623 scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue); 624 command_keys->SetString( 625 kSuggestedKey, 626 Command::AcceleratorToString(browser_action_command->accelerator())); 627 suggested_key_prefs->Set(browser_action_command->command_name(), 628 command_keys.release()); 629 } 630 631 const Command* page_action_command = 632 CommandsInfo::GetPageActionCommand(extension); 633 if (page_action_command) { 634 scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue); 635 command_keys->SetString( 636 kSuggestedKey, 637 Command::AcceleratorToString(page_action_command->accelerator())); 638 suggested_key_prefs->Set(page_action_command->command_name(), 639 command_keys.release()); 640 } 641 642 // Merge into current prefs, if present. 643 MergeSuggestedKeyPrefs(extension->id(), 644 ExtensionPrefs::Get(profile_), 645 suggested_key_prefs.Pass()); 646 } 647 648 void CommandService::RemoveDefunctExtensionSuggestedCommandPrefs( 649 const Extension* extension) { 650 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_); 651 const base::DictionaryValue* current_prefs = NULL; 652 extension_prefs->ReadPrefAsDictionary(extension->id(), 653 kCommands, 654 ¤t_prefs); 655 656 if (current_prefs) { 657 scoped_ptr<base::DictionaryValue> suggested_key_prefs( 658 current_prefs->DeepCopy()); 659 const CommandMap* named_commands = 660 CommandsInfo::GetNamedCommands(extension); 661 const Command* browser_action_command = 662 CommandsInfo::GetBrowserActionCommand(extension); 663 for (base::DictionaryValue::Iterator it(*current_prefs); 664 !it.IsAtEnd(); it.Advance()) { 665 if (it.key() == manifest_values::kBrowserActionCommandEvent) { 666 // The browser action command may be defaulted to an unassigned 667 // accelerator if a browser action is specified by the extension but a 668 // keybinding is not declared. See 669 // CommandsHandler::MaybeSetBrowserActionDefault. 670 if (!browser_action_command || 671 browser_action_command->accelerator().key_code() == 672 ui::VKEY_UNKNOWN) { 673 suggested_key_prefs->Remove(it.key(), NULL); 674 } 675 } else if (it.key() == manifest_values::kPageActionCommandEvent) { 676 if (!CommandsInfo::GetPageActionCommand(extension)) 677 suggested_key_prefs->Remove(it.key(), NULL); 678 } else if (named_commands) { 679 if (named_commands->find(it.key()) == named_commands->end()) 680 suggested_key_prefs->Remove(it.key(), NULL); 681 } 682 } 683 684 extension_prefs->UpdateExtensionPref(extension->id(), 685 kCommands, 686 suggested_key_prefs.release()); 687 } 688 } 689 690 bool CommandService::IsCommandShortcutUserModified( 691 const Extension* extension, 692 const std::string& command_name) { 693 // Get the previous suggested key, if any. 694 ui::Accelerator suggested_key; 695 bool suggested_key_was_assigned = false; 696 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_); 697 const base::DictionaryValue* commands_prefs = NULL; 698 const base::DictionaryValue* suggested_key_prefs = NULL; 699 if (extension_prefs->ReadPrefAsDictionary(extension->id(), 700 kCommands, 701 &commands_prefs) && 702 commands_prefs->GetDictionary(command_name, &suggested_key_prefs)) { 703 std::string suggested_key_string; 704 if (suggested_key_prefs->GetString(kSuggestedKey, &suggested_key_string)) { 705 suggested_key = Command::StringToAccelerator(suggested_key_string, 706 command_name); 707 } 708 709 suggested_key_prefs->GetBoolean(kSuggestedKeyWasAssigned, 710 &suggested_key_was_assigned); 711 } 712 713 // Get the active shortcut from the prefs, if any. 714 Command active_command = FindCommandByName(extension->id(), command_name); 715 716 return suggested_key_was_assigned ? 717 active_command.accelerator() != suggested_key : 718 active_command.accelerator().key_code() != ui::VKEY_UNKNOWN; 719 } 720 721 bool CommandService::IsKeybindingChanging(const Extension* extension, 722 const std::string& command_name) { 723 // Get the new assigned command, if any. 724 Command new_command; 725 if (command_name == manifest_values::kBrowserActionCommandEvent) { 726 new_command = *CommandsInfo::GetBrowserActionCommand(extension); 727 } else if (command_name == manifest_values::kPageActionCommandEvent) { 728 new_command = *CommandsInfo::GetPageActionCommand(extension); 729 } else { // This is a named command. 730 const CommandMap* named_commands = 731 CommandsInfo::GetNamedCommands(extension); 732 if (named_commands) { 733 CommandMap::const_iterator loc = named_commands->find(command_name); 734 if (loc != named_commands->end()) 735 new_command = loc->second; 736 } 737 } 738 739 return Command::StringToAccelerator( 740 GetSuggestedKeyPref(extension, command_name), command_name) != 741 new_command.accelerator(); 742 } 743 744 std::string CommandService::GetSuggestedKeyPref( 745 const Extension* extension, 746 const std::string& command_name) { 747 // Get the previous suggested key, if any. 748 ui::Accelerator suggested_key; 749 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_); 750 const base::DictionaryValue* commands_prefs = NULL; 751 if (extension_prefs->ReadPrefAsDictionary(extension->id(), 752 kCommands, 753 &commands_prefs)) { 754 const base::DictionaryValue* suggested_key_prefs = NULL; 755 std::string suggested_key; 756 if (commands_prefs->GetDictionary(command_name, &suggested_key_prefs) && 757 suggested_key_prefs->GetString(kSuggestedKey, &suggested_key)) { 758 return suggested_key; 759 } 760 } 761 762 return std::string(); 763 } 764 765 void CommandService::RemoveKeybindingPrefs(const std::string& extension_id, 766 const std::string& command_name) { 767 DictionaryPrefUpdate updater(profile_->GetPrefs(), 768 prefs::kExtensionCommands); 769 base::DictionaryValue* bindings = updater.Get(); 770 771 typedef std::vector<std::string> KeysToRemove; 772 KeysToRemove keys_to_remove; 773 for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd(); 774 it.Advance()) { 775 // Removal of keybinding preference should be limited to current platform. 776 if (!IsForCurrentPlatform(it.key())) 777 continue; 778 779 const base::DictionaryValue* item = NULL; 780 it.value().GetAsDictionary(&item); 781 782 std::string extension; 783 item->GetString(kExtension, &extension); 784 785 if (extension == extension_id) { 786 // If |command_name| is specified, delete only that command. Otherwise, 787 // delete all commands. 788 if (!command_name.empty()) { 789 std::string command; 790 item->GetString(kCommandName, &command); 791 if (command_name != command) 792 continue; 793 } 794 795 keys_to_remove.push_back(it.key()); 796 } 797 } 798 799 for (KeysToRemove::const_iterator it = keys_to_remove.begin(); 800 it != keys_to_remove.end(); ++it) { 801 std::string key = *it; 802 bindings->Remove(key, NULL); 803 804 std::pair<const std::string, const std::string> details = 805 std::make_pair(extension_id, command_name); 806 content::NotificationService::current()->Notify( 807 chrome::NOTIFICATION_EXTENSION_COMMAND_REMOVED, 808 content::Source<Profile>(profile_), 809 content::Details< 810 std::pair<const std::string, const std::string> >(&details)); 811 } 812 } 813 814 bool CommandService::GetExtensionActionCommand( 815 const std::string& extension_id, 816 QueryType query_type, 817 Command* command, 818 bool* active, 819 ExtensionCommandType action_type) const { 820 const ExtensionSet& extensions = 821 ExtensionRegistry::Get(profile_)->enabled_extensions(); 822 const Extension* extension = extensions.GetByID(extension_id); 823 CHECK(extension); 824 825 if (active) 826 *active = false; 827 828 const Command* requested_command = NULL; 829 switch (action_type) { 830 case BROWSER_ACTION: 831 requested_command = CommandsInfo::GetBrowserActionCommand(extension); 832 break; 833 case PAGE_ACTION: 834 requested_command = CommandsInfo::GetPageActionCommand(extension); 835 break; 836 case NAMED: 837 NOTREACHED(); 838 return false; 839 } 840 if (!requested_command) 841 return false; 842 843 // Look up to see if the user has overridden how the command should work. 844 Command saved_command = 845 FindCommandByName(extension_id, requested_command->command_name()); 846 ui::Accelerator shortcut_assigned = saved_command.accelerator(); 847 848 if (active) 849 *active = (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN); 850 851 if (query_type == ACTIVE_ONLY && 852 shortcut_assigned.key_code() == ui::VKEY_UNKNOWN) 853 return false; 854 855 *command = *requested_command; 856 if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN) 857 command->set_accelerator(shortcut_assigned); 858 859 return true; 860 } 861 862 template <> 863 void 864 BrowserContextKeyedAPIFactory<CommandService>::DeclareFactoryDependencies() { 865 DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance()); 866 } 867 868 } // namespace extensions 869