unload_uitest.cc revision dc0f95d653279beabeb9817299e2902918ba123e
1// Copyright (c) 2011 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/file_util.h" 6#include "base/test/test_timeouts.h" 7#include "chrome/browser/net/url_request_mock_http_job.h" 8#include "chrome/browser/ui/view_ids.h" 9#include "chrome/common/chrome_switches.h" 10#include "chrome/test/automation/browser_proxy.h" 11#include "chrome/test/automation/tab_proxy.h" 12#include "chrome/test/automation/window_proxy.h" 13#include "chrome/test/ui/ui_test.h" 14#include "net/url_request/url_request_test_util.h" 15#include "ui/base/events.h" 16#include "ui/base/message_box_flags.h" 17 18const std::string NOLISTENERS_HTML = 19 "<html><head><title>nolisteners</title></head><body></body></html>"; 20 21const std::string UNLOAD_HTML = 22 "<html><head><title>unload</title></head><body>" 23 "<script>window.onunload=function(e){}</script></body></html>"; 24 25const std::string BEFORE_UNLOAD_HTML = 26 "<html><head><title>beforeunload</title></head><body>" 27 "<script>window.onbeforeunload=function(e){return 'foo'}</script>" 28 "</body></html>"; 29 30const std::string INNER_FRAME_WITH_FOCUS_HTML = 31 "<html><head><title>innerframewithfocus</title></head><body>" 32 "<script>window.onbeforeunload=function(e){return 'foo'}</script>" 33 "<iframe src=\"data:text/html,<html><head><script>window.onload=" 34 "function(){document.getElementById('box').focus()}</script>" 35 "<body><input id='box'></input></body></html>\"></iframe>" 36 "</body></html>"; 37 38const std::string TWO_SECOND_BEFORE_UNLOAD_HTML = 39 "<html><head><title>twosecondbeforeunload</title></head><body>" 40 "<script>window.onbeforeunload=function(e){" 41 "var start = new Date().getTime();" 42 "while(new Date().getTime() - start < 2000){}" 43 "return 'foo';" 44 "}</script></body></html>"; 45 46const std::string INFINITE_UNLOAD_HTML = 47 "<html><head><title>infiniteunload</title></head><body>" 48 "<script>window.onunload=function(e){while(true){}}</script>" 49 "</body></html>"; 50 51const std::string INFINITE_BEFORE_UNLOAD_HTML = 52 "<html><head><title>infinitebeforeunload</title></head><body>" 53 "<script>window.onbeforeunload=function(e){while(true){}}</script>" 54 "</body></html>"; 55 56const std::string INFINITE_UNLOAD_ALERT_HTML = 57 "<html><head><title>infiniteunloadalert</title></head><body>" 58 "<script>window.onunload=function(e){" 59 "while(true){}" 60 "alert('foo');" 61 "}</script></body></html>"; 62 63const std::string INFINITE_BEFORE_UNLOAD_ALERT_HTML = 64 "<html><head><title>infinitebeforeunloadalert</title></head><body>" 65 "<script>window.onbeforeunload=function(e){" 66 "while(true){}" 67 "alert('foo');" 68 "}</script></body></html>"; 69 70const std::string TWO_SECOND_UNLOAD_ALERT_HTML = 71 "<html><head><title>twosecondunloadalert</title></head><body>" 72 "<script>window.onunload=function(e){" 73 "var start = new Date().getTime();" 74 "while(new Date().getTime() - start < 2000){}" 75 "alert('foo');" 76 "}</script></body></html>"; 77 78const std::string TWO_SECOND_BEFORE_UNLOAD_ALERT_HTML = 79 "<html><head><title>twosecondbeforeunloadalert</title></head><body>" 80 "<script>window.onbeforeunload=function(e){" 81 "var start = new Date().getTime();" 82 "while(new Date().getTime() - start < 2000){}" 83 "alert('foo');" 84 "}</script></body></html>"; 85 86const std::string CLOSE_TAB_WHEN_OTHER_TAB_HAS_LISTENER = 87 "<html><head><title>only_one_unload</title></head>" 88 "<body onclick=\"window.open('data:text/html," 89 "<html><head><title>popup</title></head></body>')\" " 90 "onbeforeunload='return;'>" 91 "</body></html>"; 92 93class UnloadTest : public UITest { 94 public: 95 virtual void SetUp() { 96 const testing::TestInfo* const test_info = 97 testing::UnitTest::GetInstance()->current_test_info(); 98 if (strcmp(test_info->name(), 99 "BrowserCloseTabWhenOtherTabHasListener") == 0) { 100 launch_arguments_.AppendSwitch(switches::kDisablePopupBlocking); 101 } 102 103 UITest::SetUp(); 104 } 105 106 void WaitForBrowserClosed() { 107 const int kCheckDelayMs = 100; 108 for (int max_wait_time = TestTimeouts::action_max_timeout_ms(); 109 max_wait_time > 0; max_wait_time -= kCheckDelayMs) { 110 CrashAwareSleep(kCheckDelayMs); 111 if (!IsBrowserRunning()) 112 break; 113 } 114 115 EXPECT_FALSE(IsBrowserRunning()); 116 } 117 118 void CheckTitle(const std::wstring& expected_title) { 119 const int kCheckDelayMs = 100; 120 for (int max_wait_time = TestTimeouts::action_max_timeout_ms(); 121 max_wait_time > 0; max_wait_time -= kCheckDelayMs) { 122 CrashAwareSleep(kCheckDelayMs); 123 if (expected_title == GetActiveTabTitle()) 124 break; 125 } 126 127 EXPECT_EQ(expected_title, GetActiveTabTitle()); 128 } 129 130 void NavigateToDataURL(const std::string& html_content, 131 const std::wstring& expected_title) { 132 NavigateToURL(GURL("data:text/html," + html_content)); 133 CheckTitle(expected_title); 134 } 135 136 void NavigateToNolistenersFileTwice() { 137 NavigateToURL(URLRequestMockHTTPJob::GetMockUrl( 138 FilePath(FILE_PATH_LITERAL("title2.html")))); 139 CheckTitle(L"Title Of Awesomeness"); 140 NavigateToURL(URLRequestMockHTTPJob::GetMockUrl( 141 FilePath(FILE_PATH_LITERAL("title2.html")))); 142 CheckTitle(L"Title Of Awesomeness"); 143 } 144 145 // Navigates to a URL asynchronously, then again synchronously. The first 146 // load is purposely async to test the case where the user loads another 147 // page without waiting for the first load to complete. 148 void NavigateToNolistenersFileTwiceAsync() { 149 NavigateToURLAsync( 150 URLRequestMockHTTPJob::GetMockUrl( 151 FilePath(FILE_PATH_LITERAL("title2.html")))); 152 NavigateToURL( 153 URLRequestMockHTTPJob::GetMockUrl( 154 FilePath(FILE_PATH_LITERAL("title2.html")))); 155 156 CheckTitle(L"Title Of Awesomeness"); 157 } 158 159 void LoadUrlAndQuitBrowser(const std::string& html_content, 160 const std::wstring& expected_title = L"") { 161 scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); 162 ASSERT_TRUE(browser.get()); 163 NavigateToDataURL(html_content, expected_title); 164 bool application_closed = false; 165 EXPECT_TRUE(CloseBrowser(browser.get(), &application_closed)); 166 } 167 168 void ClickModalDialogButton(ui::MessageBoxFlags::DialogButton button) { 169 bool modal_dialog_showing = false; 170 ui::MessageBoxFlags::DialogButton available_buttons; 171 EXPECT_TRUE(automation()->WaitForAppModalDialog()); 172 EXPECT_TRUE(automation()->GetShowingAppModalDialog(&modal_dialog_showing, 173 &available_buttons)); 174 ASSERT_TRUE(modal_dialog_showing); 175 EXPECT_TRUE((button & available_buttons) != 0); 176 EXPECT_TRUE(automation()->ClickAppModalDialogButton(button)); 177 } 178}; 179 180// Navigate to a page with an infinite unload handler. 181// Then two async crosssite requests to ensure 182// we don't get confused and think we're closing the tab. 183// 184// This test is flaky on the valgrind UI bots. http://crbug.com/39057 185TEST_F(UnloadTest, DISABLED_CrossSiteInfiniteUnloadAsync) { 186 // Tests makes no sense in single-process mode since the renderer is hung. 187 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) 188 return; 189 190 NavigateToDataURL(INFINITE_UNLOAD_HTML, L"infiniteunload"); 191 // Must navigate to a non-data URL to trigger cross-site codepath. 192 NavigateToNolistenersFileTwiceAsync(); 193 ASSERT_TRUE(IsBrowserRunning()); 194} 195 196// Navigate to a page with an infinite unload handler. 197// Then two sync crosssite requests to ensure 198// we correctly nav to each one. 199TEST_F(UnloadTest, CrossSiteInfiniteUnloadSync) { 200 // Tests makes no sense in single-process mode since the renderer is hung. 201 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) 202 return; 203 204 NavigateToDataURL(INFINITE_UNLOAD_HTML, L"infiniteunload"); 205 // Must navigate to a non-data URL to trigger cross-site codepath. 206 NavigateToNolistenersFileTwice(); 207 ASSERT_TRUE(IsBrowserRunning()); 208} 209 210// TODO(creis): This test is currently failing intermittently on Linux and 211// consistently on Mac and Vista. http://crbug.com/38427 212#if defined(OS_MACOSX) 213#define MAYBE_CrossSiteInfiniteUnloadAsyncInputEvent \ 214 DISABLED_CrossSiteInfiniteUnloadAsyncInputEvent 215#elif defined(OS_WIN) 216#define MAYBE_CrossSiteInfiniteUnloadAsyncInputEvent \ 217 DISABLED_CrossSiteInfiniteUnloadAsyncInputEvent 218#else 219// Flaky on Linux. http://crbug.com/38427 220#define MAYBE_CrossSiteInfiniteUnloadAsyncInputEvent \ 221 FLAKY_CrossSiteInfiniteUnloadAsyncInputEvent 222#endif 223 224// Navigate to a page with an infinite unload handler. 225// Then an async crosssite request followed by an input event to ensure that 226// the short unload timeout (not the long input event timeout) is used. 227// See crbug.com/11007. 228TEST_F(UnloadTest, MAYBE_CrossSiteInfiniteUnloadAsyncInputEvent) { 229 // Tests makes no sense in single-process mode since the renderer is hung. 230 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) 231 return; 232 233 NavigateToDataURL(INFINITE_UNLOAD_HTML, L"infiniteunload"); 234 235 // Navigate to a new URL asynchronously. 236 NavigateToURLAsync( 237 URLRequestMockHTTPJob::GetMockUrl( 238 FilePath(FILE_PATH_LITERAL("title2.html")))); 239 240 // Now send an input event while we're stalled on the unload handler. 241 scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); 242 ASSERT_TRUE(browser.get()); 243 scoped_refptr<WindowProxy> window(browser->GetWindow()); 244 ASSERT_TRUE(window.get()); 245 gfx::Rect bounds; 246 ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_0, &bounds, false)); 247 ASSERT_TRUE(browser->SimulateDrag(bounds.CenterPoint(), bounds.CenterPoint(), 248 ui::EF_LEFT_BUTTON_DOWN, false)); 249 250 // The title should update before the timeout in CheckTitle. 251 CheckTitle(L"Title Of Awesomeness"); 252 ASSERT_TRUE(IsBrowserRunning()); 253} 254 255// Navigate to a page with an infinite beforeunload handler. 256// Then two two async crosssite requests to ensure 257// we don't get confused and think we're closing the tab. 258// This test is flaky on the valgrind UI bots. http://crbug.com/39057 259TEST_F(UnloadTest, FLAKY_CrossSiteInfiniteBeforeUnloadAsync) { 260 // Tests makes no sense in single-process mode since the renderer is hung. 261 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) 262 return; 263 264 NavigateToDataURL(INFINITE_BEFORE_UNLOAD_HTML, L"infinitebeforeunload"); 265 // Must navigate to a non-data URL to trigger cross-site codepath. 266 NavigateToNolistenersFileTwiceAsync(); 267 ASSERT_TRUE(IsBrowserRunning()); 268} 269 270// Navigate to a page with an infinite beforeunload handler. 271// Then two two sync crosssite requests to ensure 272// we correctly nav to each one. 273TEST_F(UnloadTest, CrossSiteInfiniteBeforeUnloadSync) { 274 // Tests makes no sense in single-process mode since the renderer is hung. 275 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) 276 return; 277 278 NavigateToDataURL(INFINITE_BEFORE_UNLOAD_HTML, L"infinitebeforeunload"); 279 // Must navigate to a non-data URL to trigger cross-site codepath. 280 NavigateToNolistenersFileTwice(); 281 ASSERT_TRUE(IsBrowserRunning()); 282} 283 284// Tests closing the browser on a page with no unload listeners registered. 285TEST_F(UnloadTest, BrowserCloseNoUnloadListeners) { 286 LoadUrlAndQuitBrowser(NOLISTENERS_HTML, L"nolisteners"); 287} 288 289// Tests closing the browser on a page with an unload listener registered. 290// Test marked as flaky in http://crbug.com/51698 291TEST_F(UnloadTest, FLAKY_BrowserCloseUnload) { 292 LoadUrlAndQuitBrowser(UNLOAD_HTML, L"unload"); 293} 294 295// Tests closing the browser with a beforeunload handler and clicking 296// OK in the beforeunload confirm dialog. 297TEST_F(UnloadTest, BrowserCloseBeforeUnloadOK) { 298 scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); 299 ASSERT_TRUE(browser.get()); 300 NavigateToDataURL(BEFORE_UNLOAD_HTML, L"beforeunload"); 301 302 CloseBrowserAsync(browser.get()); 303 ClickModalDialogButton(ui::MessageBoxFlags::DIALOGBUTTON_OK); 304 WaitForBrowserClosed(); 305} 306 307// Tests closing the browser with a beforeunload handler and clicking 308// CANCEL in the beforeunload confirm dialog. 309TEST_F(UnloadTest, BrowserCloseBeforeUnloadCancel) { 310 scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); 311 ASSERT_TRUE(browser.get()); 312 NavigateToDataURL(BEFORE_UNLOAD_HTML, L"beforeunload"); 313 314 CloseBrowserAsync(browser.get()); 315 ClickModalDialogButton(ui::MessageBoxFlags::DIALOGBUTTON_CANCEL); 316 // There's no real graceful way to wait for something _not_ to happen, so 317 // we just wait a short period. 318 CrashAwareSleep(500); 319 ASSERT_TRUE(IsBrowserRunning()); 320 321 CloseBrowserAsync(browser.get()); 322 ClickModalDialogButton(ui::MessageBoxFlags::DIALOGBUTTON_OK); 323 WaitForBrowserClosed(); 324} 325 326#if defined(OS_LINUX) 327// Fails sometimes on Linux valgrind. http://crbug.com/45675 328#define MAYBE_BrowserCloseWithInnerFocusedFrame \ 329 FLAKY_BrowserCloseWithInnerFocusedFrame 330#else 331#define MAYBE_BrowserCloseWithInnerFocusedFrame \ 332 BrowserCloseWithInnerFocusedFrame 333#endif 334 335// Tests closing the browser and clicking OK in the beforeunload confirm dialog 336// if an inner frame has the focus. See crbug.com/32615. 337TEST_F(UnloadTest, MAYBE_BrowserCloseWithInnerFocusedFrame) { 338 scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); 339 ASSERT_TRUE(browser.get()); 340 341 NavigateToDataURL(INNER_FRAME_WITH_FOCUS_HTML, L"innerframewithfocus"); 342 343 CloseBrowserAsync(browser.get()); 344 ClickModalDialogButton(ui::MessageBoxFlags::DIALOGBUTTON_OK); 345 WaitForBrowserClosed(); 346} 347 348// Tests closing the browser with a beforeunload handler that takes 349// two seconds to run. 350TEST_F(UnloadTest, BrowserCloseTwoSecondBeforeUnload) { 351 LoadUrlAndQuitBrowser(TWO_SECOND_BEFORE_UNLOAD_HTML, 352 L"twosecondbeforeunload"); 353} 354 355// Tests closing the browser on a page with an unload listener registered where 356// the unload handler has an infinite loop. 357TEST_F(UnloadTest, BrowserCloseInfiniteUnload) { 358 // Tests makes no sense in single-process mode since the renderer is hung. 359 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) 360 return; 361 362 LoadUrlAndQuitBrowser(INFINITE_UNLOAD_HTML, L"infiniteunload"); 363} 364 365// Tests closing the browser with a beforeunload handler that hangs. 366TEST_F(UnloadTest, BrowserCloseInfiniteBeforeUnload) { 367 // Tests makes no sense in single-process mode since the renderer is hung. 368 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) 369 return; 370 371 LoadUrlAndQuitBrowser(INFINITE_BEFORE_UNLOAD_HTML, L"infinitebeforeunload"); 372} 373 374// Tests closing the browser on a page with an unload listener registered where 375// the unload handler has an infinite loop followed by an alert. 376TEST_F(UnloadTest, BrowserCloseInfiniteUnloadAlert) { 377 // Tests makes no sense in single-process mode since the renderer is hung. 378 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) 379 return; 380 381 LoadUrlAndQuitBrowser(INFINITE_UNLOAD_ALERT_HTML, L"infiniteunloadalert"); 382} 383 384// Tests closing the browser with a beforeunload handler that hangs then 385// pops up an alert. 386TEST_F(UnloadTest, BrowserCloseInfiniteBeforeUnloadAlert) { 387 // Tests makes no sense in single-process mode since the renderer is hung. 388 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) 389 return; 390 391 LoadUrlAndQuitBrowser(INFINITE_BEFORE_UNLOAD_ALERT_HTML, 392 L"infinitebeforeunloadalert"); 393} 394 395// Tests closing the browser on a page with an unload listener registered where 396// the unload handler has an 2 second long loop followed by an alert. 397TEST_F(UnloadTest, BrowserCloseTwoSecondUnloadAlert) { 398 LoadUrlAndQuitBrowser(TWO_SECOND_UNLOAD_ALERT_HTML, L"twosecondunloadalert"); 399} 400 401// Tests closing the browser with a beforeunload handler that takes 402// two seconds to run then pops up an alert. 403TEST_F(UnloadTest, BrowserCloseTwoSecondBeforeUnloadAlert) { 404 LoadUrlAndQuitBrowser(TWO_SECOND_BEFORE_UNLOAD_ALERT_HTML, 405 L"twosecondbeforeunloadalert"); 406} 407 408#if defined(OS_MACOSX) 409// http://crbug.com/45162 410#define MAYBE_BrowserCloseTabWhenOtherTabHasListener \ 411 DISABLED_BrowserCloseTabWhenOtherTabHasListener 412#elif defined(OS_WIN) 413// http://crbug.com/45281 414#define MAYBE_BrowserCloseTabWhenOtherTabHasListener \ 415 DISABLED_BrowserCloseTabWhenOtherTabHasListener 416#else 417#define MAYBE_BrowserCloseTabWhenOtherTabHasListener \ 418 BrowserCloseTabWhenOtherTabHasListener 419#endif 420 421// Tests that if there's a renderer process with two tabs, one of which has an 422// unload handler, and the other doesn't, the tab that doesn't have an unload 423// handler can be closed. 424TEST_F(UnloadTest, MAYBE_BrowserCloseTabWhenOtherTabHasListener) { 425 NavigateToDataURL(CLOSE_TAB_WHEN_OTHER_TAB_HAS_LISTENER, L"only_one_unload"); 426 427 scoped_refptr<BrowserProxy> browser = automation()->GetBrowserWindow(0); 428 ASSERT_TRUE(browser.get()); 429 scoped_refptr<WindowProxy> window = browser->GetWindow(); 430 ASSERT_TRUE(window.get()); 431 432 gfx::Rect tab_view_bounds; 433 ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_CONTAINER, 434 &tab_view_bounds, true)); 435 // Simulate a click to force user_gesture to true; if we don't, the resulting 436 // popup will be constrained, which isn't what we want to test. 437 ASSERT_TRUE(window->SimulateOSClick(tab_view_bounds.CenterPoint(), 438 ui::EF_LEFT_BUTTON_DOWN)); 439 ASSERT_TRUE(browser->WaitForTabCountToBecome(2)); 440 441 CheckTitle(L"popup"); 442 scoped_refptr<TabProxy> popup_tab(browser->GetActiveTab()); 443 ASSERT_TRUE(popup_tab.get()); 444 EXPECT_TRUE(popup_tab->Close(true)); 445 446 ASSERT_TRUE(browser->WaitForTabCountToBecome(1)); 447 scoped_refptr<TabProxy> main_tab(browser->GetActiveTab()); 448 ASSERT_TRUE(main_tab.get()); 449 std::wstring main_title; 450 EXPECT_TRUE(main_tab->GetTabTitle(&main_title)); 451 EXPECT_EQ(std::wstring(L"only_one_unload"), main_title); 452} 453 454// TODO(ojan): Add tests for unload/beforeunload that have multiple tabs 455// and multiple windows. 456