// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/memory/weak_ptr.h" #include "chrome/browser/platform_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/search/search.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/views/constrained_window_views.h" #include "chrome/browser/ui/views/frame/browser_view.h" #include "chrome/browser/ui/webui/constrained_web_dialog_ui.h" #include "chrome/common/url_constants.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/ui_test_utils.h" #include "components/web_modal/web_contents_modal_dialog_host.h" #include "components/web_modal/web_contents_modal_dialog_manager.h" #include "components/web_modal/web_contents_modal_dialog_manager_delegate.h" #include "content/public/browser/native_web_keyboard_event.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" #include "ipc/ipc_message.h" #include "ui/base/accelerators/accelerator.h" #include "ui/views/controls/textfield/textfield.h" #include "ui/views/focus/focus_manager.h" #include "ui/views/layout/fill_layout.h" #include "ui/views/test/test_widget_observer.h" #include "ui/views/window/dialog_delegate.h" #include "ui/web_dialogs/test/test_web_dialog_delegate.h" #if defined(USE_AURA) && defined(USE_X11) #include #include "ui/events/test/events_test_utils_x11.h" #endif using web_modal::WebContentsModalDialogManager; using web_modal::WebContentsModalDialogManagerDelegate; namespace { class TestConstrainedDialogContentsView : public views::View, public base::SupportsWeakPtr { public: TestConstrainedDialogContentsView() : text_field_(new views::Textfield) { SetLayoutManager(new views::FillLayout); AddChildView(text_field_); } views::View* GetInitiallyFocusedView() { return text_field_; } private: views::Textfield* text_field_; DISALLOW_COPY_AND_ASSIGN(TestConstrainedDialogContentsView); }; class TestConstrainedDialog : public views::DialogDelegate { public: TestConstrainedDialog() : contents_((new TestConstrainedDialogContentsView())->AsWeakPtr()), done_(false) { } virtual ~TestConstrainedDialog() {} virtual views::View* GetInitiallyFocusedView() OVERRIDE { return contents_ ? contents_->GetInitiallyFocusedView() : NULL; } virtual views::View* GetContentsView() OVERRIDE { return contents_.get(); } virtual views::Widget* GetWidget() OVERRIDE { return contents_ ? contents_->GetWidget() : NULL; } virtual const views::Widget* GetWidget() const OVERRIDE { return contents_ ? contents_->GetWidget() : NULL; } virtual void DeleteDelegate() OVERRIDE { // Don't delete the delegate yet. We need to keep it around for inspection // later. EXPECT_TRUE(done_); } virtual bool Accept() OVERRIDE { done_ = true; return true; } virtual bool Cancel() OVERRIDE { done_ = true; return true; } virtual ui::ModalType GetModalType() const OVERRIDE { #if defined(USE_ASH) return ui::MODAL_TYPE_CHILD; #else return views::WidgetDelegate::GetModalType(); #endif } bool done() { return done_; } private: // contents_ will be freed when the View goes away. base::WeakPtr contents_; bool done_; DISALLOW_COPY_AND_ASSIGN(TestConstrainedDialog); }; } // namespace class ConstrainedWindowViewTest : public InProcessBrowserTest { public: ConstrainedWindowViewTest() { } }; #if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(USE_AURA) // TODO(erg): linux_aura bringup: http://crbug.com/163931 #define MAYBE_FocusTest DISABLED_FocusTest #else #define MAYBE_FocusTest FocusTest #endif // Tests the following: // // *) Initially focused view in a constrained dialog receives focus reliably. // // *) Constrained windows that are queued don't register themselves as // accelerator targets until they are displayed. IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, MAYBE_FocusTest) { content::WebContents* web_contents = browser()->tab_strip_model()->GetActiveWebContents(); ASSERT_TRUE(web_contents != NULL); WebContentsModalDialogManager* web_contents_modal_dialog_manager = WebContentsModalDialogManager::FromWebContents(web_contents); ASSERT_TRUE(web_contents_modal_dialog_manager != NULL); WebContentsModalDialogManagerDelegate* modal_delegate = web_contents_modal_dialog_manager->delegate(); ASSERT_TRUE(modal_delegate != NULL); // Create a constrained dialog. It will attach itself to web_contents. scoped_ptr test_dialog1(new TestConstrainedDialog); views::Widget* window1 = views::Widget::CreateWindowAsFramelessChild( test_dialog1.get(), modal_delegate->GetWebContentsModalDialogHost()->GetHostView()); web_contents_modal_dialog_manager->ShowDialog(window1->GetNativeView()); views::FocusManager* focus_manager = window1->GetFocusManager(); ASSERT_TRUE(focus_manager); // test_dialog1's text field should be focused. EXPECT_EQ(test_dialog1->GetInitiallyFocusedView(), focus_manager->GetFocusedView()); // Now create a second constrained dialog. This will also be attached to // web_contents, but will remain hidden since the test_dialog1 is still // showing. scoped_ptr test_dialog2(new TestConstrainedDialog); views::Widget* window2 = views::Widget::CreateWindowAsFramelessChild( test_dialog2.get(), modal_delegate->GetWebContentsModalDialogHost()->GetHostView()); web_contents_modal_dialog_manager->ShowDialog(window2->GetNativeView()); // Should be the same focus_manager. ASSERT_EQ(focus_manager, window2->GetFocusManager()); // test_dialog1's text field should still be the view that has focus. EXPECT_EQ(test_dialog1->GetInitiallyFocusedView(), focus_manager->GetFocusedView()); ASSERT_TRUE(web_contents_modal_dialog_manager->IsDialogActive()); // Now send a VKEY_RETURN to the browser. This should result in closing // test_dialog1. EXPECT_TRUE(focus_manager->ProcessAccelerator( ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE))); content::RunAllPendingInMessageLoop(); EXPECT_TRUE(test_dialog1->done()); EXPECT_FALSE(test_dialog2->done()); EXPECT_TRUE(web_contents_modal_dialog_manager->IsDialogActive()); // test_dialog2 will be shown. Focus should be on test_dialog2's text field. EXPECT_EQ(test_dialog2->GetInitiallyFocusedView(), focus_manager->GetFocusedView()); int tab_with_constrained_window = browser()->tab_strip_model()->active_index(); // Create a new tab. chrome::NewTab(browser()); // The constrained dialog should no longer be selected. EXPECT_NE(test_dialog2->GetInitiallyFocusedView(), focus_manager->GetFocusedView()); browser()->tab_strip_model()->ActivateTabAt(tab_with_constrained_window, false); // Activating the previous tab should bring focus to the constrained window. EXPECT_EQ(test_dialog2->GetInitiallyFocusedView(), focus_manager->GetFocusedView()); // Send another VKEY_RETURN, closing test_dialog2 EXPECT_TRUE(focus_manager->ProcessAccelerator( ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE))); content::RunAllPendingInMessageLoop(); EXPECT_TRUE(test_dialog2->done()); EXPECT_FALSE(web_contents_modal_dialog_manager->IsDialogActive()); } #if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(USE_AURA) // TODO(erg): linux_aura bringup: http://crbug.com/163931 #define MAYBE_TabCloseTest DISABLED_TabCloseTest #else #define MAYBE_TabCloseTest TabCloseTest #endif // Tests that the constrained window is closed properly when its tab is // closed. IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, MAYBE_TabCloseTest) { content::WebContents* web_contents = browser()->tab_strip_model()->GetActiveWebContents(); ASSERT_TRUE(web_contents != NULL); WebContentsModalDialogManager* web_contents_modal_dialog_manager = WebContentsModalDialogManager::FromWebContents(web_contents); ASSERT_TRUE(web_contents_modal_dialog_manager != NULL); WebContentsModalDialogManagerDelegate* modal_delegate = web_contents_modal_dialog_manager->delegate(); ASSERT_TRUE(modal_delegate != NULL); // Create a constrained dialog. It will attach itself to web_contents. scoped_ptr test_dialog(new TestConstrainedDialog); views::Widget* window = views::Widget::CreateWindowAsFramelessChild( test_dialog.get(), modal_delegate->GetWebContentsModalDialogHost()->GetHostView()); web_contents_modal_dialog_manager->ShowDialog(window->GetNativeView()); bool closed = browser()->tab_strip_model()->CloseWebContentsAt( browser()->tab_strip_model()->active_index(), TabStripModel::CLOSE_NONE); EXPECT_TRUE(closed); content::RunAllPendingInMessageLoop(); EXPECT_TRUE(test_dialog->done()); } #if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(USE_AURA) // TODO(erg): linux_aura bringup: http://crbug.com/163931 #define MAYBE_TabSwitchTest DISABLED_TabSwitchTest #else #define MAYBE_TabSwitchTest TabSwitchTest #endif // Tests that the constrained window is hidden when an other tab is selected and // shown when its tab is selected again. IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, MAYBE_TabSwitchTest) { content::WebContents* web_contents = browser()->tab_strip_model()->GetActiveWebContents(); ASSERT_TRUE(web_contents != NULL); // Create a constrained dialog. It will attach itself to web_contents. scoped_ptr test_dialog(new TestConstrainedDialog); WebContentsModalDialogManager* web_contents_modal_dialog_manager = WebContentsModalDialogManager::FromWebContents(web_contents); WebContentsModalDialogManagerDelegate* modal_delegate = web_contents_modal_dialog_manager->delegate(); ASSERT_TRUE(modal_delegate != NULL); views::Widget* window = views::Widget::CreateWindowAsFramelessChild( test_dialog.get(), modal_delegate->GetWebContentsModalDialogHost()->GetHostView()); web_contents_modal_dialog_manager->ShowDialog(window->GetNativeView()); EXPECT_TRUE(window->IsVisible()); // Open a new tab. The constrained window should hide itself. browser()->tab_strip_model()->AppendWebContents( content::WebContents::Create( content::WebContents::CreateParams(browser()->profile())), true); EXPECT_FALSE(window->IsVisible()); // Close the new tab. The constrained window should show itself again. bool closed = browser()->tab_strip_model()->CloseWebContentsAt( browser()->tab_strip_model()->active_index(), TabStripModel::CLOSE_NONE); EXPECT_TRUE(closed); EXPECT_TRUE(window->IsVisible()); // Close the original tab. browser()->tab_strip_model()->CloseWebContentsAt( browser()->tab_strip_model()->active_index(), TabStripModel::CLOSE_NONE); content::RunAllPendingInMessageLoop(); EXPECT_TRUE(test_dialog->done()); } // Tests that the constrained window behaves properly when moving its tab // between browser windows. IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, TabMoveTest) { // Open a second browser. Browser* browser2 = CreateBrowser(browser()->profile()); // Create a second WebContents in the second browser, so that moving the // WebContents does not trigger the browser to close immediately. This mimics // the behavior when a user drags tabs between browsers. content::WebContents* web_contents = content::WebContents::Create( content::WebContents::CreateParams(browser()->profile())); browser2->tab_strip_model()->AppendWebContents(web_contents, true); ASSERT_EQ(web_contents, browser2->tab_strip_model()->GetActiveWebContents()); // Create a constrained dialog. It will attach itself to web_contents. scoped_ptr test_dialog(new TestConstrainedDialog); WebContentsModalDialogManager* web_contents_modal_dialog_manager = WebContentsModalDialogManager::FromWebContents(web_contents); WebContentsModalDialogManagerDelegate* modal_delegate = web_contents_modal_dialog_manager->delegate(); ASSERT_TRUE(modal_delegate != NULL); views::Widget* window = views::Widget::CreateWindowAsFramelessChild( test_dialog.get(), modal_delegate->GetWebContentsModalDialogHost()->GetHostView()); web_contents_modal_dialog_manager->ShowDialog(window->GetNativeView()); EXPECT_TRUE(window->IsVisible()); // Detach the web contents from the second browser's tab strip. browser2->tab_strip_model()->DetachWebContentsAt( browser2->tab_strip_model()->GetIndexOfWebContents(web_contents)); // Append the web contents to the first browser. browser()->tab_strip_model()->AppendWebContents(web_contents, true); EXPECT_TRUE(window->IsVisible()); // Close the second browser. browser2->tab_strip_model()->CloseAllTabs(); content::RunAllPendingInMessageLoop(); EXPECT_TRUE(window->IsVisible()); // Close the dialog's tab. bool closed = browser()->tab_strip_model()->CloseWebContentsAt( browser()->tab_strip_model()->GetIndexOfWebContents(web_contents), TabStripModel::CLOSE_NONE); EXPECT_TRUE(closed); content::RunAllPendingInMessageLoop(); EXPECT_TRUE(test_dialog->done()); } #if defined(OS_WIN) || (defined(USE_AURA) && defined(USE_X11)) // Forwards the key event which has |key_code| to the renderer. void ForwardKeyEvent(content::RenderViewHost* host, ui::KeyboardCode key_code) { #if defined(OS_WIN) MSG native_key_event = { NULL, WM_KEYDOWN, key_code, 0 }; #elif defined(USE_X11) ui::ScopedXI2Event x_event; x_event.InitKeyEvent(ui::ET_KEY_PRESSED, key_code, ui::EF_NONE); XEvent* native_key_event = x_event; #endif #if defined(USE_AURA) ui::KeyEvent key(native_key_event, false); ui::KeyEvent* native_ui_key_event = &key; #elif defined(OS_WIN) MSG native_ui_key_event = native_key_event; #endif host->ForwardKeyboardEvent( content::NativeWebKeyboardEvent(native_ui_key_event)); } // Tests that backspace is not processed before it's sent to the web contents. // Flaky on Win Aura and Linux ChromiumOS. See http://crbug.com/170331 #if defined(USE_AURA) #define MAYBE_BackspaceSentToWebContent DISABLED_BackspaceSentToWebContent #else #define MAYBE_BackspaceSentToWebContent BackspaceSentToWebContent #endif IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, MAYBE_BackspaceSentToWebContent) { content::WebContents* web_contents = browser()->tab_strip_model()->GetActiveWebContents(); ASSERT_TRUE(web_contents != NULL); GURL new_tab_url(chrome::kChromeUINewTabURL); ui_test_utils::NavigateToURL(browser(), new_tab_url); GURL about_url(chrome::kChromeUIAboutURL); ui_test_utils::NavigateToURL(browser(), about_url); ConstrainedWebDialogDelegate* cwdd = CreateConstrainedWebDialog( browser()->profile(), new ui::test::TestWebDialogDelegate(about_url), NULL, web_contents); content::WindowedNotificationObserver back_observer( content::NOTIFICATION_LOAD_STOP, content::Source( &web_contents->GetController())); content::RenderViewHost* render_view_host = cwdd->GetWebContents()->GetRenderViewHost(); ForwardKeyEvent(render_view_host, ui::VKEY_BACK); // Backspace is not processed as accelerator before it's sent to web contents. EXPECT_FALSE(web_contents->GetController().GetPendingEntry()); EXPECT_EQ(about_url.spec(), web_contents->GetURL().spec()); // Backspace is processed as accelerator after it's sent to web contents. content::RunAllPendingInMessageLoop(); EXPECT_TRUE(web_contents->GetController().GetPendingEntry()); // Wait for the navigation to commit, since the URL will not be visible // until then. back_observer.Wait(); EXPECT_TRUE(chrome::IsNTPURL(web_contents->GetURL(), browser()->profile())); } // Fails flakily (once per 10-20 runs) on Win Aura only. http://crbug.com/177482 // Also fails on CrOS. // Also fails on linux_aura (http://crbug.com/163931) #if defined(TOOLKIT_VIEWS) #define MAYBE_EscapeCloseConstrainedWindow DISABLED_EscapeCloseConstrainedWindow #else #define MAYBE_EscapeCloseConstrainedWindow EscapeCloseConstrainedWindow #endif // Tests that escape closes the constrained window. IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, MAYBE_EscapeCloseConstrainedWindow) { content::WebContents* web_contents = browser()->tab_strip_model()->GetActiveWebContents(); ASSERT_TRUE(web_contents != NULL); GURL new_tab_url(chrome::kChromeUINewTabURL); ui_test_utils::NavigateToURL(browser(), new_tab_url); ConstrainedWebDialogDelegate* cwdd = CreateConstrainedWebDialog( browser()->profile(), new ui::test::TestWebDialogDelegate(new_tab_url), NULL, web_contents); views::Widget* widget = views::Widget::GetWidgetForNativeView(cwdd->GetNativeDialog()); views::test::TestWidgetObserver observer(widget); content::RenderViewHost* render_view_host = cwdd->GetWebContents()->GetRenderViewHost(); ForwardKeyEvent(render_view_host, ui::VKEY_ESCAPE); // Escape is not processed as accelerator before it's sent to web contents. EXPECT_FALSE(observer.widget_closed()); content::RunAllPendingInMessageLoop(); // Escape is processed as accelerator after it's sent to web contents. EXPECT_TRUE(observer.widget_closed()); } #endif // defined(OS_WIN) || (defined(USE_AURA) && defined(USE_X11))