1 // Copyright (c) 2012 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.
9 #include "base/command_line.h"
10 #include "base/logging.h"
11 #include "base/process_util.h"
12 #include "base/utf_string_conversions.h"
13 #include "chrome/browser/net/url_request_mock_util.h"
14 #include "chrome/browser/ui/app_modal_dialogs/javascript_app_modal_dialog.h"
15 #include "chrome/browser/ui/app_modal_dialogs/native_app_modal_dialog.h"
16 #include "chrome/browser/ui/browser.h"
17 #include "chrome/browser/ui/browser_commands.h"
18 #include "chrome/browser/ui/browser_list.h"
19 #include "chrome/browser/ui/tabs/tab_strip_model.h"
20 #include "chrome/common/chrome_notification_types.h"
21 #include "chrome/common/chrome_switches.h"
22 #include "chrome/test/base/in_process_browser_test.h"
23 #include "chrome/test/base/ui_test_utils.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/browser/notification_service.h"
26 #include "content/public/browser/web_contents.h"
27 #include "content/public/test/browser_test_utils.h"
28 #include "content/test/net/url_request_mock_http_job.h"
29 #include "net/url_request/url_request_test_util.h"
31 using base::TimeDelta
;
32 using content::BrowserThread
;
34 const std::string NOLISTENERS_HTML
=
35 "<html><head><title>nolisteners</title></head><body></body></html>";
37 const std::string UNLOAD_HTML
=
38 "<html><head><title>unload</title></head><body>"
39 "<script>window.onunload=function(e){}</script></body></html>";
41 const std::string BEFORE_UNLOAD_HTML
=
42 "<html><head><title>beforeunload</title></head><body>"
43 "<script>window.onbeforeunload=function(e){"
44 "setTimeout('document.title=\"cancelled\"', 0);return 'foo'}</script>"
47 const std::string INNER_FRAME_WITH_FOCUS_HTML
=
48 "<html><head><title>innerframewithfocus</title></head><body>"
49 "<script>window.onbeforeunload=function(e){return 'foo'}</script>"
50 "<iframe src=\"data:text/html,<html><head><script>window.onload="
51 "function(){document.getElementById('box').focus()}</script>"
52 "<body><input id='box'></input></body></html>\"></iframe>"
55 const std::string TWO_SECOND_BEFORE_UNLOAD_HTML
=
56 "<html><head><title>twosecondbeforeunload</title></head><body>"
57 "<script>window.onbeforeunload=function(e){"
58 "var start = new Date().getTime();"
59 "while(new Date().getTime() - start < 2000){}"
61 "}</script></body></html>";
63 const std::string INFINITE_UNLOAD_HTML
=
64 "<html><head><title>infiniteunload</title></head><body>"
65 "<script>window.onunload=function(e){while(true){}}</script>"
68 const std::string INFINITE_BEFORE_UNLOAD_HTML
=
69 "<html><head><title>infinitebeforeunload</title></head><body>"
70 "<script>window.onbeforeunload=function(e){while(true){}}</script>"
73 const std::string INFINITE_UNLOAD_ALERT_HTML
=
74 "<html><head><title>infiniteunloadalert</title></head><body>"
75 "<script>window.onunload=function(e){"
78 "}</script></body></html>";
80 const std::string INFINITE_BEFORE_UNLOAD_ALERT_HTML
=
81 "<html><head><title>infinitebeforeunloadalert</title></head><body>"
82 "<script>window.onbeforeunload=function(e){"
85 "}</script></body></html>";
87 const std::string TWO_SECOND_UNLOAD_ALERT_HTML
=
88 "<html><head><title>twosecondunloadalert</title></head><body>"
89 "<script>window.onunload=function(e){"
90 "var start = new Date().getTime();"
91 "while(new Date().getTime() - start < 2000){}"
93 "}</script></body></html>";
95 const std::string TWO_SECOND_BEFORE_UNLOAD_ALERT_HTML
=
96 "<html><head><title>twosecondbeforeunloadalert</title></head><body>"
97 "<script>window.onbeforeunload=function(e){"
98 "var start = new Date().getTime();"
99 "while(new Date().getTime() - start < 2000){}"
101 "}</script></body></html>";
103 const std::string CLOSE_TAB_WHEN_OTHER_TAB_HAS_LISTENER
=
104 "<html><head><title>only_one_unload</title></head>"
105 "<body onclick=\"window.open('data:text/html,"
106 "<html><head><title>popup</title></head></body>')\" "
107 "onbeforeunload='return;'>"
110 class UnloadTest
: public InProcessBrowserTest
{
112 virtual void SetUpCommandLine(CommandLine
* command_line
) OVERRIDE
{
113 const testing::TestInfo
* const test_info
=
114 testing::UnitTest::GetInstance()->current_test_info();
115 if (strcmp(test_info
->name(),
116 "BrowserCloseTabWhenOtherTabHasListener") == 0) {
117 command_line
->AppendSwitch(switches::kDisablePopupBlocking
);
118 } else if (strcmp(test_info
->name(), "BrowserTerminateBeforeUnload") == 0) {
119 #if defined(OS_POSIX)
120 DisableSIGTERMHandling();
125 virtual void SetUpOnMainThread() OVERRIDE
{
126 BrowserThread::PostTask(
127 BrowserThread::IO
, FROM_HERE
,
128 base::Bind(&chrome_browser_net::SetUrlRequestMocksEnabled
, true));
131 void CheckTitle(const char* expected_title
) {
132 string16 expected
= ASCIIToUTF16(expected_title
);
134 browser()->tab_strip_model()->GetActiveWebContents()->GetTitle());
137 void NavigateToDataURL(const std::string
& html_content
,
138 const char* expected_title
) {
139 ui_test_utils::NavigateToURL(browser(),
140 GURL("data:text/html," + html_content
));
141 CheckTitle(expected_title
);
144 void NavigateToNolistenersFileTwice() {
145 GURL
url(content::URLRequestMockHTTPJob::GetMockUrl(
146 base::FilePath(FILE_PATH_LITERAL("title2.html"))));
147 ui_test_utils::NavigateToURL(browser(), url
);
148 CheckTitle("Title Of Awesomeness");
149 ui_test_utils::NavigateToURL(browser(), url
);
150 CheckTitle("Title Of Awesomeness");
153 // Navigates to a URL asynchronously, then again synchronously. The first
154 // load is purposely async to test the case where the user loads another
155 // page without waiting for the first load to complete.
156 void NavigateToNolistenersFileTwiceAsync() {
157 GURL
url(content::URLRequestMockHTTPJob::GetMockUrl(
158 base::FilePath(FILE_PATH_LITERAL("title2.html"))));
159 ui_test_utils::NavigateToURLWithDisposition(browser(), url
, CURRENT_TAB
, 0);
160 ui_test_utils::NavigateToURL(browser(), url
);
161 CheckTitle("Title Of Awesomeness");
164 void LoadUrlAndQuitBrowser(const std::string
& html_content
,
165 const char* expected_title
) {
166 NavigateToDataURL(html_content
, expected_title
);
167 content::WindowedNotificationObserver
window_observer(
168 chrome::NOTIFICATION_BROWSER_CLOSED
,
169 content::NotificationService::AllSources());
170 chrome::CloseWindow(browser());
171 window_observer
.Wait();
174 // If |accept| is true, simulates user clicking OK, otherwise simulates
176 void ClickModalDialogButton(bool accept
) {
177 AppModalDialog
* dialog
= ui_test_utils::WaitForAppModalDialog();
178 ASSERT_TRUE(dialog
->IsJavaScriptModalDialog());
179 JavaScriptAppModalDialog
* js_dialog
=
180 static_cast<JavaScriptAppModalDialog
*>(dialog
);
182 js_dialog
->native_dialog()->AcceptAppModalDialog();
184 js_dialog
->native_dialog()->CancelAppModalDialog();
188 // Navigate to a page with an infinite unload handler.
189 // Then two async crosssite requests to ensure
190 // we don't get confused and think we're closing the tab.
192 // This test is flaky on the valgrind UI bots. http://crbug.com/39057
193 IN_PROC_BROWSER_TEST_F(UnloadTest
, CrossSiteInfiniteUnloadAsync
) {
194 // Tests makes no sense in single-process mode since the renderer is hung.
195 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess
))
198 NavigateToDataURL(INFINITE_UNLOAD_HTML
, "infiniteunload");
199 // Must navigate to a non-data URL to trigger cross-site codepath.
200 NavigateToNolistenersFileTwiceAsync();
203 // Navigate to a page with an infinite unload handler.
204 // Then two sync crosssite requests to ensure
205 // we correctly nav to each one.
206 IN_PROC_BROWSER_TEST_F(UnloadTest
, CrossSiteInfiniteUnloadSync
) {
207 // Tests makes no sense in single-process mode since the renderer is hung.
208 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess
))
211 NavigateToDataURL(INFINITE_UNLOAD_HTML
, "infiniteunload");
212 // Must navigate to a non-data URL to trigger cross-site codepath.
213 NavigateToNolistenersFileTwice();
216 // Navigate to a page with an infinite beforeunload handler.
217 // Then two two async crosssite requests to ensure
218 // we don't get confused and think we're closing the tab.
219 // This test is flaky on the valgrind UI bots. http://crbug.com/39057 and
220 // http://crbug.com/86469
221 IN_PROC_BROWSER_TEST_F(UnloadTest
,
222 DISABLED_CrossSiteInfiniteBeforeUnloadAsync
) {
223 // Tests makes no sense in single-process mode since the renderer is hung.
224 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess
))
227 NavigateToDataURL(INFINITE_BEFORE_UNLOAD_HTML
, "infinitebeforeunload");
228 // Must navigate to a non-data URL to trigger cross-site codepath.
229 NavigateToNolistenersFileTwiceAsync();
232 // Navigate to a page with an infinite beforeunload handler.
233 // Then two two sync crosssite requests to ensure
234 // we correctly nav to each one.
235 // If this flakes, reopen bug http://crbug.com/86469.
236 IN_PROC_BROWSER_TEST_F(UnloadTest
, DISABLED_CrossSiteInfiniteBeforeUnloadSync
) {
237 // Tests makes no sense in single-process mode since the renderer is hung.
238 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess
))
241 NavigateToDataURL(INFINITE_BEFORE_UNLOAD_HTML
, "infinitebeforeunload");
242 // Must navigate to a non-data URL to trigger cross-site codepath.
243 NavigateToNolistenersFileTwice();
246 // Tests closing the browser on a page with no unload listeners registered.
247 IN_PROC_BROWSER_TEST_F(UnloadTest
, BrowserCloseNoUnloadListeners
) {
248 LoadUrlAndQuitBrowser(NOLISTENERS_HTML
, "nolisteners");
251 // Tests closing the browser on a page with an unload listener registered.
252 // Test marked as flaky in http://crbug.com/51698
253 IN_PROC_BROWSER_TEST_F(UnloadTest
, DISABLED_BrowserCloseUnload
) {
254 LoadUrlAndQuitBrowser(UNLOAD_HTML
, "unload");
257 // Tests closing the browser with a beforeunload handler and clicking
258 // OK in the beforeunload confirm dialog.
259 IN_PROC_BROWSER_TEST_F(UnloadTest
, BrowserCloseBeforeUnloadOK
) {
260 NavigateToDataURL(BEFORE_UNLOAD_HTML
, "beforeunload");
262 content::WindowedNotificationObserver
window_observer(
263 chrome::NOTIFICATION_BROWSER_CLOSED
,
264 content::NotificationService::AllSources());
265 chrome::CloseWindow(browser());
266 ClickModalDialogButton(true);
267 window_observer
.Wait();
270 // Tests closing the browser with a beforeunload handler and clicking
271 // CANCEL in the beforeunload confirm dialog.
272 // If this test flakes, reopen http://crbug.com/123110
273 IN_PROC_BROWSER_TEST_F(UnloadTest
, BrowserCloseBeforeUnloadCancel
) {
274 NavigateToDataURL(BEFORE_UNLOAD_HTML
, "beforeunload");
275 chrome::CloseWindow(browser());
277 // We wait for the title to change after cancelling the popup to ensure that
278 // in-flight IPCs from the renderer reach the browser. Otherwise the browser
279 // won't put up the beforeunload dialog because it's waiting for an ack from
281 string16 expected_title
= ASCIIToUTF16("cancelled");
282 content::TitleWatcher
title_watcher(
283 browser()->tab_strip_model()->GetActiveWebContents(), expected_title
);
284 ClickModalDialogButton(false);
285 ASSERT_EQ(expected_title
, title_watcher
.WaitAndGetTitle());
287 content::WindowedNotificationObserver
window_observer(
288 chrome::NOTIFICATION_BROWSER_CLOSED
,
289 content::NotificationService::AllSources());
290 chrome::CloseWindow(browser());
291 ClickModalDialogButton(true);
292 window_observer
.Wait();
295 // Tests terminating the browser with a beforeunload handler.
296 // Currently only ChromeOS shuts down gracefully.
297 #if defined(OS_CHROMEOS)
298 IN_PROC_BROWSER_TEST_F(UnloadTest
, BrowserTerminateBeforeUnload
) {
299 NavigateToDataURL(BEFORE_UNLOAD_HTML
, "beforeunload");
300 EXPECT_EQ(kill(base::GetCurrentProcessHandle(), SIGTERM
), 0);
304 // Tests closing the browser and clicking OK in the beforeunload confirm dialog
305 // if an inner frame has the focus.
306 // If this flakes, use http://crbug.com/32615 and http://crbug.com/45675
307 IN_PROC_BROWSER_TEST_F(UnloadTest
, BrowserCloseWithInnerFocusedFrame
) {
308 NavigateToDataURL(INNER_FRAME_WITH_FOCUS_HTML
, "innerframewithfocus");
310 content::WindowedNotificationObserver
window_observer(
311 chrome::NOTIFICATION_BROWSER_CLOSED
,
312 content::NotificationService::AllSources());
313 chrome::CloseWindow(browser());
314 ClickModalDialogButton(true);
315 window_observer
.Wait();
318 // Tests closing the browser with a beforeunload handler that takes
319 // two seconds to run.
320 IN_PROC_BROWSER_TEST_F(UnloadTest
, BrowserCloseTwoSecondBeforeUnload
) {
321 LoadUrlAndQuitBrowser(TWO_SECOND_BEFORE_UNLOAD_HTML
,
322 "twosecondbeforeunload");
325 // Tests closing the browser on a page with an unload listener registered where
326 // the unload handler has an infinite loop.
327 IN_PROC_BROWSER_TEST_F(UnloadTest
, BrowserCloseInfiniteUnload
) {
328 // Tests makes no sense in single-process mode since the renderer is hung.
329 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess
))
332 LoadUrlAndQuitBrowser(INFINITE_UNLOAD_HTML
, "infiniteunload");
335 // Tests closing the browser with a beforeunload handler that hangs.
336 // If this flakes, use http://crbug.com/78803 and http://crbug.com/86469
337 IN_PROC_BROWSER_TEST_F(UnloadTest
, DISABLED_BrowserCloseInfiniteBeforeUnload
) {
338 // Tests makes no sense in single-process mode since the renderer is hung.
339 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess
))
342 LoadUrlAndQuitBrowser(INFINITE_BEFORE_UNLOAD_HTML
, "infinitebeforeunload");
345 // Tests closing the browser on a page with an unload listener registered where
346 // the unload handler has an infinite loop followed by an alert.
347 // If this flakes, use http://crbug.com/86469
348 IN_PROC_BROWSER_TEST_F(UnloadTest
, BrowserCloseInfiniteUnloadAlert
) {
349 // Tests makes no sense in single-process mode since the renderer is hung.
350 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess
))
353 LoadUrlAndQuitBrowser(INFINITE_UNLOAD_ALERT_HTML
, "infiniteunloadalert");
356 // Tests closing the browser with a beforeunload handler that hangs then
358 // If this flakes, use http://crbug.com/78803 and http://crbug.com/86469.
359 IN_PROC_BROWSER_TEST_F(UnloadTest
,
360 DISABLED_BrowserCloseInfiniteBeforeUnloadAlert
) {
361 // Tests makes no sense in single-process mode since the renderer is hung.
362 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess
))
365 LoadUrlAndQuitBrowser(INFINITE_BEFORE_UNLOAD_ALERT_HTML
,
366 "infinitebeforeunloadalert");
369 // Tests closing the browser on a page with an unload listener registered where
370 // the unload handler has an 2 second long loop followed by an alert.
371 IN_PROC_BROWSER_TEST_F(UnloadTest
, BrowserCloseTwoSecondUnloadAlert
) {
372 LoadUrlAndQuitBrowser(TWO_SECOND_UNLOAD_ALERT_HTML
, "twosecondunloadalert");
375 // Tests closing the browser with a beforeunload handler that takes
376 // two seconds to run then pops up an alert.
377 IN_PROC_BROWSER_TEST_F(UnloadTest
, BrowserCloseTwoSecondBeforeUnloadAlert
) {
378 LoadUrlAndQuitBrowser(TWO_SECOND_BEFORE_UNLOAD_ALERT_HTML
,
379 "twosecondbeforeunloadalert");
382 // Tests that if there's a renderer process with two tabs, one of which has an
383 // unload handler, and the other doesn't, the tab that doesn't have an unload
384 // handler can be closed.
385 // If this flakes, see http://crbug.com/45162, http://crbug.com/45281 and
386 // http://crbug.com/86769.
387 IN_PROC_BROWSER_TEST_F(UnloadTest
, BrowserCloseTabWhenOtherTabHasListener
) {
388 NavigateToDataURL(CLOSE_TAB_WHEN_OTHER_TAB_HAS_LISTENER
, "only_one_unload");
390 // Simulate a click to force user_gesture to true; if we don't, the resulting
391 // popup will be constrained, which isn't what we want to test.
393 content::WindowedNotificationObserver
observer(
394 chrome::NOTIFICATION_TAB_ADDED
,
395 content::NotificationService::AllSources());
396 content::WindowedNotificationObserver
load_stop_observer(
397 content::NOTIFICATION_LOAD_STOP
,
398 content::NotificationService::AllSources());
399 content::SimulateMouseClick(
400 browser()->tab_strip_model()->GetActiveWebContents(), 0,
401 WebKit::WebMouseEvent::ButtonLeft
);
403 load_stop_observer
.Wait();
406 content::WindowedNotificationObserver
tab_close_observer(
407 content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
408 content::NotificationService::AllSources());
409 chrome::CloseTab(browser());
410 tab_close_observer
.Wait();
412 CheckTitle("only_one_unload");
415 // TODO(ojan): Add tests for unload/beforeunload that have multiple tabs
416 // and multiple windows.