summaryrefslogtreecommitdiff
path: root/chromium/ui/base/accelerators/accelerator_manager_unittest.cc
blob: 701c651ce144bd77530ebea1d943ad92458b6bdf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/base/accelerators/accelerator_manager.h"

#include "base/stl_util.h"
#include "base/test/scoped_feature_list.h"
#include "build/chromeos_buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/accelerators/test_accelerator_target.h"
#include "ui/base/ui_base_features.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.
const int kAcceleratorModifiers[] = {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 < base::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 << base::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 << base::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_ASH)
// TODO(crbug.com/1179893): Remove once the feature is enabled permanently.
TEST_F(AcceleratorManagerTest, NewMappingWithImprovedShortcutsDisabled) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kNewShortcutMapping},
      /*disabled_features=*/{::features::kImprovedKeyboardShortcuts});

  // Test new mapping with a ASCII punctuation shortcut that doesn't involve
  // shift.
  TestAcceleratorTarget target;
  {
    // ']' + ctrl
    const Accelerator accelerator(VKEY_OEM_6, EF_CONTROL_DOWN);
    manager_.Register({accelerator}, AcceleratorManager::kNormalPriority,
                      &target);
    KeyEvent event(ui::ET_KEY_PRESSED, ui::VKEY_1, ui::DomCode::NONE,
                   ui::EF_CONTROL_DOWN, ui::DomKey::Constant<']'>::Character,
                   base::TimeTicks());
    const Accelerator trigger(event);
    EXPECT_TRUE(manager_.IsRegistered(trigger));
    EXPECT_TRUE(manager_.Process(trigger));
  }

  // Test new mapping with a ASCII punctuation shortcut that involves shift.
  {
    // ']' + ctrl + shift, which produces '}' on US layout.
    const Accelerator accelerator(VKEY_OEM_6, EF_CONTROL_DOWN | EF_SHIFT_DOWN);
    manager_.Register({accelerator}, AcceleratorManager::kNormalPriority,
                      &target);
    KeyEvent event(ui::ET_KEY_PRESSED, ui::VKEY_1, ui::DomCode::NONE,
                   ui::EF_CONTROL_DOWN, ui::DomKey::Constant<'}'>::Character,
                   base::TimeTicks());
    const Accelerator trigger(event);
    EXPECT_TRUE(manager_.IsRegistered(trigger));
    EXPECT_TRUE(manager_.Process(trigger));
  }
}

// TODO(crbug.com/1179893): Remove once the feature is enabled permanently.
TEST_F(AcceleratorManagerTest, NewMappingSuperseded) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(features::kNewShortcutMapping);

  // When kImprovedKeyboardShortcuts is enabled, it takes precedence
  // over kNewShortcutMapping. Remove this test when kImprovedShortcutMapping
  // is made permanent.
  EXPECT_TRUE(::features::IsImprovedKeyboardShortcutsEnabled());
  EXPECT_FALSE(::features::IsNewShortcutMappingEnabled());
}

#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

#if defined(OS_CHROMEOS)

TEST_F(AcceleratorManagerTest, PositionalShortcuts_AllEqual) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(
      features::kImprovedKeyboardShortcuts);

  // 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::ET_KEY_PRESSED, VKEY_OEM_6, ui::DomCode::BRACKET_RIGHT,
                 ui::EF_CONTROL_DOWN, ui::DomKey::Constant<']'>::Character,
                 base::TimeTicks());
  const Accelerator trigger(event);
  EXPECT_TRUE(manager.IsRegistered(trigger));
  EXPECT_TRUE(manager.Process(trigger));
}

TEST_F(AcceleratorManagerTest, PositionalShortcuts_MatchingDomCode) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(
      features::kImprovedKeyboardShortcuts);

  // 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::ET_KEY_PRESSED, VKEY_OEM_PLUS, ui::DomCode::BRACKET_RIGHT,
                 ui::EF_CONTROL_DOWN, ui::DomKey::Constant<']'>::Character,
                 base::TimeTicks());
  const Accelerator trigger(event);
  EXPECT_TRUE(manager.IsRegistered(trigger));
  EXPECT_TRUE(manager.Process(trigger));
}

TEST_F(AcceleratorManagerTest, PositionalShortcuts_NotMatchingDomCode) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(
      features::kImprovedKeyboardShortcuts);

  // 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::ET_KEY_PRESSED, VKEY_OEM_6, ui::DomCode::BRACKET_LEFT,
                 ui::EF_CONTROL_DOWN, ui::DomKey::Constant<']'>::Character,
                 base::TimeTicks());
  const Accelerator trigger(event);
  EXPECT_FALSE(manager.IsRegistered(trigger));
  EXPECT_FALSE(manager.Process(trigger));
}

TEST_F(AcceleratorManagerTest, PositionalShortcuts_NonPositionalMatch) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(
      features::kImprovedKeyboardShortcuts);

  // 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::ET_KEY_PRESSED, VKEY_Z, ui::DomCode::US_Y,
                 ui::EF_CONTROL_DOWN, ui::DomKey::Constant<']'>::Character,
                 base::TimeTicks());
  const Accelerator trigger(event);
  EXPECT_TRUE(manager.IsRegistered(trigger));
  EXPECT_TRUE(manager.Process(trigger));
}

TEST_F(AcceleratorManagerTest, PositionalShortcuts_NonPositionalNonMatch) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(
      features::kImprovedKeyboardShortcuts);

  // 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::ET_KEY_PRESSED, VKEY_Y, ui::DomCode::US_Z,
                 ui::EF_CONTROL_DOWN, ui::DomKey::Constant<']'>::Character,
                 base::TimeTicks());
  const Accelerator trigger(event);
  EXPECT_FALSE(manager.IsRegistered(trigger));
  EXPECT_FALSE(manager.Process(trigger));
}

#endif  // defined(OS_CHROMEOS)

}  // namespace
}  // namespace test
}  // namespace ui