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 18 const std::string NOLISTENERS_HTML = 19 "<html><head><title>nolisteners</title></head><body></body></html>"; 20 21 const std::string UNLOAD_HTML = 22 "<html><head><title>unload</title></head><body>" 23 "<script>window.onunload=function(e){}</script></body></html>"; 24 25 const 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 30 const 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 38 const 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 46 const 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 51 const 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 56 const 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 63 const 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 70 const 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 78 const 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 86 const 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 93 class 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 CheckTitle(const std::wstring& expected_title) { 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 if (expected_title == GetActiveTabTitle()) 111 break; 112 base::PlatformThread::Sleep(kCheckDelayMs); 113 } 114 115 EXPECT_EQ(expected_title, GetActiveTabTitle()); 116 } 117 118 void NavigateToDataURL(const std::string& html_content, 119 const std::wstring& expected_title) { 120 NavigateToURL(GURL("data:text/html," + html_content)); 121 CheckTitle(expected_title); 122 } 123 124 void NavigateToNolistenersFileTwice() { 125 NavigateToURL(URLRequestMockHTTPJob::GetMockUrl( 126 FilePath(FILE_PATH_LITERAL("title2.html")))); 127 CheckTitle(L"Title Of Awesomeness"); 128 NavigateToURL(URLRequestMockHTTPJob::GetMockUrl( 129 FilePath(FILE_PATH_LITERAL("title2.html")))); 130 CheckTitle(L"Title Of Awesomeness"); 131 } 132 133 // Navigates to a URL asynchronously, then again synchronously. The first 134 // load is purposely async to test the case where the user loads another 135 // page without waiting for the first load to complete. 136 void NavigateToNolistenersFileTwiceAsync() { 137 NavigateToURLAsync( 138 URLRequestMockHTTPJob::GetMockUrl( 139 FilePath(FILE_PATH_LITERAL("title2.html")))); 140 NavigateToURL( 141 URLRequestMockHTTPJob::GetMockUrl( 142 FilePath(FILE_PATH_LITERAL("title2.html")))); 143 144 CheckTitle(L"Title Of Awesomeness"); 145 } 146 147 void LoadUrlAndQuitBrowser(const std::string& html_content, 148 const std::wstring& expected_title = L"") { 149 scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); 150 ASSERT_TRUE(browser.get()); 151 NavigateToDataURL(html_content, expected_title); 152 bool application_closed = false; 153 EXPECT_TRUE(CloseBrowser(browser.get(), &application_closed)); 154 } 155 156 void ClickModalDialogButton(ui::MessageBoxFlags::DialogButton button) { 157 bool modal_dialog_showing = false; 158 ui::MessageBoxFlags::DialogButton available_buttons; 159 EXPECT_TRUE(automation()->WaitForAppModalDialog()); 160 EXPECT_TRUE(automation()->GetShowingAppModalDialog(&modal_dialog_showing, 161 &available_buttons)); 162 ASSERT_TRUE(modal_dialog_showing); 163 EXPECT_TRUE((button & available_buttons) != 0); 164 EXPECT_TRUE(automation()->ClickAppModalDialogButton(button)); 165 } 166 }; 167 168 // Navigate to a page with an infinite unload handler. 169 // Then two async crosssite requests to ensure 170 // we don't get confused and think we're closing the tab. 171 // 172 // This test is flaky on the valgrind UI bots. http://crbug.com/39057 173 TEST_F(UnloadTest, DISABLED_CrossSiteInfiniteUnloadAsync) { 174 // Tests makes no sense in single-process mode since the renderer is hung. 175 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) 176 return; 177 178 NavigateToDataURL(INFINITE_UNLOAD_HTML, L"infiniteunload"); 179 // Must navigate to a non-data URL to trigger cross-site codepath. 180 NavigateToNolistenersFileTwiceAsync(); 181 ASSERT_TRUE(IsBrowserRunning()); 182 } 183 184 // Navigate to a page with an infinite unload handler. 185 // Then two sync crosssite requests to ensure 186 // we correctly nav to each one. 187 TEST_F(UnloadTest, CrossSiteInfiniteUnloadSync) { 188 // Tests makes no sense in single-process mode since the renderer is hung. 189 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) 190 return; 191 192 NavigateToDataURL(INFINITE_UNLOAD_HTML, L"infiniteunload"); 193 // Must navigate to a non-data URL to trigger cross-site codepath. 194 NavigateToNolistenersFileTwice(); 195 ASSERT_TRUE(IsBrowserRunning()); 196 } 197 198 // TODO(creis): This test is currently failing intermittently on Linux and 199 // consistently on Mac and Vista. http://crbug.com/38427 200 #if defined(OS_MACOSX) 201 #define MAYBE_CrossSiteInfiniteUnloadAsyncInputEvent \ 202 DISABLED_CrossSiteInfiniteUnloadAsyncInputEvent 203 #elif defined(OS_WIN) 204 #define MAYBE_CrossSiteInfiniteUnloadAsyncInputEvent \ 205 DISABLED_CrossSiteInfiniteUnloadAsyncInputEvent 206 #else 207 // Flaky on Linux. http://crbug.com/38427 208 #define MAYBE_CrossSiteInfiniteUnloadAsyncInputEvent \ 209 FLAKY_CrossSiteInfiniteUnloadAsyncInputEvent 210 #endif 211 212 // Navigate to a page with an infinite unload handler. 213 // Then an async crosssite request followed by an input event to ensure that 214 // the short unload timeout (not the long input event timeout) is used. 215 // See crbug.com/11007. 216 TEST_F(UnloadTest, MAYBE_CrossSiteInfiniteUnloadAsyncInputEvent) { 217 // Tests makes no sense in single-process mode since the renderer is hung. 218 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) 219 return; 220 221 NavigateToDataURL(INFINITE_UNLOAD_HTML, L"infiniteunload"); 222 223 // Navigate to a new URL asynchronously. 224 NavigateToURLAsync( 225 URLRequestMockHTTPJob::GetMockUrl( 226 FilePath(FILE_PATH_LITERAL("title2.html")))); 227 228 // Now send an input event while we're stalled on the unload handler. 229 scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); 230 ASSERT_TRUE(browser.get()); 231 scoped_refptr<WindowProxy> window(browser->GetWindow()); 232 ASSERT_TRUE(window.get()); 233 gfx::Rect bounds; 234 ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_0, &bounds, false)); 235 ASSERT_TRUE(browser->SimulateDrag(bounds.CenterPoint(), bounds.CenterPoint(), 236 ui::EF_LEFT_BUTTON_DOWN, false)); 237 238 // The title should update before the timeout in CheckTitle. 239 CheckTitle(L"Title Of Awesomeness"); 240 ASSERT_TRUE(IsBrowserRunning()); 241 } 242 243 // Navigate to a page with an infinite beforeunload handler. 244 // Then two two async crosssite requests to ensure 245 // we don't get confused and think we're closing the tab. 246 // This test is flaky on the valgrind UI bots. http://crbug.com/39057 247 TEST_F(UnloadTest, FLAKY_CrossSiteInfiniteBeforeUnloadAsync) { 248 // Tests makes no sense in single-process mode since the renderer is hung. 249 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) 250 return; 251 252 NavigateToDataURL(INFINITE_BEFORE_UNLOAD_HTML, L"infinitebeforeunload"); 253 // Must navigate to a non-data URL to trigger cross-site codepath. 254 NavigateToNolistenersFileTwiceAsync(); 255 ASSERT_TRUE(IsBrowserRunning()); 256 } 257 258 // Navigate to a page with an infinite beforeunload handler. 259 // Then two two sync crosssite requests to ensure 260 // we correctly nav to each one. 261 TEST_F(UnloadTest, CrossSiteInfiniteBeforeUnloadSync) { 262 // Tests makes no sense in single-process mode since the renderer is hung. 263 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) 264 return; 265 266 NavigateToDataURL(INFINITE_BEFORE_UNLOAD_HTML, L"infinitebeforeunload"); 267 // Must navigate to a non-data URL to trigger cross-site codepath. 268 NavigateToNolistenersFileTwice(); 269 ASSERT_TRUE(IsBrowserRunning()); 270 } 271 272 // Tests closing the browser on a page with no unload listeners registered. 273 TEST_F(UnloadTest, BrowserCloseNoUnloadListeners) { 274 LoadUrlAndQuitBrowser(NOLISTENERS_HTML, L"nolisteners"); 275 } 276 277 // Tests closing the browser on a page with an unload listener registered. 278 // Test marked as flaky in http://crbug.com/51698 279 TEST_F(UnloadTest, FLAKY_BrowserCloseUnload) { 280 LoadUrlAndQuitBrowser(UNLOAD_HTML, L"unload"); 281 } 282 283 // Tests closing the browser with a beforeunload handler and clicking 284 // OK in the beforeunload confirm dialog. 285 TEST_F(UnloadTest, BrowserCloseBeforeUnloadOK) { 286 scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); 287 ASSERT_TRUE(browser.get()); 288 NavigateToDataURL(BEFORE_UNLOAD_HTML, L"beforeunload"); 289 290 CloseBrowserAsync(browser.get()); 291 ClickModalDialogButton(ui::MessageBoxFlags::DIALOGBUTTON_OK); 292 293 int exit_code = -1; 294 ASSERT_TRUE(launcher_->WaitForBrowserProcessToQuit( 295 TestTimeouts::action_max_timeout_ms(), &exit_code)); 296 EXPECT_EQ(0, exit_code); // Expect a clean shutown. 297 } 298 299 // Tests closing the browser with a beforeunload handler and clicking 300 // CANCEL in the beforeunload confirm dialog. 301 TEST_F(UnloadTest, BrowserCloseBeforeUnloadCancel) { 302 scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); 303 ASSERT_TRUE(browser.get()); 304 NavigateToDataURL(BEFORE_UNLOAD_HTML, L"beforeunload"); 305 306 CloseBrowserAsync(browser.get()); 307 ClickModalDialogButton(ui::MessageBoxFlags::DIALOGBUTTON_CANCEL); 308 309 // There's no real graceful way to wait for something _not_ to happen, so 310 // we just wait a short period. 311 base::PlatformThread::Sleep(TestTimeouts::action_timeout_ms()); 312 ASSERT_TRUE(IsBrowserRunning()); 313 314 CloseBrowserAsync(browser.get()); 315 ClickModalDialogButton(ui::MessageBoxFlags::DIALOGBUTTON_OK); 316 317 int exit_code = -1; 318 ASSERT_TRUE(launcher_->WaitForBrowserProcessToQuit( 319 TestTimeouts::action_max_timeout_ms(), &exit_code)); 320 EXPECT_EQ(0, exit_code); // Expect a clean shutdown. 321 } 322 323 #if defined(OS_LINUX) 324 // Fails sometimes on Linux valgrind. http://crbug.com/45675 325 #define MAYBE_BrowserCloseWithInnerFocusedFrame \ 326 FLAKY_BrowserCloseWithInnerFocusedFrame 327 #else 328 #define MAYBE_BrowserCloseWithInnerFocusedFrame \ 329 BrowserCloseWithInnerFocusedFrame 330 #endif 331 332 // Tests closing the browser and clicking OK in the beforeunload confirm dialog 333 // if an inner frame has the focus. See crbug.com/32615. 334 TEST_F(UnloadTest, MAYBE_BrowserCloseWithInnerFocusedFrame) { 335 scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); 336 ASSERT_TRUE(browser.get()); 337 338 NavigateToDataURL(INNER_FRAME_WITH_FOCUS_HTML, L"innerframewithfocus"); 339 340 CloseBrowserAsync(browser.get()); 341 ClickModalDialogButton(ui::MessageBoxFlags::DIALOGBUTTON_OK); 342 343 int exit_code = -1; 344 ASSERT_TRUE(launcher_->WaitForBrowserProcessToQuit( 345 TestTimeouts::action_max_timeout_ms(), &exit_code)); 346 EXPECT_EQ(0, exit_code); // Expect a clean shutdown. 347 } 348 349 // Tests closing the browser with a beforeunload handler that takes 350 // two seconds to run. 351 TEST_F(UnloadTest, BrowserCloseTwoSecondBeforeUnload) { 352 LoadUrlAndQuitBrowser(TWO_SECOND_BEFORE_UNLOAD_HTML, 353 L"twosecondbeforeunload"); 354 } 355 356 // Tests closing the browser on a page with an unload listener registered where 357 // the unload handler has an infinite loop. 358 TEST_F(UnloadTest, BrowserCloseInfiniteUnload) { 359 // Tests makes no sense in single-process mode since the renderer is hung. 360 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) 361 return; 362 363 LoadUrlAndQuitBrowser(INFINITE_UNLOAD_HTML, L"infiniteunload"); 364 } 365 366 #if defined(OS_WIN) 367 // Flakily fails, times out: http://crbug.com/78803 368 #define MAYBE_BrowserCloseInfiniteBeforeUnload \ 369 DISABLED_BrowserCloseInfiniteBeforeUnload 370 #else 371 #define MAYBE_BrowserCloseInfiniteBeforeUnload BrowserCloseInfiniteBeforeUnload 372 #endif 373 // Tests closing the browser with a beforeunload handler that hangs. 374 TEST_F(UnloadTest, MAYBE_BrowserCloseInfiniteBeforeUnload) { 375 // Tests makes no sense in single-process mode since the renderer is hung. 376 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) 377 return; 378 379 LoadUrlAndQuitBrowser(INFINITE_BEFORE_UNLOAD_HTML, L"infinitebeforeunload"); 380 } 381 382 // Tests closing the browser on a page with an unload listener registered where 383 // the unload handler has an infinite loop followed by an alert. 384 TEST_F(UnloadTest, BrowserCloseInfiniteUnloadAlert) { 385 // Tests makes no sense in single-process mode since the renderer is hung. 386 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) 387 return; 388 389 LoadUrlAndQuitBrowser(INFINITE_UNLOAD_ALERT_HTML, L"infiniteunloadalert"); 390 } 391 392 #if defined(OS_WIN) 393 // Flakily fails, times out: http://crbug.com/78803 394 #define MAYBE_BrowserCloseInfiniteBeforeUnloadAlert \ 395 DISABLED_BrowserCloseInfiniteBeforeUnloadAlert 396 #else 397 #define MAYBE_BrowserCloseInfiniteBeforeUnloadAlert \ 398 BrowserCloseInfiniteBeforeUnloadAlert 399 #endif 400 // Tests closing the browser with a beforeunload handler that hangs then 401 // pops up an alert. 402 TEST_F(UnloadTest, MAYBE_BrowserCloseInfiniteBeforeUnloadAlert) { 403 // Tests makes no sense in single-process mode since the renderer is hung. 404 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) 405 return; 406 407 LoadUrlAndQuitBrowser(INFINITE_BEFORE_UNLOAD_ALERT_HTML, 408 L"infinitebeforeunloadalert"); 409 } 410 411 // Tests closing the browser on a page with an unload listener registered where 412 // the unload handler has an 2 second long loop followed by an alert. 413 TEST_F(UnloadTest, BrowserCloseTwoSecondUnloadAlert) { 414 LoadUrlAndQuitBrowser(TWO_SECOND_UNLOAD_ALERT_HTML, L"twosecondunloadalert"); 415 } 416 417 // Tests closing the browser with a beforeunload handler that takes 418 // two seconds to run then pops up an alert. 419 TEST_F(UnloadTest, BrowserCloseTwoSecondBeforeUnloadAlert) { 420 LoadUrlAndQuitBrowser(TWO_SECOND_BEFORE_UNLOAD_ALERT_HTML, 421 L"twosecondbeforeunloadalert"); 422 } 423 424 #if defined(OS_MACOSX) 425 // http://crbug.com/45162 426 #define MAYBE_BrowserCloseTabWhenOtherTabHasListener \ 427 DISABLED_BrowserCloseTabWhenOtherTabHasListener 428 #elif defined(OS_WIN) 429 // http://crbug.com/45281 430 #define MAYBE_BrowserCloseTabWhenOtherTabHasListener \ 431 DISABLED_BrowserCloseTabWhenOtherTabHasListener 432 #else 433 #define MAYBE_BrowserCloseTabWhenOtherTabHasListener \ 434 BrowserCloseTabWhenOtherTabHasListener 435 #endif 436 437 // Tests that if there's a renderer process with two tabs, one of which has an 438 // unload handler, and the other doesn't, the tab that doesn't have an unload 439 // handler can be closed. 440 TEST_F(UnloadTest, MAYBE_BrowserCloseTabWhenOtherTabHasListener) { 441 NavigateToDataURL(CLOSE_TAB_WHEN_OTHER_TAB_HAS_LISTENER, L"only_one_unload"); 442 443 scoped_refptr<BrowserProxy> browser = automation()->GetBrowserWindow(0); 444 ASSERT_TRUE(browser.get()); 445 scoped_refptr<WindowProxy> window = browser->GetWindow(); 446 ASSERT_TRUE(window.get()); 447 448 gfx::Rect tab_view_bounds; 449 ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_CONTAINER, 450 &tab_view_bounds, true)); 451 // Simulate a click to force user_gesture to true; if we don't, the resulting 452 // popup will be constrained, which isn't what we want to test. 453 ASSERT_TRUE(window->SimulateOSClick(tab_view_bounds.CenterPoint(), 454 ui::EF_LEFT_BUTTON_DOWN)); 455 ASSERT_TRUE(browser->WaitForTabCountToBecome(2)); 456 457 CheckTitle(L"popup"); 458 scoped_refptr<TabProxy> popup_tab(browser->GetActiveTab()); 459 ASSERT_TRUE(popup_tab.get()); 460 EXPECT_TRUE(popup_tab->Close(true)); 461 462 ASSERT_TRUE(browser->WaitForTabCountToBecome(1)); 463 scoped_refptr<TabProxy> main_tab(browser->GetActiveTab()); 464 ASSERT_TRUE(main_tab.get()); 465 std::wstring main_title; 466 EXPECT_TRUE(main_tab->GetTabTitle(&main_title)); 467 EXPECT_EQ(std::wstring(L"only_one_unload"), main_title); 468 } 469 470 // TODO(ojan): Add tests for unload/beforeunload that have multiple tabs 471 // and multiple windows. 472