// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/extensions/api/commands/command_service.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "chrome/browser/extensions/extension_keybinding_registry.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/prefs/scoped_user_pref_update.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/extensions/extension_manifest_constants.h" #include "chrome/common/pref_names.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_service.h" using extensions::Extension; namespace { const char kExtension[] = "extension"; const char kCommandName[] = "command_name"; std::string GetPlatformKeybindingKeyForAccelerator( const ui::Accelerator& accelerator) { return extensions::Command::CommandPlatform() + ":" + UTF16ToUTF8(accelerator.GetShortcutText()); } } // namespace namespace extensions { // static void CommandService::RegisterUserPrefs( PrefService* user_prefs) { user_prefs->RegisterDictionaryPref(prefs::kExtensionCommands, PrefService::SYNCABLE_PREF); } CommandService::CommandService(Profile* profile) : profile_(profile) { registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED, content::Source(profile)); registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED, content::Source(profile)); } CommandService::~CommandService() { } bool CommandService::GetBrowserActionCommand( const std::string& extension_id, QueryType type, extensions::Command* command, bool* active) { return GetExtensionActionCommand( extension_id, type, command, active, BROWSER_ACTION); } bool CommandService::GetPageActionCommand( const std::string& extension_id, QueryType type, extensions::Command* command, bool* active) { return GetExtensionActionCommand( extension_id, type, command, active, PAGE_ACTION); } bool CommandService::GetScriptBadgeCommand( const std::string& extension_id, QueryType type, extensions::Command* command, bool* active) { return GetExtensionActionCommand( extension_id, type, command, active, SCRIPT_BADGE); } bool CommandService::GetNamedCommands(const std::string& extension_id, QueryType type, extensions::CommandMap* command_map) { const ExtensionSet* extensions = ExtensionSystem::Get(profile_)->extension_service()->extensions(); const Extension* extension = extensions->GetByID(extension_id); CHECK(extension); command_map->clear(); const extensions::CommandMap& commands = extension->named_commands(); if (commands.empty()) return false; extensions::CommandMap::const_iterator iter = commands.begin(); for (; iter != commands.end(); ++iter) { ui::Accelerator shortcut_assigned = FindShortcutForCommand(extension_id, iter->second.command_name()); if (type == ACTIVE_ONLY && shortcut_assigned.key_code() == ui::VKEY_UNKNOWN) continue; extensions::Command command = iter->second; if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN) command.set_accelerator(shortcut_assigned); (*command_map)[iter->second.command_name()] = command; } return true; } bool CommandService::AddKeybindingPref( const ui::Accelerator& accelerator, std::string extension_id, std::string command_name, bool allow_overrides) { if (accelerator.key_code() == ui::VKEY_UNKNOWN) return false; DictionaryPrefUpdate updater(profile_->GetPrefs(), prefs::kExtensionCommands); DictionaryValue* bindings = updater.Get(); std::string key = GetPlatformKeybindingKeyForAccelerator(accelerator); if (!allow_overrides && bindings->HasKey(key)) return false; // Already taken. DictionaryValue* keybinding = new DictionaryValue(); keybinding->SetString(kExtension, extension_id); keybinding->SetString(kCommandName, command_name); bindings->Set(key, keybinding); std::pair details = std::make_pair(extension_id, command_name); content::NotificationService::current()->Notify( chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED, content::Source(profile_), content::Details< std::pair >(&details)); return true; } void CommandService::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { switch (type) { case chrome::NOTIFICATION_EXTENSION_INSTALLED: AssignInitialKeybindings( content::Details(details).ptr()); break; case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: RemoveKeybindingPrefs( content::Details(details).ptr()->id(), std::string()); break; default: NOTREACHED(); break; } } void CommandService::UpdateKeybindingPrefs(const std::string& extension_id, const std::string& command_name, const std::string& keystroke) { // The extension command might be assigned another shortcut. Remove that // shortcut before proceeding. RemoveKeybindingPrefs(extension_id, command_name); ui::Accelerator accelerator = Command::StringToAccelerator(keystroke); AddKeybindingPref(accelerator, extension_id, command_name, true); } ui::Accelerator CommandService::FindShortcutForCommand( const std::string& extension_id, const std::string& command) { const DictionaryValue* bindings = profile_->GetPrefs()->GetDictionary(prefs::kExtensionCommands); for (DictionaryValue::key_iterator it = bindings->begin_keys(); it != bindings->end_keys(); ++it) { const DictionaryValue* item = NULL; bindings->GetDictionary(*it, &item); std::string extension; item->GetString(kExtension, &extension); if (extension != extension_id) continue; std::string command_name; item->GetString(kCommandName, &command_name); if (command != command_name) continue; std::string shortcut = *it; if (StartsWithASCII(shortcut, Command::CommandPlatform() + ":", true)) shortcut = shortcut.substr(Command::CommandPlatform().length() + 1); return Command::StringToAccelerator(shortcut); } return ui::Accelerator(); } void CommandService::AssignInitialKeybindings(const Extension* extension) { const extensions::CommandMap& commands = extension->named_commands(); extensions::CommandMap::const_iterator iter = commands.begin(); for (; iter != commands.end(); ++iter) { AddKeybindingPref(iter->second.accelerator(), extension->id(), iter->second.command_name(), false); // Overwriting not allowed. } const extensions::Command* browser_action_command = extension->browser_action_command(); if (browser_action_command) { AddKeybindingPref(browser_action_command->accelerator(), extension->id(), browser_action_command->command_name(), false); // Overwriting not allowed. } const extensions::Command* page_action_command = extension->page_action_command(); if (page_action_command) { AddKeybindingPref(page_action_command->accelerator(), extension->id(), page_action_command->command_name(), false); // Overwriting not allowed. } const extensions::Command* script_badge_command = extension->script_badge_command(); if (script_badge_command) { AddKeybindingPref(script_badge_command->accelerator(), extension->id(), script_badge_command->command_name(), false); // Overwriting not allowed. } } void CommandService::RemoveKeybindingPrefs(const std::string& extension_id, const std::string& command_name) { DictionaryPrefUpdate updater(profile_->GetPrefs(), prefs::kExtensionCommands); DictionaryValue* bindings = updater.Get(); typedef std::vector KeysToRemove; KeysToRemove keys_to_remove; for (DictionaryValue::key_iterator it = bindings->begin_keys(); it != bindings->end_keys(); ++it) { std::string key = *it; DictionaryValue* item = NULL; bindings->GetDictionary(key, &item); std::string extension; item->GetString(kExtension, &extension); if (extension == extension_id) { // If |command_name| is specified, delete only that command. Otherwise, // delete all commands. if (!command_name.empty()) { std::string command; item->GetString(kCommandName, &command); if (command_name != command) continue; } keys_to_remove.push_back(key); } } for (KeysToRemove::const_iterator it = keys_to_remove.begin(); it != keys_to_remove.end(); ++it) { std::string key = *it; bindings->Remove(key, NULL); std::pair details = std::make_pair(extension_id, command_name); content::NotificationService::current()->Notify( chrome::NOTIFICATION_EXTENSION_COMMAND_REMOVED, content::Source(profile_), content::Details< std::pair >(&details)); } } bool CommandService::GetExtensionActionCommand( const std::string& extension_id, QueryType query_type, extensions::Command* command, bool* active, ExtensionActionType action_type) { ExtensionService* service = ExtensionSystem::Get(profile_)->extension_service(); if (!service) return false; // Can happen in tests. const ExtensionSet* extensions = service->extensions(); const Extension* extension = extensions->GetByID(extension_id); CHECK(extension); if (active) *active = false; const extensions::Command* requested_command = NULL; switch (action_type) { case BROWSER_ACTION: requested_command = extension->browser_action_command(); break; case PAGE_ACTION: requested_command = extension->page_action_command(); break; case SCRIPT_BADGE: requested_command = extension->script_badge_command(); break; } if (!requested_command) return false; ui::Accelerator shortcut_assigned = FindShortcutForCommand(extension_id, requested_command->command_name()); if (active) *active = (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN); if (query_type == ACTIVE_ONLY && shortcut_assigned.key_code() == ui::VKEY_UNKNOWN) return false; *command = *requested_command; if (shortcut_assigned.key_code() != ui::VKEY_UNKNOWN) command->set_accelerator(shortcut_assigned); return true; } } // namespace extensions