constrained_window_views_browsertest.cc revision 3551c9c881056c480085172ff9840cab31610854
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_host.h"
18#include "components/web_modal/web_contents_modal_dialog_manager.h"
19#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
20#include "content/public/browser/native_web_keyboard_event.h"
21#include "content/public/browser/navigation_controller.h"
22#include "content/public/browser/render_view_host.h"
23#include "content/public/browser/web_contents.h"
24#include "content/public/browser/web_contents_view.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/base/x/x11_util.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// Tests the following:
133//
134// *) Initially focused view in a constrained dialog receives focus reliably.
135//
136// *) Constrained windows that are queued don't register themselves as
137//    accelerator targets until they are displayed.
138IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, FocusTest) {
139  content::WebContents* web_contents =
140      browser()->tab_strip_model()->GetActiveWebContents();
141  ASSERT_TRUE(web_contents != NULL);
142  WebContentsModalDialogManager* web_contents_modal_dialog_manager =
143      WebContentsModalDialogManager::FromWebContents(web_contents);
144  ASSERT_TRUE(web_contents_modal_dialog_manager != NULL);
145  WebContentsModalDialogManagerDelegate* modal_delegate =
146      web_contents_modal_dialog_manager->delegate();
147  ASSERT_TRUE(modal_delegate != NULL);
148
149  // Create a constrained dialog.  It will attach itself to web_contents.
150  scoped_ptr<TestConstrainedDialog> test_dialog1(new TestConstrainedDialog);
151  views::Widget* window1 = views::Widget::CreateWindowAsFramelessChild(
152      test_dialog1.get(),
153      web_contents->GetView()->GetNativeView(),
154      modal_delegate->GetWebContentsModalDialogHost()->GetHostView());
155  web_contents_modal_dialog_manager->ShowDialog(window1->GetNativeView());
156
157  views::FocusManager* focus_manager = window1->GetFocusManager();
158  ASSERT_TRUE(focus_manager);
159
160  // test_dialog1's text field should be focused.
161  EXPECT_EQ(test_dialog1->GetInitiallyFocusedView(),
162            focus_manager->GetFocusedView());
163
164  // Now create a second constrained dialog.  This will also be attached to
165  // web_contents, but will remain hidden since the test_dialog1 is still
166  // showing.
167  scoped_ptr<TestConstrainedDialog> test_dialog2(new TestConstrainedDialog);
168  views::Widget* window2 = views::Widget::CreateWindowAsFramelessChild(
169      test_dialog2.get(),
170      web_contents->GetView()->GetNativeView(),
171      modal_delegate->GetWebContentsModalDialogHost()->GetHostView());
172  web_contents_modal_dialog_manager->ShowDialog(window2->GetNativeView());
173  // Should be the same focus_manager.
174  ASSERT_EQ(focus_manager, window2->GetFocusManager());
175
176  // test_dialog1's text field should still be the view that has focus.
177  EXPECT_EQ(test_dialog1->GetInitiallyFocusedView(),
178            focus_manager->GetFocusedView());
179  ASSERT_TRUE(web_contents_modal_dialog_manager->IsShowingDialog());
180
181  // Now send a VKEY_RETURN to the browser.  This should result in closing
182  // test_dialog1.
183  EXPECT_TRUE(focus_manager->ProcessAccelerator(
184      ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)));
185  content::RunAllPendingInMessageLoop();
186
187  EXPECT_TRUE(test_dialog1->done());
188  EXPECT_FALSE(test_dialog2->done());
189  EXPECT_TRUE(web_contents_modal_dialog_manager->IsShowingDialog());
190
191  // test_dialog2 will be shown.  Focus should be on test_dialog2's text field.
192  EXPECT_EQ(test_dialog2->GetInitiallyFocusedView(),
193            focus_manager->GetFocusedView());
194
195  int tab_with_constrained_window =
196      browser()->tab_strip_model()->active_index();
197
198  // Create a new tab.
199  chrome::NewTab(browser());
200
201  // The constrained dialog should no longer be selected.
202  EXPECT_NE(test_dialog2->GetInitiallyFocusedView(),
203            focus_manager->GetFocusedView());
204
205  browser()->tab_strip_model()->ActivateTabAt(tab_with_constrained_window,
206                                              false);
207
208  // Activating the previous tab should bring focus to the constrained window.
209  EXPECT_EQ(test_dialog2->GetInitiallyFocusedView(),
210            focus_manager->GetFocusedView());
211
212  // Send another VKEY_RETURN, closing test_dialog2
213  EXPECT_TRUE(focus_manager->ProcessAccelerator(
214      ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)));
215  content::RunAllPendingInMessageLoop();
216  EXPECT_TRUE(test_dialog2->done());
217  EXPECT_FALSE(web_contents_modal_dialog_manager->IsShowingDialog());
218}
219
220// Tests that the constrained window is closed properly when its tab is
221// closed.
222IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, TabCloseTest) {
223  content::WebContents* web_contents =
224      browser()->tab_strip_model()->GetActiveWebContents();
225  ASSERT_TRUE(web_contents != NULL);
226  WebContentsModalDialogManager* web_contents_modal_dialog_manager =
227      WebContentsModalDialogManager::FromWebContents(web_contents);
228  ASSERT_TRUE(web_contents_modal_dialog_manager != NULL);
229  WebContentsModalDialogManagerDelegate* modal_delegate =
230      web_contents_modal_dialog_manager->delegate();
231  ASSERT_TRUE(modal_delegate != NULL);
232
233  // Create a constrained dialog.  It will attach itself to web_contents.
234  scoped_ptr<TestConstrainedDialog> test_dialog(new TestConstrainedDialog);
235  views::Widget* window = views::Widget::CreateWindowAsFramelessChild(
236      test_dialog.get(),
237      web_contents->GetView()->GetNativeView(),
238      modal_delegate->GetWebContentsModalDialogHost()->GetHostView());
239  web_contents_modal_dialog_manager->ShowDialog(window->GetNativeView());
240
241  bool closed =
242      browser()->tab_strip_model()->CloseWebContentsAt(
243          browser()->tab_strip_model()->active_index(),
244          TabStripModel::CLOSE_NONE);
245  EXPECT_TRUE(closed);
246  content::RunAllPendingInMessageLoop();
247  EXPECT_TRUE(test_dialog->done());
248}
249
250// Tests that the constrained window is hidden when an other tab is selected and
251// shown when its tab is selected again.
252IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, TabSwitchTest) {
253  content::WebContents* web_contents =
254      browser()->tab_strip_model()->GetActiveWebContents();
255  ASSERT_TRUE(web_contents != NULL);
256
257  // Create a constrained dialog.  It will attach itself to web_contents.
258  scoped_ptr<TestConstrainedDialog> test_dialog(new TestConstrainedDialog);
259  WebContentsModalDialogManager* web_contents_modal_dialog_manager =
260      WebContentsModalDialogManager::FromWebContents(web_contents);
261  WebContentsModalDialogManagerDelegate* modal_delegate =
262      web_contents_modal_dialog_manager->delegate();
263  ASSERT_TRUE(modal_delegate != NULL);
264  views::Widget* window = views::Widget::CreateWindowAsFramelessChild(
265      test_dialog.get(),
266      web_contents->GetView()->GetNativeView(),
267      modal_delegate->GetWebContentsModalDialogHost()->GetHostView());
268  web_contents_modal_dialog_manager->ShowDialog(window->GetNativeView());
269  EXPECT_TRUE(window->IsVisible());
270
271  // Open a new tab. The constrained window should hide itself.
272  browser()->tab_strip_model()->AppendWebContents(
273      content::WebContents::Create(
274          content::WebContents::CreateParams(browser()->profile())),
275      true);
276  EXPECT_FALSE(window->IsVisible());
277
278  // Close the new tab. The constrained window should show itself again.
279  bool closed =
280      browser()->tab_strip_model()->CloseWebContentsAt(
281          browser()->tab_strip_model()->active_index(),
282          TabStripModel::CLOSE_NONE);
283  EXPECT_TRUE(closed);
284  EXPECT_TRUE(window->IsVisible());
285
286  // Close the original tab.
287  browser()->tab_strip_model()->CloseWebContentsAt(
288      browser()->tab_strip_model()->active_index(),
289      TabStripModel::CLOSE_NONE);
290  content::RunAllPendingInMessageLoop();
291  EXPECT_TRUE(test_dialog->done());
292}
293
294#if defined(OS_WIN) || (defined(USE_AURA) && defined(USE_X11))
295
296// Forwards the key event which has |key_code| to the renderer.
297void ForwardKeyEvent(content::RenderViewHost* host, ui::KeyboardCode key_code) {
298#if defined(OS_WIN)
299  MSG native_key_event = { NULL, WM_KEYDOWN, key_code, 0 };
300#elif defined(USE_X11)
301  XEvent x_event;
302  ui::InitXKeyEventForTesting(
303      ui::ET_KEY_PRESSED, key_code, ui::EF_NONE, &x_event);
304  XEvent* native_key_event = &x_event;
305#endif
306
307#if defined(USE_AURA)
308  ui::KeyEvent key(native_key_event, false);
309  ui::KeyEvent* native_ui_key_event = &key;
310#elif defined(OS_WIN)
311  MSG native_ui_key_event = native_key_event;
312#endif
313
314  host->ForwardKeyboardEvent(
315      content::NativeWebKeyboardEvent(native_ui_key_event));
316}
317
318// Tests that backspace is not processed before it's sent to the web contents.
319// Flaky on Win Aura and Linux ChromiumOS. See http://crbug.com/170331
320#if defined(USE_AURA)
321#define MAYBE_BackspaceSentToWebContent DISABLED_BackspaceSentToWebContent
322#else
323#define MAYBE_BackspaceSentToWebContent BackspaceSentToWebContent
324#endif
325IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest,
326                       MAYBE_BackspaceSentToWebContent) {
327  content::WebContents* web_contents =
328      browser()->tab_strip_model()->GetActiveWebContents();
329  ASSERT_TRUE(web_contents != NULL);
330
331  GURL new_tab_url(chrome::kChromeUINewTabURL);
332  ui_test_utils::NavigateToURL(browser(), new_tab_url);
333  GURL about_url(chrome::kChromeUIAboutURL);
334  ui_test_utils::NavigateToURL(browser(), about_url);
335
336  ConstrainedWebDialogDelegate* cwdd = CreateConstrainedWebDialog(
337      browser()->profile(),
338      new ui::test::TestWebDialogDelegate(about_url),
339      NULL,
340      web_contents);
341
342  content::WindowedNotificationObserver back_observer(
343      content::NOTIFICATION_LOAD_STOP,
344      content::Source<content::NavigationController>(
345          &web_contents->GetController()));
346  content::RenderViewHost* render_view_host =
347      cwdd->GetWebContents()->GetRenderViewHost();
348  ForwardKeyEvent(render_view_host, ui::VKEY_BACK);
349
350  // Backspace is not processed as accelerator before it's sent to web contents.
351  EXPECT_FALSE(web_contents->GetController().GetPendingEntry());
352  EXPECT_EQ(about_url.spec(), web_contents->GetURL().spec());
353
354  // Backspace is processed as accelerator after it's sent to web contents.
355  content::RunAllPendingInMessageLoop();
356  EXPECT_TRUE(web_contents->GetController().GetPendingEntry());
357
358  // Wait for the navigation to commit, since the URL will not be visible
359  // until then.
360  back_observer.Wait();
361  EXPECT_EQ(new_tab_url.spec(), web_contents->GetURL().spec());
362}
363
364// Fails flakily (once per 10-20 runs) on Win Aura only. http://crbug.com/177482
365// Also fails on CrOS.
366#if defined(OS_WIN) || defined(OS_CHROMEOS)
367#define MAYBE_EscapeCloseConstrainedWindow DISABLED_EscapeCloseConstrainedWindow
368#else
369#define MAYBE_EscapeCloseConstrainedWindow EscapeCloseConstrainedWindow
370#endif
371
372// Tests that escape closes the constrained window.
373IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest,
374                       MAYBE_EscapeCloseConstrainedWindow) {
375  content::WebContents* web_contents =
376      browser()->tab_strip_model()->GetActiveWebContents();
377  ASSERT_TRUE(web_contents != NULL);
378
379  GURL new_tab_url(chrome::kChromeUINewTabURL);
380  ui_test_utils::NavigateToURL(browser(), new_tab_url);
381  ConstrainedWebDialogDelegate* cwdd = CreateConstrainedWebDialog(
382      browser()->profile(),
383      new ui::test::TestWebDialogDelegate(new_tab_url),
384      NULL,
385      web_contents);
386
387  views::Widget* widget =
388      views::Widget::GetWidgetForNativeView(cwdd->GetNativeDialog());
389  views::test::TestWidgetObserver observer(widget);
390
391  content::RenderViewHost* render_view_host =
392      cwdd->GetWebContents()->GetRenderViewHost();
393  ForwardKeyEvent(render_view_host, ui::VKEY_ESCAPE);
394
395  // Escape is not processed as accelerator before it's sent to web contents.
396  EXPECT_FALSE(observer.widget_closed());
397
398  content::RunAllPendingInMessageLoop();
399
400  // Escape is processed as accelerator after it's sent to web contents.
401  EXPECT_TRUE(observer.widget_closed());
402}
403
404#endif  // defined(OS_WIN) || (defined(USE_AURA) && defined(USE_X11))
405