| // 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 <X11/Xlib.h> |
| #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<TestConstrainedDialogContentsView> { |
| 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<TestConstrainedDialogContentsView> 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<TestConstrainedDialog> 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<TestConstrainedDialog> 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<TestConstrainedDialog> 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<TestConstrainedDialog> 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<TestConstrainedDialog> 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<content::NavigationController>( |
| &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)) |