1 // Copyright (c) 2012 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.
5 // This functionality currently works on Windows and on Linux when
6 // toolkit_views is defined (i.e. for Chrome OS). It's not needed
7 // on the Mac, and it's not yet implemented on Linux.
9 #include "base/memory/weak_ptr.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/strings/string_util.h"
12 #include "base/time/time.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/browser_window.h"
16 #include "chrome/browser/ui/tabs/tab_strip_model.h"
17 #include "chrome/browser/ui/views/frame/browser_view.h"
18 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
19 #include "chrome/test/base/in_process_browser_test.h"
20 #include "chrome/test/base/interactive_test_utils.h"
21 #include "chrome/test/base/ui_test_utils.h"
22 #include "ui/base/test/ui_controls.h"
23 #include "ui/events/event_constants.h"
24 #include "ui/events/keycodes/keyboard_codes.h"
25 #include "ui/views/controls/menu/menu_listener.h"
26 #include "ui/views/focus/focus_manager.h"
27 #include "ui/views/view.h"
28 #include "ui/views/widget/widget.h"
32 // An async version of SendKeyPressSync since we don't get notified when a
34 void SendKeyPress(Browser
* browser
, ui::KeyboardCode key
) {
35 ASSERT_TRUE(ui_controls::SendKeyPress(
36 browser
->window()->GetNativeWindow(), key
, false, false, false, false));
39 // Helper class that waits until the focus has changed to a view other
40 // than the one with the provided view id.
41 class ViewFocusChangeWaiter
: public views::FocusChangeListener
{
43 ViewFocusChangeWaiter(views::FocusManager
* focus_manager
,
45 : focus_manager_(focus_manager
),
46 previous_view_id_(previous_view_id
),
48 focus_manager_
->AddFocusChangeListener(this);
49 // Call the focus change notification once in case the focus has
51 OnWillChangeFocus(NULL
, focus_manager_
->GetFocusedView());
54 ~ViewFocusChangeWaiter() override
{
55 focus_manager_
->RemoveFocusChangeListener(this);
59 content::RunMessageLoop();
63 // Inherited from FocusChangeListener
64 void OnWillChangeFocus(views::View
* focused_before
,
65 views::View
* focused_now
) override
{}
67 void OnDidChangeFocus(views::View
* focused_before
,
68 views::View
* focused_now
) override
{
69 if (focused_now
&& focused_now
->id() != previous_view_id_
) {
70 base::MessageLoop::current()->PostTask(FROM_HERE
,
71 base::MessageLoop::QuitClosure());
75 views::FocusManager
* focus_manager_
;
76 int previous_view_id_
;
77 base::WeakPtrFactory
<ViewFocusChangeWaiter
> weak_factory_
;
79 DISALLOW_COPY_AND_ASSIGN(ViewFocusChangeWaiter
);
82 class SendKeysMenuListener
: public views::MenuListener
{
84 SendKeysMenuListener(ToolbarView
* toolbar_view
,
86 bool test_dismiss_menu
)
87 : toolbar_view_(toolbar_view
), browser_(browser
), menu_open_count_(0),
88 test_dismiss_menu_(test_dismiss_menu
) {
89 toolbar_view_
->AddMenuListener(this);
92 ~SendKeysMenuListener() override
{
93 if (test_dismiss_menu_
)
94 toolbar_view_
->RemoveMenuListener(this);
97 int menu_open_count() const {
98 return menu_open_count_
;
102 // Overridden from views::MenuListener:
103 void OnMenuOpened() override
{
105 if (!test_dismiss_menu_
) {
106 toolbar_view_
->RemoveMenuListener(this);
107 // Press DOWN to select the first item, then RETURN to select it.
108 SendKeyPress(browser_
, ui::VKEY_DOWN
);
109 SendKeyPress(browser_
, ui::VKEY_RETURN
);
111 SendKeyPress(browser_
, ui::VKEY_ESCAPE
);
112 base::MessageLoop::current()->PostDelayedTask(
114 base::MessageLoop::QuitClosure(),
115 base::TimeDelta::FromMilliseconds(200));
119 ToolbarView
* toolbar_view_
;
121 // Keeps track of the number of times the menu was opened.
122 int menu_open_count_
;
123 // If this is set then on receiving a notification that the menu was opened
124 // we dismiss it by sending the ESC key.
125 bool test_dismiss_menu_
;
127 DISALLOW_COPY_AND_ASSIGN(SendKeysMenuListener
);
130 class KeyboardAccessTest
: public InProcessBrowserTest
{
132 KeyboardAccessTest() {}
134 // Use the keyboard to select "New Tab" from the app menu.
135 // This test depends on the fact that there is one menu and that
136 // New Tab is the first item in the menu. If the menus change,
137 // this test will need to be changed to reflect that.
139 // If alternate_key_sequence is true, use "Alt" instead of "F10" to
140 // open the menu bar, and "Down" instead of "Enter" to open a menu.
141 // If focus_omnibox is true then the test on startup sets focus to the
143 void TestMenuKeyboardAccess(bool alternate_key_sequence
,
147 int GetFocusedViewID() {
148 gfx::NativeWindow window
= browser()->window()->GetNativeWindow();
149 views::Widget
* widget
= views::Widget::GetWidgetForNativeWindow(window
);
150 const views::FocusManager
* focus_manager
= widget
->GetFocusManager();
151 const views::View
* focused_view
= focus_manager
->GetFocusedView();
152 return focused_view
? focused_view
->id() : -1;
155 void WaitForFocusedViewIDToChange(int original_view_id
) {
156 if (GetFocusedViewID() != original_view_id
)
158 gfx::NativeWindow window
= browser()->window()->GetNativeWindow();
159 views::Widget
* widget
= views::Widget::GetWidgetForNativeWindow(window
);
160 views::FocusManager
* focus_manager
= widget
->GetFocusManager();
161 ViewFocusChangeWaiter
waiter(focus_manager
, original_view_id
);
166 // Opens the system menu on Windows with the Alt Space combination and selects
167 // the New Tab option from the menu.
168 void TestSystemMenuWithKeyboard();
171 // Uses the keyboard to select the wrench menu i.e. with the F10 key.
172 // It verifies that the menu when dismissed by sending the ESC key it does
173 // not display twice.
174 void TestMenuKeyboardAccessAndDismiss();
176 DISALLOW_COPY_AND_ASSIGN(KeyboardAccessTest
);
179 void KeyboardAccessTest::TestMenuKeyboardAccess(bool alternate_key_sequence
,
181 bool focus_omnibox
) {
182 // Navigate to a page in the first tab, which makes sure that focus is
183 // set to the browser window.
184 ui_test_utils::NavigateToURL(browser(), GURL("about:"));
186 // The initial tab index should be 0.
187 ASSERT_EQ(0, browser()->tab_strip_model()->active_index());
189 ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
191 // Get the focused view ID, then press a key to activate the
192 // page menu, then wait until the focused view changes.
193 int original_view_id
= GetFocusedViewID();
195 content::WindowedNotificationObserver
new_tab_observer(
196 chrome::NOTIFICATION_TAB_ADDED
,
197 content::Source
<content::WebContentsDelegate
>(browser()));
199 BrowserView
* browser_view
= reinterpret_cast<BrowserView
*>(
200 browser()->window());
201 ToolbarView
* toolbar_view
= browser_view
->GetToolbarView();
202 SendKeysMenuListener
menu_listener(toolbar_view
, browser(), false);
205 browser()->window()->GetLocationBar()->FocusLocation(false);
207 #if defined(OS_CHROMEOS)
208 // Chrome OS doesn't have a way to just focus the wrench menu, so we use Alt+F
209 // to bring up the menu.
210 ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
211 browser(), ui::VKEY_F
, false, shift
, true, false));
213 ui::KeyboardCode menu_key
=
214 alternate_key_sequence
? ui::VKEY_MENU
: ui::VKEY_F10
;
215 ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
216 browser(), menu_key
, false, shift
, false, false));
220 // Verify Chrome does not move the view focus. We should not move the view
221 // focus when typing a menu key with modifier keys, such as shift keys or
223 int new_view_id
= GetFocusedViewID();
224 ASSERT_EQ(original_view_id
, new_view_id
);
228 WaitForFocusedViewIDToChange(original_view_id
);
230 // See above comment. Since we already brought up the menu, no need to do this
232 #if !defined(OS_CHROMEOS)
233 if (alternate_key_sequence
)
234 SendKeyPress(browser(), ui::VKEY_DOWN
);
236 SendKeyPress(browser(), ui::VKEY_RETURN
);
239 // Wait for the new tab to appear.
240 new_tab_observer
.Wait();
242 // Make sure that the new tab index is 1.
243 ASSERT_EQ(1, browser()->tab_strip_model()->active_index());
248 // This CBT hook is set for the duration of the TestSystemMenuWithKeyboard test
249 LRESULT CALLBACK
SystemMenuTestCBTHook(int n_code
,
252 // Look for the system menu window getting created or becoming visible and
253 // then select the New Tab option from the menu.
254 if (n_code
== HCBT_ACTIVATE
|| n_code
== HCBT_CREATEWND
) {
255 wchar_t class_name
[MAX_PATH
] = {0};
256 GetClassName(reinterpret_cast<HWND
>(w_param
),
258 arraysize(class_name
));
259 if (base::LowerCaseEqualsASCII(class_name
, "#32768")) {
260 // Select the New Tab option and then send the enter key to execute it.
261 ::PostMessage(reinterpret_cast<HWND
>(w_param
), WM_CHAR
, 'T', 0);
262 ::PostMessage(reinterpret_cast<HWND
>(w_param
), WM_KEYDOWN
, VK_RETURN
, 0);
263 ::PostMessage(reinterpret_cast<HWND
>(w_param
), WM_KEYUP
, VK_RETURN
, 0);
266 return ::CallNextHookEx(0, n_code
, w_param
, l_param
);
269 void KeyboardAccessTest::TestSystemMenuWithKeyboard() {
270 // Navigate to a page in the first tab, which makes sure that focus is
271 // set to the browser window.
272 ui_test_utils::NavigateToURL(browser(), GURL("about:"));
274 ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
276 content::WindowedNotificationObserver
new_tab_observer(
277 chrome::NOTIFICATION_TAB_ADDED
,
278 content::Source
<content::WebContentsDelegate
>(browser()));
279 // Sending the Alt space keys to the browser will bring up the system menu
280 // which runs a model loop. We set a CBT hook to look for the menu and send
282 HHOOK cbt_hook
= ::SetWindowsHookEx(WH_CBT
,
283 SystemMenuTestCBTHook
,
285 ::GetCurrentThreadId());
286 ASSERT_TRUE(cbt_hook
!= NULL
);
288 bool ret
= ui_test_utils::SendKeyPressSync(
289 browser(), ui::VKEY_SPACE
, false, false, true, false);
293 // Wait for the new tab to appear.
294 new_tab_observer
.Wait();
295 // Make sure that the new tab index is 1.
296 ASSERT_EQ(1, browser()->tab_strip_model()->active_index());
298 ::UnhookWindowsHookEx(cbt_hook
);
302 void KeyboardAccessTest::TestMenuKeyboardAccessAndDismiss() {
303 ui_test_utils::NavigateToURL(browser(), GURL("about:"));
305 ASSERT_EQ(0, browser()->tab_strip_model()->active_index());
307 ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
309 int original_view_id
= GetFocusedViewID();
311 BrowserView
* browser_view
= reinterpret_cast<BrowserView
*>(
312 browser()->window());
313 ToolbarView
* toolbar_view
= browser_view
->GetToolbarView();
314 SendKeysMenuListener
menu_listener(toolbar_view
, browser(), true);
316 browser()->window()->GetLocationBar()->FocusLocation(false);
318 ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
319 browser(), ui::VKEY_F10
, false, false, false, false));
321 WaitForFocusedViewIDToChange(original_view_id
);
323 SendKeyPress(browser(), ui::VKEY_DOWN
);
324 content::RunMessageLoop();
325 ASSERT_EQ(1, menu_listener
.menu_open_count());
328 // http://crbug.com/62310.
329 #if defined(OS_CHROMEOS)
330 #define MAYBE_TestMenuKeyboardAccess DISABLED_TestMenuKeyboardAccess
332 #define MAYBE_TestMenuKeyboardAccess TestMenuKeyboardAccess
335 IN_PROC_BROWSER_TEST_F(KeyboardAccessTest
, MAYBE_TestMenuKeyboardAccess
) {
336 TestMenuKeyboardAccess(false, false, false);
339 // http://crbug.com/62310.
340 #if defined(OS_CHROMEOS)
341 #define MAYBE_TestAltMenuKeyboardAccess DISABLED_TestAltMenuKeyboardAccess
343 #define MAYBE_TestAltMenuKeyboardAccess TestAltMenuKeyboardAccess
346 IN_PROC_BROWSER_TEST_F(KeyboardAccessTest
, MAYBE_TestAltMenuKeyboardAccess
) {
347 TestMenuKeyboardAccess(true, false, false);
350 // If this flakes, use http://crbug.com/62311.
352 #define MAYBE_TestShiftAltMenuKeyboardAccess DISABLED_TestShiftAltMenuKeyboardAccess
354 #define MAYBE_TestShiftAltMenuKeyboardAccess TestShiftAltMenuKeyboardAccess
356 IN_PROC_BROWSER_TEST_F(KeyboardAccessTest
,
357 MAYBE_TestShiftAltMenuKeyboardAccess
) {
358 TestMenuKeyboardAccess(true, true, false);
362 IN_PROC_BROWSER_TEST_F(KeyboardAccessTest
,
363 DISABLED_TestAltMenuKeyboardAccessFocusOmnibox
) {
364 TestMenuKeyboardAccess(true, false, true);
367 IN_PROC_BROWSER_TEST_F(KeyboardAccessTest
,
368 DISABLED_TestSystemMenuWithKeyboard
) {
369 TestSystemMenuWithKeyboard();
373 #if !defined(OS_WIN) && defined(USE_AURA) && !defined(USE_OZONE)
374 // ozone bringup - http://crbug.com/401304
375 IN_PROC_BROWSER_TEST_F(KeyboardAccessTest
, TestMenuKeyboardOpenDismiss
) {
376 TestMenuKeyboardAccessAndDismiss();
380 // Test that JavaScript cannot intercept reserved keyboard accelerators like
381 // ctrl-t to open a new tab or ctrl-f4 to close a tab.
382 // TODO(isherman): This test times out on ChromeOS. We should merge it with
383 // BrowserKeyEventsTest.ReservedAccelerators, but just disable for now.
384 // If this flakes, use http://crbug.com/62311.
385 IN_PROC_BROWSER_TEST_F(KeyboardAccessTest
, ReserveKeyboardAccelerators
) {
386 const std::string kBadPage
=
388 "document.onkeydown = function() {"
389 " event.preventDefault();"
393 GURL
url("data:text/html," + kBadPage
);
394 ui_test_utils::NavigateToURLWithDisposition(
395 browser(), url
, NEW_FOREGROUND_TAB
,
396 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION
);
398 ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
399 browser(), ui::VKEY_TAB
, true, false, false, false));
400 ASSERT_EQ(0, browser()->tab_strip_model()->active_index());
402 ui_test_utils::NavigateToURLWithDisposition(
403 browser(), url
, NEW_FOREGROUND_TAB
,
404 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION
);
405 ASSERT_EQ(2, browser()->tab_strip_model()->active_index());
407 ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
408 browser(), ui::VKEY_W
, true, false, false, false));
409 ASSERT_EQ(0, browser()->tab_strip_model()->active_index());
412 #if defined(OS_WIN) // These keys are Windows-only.
413 IN_PROC_BROWSER_TEST_F(KeyboardAccessTest
, BackForwardKeys
) {
414 // Navigate to create some history.
415 ui_test_utils::NavigateToURL(browser(), GURL("chrome://version/"));
416 ui_test_utils::NavigateToURL(browser(), GURL("chrome://about/"));
418 base::string16 before_back
;
419 ASSERT_TRUE(ui_test_utils::GetCurrentTabTitle(browser(), &before_back
));
422 ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
423 browser(), ui::VKEY_BROWSER_BACK
, false, false, false, false));
425 base::string16 after_back
;
426 ASSERT_TRUE(ui_test_utils::GetCurrentTabTitle(browser(), &after_back
));
428 EXPECT_NE(before_back
, after_back
);
431 ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
432 browser(), ui::VKEY_BROWSER_FORWARD
, false, false, false, false));
434 base::string16 after_forward
;
435 ASSERT_TRUE(ui_test_utils::GetCurrentTabTitle(browser(), &after_forward
));
437 EXPECT_EQ(before_back
, after_forward
);