constrained_window_views_browsertest.cc revision f2477e01787aa58f445919b809d89e252beef54f
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#include "base/memory/weak_ptr.h"
6#include "chrome/browser/platform_util.h"
7#include "chrome/browser/profiles/profile.h"
8#include "chrome/browser/search/search.h"
9#include "chrome/browser/ui/browser.h"
10#include "chrome/browser/ui/browser_commands.h"
11#include "chrome/browser/ui/tabs/tab_strip_model.h"
12#include "chrome/browser/ui/views/constrained_window_views.h"
13#include "chrome/browser/ui/views/frame/browser_view.h"
14#include "chrome/browser/ui/webui/constrained_web_dialog_ui.h"
15#include "chrome/common/url_constants.h"
16#include "chrome/test/base/in_process_browser_test.h"
17#include "chrome/test/base/ui_test_utils.h"
18#include "components/web_modal/web_contents_modal_dialog_host.h"
19#include "components/web_modal/web_contents_modal_dialog_manager.h"
20#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
21#include "content/public/browser/native_web_keyboard_event.h"
22#include "content/public/browser/navigation_controller.h"
23#include "content/public/browser/render_view_host.h"
24#include "content/public/browser/web_contents.h"
25#include "ipc/ipc_message.h"
26#include "ui/base/accelerators/accelerator.h"
27#include "ui/views/controls/textfield/textfield.h"
28#include "ui/views/focus/focus_manager.h"
29#include "ui/views/layout/fill_layout.h"
30#include "ui/views/test/test_widget_observer.h"
31#include "ui/views/window/dialog_delegate.h"
32#include "ui/web_dialogs/test/test_web_dialog_delegate.h"
33
34#if defined(USE_AURA) && defined(USE_X11)
35#include <X11/Xlib.h>
36#include "ui/events/test/events_test_utils_x11.h"
37#endif
38
39using web_modal::WebContentsModalDialogManager;
40using web_modal::WebContentsModalDialogManagerDelegate;
41
42namespace {
43
44class TestConstrainedDialogContentsView
45    : public views::View,
46      public base::SupportsWeakPtr<TestConstrainedDialogContentsView> {
47 public:
48  TestConstrainedDialogContentsView()
49      : text_field_(new views::Textfield) {
50    SetLayoutManager(new views::FillLayout);
51    AddChildView(text_field_);
52  }
53
54  views::View* GetInitiallyFocusedView() {
55    return text_field_;
56  }
57
58 private:
59  views::Textfield* text_field_;
60  DISALLOW_COPY_AND_ASSIGN(TestConstrainedDialogContentsView);
61};
62
63class TestConstrainedDialog : public views::DialogDelegate {
64 public:
65  TestConstrainedDialog()
66      : contents_((new TestConstrainedDialogContentsView())->AsWeakPtr()),
67        done_(false) {
68  }
69
70  virtual ~TestConstrainedDialog() {}
71
72  virtual views::View* GetInitiallyFocusedView() OVERRIDE {
73    return contents_ ? contents_->GetInitiallyFocusedView() : NULL;
74  }
75
76  virtual views::View* GetContentsView() OVERRIDE {
77    return contents_.get();
78  }
79
80  virtual views::Widget* GetWidget() OVERRIDE {
81    return contents_ ? contents_->GetWidget() : NULL;
82  }
83
84  virtual const views::Widget* GetWidget() const OVERRIDE {
85    return contents_ ? contents_->GetWidget() : NULL;
86  }
87
88  virtual void DeleteDelegate() OVERRIDE {
89    // Don't delete the delegate yet.  We need to keep it around for inspection
90    // later.
91    EXPECT_TRUE(done_);
92  }
93
94  virtual bool Accept() OVERRIDE {
95    done_ = true;
96    return true;
97  }
98
99  virtual bool Cancel() OVERRIDE {
100    done_ = true;
101    return true;
102  }
103
104  virtual ui::ModalType GetModalType() const OVERRIDE {
105#if defined(USE_ASH)
106    return ui::MODAL_TYPE_CHILD;
107#else
108    return views::WidgetDelegate::GetModalType();
109#endif
110  }
111
112  bool done() {
113    return done_;
114  }
115
116 private:
117  // contents_ will be freed when the View goes away.
118  base::WeakPtr<TestConstrainedDialogContentsView> contents_;
119  bool done_;
120
121  DISALLOW_COPY_AND_ASSIGN(TestConstrainedDialog);
122};
123
124} // namespace
125
126class ConstrainedWindowViewTest : public InProcessBrowserTest {
127 public:
128  ConstrainedWindowViewTest() {
129  }
130};
131
132#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(USE_AURA)
133// TODO(erg): linux_aura bringup: http://crbug.com/163931
134#define MAYBE_FocusTest DISABLED_FocusTest
135#else
136#define MAYBE_FocusTest FocusTest
137#endif
138
139// Tests the following:
140//
141// *) Initially focused view in a constrained dialog receives focus reliably.
142//
143// *) Constrained windows that are queued don't register themselves as
144//    accelerator targets until they are displayed.
145IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, MAYBE_FocusTest) {
146  content::WebContents* web_contents =
147      browser()->tab_strip_model()->GetActiveWebContents();
148  ASSERT_TRUE(web_contents != NULL);
149  WebContentsModalDialogManager* web_contents_modal_dialog_manager =
150      WebContentsModalDialogManager::FromWebContents(web_contents);
151  ASSERT_TRUE(web_contents_modal_dialog_manager != NULL);
152  WebContentsModalDialogManagerDelegate* modal_delegate =
153      web_contents_modal_dialog_manager->delegate();
154  ASSERT_TRUE(modal_delegate != NULL);
155
156  // Create a constrained dialog.  It will attach itself to web_contents.
157  scoped_ptr<TestConstrainedDialog> test_dialog1(new TestConstrainedDialog);
158  views::Widget* window1 = views::Widget::CreateWindowAsFramelessChild(
159      test_dialog1.get(),
160      modal_delegate->GetWebContentsModalDialogHost()->GetHostView());
161  web_contents_modal_dialog_manager->ShowDialog(window1->GetNativeView());
162
163  views::FocusManager* focus_manager = window1->GetFocusManager();
164  ASSERT_TRUE(focus_manager);
165
166  // test_dialog1's text field should be focused.
167  EXPECT_EQ(test_dialog1->GetInitiallyFocusedView(),
168            focus_manager->GetFocusedView());
169
170  // Now create a second constrained dialog.  This will also be attached to
171  // web_contents, but will remain hidden since the test_dialog1 is still
172  // showing.
173  scoped_ptr<TestConstrainedDialog> test_dialog2(new TestConstrainedDialog);
174  views::Widget* window2 = views::Widget::CreateWindowAsFramelessChild(
175      test_dialog2.get(),
176      modal_delegate->GetWebContentsModalDialogHost()->GetHostView());
177  web_contents_modal_dialog_manager->ShowDialog(window2->GetNativeView());
178  // Should be the same focus_manager.
179  ASSERT_EQ(focus_manager, window2->GetFocusManager());
180
181  // test_dialog1's text field should still be the view that has focus.
182  EXPECT_EQ(test_dialog1->GetInitiallyFocusedView(),
183            focus_manager->GetFocusedView());
184  ASSERT_TRUE(web_contents_modal_dialog_manager->IsDialogActive());
185
186  // Now send a VKEY_RETURN to the browser.  This should result in closing
187  // test_dialog1.
188  EXPECT_TRUE(focus_manager->ProcessAccelerator(
189      ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)));
190  content::RunAllPendingInMessageLoop();
191
192  EXPECT_TRUE(test_dialog1->done());
193  EXPECT_FALSE(test_dialog2->done());
194  EXPECT_TRUE(web_contents_modal_dialog_manager->IsDialogActive());
195
196  // test_dialog2 will be shown.  Focus should be on test_dialog2's text field.
197  EXPECT_EQ(test_dialog2->GetInitiallyFocusedView(),
198            focus_manager->GetFocusedView());
199
200  int tab_with_constrained_window =
201      browser()->tab_strip_model()->active_index();
202
203  // Create a new tab.
204  chrome::NewTab(browser());
205
206  // The constrained dialog should no longer be selected.
207  EXPECT_NE(test_dialog2->GetInitiallyFocusedView(),
208            focus_manager->GetFocusedView());
209
210  browser()->tab_strip_model()->ActivateTabAt(tab_with_constrained_window,
211                                              false);
212
213  // Activating the previous tab should bring focus to the constrained window.
214  EXPECT_EQ(test_dialog2->GetInitiallyFocusedView(),
215            focus_manager->GetFocusedView());
216
217  // Send another VKEY_RETURN, closing test_dialog2
218  EXPECT_TRUE(focus_manager->ProcessAccelerator(
219      ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)));
220  content::RunAllPendingInMessageLoop();
221  EXPECT_TRUE(test_dialog2->done());
222  EXPECT_FALSE(web_contents_modal_dialog_manager->IsDialogActive());
223}
224
225#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(USE_AURA)
226// TODO(erg): linux_aura bringup: http://crbug.com/163931
227#define MAYBE_TabCloseTest DISABLED_TabCloseTest
228#else
229#define MAYBE_TabCloseTest TabCloseTest
230#endif
231
232// Tests that the constrained window is closed properly when its tab is
233// closed.
234IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, MAYBE_TabCloseTest) {
235  content::WebContents* web_contents =
236      browser()->tab_strip_model()->GetActiveWebContents();
237  ASSERT_TRUE(web_contents != NULL);
238  WebContentsModalDialogManager* web_contents_modal_dialog_manager =
239      WebContentsModalDialogManager::FromWebContents(web_contents);
240  ASSERT_TRUE(web_contents_modal_dialog_manager != NULL);
241  WebContentsModalDialogManagerDelegate* modal_delegate =
242      web_contents_modal_dialog_manager->delegate();
243  ASSERT_TRUE(modal_delegate != NULL);
244
245  // Create a constrained dialog.  It will attach itself to web_contents.
246  scoped_ptr<TestConstrainedDialog> test_dialog(new TestConstrainedDialog);
247  views::Widget* window = views::Widget::CreateWindowAsFramelessChild(
248      test_dialog.get(),
249      modal_delegate->GetWebContentsModalDialogHost()->GetHostView());
250  web_contents_modal_dialog_manager->ShowDialog(window->GetNativeView());
251
252  bool closed =
253      browser()->tab_strip_model()->CloseWebContentsAt(
254          browser()->tab_strip_model()->active_index(),
255          TabStripModel::CLOSE_NONE);
256  EXPECT_TRUE(closed);
257  content::RunAllPendingInMessageLoop();
258  EXPECT_TRUE(test_dialog->done());
259}
260
261#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(USE_AURA)
262// TODO(erg): linux_aura bringup: http://crbug.com/163931
263#define MAYBE_TabSwitchTest DISABLED_TabSwitchTest
264#else
265#define MAYBE_TabSwitchTest TabSwitchTest
266#endif
267
268// Tests that the constrained window is hidden when an other tab is selected and
269// shown when its tab is selected again.
270IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, MAYBE_TabSwitchTest) {
271  content::WebContents* web_contents =
272      browser()->tab_strip_model()->GetActiveWebContents();
273  ASSERT_TRUE(web_contents != NULL);
274
275  // Create a constrained dialog.  It will attach itself to web_contents.
276  scoped_ptr<TestConstrainedDialog> test_dialog(new TestConstrainedDialog);
277  WebContentsModalDialogManager* web_contents_modal_dialog_manager =
278      WebContentsModalDialogManager::FromWebContents(web_contents);
279  WebContentsModalDialogManagerDelegate* modal_delegate =
280      web_contents_modal_dialog_manager->delegate();
281  ASSERT_TRUE(modal_delegate != NULL);
282  views::Widget* window = views::Widget::CreateWindowAsFramelessChild(
283      test_dialog.get(),
284      modal_delegate->GetWebContentsModalDialogHost()->GetHostView());
285  web_contents_modal_dialog_manager->ShowDialog(window->GetNativeView());
286  EXPECT_TRUE(window->IsVisible());
287
288  // Open a new tab. The constrained window should hide itself.
289  browser()->tab_strip_model()->AppendWebContents(
290      content::WebContents::Create(
291          content::WebContents::CreateParams(browser()->profile())),
292      true);
293  EXPECT_FALSE(window->IsVisible());
294
295  // Close the new tab. The constrained window should show itself again.
296  bool closed =
297      browser()->tab_strip_model()->CloseWebContentsAt(
298          browser()->tab_strip_model()->active_index(),
299          TabStripModel::CLOSE_NONE);
300  EXPECT_TRUE(closed);
301  EXPECT_TRUE(window->IsVisible());
302
303  // Close the original tab.
304  browser()->tab_strip_model()->CloseWebContentsAt(
305      browser()->tab_strip_model()->active_index(),
306      TabStripModel::CLOSE_NONE);
307  content::RunAllPendingInMessageLoop();
308  EXPECT_TRUE(test_dialog->done());
309}
310
311// Tests that the constrained window behaves properly when moving its tab
312// between browser windows.
313IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, TabMoveTest) {
314  // Open a second browser.
315  Browser* browser2 = CreateBrowser(browser()->profile());
316
317  // Create a second WebContents in the second browser, so that moving the
318  // WebContents does not trigger the browser to close immediately. This mimics
319  // the behavior when a user drags tabs between browsers.
320  content::WebContents* web_contents = content::WebContents::Create(
321      content::WebContents::CreateParams(browser()->profile()));
322  browser2->tab_strip_model()->AppendWebContents(web_contents, true);
323  ASSERT_EQ(web_contents, browser2->tab_strip_model()->GetActiveWebContents());
324
325  // Create a constrained dialog.  It will attach itself to web_contents.
326  scoped_ptr<TestConstrainedDialog> test_dialog(new TestConstrainedDialog);
327  WebContentsModalDialogManager* web_contents_modal_dialog_manager =
328      WebContentsModalDialogManager::FromWebContents(web_contents);
329  WebContentsModalDialogManagerDelegate* modal_delegate =
330      web_contents_modal_dialog_manager->delegate();
331  ASSERT_TRUE(modal_delegate != NULL);
332  views::Widget* window = views::Widget::CreateWindowAsFramelessChild(
333      test_dialog.get(),
334      modal_delegate->GetWebContentsModalDialogHost()->GetHostView());
335  web_contents_modal_dialog_manager->ShowDialog(window->GetNativeView());
336  EXPECT_TRUE(window->IsVisible());
337
338  // Detach the web contents from the second browser's tab strip.
339  browser2->tab_strip_model()->DetachWebContentsAt(
340      browser2->tab_strip_model()->GetIndexOfWebContents(web_contents));
341
342  // Append the web contents to the first browser.
343  browser()->tab_strip_model()->AppendWebContents(web_contents, true);
344  EXPECT_TRUE(window->IsVisible());
345
346  // Close the second browser.
347  browser2->tab_strip_model()->CloseAllTabs();
348  content::RunAllPendingInMessageLoop();
349  EXPECT_TRUE(window->IsVisible());
350
351  // Close the dialog's tab.
352  bool closed =
353      browser()->tab_strip_model()->CloseWebContentsAt(
354          browser()->tab_strip_model()->GetIndexOfWebContents(web_contents),
355          TabStripModel::CLOSE_NONE);
356  EXPECT_TRUE(closed);
357  content::RunAllPendingInMessageLoop();
358  EXPECT_TRUE(test_dialog->done());
359}
360
361#if defined(OS_WIN) || (defined(USE_AURA) && defined(USE_X11))
362
363// Forwards the key event which has |key_code| to the renderer.
364void ForwardKeyEvent(content::RenderViewHost* host, ui::KeyboardCode key_code) {
365#if defined(OS_WIN)
366  MSG native_key_event = { NULL, WM_KEYDOWN, key_code, 0 };
367#elif defined(USE_X11)
368  ui::ScopedXI2Event x_event;
369  x_event.InitKeyEvent(ui::ET_KEY_PRESSED, key_code, ui::EF_NONE);
370  XEvent* native_key_event = x_event;
371#endif
372
373#if defined(USE_AURA)
374  ui::KeyEvent key(native_key_event, false);
375  ui::KeyEvent* native_ui_key_event = &key;
376#elif defined(OS_WIN)
377  MSG native_ui_key_event = native_key_event;
378#endif
379
380  host->ForwardKeyboardEvent(
381      content::NativeWebKeyboardEvent(native_ui_key_event));
382}
383
384// Tests that backspace is not processed before it's sent to the web contents.
385// Flaky on Win Aura and Linux ChromiumOS. See http://crbug.com/170331
386#if defined(USE_AURA)
387#define MAYBE_BackspaceSentToWebContent DISABLED_BackspaceSentToWebContent
388#else
389#define MAYBE_BackspaceSentToWebContent BackspaceSentToWebContent
390#endif
391IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest,
392                       MAYBE_BackspaceSentToWebContent) {
393  content::WebContents* web_contents =
394      browser()->tab_strip_model()->GetActiveWebContents();
395  ASSERT_TRUE(web_contents != NULL);
396
397  GURL new_tab_url(chrome::kChromeUINewTabURL);
398  ui_test_utils::NavigateToURL(browser(), new_tab_url);
399  GURL about_url(chrome::kChromeUIAboutURL);
400  ui_test_utils::NavigateToURL(browser(), about_url);
401
402  ConstrainedWebDialogDelegate* cwdd = CreateConstrainedWebDialog(
403      browser()->profile(),
404      new ui::test::TestWebDialogDelegate(about_url),
405      NULL,
406      web_contents);
407
408  content::WindowedNotificationObserver back_observer(
409      content::NOTIFICATION_LOAD_STOP,
410      content::Source<content::NavigationController>(
411          &web_contents->GetController()));
412  content::RenderViewHost* render_view_host =
413      cwdd->GetWebContents()->GetRenderViewHost();
414  ForwardKeyEvent(render_view_host, ui::VKEY_BACK);
415
416  // Backspace is not processed as accelerator before it's sent to web contents.
417  EXPECT_FALSE(web_contents->GetController().GetPendingEntry());
418  EXPECT_EQ(about_url.spec(), web_contents->GetURL().spec());
419
420  // Backspace is processed as accelerator after it's sent to web contents.
421  content::RunAllPendingInMessageLoop();
422  EXPECT_TRUE(web_contents->GetController().GetPendingEntry());
423
424  // Wait for the navigation to commit, since the URL will not be visible
425  // until then.
426  back_observer.Wait();
427  EXPECT_TRUE(chrome::IsNTPURL(web_contents->GetURL(), browser()->profile()));
428}
429
430// Fails flakily (once per 10-20 runs) on Win Aura only. http://crbug.com/177482
431// Also fails on CrOS.
432// Also fails on linux_aura (http://crbug.com/163931)
433#if defined(TOOLKIT_VIEWS)
434#define MAYBE_EscapeCloseConstrainedWindow DISABLED_EscapeCloseConstrainedWindow
435#else
436#define MAYBE_EscapeCloseConstrainedWindow EscapeCloseConstrainedWindow
437#endif
438
439// Tests that escape closes the constrained window.
440IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest,
441                       MAYBE_EscapeCloseConstrainedWindow) {
442  content::WebContents* web_contents =
443      browser()->tab_strip_model()->GetActiveWebContents();
444  ASSERT_TRUE(web_contents != NULL);
445
446  GURL new_tab_url(chrome::kChromeUINewTabURL);
447  ui_test_utils::NavigateToURL(browser(), new_tab_url);
448  ConstrainedWebDialogDelegate* cwdd = CreateConstrainedWebDialog(
449      browser()->profile(),
450      new ui::test::TestWebDialogDelegate(new_tab_url),
451      NULL,
452      web_contents);
453
454  views::Widget* widget =
455      views::Widget::GetWidgetForNativeView(cwdd->GetNativeDialog());
456  views::test::TestWidgetObserver observer(widget);
457
458  content::RenderViewHost* render_view_host =
459      cwdd->GetWebContents()->GetRenderViewHost();
460  ForwardKeyEvent(render_view_host, ui::VKEY_ESCAPE);
461
462  // Escape is not processed as accelerator before it's sent to web contents.
463  EXPECT_FALSE(observer.widget_closed());
464
465  content::RunAllPendingInMessageLoop();
466
467  // Escape is processed as accelerator after it's sent to web contents.
468  EXPECT_TRUE(observer.widget_closed());
469}
470
471#endif  // defined(OS_WIN) || (defined(USE_AURA) && defined(USE_X11))
472