command_service.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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 35namespace extensions { 36namespace { 37 38const char kExtension[] = "extension"; 39const char kCommandName[] = "command_name"; 40const char kGlobal[] = "global"; 41 42// A preference that stores keybinding state associated with extension commands. 43const char kCommands[] = "commands"; 44 45// Preference key name for saving the extension-suggested key. 46const char kSuggestedKey[] = "suggested_key"; 47 48// Preference key name for saving whether the extension-suggested key was 49// actually assigned. 50const char kSuggestedKeyWasAssigned[] = "was_assigned"; 51 52// A preference that indicates that the initial keybindings for the given 53// extension have been set. 54const char kInitialBindingsHaveBeenAssigned[] = "initial_keybindings_set"; 55 56std::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 71bool IsForCurrentPlatform(const std::string& key) { 72 return StartsWithASCII(key, Command::CommandPlatform() + ":", true); 73} 74 75void SetInitialBindingsHaveBeenAssigned( 76 ExtensionPrefs* prefs, const std::string& extension_id) { 77 prefs->UpdateExtensionPref(extension_id, kInitialBindingsHaveBeenAssigned, 78 new base::FundamentalValue(true)); 79} 80 81bool 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. 94void 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 115void CommandService::RegisterProfilePrefs( 116 user_prefs::PrefRegistrySyncable* registry) { 117 registry->RegisterDictionaryPref( 118 prefs::kExtensionCommands, 119 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); 120} 121 122CommandService::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 131CommandService::~CommandService() { 132} 133 134static base::LazyInstance<BrowserContextKeyedAPIFactory<CommandService> > 135 g_factory = LAZY_INSTANCE_INITIALIZER; 136 137// static 138BrowserContextKeyedAPIFactory<CommandService>* 139CommandService::GetFactoryInstance() { 140 return g_factory.Pointer(); 141} 142 143// static 144CommandService* CommandService::Get(content::BrowserContext* context) { 145 return BrowserContextKeyedAPIFactory<CommandService>::Get(context); 146} 147 148// static 149bool 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 157bool 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 165bool 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 173bool 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 181bool 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 219bool 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 297void 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 306void CommandService::OnExtensionUninstalled( 307 content::BrowserContext* browser_context, 308 const Extension* extension) { 309 RemoveKeybindingPrefs(extension->id(), std::string()); 310} 311 312void 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 327bool 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 342Command 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 item->GetBoolean(kGlobal, &global); 365 366 std::vector<std::string> tokens; 367 base::SplitString(shortcut, ':', &tokens); 368 CHECK(tokens.size() >= 2); 369 shortcut = tokens[1]; 370 371 return Command(command_name, base::string16(), shortcut, global); 372 } 373 374 return Command(); 375} 376 377bool CommandService::GetBoundExtensionCommand( 378 const std::string& extension_id, 379 const ui::Accelerator& accelerator, 380 Command* command, 381 ExtensionCommandType* command_type) const { 382 const Extension* extension = 383 ExtensionRegistry::Get(profile_) 384 ->GetExtensionById(extension_id, ExtensionRegistry::ENABLED); 385 CHECK(extension); 386 387 Command prospective_command; 388 CommandMap command_map; 389 bool active = false; 390 if (GetBrowserActionCommand(extension_id, 391 CommandService::ACTIVE_ONLY, 392 &prospective_command, 393 &active) && 394 active && accelerator == prospective_command.accelerator()) { 395 if (command) 396 *command = prospective_command; 397 if (command_type) 398 *command_type = BROWSER_ACTION; 399 return true; 400 } else if (GetPageActionCommand(extension_id, 401 CommandService::ACTIVE_ONLY, 402 &prospective_command, 403 &active) && 404 active && accelerator == prospective_command.accelerator()) { 405 if (command) 406 *command = prospective_command; 407 if (command_type) 408 *command_type = PAGE_ACTION; 409 return true; 410 } else if (GetNamedCommands(extension_id, 411 CommandService::ACTIVE_ONLY, 412 CommandService::REGULAR, 413 &command_map)) { 414 for (CommandMap::const_iterator it = command_map.begin(); 415 it != command_map.end(); 416 ++it) { 417 if (accelerator == it->second.accelerator()) { 418 if (command) 419 *command = it->second; 420 if (command_type) 421 *command_type = NAMED; 422 return true; 423 } 424 } 425 } 426 return false; 427} 428 429bool CommandService::OverridesBookmarkShortcut( 430 const Extension* extension) const { 431 return RemovesBookmarkShortcut(extension) && 432 GetBoundExtensionCommand( 433 extension->id(), 434 chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE), 435 NULL, 436 NULL); 437} 438 439void CommandService::UpdateKeybindings(const Extension* extension) { 440 const ExtensionSet& extensions = 441 ExtensionRegistry::Get(profile_)->enabled_extensions(); 442 // The extension is not added to the profile by this point on first install, 443 // so don't try to check for existing keybindings. 444 if (extensions.GetByID(extension->id())) 445 RemoveRelinquishedKeybindings(extension); 446 AssignKeybindings(extension); 447 UpdateExtensionSuggestedCommandPrefs(extension); 448 RemoveDefunctExtensionSuggestedCommandPrefs(extension); 449} 450 451void CommandService::RemoveRelinquishedKeybindings(const Extension* extension) { 452 // Remove keybindings if they have been removed by the extension and the user 453 // has not modified them. 454 CommandMap existing_command_map; 455 if (GetNamedCommands(extension->id(), 456 CommandService::ACTIVE_ONLY, 457 CommandService::REGULAR, 458 &existing_command_map)) { 459 const CommandMap* new_command_map = 460 CommandsInfo::GetNamedCommands(extension); 461 for (CommandMap::const_iterator it = existing_command_map.begin(); 462 it != existing_command_map.end(); ++it) { 463 std::string command_name = it->first; 464 if (new_command_map->find(command_name) == new_command_map->end() && 465 !IsCommandShortcutUserModified(extension, command_name)) { 466 RemoveKeybindingPrefs(extension->id(), command_name); 467 } 468 } 469 } 470 471 Command existing_browser_action_command; 472 const Command* new_browser_action_command = 473 CommandsInfo::GetBrowserActionCommand(extension); 474 if (GetBrowserActionCommand(extension->id(), 475 CommandService::ACTIVE_ONLY, 476 &existing_browser_action_command, 477 NULL) && 478 // The browser action command may be defaulted to an unassigned 479 // accelerator if a browser action is specified by the extension but a 480 // keybinding is not declared. See 481 // CommandsHandler::MaybeSetBrowserActionDefault. 482 (!new_browser_action_command || 483 new_browser_action_command->accelerator().key_code() == 484 ui::VKEY_UNKNOWN) && 485 !IsCommandShortcutUserModified( 486 extension, 487 existing_browser_action_command.command_name())) { 488 RemoveKeybindingPrefs(extension->id(), 489 existing_browser_action_command.command_name()); 490 } 491 492 Command existing_page_action_command; 493 if (GetPageActionCommand(extension->id(), 494 CommandService::ACTIVE_ONLY, 495 &existing_page_action_command, 496 NULL) && 497 !CommandsInfo::GetPageActionCommand(extension) && 498 !IsCommandShortcutUserModified( 499 extension, 500 existing_page_action_command.command_name())) { 501 RemoveKeybindingPrefs(extension->id(), 502 existing_page_action_command.command_name()); 503 } 504} 505 506void CommandService::AssignKeybindings(const Extension* extension) { 507 const CommandMap* commands = CommandsInfo::GetNamedCommands(extension); 508 if (!commands) 509 return; 510 511 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_); 512 // TODO(wittman): remove use of this pref after M37 hits stable. 513 if (!InitialBindingsHaveBeenAssigned(extension_prefs, extension->id())) 514 SetInitialBindingsHaveBeenAssigned(extension_prefs, extension->id()); 515 516 for (CommandMap::const_iterator iter = commands->begin(); 517 iter != commands->end(); ++iter) { 518 const Command command = iter->second; 519 if (CanAutoAssign(command, extension)) { 520 AddKeybindingPref(command.accelerator(), 521 extension->id(), 522 command.command_name(), 523 false, // Overwriting not allowed. 524 command.global()); 525 } 526 } 527 528 const Command* browser_action_command = 529 CommandsInfo::GetBrowserActionCommand(extension); 530 if (browser_action_command && 531 CanAutoAssign(*browser_action_command, extension)) { 532 AddKeybindingPref(browser_action_command->accelerator(), 533 extension->id(), 534 browser_action_command->command_name(), 535 false, // Overwriting not allowed. 536 false); // Not global. 537 } 538 539 const Command* page_action_command = 540 CommandsInfo::GetPageActionCommand(extension); 541 if (page_action_command && CanAutoAssign(*page_action_command, extension)) { 542 AddKeybindingPref(page_action_command->accelerator(), 543 extension->id(), 544 page_action_command->command_name(), 545 false, // Overwriting not allowed. 546 false); // Not global. 547 } 548} 549 550bool CommandService::CanAutoAssign(const Command &command, 551 const Extension* extension) { 552 // Media Keys are non-exclusive, so allow auto-assigning them. 553 if (Command::IsMediaKey(command.accelerator())) 554 return true; 555 556 // Extensions are allowed to auto-assign updated keys if the user has not 557 // changed from the previous value. 558 if (IsCommandShortcutUserModified(extension, command.command_name())) 559 return false; 560 561 if (command.global()) { 562 using namespace extensions; 563 if (command.command_name() == manifest_values::kBrowserActionCommandEvent || 564 command.command_name() == manifest_values::kPageActionCommandEvent) 565 return false; // Browser and page actions are not global in nature. 566 567 // Global shortcuts are restricted to (Ctrl|Command)+Shift+[0-9]. 568#if defined OS_MACOSX 569 if (!command.accelerator().IsCmdDown()) 570 return false; 571#else 572 if (!command.accelerator().IsCtrlDown()) 573 return false; 574#endif 575 if (!command.accelerator().IsShiftDown()) 576 return false; 577 return (command.accelerator().key_code() >= ui::VKEY_0 && 578 command.accelerator().key_code() <= ui::VKEY_9); 579 } else { 580 // Not a global command, check if Chrome shortcut and whether 581 // we can override it. 582 if (command.accelerator() == 583 chrome::GetPrimaryChromeAcceleratorForCommandId(IDC_BOOKMARK_PAGE) && 584 CommandService::RemovesBookmarkShortcut(extension)) { 585 // If this check fails it either means we have an API to override a 586 // key that isn't a ChromeAccelerator (and the API can therefore be 587 // deprecated) or the IsChromeAccelerator isn't consistently 588 // returning true for all accelerators. 589 DCHECK(chrome::IsChromeAccelerator(command.accelerator(), profile_)); 590 return true; 591 } 592 593 return !chrome::IsChromeAccelerator(command.accelerator(), profile_); 594 } 595} 596 597void CommandService::UpdateExtensionSuggestedCommandPrefs( 598 const Extension* extension) { 599 scoped_ptr<base::DictionaryValue> suggested_key_prefs( 600 new base::DictionaryValue); 601 602 const CommandMap* commands = CommandsInfo::GetNamedCommands(extension); 603 if (commands) { 604 for (CommandMap::const_iterator iter = commands->begin(); 605 iter != commands->end(); ++iter) { 606 const Command command = iter->second; 607 scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue); 608 command_keys->SetString( 609 kSuggestedKey, 610 Command::AcceleratorToString(command.accelerator())); 611 suggested_key_prefs->Set(command.command_name(), command_keys.release()); 612 } 613 } 614 615 const Command* browser_action_command = 616 CommandsInfo::GetBrowserActionCommand(extension); 617 // The browser action command may be defaulted to an unassigned accelerator if 618 // a browser action is specified by the extension but a keybinding is not 619 // declared. See CommandsHandler::MaybeSetBrowserActionDefault. 620 if (browser_action_command && 621 browser_action_command->accelerator().key_code() != ui::VKEY_UNKNOWN) { 622 scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue); 623 command_keys->SetString( 624 kSuggestedKey, 625 Command::AcceleratorToString(browser_action_command->accelerator())); 626 suggested_key_prefs->Set(browser_action_command->command_name(), 627 command_keys.release()); 628 } 629 630 const Command* page_action_command = 631 CommandsInfo::GetPageActionCommand(extension); 632 if (page_action_command) { 633 scoped_ptr<base::DictionaryValue> command_keys(new base::DictionaryValue); 634 command_keys->SetString( 635 kSuggestedKey, 636 Command::AcceleratorToString(page_action_command->accelerator())); 637 suggested_key_prefs->Set(page_action_command->command_name(), 638 command_keys.release()); 639 } 640 641 // Merge into current prefs, if present. 642 MergeSuggestedKeyPrefs(extension->id(), 643 ExtensionPrefs::Get(profile_), 644 suggested_key_prefs.Pass()); 645} 646 647void CommandService::RemoveDefunctExtensionSuggestedCommandPrefs( 648 const Extension* extension) { 649 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_); 650 const base::DictionaryValue* current_prefs = NULL; 651 extension_prefs->ReadPrefAsDictionary(extension->id(), 652 kCommands, 653 ¤t_prefs); 654 655 if (current_prefs) { 656 scoped_ptr<base::DictionaryValue> suggested_key_prefs( 657 current_prefs->DeepCopy()); 658 const CommandMap* named_commands = 659 CommandsInfo::GetNamedCommands(extension); 660 const Command* browser_action_command = 661 CommandsInfo::GetBrowserActionCommand(extension); 662 for (base::DictionaryValue::Iterator it(*current_prefs); 663 !it.IsAtEnd(); it.Advance()) { 664 if (it.key() == manifest_values::kBrowserActionCommandEvent) { 665 // The browser action command may be defaulted to an unassigned 666 // accelerator if a browser action is specified by the extension but a 667 // keybinding is not declared. See 668 // CommandsHandler::MaybeSetBrowserActionDefault. 669 if (!browser_action_command || 670 browser_action_command->accelerator().key_code() == 671 ui::VKEY_UNKNOWN) { 672 suggested_key_prefs->Remove(it.key(), NULL); 673 } 674 } else if (it.key() == manifest_values::kPageActionCommandEvent) { 675 if (!CommandsInfo::GetPageActionCommand(extension)) 676 suggested_key_prefs->Remove(it.key(), NULL); 677 } else if (named_commands) { 678 if (named_commands->find(it.key()) == named_commands->end()) 679 suggested_key_prefs->Remove(it.key(), NULL); 680 } 681 } 682 683 extension_prefs->UpdateExtensionPref(extension->id(), 684 kCommands, 685 suggested_key_prefs.release()); 686 } 687} 688 689bool CommandService::IsCommandShortcutUserModified( 690 const Extension* extension, 691 const std::string& command_name) { 692 // Get the previous suggested key, if any. 693 ui::Accelerator suggested_key; 694 bool suggested_key_was_assigned = false; 695 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_); 696 const base::DictionaryValue* commands_prefs = NULL; 697 const base::DictionaryValue* suggested_key_prefs = NULL; 698 if (extension_prefs->ReadPrefAsDictionary(extension->id(), 699 kCommands, 700 &commands_prefs) && 701 commands_prefs->GetDictionary(command_name, &suggested_key_prefs)) { 702 std::string suggested_key_string; 703 if (suggested_key_prefs->GetString(kSuggestedKey, &suggested_key_string)) { 704 suggested_key = Command::StringToAccelerator(suggested_key_string, 705 command_name); 706 } 707 708 suggested_key_prefs->GetBoolean(kSuggestedKeyWasAssigned, 709 &suggested_key_was_assigned); 710 } 711 712 // Get the active shortcut from the prefs, if any. 713 Command active_command = FindCommandByName(extension->id(), command_name); 714 715 return suggested_key_was_assigned ? 716 active_command.accelerator() != suggested_key : 717 active_command.accelerator().key_code() != ui::VKEY_UNKNOWN; 718} 719 720bool CommandService::IsKeybindingChanging(const Extension* extension, 721 const std::string& command_name) { 722 // Get the new assigned command, if any. 723 Command new_command; 724 if (command_name == manifest_values::kBrowserActionCommandEvent) { 725 new_command = *CommandsInfo::GetBrowserActionCommand(extension); 726 } else if (command_name == manifest_values::kPageActionCommandEvent) { 727 new_command = *CommandsInfo::GetPageActionCommand(extension); 728 } else { // This is a named command. 729 const CommandMap* named_commands = 730 CommandsInfo::GetNamedCommands(extension); 731 if (named_commands) { 732 CommandMap::const_iterator loc = named_commands->find(command_name); 733 if (loc != named_commands->end()) 734 new_command = loc->second; 735 } 736 } 737 738 return Command::StringToAccelerator( 739 GetSuggestedKeyPref(extension, command_name), command_name) != 740 new_command.accelerator(); 741} 742 743std::string CommandService::GetSuggestedKeyPref( 744 const Extension* extension, 745 const std::string& command_name) { 746 // Get the previous suggested key, if any. 747 ui::Accelerator suggested_key; 748 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_); 749 const base::DictionaryValue* commands_prefs = NULL; 750 if (extension_prefs->ReadPrefAsDictionary(extension->id(), 751 kCommands, 752 &commands_prefs)) { 753 const base::DictionaryValue* suggested_key_prefs = NULL; 754 std::string suggested_key; 755 if (commands_prefs->GetDictionary(command_name, &suggested_key_prefs) && 756 suggested_key_prefs->GetString(kSuggestedKey, &suggested_key)) { 757 return suggested_key; 758 } 759 } 760 761 return std::string(); 762} 763 764void CommandService::RemoveKeybindingPrefs(const std::string& extension_id, 765 const std::string& command_name) { 766 DictionaryPrefUpdate updater(profile_->GetPrefs(), 767 prefs::kExtensionCommands); 768 base::DictionaryValue* bindings = updater.Get(); 769 770 typedef std::vector<std::string> KeysToRemove; 771 KeysToRemove keys_to_remove; 772 for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd(); 773 it.Advance()) { 774 // Removal of keybinding preference should be limited to current platform. 775 if (!IsForCurrentPlatform(it.key())) 776 continue; 777 778 const base::DictionaryValue* item = NULL; 779 it.value().GetAsDictionary(&item); 780 781 std::string extension; 782 item->GetString(kExtension, &extension); 783 784 if (extension == extension_id) { 785 // If |command_name| is specified, delete only that command. Otherwise, 786 // delete all commands. 787 if (!command_name.empty()) { 788 std::string command; 789 item->GetString(kCommandName, &command); 790 if (command_name != command) 791 continue; 792 } 793 794 keys_to_remove.push_back(it.key()); 795 } 796 } 797 798 for (KeysToRemove::const_iterator it = keys_to_remove.begin(); 799 it != keys_to_remove.end(); ++it) { 800 std::string key = *it; 801 bindings->Remove(key, NULL); 802 803 std::pair<const std::string, const std::string> details = 804 std::make_pair(extension_id, command_name); 805 content::NotificationService::current()->Notify( 806 chrome::NOTIFICATION_EXTENSION_COMMAND_REMOVED, 807 content::Source<Profile>(profile_), 808 content::Details< 809 std::pair<const std::string, const std::string> >(&details)); 810 } 811} 812 813bool CommandService::GetExtensionActionCommand( 814 const std::string& extension_id, 815 QueryType query_type, 816 Command* command, 817 bool* active, 818 ExtensionCommandType action_type) const { 819 const ExtensionSet& extensions = 820 ExtensionRegistry::Get(profile_)->enabled_extensions(); 821 const Extension* extension = extensions.GetByID(extension_id); 822 CHECK(extension); 823 824 if (active) 825 *active = false; 826 827 const Command* requested_command = NULL; 828 switch (action_type) { 829 case BROWSER_ACTION: 830 requested_command = CommandsInfo::GetBrowserActionCommand(extension); 831 break; 832 case PAGE_ACTION: 833 requested_command = CommandsInfo::GetPageActionCommand(extension); 834 break; 835 case NAMED: 836 NOTREACHED(); 837 return false; 838 } 839 if (!requested_command) 840 return false; 841 842 // Look up to see if the user has overridden how the command should work. 843 Command saved_command = 844 FindCommandByName(extension_id, requested_command->command_name()); 845 ui::Accelerator shortcut_assigned = saved_command.accelerator(); 846 847 if (active) 848 *active = (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN); 849 850 if (query_type == ACTIVE_ONLY && 851 shortcut_assigned.key_code() == ui::VKEY_UNKNOWN) 852 return false; 853 854 *command = *requested_command; 855 if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN) 856 command->set_accelerator(shortcut_assigned); 857 858 return true; 859} 860 861template <> 862void 863BrowserContextKeyedAPIFactory<CommandService>::DeclareFactoryDependencies() { 864 DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance()); 865} 866 867} // namespace extensions 868