| // 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/lazy_instance.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/extensions/api/commands/commands.h" |
| #include "chrome/browser/extensions/extension_function_registry.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/browser/ui/accelerator_utils.h" |
| #include "chrome/common/extensions/api/commands/commands_handler.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/user_prefs/pref_registry_syncable.h" |
| #include "content/public/browser/notification_details.h" |
| #include "content/public/browser/notification_service.h" |
| |
| using extensions::Extension; |
| using extensions::ExtensionPrefs; |
| |
| namespace { |
| |
| const char kExtension[] = "extension"; |
| const char kCommandName[] = "command_name"; |
| |
| // A preference that indicates that the initial keybindings for the given |
| // extension have been set. |
| const char kInitialBindingsHaveBeenAssigned[] = "initial_keybindings_set"; |
| |
| std::string GetPlatformKeybindingKeyForAccelerator( |
| const ui::Accelerator& accelerator) { |
| return extensions::Command::CommandPlatform() + ":" + |
| extensions::Command::AcceleratorToString(accelerator); |
| } |
| |
| void SetInitialBindingsHaveBeenAssigned( |
| ExtensionPrefs* prefs, const std::string& extension_id) { |
| prefs->UpdateExtensionPref(extension_id, kInitialBindingsHaveBeenAssigned, |
| new base::FundamentalValue(true)); |
| } |
| |
| bool InitialBindingsHaveBeenAssigned( |
| const ExtensionPrefs* prefs, const std::string& extension_id) { |
| bool assigned = false; |
| if (!prefs || !prefs->ReadPrefAsBoolean(extension_id, |
| kInitialBindingsHaveBeenAssigned, |
| &assigned)) |
| return false; |
| |
| return assigned; |
| } |
| |
| } // namespace |
| |
| namespace extensions { |
| |
| // static |
| void CommandService::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| registry->RegisterDictionaryPref( |
| prefs::kExtensionCommands, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| } |
| |
| CommandService::CommandService(Profile* profile) |
| : profile_(profile) { |
| ExtensionFunctionRegistry::GetInstance()-> |
| RegisterFunction<GetAllCommandsFunction>(); |
| |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED, |
| content::Source<Profile>(profile)); |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED, |
| content::Source<Profile>(profile)); |
| } |
| |
| CommandService::~CommandService() { |
| } |
| |
| static base::LazyInstance<ProfileKeyedAPIFactory<CommandService> > |
| g_factory = LAZY_INSTANCE_INITIALIZER; |
| |
| // static |
| ProfileKeyedAPIFactory<CommandService>* CommandService::GetFactoryInstance() { |
| return &g_factory.Get(); |
| } |
| |
| // static |
| CommandService* CommandService::Get(Profile* profile) { |
| return ProfileKeyedAPIFactory<CommandService>::GetForProfile(profile); |
| } |
| |
| 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 = |
| CommandsInfo::GetNamedCommands(extension); |
| if (!commands) |
| 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); |
| base::DictionaryValue* bindings = updater.Get(); |
| |
| std::string key = GetPlatformKeybindingKeyForAccelerator(accelerator); |
| |
| if (!allow_overrides && bindings->HasKey(key)) |
| return false; // Already taken. |
| |
| base::DictionaryValue* keybinding = new base::DictionaryValue(); |
| keybinding->SetString(kExtension, extension_id); |
| keybinding->SetString(kCommandName, command_name); |
| |
| bindings->Set(key, keybinding); |
| |
| std::pair<const std::string, const std::string> details = |
| std::make_pair(extension_id, command_name); |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED, |
| content::Source<Profile>(profile_), |
| content::Details< |
| std::pair<const std::string, const std::string> >(&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<const InstalledExtensionInfo>(details)->extension); |
| break; |
| case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: |
| RemoveKeybindingPrefs( |
| content::Details<const Extension>(details)->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 base::DictionaryValue* bindings = |
| profile_->GetPrefs()->GetDictionary(prefs::kExtensionCommands); |
| for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd(); |
| it.Advance()) { |
| const base::DictionaryValue* item = NULL; |
| it.value().GetAsDictionary(&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.key(); |
| 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 = |
| CommandsInfo::GetNamedCommands(extension); |
| if (!commands) |
| return; |
| |
| ExtensionService* extension_service = |
| ExtensionSystem::Get(profile_)->extension_service(); |
| ExtensionPrefs* extension_prefs = extension_service->extension_prefs(); |
| if (InitialBindingsHaveBeenAssigned(extension_prefs, extension->id())) |
| return; |
| SetInitialBindingsHaveBeenAssigned(extension_prefs, extension->id()); |
| |
| extensions::CommandMap::const_iterator iter = commands->begin(); |
| for (; iter != commands->end(); ++iter) { |
| if (!chrome::IsChromeAccelerator( |
| iter->second.accelerator(), profile_)) { |
| AddKeybindingPref(iter->second.accelerator(), |
| extension->id(), |
| iter->second.command_name(), |
| false); // Overwriting not allowed. |
| } |
| } |
| |
| const extensions::Command* browser_action_command = |
| CommandsInfo::GetBrowserActionCommand(extension); |
| if (browser_action_command) { |
| if (!chrome::IsChromeAccelerator( |
| browser_action_command->accelerator(), profile_)) { |
| AddKeybindingPref(browser_action_command->accelerator(), |
| extension->id(), |
| browser_action_command->command_name(), |
| false); // Overwriting not allowed. |
| } |
| } |
| |
| const extensions::Command* page_action_command = |
| CommandsInfo::GetPageActionCommand(extension); |
| if (page_action_command) { |
| if (!chrome::IsChromeAccelerator( |
| page_action_command->accelerator(), profile_)) { |
| AddKeybindingPref(page_action_command->accelerator(), |
| extension->id(), |
| page_action_command->command_name(), |
| false); // Overwriting not allowed. |
| } |
| } |
| |
| const extensions::Command* script_badge_command = |
| CommandsInfo::GetScriptBadgeCommand(extension); |
| if (script_badge_command) { |
| if (!chrome::IsChromeAccelerator( |
| script_badge_command->accelerator(), profile_)) { |
| 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); |
| base::DictionaryValue* bindings = updater.Get(); |
| |
| typedef std::vector<std::string> KeysToRemove; |
| KeysToRemove keys_to_remove; |
| for (base::DictionaryValue::Iterator it(*bindings); !it.IsAtEnd(); |
| it.Advance()) { |
| const base::DictionaryValue* item = NULL; |
| it.value().GetAsDictionary(&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(it.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<const std::string, const std::string> details = |
| std::make_pair(extension_id, command_name); |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_EXTENSION_COMMAND_REMOVED, |
| content::Source<Profile>(profile_), |
| content::Details< |
| std::pair<const std::string, const std::string> >(&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 = CommandsInfo::GetBrowserActionCommand(extension); |
| break; |
| case PAGE_ACTION: |
| requested_command = CommandsInfo::GetPageActionCommand(extension); |
| break; |
| case SCRIPT_BADGE: |
| requested_command = CommandsInfo::GetScriptBadgeCommand(extension); |
| 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 |