| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // This functionality currently works on Windows and on Linux when |
| // toolkit_views is defined (i.e. for Chrome OS). It's not needed |
| // on the Mac, and it's not yet implemented on Linux. |
| |
| #include "base/memory/weak_ptr.h" |
| #include "base/message_loop.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/views/frame/browser_view.h" |
| #include "chrome/browser/ui/views/toolbar_view.h" |
| #include "chrome/common/chrome_notification_types.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "ui/base/events/event_constants.h" |
| #include "ui/base/keycodes/keyboard_codes.h" |
| #include "ui/ui_controls/ui_controls.h" |
| #include "ui/views/controls/menu/menu_listener.h" |
| #include "ui/views/focus/focus_manager.h" |
| #include "ui/views/view.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace { |
| |
| // An async version of SendKeyPressSync since we don't get notified when a |
| // menu is showing. |
| void SendKeyPress(Browser* browser, ui::KeyboardCode key) { |
| ASSERT_TRUE(ui_controls::SendKeyPress( |
| browser->window()->GetNativeWindow(), key, false, false, false, false)); |
| } |
| |
| // Helper class that waits until the focus has changed to a view other |
| // than the one with the provided view id. |
| class ViewFocusChangeWaiter : public views::FocusChangeListener { |
| public: |
| ViewFocusChangeWaiter(views::FocusManager* focus_manager, |
| int previous_view_id) |
| : focus_manager_(focus_manager), |
| previous_view_id_(previous_view_id), |
| ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { |
| focus_manager_->AddFocusChangeListener(this); |
| // Call the focus change notification once in case the focus has |
| // already changed. |
| OnWillChangeFocus(NULL, focus_manager_->GetFocusedView()); |
| } |
| |
| virtual ~ViewFocusChangeWaiter() { |
| focus_manager_->RemoveFocusChangeListener(this); |
| } |
| |
| void Wait() { |
| content::RunMessageLoop(); |
| } |
| |
| private: |
| // Inherited from FocusChangeListener |
| virtual void OnWillChangeFocus(views::View* focused_before, |
| views::View* focused_now) { |
| } |
| |
| virtual void OnDidChangeFocus(views::View* focused_before, |
| views::View* focused_now) { |
| if (focused_now && focused_now->id() != previous_view_id_) { |
| MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure()); |
| } |
| } |
| |
| views::FocusManager* focus_manager_; |
| int previous_view_id_; |
| base::WeakPtrFactory<ViewFocusChangeWaiter> weak_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ViewFocusChangeWaiter); |
| }; |
| |
| class SendKeysMenuListener : public views::MenuListener { |
| public: |
| SendKeysMenuListener(ToolbarView* toolbar_view, Browser* browser) |
| : toolbar_view_(toolbar_view), browser_(browser) { |
| toolbar_view_->AddMenuListener(this); |
| } |
| |
| virtual ~SendKeysMenuListener() {} |
| |
| private: |
| // Overridden from views::MenuListener: |
| virtual void OnMenuOpened() OVERRIDE { |
| toolbar_view_->RemoveMenuListener(this); |
| // Press DOWN to select the first item, then RETURN to select it. |
| SendKeyPress(browser_, ui::VKEY_DOWN); |
| SendKeyPress(browser_, ui::VKEY_RETURN); |
| } |
| |
| ToolbarView* toolbar_view_; |
| Browser* browser_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SendKeysMenuListener); |
| }; |
| |
| class KeyboardAccessTest : public InProcessBrowserTest { |
| public: |
| KeyboardAccessTest() {} |
| |
| // Use the keyboard to select "New Tab" from the app menu. |
| // This test depends on the fact that there is one menu and that |
| // New Tab is the first item in the menu. If the menus change, |
| // this test will need to be changed to reflect that. |
| // |
| // If alternate_key_sequence is true, use "Alt" instead of "F10" to |
| // open the menu bar, and "Down" instead of "Enter" to open a menu. |
| void TestMenuKeyboardAccess(bool alternate_key_sequence, bool shift); |
| |
| int GetFocusedViewID() { |
| gfx::NativeWindow window = browser()->window()->GetNativeWindow(); |
| views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window); |
| const views::FocusManager* focus_manager = widget->GetFocusManager(); |
| const views::View* focused_view = focus_manager->GetFocusedView(); |
| return focused_view ? focused_view->id() : -1; |
| } |
| |
| void WaitForFocusedViewIDToChange(int original_view_id) { |
| if (GetFocusedViewID() != original_view_id) |
| return; |
| gfx::NativeWindow window = browser()->window()->GetNativeWindow(); |
| views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window); |
| views::FocusManager* focus_manager = widget->GetFocusManager(); |
| ViewFocusChangeWaiter waiter(focus_manager, original_view_id); |
| waiter.Wait(); |
| } |
| |
| DISALLOW_COPY_AND_ASSIGN(KeyboardAccessTest); |
| }; |
| |
| void KeyboardAccessTest::TestMenuKeyboardAccess(bool alternate_key_sequence, |
| bool shift) { |
| // Navigate to a page in the first tab, which makes sure that focus is |
| // set to the browser window. |
| ui_test_utils::NavigateToURL(browser(), GURL("about:")); |
| |
| // The initial tab index should be 0. |
| ASSERT_EQ(0, browser()->active_index()); |
| |
| ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser())); |
| |
| // Get the focused view ID, then press a key to activate the |
| // page menu, then wait until the focused view changes. |
| int original_view_id = GetFocusedViewID(); |
| |
| content::WindowedNotificationObserver new_tab_observer( |
| chrome::NOTIFICATION_TAB_ADDED, |
| content::Source<content::WebContentsDelegate>(browser())); |
| |
| BrowserView* browser_view = reinterpret_cast<BrowserView*>( |
| browser()->window()); |
| ToolbarView* toolbar_view = browser_view->GetToolbarView(); |
| SendKeysMenuListener menu_listener(toolbar_view, browser()); |
| |
| #if defined(OS_CHROMEOS) |
| // Chrome OS doesn't have a way to just focus the wrench menu, so we use Alt+F |
| // to bring up the menu. |
| ASSERT_TRUE(ui_test_utils::SendKeyPressSync( |
| browser(), ui::VKEY_F, false, shift, true, false)); |
| #else |
| ui::KeyboardCode menu_key = |
| alternate_key_sequence ? ui::VKEY_MENU : ui::VKEY_F10; |
| ASSERT_TRUE(ui_test_utils::SendKeyPressSync( |
| browser(), menu_key, false, shift, false, false)); |
| #endif |
| |
| if (shift) { |
| // Verify Chrome does not move the view focus. We should not move the view |
| // focus when typing a menu key with modifier keys, such as shift keys or |
| // control keys. |
| int new_view_id = GetFocusedViewID(); |
| ASSERT_EQ(original_view_id, new_view_id); |
| return; |
| } |
| |
| WaitForFocusedViewIDToChange(original_view_id); |
| |
| // See above comment. Since we already brought up the menu, no need to do this |
| // on ChromeOS. |
| #if !defined(OS_CHROMEOS) |
| if (alternate_key_sequence) |
| SendKeyPress(browser(), ui::VKEY_DOWN); |
| else |
| SendKeyPress(browser(), ui::VKEY_RETURN); |
| #endif |
| |
| // Wait for the new tab to appear. |
| new_tab_observer.Wait(); |
| |
| // Make sure that the new tab index is 1. |
| ASSERT_EQ(1, browser()->active_index()); |
| } |
| |
| // http://crbug.com/62310. |
| #if defined(OS_CHROMEOS) |
| #define MAYBE_TestMenuKeyboardAccess DISABLED_TestMenuKeyboardAccess |
| #else |
| #define MAYBE_TestMenuKeyboardAccess TestMenuKeyboardAccess |
| #endif |
| |
| IN_PROC_BROWSER_TEST_F(KeyboardAccessTest, MAYBE_TestMenuKeyboardAccess) { |
| TestMenuKeyboardAccess(false, false); |
| } |
| |
| // http://crbug.com/62310. |
| #if defined(OS_CHROMEOS) |
| #define MAYBE_TestAltMenuKeyboardAccess DISABLED_TestAltMenuKeyboardAccess |
| #else |
| #define MAYBE_TestAltMenuKeyboardAccess TestAltMenuKeyboardAccess |
| #endif |
| |
| IN_PROC_BROWSER_TEST_F(KeyboardAccessTest, MAYBE_TestAltMenuKeyboardAccess) { |
| TestMenuKeyboardAccess(true, false); |
| } |
| |
| // If this flakes, use http://crbug.com/62311. |
| #if defined(OS_WIN) |
| #define MAYBE_TestShiftAltMenuKeyboardAccess DISABLED_TestShiftAltMenuKeyboardAccess |
| #else |
| #define MAYBE_TestShiftAltMenuKeyboardAccess TestShiftAltMenuKeyboardAccess |
| #endif |
| IN_PROC_BROWSER_TEST_F(KeyboardAccessTest, |
| MAYBE_TestShiftAltMenuKeyboardAccess) { |
| TestMenuKeyboardAccess(true, true); |
| } |
| |
| // Test that JavaScript cannot intercept reserved keyboard accelerators like |
| // ctrl-t to open a new tab or ctrl-f4 to close a tab. |
| // TODO(isherman): This test times out on ChromeOS. We should merge it with |
| // BrowserKeyEventsTest.ReservedAccelerators, but just disable for now. |
| // If this flakes, use http://crbug.com/62311. |
| IN_PROC_BROWSER_TEST_F(KeyboardAccessTest, ReserveKeyboardAccelerators) { |
| const std::string kBadPage = |
| "<html><script>" |
| "document.onkeydown = function() {" |
| " event.preventDefault();" |
| " return false;" |
| "}" |
| "</script></html>"; |
| GURL url("data:text/html," + kBadPage); |
| ui_test_utils::NavigateToURLWithDisposition( |
| browser(), url, NEW_FOREGROUND_TAB, |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); |
| |
| ASSERT_TRUE(ui_test_utils::SendKeyPressSync( |
| browser(), ui::VKEY_TAB, true, false, false, false)); |
| ASSERT_EQ(0, browser()->active_index()); |
| |
| ui_test_utils::NavigateToURLWithDisposition( |
| browser(), url, NEW_FOREGROUND_TAB, |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); |
| ASSERT_EQ(2, browser()->active_index()); |
| |
| ASSERT_TRUE(ui_test_utils::SendKeyPressSync( |
| browser(), ui::VKEY_W, true, false, false, false)); |
| ASSERT_EQ(0, browser()->active_index()); |
| } |
| |
| } // namespace |