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