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