constrained_window_views_browsertest.cc revision 010d83a9304c5a91596085d917d248abff47903a
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/notification_types.h" 24#include "content/public/browser/render_view_host.h" 25#include "content/public/browser/web_contents.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/test/events_test_utils_x11.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 modal_delegate->GetWebContentsModalDialogHost()->GetHostView()); 162 web_contents_modal_dialog_manager->ShowModalDialog(window1->GetNativeView()); 163 164 views::FocusManager* focus_manager = window1->GetFocusManager(); 165 ASSERT_TRUE(focus_manager); 166 167 // test_dialog1's text field should be focused. 168 EXPECT_EQ(test_dialog1->GetInitiallyFocusedView(), 169 focus_manager->GetFocusedView()); 170 171 // Now create a second constrained dialog. This will also be attached to 172 // web_contents, but will remain hidden since the test_dialog1 is still 173 // showing. 174 scoped_ptr<TestConstrainedDialog> test_dialog2(new TestConstrainedDialog); 175 views::Widget* window2 = views::Widget::CreateWindowAsFramelessChild( 176 test_dialog2.get(), 177 modal_delegate->GetWebContentsModalDialogHost()->GetHostView()); 178 web_contents_modal_dialog_manager->ShowModalDialog(window2->GetNativeView()); 179 // Should be the same focus_manager. 180 ASSERT_EQ(focus_manager, window2->GetFocusManager()); 181 182 // test_dialog1's text field should still be the view that has focus. 183 EXPECT_EQ(test_dialog1->GetInitiallyFocusedView(), 184 focus_manager->GetFocusedView()); 185 ASSERT_TRUE(web_contents_modal_dialog_manager->IsDialogActive()); 186 187 // Now send a VKEY_RETURN to the browser. This should result in closing 188 // test_dialog1. 189 EXPECT_TRUE(focus_manager->ProcessAccelerator( 190 ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE))); 191 content::RunAllPendingInMessageLoop(); 192 193 EXPECT_TRUE(test_dialog1->done()); 194 EXPECT_FALSE(test_dialog2->done()); 195 EXPECT_TRUE(web_contents_modal_dialog_manager->IsDialogActive()); 196 197 // test_dialog2 will be shown. Focus should be on test_dialog2's text field. 198 EXPECT_EQ(test_dialog2->GetInitiallyFocusedView(), 199 focus_manager->GetFocusedView()); 200 201 int tab_with_constrained_window = 202 browser()->tab_strip_model()->active_index(); 203 204 // Create a new tab. 205 chrome::NewTab(browser()); 206 207 // The constrained dialog should no longer be selected. 208 EXPECT_NE(test_dialog2->GetInitiallyFocusedView(), 209 focus_manager->GetFocusedView()); 210 211 browser()->tab_strip_model()->ActivateTabAt(tab_with_constrained_window, 212 false); 213 214 // Activating the previous tab should bring focus to the constrained window. 215 EXPECT_EQ(test_dialog2->GetInitiallyFocusedView(), 216 focus_manager->GetFocusedView()); 217 218 // Send another VKEY_RETURN, closing test_dialog2 219 EXPECT_TRUE(focus_manager->ProcessAccelerator( 220 ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE))); 221 content::RunAllPendingInMessageLoop(); 222 EXPECT_TRUE(test_dialog2->done()); 223 EXPECT_FALSE(web_contents_modal_dialog_manager->IsDialogActive()); 224} 225 226#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(USE_AURA) 227// TODO(erg): linux_aura bringup: http://crbug.com/163931 228#define MAYBE_TabCloseTest DISABLED_TabCloseTest 229#else 230#define MAYBE_TabCloseTest TabCloseTest 231#endif 232 233// Tests that the constrained window is closed properly when its tab is 234// closed. 235IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, MAYBE_TabCloseTest) { 236 content::WebContents* web_contents = 237 browser()->tab_strip_model()->GetActiveWebContents(); 238 ASSERT_TRUE(web_contents != NULL); 239 WebContentsModalDialogManager* web_contents_modal_dialog_manager = 240 WebContentsModalDialogManager::FromWebContents(web_contents); 241 ASSERT_TRUE(web_contents_modal_dialog_manager != NULL); 242 WebContentsModalDialogManagerDelegate* modal_delegate = 243 web_contents_modal_dialog_manager->delegate(); 244 ASSERT_TRUE(modal_delegate != NULL); 245 246 // Create a constrained dialog. It will attach itself to web_contents. 247 scoped_ptr<TestConstrainedDialog> test_dialog(new TestConstrainedDialog); 248 views::Widget* window = views::Widget::CreateWindowAsFramelessChild( 249 test_dialog.get(), 250 modal_delegate->GetWebContentsModalDialogHost()->GetHostView()); 251 web_contents_modal_dialog_manager->ShowModalDialog(window->GetNativeView()); 252 253 bool closed = 254 browser()->tab_strip_model()->CloseWebContentsAt( 255 browser()->tab_strip_model()->active_index(), 256 TabStripModel::CLOSE_NONE); 257 EXPECT_TRUE(closed); 258 content::RunAllPendingInMessageLoop(); 259 EXPECT_TRUE(test_dialog->done()); 260} 261 262#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(USE_AURA) 263// TODO(erg): linux_aura bringup: http://crbug.com/163931 264#define MAYBE_TabSwitchTest DISABLED_TabSwitchTest 265#else 266#define MAYBE_TabSwitchTest TabSwitchTest 267#endif 268 269// Tests that the constrained window is hidden when an other tab is selected and 270// shown when its tab is selected again. 271IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, MAYBE_TabSwitchTest) { 272 content::WebContents* web_contents = 273 browser()->tab_strip_model()->GetActiveWebContents(); 274 ASSERT_TRUE(web_contents != NULL); 275 276 // Create a constrained dialog. It will attach itself to web_contents. 277 scoped_ptr<TestConstrainedDialog> test_dialog(new TestConstrainedDialog); 278 WebContentsModalDialogManager* web_contents_modal_dialog_manager = 279 WebContentsModalDialogManager::FromWebContents(web_contents); 280 WebContentsModalDialogManagerDelegate* modal_delegate = 281 web_contents_modal_dialog_manager->delegate(); 282 ASSERT_TRUE(modal_delegate != NULL); 283 views::Widget* window = views::Widget::CreateWindowAsFramelessChild( 284 test_dialog.get(), 285 modal_delegate->GetWebContentsModalDialogHost()->GetHostView()); 286 web_contents_modal_dialog_manager->ShowModalDialog(window->GetNativeView()); 287 EXPECT_TRUE(window->IsVisible()); 288 289 // Open a new tab. The constrained window should hide itself. 290 browser()->tab_strip_model()->AppendWebContents( 291 content::WebContents::Create( 292 content::WebContents::CreateParams(browser()->profile())), 293 true); 294 EXPECT_FALSE(window->IsVisible()); 295 296 // Close the new tab. The constrained window should show itself again. 297 bool closed = 298 browser()->tab_strip_model()->CloseWebContentsAt( 299 browser()->tab_strip_model()->active_index(), 300 TabStripModel::CLOSE_NONE); 301 EXPECT_TRUE(closed); 302 EXPECT_TRUE(window->IsVisible()); 303 304 // Close the original tab. 305 browser()->tab_strip_model()->CloseWebContentsAt( 306 browser()->tab_strip_model()->active_index(), 307 TabStripModel::CLOSE_NONE); 308 content::RunAllPendingInMessageLoop(); 309 EXPECT_TRUE(test_dialog->done()); 310} 311 312// Tests that the constrained window behaves properly when moving its tab 313// between browser windows. 314IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, TabMoveTest) { 315 // Open a second browser. 316 Browser* browser2 = CreateBrowser(browser()->profile()); 317 318 // Create a second WebContents in the second browser, so that moving the 319 // WebContents does not trigger the browser to close immediately. This mimics 320 // the behavior when a user drags tabs between browsers. 321 content::WebContents* web_contents = content::WebContents::Create( 322 content::WebContents::CreateParams(browser()->profile())); 323 browser2->tab_strip_model()->AppendWebContents(web_contents, true); 324 ASSERT_EQ(web_contents, browser2->tab_strip_model()->GetActiveWebContents()); 325 326 // Create a constrained dialog. It will attach itself to web_contents. 327 scoped_ptr<TestConstrainedDialog> test_dialog(new TestConstrainedDialog); 328 WebContentsModalDialogManager* web_contents_modal_dialog_manager = 329 WebContentsModalDialogManager::FromWebContents(web_contents); 330 WebContentsModalDialogManagerDelegate* modal_delegate = 331 web_contents_modal_dialog_manager->delegate(); 332 ASSERT_TRUE(modal_delegate != NULL); 333 views::Widget* window = views::Widget::CreateWindowAsFramelessChild( 334 test_dialog.get(), 335 modal_delegate->GetWebContentsModalDialogHost()->GetHostView()); 336 web_contents_modal_dialog_manager->ShowModalDialog(window->GetNativeView()); 337 EXPECT_TRUE(window->IsVisible()); 338 339 // Detach the web contents from the second browser's tab strip. 340 browser2->tab_strip_model()->DetachWebContentsAt( 341 browser2->tab_strip_model()->GetIndexOfWebContents(web_contents)); 342 343 // Append the web contents to the first browser. 344 browser()->tab_strip_model()->AppendWebContents(web_contents, true); 345 EXPECT_TRUE(window->IsVisible()); 346 347 // Close the second browser. 348 browser2->tab_strip_model()->CloseAllTabs(); 349 content::RunAllPendingInMessageLoop(); 350 EXPECT_TRUE(window->IsVisible()); 351 352 // Close the dialog's tab. 353 bool closed = 354 browser()->tab_strip_model()->CloseWebContentsAt( 355 browser()->tab_strip_model()->GetIndexOfWebContents(web_contents), 356 TabStripModel::CLOSE_NONE); 357 EXPECT_TRUE(closed); 358 content::RunAllPendingInMessageLoop(); 359 EXPECT_TRUE(test_dialog->done()); 360} 361 362#if defined(OS_WIN) || (defined(USE_AURA) && defined(USE_X11)) 363 364// Forwards the key event which has |key_code| to the renderer. 365void ForwardKeyEvent(content::RenderViewHost* host, ui::KeyboardCode key_code) { 366#if defined(OS_WIN) 367 MSG native_key_event = { NULL, WM_KEYDOWN, key_code, 0 }; 368#elif defined(USE_X11) 369 ui::ScopedXI2Event x_event; 370 x_event.InitKeyEvent(ui::ET_KEY_PRESSED, key_code, ui::EF_NONE); 371 XEvent* native_key_event = x_event; 372#endif 373 374#if defined(USE_AURA) 375 ui::KeyEvent key(native_key_event, false); 376 ui::KeyEvent* native_ui_key_event = &key; 377#elif defined(OS_WIN) 378 MSG native_ui_key_event = native_key_event; 379#endif 380 381 host->ForwardKeyboardEvent( 382 content::NativeWebKeyboardEvent(native_ui_key_event)); 383} 384 385// Tests that backspace is not processed before it's sent to the web contents. 386// Flaky on Win Aura and Linux ChromiumOS. See http://crbug.com/170331 387#if defined(USE_AURA) 388#define MAYBE_BackspaceSentToWebContent DISABLED_BackspaceSentToWebContent 389#else 390#define MAYBE_BackspaceSentToWebContent BackspaceSentToWebContent 391#endif 392IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, 393 MAYBE_BackspaceSentToWebContent) { 394 content::WebContents* web_contents = 395 browser()->tab_strip_model()->GetActiveWebContents(); 396 ASSERT_TRUE(web_contents != NULL); 397 398 GURL new_tab_url(chrome::kChromeUINewTabURL); 399 ui_test_utils::NavigateToURL(browser(), new_tab_url); 400 GURL about_url(chrome::kChromeUIAboutURL); 401 ui_test_utils::NavigateToURL(browser(), about_url); 402 403 ConstrainedWebDialogDelegate* cwdd = CreateConstrainedWebDialog( 404 browser()->profile(), 405 new ui::test::TestWebDialogDelegate(about_url), 406 NULL, 407 web_contents); 408 409 content::WindowedNotificationObserver back_observer( 410 content::NOTIFICATION_LOAD_STOP, 411 content::Source<content::NavigationController>( 412 &web_contents->GetController())); 413 content::RenderViewHost* render_view_host = 414 cwdd->GetWebContents()->GetRenderViewHost(); 415 ForwardKeyEvent(render_view_host, ui::VKEY_BACK); 416 417 // Backspace is not processed as accelerator before it's sent to web contents. 418 EXPECT_FALSE(web_contents->GetController().GetPendingEntry()); 419 EXPECT_EQ(about_url.spec(), web_contents->GetURL().spec()); 420 421 // Backspace is processed as accelerator after it's sent to web contents. 422 content::RunAllPendingInMessageLoop(); 423 EXPECT_TRUE(web_contents->GetController().GetPendingEntry()); 424 425 // Wait for the navigation to commit, since the URL will not be visible 426 // until then. 427 back_observer.Wait(); 428 EXPECT_TRUE(chrome::IsNTPURL(web_contents->GetURL(), browser()->profile())); 429} 430 431// Fails flakily (once per 10-20 runs) on Win Aura only. http://crbug.com/177482 432// Also fails on CrOS. 433// Also fails on linux_aura (http://crbug.com/163931) 434#if defined(TOOLKIT_VIEWS) 435#define MAYBE_EscapeCloseConstrainedWindow DISABLED_EscapeCloseConstrainedWindow 436#else 437#define MAYBE_EscapeCloseConstrainedWindow EscapeCloseConstrainedWindow 438#endif 439 440// Tests that escape closes the constrained window. 441IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, 442 MAYBE_EscapeCloseConstrainedWindow) { 443 content::WebContents* web_contents = 444 browser()->tab_strip_model()->GetActiveWebContents(); 445 ASSERT_TRUE(web_contents != NULL); 446 447 GURL new_tab_url(chrome::kChromeUINewTabURL); 448 ui_test_utils::NavigateToURL(browser(), new_tab_url); 449 ConstrainedWebDialogDelegate* cwdd = CreateConstrainedWebDialog( 450 browser()->profile(), 451 new ui::test::TestWebDialogDelegate(new_tab_url), 452 NULL, 453 web_contents); 454 455 views::Widget* widget = 456 views::Widget::GetWidgetForNativeView(cwdd->GetNativeDialog()); 457 views::test::TestWidgetObserver observer(widget); 458 459 content::RenderViewHost* render_view_host = 460 cwdd->GetWebContents()->GetRenderViewHost(); 461 ForwardKeyEvent(render_view_host, ui::VKEY_ESCAPE); 462 463 // Escape is not processed as accelerator before it's sent to web contents. 464 EXPECT_FALSE(observer.widget_closed()); 465 466 content::RunAllPendingInMessageLoop(); 467 468 // Escape is processed as accelerator after it's sent to web contents. 469 EXPECT_TRUE(observer.widget_closed()); 470} 471 472#endif // defined(OS_WIN) || (defined(USE_AURA) && defined(USE_X11)) 473