[go: nahoru, domu]

blob: 5092f40b5cabf95ebaac2f338b59361a00dd9f63 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.h"
#include <map>
#include <memory>
#include <string>
#include <variant>
#include <vector>
#include "ash/accelerators/accelerator_controller_impl.h"
#include "ash/accelerators/ash_accelerator_configuration.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/cpp/accelerator_configuration.h"
#include "ash/public/cpp/accelerators.h"
#include "ash/public/mojom/accelerator_configuration.mojom.h"
#include "ash/public/mojom/accelerator_info.mojom-shared.h"
#include "ash/public/mojom/accelerator_info.mojom.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/test/ash_test_base.h"
#include "ash/webui/shortcut_customization_ui/backend/accelerator_layout_table.h"
#include "ash/webui/shortcut_customization_ui/mojom/shortcut_customization.mojom-forward.h"
#include "ash/webui/shortcut_customization_ui/mojom/shortcut_customization.mojom-test-utils.h"
#include "ash/webui/shortcut_customization_ui/mojom/shortcut_customization.mojom.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/ash/components/test/ash_test_suite.h"
#include "device/udev_linux/fake_udev_loader.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/ime/ash/input_method_manager.h"
#include "ui/base/ime/ash/mock_input_method_manager.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_features.h"
#include "ui/events/ash/keyboard_capability.h"
#include "ui/events/devices/device_data_manager_test_api.h"
#include "ui/events/devices/input_device.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
namespace ash {
using NonConfigurableActionToParts =
const std::map<NonConfigurableActions,
const std::vector<mojom::TextAcceleratorPartPtr&>>;
namespace {
using shortcut_customization::mojom::AcceleratorResultDataPtr;
using shortcut_customization::mojom::SimpleAccelerator;
using shortcut_customization::mojom::SimpleAcceleratorPtr;
using mojom::AcceleratorConfigResult;
constexpr char kKbdTopRowPropertyName[] = "CROS_KEYBOARD_TOP_ROW_LAYOUT";
constexpr char kKbdTopRowLayout2Tag[] = "2";
class FakeDeviceManager {
public:
FakeDeviceManager() = default;
FakeDeviceManager(const FakeDeviceManager&) = delete;
FakeDeviceManager& operator=(const FakeDeviceManager&) = delete;
~FakeDeviceManager() = default;
// Add a fake keyboard to DeviceDataManagerTestApi and provide layout info to
// fake udev.
void AddFakeKeyboard(const ui::InputDevice& fake_keyboard,
const std::string& layout) {
fake_keyboard_devices_.push_back(fake_keyboard);
ui::DeviceDataManagerTestApi().SetKeyboardDevices({});
ui::DeviceDataManagerTestApi().SetKeyboardDevices(fake_keyboard_devices_);
ui::DeviceDataManagerTestApi().OnDeviceListsComplete();
std::map<std::string, std::string> sysfs_properties;
std::map<std::string, std::string> sysfs_attributes;
sysfs_properties[kKbdTopRowPropertyName] = layout;
fake_udev_.AddFakeDevice(fake_keyboard.name, fake_keyboard.sys_path.value(),
/*subsystem=*/"input", /*devnode=*/absl::nullopt,
/*devtype=*/absl::nullopt,
std::move(sysfs_attributes),
std::move(sysfs_properties));
}
void RemoveAllDevices() {
fake_udev_.Reset();
fake_keyboard_devices_.clear();
}
private:
testing::FakeUdevLoader fake_udev_;
std::vector<ui::InputDevice> fake_keyboard_devices_;
};
class FakeAcceleratorsUpdatedObserver
: public shortcut_ui::AcceleratorConfigurationProvider::
AcceleratorsUpdatedObserver {
public:
FakeAcceleratorsUpdatedObserver() = default;
~FakeAcceleratorsUpdatedObserver() override = default;
int num_times_notified() const { return num_times_notified_; }
void clear_num_times_notified() { num_times_notified_ = 0; }
// AcceleratorConfigurationProvider::AcceleratorsUpdatedObserver:
void OnAcceleratorsUpdated(
shortcut_ui::AcceleratorConfigurationProvider::AcceleratorConfigurationMap
config) override {
++num_times_notified_;
}
private:
int num_times_notified_ = 0;
};
class FakeAcceleratorsUpdatedMojoObserver
: public shortcut_customization::mojom::AcceleratorsUpdatedObserver {
public:
void OnAcceleratorsUpdated(
shortcut_ui::AcceleratorConfigurationProvider::AcceleratorConfigurationMap
config) override {
config_ = std::move(config);
++num_times_notified_;
}
mojo::PendingRemote<
shortcut_customization::mojom::AcceleratorsUpdatedObserver>
pending_remote() {
return receiver_.BindNewPipeAndPassRemote();
}
int num_times_notified() { return num_times_notified_; }
void clear_num_times_notified() { num_times_notified_ = 0; }
shortcut_ui::AcceleratorConfigurationProvider::AcceleratorConfigurationMap
config() {
return mojo::Clone(config_);
}
private:
mojo::Receiver<shortcut_customization::mojom::AcceleratorsUpdatedObserver>
receiver_{this};
shortcut_ui::AcceleratorConfigurationProvider::AcceleratorConfigurationMap
config_;
int num_times_notified_ = 0;
};
bool AreAcceleratorsEqual(const ui::Accelerator& expected_accelerator,
const mojom::AcceleratorInfoPtr& actual_info) {
const bool accelerator_equals =
expected_accelerator ==
actual_info->layout_properties->get_standard_accelerator()->accelerator;
const bool key_display_equals =
ash::GetKeyDisplay(expected_accelerator.key_code()) ==
actual_info->layout_properties->get_standard_accelerator()->key_display;
return accelerator_equals && key_display_equals;
}
bool CompareAccelerators(const ash::AcceleratorData& expected_data,
const mojom::AcceleratorInfoPtr& actual_info) {
ui::Accelerator expected_accelerator(expected_data.keycode,
expected_data.modifiers);
return AreAcceleratorsEqual(expected_accelerator, actual_info);
}
bool CompareAccelerators(const ui::Accelerator& expected_accelerator,
const mojom::AcceleratorInfoPtr& actual_info) {
return AreAcceleratorsEqual(expected_accelerator, actual_info);
}
void CompareInputDevices(const ui::InputDevice& expected,
const ui::InputDevice& actual) {
EXPECT_EQ(expected.type, actual.type);
EXPECT_EQ(expected.id, actual.id);
EXPECT_EQ(expected.name, actual.name);
}
void ExpectMojomAcceleratorsEqual(
ash::mojom::AcceleratorSource source,
const base::span<const ash::AcceleratorData>& expected,
const ash::shortcut_ui::AcceleratorConfigurationProvider::
AcceleratorConfigurationMap& actual_config) {
const auto& source_config_iter = actual_config.find(source);
EXPECT_TRUE(source_config_iter != actual_config.end());
for (const auto& [action_id, actual_accels] : source_config_iter->second) {
for (const auto& actual_info : actual_accels) {
bool found_match = false;
for (const auto& expected_data : expected) {
found_match =
CompareAccelerators(expected_data, mojo::Clone(actual_info));
if (found_match) {
break;
}
}
EXPECT_TRUE(found_match);
}
}
}
// Validates that the passed in layout infos have matching accelerator layouts
// in `kAcceleratorLayouts`. If this throws an expectation error it means that
// the there is a inconsistency between the layouts in `kAcceleratorLayouts` and
// the data provided by `AcceleratorConfigurationProvider`.
void ValidateAcceleratorLayouts(
const std::vector<ash::mojom::AcceleratorLayoutInfoPtr>&
actual_layout_infos) {
for (const auto& actual : actual_layout_infos) {
// Iterate through `kAcceleratorLayouts` to find the matching action.
bool found_match = false;
for (const auto& expected_layout : kAcceleratorLayouts) {
if (expected_layout.action_id == actual->action &&
expected_layout.source == actual->source) {
EXPECT_EQ(expected_layout.category, actual->category);
EXPECT_EQ(expected_layout.sub_category, actual->sub_category);
EXPECT_EQ(expected_layout.layout_style, actual->style);
EXPECT_EQ(expected_layout.source, actual->source);
EXPECT_EQ(
l10n_util::GetStringUTF16(expected_layout.description_string_id),
actual->description);
found_match = true;
break;
}
}
EXPECT_TRUE(found_match);
}
}
void ValidateTextAccelerators(const TextAcceleratorPart& lhs,
const mojom::TextAcceleratorPartPtr& rhs) {
EXPECT_EQ(lhs.text, rhs->text);
EXPECT_EQ(lhs.type, rhs->type);
}
std::vector<mojom::TextAcceleratorPartPtr> RemovePlainTextParts(
const std::vector<mojom::TextAcceleratorPartPtr>& parts) {
std::vector<mojom::TextAcceleratorPartPtr> res;
for (const auto& part : parts) {
if (part->type == mojom::TextAcceleratorPartType::kPlainText) {
continue;
}
res.push_back(mojo::Clone(part));
}
return res;
}
std::vector<std::u16string> SplitStringOnOffsets(
const std::u16string& input,
const std::vector<size_t>& offsets) {
DCHECK(std::is_sorted(offsets.begin(), offsets.end()));
std::vector<std::u16string> parts;
// At most there will be len(offsets) + 1 text parts.
parts.reserve(offsets.size() + 1);
size_t upto = 0;
for (auto offset : offsets) {
DCHECK_LE(offset, input.size());
if (offset == upto) {
continue;
}
DCHECK(offset >= upto);
parts.push_back(input.substr(upto, offset - upto));
upto = offset;
}
// Handles the case where there's plain text after the last replacement.
if (upto < input.size()) {
parts.push_back(input.substr(upto));
}
return parts;
}
} // namespace
namespace shortcut_ui {
class AcceleratorConfigurationProviderTest : public AshTestBase {
public:
AcceleratorConfigurationProviderTest() = default;
~AcceleratorConfigurationProviderTest() override = default;
class TestInputMethodManager : public input_method::MockInputMethodManager {
public:
void AddObserver(
input_method::InputMethodManager::Observer* observer) override {
observers_.AddObserver(observer);
}
void RemoveObserver(
input_method::InputMethodManager::Observer* observer) override {
observers_.RemoveObserver(observer);
}
// Calls all observers with Observer::InputMethodChanged
void NotifyInputMethodChanged() {
for (auto& observer : observers_) {
observer.InputMethodChanged(
/*manager=*/this, /*profile=*/nullptr, /*show_message=*/false);
}
}
base::ObserverList<InputMethodManager::Observer>::Unchecked observers_;
};
// AshTestBase:
void SetUp() override {
scoped_feature_list_.InitWithFeatures(
{::features::kImprovedKeyboardShortcuts,
::features::kShortcutCustomization},
{});
input_method_manager_ = new TestInputMethodManager();
input_method::InputMethodManager::Initialize(input_method_manager_);
ui::ResourceBundle::CleanupSharedInstance();
AshTestSuite::LoadTestResources();
AshTestBase::SetUp();
provider_ = std::make_unique<AcceleratorConfigurationProvider>();
provider_->AddObserver(&observer_);
// After the provider is constructed, the observer should not have been
// notified yet.
EXPECT_EQ(0, observer_.num_times_notified());
non_configurable_actions_map_ =
provider_->GetNonConfigurableAcceleratorsForTesting();
fake_keyboard_manager_ = std::make_unique<FakeDeviceManager>();
// Add a fake layout2 keyboard.
ui::InputDevice fake_keyboard(
/*id=*/1, /*type=*/ui::InputDeviceType::INPUT_DEVICE_BLUETOOTH,
/*name=*/"fake_Keyboard");
fake_keyboard.sys_path = base::FilePath("path1");
fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard,
kKbdTopRowLayout2Tag);
provider_->ignore_layouts_for_testing_ = true;
base::RunLoop().RunUntilIdle();
// After adding a fake keyboard, clear the observer call count.
observer_.clear_num_times_notified();
EXPECT_EQ(0, observer_.num_times_notified());
}
void TearDown() override {
fake_keyboard_manager_->RemoveAllDevices();
provider_->RemoveObserver(&observer_);
// `provider_` has a dependency on `input_method_manager_`.
provider_.reset();
AshTestBase::TearDown();
input_method::InputMethodManager::Shutdown();
input_method_manager_ = nullptr;
}
protected:
const std::vector<ui::InputDevice>& GetConnectedKeyboards() {
return provider_->connected_keyboards_;
}
void SetUpObserver(FakeAcceleratorsUpdatedMojoObserver* mojo_observer) {
provider_->AddObserver(mojo_observer->pending_remote());
base::RunLoop().RunUntilIdle();
}
void SetLayoutDetailsMap(
const std::vector<AcceleratorLayoutDetails>& layouts) {
provider_->SetLayoutDetailsMapForTesting(layouts);
}
const std::vector<ui::Accelerator>& GetAcceleratorsForAction(int action_id) {
return non_configurable_actions_map_
.find(static_cast<ash::NonConfigurableActions>(action_id))
->second.accelerators.value();
}
const std::vector<TextAcceleratorPart>& GetReplacementsForAction(
int action_id) {
return non_configurable_actions_map_
.find(static_cast<ash::NonConfigurableActions>(action_id))
->second.replacements.value();
}
bool TextAccelContainsReplacements(int action_id) {
return non_configurable_actions_map_
.find(static_cast<ash::NonConfigurableActions>(action_id))
->second.replacements.has_value();
}
int GetMessageIdForTextAccel(int action_id) {
return non_configurable_actions_map_
.find(static_cast<ash::NonConfigurableActions>(action_id))
->second.message_id.value();
}
const std::vector<ui::Accelerator>& GetNonConfigurableAcceleratorsForActionId(
uint32_t id) {
const auto accelerator_iter =
provider_->id_to_non_configurable_accelerators_.find(id);
DCHECK(accelerator_iter !=
provider_->id_to_non_configurable_accelerators_.end());
return accelerator_iter->second;
}
uint32_t GetNonConfigurableIdFromAccelerator(ui::Accelerator accelerator) {
return provider_->non_configurable_accelerator_to_id_.Get(accelerator);
}
std::unique_ptr<AcceleratorConfigurationProvider> provider_;
NonConfigurableActionsMap non_configurable_actions_map_;
base::test::ScopedFeatureList scoped_feature_list_;
// Test global singleton. Delete is handled by InputMethodManager::Shutdown().
base::raw_ptr<TestInputMethodManager> input_method_manager_;
std::unique_ptr<FakeDeviceManager> fake_keyboard_manager_;
FakeAcceleratorsUpdatedObserver observer_;
};
TEST_F(AcceleratorConfigurationProviderTest, ResetReceiverOnBindInterface) {
mojo::Remote<shortcut_customization::mojom::AcceleratorConfigurationProvider>
remote;
provider_->BindInterface(remote.BindNewPipeAndPassReceiver());
base::RunLoop().RunUntilIdle();
remote.reset();
provider_->BindInterface(remote.BindNewPipeAndPassReceiver());
base::RunLoop().RunUntilIdle();
}
TEST_F(AcceleratorConfigurationProviderTest, BrowserIsMutable) {
// Verify that requesting IsMutable state for Browser accelerators returns
// false.
provider_->IsMutable(ash::mojom::AcceleratorSource::kBrowser,
base::BindLambdaForTesting([&](bool is_mutable) {
// Browser accelerators are not mutable.
EXPECT_FALSE(is_mutable);
}));
base::RunLoop().RunUntilIdle();
}
TEST_F(AcceleratorConfigurationProviderTest, AshIsMutable) {
// Verify that requesting IsMutable state for Ash accelerators returns true.
provider_->IsMutable(ash::mojom::AcceleratorSource::kAsh,
base::BindLambdaForTesting([&](bool is_mutable) {
// Ash accelerators are mutable.
EXPECT_TRUE(is_mutable);
}));
base::RunLoop().RunUntilIdle();
}
TEST_F(AcceleratorConfigurationProviderTest, InitialAccelInitCalls) {
FakeAcceleratorsUpdatedMojoObserver mojo_observer;
SetUpObserver(&mojo_observer);
EXPECT_EQ(0, mojo_observer.num_times_notified());
EXPECT_EQ(0, observer_.num_times_notified());
Shell::Get()->ash_accelerator_configuration()->Initialize();
base::RunLoop().RunUntilIdle();
// Observer is initially notified twice, one for ash accelerators and the
// other for deprecated accelerators.
EXPECT_EQ(2, mojo_observer.num_times_notified());
EXPECT_EQ(2, observer_.num_times_notified());
}
TEST_F(AcceleratorConfigurationProviderTest, AshAcceleratorsUpdated) {
FakeAcceleratorsUpdatedMojoObserver mojo_observer;
SetUpObserver(&mojo_observer);
EXPECT_EQ(0, mojo_observer.num_times_notified());
EXPECT_EQ(0, observer_.num_times_notified());
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_TAB, ui::EF_ALT_DOWN,
CYCLE_FORWARD_MRU},
{/*trigger_on_press=*/true, ui::VKEY_TAB,
ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN, CYCLE_BACKWARD_MRU},
{/*trigger_on_press=*/true, ui::VKEY_ESCAPE, ui::EF_COMMAND_DOWN,
SHOW_TASK_MANAGER},
};
Shell::Get()->ash_accelerator_configuration()->Initialize(test_data);
base::RunLoop().RunUntilIdle();
// Notified once after instantiating the accelerators.
EXPECT_EQ(1, mojo_observer.num_times_notified());
EXPECT_EQ(1, observer_.num_times_notified());
// Verify observer received the correct accelerators.
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh, test_data,
mojo_observer.config());
// Initialize with a new set of accelerators.
const AcceleratorData updated_test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_ZOOM, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
{/*trigger_on_press=*/true, ui::VKEY_ZOOM, ui::EF_ALT_DOWN,
SWAP_PRIMARY_DISPLAY},
{/*trigger_on_press=*/true, ui::VKEY_MEDIA_LAUNCH_APP1,
ui::EF_CONTROL_DOWN, TAKE_SCREENSHOT},
};
Shell::Get()->ash_accelerator_configuration()->Initialize(updated_test_data);
base::RunLoop().RunUntilIdle();
// Observers are notified again after a new set of accelerators are provided.
EXPECT_EQ(2, mojo_observer.num_times_notified());
EXPECT_EQ(2, observer_.num_times_notified());
// Verify observer has been updated with the new set of accelerators.
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh,
updated_test_data, mojo_observer.config());
}
TEST_F(AcceleratorConfigurationProviderTest, ConnectedKeyboardsUpdated) {
// Ensure there are no keyboards plugged in at first.
ui::DeviceDataManagerTestApi().SetKeyboardDevices({});
FakeAcceleratorsUpdatedMojoObserver mojo_observer;
SetUpObserver(&mojo_observer);
EXPECT_EQ(0, mojo_observer.num_times_notified());
const std::vector<ui::InputDevice>& actual_devices = GetConnectedKeyboards();
EXPECT_EQ(0u, actual_devices.size());
ui::InputDevice expected_test_keyboard(
1, ui::InputDeviceType::INPUT_DEVICE_INTERNAL, "Keyboard");
std::vector<ui::InputDevice> keyboard_devices;
keyboard_devices.push_back(expected_test_keyboard);
ui::DeviceDataManagerTestApi().SetKeyboardDevices(keyboard_devices);
const std::vector<ui::InputDevice>& actual_devices2 = GetConnectedKeyboards();
EXPECT_EQ(1u, actual_devices2.size());
CompareInputDevices(expected_test_keyboard, actual_devices[0]);
base::RunLoop().RunUntilIdle();
// Adding a new keyboard should trigger the UpdatedAccelerators observer.
EXPECT_EQ(1, mojo_observer.num_times_notified());
}
TEST_F(AcceleratorConfigurationProviderTest, ValidateAllAcceleratorLayouts) {
// Initialize with all default accelerators.
Shell::Get()->ash_accelerator_configuration()->Initialize();
base::RunLoop().RunUntilIdle();
// Get all default accelerator layout infos and verify that they have the
// correctly mapped layout details
provider_->GetAcceleratorLayoutInfos(base::BindLambdaForTesting(
[&](std::vector<mojom::AcceleratorLayoutInfoPtr> actual_layout_infos) {
ValidateAcceleratorLayouts(actual_layout_infos);
}));
// Validate that the non-callback version of this method returns correct data.
ValidateAcceleratorLayouts(provider_->GetAcceleratorLayoutInfos());
}
TEST_F(AcceleratorConfigurationProviderTest, FilterOutHiddenAccelerators) {
FakeAcceleratorsUpdatedMojoObserver mojo_observer;
SetUpObserver(&mojo_observer);
EXPECT_EQ(0, mojo_observer.num_times_notified());
EXPECT_EQ(0, observer_.num_times_notified());
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_TAB, ui::EF_ALT_DOWN,
CYCLE_FORWARD_MRU},
{/*trigger_on_press=*/true, ui::VKEY_ESCAPE, ui::EF_COMMAND_DOWN,
SHOW_TASK_MANAGER},
// Accelerators that should be hidden from display.
{/*trigger_on_press=*/true, ui::VKEY_BROWSER_SEARCH, ui::EF_SHIFT_DOWN,
TOGGLE_APP_LIST},
{/*trigger_on_press=*/false, ui::VKEY_LWIN, ui::EF_SHIFT_DOWN,
TOGGLE_APP_LIST},
{/*trigger_on_press=*/true, ui::VKEY_F14, ui::EF_NONE,
SHOW_SHORTCUT_VIEWER},
{/*trigger_on_press=*/true, ui::VKEY_OEM_2,
ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
SHOW_SHORTCUT_VIEWER},
{/*trigger_on_press=*/true, ui::VKEY_OEM_2,
ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, OPEN_GET_HELP},
{/*trigger_on_press=*/false, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
SWITCH_TO_LAST_USED_IME},
{/*trigger_on_press=*/true, ui::VKEY_ZOOM, ui::EF_SHIFT_DOWN,
TOGGLE_FULLSCREEN},
};
// Initialize with a set of accelerators that include hidden accelerators.
Shell::Get()->ash_accelerator_configuration()->Initialize(test_data);
base::RunLoop().RunUntilIdle();
const AcceleratorData expected_test_data[]{
{/*trigger_on_press=*/true, ui::VKEY_TAB, ui::EF_ALT_DOWN,
CYCLE_FORWARD_MRU},
{/*trigger_on_press=*/true, ui::VKEY_ESCAPE, ui::EF_COMMAND_DOWN,
SHOW_TASK_MANAGER},
};
EXPECT_EQ(1, observer_.num_times_notified());
// Verify observer won't receive hidden accelerators.
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh,
expected_test_data, mojo_observer.config());
}
TEST_F(AcceleratorConfigurationProviderTest, TopRowKeyAcceleratorRemapped) {
// Add a fake layout2 keyboard.
ui::InputDevice fake_keyboard(
/*id=*/1, /*type=*/ui::InputDeviceType::INPUT_DEVICE_BLUETOOTH,
/*name=*/"fake_Keyboard");
fake_keyboard.sys_path = base::FilePath("path1");
fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard, kKbdTopRowLayout2Tag);
FakeAcceleratorsUpdatedMojoObserver mojo_observer;
SetUpObserver(&mojo_observer);
EXPECT_EQ(0, mojo_observer.num_times_notified());
// Top row keys are not function keys by default.
EXPECT_FALSE(Shell::Get()->keyboard_capability()->TopRowKeysAreFKeys());
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_TAB, ui::EF_ALT_DOWN,
CYCLE_FORWARD_MRU},
{/*trigger_on_press=*/true, ui::VKEY_TAB,
ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN, CYCLE_BACKWARD_MRU},
{/*trigger_on_press=*/true, ui::VKEY_ESCAPE, ui::EF_COMMAND_DOWN,
SHOW_TASK_MANAGER},
{/*trigger_on_press=*/true, ui::VKEY_ZOOM, ui::EF_SHIFT_DOWN,
TOGGLE_FULLSCREEN},
{/*trigger_on_press=*/true, ui::VKEY_ZOOM, ui::EF_NONE,
TOGGLE_FULLSCREEN},
{/*trigger_on_press=*/true, ui::VKEY_BRIGHTNESS_UP, ui::EF_NONE,
BRIGHTNESS_UP},
{/*trigger_on_press=*/true, ui::VKEY_BRIGHTNESS_UP, ui::EF_ALT_DOWN,
KEYBOARD_BRIGHTNESS_UP},
// Fake accelerator data - [search] is part of the original accelerator.
{/*trigger_on_press=*/true, ui::VKEY_BRIGHTNESS_UP,
ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN, KEYBOARD_BRIGHTNESS_UP},
{/*trigger_on_press=*/true, ui::VKEY_ZOOM, ui::EF_COMMAND_DOWN,
TOGGLE_FULLSCREEN},
};
Shell::Get()->ash_accelerator_configuration()->Initialize(test_data);
base::RunLoop().RunUntilIdle();
// Notified once after instantiating the accelerators.
EXPECT_EQ(1, mojo_observer.num_times_notified());
// Verify observer received the correct accelerators.
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh, test_data,
mojo_observer.config());
// Enable TopRowKeysAreFKeys.
Shell::Get()->session_controller()->GetActivePrefService()->SetBoolean(
prefs::kSendFunctionKeys, true);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(Shell::Get()->keyboard_capability()->TopRowKeysAreFKeys());
EXPECT_EQ(2, mojo_observer.num_times_notified());
// Initialize the same test_data again, but with
// TopRowKeysAsFunctionKeysEnabled.
Shell::Get()->ash_accelerator_configuration()->Initialize(test_data);
base::RunLoop().RunUntilIdle();
// When TopRowKeysAsFunctionKeys enabled, top row shortcut will become [Fkey]
// + [search] + [modifier].
const AcceleratorData expected_test_data[] = {
// alt + tab -> alt + tab
{/*trigger_on_press=*/true, ui::VKEY_TAB, ui::EF_ALT_DOWN,
CYCLE_FORWARD_MRU},
// alt + shift + tab -> alt + shift + tab
{/*trigger_on_press=*/true, ui::VKEY_TAB,
ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN, CYCLE_BACKWARD_MRU},
// search + esc -> search + esc
{/*trigger_on_press=*/true, ui::VKEY_ESCAPE, ui::EF_COMMAND_DOWN,
SHOW_TASK_MANAGER},
// shift + zoom -> shift + search + VKEY_ZOOM
{/*trigger_on_press=*/true, ui::VKEY_ZOOM,
ui::EF_SHIFT_DOWN | ui::EF_COMMAND_DOWN, TOGGLE_FULLSCREEN},
// zoom -> search + VKEY_ZOOM
{/*trigger_on_press=*/true, ui::VKEY_ZOOM, ui::EF_COMMAND_DOWN,
TOGGLE_FULLSCREEN},
// brightness_up -> search + VKEY_BRIGHTNESS_UP
{/*trigger_on_press=*/true, ui::VKEY_BRIGHTNESS_UP, ui::EF_COMMAND_DOWN,
BRIGHTNESS_UP},
// alt + brightness_up -> alt + search + VKEY_BRIGHTNESS_UP
{/*trigger_on_press=*/true, ui::VKEY_BRIGHTNESS_UP,
ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN, KEYBOARD_BRIGHTNESS_UP},
};
EXPECT_EQ(3, mojo_observer.num_times_notified());
// Verify observer received the top-row-remapped accelerators.
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh,
expected_test_data, mojo_observer.config());
}
TEST_F(AcceleratorConfigurationProviderTest, SixPackKeyAcceleratorRemapped) {
FakeAcceleratorsUpdatedMojoObserver mojo_observer;
SetUpObserver(&mojo_observer);
EXPECT_EQ(0, mojo_observer.num_times_notified());
// kImprovedKeyboardShortcuts is enabled.
EXPECT_TRUE(::features::IsImprovedKeyboardShortcutsEnabled());
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_TAB, ui::EF_ALT_DOWN,
CYCLE_FORWARD_MRU},
// Below are fake shortcuts, only used for testing.
{/*trigger_on_press=*/true, ui::VKEY_DELETE, ui::EF_NONE,
CYCLE_BACKWARD_MRU},
{/*trigger_on_press=*/true, ui::VKEY_HOME, ui::EF_NONE,
TAKE_WINDOW_SCREENSHOT},
{/*trigger_on_press=*/true, ui::VKEY_HOME, ui::EF_ALT_DOWN,
KEYBOARD_BRIGHTNESS_UP},
{/*trigger_on_press=*/true, ui::VKEY_END, ui::EF_SHIFT_DOWN,
DISABLE_CAPS_LOCK},
{/*trigger_on_press=*/true, ui::VKEY_NEXT, ui::EF_ALT_DOWN, NEW_TAB},
{/*trigger_on_press=*/true, ui::VKEY_INSERT, ui::EF_NONE, NEW_TAB},
{/*trigger_on_press=*/true, ui::VKEY_INSERT, ui::EF_ALT_DOWN, NEW_TAB},
// When [search] is part of the original accelerator.
{/*trigger_on_press=*/true, ui::VKEY_HOME,
ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN, KEYBOARD_BRIGHTNESS_UP},
{/*trigger_on_press=*/true, ui::VKEY_END,
ui::EF_SHIFT_DOWN | ui::EF_COMMAND_DOWN, DISABLE_CAPS_LOCK},
// Edge case: [Shift] + [Delete].
{/*trigger_on_press=*/true, ui::VKEY_DELETE, ui::EF_SHIFT_DOWN,
DESKS_NEW_DESK},
};
const AcceleratorData expected_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_TAB, ui::EF_ALT_DOWN,
CYCLE_FORWARD_MRU},
// Below are fake shortcuts, only used for testing.
{/*trigger_on_press=*/true, ui::VKEY_DELETE, ui::EF_NONE,
CYCLE_BACKWARD_MRU},
{/*trigger_on_press=*/true, ui::VKEY_HOME, ui::EF_NONE,
TAKE_WINDOW_SCREENSHOT},
{/*trigger_on_press=*/true, ui::VKEY_HOME, ui::EF_ALT_DOWN,
KEYBOARD_BRIGHTNESS_UP},
{/*trigger_on_press=*/true, ui::VKEY_END, ui::EF_SHIFT_DOWN,
DISABLE_CAPS_LOCK},
{/*trigger_on_press=*/true, ui::VKEY_NEXT, ui::EF_ALT_DOWN, NEW_TAB},
{/*trigger_on_press=*/true, ui::VKEY_INSERT, ui::EF_NONE, NEW_TAB},
{/*trigger_on_press=*/true, ui::VKEY_INSERT, ui::EF_ALT_DOWN, NEW_TAB},
// When [search] is part of the original accelerator. No remapping is
// done. Search+Alt+Home -> Search+Alt+Home.
{/*trigger_on_press=*/true, ui::VKEY_HOME,
ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN, KEYBOARD_BRIGHTNESS_UP},
// Search+Shift+End -> Search+Shift+End.
{/*trigger_on_press=*/true, ui::VKEY_END,
ui::EF_SHIFT_DOWN | ui::EF_COMMAND_DOWN, DISABLE_CAPS_LOCK},
// Edge case: [Shift] + [Delete]. It should not remapped to
// [Shift]+[Search]+[Back](aka, Insert).
// Shift+Delete -> Shift+Delete
{/*trigger_on_press=*/true, ui::VKEY_DELETE, ui::EF_SHIFT_DOWN,
DESKS_NEW_DESK},
// Additional six-pack remapped accelerators.
// Delete -> Search+Backspace
{/*trigger_on_press=*/true, ui::VKEY_BACK, ui::EF_COMMAND_DOWN,
CYCLE_BACKWARD_MRU},
// Home -> Search+Left
{/*trigger_on_press=*/true, ui::VKEY_LEFT, ui::EF_COMMAND_DOWN,
TAKE_WINDOW_SCREENSHOT},
// Alt+Home -> Search+Alt+Left
{/*trigger_on_press=*/true, ui::VKEY_LEFT,
ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN, KEYBOARD_BRIGHTNESS_UP},
// Shift+End -> Search+Shift+Right
{/*trigger_on_press=*/true, ui::VKEY_RIGHT,
ui::EF_SHIFT_DOWN | ui::EF_COMMAND_DOWN, DISABLE_CAPS_LOCK},
// Alt+Next -> Search+Alt+Down
{/*trigger_on_press=*/true, ui::VKEY_DOWN,
ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN, NEW_TAB},
// Insert -> Search+Shift+Backspace
{/*trigger_on_press=*/true, ui::VKEY_BACK,
ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN, NEW_TAB},
// Alt+Insert -> Search+Shift+Alt+Backspace
{/*trigger_on_press=*/true, ui::VKEY_BACK,
ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN, NEW_TAB},
};
Shell::Get()->ash_accelerator_configuration()->Initialize(test_data);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, mojo_observer.num_times_notified());
// Verify observer received the correct remapped accelerators.
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh, expected_data,
mojo_observer.config());
}
TEST_F(AcceleratorConfigurationProviderTest,
ReversedSixPackKeyAcceleratorRemapped) {
FakeAcceleratorsUpdatedMojoObserver mojo_observer;
SetUpObserver(&mojo_observer);
EXPECT_EQ(0, mojo_observer.num_times_notified());
// kImprovedKeyboardShortcuts is enabled.
EXPECT_TRUE(::features::IsImprovedKeyboardShortcutsEnabled());
const AcceleratorData test_data[] = {
// Below are fake shortcuts, only used for testing.
{/*trigger_on_press=*/true, ui::VKEY_LEFT, ui::EF_ALT_DOWN,
CYCLE_BACKWARD_MRU},
{/*trigger_on_press=*/true, ui::VKEY_LEFT, ui::EF_COMMAND_DOWN, NEW_TAB},
{/*trigger_on_press=*/true, ui::VKEY_TAB,
ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, DISABLE_CAPS_LOCK},
{/*trigger_on_press=*/true, ui::VKEY_LEFT,
ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, KEYBOARD_BRIGHTNESS_UP},
{/*trigger_on_press=*/true, ui::VKEY_LEFT,
ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN,
TAKE_WINDOW_SCREENSHOT},
{/*trigger_on_press=*/true, ui::VKEY_UP,
ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, DESKS_NEW_DESK},
{/*trigger_on_press=*/true, ui::VKEY_RIGHT,
ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN,
TOGGLE_FULLSCREEN},
{/*trigger_on_press=*/true, ui::VKEY_DOWN,
ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, KEYBOARD_BRIGHTNESS_DOWN},
{/*trigger_on_press=*/true, ui::VKEY_BACK,
ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, CYCLE_FORWARD_MRU},
{/*trigger_on_press=*/true, ui::VKEY_BACK,
ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN,
SHOW_TASK_MANAGER},
{/*trigger_on_press=*/true, ui::VKEY_BACK,
ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN, BRIGHTNESS_UP},
};
const AcceleratorData expected_data[] = {
// When [Search] is not part of original accelerator, no remapping is
// done. [Left]+[Alt]>[Left]+[Alt].
{/*trigger_on_press=*/true, ui::VKEY_LEFT, ui::EF_ALT_DOWN,
CYCLE_BACKWARD_MRU},
// When [Search] is the only modifier, [Left]+[Search]->[Home].
{/*trigger_on_press=*/true, ui::VKEY_LEFT, ui::EF_COMMAND_DOWN, NEW_TAB},
{/*trigger_on_press=*/true, ui::VKEY_HOME, ui::EF_NONE, NEW_TAB},
// When key code is not reversed six pack key, no remapping is done.
// [Tab]+[Search]+[Alt]->[Tab]+[Search]+[Alt].
{/*trigger_on_press=*/true, ui::VKEY_TAB,
ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, DISABLE_CAPS_LOCK},
// [Left]+[Search]+[Alt]->[Home]+[Alt].
{/*trigger_on_press=*/true, ui::VKEY_LEFT,
ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, KEYBOARD_BRIGHTNESS_UP},
{/*trigger_on_press=*/true, ui::VKEY_HOME, ui::EF_ALT_DOWN,
KEYBOARD_BRIGHTNESS_UP},
// [Left]+[Search]+[Shift]+[Alt]->[Home]+[Shift]+[Alt].
{/*trigger_on_press=*/true, ui::VKEY_LEFT,
ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN,
TAKE_WINDOW_SCREENSHOT},
{/*trigger_on_press=*/true, ui::VKEY_HOME,
ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN, TAKE_WINDOW_SCREENSHOT},
// [Up]+[Search]+[Alt]->[Prior]+[Alt].
{/*trigger_on_press=*/true, ui::VKEY_UP,
ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, DESKS_NEW_DESK},
{/*trigger_on_press=*/true, ui::VKEY_PRIOR, ui::EF_ALT_DOWN,
DESKS_NEW_DESK},
// [Right]+[Search]+[Shift]+[Alt]->[End]+[Shift]+[Alt].
{/*trigger_on_press=*/true, ui::VKEY_RIGHT,
ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN,
TOGGLE_FULLSCREEN},
{/*trigger_on_press=*/true, ui::VKEY_END,
ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN, TOGGLE_FULLSCREEN},
// [Down]+[Search]+[Alt]->[Next]+[Alt].
{/*trigger_on_press=*/true, ui::VKEY_DOWN,
ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, KEYBOARD_BRIGHTNESS_DOWN},
{/*trigger_on_press=*/true, ui::VKEY_NEXT, ui::EF_ALT_DOWN,
KEYBOARD_BRIGHTNESS_DOWN},
// [Back]+[Search]+[Alt]->[Delete]+[Alt].
{/*trigger_on_press=*/true, ui::VKEY_BACK,
ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN, CYCLE_FORWARD_MRU},
{/*trigger_on_press=*/true, ui::VKEY_DELETE, ui::EF_ALT_DOWN,
CYCLE_FORWARD_MRU},
// [Back]+[Search]+[Shift]+[Alt]->[Insert]+[Alt].
{/*trigger_on_press=*/true, ui::VKEY_BACK,
ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN,
SHOW_TASK_MANAGER},
{/*trigger_on_press=*/true, ui::VKEY_INSERT, ui::EF_ALT_DOWN,
SHOW_TASK_MANAGER},
// [Back]+[Search]+[Shift] -> [Insert].
{/*trigger_on_press=*/true, ui::VKEY_BACK,
ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN, BRIGHTNESS_UP},
{/*trigger_on_press=*/true, ui::VKEY_INSERT, ui::EF_NONE, BRIGHTNESS_UP},
};
Shell::Get()->ash_accelerator_configuration()->Initialize(test_data);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, mojo_observer.num_times_notified());
// Verify observer received the correct remapped accelerators.
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh, expected_data,
mojo_observer.config());
}
TEST_F(AcceleratorConfigurationProviderTest, InputMethodChanged) {
FakeAcceleratorsUpdatedMojoObserver mojo_observer;
SetUpObserver(&mojo_observer);
EXPECT_EQ(0, mojo_observer.num_times_notified());
Shell::Get()->ash_accelerator_configuration()->Initialize();
base::RunLoop().RunUntilIdle();
// Clear extraneous observer calls.
mojo_observer.clear_num_times_notified();
EXPECT_EQ(0, mojo_observer.num_times_notified());
// Change input method, expect observer to be called.
input_method_manager_->NotifyInputMethodChanged();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, mojo_observer.num_times_notified());
}
TEST_F(AcceleratorConfigurationProviderTest, TestGetKeyDisplay) {
EXPECT_EQ(u"c", ash::GetKeyDisplay(ui::VKEY_C));
EXPECT_EQ(u"MicrophoneMuteToggle",
ash::GetKeyDisplay(ui::VKEY_MICROPHONE_MUTE_TOGGLE));
EXPECT_EQ(u"ToggleWifi", ash::GetKeyDisplay(ui::VKEY_WLAN));
EXPECT_EQ(u"tab", ash::GetKeyDisplay(ui::VKEY_TAB));
EXPECT_EQ(u"esc", ash::GetKeyDisplay(ui::VKEY_ESCAPE));
EXPECT_EQ(u"backspace", ash::GetKeyDisplay(ui::VKEY_BACK));
EXPECT_EQ(u"enter", ash::GetKeyDisplay(ui::VKEY_RETURN));
EXPECT_EQ(u"space", ash::GetKeyDisplay(ui::VKEY_SPACE));
EXPECT_EQ(u"home", ash::GetKeyDisplay(ui::VKEY_HOME));
EXPECT_EQ(u"end", ash::GetKeyDisplay(ui::VKEY_END));
EXPECT_EQ(u"delete", ash::GetKeyDisplay(ui::VKEY_DELETE));
EXPECT_EQ(u"insert", ash::GetKeyDisplay(ui::VKEY_INSERT));
EXPECT_EQ(u"page up", ash::GetKeyDisplay(ui::VKEY_PRIOR));
EXPECT_EQ(u"page down", ash::GetKeyDisplay(ui::VKEY_NEXT));
EXPECT_EQ(u"meta", ash::GetKeyDisplay(ui::VKEY_LWIN));
EXPECT_EQ(u"alt", ash::GetKeyDisplay(ui::VKEY_MENU));
EXPECT_EQ(u"MediaPlay", ash::GetKeyDisplay(ui::VKEY_MEDIA_PLAY));
}
TEST_F(AcceleratorConfigurationProviderTest, NonConfigurableActions) {
FakeAcceleratorsUpdatedMojoObserver mojo_observer;
SetUpObserver(&mojo_observer);
base::RunLoop().RunUntilIdle();
auto config = mojo_observer.config();
for (const auto& [id, accel_infos] :
config[mojom::AcceleratorSource::kAmbient]) {
for (const auto& info : accel_infos) {
if (info->layout_properties->is_standard_accelerator()) {
bool found_match = false;
for (const auto& expected_data : GetAcceleratorsForAction(id)) {
found_match = CompareAccelerators(expected_data, mojo::Clone(info));
if (found_match) {
break;
}
}
// Matching Accelerator was found.
EXPECT_TRUE(found_match);
} else {
const auto& text_accel =
info->layout_properties->get_text_accelerator()->parts;
if (!TextAccelContainsReplacements(id)) {
// Ambient accelerators that contain no replacements e.g., Drag the
// link to the tab's address bar
EXPECT_EQ(text_accel[0]->text,
l10n_util::GetStringUTF16(GetMessageIdForTextAccel(id)));
continue;
}
// We're only concerned with validating the replacements
// (keys/modifiers). Validating the plain text parts is handled by the
// paramaterized tests below.
const auto& text_accel_parts = RemovePlainTextParts(text_accel);
const auto& replacement_parts = GetReplacementsForAction(id);
for (size_t i = 0; i < replacement_parts.size(); i++) {
ValidateTextAccelerators(replacement_parts[i], text_accel_parts[i]);
}
}
}
}
}
// Tests that standard non-configurable look up is correctly configured and
// matches the predefined non-configurable list.
TEST_F(AcceleratorConfigurationProviderTest, NonConfigurableLookup) {
base::RunLoop().RunUntilIdle();
for (const auto& [ambient_action_id, accelerators_details] :
non_configurable_actions_map_) {
// Only standard accelerators are present in the lookup maps.
if (accelerators_details.IsStandardAccelerator()) {
std::vector<ui::Accelerator> actual_accelerators =
GetNonConfigurableAcceleratorsForActionId(
static_cast<uint32_t>(ambient_action_id));
EXPECT_TRUE(base::ranges::is_permutation(
actual_accelerators, accelerators_details.accelerators.value()));
}
}
}
// Tests that standard non-configurable reverse look up is correctly configured
// and matches the predefined non-configurable list.
TEST_F(AcceleratorConfigurationProviderTest, NonConfigurableReverseLookup) {
base::RunLoop().RunUntilIdle();
for (const auto& [ambient_action_id, accelerators_details] :
non_configurable_actions_map_) {
// Only standard accelerators are present in the lookup maps.
if (accelerators_details.IsStandardAccelerator()) {
for (const auto& accelerator :
accelerators_details.accelerators.value()) {
const uint32_t found_id =
GetNonConfigurableIdFromAccelerator(accelerator);
EXPECT_EQ(ambient_action_id, found_id);
}
}
}
}
TEST_F(AcceleratorConfigurationProviderTest, RemoveAccelerator) {
FakeAcceleratorsUpdatedMojoObserver observer;
SetUpObserver(&observer);
// Initialize with all custom accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
SWITCH_TO_NEXT_IME},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
base::RunLoop().RunUntilIdle();
// Verify accelerators are populated.
EXPECT_EQ(sizeof(test_data) / sizeof(AcceleratorData),
config->GetAllAccelerators().size());
// Remove the accelerator.
provider_->RemoveAccelerator(
mojom::AcceleratorSource::kAsh, SWITCH_TO_NEXT_IME,
ui::Accelerator(ui::VKEY_SPACE, ui::EF_CONTROL_DOWN),
base::BindLambdaForTesting([&](AcceleratorResultDataPtr result) {
EXPECT_EQ(AcceleratorConfigResult::kSuccess, result->result);
EXPECT_FALSE(result->shortcut_name.has_value());
// Verify the accelerator was removed.
std::vector<ui::Accelerator> updated_accelerators =
config->GetAllAccelerators();
EXPECT_EQ(0u, updated_accelerators.size());
// Now verify that removing the default for `SWITCH_TO_NEXT_IME` will
// only disable it from the config.
base::RunLoop().RunUntilIdle();
AcceleratorConfigurationProvider::AcceleratorConfigurationMap
actual_config = observer.config();
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh, test_data,
actual_config);
std::vector<mojom::AcceleratorInfoPtr> actual_infos(mojo::Clone(
actual_config[mojom::AcceleratorSource::kAsh][SWITCH_TO_NEXT_IME]));
EXPECT_EQ(1u, actual_infos.size());
// A disabled default accelerator should be marked as `kDisabledByUser`.
EXPECT_EQ(mojom::AcceleratorState::kDisabledByUser,
actual_infos[0]->state);
EXPECT_EQ(mojom::AcceleratorType::kDefault, actual_infos[0]->type);
}));
base::RunLoop().RunUntilIdle();
}
TEST_F(AcceleratorConfigurationProviderTest, RemoveAcceleratorThatDoesntExist) {
// Initialize with all custom accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
{/*trigger_on_press=*/true, ui::VKEY_ZOOM, ui::EF_ALT_DOWN,
SWAP_PRIMARY_DISPLAY},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
base::RunLoop().RunUntilIdle();
// Remove the accelerator.
provider_->RemoveAccelerator(
mojom::AcceleratorSource::kAsh, TOGGLE_MIRROR_MODE,
ui::Accelerator(ui::VKEY_C, ui::EF_CONTROL_DOWN),
base::BindLambdaForTesting([&](AcceleratorResultDataPtr result) {
EXPECT_EQ(AcceleratorConfigResult::kNotFound, result->result);
EXPECT_FALSE(result->shortcut_name.has_value());
}));
base::RunLoop().RunUntilIdle();
}
TEST_F(AcceleratorConfigurationProviderTest, RemoveAcceleratorNonAsh) {
// Initialize with all custom accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
{/*trigger_on_press=*/true, ui::VKEY_ZOOM, ui::EF_ALT_DOWN,
SWAP_PRIMARY_DISPLAY},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
base::RunLoop().RunUntilIdle();
// Remove the accelerator.
provider_->RemoveAccelerator(
mojom::AcceleratorSource::kBrowser, TOGGLE_MIRROR_MODE,
ui::Accelerator(ui::VKEY_C, ui::EF_CONTROL_DOWN),
base::BindLambdaForTesting([&](AcceleratorResultDataPtr result) {
EXPECT_EQ(AcceleratorConfigResult::kActionLocked, result->result);
EXPECT_FALSE(result->shortcut_name.has_value());
}));
base::RunLoop().RunUntilIdle();
}
TEST_F(AcceleratorConfigurationProviderTest, RemoveAndRestoreAllDefaults) {
FakeAcceleratorsUpdatedMojoObserver observer;
SetUpObserver(&observer);
// Initialize with all custom accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
SWITCH_TO_NEXT_IME},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
config->InitializeDeprecatedAccelerators({}, {});
base::RunLoop().RunUntilIdle();
// Verify accelerators are populated.
EXPECT_EQ(sizeof(test_data) / sizeof(AcceleratorData),
config->GetAllAccelerators().size());
AcceleratorResultDataPtr result;
// Remove the accelerator.
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.RemoveAccelerator(
mojom::AcceleratorSource::kAsh, SWITCH_TO_NEXT_IME,
ui::Accelerator(ui::VKEY_SPACE, ui::EF_CONTROL_DOWN), &result);
EXPECT_EQ(AcceleratorConfigResult::kSuccess, result->result);
EXPECT_FALSE(result->shortcut_name.has_value());
// Verify the accelerator was removed.
std::vector<ui::Accelerator> updated_accelerators =
config->GetAllAccelerators();
EXPECT_EQ(0u, updated_accelerators.size());
// Now verify that removing the default for `SWITCH_TO_NEXT_IME` will
// only disable it from the config.
base::RunLoop().RunUntilIdle();
AcceleratorConfigurationProvider::AcceleratorConfigurationMap actual_config =
observer.config();
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh, test_data,
actual_config);
std::vector<mojom::AcceleratorInfoPtr> actual_infos(mojo::Clone(
actual_config[mojom::AcceleratorSource::kAsh][SWITCH_TO_NEXT_IME]));
EXPECT_EQ(1u, actual_infos.size());
// A disabled default accelerator should be marked as `kDisabledByUser`.
EXPECT_EQ(mojom::AcceleratorState::kDisabledByUser, actual_infos[0]->state);
EXPECT_EQ(mojom::AcceleratorType::kDefault, actual_infos[0]->type);
// Restore all defaults.
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.RestoreAllDefaults(&result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kSuccess, result->result);
base::RunLoop().RunUntilIdle();
// Verify accelerators were restored.
updated_accelerators = config->GetAllAccelerators();
EXPECT_EQ(1u, updated_accelerators.size());
// Verify that the accelerator is back to their default states.
actual_config = observer.config();
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh, test_data,
actual_config);
actual_infos = mojo::Clone(
actual_config[mojom::AcceleratorSource::kAsh][SWITCH_TO_NEXT_IME]);
EXPECT_EQ(1u, actual_infos.size());
// Resetting to default will reset it back to `kEnabled`.
EXPECT_EQ(mojom::AcceleratorState::kEnabled, actual_infos[0]->state);
EXPECT_EQ(mojom::AcceleratorType::kDefault, actual_infos[0]->type);
}
TEST_F(AcceleratorConfigurationProviderTest, RemoveAndResoreDefault) {
FakeAcceleratorsUpdatedMojoObserver observer;
SetUpObserver(&observer);
// Initialize with all custom accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
base::RunLoop().RunUntilIdle();
// Verify accelerators are populated.
EXPECT_EQ(sizeof(test_data) / sizeof(AcceleratorData),
config->GetAllAccelerators().size());
AcceleratorResultDataPtr result;
// Remove the accelerator.
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.RemoveAccelerator(
mojom::AcceleratorSource::kAsh, TOGGLE_MIRROR_MODE,
ui::Accelerator(ui::VKEY_SPACE, ui::EF_CONTROL_DOWN), &result);
EXPECT_EQ(AcceleratorConfigResult::kSuccess, result->result);
EXPECT_FALSE(result->shortcut_name.has_value());
// Verify the accelerator was removed.
std::vector<ui::Accelerator> updated_accelerators =
config->GetAllAccelerators();
EXPECT_EQ(0u, updated_accelerators.size());
// Now verify that removing the default for `TOGGLE_MIRROR_MODE` will
// only disable it from the config.
base::RunLoop().RunUntilIdle();
AcceleratorConfigurationProvider::AcceleratorConfigurationMap actual_config =
observer.config();
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh, test_data,
actual_config);
std::vector<mojom::AcceleratorInfoPtr> actual_infos(mojo::Clone(
actual_config[mojom::AcceleratorSource::kAsh][TOGGLE_MIRROR_MODE]));
EXPECT_EQ(1u, actual_infos.size());
// A disabled default accelerator should be marked as `kDisabledByUser`.
EXPECT_EQ(mojom::AcceleratorState::kDisabledByUser, actual_infos[0]->state);
EXPECT_EQ(mojom::AcceleratorType::kDefault, actual_infos[0]->type);
// Restore all defaults.
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.RestoreDefault(mojom::AcceleratorSource::kAsh, TOGGLE_MIRROR_MODE,
&result);
base::RunLoop().RunUntilIdle();
// Verify the accelerator was restored.
updated_accelerators = config->GetAllAccelerators();
EXPECT_EQ(1u, updated_accelerators.size());
// Verify that the accelerator is back to their default states.
actual_config = observer.config();
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh, test_data,
actual_config);
actual_infos = mojo::Clone(
actual_config[mojom::AcceleratorSource::kAsh][TOGGLE_MIRROR_MODE]);
EXPECT_EQ(1u, actual_infos.size());
// Resetting to default will reset it back to `kEnabled`.
EXPECT_EQ(mojom::AcceleratorState::kEnabled, actual_infos[0]->state);
EXPECT_EQ(mojom::AcceleratorType::kDefault, actual_infos[0]->type);
}
TEST_F(AcceleratorConfigurationProviderTest, RestoreDefaultNonAsh) {
// Initialize with all custom accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
{/*trigger_on_press=*/true, ui::VKEY_ZOOM, ui::EF_ALT_DOWN,
SWAP_PRIMARY_DISPLAY},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
base::RunLoop().RunUntilIdle();
// Remove the accelerator.
provider_->RestoreDefault(
mojom::AcceleratorSource::kBrowser, TOGGLE_MIRROR_MODE,
base::BindLambdaForTesting([&](AcceleratorResultDataPtr result) {
EXPECT_EQ(AcceleratorConfigResult::kActionLocked, result->result);
EXPECT_FALSE(result->shortcut_name.has_value());
}));
base::RunLoop().RunUntilIdle();
}
TEST_F(AcceleratorConfigurationProviderTest, AddAcceleratorBadSource) {
// Initialize default accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
base::RunLoop().RunUntilIdle();
AcceleratorResultDataPtr result;
const ui::Accelerator accelerator(ui::VKEY_M, ui::EF_COMMAND_DOWN);
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.AddAccelerator(mojom::AcceleratorSource::kBrowser, /*action_id=*/0,
accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kActionLocked, result->result);
}
TEST_F(AcceleratorConfigurationProviderTest, AddAcceleratorBadAccelerator) {
// Initialize default accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
base::RunLoop().RunUntilIdle();
AcceleratorResultDataPtr result;
// Missing modifier results in an error.
const ui::Accelerator accelerator(ui::VKEY_M, ui::EF_NONE);
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.AddAccelerator(mojom::AcceleratorSource::kAsh, TOGGLE_MIRROR_MODE,
accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kMissingModifier, result->result);
// Shift as the only modifier is an error.
const ui::Accelerator shift_only_accelerator(ui::VKEY_M, ui::EF_SHIFT_DOWN);
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.AddAccelerator(mojom::AcceleratorSource::kAsh, TOGGLE_MIRROR_MODE,
shift_only_accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kShiftOnlyNotAllowed,
result->result);
// Top-row key cannot be used as the key
const ui::Accelerator top_row_accelerator(ui::VKEY_BRIGHTNESS_DOWN,
ui::EF_CONTROL_DOWN);
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.AddAccelerator(mojom::AcceleratorSource::kAsh, TOGGLE_MIRROR_MODE,
top_row_accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kKeyNotAllowed, result->result);
}
TEST_F(AcceleratorConfigurationProviderTest, AddAcceleratorNonConfigConflict) {
// Initialize default accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
// Ctrl + H is used by the Browser Shortcut, Open History Page.
const ui::Accelerator accelerator(ui::VKEY_H, ui::EF_CONTROL_DOWN);
NonConfigurableActionsMap non_config_map = {
{NonConfigurableActions::kBrowserShowHistory,
NonConfigurableAcceleratorDetails({accelerator})}};
base::RunLoop().RunUntilIdle();
AcceleratorResultDataPtr result;
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.AddAccelerator(mojom::AcceleratorSource::kAsh, TOGGLE_MIRROR_MODE,
accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kConflict, result->result);
EXPECT_EQ(l10n_util::GetStringUTF16(
IDS_BROWSER_ACCELERATOR_DESCRIPTION_SHOW_HISTORY),
result->shortcut_name);
}
TEST_F(AcceleratorConfigurationProviderTest, AddAcceleratorNoConflict) {
FakeAcceleratorsUpdatedMojoObserver observer;
SetUpObserver(&observer);
// Initialize default accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
// Override non-configurable accelerators.
const ui::Accelerator browser_accelerator(ui::VKEY_H, ui::EF_CONTROL_DOWN);
NonConfigurableActionsMap non_config_map = {
{NonConfigurableActions::kBrowserShowHistory,
NonConfigurableAcceleratorDetails({browser_accelerator})}};
base::RunLoop().RunUntilIdle();
const ui::Accelerator good_accelerator(ui::VKEY_M, ui::EF_COMMAND_DOWN);
AcceleratorResultDataPtr result;
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.AddAccelerator(mojom::AcceleratorSource::kAsh, TOGGLE_MIRROR_MODE,
good_accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kSuccess, result->result);
const AcceleratorData updated_test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
{/*trigger_on_press=*/true, ui::VKEY_M, ui::EF_COMMAND_DOWN,
TOGGLE_MIRROR_MODE},
};
AcceleratorConfigurationProvider::AcceleratorConfigurationMap actual_config =
observer.config();
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh,
updated_test_data, actual_config);
}
TEST_F(AcceleratorConfigurationProviderTest, AddAcceleratorConflictLocked) {
// Initialize default accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
{/*trigger_on_press=*/true, ui::VKEY_M, ui::EF_COMMAND_DOWN, OPEN_CROSH},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
SetLayoutDetailsMap(
{{OPEN_CROSH, IDS_ASH_ACCELERATOR_DESCRIPTION_OPEN_CROSH,
mojom::AcceleratorCategory::kGeneral,
mojom::AcceleratorSubcategory::kGeneralControls,
/*locked=*/true, mojom::AcceleratorLayoutStyle::kDefault,
mojom::AcceleratorSource::kAsh}});
// Override non-configurable accelerators.
const ui::Accelerator browser_accelerator(ui::VKEY_H, ui::EF_CONTROL_DOWN);
NonConfigurableActionsMap non_config_map = {
{NonConfigurableActions::kBrowserShowHistory,
NonConfigurableAcceleratorDetails({browser_accelerator})}};
base::RunLoop().RunUntilIdle();
const ui::Accelerator conflict_accelerator(ui::VKEY_M, ui::EF_COMMAND_DOWN);
AcceleratorResultDataPtr result;
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.AddAccelerator(mojom::AcceleratorSource::kAsh, TOGGLE_MIRROR_MODE,
conflict_accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kActionLocked, result->result);
EXPECT_EQ(
l10n_util::GetStringUTF16(IDS_ASH_ACCELERATOR_DESCRIPTION_OPEN_CROSH),
result->shortcut_name);
}
TEST_F(AcceleratorConfigurationProviderTest, AddAcceleratorConflictReset) {
FakeAcceleratorsUpdatedMojoObserver observer;
SetUpObserver(&observer);
// Initialize default accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
{/*trigger_on_press=*/true, ui::VKEY_M, ui::EF_COMMAND_DOWN, OPEN_CROSH},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
base::RunLoop().RunUntilIdle();
const ui::Accelerator conflict_accelerator(ui::VKEY_M, ui::EF_COMMAND_DOWN);
AcceleratorResultDataPtr result;
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.AddAccelerator(mojom::AcceleratorSource::kAsh, TOGGLE_MIRROR_MODE,
conflict_accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kConflictCanOverride,
result->result);
EXPECT_EQ(
l10n_util::GetStringUTF16(IDS_ASH_ACCELERATOR_DESCRIPTION_OPEN_CROSH),
result->shortcut_name);
AcceleratorConfigurationProvider::AcceleratorConfigurationMap actual_config =
observer.config();
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh, test_data,
actual_config);
// Simulate the user is no longer capturing the input, expect that re-pressing
// the accelerato will still give the same error
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.PreventProcessingAccelerators(false);
// Press the same accelerator, expect the same error.
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.AddAccelerator(mojom::AcceleratorSource::kAsh, TOGGLE_MIRROR_MODE,
conflict_accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kConflictCanOverride,
result->result);
EXPECT_EQ(
l10n_util::GetStringUTF16(IDS_ASH_ACCELERATOR_DESCRIPTION_OPEN_CROSH),
result->shortcut_name);
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh, test_data,
observer.config());
}
TEST_F(AcceleratorConfigurationProviderTest, AddAcceleratorConflictOverride) {
FakeAcceleratorsUpdatedMojoObserver observer;
SetUpObserver(&observer);
// Initialize default accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
{/*trigger_on_press=*/true, ui::VKEY_M, ui::EF_COMMAND_DOWN, OPEN_CROSH},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
// Override non-configurable accelerators.
const ui::Accelerator browser_accelerator(ui::VKEY_H, ui::EF_CONTROL_DOWN);
NonConfigurableActionsMap non_config_map = {
{NonConfigurableActions::kBrowserShowHistory,
NonConfigurableAcceleratorDetails({browser_accelerator})}};
base::RunLoop().RunUntilIdle();
const ui::Accelerator conflict_accelerator(ui::VKEY_M, ui::EF_COMMAND_DOWN);
AcceleratorResultDataPtr result;
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.AddAccelerator(mojom::AcceleratorSource::kAsh, TOGGLE_MIRROR_MODE,
conflict_accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kConflictCanOverride,
result->result);
EXPECT_EQ(
l10n_util::GetStringUTF16(IDS_ASH_ACCELERATOR_DESCRIPTION_OPEN_CROSH),
result->shortcut_name);
AcceleratorConfigurationProvider::AcceleratorConfigurationMap actual_config =
observer.config();
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh, test_data,
actual_config);
// Now override the accelerator.
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.AddAccelerator(mojom::AcceleratorSource::kAsh, TOGGLE_MIRROR_MODE,
conflict_accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kSuccess, result->result);
// Since this is an overridable accelerator, nothing should change at first.
const AcceleratorData updated_test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
{/*trigger_on_press=*/true, ui::VKEY_M, ui::EF_COMMAND_DOWN,
TOGGLE_MIRROR_MODE},
};
actual_config = observer.config();
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh,
updated_test_data, actual_config);
}
TEST_F(AcceleratorConfigurationProviderTest, ReplaceBadSourceOrAction) {
FakeAcceleratorsUpdatedMojoObserver observer;
SetUpObserver(&observer);
// Initialize default accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
base::RunLoop().RunUntilIdle();
AcceleratorResultDataPtr result;
const ui::Accelerator old_accelerator(ui::VKEY_SPACE, ui::EF_CONTROL_DOWN);
const ui::Accelerator new_accelerator(ui::VKEY_M, ui::EF_ALT_DOWN);
// Browser is a locked source.
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.ReplaceAccelerator(mojom::AcceleratorSource::kBrowser,
TOGGLE_MIRROR_MODE, old_accelerator,
new_accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kActionLocked, result->result);
// TOGGLE_CALENDAR does not exist.
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.ReplaceAccelerator(mojom::AcceleratorSource::kAsh, TOGGLE_CALENDAR,
old_accelerator, new_accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kNotFound, result->result);
}
TEST_F(AcceleratorConfigurationProviderTest, AddAcceleratorConflictThenGood) {
FakeAcceleratorsUpdatedMojoObserver observer;
SetUpObserver(&observer);
// Initialize default accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
{/*trigger_on_press=*/true, ui::VKEY_M, ui::EF_COMMAND_DOWN, OPEN_CROSH},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
// Override non-configurable accelerators.
const ui::Accelerator browser_accelerator(ui::VKEY_H, ui::EF_CONTROL_DOWN);
NonConfigurableActionsMap non_config_map = {
{NonConfigurableActions::kBrowserShowHistory,
NonConfigurableAcceleratorDetails({browser_accelerator})}};
base::RunLoop().RunUntilIdle();
const ui::Accelerator conflict_accelerator(ui::VKEY_M, ui::EF_COMMAND_DOWN);
AcceleratorResultDataPtr result;
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.AddAccelerator(mojom::AcceleratorSource::kAsh, TOGGLE_MIRROR_MODE,
conflict_accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kConflictCanOverride,
result->result);
EXPECT_EQ(
l10n_util::GetStringUTF16(IDS_ASH_ACCELERATOR_DESCRIPTION_OPEN_CROSH),
result->shortcut_name);
AcceleratorConfigurationProvider::AcceleratorConfigurationMap actual_config =
observer.config();
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh, test_data,
actual_config);
// Now use a non-conflicting accelerator.
const ui::Accelerator good_accelerator(ui::VKEY_K, ui::EF_COMMAND_DOWN);
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.AddAccelerator(mojom::AcceleratorSource::kAsh, TOGGLE_MIRROR_MODE,
good_accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kSuccess, result->result);
const AcceleratorData updated_test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
{/*trigger_on_press=*/true, ui::VKEY_K, ui::EF_COMMAND_DOWN,
TOGGLE_MIRROR_MODE},
{/*trigger_on_press=*/true, ui::VKEY_M, ui::EF_COMMAND_DOWN, OPEN_CROSH},
};
actual_config = observer.config();
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh,
updated_test_data, actual_config);
}
TEST_F(AcceleratorConfigurationProviderTest, PreventProcessingAccelerators) {
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.PreventProcessingAccelerators(true);
EXPECT_TRUE(Shell::Get()
->accelerator_controller()
->ShouldPreventProcessingAccelerators());
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.PreventProcessingAccelerators(false);
EXPECT_FALSE(Shell::Get()
->accelerator_controller()
->ShouldPreventProcessingAccelerators());
}
TEST_F(AcceleratorConfigurationProviderTest, ReplaceDefaultAccelerator) {
FakeAcceleratorsUpdatedMojoObserver observer;
SetUpObserver(&observer);
// Initialize default accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
base::RunLoop().RunUntilIdle();
AcceleratorResultDataPtr result;
const ui::Accelerator old_accelerator(ui::VKEY_SPACE, ui::EF_CONTROL_DOWN);
const ui::Accelerator new_accelerator(ui::VKEY_M, ui::EF_ALT_DOWN);
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.ReplaceAccelerator(mojom::AcceleratorSource::kAsh,
TOGGLE_MIRROR_MODE, old_accelerator,
new_accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kSuccess, result->result);
base::RunLoop().RunUntilIdle();
const AcceleratorData updated_test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
{/*trigger_on_press=*/true, ui::VKEY_M, ui::EF_ALT_DOWN,
TOGGLE_MIRROR_MODE},
};
// Replacing a default will result in disabling the default and then adding
// the new accelerator.
AcceleratorConfigurationProvider::AcceleratorConfigurationMap actual_config =
observer.config();
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh,
updated_test_data, mojo::Clone(actual_config));
std::vector<mojom::AcceleratorInfoPtr> actual_infos(mojo::Clone(
actual_config[mojom::AcceleratorSource::kAsh][TOGGLE_MIRROR_MODE]));
EXPECT_EQ(2u, actual_infos.size());
// A disabled default accelerator should be marked as `kDisabledByUser`.
EXPECT_EQ(mojom::AcceleratorState::kDisabledByUser, actual_infos[0]->state);
EXPECT_EQ(mojom::AcceleratorType::kDefault, actual_infos[0]->type);
}
TEST_F(AcceleratorConfigurationProviderTest, ReplaceAcceleratorDoesNotExist) {
FakeAcceleratorsUpdatedMojoObserver observer;
SetUpObserver(&observer);
// Initialize default accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
base::RunLoop().RunUntilIdle();
AcceleratorResultDataPtr result;
const ui::Accelerator old_accelerator(ui::VKEY_J, ui::EF_CONTROL_DOWN);
const ui::Accelerator new_accelerator(ui::VKEY_M, ui::EF_ALT_DOWN);
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.ReplaceAccelerator(mojom::AcceleratorSource::kAsh,
TOGGLE_MIRROR_MODE, old_accelerator,
new_accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kNotFound, result->result);
}
TEST_F(AcceleratorConfigurationProviderTest, AddThenReplaceAccelerator) {
FakeAcceleratorsUpdatedMojoObserver observer;
SetUpObserver(&observer);
// Initialize default accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
base::RunLoop().RunUntilIdle();
AcceleratorResultDataPtr result;
const ui::Accelerator accelerator(ui::VKEY_M, ui::EF_ALT_DOWN);
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.AddAccelerator(mojom::AcceleratorSource::kAsh, TOGGLE_MIRROR_MODE,
accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kSuccess, result->result);
const AcceleratorData updated_test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
{/*trigger_on_press=*/true, ui::VKEY_M, ui::EF_ALT_DOWN,
TOGGLE_MIRROR_MODE},
};
AcceleratorConfigurationProvider::AcceleratorConfigurationMap actual_config =
observer.config();
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh,
updated_test_data, mojo::Clone(actual_config));
// Now replace the newly added accelerator.
const ui::Accelerator new_accelerator(ui::VKEY_C, ui::EF_COMMAND_DOWN);
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.ReplaceAccelerator(mojom::AcceleratorSource::kAsh,
TOGGLE_MIRROR_MODE, accelerator, new_accelerator,
&result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kSuccess, result->result);
const AcceleratorData updated_test_data2[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
{/*trigger_on_press=*/true, ui::VKEY_C, ui::EF_COMMAND_DOWN,
TOGGLE_MIRROR_MODE},
};
actual_config = observer.config();
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh,
updated_test_data2, mojo::Clone(actual_config));
}
TEST_F(AcceleratorConfigurationProviderTest,
ReplaceOtherDefaultAcceleratorAction) {
FakeAcceleratorsUpdatedMojoObserver observer;
SetUpObserver(&observer);
// Initialize default accelerators.
const AcceleratorData test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
{/*trigger_on_press=*/true, ui::VKEY_D, ui::EF_ALT_DOWN, TOGGLE_CALENDAR},
};
AshAcceleratorConfiguration* config =
Shell::Get()->ash_accelerator_configuration();
config->Initialize(test_data);
base::RunLoop().RunUntilIdle();
// Replace the default of `TOGGLE_CALENDAR` with that of the default of
// `TOGGLE_MIRROR_MODE`. This results in `TOGGLE_MIRROR_MODE` to have only the
// disabled default accelerator, `TOGGLE_CALENDAR` will also have disabled
// default accelerator but also a new accelerator added.
AcceleratorResultDataPtr result;
const ui::Accelerator old_accelerator(ui::VKEY_D, ui::EF_ALT_DOWN);
const ui::Accelerator new_accelerator(ui::VKEY_SPACE, ui::EF_CONTROL_DOWN);
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.ReplaceAccelerator(mojom::AcceleratorSource::kAsh, TOGGLE_CALENDAR,
old_accelerator, new_accelerator, &result);
// Overridable accelerator, but will need to re-call `ReplaceAccelerator` to
// confirm the override.
EXPECT_EQ(mojom::AcceleratorConfigResult::kConflictCanOverride,
result->result);
EXPECT_EQ(l10n_util::GetStringUTF16(
IDS_ASH_ACCELERATOR_DESCRIPTION_TOGGLE_MIRROR_MODE),
result->shortcut_name);
ash::shortcut_customization::mojom::
AcceleratorConfigurationProviderAsyncWaiter(provider_.get())
.ReplaceAccelerator(mojom::AcceleratorSource::kAsh, TOGGLE_CALENDAR,
old_accelerator, new_accelerator, &result);
EXPECT_EQ(mojom::AcceleratorConfigResult::kSuccess, result->result);
base::RunLoop().RunUntilIdle();
const AcceleratorData updated_test_data[] = {
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_MIRROR_MODE},
{/*trigger_on_press=*/true, ui::VKEY_D, ui::EF_ALT_DOWN, TOGGLE_CALENDAR},
{/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
TOGGLE_CALENDAR},
};
AcceleratorConfigurationProvider::AcceleratorConfigurationMap actual_config =
observer.config();
ExpectMojomAcceleratorsEqual(mojom::AcceleratorSource::kAsh,
updated_test_data, mojo::Clone(actual_config));
std::vector<mojom::AcceleratorInfoPtr> actual_infos(mojo::Clone(
actual_config[mojom::AcceleratorSource::kAsh][TOGGLE_MIRROR_MODE]));
EXPECT_EQ(1u, actual_infos.size());
EXPECT_EQ(mojom::AcceleratorState::kDisabledByUser, actual_infos[0]->state);
EXPECT_EQ(mojom::AcceleratorType::kDefault, actual_infos[0]->type);
std::vector<mojom::AcceleratorInfoPtr> actual_infos2(mojo::Clone(
actual_config[mojom::AcceleratorSource::kAsh][TOGGLE_CALENDAR]));
EXPECT_EQ(2u, actual_infos2.size());
EXPECT_EQ(mojom::AcceleratorState::kDisabledByUser, actual_infos2[0]->state);
EXPECT_EQ(mojom::AcceleratorType::kDefault, actual_infos2[0]->type);
EXPECT_EQ(mojom::AcceleratorState::kEnabled, actual_infos2[1]->state);
EXPECT_EQ(mojom::AcceleratorType::kDefault, actual_infos2[1]->type);
}
using FlagsKeyboardCodesVariant =
std::variant<ui::EventFlags, ui::KeyboardCode, TextAcceleratorDelimiter>;
using FlagsKeyboardCodeStringVariant = std::variant<ui::EventFlags,
ui::KeyboardCode,
std::u16string,
TextAcceleratorDelimiter>;
class TextAcceleratorParsingTest
: public AcceleratorConfigurationProviderTest,
public testing::WithParamInterface<
std::tuple<std::u16string,
std::vector<FlagsKeyboardCodesVariant>,
std::vector<FlagsKeyboardCodeStringVariant>>> {
public:
void SetUp() override {
AcceleratorConfigurationProviderTest::SetUp();
std::vector<FlagsKeyboardCodesVariant> replacements_parts;
std::vector<FlagsKeyboardCodeStringVariant> variants;
std::tie(replacement_string_, replacements_parts, variants) = GetParam();
for (const auto& r : replacements_parts) {
if (std::holds_alternative<ui::KeyboardCode>(r)) {
replacements_.emplace_back(std::get<ui::KeyboardCode>(r));
} else if (std::holds_alternative<ui::EventFlags>(r)) {
replacements_.emplace_back(std::get<ui::EventFlags>(r));
} else {
replacements_.emplace_back(std::get<TextAcceleratorDelimiter>(r));
}
}
for (const auto& v : variants) {
if (std::holds_alternative<std::u16string>(v)) {
expected_parts_.emplace_back(std::get<std::u16string>(v));
} else if (std::holds_alternative<ui::KeyboardCode>(v)) {
expected_parts_.emplace_back(std::get<ui::KeyboardCode>(v));
} else if (std::holds_alternative<ui::EventFlags>(v)) {
expected_parts_.emplace_back(std::get<ui::EventFlags>(v));
} else {
expected_parts_.emplace_back(std::get<TextAcceleratorDelimiter>(v));
}
}
}
protected:
std::u16string replacement_string_;
std::vector<TextAcceleratorPart> replacements_;
std::vector<TextAcceleratorPart> expected_parts_;
};
INSTANTIATE_TEST_SUITE_P(
// Empty to simplify gtest output
,
TextAcceleratorParsingTest,
// The tuple contains an example replacement string, the replacements to
// use in the string (keys, modifiers, plain text), and the result we
// expect to receive given these inputs.
testing::ValuesIn(
std::vector<std::tuple<std::u16string,
std::vector<FlagsKeyboardCodesVariant>,
std::vector<FlagsKeyboardCodeStringVariant>>>{
{
u"$1 $2 $3 through $4",
{ui::EF_CONTROL_DOWN, TextAcceleratorDelimiter::kPlusSign,
ui::VKEY_1, ui::VKEY_8},
{ui::EF_CONTROL_DOWN, u" ", TextAcceleratorDelimiter::kPlusSign,
u" ", ui::VKEY_1, u" through ", ui::VKEY_8},
},
{
u"Press $1 and $2",
{ui::EF_CONTROL_DOWN, ui::VKEY_C},
{u"Press ", ui::EF_CONTROL_DOWN, u" and ", ui::VKEY_C},
},
{
u"Press $1 $2 $3",
{ui::VKEY_A, ui::VKEY_B, ui::VKEY_C},
{u"Press ", ui::VKEY_A, u" ", ui::VKEY_B, u" ", ui::VKEY_C},
},
{
u"$1 $2 $3 Press",
{ui::VKEY_A, ui::VKEY_B, ui::VKEY_C},
{ui::VKEY_A, u" ", ui::VKEY_B, u" ", ui::VKEY_C, u" Press"},
},
{
u"$1$2$3",
{ui::VKEY_A, ui::VKEY_B, ui::VKEY_C},
{ui::VKEY_A, ui::VKEY_B, ui::VKEY_C},
},
{
u"$1 and $2",
{ui::VKEY_A, ui::VKEY_B},
{ui::VKEY_A, u" and ", ui::VKEY_B},
},
{
u"A $1 $2 D",
{ui::VKEY_B, ui::VKEY_C},
{u"A ", ui::VKEY_B, u" ", ui::VKEY_C, u" D"},
},
{
u"$1",
{ui::VKEY_B},
{ui::VKEY_B},
},
{
u"$1 ",
{ui::VKEY_B},
{ui::VKEY_B, u" "},
},
{
u" $1",
{ui::VKEY_B},
{u" ", ui::VKEY_B},
},
{
u"$1",
{TextAcceleratorDelimiter::kPlusSign},
{TextAcceleratorDelimiter::kPlusSign},
},
{
u"$1 ",
{TextAcceleratorDelimiter::kPlusSign},
{TextAcceleratorDelimiter::kPlusSign, u" "},
},
{
u" $1",
{TextAcceleratorDelimiter::kPlusSign},
{u" ", TextAcceleratorDelimiter::kPlusSign},
},
{
u"Drag the link to a blank area on the tab strip",
{},
{u"Drag the link to a blank area on the tab strip"},
},
{
u"$1a$2$3bc",
{ui::EF_SHIFT_DOWN, ui::VKEY_B, ui::VKEY_C},
{ui::EF_SHIFT_DOWN, u"a", ui::VKEY_B, ui::VKEY_C, u"bc"},
}}));
TEST_P(TextAcceleratorParsingTest, TextAcceleratorParsing) {
auto& bundle = ui::ResourceBundle::GetSharedInstance();
int FAKE_RESOURCE_ID = 1;
bundle.OverrideLocaleStringResource(FAKE_RESOURCE_ID, replacement_string_);
const auto text_accelerator = provider_->CreateTextAcceleratorProperties(
{FAKE_RESOURCE_ID, replacements_});
EXPECT_EQ(expected_parts_.size(), text_accelerator->parts.size());
for (size_t i = 0; i < expected_parts_.size(); i++) {
ValidateTextAccelerators(expected_parts_[i], text_accelerator->parts[i]);
}
}
class GetPlainTextPartsTest : public AcceleratorConfigurationProviderTest,
public testing::WithParamInterface<
std::tuple<std::u16string,
std::vector<size_t>,
std::vector<std::u16string>>> {
public:
void SetUp() override {
AcceleratorConfigurationProviderTest::SetUp();
std::tie(input_, offsets_, expected_output_) = GetParam();
}
protected:
std::u16string input_;
std::vector<size_t> offsets_;
std::vector<std::u16string> expected_output_;
};
INSTANTIATE_TEST_SUITE_P(
// Empty to simplify gtest output
,
GetPlainTextPartsTest,
// The tuple contains an example replacement string with the placeholders
// removed, the expected list of offsets which must be sorted and contains
// the start points of our replacements, and the plain text parts we expect
// receive given these inputs.
testing::ValuesIn(std::vector<std::tuple<std::u16string,
std::vector<size_t>,
std::vector<std::u16string>>>{
{u"abc", {0, 1, 1}, {u"a", u"bc"}},
{u"abc", {0, 1, 2}, {u"a", u"b", u"c"}},
{u"a b", {0, 1}, {u"a", u" b"}},
{u"a b", {0, 2}, {u"a ", u"b"}},
{u"Press and ", {6, 11}, {u"Press ", u" and "}},
{u"", {0}, {}},
{u"No replacements", {}, {u"No replacements"}},
{u"a and bc", {0, 6}, {u"a and ", u"bc"}},
}));
TEST_P(GetPlainTextPartsTest, GetPlainTextParts) {
const auto parts = SplitStringOnOffsets(input_, offsets_);
EXPECT_EQ(expected_output_, parts);
}
} // namespace shortcut_ui
} // namespace ash