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