• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
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.
8 
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"
29 
30 namespace {
31 
32 // An async version of SendKeyPressSync since we don't get notified when a
33 // menu is showing.
SendKeyPress(Browser * browser,ui::KeyboardCode key)34 void SendKeyPress(Browser* browser, ui::KeyboardCode key) {
35   ASSERT_TRUE(ui_controls::SendKeyPress(
36       browser->window()->GetNativeWindow(), key, false, false, false, false));
37 }
38 
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 {
42  public:
ViewFocusChangeWaiter(views::FocusManager * focus_manager,int previous_view_id)43   ViewFocusChangeWaiter(views::FocusManager* focus_manager,
44                         int previous_view_id)
45       : focus_manager_(focus_manager),
46         previous_view_id_(previous_view_id),
47         weak_factory_(this) {
48     focus_manager_->AddFocusChangeListener(this);
49     // Call the focus change notification once in case the focus has
50     // already changed.
51     OnWillChangeFocus(NULL, focus_manager_->GetFocusedView());
52   }
53 
~ViewFocusChangeWaiter()54   virtual ~ViewFocusChangeWaiter() {
55     focus_manager_->RemoveFocusChangeListener(this);
56   }
57 
Wait()58   void Wait() {
59     content::RunMessageLoop();
60   }
61 
62  private:
63   // Inherited from FocusChangeListener
OnWillChangeFocus(views::View * focused_before,views::View * focused_now)64   virtual void OnWillChangeFocus(views::View* focused_before,
65                                  views::View* focused_now) OVERRIDE {
66   }
67 
OnDidChangeFocus(views::View * focused_before,views::View * focused_now)68   virtual void OnDidChangeFocus(views::View* focused_before,
69                                 views::View* focused_now) OVERRIDE {
70     if (focused_now && focused_now->id() != previous_view_id_) {
71       base::MessageLoop::current()->PostTask(FROM_HERE,
72                                              base::MessageLoop::QuitClosure());
73     }
74   }
75 
76   views::FocusManager* focus_manager_;
77   int previous_view_id_;
78   base::WeakPtrFactory<ViewFocusChangeWaiter> weak_factory_;
79 
80   DISALLOW_COPY_AND_ASSIGN(ViewFocusChangeWaiter);
81 };
82 
83 class SendKeysMenuListener : public views::MenuListener {
84  public:
SendKeysMenuListener(ToolbarView * toolbar_view,Browser * browser,bool test_dismiss_menu)85   SendKeysMenuListener(ToolbarView* toolbar_view,
86                        Browser* browser,
87                        bool test_dismiss_menu)
88       : toolbar_view_(toolbar_view), browser_(browser), menu_open_count_(0),
89         test_dismiss_menu_(test_dismiss_menu) {
90     toolbar_view_->AddMenuListener(this);
91   }
92 
~SendKeysMenuListener()93   virtual ~SendKeysMenuListener() {
94     if (test_dismiss_menu_)
95       toolbar_view_->RemoveMenuListener(this);
96   }
97 
menu_open_count() const98   int menu_open_count() const {
99     return menu_open_count_;
100   }
101 
102  private:
103   // Overridden from views::MenuListener:
OnMenuOpened()104   virtual void OnMenuOpened() OVERRIDE {
105     menu_open_count_++;
106     if (!test_dismiss_menu_) {
107       toolbar_view_->RemoveMenuListener(this);
108       // Press DOWN to select the first item, then RETURN to select it.
109       SendKeyPress(browser_, ui::VKEY_DOWN);
110       SendKeyPress(browser_, ui::VKEY_RETURN);
111     } else {
112       SendKeyPress(browser_, ui::VKEY_ESCAPE);
113       base::MessageLoop::current()->PostDelayedTask(
114           FROM_HERE,
115           base::MessageLoop::QuitClosure(),
116           base::TimeDelta::FromMilliseconds(200));
117     }
118   }
119 
120   ToolbarView* toolbar_view_;
121   Browser* browser_;
122   // Keeps track of the number of times the menu was opened.
123   int menu_open_count_;
124   // If this is set then on receiving a notification that the menu was opened
125   // we dismiss it by sending the ESC key.
126   bool test_dismiss_menu_;
127 
128   DISALLOW_COPY_AND_ASSIGN(SendKeysMenuListener);
129 };
130 
131 class KeyboardAccessTest : public InProcessBrowserTest {
132  public:
KeyboardAccessTest()133   KeyboardAccessTest() {}
134 
135   // Use the keyboard to select "New Tab" from the app menu.
136   // This test depends on the fact that there is one menu and that
137   // New Tab is the first item in the menu. If the menus change,
138   // this test will need to be changed to reflect that.
139   //
140   // If alternate_key_sequence is true, use "Alt" instead of "F10" to
141   // open the menu bar, and "Down" instead of "Enter" to open a menu.
142   // If focus_omnibox is true then the test on startup sets focus to the
143   // omnibox.
144   void TestMenuKeyboardAccess(bool alternate_key_sequence,
145                               bool shift,
146                               bool focus_omnibox);
147 
GetFocusedViewID()148   int GetFocusedViewID() {
149     gfx::NativeWindow window = browser()->window()->GetNativeWindow();
150     views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
151     const views::FocusManager* focus_manager = widget->GetFocusManager();
152     const views::View* focused_view = focus_manager->GetFocusedView();
153     return focused_view ? focused_view->id() : -1;
154   }
155 
WaitForFocusedViewIDToChange(int original_view_id)156   void WaitForFocusedViewIDToChange(int original_view_id) {
157     if (GetFocusedViewID() != original_view_id)
158       return;
159     gfx::NativeWindow window = browser()->window()->GetNativeWindow();
160     views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
161     views::FocusManager* focus_manager = widget->GetFocusManager();
162     ViewFocusChangeWaiter waiter(focus_manager, original_view_id);
163     waiter.Wait();
164   }
165 
166 #if defined(OS_WIN)
167   // Opens the system menu on Windows with the Alt Space combination and selects
168   // the New Tab option from the menu.
169   void TestSystemMenuWithKeyboard();
170 #endif
171 
172   // Uses the keyboard to select the wrench menu i.e. with the F10 key.
173   // It verifies that the menu when dismissed by sending the ESC key it does
174   // not display twice.
175   void TestMenuKeyboardAccessAndDismiss();
176 
177   DISALLOW_COPY_AND_ASSIGN(KeyboardAccessTest);
178 };
179 
TestMenuKeyboardAccess(bool alternate_key_sequence,bool shift,bool focus_omnibox)180 void KeyboardAccessTest::TestMenuKeyboardAccess(bool alternate_key_sequence,
181                                                 bool shift,
182                                                 bool focus_omnibox) {
183   // Navigate to a page in the first tab, which makes sure that focus is
184   // set to the browser window.
185   ui_test_utils::NavigateToURL(browser(), GURL("about:"));
186 
187   // The initial tab index should be 0.
188   ASSERT_EQ(0, browser()->tab_strip_model()->active_index());
189 
190   ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
191 
192   // Get the focused view ID, then press a key to activate the
193   // page menu, then wait until the focused view changes.
194   int original_view_id = GetFocusedViewID();
195 
196   content::WindowedNotificationObserver new_tab_observer(
197       chrome::NOTIFICATION_TAB_ADDED,
198       content::Source<content::WebContentsDelegate>(browser()));
199 
200   BrowserView* browser_view = reinterpret_cast<BrowserView*>(
201       browser()->window());
202   ToolbarView* toolbar_view = browser_view->GetToolbarView();
203   SendKeysMenuListener menu_listener(toolbar_view, browser(), false);
204 
205   if (focus_omnibox)
206     browser()->window()->GetLocationBar()->FocusLocation(false);
207 
208 #if defined(OS_CHROMEOS)
209   // Chrome OS doesn't have a way to just focus the wrench menu, so we use Alt+F
210   // to bring up the menu.
211   ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
212       browser(), ui::VKEY_F, false, shift, true, false));
213 #else
214   ui::KeyboardCode menu_key =
215       alternate_key_sequence ? ui::VKEY_MENU : ui::VKEY_F10;
216   ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
217       browser(), menu_key, false, shift, false, false));
218 #endif
219 
220   if (shift) {
221     // Verify Chrome does not move the view focus. We should not move the view
222     // focus when typing a menu key with modifier keys, such as shift keys or
223     // control keys.
224     int new_view_id = GetFocusedViewID();
225     ASSERT_EQ(original_view_id, new_view_id);
226     return;
227   }
228 
229   WaitForFocusedViewIDToChange(original_view_id);
230 
231   // See above comment. Since we already brought up the menu, no need to do this
232   // on ChromeOS.
233 #if !defined(OS_CHROMEOS)
234   if (alternate_key_sequence)
235     SendKeyPress(browser(), ui::VKEY_DOWN);
236   else
237     SendKeyPress(browser(), ui::VKEY_RETURN);
238 #endif
239 
240   // Wait for the new tab to appear.
241   new_tab_observer.Wait();
242 
243   // Make sure that the new tab index is 1.
244   ASSERT_EQ(1, browser()->tab_strip_model()->active_index());
245 }
246 
247 #if defined(OS_WIN)
248 
249 // This CBT hook is set for the duration of the TestSystemMenuWithKeyboard test
SystemMenuTestCBTHook(int n_code,WPARAM w_param,LPARAM l_param)250 LRESULT CALLBACK SystemMenuTestCBTHook(int n_code,
251                                        WPARAM w_param,
252                                        LPARAM l_param) {
253   // Look for the system menu window getting created or becoming visible and
254   // then select the New Tab option from the menu.
255   if (n_code == HCBT_ACTIVATE || n_code == HCBT_CREATEWND) {
256     wchar_t class_name[MAX_PATH] = {0};
257     GetClassName(reinterpret_cast<HWND>(w_param),
258                  class_name,
259                  arraysize(class_name));
260     if (LowerCaseEqualsASCII(class_name, "#32768")) {
261       // Select the New Tab option and then send the enter key to execute it.
262       ::PostMessage(reinterpret_cast<HWND>(w_param), WM_CHAR, 'T', 0);
263       ::PostMessage(reinterpret_cast<HWND>(w_param), WM_KEYDOWN, VK_RETURN, 0);
264       ::PostMessage(reinterpret_cast<HWND>(w_param), WM_KEYUP, VK_RETURN, 0);
265     }
266   }
267   return ::CallNextHookEx(0, n_code, w_param, l_param);
268 }
269 
TestSystemMenuWithKeyboard()270 void KeyboardAccessTest::TestSystemMenuWithKeyboard() {
271   // Navigate to a page in the first tab, which makes sure that focus is
272   // set to the browser window.
273   ui_test_utils::NavigateToURL(browser(), GURL("about:"));
274 
275   ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
276 
277   content::WindowedNotificationObserver new_tab_observer(
278       chrome::NOTIFICATION_TAB_ADDED,
279       content::Source<content::WebContentsDelegate>(browser()));
280   // Sending the Alt space keys to the browser will bring up the system menu
281   // which runs a model loop. We set a CBT hook to look for the menu and send
282   // keystrokes to it.
283   HHOOK cbt_hook = ::SetWindowsHookEx(WH_CBT,
284                                       SystemMenuTestCBTHook,
285                                       NULL,
286                                       ::GetCurrentThreadId());
287   ASSERT_TRUE(cbt_hook != NULL);
288 
289   bool ret = ui_test_utils::SendKeyPressSync(
290       browser(), ui::VKEY_SPACE, false, false, true, false);
291   EXPECT_TRUE(ret);
292 
293   if (ret) {
294     // Wait for the new tab to appear.
295     new_tab_observer.Wait();
296     // Make sure that the new tab index is 1.
297     ASSERT_EQ(1, browser()->tab_strip_model()->active_index());
298   }
299   ::UnhookWindowsHookEx(cbt_hook);
300 }
301 #endif
302 
TestMenuKeyboardAccessAndDismiss()303 void KeyboardAccessTest::TestMenuKeyboardAccessAndDismiss() {
304   ui_test_utils::NavigateToURL(browser(), GURL("about:"));
305 
306   ASSERT_EQ(0, browser()->tab_strip_model()->active_index());
307 
308   ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
309 
310   int original_view_id = GetFocusedViewID();
311 
312   BrowserView* browser_view = reinterpret_cast<BrowserView*>(
313       browser()->window());
314   ToolbarView* toolbar_view = browser_view->GetToolbarView();
315   SendKeysMenuListener menu_listener(toolbar_view, browser(), true);
316 
317   browser()->window()->GetLocationBar()->FocusLocation(false);
318 
319   ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
320       browser(), ui::VKEY_F10, false, false, false, false));
321 
322   WaitForFocusedViewIDToChange(original_view_id);
323 
324   SendKeyPress(browser(), ui::VKEY_DOWN);
325   content::RunMessageLoop();
326   ASSERT_EQ(1, menu_listener.menu_open_count());
327 }
328 
329 // http://crbug.com/62310.
330 #if defined(OS_CHROMEOS)
331 #define MAYBE_TestMenuKeyboardAccess DISABLED_TestMenuKeyboardAccess
332 #else
333 #define MAYBE_TestMenuKeyboardAccess TestMenuKeyboardAccess
334 #endif
335 
IN_PROC_BROWSER_TEST_F(KeyboardAccessTest,MAYBE_TestMenuKeyboardAccess)336 IN_PROC_BROWSER_TEST_F(KeyboardAccessTest, MAYBE_TestMenuKeyboardAccess) {
337   TestMenuKeyboardAccess(false, false, false);
338 }
339 
340 // http://crbug.com/62310.
341 #if defined(OS_CHROMEOS)
342 #define MAYBE_TestAltMenuKeyboardAccess DISABLED_TestAltMenuKeyboardAccess
343 #else
344 #define MAYBE_TestAltMenuKeyboardAccess TestAltMenuKeyboardAccess
345 #endif
346 
IN_PROC_BROWSER_TEST_F(KeyboardAccessTest,MAYBE_TestAltMenuKeyboardAccess)347 IN_PROC_BROWSER_TEST_F(KeyboardAccessTest, MAYBE_TestAltMenuKeyboardAccess) {
348   TestMenuKeyboardAccess(true, false, false);
349 }
350 
351 // If this flakes, use http://crbug.com/62311.
352 #if defined(OS_WIN)
353 #define MAYBE_TestShiftAltMenuKeyboardAccess DISABLED_TestShiftAltMenuKeyboardAccess
354 #else
355 #define MAYBE_TestShiftAltMenuKeyboardAccess TestShiftAltMenuKeyboardAccess
356 #endif
IN_PROC_BROWSER_TEST_F(KeyboardAccessTest,MAYBE_TestShiftAltMenuKeyboardAccess)357 IN_PROC_BROWSER_TEST_F(KeyboardAccessTest,
358                        MAYBE_TestShiftAltMenuKeyboardAccess) {
359   TestMenuKeyboardAccess(true, true, false);
360 }
361 
362 #if defined(OS_WIN)
IN_PROC_BROWSER_TEST_F(KeyboardAccessTest,DISABLED_TestAltMenuKeyboardAccessFocusOmnibox)363 IN_PROC_BROWSER_TEST_F(KeyboardAccessTest,
364                        DISABLED_TestAltMenuKeyboardAccessFocusOmnibox) {
365   TestMenuKeyboardAccess(true, false, true);
366 }
367 
IN_PROC_BROWSER_TEST_F(KeyboardAccessTest,DISABLED_TestSystemMenuWithKeyboard)368 IN_PROC_BROWSER_TEST_F(KeyboardAccessTest,
369                        DISABLED_TestSystemMenuWithKeyboard) {
370   TestSystemMenuWithKeyboard();
371 }
372 #endif
373 
374 #if !defined(OS_WIN) && defined(USE_AURA)
IN_PROC_BROWSER_TEST_F(KeyboardAccessTest,TestMenuKeyboardOpenDismiss)375 IN_PROC_BROWSER_TEST_F(KeyboardAccessTest, TestMenuKeyboardOpenDismiss) {
376   TestMenuKeyboardAccessAndDismiss();
377 }
378 #endif
379 
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.
IN_PROC_BROWSER_TEST_F(KeyboardAccessTest,ReserveKeyboardAccelerators)385 IN_PROC_BROWSER_TEST_F(KeyboardAccessTest, ReserveKeyboardAccelerators) {
386   const std::string kBadPage =
387       "<html><script>"
388       "document.onkeydown = function() {"
389       "  event.preventDefault();"
390       "  return false;"
391       "}"
392       "</script></html>";
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);
397 
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());
401 
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());
406 
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());
410 }
411 
412 #if defined(OS_WIN)  // These keys are Windows-only.
IN_PROC_BROWSER_TEST_F(KeyboardAccessTest,BackForwardKeys)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/"));
417 
418   base::string16 before_back;
419   ASSERT_TRUE(ui_test_utils::GetCurrentTabTitle(browser(), &before_back));
420 
421   // Navigate back.
422   ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
423       browser(), ui::VKEY_BROWSER_BACK, false, false, false, false));
424 
425   base::string16 after_back;
426   ASSERT_TRUE(ui_test_utils::GetCurrentTabTitle(browser(), &after_back));
427 
428   EXPECT_NE(before_back, after_back);
429 
430   // And then forward.
431   ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
432       browser(), ui::VKEY_BROWSER_FORWARD, false, false, false, false));
433 
434   base::string16 after_forward;
435   ASSERT_TRUE(ui_test_utils::GetCurrentTabTitle(browser(), &after_forward));
436 
437   EXPECT_EQ(before_back, after_forward);
438 }
439 #endif
440 
441 }  // namespace
442