blob: 9bbb62460069c7f56c4c02537d9c55a05984bc8f [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/commands/command_service.h"
#include <memory>
#include <string_view>
#include <utility>
#include <vector>
#include "base/lazy_instance.h"
#include "base/observer_list.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_commands_global_registry.h"
#include "chrome/browser/extensions/extension_keybinding_registry.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/accelerator_utils.h"
#include "chrome/common/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "extensions/browser/extension_function_registry.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/api/commands/commands_handler.h"
#include "extensions/common/command.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/feature_switch.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/permissions/permissions_data.h"
#include "ui/base/accelerators/command.h"
namespace extensions {
namespace {
const char kExtension[] = "extension";
const char kCommandName[] = "command_name";
const char kGlobal[] = "global";
// A preference that stores keybinding state associated with extension commands.
const char kCommands[] = "commands";
// Preference key name for saving the extension-suggested key.
const char kSuggestedKey[] = "suggested_key";
// Preference key name for saving whether the extension-suggested key was
// actually assigned.
const char kSuggestedKeyWasAssigned[] = "was_assigned";
std::string GetPlatformKeybindingKeyForAccelerator(
const ui::Accelerator& accelerator,
const ExtensionId& extension_id) {
std::string key = Command::CommandPlatform() + ":" +
Command::AcceleratorToString(accelerator);
// Media keys have a 1-to-many relationship with targets, unlike regular
// shortcut (1-to-1 relationship). That means two or more extensions can
// register for the same media key so the extension ID needs to be added to
// the key to make sure the key is unique.
if (accelerator.IsMediaKey()) {
key += ":" + extension_id;
}
return key;
}
bool IsForCurrentPlatform(const std::string& key) {
return base::StartsWith(key, Command::CommandPlatform() + ":",
base::CompareCase::SENSITIVE);
}
// Merge |suggested_key_prefs| into the saved preferences for the extension. We
// merge rather than overwrite to preserve existing was_assigned preferences.
void MergeSuggestedKeyPrefs(const ExtensionId& extension_id,
ExtensionPrefs* extension_prefs,
base::Value::Dict suggested_key_prefs) {
const base::Value::Dict* current_prefs =
extension_prefs->ReadPrefAsDict(extension_id, kCommands);
if (current_prefs) {
base::Value::Dict new_prefs = current_prefs->Clone();
new_prefs.Merge(std::move(suggested_key_prefs));
suggested_key_prefs = std::move(new_prefs);
}
extension_prefs->UpdateExtensionPref(
extension_id, kCommands, base::Value(std::move(suggested_key_prefs)));
}
// Clears the "was_assigned" preference for a list of `removed_commands`. This
// is called when a keybinding is removed to signify that the suggested key is
// no longer assigned, so it can be auto-assigned again in the future.
void ClearSuggestedKeyWasAssignedPrefs(
const ExtensionId& extension_id,
ExtensionPrefs& extension_prefs,
const std::vector<Command>& removed_commands) {
if (removed_commands.empty()) {
return;
}
ExtensionPrefs::ScopedDictionaryUpdate updater(&extension_prefs, extension_id,
kCommands);
std::unique_ptr<prefs::DictionaryValueUpdate> current_prefs = updater.Get();
for (const Command& removed_command : removed_commands) {
std::unique_ptr<prefs::DictionaryValueUpdate> command_prefs;
if (current_prefs->GetDictionary(removed_command.command_name(),
&command_prefs)) {
command_prefs->Remove(kSuggestedKeyWasAssigned);
}
}
}
// Returns true if a command is relevant for the given `query_type`.
bool IsCommandRelevant(CommandService::QueryType query_type,
bool is_active,
bool user_modified) {
switch (query_type) {
case CommandService::ALL:
return true;
case CommandService::ACTIVE:
return is_active;
case CommandService::ACTIVE_OR_USER_MODIFIED:
// We want to be able to include commands that were explicitly unset by
// the user (via ACTIVE_OR_USER_MODIFIED) so that we don't override
// their preference with the default binding. See crbug.com/436279086.
return is_active || user_modified;
}
}
} // namespace
// static
void CommandService::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterDictionaryPref(
prefs::kExtensionCommands,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
}
CommandService::CommandService(content::BrowserContext* context)
: profile_(Profile::FromBrowserContext(context)) {
extension_registry_observation_.Observe(ExtensionRegistry::Get(profile_));
}
CommandService::~CommandService() {
for (auto& observer : observers_)
observer.OnCommandServiceDestroying();
}
static base::LazyInstance<BrowserContextKeyedAPIFactory<CommandService>>::
DestructorAtExit g_command_service_factory = LAZY_INSTANCE_INITIALIZER;
// static
BrowserContextKeyedAPIFactory<CommandService>*
CommandService::GetFactoryInstance() {
return g_command_service_factory.Pointer();
}
// static
CommandService* CommandService::Get(content::BrowserContext* context) {
return BrowserContextKeyedAPIFactory<CommandService>::Get(context);
}
bool CommandService::GetNamedCommands(const ExtensionId& extension_id,
QueryType type,
CommandScope scope,
ui::CommandMap* command_map) const {
const Extension* extension =
GetExtensionInEnabledOrDisabledExtensions(extension_id);
if (!extension) {
return false;
}
command_map->clear();
const ui::CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
if (!commands)
return false;
for (const auto& named_command : *commands) {
// Look up to see if the user has overridden how the command should work.
Command saved_command =
FindCommandByName(extension_id, named_command.second.command_name());
ui::Accelerator shortcut_assigned = saved_command.accelerator();
bool user_modified = IsCommandShortcutUserModified(
extension, named_command.second.command_name());
bool is_active = shortcut_assigned.key_code() != ui::VKEY_UNKNOWN;
if (!IsCommandRelevant(type, is_active, user_modified)) {
continue;
}
ui::Command command = named_command.second;
if (scope != ANY_SCOPE && ((scope == GLOBAL) != saved_command.global()))
continue;
if (is_active || user_modified) {
command.set_accelerator(shortcut_assigned);
}
command.set_global(saved_command.global());
(*command_map)[named_command.second.command_name()] = command;
}
return !command_map->empty();
}
bool CommandService::AddKeybindingPref(const ui::Accelerator& accelerator,
const ExtensionId& extension_id,
const std::string& command_name,
bool allow_overrides,
bool global) {
// Nothing needs to be done if the existing command is the same as the desired
// new one.
Command existing_command = FindCommandByName(extension_id, command_name);
if (existing_command.command_name() == command_name &&
existing_command.accelerator() == accelerator &&
existing_command.global() == global) {
return true;
}
// Media Keys are allowed to be used by named command only.
DCHECK(!accelerator.IsMediaKey() ||
!Command::IsActionRelatedCommand(command_name));
ScopedDictPrefUpdate updater(profile_->GetPrefs(), prefs::kExtensionCommands);
base::Value::Dict& bindings = updater.Get();
std::string key = GetPlatformKeybindingKeyForAccelerator(accelerator,
extension_id);
if (bindings.Find(key)) {
if (!allow_overrides)
return false; // Already taken.
// If the shortcut has been assigned to another command, it should be
// removed before overriding, so that |ExtensionKeybindingRegistry| can get
// a chance to do clean-up.
const base::Value::Dict* item = bindings.FindDict(key);
const ExtensionId* old_extension_id = item->FindString(kExtension);
const std::string* old_command_name = item->FindString(kCommandName);
RemoveKeybindingPrefs(old_extension_id ? *old_extension_id : std::string(),
old_command_name ? *old_command_name : std::string());
}
// If the command that is taking a new shortcut already has a shortcut, remove
// it before assigning the new one.
if (existing_command.accelerator().key_code() != ui::VKEY_UNKNOWN)
RemoveKeybindingPrefs(extension_id, command_name);
if (accelerator.key_code() != ui::VKEY_UNKNOWN) {
// Set the keybinding pref.
base::Value::Dict keybinding;
keybinding.Set(kExtension, extension_id);
keybinding.Set(kCommandName, command_name);
keybinding.Set(kGlobal, global);
bindings.Set(key, std::move(keybinding));
}
// Set the was_assigned pref for the suggested key.
base::Value::Dict command_keys;
command_keys.Set(kSuggestedKeyWasAssigned, true);
base::Value::Dict suggested_key_prefs;
suggested_key_prefs.Set(command_name, base::Value(std::move(command_keys)));
MergeSuggestedKeyPrefs(extension_id, ExtensionPrefs::Get(profile_),
std::move(suggested_key_prefs));
// Fetch the newly-updated command, and notify the observers.
Command command = FindCommandByName(extension_id, command_name);
for (auto& observer : observers_) {
if (accelerator.key_code() != ui::VKEY_UNKNOWN) {
observer.OnExtensionCommandAdded(extension_id, command);
} else {
observer.OnExtensionCommandRemoved(extension_id, command);
}
}
return true;
}
void CommandService::OnExtensionWillBeInstalled(
content::BrowserContext* browser_context,
const Extension* extension,
bool is_update,
const std::string& old_name) {
UpdateKeybindings(extension);
}
void CommandService::OnExtensionUninstalled(
content::BrowserContext* browser_context,
const Extension* extension,
extensions::UninstallReason reason) {
// Adding a component extensions will only trigger install the first time on a
// clean profile or on a version increase (see
// ComponentLoader::AddComponentExtension). It will, however, always trigger
// an uninstall on removal. See http://crbug.com/458612. Isolate this case and
// ignore it.
if (reason == extensions::UNINSTALL_REASON_COMPONENT_REMOVED)
return;
RemoveKeybindingPrefs(extension->id(), std::string());
}
void CommandService::UpdateKeybindingPrefs(const ExtensionId& extension_id,
const std::string& command_name,
const std::string& keystroke) {
Command command = FindCommandByName(extension_id, command_name);
// The extension command might be assigned another shortcut. Remove that
// shortcut before proceeding.
RemoveKeybindingPrefs(extension_id, command_name);
ui::Accelerator accelerator =
Command::StringToAccelerator(keystroke, command_name);
AddKeybindingPref(accelerator, extension_id, command_name,
true, command.global());
}
bool CommandService::SetScope(const ExtensionId& extension_id,
const std::string& command_name,
bool global) {
Command command = FindCommandByName(extension_id, command_name);
if (global == command.global())
return false;
// Pre-existing shortcuts must be removed before proceeding because the
// handlers for global and non-global extensions are not one and the same.
RemoveKeybindingPrefs(extension_id, command_name);
AddKeybindingPref(command.accelerator(), extension_id,
command_name, true, global);
return true;
}
Command CommandService::FindCommandByName(const ExtensionId& extension_id,
const std::string& command) const {
const base::Value::Dict& bindings =
profile_->GetPrefs()->GetDict(prefs::kExtensionCommands);
for (const auto it : bindings) {
const ExtensionId* extension = it.second.GetDict().FindString(kExtension);
if (!extension || *extension != extension_id)
continue;
const std::string* command_name =
it.second.GetDict().FindString(kCommandName);
if (!command_name || *command_name != command)
continue;
// Format stored in Preferences is: "Platform:Shortcut[:ExtensionId]".
std::string shortcut = it.first;
if (!IsForCurrentPlatform(shortcut))
continue;
std::optional<bool> global = it.second.GetDict().FindBool(kGlobal);
std::vector<std::string_view> tokens = base::SplitStringPiece(
shortcut, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
CHECK(tokens.size() >= 2);
return Command(*command_name, std::u16string(), std::string(tokens[1]),
global.value_or(false));
}
return Command();
}
void CommandService::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void CommandService::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
const Extension* CommandService::GetExtensionInEnabledOrDisabledExtensions(
const ExtensionId& extension_id) const {
const ExtensionSet& enabled_extensions =
ExtensionRegistry::Get(profile_)->enabled_extensions();
const Extension* enabled_extension = enabled_extensions.GetByID(extension_id);
if (enabled_extension) {
return enabled_extension;
}
const ExtensionSet& disabled_extensions =
ExtensionRegistry::Get(profile_)->disabled_extensions();
return disabled_extensions.GetByID(extension_id);
}
bool CommandService::IsUpgradeFromMV2ToMV3(
const Extension* extension,
const std::string& existing_command_name) const {
bool browser_or_page_action_command_in_bindings =
existing_command_name == manifest_values::kBrowserActionCommandEvent ||
existing_command_name == manifest_values::kPageActionCommandEvent;
return browser_or_page_action_command_in_bindings &&
CommandsInfo::GetActionCommand(extension);
}
void CommandService::UpdateKeybindings(const Extension* extension) {
if (GetExtensionInEnabledOrDisabledExtensions(extension->id())) {
RemoveRelinquishedKeybindings(extension);
}
AssignKeybindings(extension);
UpdateExtensionSuggestedCommandPrefs(extension);
RemoveDefunctExtensionSuggestedCommandPrefs(extension);
}
void CommandService::RemoveRelinquishedKeybindings(const Extension* extension) {
// Remove keybindings if they have been removed by the extension and the user
// has not modified them.
ui::CommandMap existing_command_map;
if (GetNamedCommands(extension->id(), CommandService::ACTIVE_OR_USER_MODIFIED,
CommandService::REGULAR, &existing_command_map)) {
const ui::CommandMap* new_command_map =
CommandsInfo::GetNamedCommands(extension);
for (ui::CommandMap::const_iterator it = existing_command_map.begin();
it != existing_command_map.end(); ++it) {
std::string command_name = it->first;
if (new_command_map->find(command_name) == new_command_map->end() &&
!IsCommandShortcutUserModified(extension, command_name)) {
RemoveKeybindingPrefs(extension->id(), command_name);
}
}
}
auto remove_overrides_if_unused = [this, extension](ActionInfo::Type type) {
Command existing_command;
if (!GetExtensionActionCommand(extension->id(), type,
CommandService::ACTIVE_OR_USER_MODIFIED,
&existing_command, nullptr)) {
// No keybindings to remove.
return;
}
const std::string& existing_command_name = existing_command.command_name();
bool is_shortcut_user_modified =
IsCommandShortcutUserModified(extension, existing_command_name);
bool is_upgrade_from_mv2_to_mv3 =
IsUpgradeFromMV2ToMV3(extension, existing_command_name);
if (is_shortcut_user_modified && is_upgrade_from_mv2_to_mv3) {
// TODO(jlulejian): Could this be an out param to IsUpgradeFromMV2ToMV3?
const Command* action_command = CommandsInfo::GetActionCommand(extension);
AddKeybindingPref(existing_command.accelerator(), extension->id(),
action_command->command_name(), true,
action_command->global());
} else if (is_shortcut_user_modified) {
// Don't relinquish user-modified shortcuts otherwise.
return;
}
const Command* new_command = nullptr;
switch (type) {
case ActionInfo::Type::kAction:
new_command = CommandsInfo::GetActionCommand(extension);
break;
case ActionInfo::Type::kBrowser:
new_command = CommandsInfo::GetBrowserActionCommand(extension);
break;
case ActionInfo::Type::kPage:
new_command = CommandsInfo::GetPageActionCommand(extension);
break;
}
// The shortcuts should be removed if there is no command specified in the
// new extension, or the only command specified is synthesized (i.e.,
// assigned to ui::VKEY_UNKNOWN), which happens for browser action commands.
// See CommandsHandler::MaybeSetActionDefault().
// TODO(devlin): Should this logic apply to ActionInfo::Type::kAction?
// See https://crbug.com/893373.
const bool should_relinquish =
!new_command ||
(type == ActionInfo::Type::kBrowser &&
new_command->accelerator().key_code() == ui::VKEY_UNKNOWN);
if (!should_relinquish)
return;
RemoveKeybindingPrefs(extension->id(), existing_command_name);
};
// TODO(crbug.com/40124879): Extensions shouldn't be able to specify
// commands for actions they don't have, so we should just be able to query
// for a single action type.
for (ActionInfo::Type type :
{ActionInfo::Type::kAction, ActionInfo::Type::kBrowser,
ActionInfo::Type::kPage}) {
remove_overrides_if_unused(type);
}
}
void CommandService::AssignKeybindings(const Extension* extension) {
const ui::CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
if (!commands)
return;
for (const auto& named_command : *commands) {
const ui::Command command = named_command.second;
if (CanAutoAssign(command, extension)) {
AddKeybindingPref(command.accelerator(),
extension->id(),
command.command_name(),
false, // Overwriting not allowed.
command.global());
}
}
const Command* browser_action_command =
CommandsInfo::GetBrowserActionCommand(extension);
if (browser_action_command &&
CanAutoAssign(*browser_action_command, extension)) {
AddKeybindingPref(browser_action_command->accelerator(),
extension->id(),
browser_action_command->command_name(),
false, // Overwriting not allowed.
false); // Not global.
}
const Command* page_action_command =
CommandsInfo::GetPageActionCommand(extension);
if (page_action_command && CanAutoAssign(*page_action_command, extension)) {
AddKeybindingPref(page_action_command->accelerator(),
extension->id(),
page_action_command->command_name(),
false, // Overwriting not allowed.
false); // Not global.
}
const Command* action_command = CommandsInfo::GetActionCommand(extension);
if (action_command && CanAutoAssign(*action_command, extension)) {
AddKeybindingPref(action_command->accelerator(), extension->id(),
action_command->command_name(),
false, // Overwriting not allowed.
false); // Not global.
}
}
bool CommandService::CanAutoAssign(const ui::Command& command,
const Extension* extension) {
// Extensions are allowed to auto-assign updated keys if the user has not
// changed from the previous value.
if (IsCommandShortcutUserModified(extension, command.command_name()))
return false;
// Media Keys are non-exclusive, so allow auto-assigning them.
if (command.accelerator().IsMediaKey()) {
return true;
}
if (command.global()) {
if (Command::IsActionRelatedCommand(command.command_name()))
return false; // Browser and page actions are not global in nature.
if (extension->permissions_data()->HasAPIPermission(
mojom::APIPermissionID::kCommandsAccessibility))
return true;
// Global shortcuts are restricted to (Ctrl|Command)+Shift+[0-9].
#if BUILDFLAG(IS_MAC)
if (!command.accelerator().IsCmdDown())
return false;
#else
if (!command.accelerator().IsCtrlDown())
return false;
#endif
if (!command.accelerator().IsShiftDown())
return false;
return (command.accelerator().key_code() >= ui::VKEY_0 &&
command.accelerator().key_code() <= ui::VKEY_9);
}
// Not a global command, check if the command is a Chrome shortcut.
return !chrome::IsChromeAccelerator(command.accelerator());
}
void CommandService::UpdateExtensionSuggestedCommandPrefs(
const Extension* extension) {
base::Value::Dict suggested_key_prefs;
const ui::CommandMap* commands = CommandsInfo::GetNamedCommands(extension);
if (commands) {
for (const auto& named_command : *commands) {
const ui::Command command = named_command.second;
base::Value::Dict command_keys;
command_keys.Set(kSuggestedKey,
Command::AcceleratorToString(command.accelerator()));
suggested_key_prefs.Set(command.command_name(), std::move(command_keys));
}
}
const Command* browser_action_command =
CommandsInfo::GetBrowserActionCommand(extension);
// The browser action command may be defaulted to an unassigned accelerator if
// a browser action is specified by the extension but a keybinding is not
// declared. See CommandsHandler::MaybeSetActionDefault.
if (browser_action_command &&
browser_action_command->accelerator().key_code() != ui::VKEY_UNKNOWN) {
base::Value::Dict command_keys;
command_keys.Set(kSuggestedKey, Command::AcceleratorToString(
browser_action_command->accelerator()));
suggested_key_prefs.Set(browser_action_command->command_name(),
std::move(command_keys));
}
const Command* action_command = CommandsInfo::GetActionCommand(extension);
if (action_command &&
action_command->accelerator().key_code() != ui::VKEY_UNKNOWN) {
base::Value::Dict command_keys;
command_keys.Set(kSuggestedKey, Command::AcceleratorToString(
action_command->accelerator()));
suggested_key_prefs.Set(action_command->command_name(),
std::move(command_keys));
}
const Command* page_action_command =
CommandsInfo::GetPageActionCommand(extension);
if (page_action_command) {
base::Value::Dict command_keys;
command_keys.Set(kSuggestedKey, Command::AcceleratorToString(
page_action_command->accelerator()));
suggested_key_prefs.Set(page_action_command->command_name(),
std::move(command_keys));
}
// Merge into current prefs, if present.
MergeSuggestedKeyPrefs(extension->id(), ExtensionPrefs::Get(profile_),
std::move(suggested_key_prefs));
}
void CommandService::RemoveDefunctExtensionSuggestedCommandPrefs(
const Extension* extension) {
ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
const base::Value::Dict* current_prefs =
extension_prefs->ReadPrefAsDict(extension->id(), kCommands);
if (current_prefs) {
base::Value::Dict suggested_key_prefs = current_prefs->Clone();
const ui::CommandMap* named_commands =
CommandsInfo::GetNamedCommands(extension);
const Command* browser_action_command =
CommandsInfo::GetBrowserActionCommand(extension);
for (const auto [key, _] : *current_prefs) {
if (key == manifest_values::kBrowserActionCommandEvent) {
// The browser action command may be defaulted to an unassigned
// accelerator if a browser action is specified by the extension but a
// keybinding is not declared. See
// CommandsHandler::MaybeSetActionDefault.
if (!browser_action_command ||
browser_action_command->accelerator().key_code() ==
ui::VKEY_UNKNOWN) {
suggested_key_prefs.Remove(key);
}
} else if (key == manifest_values::kPageActionCommandEvent) {
if (!CommandsInfo::GetPageActionCommand(extension))
suggested_key_prefs.Remove(key);
} else if (key == manifest_values::kActionCommandEvent) {
if (!CommandsInfo::GetActionCommand(extension))
suggested_key_prefs.Remove(key);
} else if (named_commands) {
if (named_commands->find(key) == named_commands->end())
suggested_key_prefs.Remove(key);
}
}
extension_prefs->UpdateExtensionPref(
extension->id(), kCommands,
base::Value(std::move(suggested_key_prefs)));
}
}
bool CommandService::IsCommandShortcutUserModified(
const Extension* extension,
const std::string& command_name) const {
// Get the previous suggested key, if any.
ui::Accelerator suggested_key;
std::optional<bool> suggested_key_was_assigned;
ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
const base::Value::Dict* commands_prefs =
extension_prefs->ReadPrefAsDict(extension->id(), kCommands);
if (commands_prefs) {
const base::Value::Dict* suggested_key_prefs =
commands_prefs->FindDict(command_name);
if (suggested_key_prefs) {
const std::string* suggested_key_string =
suggested_key_prefs->FindString(kSuggestedKey);
if (suggested_key_string) {
suggested_key =
Command::StringToAccelerator(*suggested_key_string, command_name);
}
suggested_key_was_assigned =
suggested_key_prefs->FindBool(kSuggestedKeyWasAssigned);
}
}
// Get the active shortcut from the prefs, if any.
Command active_command = FindCommandByName(extension->id(), command_name);
return suggested_key_was_assigned.value_or(false)
? active_command.accelerator() != suggested_key
: active_command.accelerator().key_code() != ui::VKEY_UNKNOWN;
}
void CommandService::RemoveKeybindingPrefs(const ExtensionId& extension_id,
const std::string& command_name) {
ScopedDictPrefUpdate updater(profile_->GetPrefs(), prefs::kExtensionCommands);
base::Value::Dict& bindings = updater.Get();
typedef std::vector<std::string> KeysToRemove;
KeysToRemove keys_to_remove;
std::vector<Command> removed_commands;
for (const auto it : bindings) {
// Removal of keybinding preference should be limited to current platform.
if (!IsForCurrentPlatform(it.first))
continue;
const base::Value::Dict& dict = it.second.GetDict();
const ExtensionId* extension = dict.FindString(kExtension);
if (extension && *extension == extension_id) {
// If |command_name| is specified, delete only that command. Otherwise,
// delete all commands.
const std::string* command = dict.FindString(kCommandName);
if (command && !command_name.empty() && command_name != *command)
continue;
removed_commands.push_back(FindCommandByName(extension_id, *command));
keys_to_remove.push_back(it.first);
}
}
for (KeysToRemove::const_iterator it = keys_to_remove.begin();
it != keys_to_remove.end(); ++it) {
std::string key = *it;
bindings.Remove(key);
}
// When a keybinding is removed, we also clear the "was_assigned" bit in the
// extension prefs.
ClearSuggestedKeyWasAssignedPrefs(
extension_id, *ExtensionPrefs::Get(profile_), removed_commands);
for (const Command& removed_command : removed_commands) {
for (auto& observer : observers_)
observer.OnExtensionCommandRemoved(extension_id, removed_command);
}
}
bool CommandService::GetExtensionActionCommand(const ExtensionId& extension_id,
ActionInfo::Type action_type,
QueryType query_type,
Command* command,
bool* active) const {
const Extension* extension =
GetExtensionInEnabledOrDisabledExtensions(extension_id);
if (!extension) {
return false;
}
if (active)
*active = false;
const Command* requested_command = nullptr;
switch (action_type) {
case ActionInfo::Type::kBrowser:
requested_command = CommandsInfo::GetBrowserActionCommand(extension);
break;
case ActionInfo::Type::kPage:
requested_command = CommandsInfo::GetPageActionCommand(extension);
break;
case ActionInfo::Type::kAction:
requested_command = CommandsInfo::GetActionCommand(extension);
break;
}
if (!requested_command)
return false;
// Look up to see if the user has overridden how the command should work.
Command saved_command =
FindCommandByName(extension_id, requested_command->command_name());
ui::Accelerator shortcut_assigned = saved_command.accelerator();
bool is_active = shortcut_assigned.key_code() != ui::VKEY_UNKNOWN;
if (active)
*active = is_active;
bool user_modified = IsCommandShortcutUserModified(
extension, requested_command->command_name());
if (!IsCommandRelevant(query_type, is_active, user_modified)) {
return false;
}
*command = *requested_command;
if (is_active || user_modified) {
command->set_accelerator(shortcut_assigned);
}
return true;
}
template <>
void BrowserContextKeyedAPIFactory<
CommandService>::DeclareFactoryDependencies() {
DependsOn(ExtensionCommandsGlobalRegistry::GetFactoryInstance());
}
} // namespace extensions