| // 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 "ui/base/accelerators/accelerator_manager.h" |
| |
| #include "build/build_config.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/accelerators/test_accelerator_target.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/events/keycodes/dom/dom_code.h" |
| #include "ui/events/keycodes/keyboard_codes.h" |
| |
| namespace ui { |
| namespace test { |
| namespace { |
| |
| Accelerator GetAccelerator(KeyboardCode code, int mask) { |
| return Accelerator(code, mask); |
| } |
| |
| // Possible flags used for accelerators. |
| constexpr auto kAcceleratorModifiers = std::to_array<int>({ |
| EF_SHIFT_DOWN, |
| EF_CONTROL_DOWN, |
| EF_ALT_DOWN, |
| EF_COMMAND_DOWN, |
| }); |
| |
| // Returns a set of flags from id, where id is a bitmask into |
| // kAcceleratorModifiers used to determine which flags are set. |
| int BuildAcceleratorModifier(int id) { |
| int result = 0; |
| for (size_t i = 0; i < std::size(kAcceleratorModifiers); ++i) { |
| if (((1 << i) & id) != 0) |
| result |= kAcceleratorModifiers[i]; |
| } |
| return result; |
| } |
| |
| class AcceleratorManagerTest : public testing::Test { |
| public: |
| AcceleratorManagerTest() = default; |
| ~AcceleratorManagerTest() override = default; |
| |
| protected: |
| AcceleratorManager manager_; |
| }; |
| |
| TEST_F(AcceleratorManagerTest, Register) { |
| TestAcceleratorTarget target; |
| const Accelerator accelerator_a(VKEY_A, EF_NONE); |
| const Accelerator accelerator_b(VKEY_B, EF_NONE); |
| const Accelerator accelerator_c(VKEY_C, EF_NONE); |
| const Accelerator accelerator_d(VKEY_D, EF_NONE); |
| manager_.Register( |
| {accelerator_a, accelerator_b, accelerator_c, accelerator_d}, |
| AcceleratorManager::kNormalPriority, &target); |
| |
| // The registered accelerators are processed. |
| EXPECT_TRUE(manager_.Process(accelerator_a)); |
| EXPECT_TRUE(manager_.Process(accelerator_b)); |
| EXPECT_TRUE(manager_.Process(accelerator_c)); |
| EXPECT_TRUE(manager_.Process(accelerator_d)); |
| EXPECT_EQ(4, target.accelerator_count()); |
| } |
| |
| TEST_F(AcceleratorManagerTest, RegisterMultipleTarget) { |
| const Accelerator accelerator_a(VKEY_A, EF_NONE); |
| TestAcceleratorTarget target1; |
| manager_.Register({accelerator_a}, AcceleratorManager::kNormalPriority, |
| &target1); |
| TestAcceleratorTarget target2; |
| manager_.Register({accelerator_a}, AcceleratorManager::kNormalPriority, |
| &target2); |
| |
| // If multiple targets are registered with the same accelerator, the target |
| // registered later processes the accelerator. |
| EXPECT_TRUE(manager_.Process(accelerator_a)); |
| EXPECT_EQ(0, target1.accelerator_count()); |
| EXPECT_EQ(1, target2.accelerator_count()); |
| } |
| |
| TEST_F(AcceleratorManagerTest, Unregister) { |
| const Accelerator accelerator_a(VKEY_A, EF_NONE); |
| TestAcceleratorTarget target; |
| const Accelerator accelerator_b(VKEY_B, EF_NONE); |
| manager_.Register({accelerator_a, accelerator_b}, |
| AcceleratorManager::kNormalPriority, &target); |
| |
| // Unregistering a different accelerator does not affect the other |
| // accelerator. |
| manager_.Unregister(accelerator_b, &target); |
| EXPECT_TRUE(manager_.Process(accelerator_a)); |
| EXPECT_EQ(1, target.accelerator_count()); |
| |
| // The unregistered accelerator is no longer processed. |
| target.ResetCounts(); |
| manager_.Unregister(accelerator_a, &target); |
| EXPECT_FALSE(manager_.Process(accelerator_a)); |
| EXPECT_EQ(0, target.accelerator_count()); |
| } |
| |
| TEST_F(AcceleratorManagerTest, UnregisterAll) { |
| const Accelerator accelerator_a(VKEY_A, EF_NONE); |
| TestAcceleratorTarget target1; |
| const Accelerator accelerator_b(VKEY_B, EF_NONE); |
| manager_.Register({accelerator_a, accelerator_b}, |
| AcceleratorManager::kNormalPriority, &target1); |
| |
| const Accelerator accelerator_c(VKEY_C, EF_NONE); |
| TestAcceleratorTarget target2; |
| manager_.Register({accelerator_c}, AcceleratorManager::kNormalPriority, |
| &target2); |
| |
| manager_.UnregisterAll(&target1); |
| |
| // All the accelerators registered for |target1| are no longer processed. |
| EXPECT_FALSE(manager_.Process(accelerator_a)); |
| EXPECT_FALSE(manager_.Process(accelerator_b)); |
| EXPECT_EQ(0, target1.accelerator_count()); |
| |
| // UnregisterAll with a different target does not affect the other target. |
| EXPECT_TRUE(manager_.Process(accelerator_c)); |
| EXPECT_EQ(1, target2.accelerator_count()); |
| } |
| |
| TEST_F(AcceleratorManagerTest, Process) { |
| TestAcceleratorTarget target; |
| |
| // Test all cases of possible modifiers. |
| for (size_t i = 0; i < (1 << std::size(kAcceleratorModifiers)); ++i) { |
| const int modifiers = BuildAcceleratorModifier(i); |
| Accelerator accelerator(GetAccelerator(VKEY_A, modifiers)); |
| manager_.Register({accelerator}, AcceleratorManager::kNormalPriority, |
| &target); |
| |
| // The registered accelerator is processed. |
| const int last_count = target.accelerator_count(); |
| EXPECT_TRUE(manager_.Process(accelerator)) << i; |
| EXPECT_EQ(last_count + 1, target.accelerator_count()) << i; |
| |
| // The non-registered accelerators are not processed. |
| accelerator.set_key_state(Accelerator::KeyState::RELEASED); |
| EXPECT_FALSE(manager_.Process(accelerator)) << i; // different type |
| |
| EXPECT_FALSE(manager_.Process(GetAccelerator(VKEY_UNKNOWN, modifiers))) |
| << i; // different vkey |
| EXPECT_FALSE(manager_.Process(GetAccelerator(VKEY_B, modifiers))) |
| << i; // different vkey |
| EXPECT_FALSE(manager_.Process(GetAccelerator(VKEY_SHIFT, modifiers))) |
| << i; // different vkey |
| |
| for (size_t test_i = 0; test_i < (1 << std::size(kAcceleratorModifiers)); |
| ++test_i) { |
| if (test_i == i) |
| continue; |
| const int test_modifiers = BuildAcceleratorModifier(test_i); |
| const Accelerator test_accelerator( |
| GetAccelerator(VKEY_A, test_modifiers)); |
| EXPECT_FALSE(manager_.Process(test_accelerator)) << " i=" << i |
| << " test_i=" << test_i; |
| } |
| |
| EXPECT_EQ(last_count + 1, target.accelerator_count()) << i; |
| manager_.UnregisterAll(&target); |
| } |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| |
| TEST_F(AcceleratorManagerTest, PositionalShortcuts_AllEqual) { |
| // Use a local instance so that the feature is enabled during construction. |
| AcceleratorManager manager; |
| manager.SetUsePositionalLookup(true); |
| |
| // Test what would be ctrl + ']' (VKEY_OEM_6) on a US keyboard. This |
| // should match. |
| TestAcceleratorTarget target; |
| const Accelerator accelerator(VKEY_OEM_6, EF_CONTROL_DOWN); |
| manager.Register({accelerator}, AcceleratorManager::kNormalPriority, &target); |
| KeyEvent event(ui::EventType::kKeyPressed, VKEY_OEM_6, |
| ui::DomCode::BRACKET_RIGHT, ui::EF_CONTROL_DOWN, |
| ui::DomKey::FromCharacter(']'), base::TimeTicks()); |
| const Accelerator trigger(event); |
| EXPECT_TRUE(manager.IsRegistered(trigger)); |
| EXPECT_TRUE(manager.Process(trigger)); |
| } |
| |
| TEST_F(AcceleratorManagerTest, PositionalShortcuts_MatchingDomCode) { |
| // Use a local instance so that the feature is enabled during construction. |
| AcceleratorManager manager; |
| manager.SetUsePositionalLookup(true); |
| |
| // Test what would be ctrl + ']' on a US keyboard with matching DomCode |
| // and different VKEY (eg. '+'). This is the use case of a positional key |
| // on the German keyboard. Since the DomCode matches, this should match. |
| TestAcceleratorTarget target; |
| const Accelerator accelerator(VKEY_OEM_6, EF_CONTROL_DOWN); |
| manager.Register({accelerator}, AcceleratorManager::kNormalPriority, &target); |
| KeyEvent event(ui::EventType::kKeyPressed, VKEY_OEM_PLUS, |
| ui::DomCode::BRACKET_RIGHT, ui::EF_CONTROL_DOWN, |
| ui::DomKey::FromCharacter(']'), base::TimeTicks()); |
| const Accelerator trigger(event); |
| EXPECT_TRUE(manager.IsRegistered(trigger)); |
| EXPECT_TRUE(manager.Process(trigger)); |
| } |
| |
| TEST_F(AcceleratorManagerTest, PositionalShortcuts_NotMatchingDomCode) { |
| // Use a local instance so that the feature is enabled during construction. |
| AcceleratorManager manager; |
| manager.SetUsePositionalLookup(true); |
| |
| // Test what would be ctrl + ']' on a US keyboard using positional mapping |
| // for a German layout. The accelerator is registered using the US VKEY and |
| // triggered with a KeyEvent with the US VKEY but a mismatched DomCode. This |
| // should not match. This prevents ghost shortcuts on non-US layouts. |
| TestAcceleratorTarget target; |
| const Accelerator accelerator(VKEY_OEM_6, EF_CONTROL_DOWN); |
| manager.Register({accelerator}, AcceleratorManager::kNormalPriority, &target); |
| KeyEvent event(ui::EventType::kKeyPressed, VKEY_OEM_6, |
| ui::DomCode::BRACKET_LEFT, ui::EF_CONTROL_DOWN, |
| ui::DomKey::FromCharacter(']'), base::TimeTicks()); |
| const Accelerator trigger(event); |
| EXPECT_FALSE(manager.IsRegistered(trigger)); |
| EXPECT_FALSE(manager.Process(trigger)); |
| } |
| |
| TEST_F(AcceleratorManagerTest, PositionalShortcuts_NonPositionalMatch) { |
| // Use a local instance so that the feature is enabled during construction. |
| AcceleratorManager manager; |
| manager.SetUsePositionalLookup(true); |
| |
| // Test ctrl + 'Z' for the German layout. Since 'Z' is not a positional |
| // key it should match based on the VKEY, regardless of the DomCode. In this |
| // case the 'Z' has DomCode US_Y (ie. QWERTZ keyboard), but it should still |
| // match. |
| TestAcceleratorTarget target; |
| const Accelerator accelerator(VKEY_Z, EF_CONTROL_DOWN); |
| manager.Register({accelerator}, AcceleratorManager::kNormalPriority, &target); |
| KeyEvent event(ui::EventType::kKeyPressed, VKEY_Z, ui::DomCode::US_Y, |
| ui::EF_CONTROL_DOWN, ui::DomKey::FromCharacter(']'), |
| base::TimeTicks()); |
| const Accelerator trigger(event); |
| EXPECT_TRUE(manager.IsRegistered(trigger)); |
| EXPECT_TRUE(manager.Process(trigger)); |
| } |
| |
| TEST_F(AcceleratorManagerTest, PositionalShortcuts_NonPositionalNonMatch) { |
| // Use a local instance so that the feature is enabled during construction. |
| AcceleratorManager manager; |
| manager.SetUsePositionalLookup(true); |
| |
| // Test ctrl + 'Z' for the German layout. The 'Y' key (in the US_Z position), |
| // should not match. Alphanumeric keys are not positional, and pressing the |
| // key with DomCode::US_Z should not match when it's mapped to VKEY_Y. |
| TestAcceleratorTarget target; |
| const Accelerator accelerator(VKEY_Z, EF_CONTROL_DOWN); |
| manager.Register({accelerator}, AcceleratorManager::kNormalPriority, &target); |
| KeyEvent event(ui::EventType::kKeyPressed, VKEY_Y, ui::DomCode::US_Z, |
| ui::EF_CONTROL_DOWN, ui::DomKey::FromCharacter(']'), |
| base::TimeTicks()); |
| const Accelerator trigger(event); |
| EXPECT_FALSE(manager.IsRegistered(trigger)); |
| EXPECT_FALSE(manager.Process(trigger)); |
| } |
| |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| } // namespace |
| } // namespace test |
| } // namespace ui |