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 38 using web_modal::WebContentsModalDialogManager; 39 40 namespace { 41 42 class 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 61 class 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 124 class 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. 136 IN_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. 219 IN_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. 247 IN_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. 290 void 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 318 IN_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. 366 IN_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