1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <gtest/gtest.h>
6
7#import <Carbon/Carbon.h>
8#import <Cocoa/Cocoa.h>
9
10#include "base/files/file_path.h"
11#import "base/mac/foundation_util.h"
12#include "base/mac/scoped_nsobject.h"
13#include "base/path_service.h"
14#import "content/browser/cocoa/system_hotkey_map.h"
15#include "content/public/common/content_paths.h"
16
17namespace content {
18
19class SystemHotkeyMapTest : public ::testing::Test {
20 protected:
21  SystemHotkeyMapTest() {}
22
23  NSDictionary* DictionaryFromTestFile(const char* file) {
24    base::FilePath test_data_dir;
25    bool result = base::PathService::Get(DIR_TEST_DATA, &test_data_dir);
26    if (!result)
27      return nil;
28
29    base::FilePath test_path = test_data_dir.AppendASCII(file);
30    return [NSDictionary
31        dictionaryWithContentsOfURL:base::mac::FilePathToNSURL(test_path)];
32  }
33
34  void AddEntryToDictionary(BOOL enabled,
35                            unsigned short key_code,
36                            NSUInteger modifiers) {
37    NSMutableArray* parameters = [NSMutableArray array];
38    // The first parameter is unused.
39    [parameters addObject:[NSNumber numberWithInt:65535]];
40    [parameters addObject:[NSNumber numberWithUnsignedShort:key_code]];
41    [parameters addObject:[NSNumber numberWithUnsignedInteger:modifiers]];
42
43    NSMutableDictionary* value_dictionary = [NSMutableDictionary dictionary];
44    [value_dictionary setObject:parameters forKey:@"parameters"];
45    [value_dictionary setObject:@"standard" forKey:@"type"];
46
47    NSMutableDictionary* outer_dictionary = [NSMutableDictionary dictionary];
48    [outer_dictionary setObject:value_dictionary forKey:@"value"];
49
50    NSNumber* enabled_number = [NSNumber numberWithBool:enabled];
51    [outer_dictionary setObject:enabled_number forKey:@"enabled"];
52
53    NSString* key = [NSString stringWithFormat:@"%d", count_];
54    [system_hotkey_inner_dictionary_ setObject:outer_dictionary forKey:key];
55    ++count_;
56  }
57
58  void SetUp() override {
59    system_hotkey_dictionary_.reset([[NSMutableDictionary alloc] init]);
60    system_hotkey_inner_dictionary_.reset([[NSMutableDictionary alloc] init]);
61    [system_hotkey_dictionary_ setObject:system_hotkey_inner_dictionary_
62                                  forKey:@"AppleSymbolicHotKeys"];
63    count_ = 100;
64  }
65
66  void TearDown() override {
67    system_hotkey_dictionary_.reset();
68    system_hotkey_inner_dictionary_.reset();
69  }
70
71  // A constructed dictionary that matches the format of the one that would be
72  // parsed from the system hotkeys plist.
73  base::scoped_nsobject<NSMutableDictionary> system_hotkey_dictionary_;
74
75 private:
76  // A reference to the mutable dictionary to which new entries are added.
77  base::scoped_nsobject<NSMutableDictionary> system_hotkey_inner_dictionary_;
78  // Each entry in the system_hotkey_inner_dictionary_ needs to have a unique
79  // key. This count is used to generate those unique keys.
80  int count_;
81};
82
83TEST_F(SystemHotkeyMapTest, Parse) {
84  // This plist was pulled from a real machine. It is extensively populated,
85  // and has no missing or incomplete entries.
86  NSDictionary* dictionary =
87      DictionaryFromTestFile("mac/mac_system_hotkeys.plist");
88  ASSERT_TRUE(dictionary);
89
90  SystemHotkeyMap map;
91  bool result = map.ParseDictionary(dictionary);
92  EXPECT_TRUE(result);
93
94  // Command + ` is a common key binding. It should exist.
95  unsigned short key_code = kVK_ANSI_Grave;
96  NSUInteger modifiers = NSCommandKeyMask;
97  EXPECT_TRUE(map.IsHotkeyReserved(key_code, modifiers));
98
99  // Command + Shift + ` is a common key binding. It should exist.
100  modifiers = NSCommandKeyMask | NSShiftKeyMask;
101  EXPECT_TRUE(map.IsHotkeyReserved(key_code, modifiers));
102
103  // Command + Shift + Ctr + ` is not a common key binding.
104  modifiers = NSCommandKeyMask | NSShiftKeyMask | NSControlKeyMask;
105  EXPECT_FALSE(map.IsHotkeyReserved(key_code, modifiers));
106
107  // Command + L is not a common key binding.
108  key_code = kVK_ANSI_L;
109  modifiers = NSCommandKeyMask;
110  EXPECT_FALSE(map.IsHotkeyReserved(key_code, modifiers));
111}
112
113TEST_F(SystemHotkeyMapTest, ParseNil) {
114  NSDictionary* dictionary = nil;
115
116  SystemHotkeyMap map;
117  bool result = map.ParseDictionary(dictionary);
118  EXPECT_FALSE(result);
119}
120
121TEST_F(SystemHotkeyMapTest, ParseMouse) {
122  // This plist was pulled from a real machine. It has missing entries,
123  // incomplete entries, and mouse hotkeys.
124  NSDictionary* dictionary =
125      DictionaryFromTestFile("mac/mac_system_hotkeys_sparse.plist");
126  ASSERT_TRUE(dictionary);
127
128  SystemHotkeyMap map;
129  bool result = map.ParseDictionary(dictionary);
130  EXPECT_TRUE(result);
131
132  // Command + ` is a common key binding. It is missing, but since OS X uses the
133  // default value the hotkey should still be reserved.
134  // https://crbug.com/383558
135  // https://crbug.com/145062
136  unsigned short key_code = kVK_ANSI_Grave;
137  NSUInteger modifiers = NSCommandKeyMask;
138  EXPECT_TRUE(map.IsHotkeyReserved(key_code, modifiers));
139
140  // There is a mouse keybinding for 0x08. It should not apply to keyboard
141  // hotkeys.
142  key_code = kVK_ANSI_C;
143  modifiers = 0;
144  EXPECT_FALSE(map.IsHotkeyReserved(key_code, modifiers));
145
146  // Command + Alt + = is an accessibility shortcut. Its entry in the plist is
147  // incomplete.
148  // TODO(erikchen): OSX uses the default bindings, so this hotkey should still
149  // be reserved.
150  // http://crbug.com/383558
151  key_code = kVK_ANSI_Equal;
152  modifiers = NSCommandKeyMask | NSAlternateKeyMask;
153  EXPECT_FALSE(map.IsHotkeyReserved(key_code, modifiers));
154}
155
156TEST_F(SystemHotkeyMapTest, ParseCustomEntries) {
157  unsigned short key_code = kVK_ANSI_C;
158
159  AddEntryToDictionary(YES, key_code, 0);
160  AddEntryToDictionary(YES, key_code, NSAlphaShiftKeyMask);
161  AddEntryToDictionary(YES, key_code, NSShiftKeyMask);
162  AddEntryToDictionary(YES, key_code, NSControlKeyMask);
163  AddEntryToDictionary(YES, key_code, NSFunctionKeyMask);
164  AddEntryToDictionary(YES, key_code, NSFunctionKeyMask | NSControlKeyMask);
165  AddEntryToDictionary(NO, key_code, NSAlternateKeyMask);
166
167  SystemHotkeyMap map;
168
169  bool result = map.ParseDictionary(system_hotkey_dictionary_.get());
170  EXPECT_TRUE(result);
171
172  // Entries without control, command, or alternate key mask should not be
173  // reserved.
174  EXPECT_FALSE(map.IsHotkeyReserved(key_code, 0));
175  EXPECT_FALSE(map.IsHotkeyReserved(key_code, NSAlphaShiftKeyMask));
176  EXPECT_FALSE(map.IsHotkeyReserved(key_code, NSShiftKeyMask));
177  EXPECT_FALSE(map.IsHotkeyReserved(key_code, NSFunctionKeyMask));
178
179  // Unlisted entries should not be reserved.
180  EXPECT_FALSE(map.IsHotkeyReserved(key_code, NSCommandKeyMask));
181
182  // Disabled entries should not be reserved.
183  EXPECT_FALSE(map.IsHotkeyReserved(key_code, NSAlternateKeyMask));
184
185  // Other entries should be reserved.
186  EXPECT_TRUE(map.IsHotkeyReserved(key_code, NSControlKeyMask));
187  EXPECT_TRUE(
188      map.IsHotkeyReserved(key_code, NSFunctionKeyMask | NSControlKeyMask));
189}
190
191}  // namespace content
192