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