// 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/file_util.h" #include "base/test/test_timeouts.h" #include "chrome/browser/ui/view_ids.h" #include "chrome/common/chrome_switches.h" #include "chrome/test/automation/automation_proxy.h" #include "chrome/test/automation/browser_proxy.h" #include "chrome/test/automation/tab_proxy.h" #include "chrome/test/automation/window_proxy.h" #include "chrome/test/ui/ui_test.h" #include "content/browser/net/url_request_mock_http_job.h" #include "net/url_request/url_request_test_util.h" #include "ui/base/events.h" #include "ui/base/ui_base_types.h" using base::TimeDelta; const std::string NOLISTENERS_HTML = "nolisteners"; const std::string UNLOAD_HTML = "unload" ""; const std::string BEFORE_UNLOAD_HTML = "beforeunload" "" ""; const std::string INNER_FRAME_WITH_FOCUS_HTML = "innerframewithfocus" "" "" ""; const std::string TWO_SECOND_BEFORE_UNLOAD_HTML = "twosecondbeforeunload" ""; const std::string INFINITE_UNLOAD_HTML = "infiniteunload" "" ""; const std::string INFINITE_BEFORE_UNLOAD_HTML = "infinitebeforeunload" "" ""; const std::string INFINITE_UNLOAD_ALERT_HTML = "infiniteunloadalert" ""; const std::string INFINITE_BEFORE_UNLOAD_ALERT_HTML = "infinitebeforeunloadalert" ""; const std::string TWO_SECOND_UNLOAD_ALERT_HTML = "twosecondunloadalert" ""; const std::string TWO_SECOND_BEFORE_UNLOAD_ALERT_HTML = "twosecondbeforeunloadalert" ""; const std::string CLOSE_TAB_WHEN_OTHER_TAB_HAS_LISTENER = "only_one_unload" "popup')\" " "onbeforeunload='return;'>" ""; class UnloadTest : public UITest { public: virtual void SetUp() { const testing::TestInfo* const test_info = testing::UnitTest::GetInstance()->current_test_info(); if (strcmp(test_info->name(), "BrowserCloseTabWhenOtherTabHasListener") == 0) { launch_arguments_.AppendSwitch(switches::kDisablePopupBlocking); } UITest::SetUp(); } void CheckTitle(const std::wstring& expected_title) { const TimeDelta kCheckDelay = TimeDelta::FromMilliseconds(100); for (TimeDelta max_wait_time = TestTimeouts::action_max_timeout(); max_wait_time > TimeDelta(); max_wait_time -= kCheckDelay) { if (expected_title == GetActiveTabTitle()) break; base::PlatformThread::Sleep(kCheckDelay); } EXPECT_EQ(expected_title, GetActiveTabTitle()); } void NavigateToDataURL(const std::string& html_content, const std::wstring& expected_title) { NavigateToURL(GURL("data:text/html," + html_content)); CheckTitle(expected_title); } void NavigateToNolistenersFileTwice() { NavigateToURL(URLRequestMockHTTPJob::GetMockUrl( FilePath(FILE_PATH_LITERAL("title2.html")))); CheckTitle(L"Title Of Awesomeness"); NavigateToURL(URLRequestMockHTTPJob::GetMockUrl( FilePath(FILE_PATH_LITERAL("title2.html")))); CheckTitle(L"Title Of Awesomeness"); } // Navigates to a URL asynchronously, then again synchronously. The first // load is purposely async to test the case where the user loads another // page without waiting for the first load to complete. void NavigateToNolistenersFileTwiceAsync() { NavigateToURLAsync( URLRequestMockHTTPJob::GetMockUrl( FilePath(FILE_PATH_LITERAL("title2.html")))); NavigateToURL( URLRequestMockHTTPJob::GetMockUrl( FilePath(FILE_PATH_LITERAL("title2.html")))); CheckTitle(L"Title Of Awesomeness"); } void LoadUrlAndQuitBrowser(const std::string& html_content, const std::wstring& expected_title = L"") { scoped_refptr browser(automation()->GetBrowserWindow(0)); ASSERT_TRUE(browser.get()); NavigateToDataURL(html_content, expected_title); bool application_closed = false; EXPECT_TRUE(CloseBrowser(browser.get(), &application_closed)); } void ClickModalDialogButton(ui::DialogButton button) { bool modal_dialog_showing = false; ui::DialogButton available_buttons; EXPECT_TRUE(automation()->WaitForAppModalDialog()); EXPECT_TRUE(automation()->GetShowingAppModalDialog(&modal_dialog_showing, &available_buttons)); ASSERT_TRUE(modal_dialog_showing); EXPECT_TRUE((button & available_buttons) != 0); EXPECT_TRUE(automation()->ClickAppModalDialogButton(button)); } }; // Navigate to a page with an infinite unload handler. // Then two async crosssite requests to ensure // we don't get confused and think we're closing the tab. // // This test is flaky on the valgrind UI bots. http://crbug.com/39057 TEST_F(UnloadTest, DISABLED_CrossSiteInfiniteUnloadAsync) { // Tests makes no sense in single-process mode since the renderer is hung. if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) return; NavigateToDataURL(INFINITE_UNLOAD_HTML, L"infiniteunload"); // Must navigate to a non-data URL to trigger cross-site codepath. NavigateToNolistenersFileTwiceAsync(); } // Navigate to a page with an infinite unload handler. // Then two sync crosssite requests to ensure // we correctly nav to each one. TEST_F(UnloadTest, CrossSiteInfiniteUnloadSync) { // Tests makes no sense in single-process mode since the renderer is hung. if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) return; NavigateToDataURL(INFINITE_UNLOAD_HTML, L"infiniteunload"); // Must navigate to a non-data URL to trigger cross-site codepath. NavigateToNolistenersFileTwice(); } // Navigate to a page with an infinite unload handler. // Then an async crosssite request followed by an input event to ensure that // the short unload timeout (not the long input event timeout) is used. // TODO(creis): This test is currently failing intermittently on Linux and // consistently on Mac and Vista. http://crbug.com/38427 // See crbug.com/11007. TEST_F(UnloadTest, DISABLED_CrossSiteInfiniteUnloadAsyncInputEvent) { // Tests makes no sense in single-process mode since the renderer is hung. if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) return; NavigateToDataURL(INFINITE_UNLOAD_HTML, L"infiniteunload"); // Navigate to a new URL asynchronously. NavigateToURLAsync( URLRequestMockHTTPJob::GetMockUrl( FilePath(FILE_PATH_LITERAL("title2.html")))); // Now send an input event while we're stalled on the unload handler. scoped_refptr browser(automation()->GetBrowserWindow(0)); ASSERT_TRUE(browser.get()); scoped_refptr window(browser->GetWindow()); ASSERT_TRUE(window.get()); gfx::Rect bounds; ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_0, &bounds, false)); ASSERT_TRUE(browser->SimulateDrag(bounds.CenterPoint(), bounds.CenterPoint(), ui::EF_LEFT_MOUSE_BUTTON, false)); // The title should update before the timeout in CheckTitle. CheckTitle(L"Title Of Awesomeness"); } // Navigate to a page with an infinite beforeunload handler. // Then two two async crosssite requests to ensure // we don't get confused and think we're closing the tab. // This test is flaky on the valgrind UI bots. http://crbug.com/39057 TEST_F(UnloadTest, DISABLED_CrossSiteInfiniteBeforeUnloadAsync) { // Tests makes no sense in single-process mode since the renderer is hung. if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) return; NavigateToDataURL(INFINITE_BEFORE_UNLOAD_HTML, L"infinitebeforeunload"); // Must navigate to a non-data URL to trigger cross-site codepath. NavigateToNolistenersFileTwiceAsync(); } #if defined(OS_LINUX) // Flaky 86469. #define MAYBE_CrossSiteInfiniteBeforeUnloadSync \ DISABLED_CrossSiteInfiniteBeforeUnloadSync #else #define MAYBE_CrossSiteInfiniteBeforeUnloadSync \ CrossSiteInfiniteBeforeUnloadSync #endif // Navigate to a page with an infinite beforeunload handler. // Then two two sync crosssite requests to ensure // we correctly nav to each one. TEST_F(UnloadTest, MAYBE_CrossSiteInfiniteBeforeUnloadSync) { // Tests makes no sense in single-process mode since the renderer is hung. if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) return; NavigateToDataURL(INFINITE_BEFORE_UNLOAD_HTML, L"infinitebeforeunload"); // Must navigate to a non-data URL to trigger cross-site codepath. NavigateToNolistenersFileTwice(); } // Tests closing the browser on a page with no unload listeners registered. TEST_F(UnloadTest, BrowserCloseNoUnloadListeners) { LoadUrlAndQuitBrowser(NOLISTENERS_HTML, L"nolisteners"); } // Tests closing the browser on a page with an unload listener registered. // Test marked as flaky in http://crbug.com/51698 TEST_F(UnloadTest, DISABLED_BrowserCloseUnload) { LoadUrlAndQuitBrowser(UNLOAD_HTML, L"unload"); } // Tests closing the browser with a beforeunload handler and clicking // OK in the beforeunload confirm dialog. TEST_F(UnloadTest, BrowserCloseBeforeUnloadOK) { scoped_refptr browser(automation()->GetBrowserWindow(0)); ASSERT_TRUE(browser.get()); NavigateToDataURL(BEFORE_UNLOAD_HTML, L"beforeunload"); CloseBrowserAsync(browser.get()); ClickModalDialogButton(ui::DIALOG_BUTTON_OK); int exit_code = -1; ASSERT_TRUE(launcher_->WaitForBrowserProcessToQuit( TestTimeouts::action_max_timeout_ms(), &exit_code)); EXPECT_EQ(0, exit_code); // Expect a clean shutown. } // Tests closing the browser with a beforeunload handler and clicking // CANCEL in the beforeunload confirm dialog. TEST_F(UnloadTest, BrowserCloseBeforeUnloadCancel) { scoped_refptr browser(automation()->GetBrowserWindow(0)); ASSERT_TRUE(browser.get()); NavigateToDataURL(BEFORE_UNLOAD_HTML, L"beforeunload"); CloseBrowserAsync(browser.get()); ClickModalDialogButton(ui::DIALOG_BUTTON_CANCEL); // There's no real graceful way to wait for something _not_ to happen, so // we just wait a short period. base::PlatformThread::Sleep(TestTimeouts::action_timeout()); CloseBrowserAsync(browser.get()); ClickModalDialogButton(ui::DIALOG_BUTTON_OK); int exit_code = -1; ASSERT_TRUE(launcher_->WaitForBrowserProcessToQuit( TestTimeouts::action_max_timeout_ms(), &exit_code)); EXPECT_EQ(0, exit_code); // Expect a clean shutdown. } // Tests terminating the browser with a beforeunload handler. // Currently only ChromeOS shuts down gracefully. #if defined(OS_CHROMEOS) TEST_F(UnloadTest, BrowserTerminateBeforeUnload) { scoped_refptr browser(automation()->GetBrowserWindow(0)); ASSERT_TRUE(browser.get()); NavigateToDataURL(BEFORE_UNLOAD_HTML, L"beforeunload"); TerminateBrowser(); } #endif #if defined(OS_LINUX) // Fails sometimes on Linux valgrind. http://crbug.com/45675 #define MAYBE_BrowserCloseWithInnerFocusedFrame \ DISABLED_BrowserCloseWithInnerFocusedFrame #else #define MAYBE_BrowserCloseWithInnerFocusedFrame \ BrowserCloseWithInnerFocusedFrame #endif // Tests closing the browser and clicking OK in the beforeunload confirm dialog // if an inner frame has the focus. See crbug.com/32615. TEST_F(UnloadTest, MAYBE_BrowserCloseWithInnerFocusedFrame) { scoped_refptr browser(automation()->GetBrowserWindow(0)); ASSERT_TRUE(browser.get()); NavigateToDataURL(INNER_FRAME_WITH_FOCUS_HTML, L"innerframewithfocus"); CloseBrowserAsync(browser.get()); ClickModalDialogButton(ui::DIALOG_BUTTON_OK); int exit_code = -1; ASSERT_TRUE(launcher_->WaitForBrowserProcessToQuit( TestTimeouts::action_max_timeout_ms(), &exit_code)); EXPECT_EQ(0, exit_code); // Expect a clean shutdown. } // Tests closing the browser with a beforeunload handler that takes // two seconds to run. TEST_F(UnloadTest, BrowserCloseTwoSecondBeforeUnload) { LoadUrlAndQuitBrowser(TWO_SECOND_BEFORE_UNLOAD_HTML, L"twosecondbeforeunload"); } // Tests closing the browser on a page with an unload listener registered where // the unload handler has an infinite loop. TEST_F(UnloadTest, BrowserCloseInfiniteUnload) { // Tests makes no sense in single-process mode since the renderer is hung. if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) return; LoadUrlAndQuitBrowser(INFINITE_UNLOAD_HTML, L"infiniteunload"); } #if defined(OS_WIN) || defined(OS_CHROMEOS) || defined(OS_LINUX) // Flakily fails, times out: http://crbug.com/78803 // Flakily fails: http://crbug.com/86469 #define MAYBE_BrowserCloseInfiniteBeforeUnload \ DISABLED_BrowserCloseInfiniteBeforeUnload #else #define MAYBE_BrowserCloseInfiniteBeforeUnload BrowserCloseInfiniteBeforeUnload #endif // Tests closing the browser with a beforeunload handler that hangs. TEST_F(UnloadTest, MAYBE_BrowserCloseInfiniteBeforeUnload) { // Tests makes no sense in single-process mode since the renderer is hung. if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) return; LoadUrlAndQuitBrowser(INFINITE_BEFORE_UNLOAD_HTML, L"infinitebeforeunload"); } #if defined(OS_LINUX) // Flaky 86469. #define MAYBE_BrowserCloseInfiniteUnloadAlert \ DISABLED_BrowserCloseInfiniteUnloadAlert #else #define MAYBE_BrowserCloseInfiniteUnloadAlert BrowserCloseInfiniteUnloadAlert #endif // Tests closing the browser on a page with an unload listener registered where // the unload handler has an infinite loop followed by an alert. TEST_F(UnloadTest, MAYBE_BrowserCloseInfiniteUnloadAlert) { // Tests makes no sense in single-process mode since the renderer is hung. if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) return; LoadUrlAndQuitBrowser(INFINITE_UNLOAD_ALERT_HTML, L"infiniteunloadalert"); } #if defined(OS_WIN) || defined(OS_LINUX) // Flakily fails, times out: http://crbug.com/78803 #define MAYBE_BrowserCloseInfiniteBeforeUnloadAlert \ DISABLED_BrowserCloseInfiniteBeforeUnloadAlert #else #define MAYBE_BrowserCloseInfiniteBeforeUnloadAlert \ BrowserCloseInfiniteBeforeUnloadAlert #endif // Tests closing the browser with a beforeunload handler that hangs then // pops up an alert. TEST_F(UnloadTest, MAYBE_BrowserCloseInfiniteBeforeUnloadAlert) { // Tests makes no sense in single-process mode since the renderer is hung. if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) return; LoadUrlAndQuitBrowser(INFINITE_BEFORE_UNLOAD_ALERT_HTML, L"infinitebeforeunloadalert"); } // Tests closing the browser on a page with an unload listener registered where // the unload handler has an 2 second long loop followed by an alert. TEST_F(UnloadTest, BrowserCloseTwoSecondUnloadAlert) { LoadUrlAndQuitBrowser(TWO_SECOND_UNLOAD_ALERT_HTML, L"twosecondunloadalert"); } // Tests closing the browser with a beforeunload handler that takes // two seconds to run then pops up an alert. TEST_F(UnloadTest, BrowserCloseTwoSecondBeforeUnloadAlert) { LoadUrlAndQuitBrowser(TWO_SECOND_BEFORE_UNLOAD_ALERT_HTML, L"twosecondbeforeunloadalert"); } #if defined(OS_MACOSX) // http://crbug.com/45162 #define MAYBE_BrowserCloseTabWhenOtherTabHasListener \ DISABLED_BrowserCloseTabWhenOtherTabHasListener #elif defined(OS_WIN) // http://crbug.com/45281 #define MAYBE_BrowserCloseTabWhenOtherTabHasListener \ DISABLED_BrowserCloseTabWhenOtherTabHasListener #elif defined(OS_CHROMEOS) // http://crbug.com/86769 #define MAYBE_BrowserCloseTabWhenOtherTabHasListener \ DISABLED_BrowserCloseTabWhenOtherTabHasListener #else #define MAYBE_BrowserCloseTabWhenOtherTabHasListener \ BrowserCloseTabWhenOtherTabHasListener #endif // Tests that if there's a renderer process with two tabs, one of which has an // unload handler, and the other doesn't, the tab that doesn't have an unload // handler can be closed. TEST_F(UnloadTest, MAYBE_BrowserCloseTabWhenOtherTabHasListener) { NavigateToDataURL(CLOSE_TAB_WHEN_OTHER_TAB_HAS_LISTENER, L"only_one_unload"); scoped_refptr browser = automation()->GetBrowserWindow(0); ASSERT_TRUE(browser.get()); scoped_refptr window = browser->GetWindow(); ASSERT_TRUE(window.get()); gfx::Rect tab_view_bounds; ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_CONTAINER, &tab_view_bounds, true)); // Simulate a click to force user_gesture to true; if we don't, the resulting // popup will be constrained, which isn't what we want to test. ASSERT_TRUE(window->SimulateOSClick(tab_view_bounds.CenterPoint(), ui::EF_LEFT_MOUSE_BUTTON)); ASSERT_TRUE(browser->WaitForTabCountToBecome(2)); CheckTitle(L"popup"); scoped_refptr popup_tab(browser->GetActiveTab()); ASSERT_TRUE(popup_tab.get()); EXPECT_TRUE(popup_tab->Close(true)); ASSERT_TRUE(browser->WaitForTabCountToBecome(1)); scoped_refptr main_tab(browser->GetActiveTab()); ASSERT_TRUE(main_tab.get()); std::wstring main_title; EXPECT_TRUE(main_tab->GetTabTitle(&main_title)); EXPECT_EQ(std::wstring(L"only_one_unload"), main_title); } // TODO(ojan): Add tests for unload/beforeunload that have multiple tabs // and multiple windows.