1// Copyright (c) 2013 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 "chrome/browser/extensions/extension_apitest.h"
6#include "chrome/browser/extensions/window_controller.h"
7#include "chrome/browser/ui/browser_window.h"
8#include "chrome/browser/ui/tabs/tab_strip_model.h"
9#include "chrome/test/base/interactive_test_utils.h"
10#include "content/public/test/browser_test_utils.h"
11#include "extensions/test/result_catcher.h"
12#include "ui/base/base_window.h"
13#include "ui/base/test/ui_controls.h"
14
15#if defined(OS_LINUX)
16#include <X11/Xlib.h>
17#include <X11/extensions/XTest.h>
18#include <X11/keysym.h>
19
20#include "ui/events/keycodes/keyboard_code_conversion_x.h"
21#include "ui/gfx/x/x11_types.h"
22#endif
23
24#if defined(OS_MACOSX)
25#include <Carbon/Carbon.h>
26
27#include "base/mac/scoped_cftyperef.h"
28#endif
29
30namespace extensions {
31
32typedef ExtensionApiTest GlobalCommandsApiTest;
33
34#if defined(OS_LINUX) && defined(USE_X11)
35// Send a simulated key press and release event, where |control|, |shift| or
36// |alt| indicates whether the key is struck with corresponding modifier.
37void SendNativeKeyEventToXDisplay(ui::KeyboardCode key,
38                                  bool control,
39                                  bool shift,
40                                  bool alt) {
41  Display* display = gfx::GetXDisplay();
42  KeyCode ctrl_key_code = XKeysymToKeycode(display, XK_Control_L);
43  KeyCode shift_key_code = XKeysymToKeycode(display, XK_Shift_L);
44  KeyCode alt_key_code = XKeysymToKeycode(display, XK_Alt_L);
45
46  // Release modifiers first of all to make sure this function can work as
47  // expected. For example, when |control| is false, but the status of Ctrl key
48  // is down, we will generate a keyboard event with unwanted Ctrl key.
49  XTestFakeKeyEvent(display, ctrl_key_code, False, CurrentTime);
50  XTestFakeKeyEvent(display, shift_key_code, False, CurrentTime);
51  XTestFakeKeyEvent(display, alt_key_code, False, CurrentTime);
52
53  typedef std::vector<KeyCode> KeyCodes;
54  KeyCodes key_codes;
55  if (control)
56    key_codes.push_back(ctrl_key_code);
57  if (shift)
58    key_codes.push_back(shift_key_code);
59  if (alt)
60    key_codes.push_back(alt_key_code);
61
62  key_codes.push_back(XKeysymToKeycode(display,
63                                       XKeysymForWindowsKeyCode(key, false)));
64
65  // Simulate the keys being pressed.
66  for (KeyCodes::iterator it = key_codes.begin(); it != key_codes.end(); it++)
67    XTestFakeKeyEvent(display, *it, True, CurrentTime);
68
69  // Simulate the keys being released.
70  for (KeyCodes::iterator it = key_codes.begin(); it != key_codes.end(); it++)
71    XTestFakeKeyEvent(display, *it, False, CurrentTime);
72
73  XFlush(display);
74}
75#endif  // OS_LINUX && USE_X11
76
77#if defined(OS_MACOSX)
78using base::ScopedCFTypeRef;
79
80void SendNativeCommandShift(int key_code) {
81  CGEventSourceRef event_source =
82      CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
83  CGEventTapLocation event_tap_location = kCGHIDEventTap;
84
85  // Create the keyboard press events.
86  ScopedCFTypeRef<CGEventRef> command_down(CGEventCreateKeyboardEvent(
87      event_source, kVK_Command, true));
88  ScopedCFTypeRef<CGEventRef> shift_down(CGEventCreateKeyboardEvent(
89      event_source, kVK_Shift, true));
90  ScopedCFTypeRef<CGEventRef> key_down(CGEventCreateKeyboardEvent(
91      event_source, key_code, true));
92  CGEventSetFlags(key_down, kCGEventFlagMaskCommand | kCGEventFlagMaskShift);
93
94  // Create the keyboard release events.
95  ScopedCFTypeRef<CGEventRef> command_up(CGEventCreateKeyboardEvent(
96      event_source, kVK_Command, false));
97  ScopedCFTypeRef<CGEventRef> shift_up(CGEventCreateKeyboardEvent(
98      event_source, kVK_Shift, false));
99  ScopedCFTypeRef<CGEventRef> key_up(CGEventCreateKeyboardEvent(
100      event_source, key_code, false));
101  CGEventSetFlags(key_up, kCGEventFlagMaskCommand | kCGEventFlagMaskShift);
102
103  // Post all of the events.
104  CGEventPost(event_tap_location, command_down);
105  CGEventPost(event_tap_location, shift_down);
106  CGEventPost(event_tap_location, key_down);
107  CGEventPost(event_tap_location, key_up);
108  CGEventPost(event_tap_location, shift_up);
109  CGEventPost(event_tap_location, command_up);
110
111  CFRelease(event_source);
112}
113#endif
114
115// Test the basics of global commands and make sure they work when Chrome
116// doesn't have focus. Also test that non-global commands are not treated as
117// global and that keys beyond Ctrl+Shift+[0..9] cannot be auto-assigned by an
118// extension.
119IN_PROC_BROWSER_TEST_F(GlobalCommandsApiTest, GlobalCommand) {
120  // Load the extension in the non-incognito browser.
121  ResultCatcher catcher;
122  ASSERT_TRUE(RunExtensionTest("keybinding/global")) << message_;
123  ASSERT_TRUE(catcher.GetNextResult());
124
125#if defined(OS_WIN) || defined(OS_CHROMEOS)
126  // Our infrastructure for sending keys expects a browser to send them to, but
127  // to properly test global shortcuts you need to send them to another target.
128  // So, create an incognito browser to use as a target to send the shortcuts
129  // to. It will ignore all of them and allow us test whether the global
130  // shortcut really is global in nature and also that the non-global shortcut
131  // is non-global.
132  Browser* incognito_browser = CreateIncognitoBrowser();
133
134  // Try to activate the non-global shortcut (Ctrl+Shift+1) and the
135  // non-assignable shortcut (Ctrl+Shift+A) by sending the keystrokes to the
136  // incognito browser. Both shortcuts should have no effect (extension is not
137  // loaded there).
138  ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
139      incognito_browser, ui::VKEY_1, true, true, false, false));
140  ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
141      incognito_browser, ui::VKEY_A, true, true, false, false));
142
143  // Activate the shortcut (Ctrl+Shift+8). This should have an effect.
144  ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
145      incognito_browser, ui::VKEY_8, true, true, false, false));
146#elif defined(OS_LINUX) && defined(USE_X11)
147  // Create an incognito browser to capture the focus.
148  CreateIncognitoBrowser();
149
150  // On Linux, our infrastructure for sending keys just synthesize keyboard
151  // event and send them directly to the specified window, without notifying the
152  // X root window. It didn't work while testing global shortcut because the
153  // stuff of global shortcut on Linux need to be notified when KeyPress event
154  // is happening on X root window. So we simulate the keyboard input here.
155  SendNativeKeyEventToXDisplay(ui::VKEY_1, true, true, false);
156  SendNativeKeyEventToXDisplay(ui::VKEY_A, true, true, false);
157  SendNativeKeyEventToXDisplay(ui::VKEY_8, true, true, false);
158#elif defined(OS_MACOSX)
159  // Create an incognito browser to capture the focus.
160  CreateIncognitoBrowser();
161
162  // Send some native mac key events.
163  SendNativeCommandShift(kVK_ANSI_1);
164  SendNativeCommandShift(kVK_ANSI_A);
165  SendNativeCommandShift(kVK_ANSI_8);
166#endif
167
168  // If this fails, it might be because the global shortcut failed to work,
169  // but it might also be because the non-global shortcuts unexpectedly
170  // worked.
171  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
172}
173
174#if defined(OS_WIN)
175// Feature only fully implemented on Windows, other platforms coming.
176// TODO(smus): On mac, SendKeyPress must first support media keys.
177#define MAYBE_GlobalDuplicatedMediaKey GlobalDuplicatedMediaKey
178#else
179#define MAYBE_GlobalDuplicatedMediaKey DISABLED_GlobalDuplicatedMediaKey
180#endif
181
182IN_PROC_BROWSER_TEST_F(GlobalCommandsApiTest, MAYBE_GlobalDuplicatedMediaKey) {
183  ResultCatcher catcher;
184  ASSERT_TRUE(RunExtensionTest("keybinding/global_media_keys_0")) << message_;
185  ASSERT_TRUE(catcher.GetNextResult());
186  ASSERT_TRUE(RunExtensionTest("keybinding/global_media_keys_1")) << message_;
187  ASSERT_TRUE(catcher.GetNextResult());
188
189  Browser* incognito_browser = CreateIncognitoBrowser();  // Ditto.
190  WindowController* controller =
191      incognito_browser->extension_window_controller();
192
193  ui_controls::SendKeyPress(controller->window()->GetNativeWindow(),
194                            ui::VKEY_MEDIA_NEXT_TRACK,
195                            false,
196                            false,
197                            false,
198                            false);
199
200  // We should get two success results.
201  ASSERT_TRUE(catcher.GetNextResult());
202  ASSERT_TRUE(catcher.GetNextResult());
203}
204
205}  // namespace extensions
206