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