diff options
Diffstat (limited to 'chrome/browser/unload_uitest.cc')
-rw-r--r-- | chrome/browser/unload_uitest.cc | 453 |
1 files changed, 453 insertions, 0 deletions
diff --git a/chrome/browser/unload_uitest.cc b/chrome/browser/unload_uitest.cc new file mode 100644 index 0000000..b6ed109 --- /dev/null +++ b/chrome/browser/unload_uitest.cc @@ -0,0 +1,453 @@ +// Copyright (c) 2006-2009 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 "app/message_box_flags.h" +#include "base/file_util.h" +#include "base/platform_thread.h" +#include "chrome/browser/net/url_request_mock_http_job.h" +#include "chrome/browser/view_ids.h" +#include "chrome/common/chrome_switches.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 "net/url_request/url_request_unittest.h" +#include "views/event.h" + +#if defined(OS_LINUX) +// http://crbug.com/47575 +#define SKIP_LINUX(test) DISABLED_##test +#else +#define SKIP_LINUX(test) test +#endif + +const std::string NOLISTENERS_HTML = + "<html><head><title>nolisteners</title></head><body></body></html>"; + +const std::string UNLOAD_HTML = + "<html><head><title>unload</title></head><body>" + "<script>window.onunload=function(e){}</script></body></html>"; + +const std::string BEFORE_UNLOAD_HTML = + "<html><head><title>beforeunload</title></head><body>" + "<script>window.onbeforeunload=function(e){return 'foo'}</script>" + "</body></html>"; + +const std::string INNER_FRAME_WITH_FOCUS_HTML = + "<html><head><title>innerframewithfocus</title></head><body>" + "<script>window.onbeforeunload=function(e){return 'foo'}</script>" + "<iframe src=\"data:text/html,<html><head><script>window.onload=" + "function(){document.getElementById('box').focus()}</script>" + "<body><input id='box'></input></body></html>\"></iframe>" + "</body></html>"; + +const std::string TWO_SECOND_BEFORE_UNLOAD_HTML = + "<html><head><title>twosecondbeforeunload</title></head><body>" + "<script>window.onbeforeunload=function(e){" + "var start = new Date().getTime();" + "while(new Date().getTime() - start < 2000){}" + "return 'foo';" + "}</script></body></html>"; + +const std::string INFINITE_UNLOAD_HTML = + "<html><head><title>infiniteunload</title></head><body>" + "<script>window.onunload=function(e){while(true){}}</script>" + "</body></html>"; + +const std::string INFINITE_BEFORE_UNLOAD_HTML = + "<html><head><title>infinitebeforeunload</title></head><body>" + "<script>window.onbeforeunload=function(e){while(true){}}</script>" + "</body></html>"; + +const std::string INFINITE_UNLOAD_ALERT_HTML = + "<html><head><title>infiniteunloadalert</title></head><body>" + "<script>window.onunload=function(e){" + "while(true){}" + "alert('foo');" + "}</script></body></html>"; + +const std::string INFINITE_BEFORE_UNLOAD_ALERT_HTML = + "<html><head><title>infinitebeforeunloadalert</title></head><body>" + "<script>window.onbeforeunload=function(e){" + "while(true){}" + "alert('foo');" + "}</script></body></html>"; + +const std::string TWO_SECOND_UNLOAD_ALERT_HTML = + "<html><head><title>twosecondunloadalert</title></head><body>" + "<script>window.onunload=function(e){" + "var start = new Date().getTime();" + "while(new Date().getTime() - start < 2000){}" + "alert('foo');" + "}</script></body></html>"; + +const std::string TWO_SECOND_BEFORE_UNLOAD_ALERT_HTML = + "<html><head><title>twosecondbeforeunloadalert</title></head><body>" + "<script>window.onbeforeunload=function(e){" + "var start = new Date().getTime();" + "while(new Date().getTime() - start < 2000){}" + "alert('foo');" + "}</script></body></html>"; + +const std::string CLOSE_TAB_WHEN_OTHER_TAB_HAS_LISTENER = + "<html><head><title>only_one_unload</title></head>" + "<body onclick=\"window.open('data:text/html," + "<html><head><title>popup</title></head></body>')\" " + "onbeforeunload='return;'>" + "</body></html>"; + +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 WaitForBrowserClosed() { + const int kCheckDelayMs = 100; + for (int max_wait_time = action_max_timeout_ms(); + max_wait_time > 0; max_wait_time -= kCheckDelayMs) { + CrashAwareSleep(kCheckDelayMs); + if (!IsBrowserRunning()) + break; + } + + EXPECT_FALSE(IsBrowserRunning()); + } + + void CheckTitle(const std::wstring& expected_title) { + const int kCheckDelayMs = 100; + for (int max_wait_time = action_max_timeout_ms(); + max_wait_time > 0; max_wait_time -= kCheckDelayMs) { + CrashAwareSleep(kCheckDelayMs); + if (expected_title == GetActiveTabTitle()) + break; + } + + 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<BrowserProxy> 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(MessageBoxFlags::DialogButton button) { + bool modal_dialog_showing = false; + MessageBoxFlags::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(); + ASSERT_TRUE(IsBrowserRunning()); +} + +// 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(); + ASSERT_TRUE(IsBrowserRunning()); +} + +// TODO(creis): This test is currently failing intermittently on one of the test +// bots. Investigating with crbug.com/34827. +// +// 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. +// See crbug.com/11007. +TEST_F(UnloadTest, FLAKY_CrossSiteInfiniteUnloadAsyncInputEvent) { + // Tests makes no sense in single-process mode since the renderer is hung.c + 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<BrowserProxy> browser(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser.get()); + scoped_refptr<WindowProxy> 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(), + views::Event::EF_LEFT_BUTTON_DOWN, false)); + + // The title should update before the timeout in CheckTitle. + CheckTitle(L"Title Of Awesomeness"); + ASSERT_TRUE(IsBrowserRunning()); +} + +// 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, FLAKY_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(); + ASSERT_TRUE(IsBrowserRunning()); +} + +// 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, 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(); + ASSERT_TRUE(IsBrowserRunning()); +} + +// Tests closing the browser on a page with no unload listeners registered. +TEST_F(UnloadTest, SKIP_LINUX(BrowserCloseNoUnloadListeners)) { + LoadUrlAndQuitBrowser(NOLISTENERS_HTML, L"nolisteners"); +} + +// Tests closing the browser on a page with an unload listener registered. +TEST_F(UnloadTest, SKIP_LINUX(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<BrowserProxy> browser(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser.get()); + NavigateToDataURL(BEFORE_UNLOAD_HTML, L"beforeunload"); + + CloseBrowserAsync(browser.get()); + ClickModalDialogButton(MessageBoxFlags::DIALOGBUTTON_OK); + WaitForBrowserClosed(); +} + +// Tests closing the browser with a beforeunload handler and clicking +// CANCEL in the beforeunload confirm dialog. +TEST_F(UnloadTest, BrowserCloseBeforeUnloadCancel) { + scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser.get()); + NavigateToDataURL(BEFORE_UNLOAD_HTML, L"beforeunload"); + + CloseBrowserAsync(browser.get()); + ClickModalDialogButton(MessageBoxFlags::DIALOGBUTTON_CANCEL); + // There's no real graceful way to wait for something _not_ to happen, so + // we just wait a short period. + CrashAwareSleep(500); + ASSERT_TRUE(IsBrowserRunning()); + + CloseBrowserAsync(browser.get()); + ClickModalDialogButton(MessageBoxFlags::DIALOGBUTTON_OK); + WaitForBrowserClosed(); +} + +#if defined(OS_LINUX) +// Fails sometimes on Linux valgrind. http://crbug.com/32615 +#define MAYBE_BrowserCloseWithInnerFocusedFrame \ + FLAKY_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<BrowserProxy> browser(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser.get()); + + NavigateToDataURL(INNER_FRAME_WITH_FOCUS_HTML, L"innerframewithfocus"); + + CloseBrowserAsync(browser.get()); + ClickModalDialogButton(MessageBoxFlags::DIALOGBUTTON_OK); + WaitForBrowserClosed(); +} + +// Tests closing the browser with a beforeunload handler that takes +// two seconds to run. +TEST_F(UnloadTest, SKIP_LINUX(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, SKIP_LINUX(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"); +} + +// Tests closing the browser with a beforeunload handler that hangs. +TEST_F(UnloadTest, SKIP_LINUX(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"); +} + +// 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, SKIP_LINUX(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"); +} + +// Tests closing the browser with a beforeunload handler that hangs then +// pops up an alert. +TEST_F(UnloadTest, SKIP_LINUX(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, SKIP_LINUX(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, SKIP_LINUX(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 \ + FAILS_BrowserCloseTabWhenOtherTabHasListener +#else +// Flaky on Linux as well. http://crbug.com/45562 +#define MAYBE_BrowserCloseTabWhenOtherTabHasListener \ + FLAKY_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<BrowserProxy> browser = automation()->GetBrowserWindow(0); + ASSERT_TRUE(browser.get()); + scoped_refptr<WindowProxy> 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(), + views::Event::EF_LEFT_BUTTON_DOWN)); + ASSERT_TRUE(browser->WaitForTabCountToBecome(2, action_timeout_ms())); + + scoped_refptr<TabProxy> popup_tab(browser->GetActiveTab()); + ASSERT_TRUE(popup_tab.get()); + std::wstring popup_title; + EXPECT_TRUE(popup_tab->GetTabTitle(&popup_title)); + EXPECT_EQ(std::wstring(L"popup"), popup_title); + EXPECT_TRUE(popup_tab->Close(true)); + + ASSERT_TRUE(browser->WaitForTabCountToBecome(1, action_timeout_ms())); + scoped_refptr<TabProxy> 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. |