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