1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/command_line.h"
6 #include "base/functional/bind.h"
7 #include "base/run_loop.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "build/build_config.h"
10 #include "build/chromeos_buildflags.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/ui/browser.h"
13 #include "chrome/browser/ui/browser_commands.h"
14 #include "chrome/browser/ui/browser_list.h"
15 #include "chrome/browser/ui/tabs/tab_strip_model.h"
16 #include "chrome/common/chrome_switches.h"
17 #include "chrome/test/base/in_process_browser_test.h"
18 #include "chrome/test/base/ui_test_utils.h"
19 #include "components/embedder_support/switches.h"
20 #include "components/javascript_dialogs/app_modal_dialog_controller.h"
21 #include "components/javascript_dialogs/app_modal_dialog_view.h"
22 #include "content/public/browser/browser_task_traits.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/render_frame_host.h"
25 #include "content/public/browser/web_contents.h"
26 #include "content/public/common/content_switches.h"
27 #include "content/public/test/browser_test.h"
28 #include "content/public/test/browser_test_utils.h"
29 #include "content/public/test/test_navigation_observer.h"
30 #include "net/dns/mock_host_resolver.h"
31 #include "net/test/embedded_test_server/embedded_test_server.h"
32 #include "third_party/blink/public/common/features.h"
34 using content::BrowserThread;
36 const char NOLISTENERS_HTML[] =
37 "<html><head><title>nolisteners</title></head><body></body></html>";
39 const char UNLOAD_HTML[] =
40 "<html><head><title>unload</title></head><body>"
41 "<script>window.onunload=function(e){}</script></body></html>";
43 const char BEFORE_UNLOAD_HTML[] =
44 "<html><head><title>beforeunload</title></head><body>"
45 "<script>window.onbeforeunload=function(e){"
46 "setTimeout('document.title=\"cancelled\"', 0);return 'foo'}</script>"
49 const char INNER_FRAME_WITH_FOCUS_HTML[] =
50 "<html><head><title>innerframewithfocus</title></head><body>"
51 "<script>window.onbeforeunload=function(e){return 'foo'}</script>"
52 "<iframe src=\"data:text/html,<html><head><script>window.onload="
53 "function(){document.getElementById('box').focus()}</script>"
54 "<body><input id='box'></input></body></html>\"></iframe>"
57 const char INFINITE_UNLOAD_HTML[] =
58 "<html><head><title>infiniteunload</title></head><body>"
59 "<script>window.onunload=function(e){while(true){}}</script>"
62 const char INFINITE_BEFORE_UNLOAD_HTML[] =
63 "<html><head><title>infinitebeforeunload</title></head><body>"
64 "<script>window.onbeforeunload=function(e){while(true){}}</script>"
67 const char INFINITE_UNLOAD_ALERT_HTML[] =
68 "<html><head><title>infiniteunloadalert</title></head><body>"
69 "<script>window.onunload=function(e){"
72 "}</script></body></html>";
74 const char INFINITE_BEFORE_UNLOAD_ALERT_HTML[] =
75 "<html><head><title>infinitebeforeunloadalert</title></head><body>"
76 "<script>window.onbeforeunload=function(e){"
79 "}</script></body></html>";
81 const char TWO_SECOND_UNLOAD_ALERT_HTML[] =
82 "<html><head><title>twosecondunloadalert</title></head><body>"
83 "<script>window.onunload=function(e){"
84 "var start = new Date().getTime();"
85 "while(new Date().getTime() - start < 2000){}"
87 "}</script></body></html>";
89 const char TWO_SECOND_BEFORE_UNLOAD_ALERT_HTML[] =
90 "<html><head><title>twosecondbeforeunloadalert</title></head><body>"
91 "<script>window.onbeforeunload=function(e){"
92 "var start = new Date().getTime();"
93 "while(new Date().getTime() - start < 2000){}"
95 "}</script></body></html>";
97 const char CLOSE_TAB_WHEN_OTHER_TAB_HAS_LISTENER[] =
98 "<html><head><title>only_one_unload</title></head>"
100 "function openPopup() {"
101 " var w = window.open('about:blank');"
102 " w.document.write('<html><head><title>popup</title></head></body>');"
105 "<body onclick='openPopup()' onbeforeunload='return;'>"
108 class UnloadResults {
110 UnloadResults() : successes_(0), aborts_(0) {}
112 void AddSuccess(const base::FilePath&) { successes_++; }
113 void AddAbort(const base::FilePath&) { aborts_++; }
114 void AddError(const base::FilePath&) {
115 ADD_FAILURE() << "AddError should not be called.";
118 int get_successes() { return successes_; }
119 int get_aborts() { return aborts_; }
126 class UnloadTest : public InProcessBrowserTest {
129 scoped_feature_list.InitAndEnableFeature(
130 blink::features::kBeforeunloadEventCancelByPreventDefault);
132 void SetUpCommandLine(base::CommandLine* command_line) override {
133 const testing::TestInfo* const test_info =
134 testing::UnitTest::GetInstance()->current_test_info();
135 if (strstr(test_info->name(), "BrowserCloseTabWhenOtherTabHasListener") !=
137 command_line->AppendSwitch(embedder_support::kDisablePopupBlocking);
138 } else if (strstr(test_info->name(), "BrowserTerminateBeforeUnload") !=
140 #if BUILDFLAG(IS_POSIX)
141 DisableSIGTERMHandling();
146 void SetUpOnMainThread() override {
147 host_resolver()->AddRule("*", "127.0.0.1");
150 void CheckTitle(const char* expected_title, bool wait = false) {
151 auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents();
152 std::u16string expected = base::ASCIIToUTF16(expected_title);
153 std::u16string actual;
155 actual = content::TitleWatcher(web_contents, expected).WaitAndGetTitle();
157 actual = web_contents->GetTitle();
158 EXPECT_EQ(expected, actual);
161 void NavigateToDataURL(const char* html_content, const char* expected_title) {
162 ASSERT_TRUE(ui_test_utils::NavigateToURL(
163 browser(), GURL(std::string("data:text/html,") + html_content)));
164 CheckTitle(expected_title);
167 void NavigateToNolistenersFileTwice() {
168 ASSERT_TRUE(embedded_test_server()->Start());
169 GURL url(embedded_test_server()->GetURL("/title2.html"));
170 ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
171 CheckTitle("Title Of Awesomeness");
172 ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
173 CheckTitle("Title Of Awesomeness");
176 // Navigates to a URL asynchronously, then again synchronously. The first
177 // load is purposely async to test the case where the user loads another
178 // page without waiting for the first load to complete.
179 void NavigateToNolistenersFileTwiceAsync() {
180 ASSERT_TRUE(embedded_test_server()->Start());
181 GURL url(embedded_test_server()->GetURL("/title2.html"));
182 ui_test_utils::NavigateToURLWithDisposition(
183 browser(), url, WindowOpenDisposition::CURRENT_TAB, 0);
184 ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
185 CheckTitle("Title Of Awesomeness");
188 void LoadUrlAndQuitBrowser(const char* html_content,
189 const char* expected_title) {
190 NavigateToDataURL(html_content, expected_title);
191 CloseBrowserSynchronously(browser());
194 // If |accept| is true, simulates user clicking OK, otherwise simulates
196 void ClickModalDialogButton(bool accept) {
197 javascript_dialogs::AppModalDialogController* dialog =
198 ui_test_utils::WaitForAppModalDialog();
200 dialog->view()->AcceptAppModalDialog();
202 dialog->view()->CancelAppModalDialog();
205 void PrepareForDialog(Browser* browser) {
206 for (int i = 0; i < browser->tab_strip_model()->count(); i++) {
207 content::PrepContentsForBeforeUnloadTest(
208 browser->tab_strip_model()->GetWebContentsAt(i));
212 void CloseBrowsersVerifyUnloadSuccess(bool force) {
213 UnloadResults unload_results;
214 BrowserList::CloseAllBrowsersWithProfile(
215 browser()->profile(),
216 base::BindRepeating(&UnloadResults::AddSuccess,
217 base::Unretained(&unload_results)),
218 base::BindRepeating(&UnloadResults::AddAbort,
219 base::Unretained(&unload_results)),
221 ui_test_utils::WaitForBrowserToClose();
222 EXPECT_EQ(1, unload_results.get_successes());
223 EXPECT_EQ(0, unload_results.get_aborts());
226 // The test harness cannot close the window automatically, because it requires
227 // confirmation. We close the window manually instead.
228 void ManuallyCloseWindow() {
229 chrome::CloseWindow(browser());
230 ClickModalDialogButton(true);
231 ui_test_utils::WaitForBrowserToClose();
234 const std::string GenerateDataURL(std::string listener_html,
235 bool is_onbeforeunload = true) {
236 std::string listener =
238 ? "window.onbeforeunload=function(event){"
239 "setTimeout('document.title=\"cancelled\"', "
242 : "window.addEventListener('beforeunload', function(event){"
243 "setTimeout('document.title=\"cancelled\"', "
245 listener_html + "})";
247 "<html><head><title>beforeunload</title></head><body>"
256 base::test::ScopedFeatureList scoped_feature_list;
259 // Navigate to a page with an infinite unload handler.
260 // Then two async crosssite requests to ensure
261 // we don't get confused and think we're closing the tab.
263 // This test is flaky on the valgrind UI bots. http://crbug.com/39057
264 IN_PROC_BROWSER_TEST_F(UnloadTest, CrossSiteInfiniteUnloadAsync) {
265 // Tests makes no sense in single-process mode since the renderer is hung.
266 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
267 switches::kSingleProcess))
270 NavigateToDataURL(INFINITE_UNLOAD_HTML, "infiniteunload");
271 // Must navigate to a non-data URL to trigger cross-site codepath.
272 NavigateToNolistenersFileTwiceAsync();
275 // Navigate to a page with an infinite unload handler.
276 // Then two sync crosssite requests to ensure
277 // we correctly nav to each one.
278 IN_PROC_BROWSER_TEST_F(UnloadTest, CrossSiteInfiniteUnloadSync) {
279 // Tests makes no sense in single-process mode since the renderer is hung.
280 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
281 switches::kSingleProcess))
284 NavigateToDataURL(INFINITE_UNLOAD_HTML, "infiniteunload");
285 // Must navigate to a non-data URL to trigger cross-site codepath.
286 NavigateToNolistenersFileTwice();
289 // Navigate to a page with an infinite beforeunload handler.
290 // Then two two async crosssite requests to ensure
291 // we don't get confused and think we're closing the tab.
292 // This test is flaky on the valgrind UI bots. http://crbug.com/39057 and
293 // http://crbug.com/86469
294 IN_PROC_BROWSER_TEST_F(UnloadTest, CrossSiteInfiniteBeforeUnloadAsync) {
295 // Tests makes no sense in single-process mode since the renderer is hung.
296 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
297 switches::kSingleProcess))
300 NavigateToDataURL(INFINITE_BEFORE_UNLOAD_HTML, "infinitebeforeunload");
301 // Must navigate to a non-data URL to trigger cross-site codepath.
302 NavigateToNolistenersFileTwiceAsync();
305 // Navigate to a page with an infinite beforeunload handler.
306 // Then two two sync crosssite requests to ensure
307 // we correctly nav to each one.
308 // Flaky on Win, Linux, and Mac; http://crbug.com/462671.
309 IN_PROC_BROWSER_TEST_F(UnloadTest, DISABLED_CrossSiteInfiniteBeforeUnloadSync) {
310 // Tests makes no sense in single-process mode since the renderer is hung.
311 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
312 switches::kSingleProcess))
315 NavigateToDataURL(INFINITE_BEFORE_UNLOAD_HTML, "infinitebeforeunload");
316 // Must navigate to a non-data URL to trigger cross-site codepath.
317 NavigateToNolistenersFileTwice();
320 // Tests closing the browser on a page with no unload listeners registered.
321 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseNoUnloadListeners) {
322 LoadUrlAndQuitBrowser(NOLISTENERS_HTML, "nolisteners");
325 // Tests closing the browser on a page with an unload listener registered.
326 // Test marked as flaky in http://crbug.com/51698
327 IN_PROC_BROWSER_TEST_F(UnloadTest, DISABLED_BrowserCloseUnload) {
328 LoadUrlAndQuitBrowser(UNLOAD_HTML, "unload");
331 // Tests closing the browser with a beforeunload handler and clicking
332 // OK in the beforeunload confirm dialog.
333 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseBeforeUnloadOK) {
334 NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
335 PrepareForDialog(browser());
337 chrome::CloseWindow(browser());
338 ClickModalDialogButton(true);
339 ui_test_utils::WaitForBrowserToClose();
342 // Tests closing the browser with a beforeunload handler and clicking
343 // CANCEL in the beforeunload confirm dialog.
344 // If this test flakes, reopen http://crbug.com/123110
345 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseBeforeUnloadCancel) {
346 NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
347 PrepareForDialog(browser());
348 chrome::CloseWindow(browser());
350 // We wait for the title to change after cancelling the closure of browser
351 // window, to ensure that in-flight IPCs from the renderer reach the browser.
352 // Otherwise the browser won't put up the beforeunload dialog because it's
353 // waiting for an ack from the renderer.
354 std::u16string expected_title = u"cancelled";
355 content::TitleWatcher title_watcher(
356 browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
357 ClickModalDialogButton(false);
358 ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
360 ManuallyCloseWindow();
363 // Tests closing the browser by BrowserList::CloseAllBrowsersWithProfile,
364 // on a page with no unload listeners registered.
365 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserListCloseNoUnloadListeners) {
366 NavigateToDataURL(NOLISTENERS_HTML, "nolisteners");
368 CloseBrowsersVerifyUnloadSuccess(false);
371 // Tests closing the browser by BrowserList::CloseAllBrowsersWithProfile, with a
372 // beforeunload handler and clicking Leave in the beforeunload confirm dialog.
373 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserListCloseBeforeUnloadOK) {
374 NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
375 PrepareForDialog(browser());
377 UnloadResults unload_results;
378 BrowserList::CloseAllBrowsersWithProfile(
379 browser()->profile(),
380 base::BindRepeating(&UnloadResults::AddSuccess,
381 base::Unretained(&unload_results)),
382 base::BindRepeating(&UnloadResults::AddAbort,
383 base::Unretained(&unload_results)),
385 ClickModalDialogButton(true);
386 ui_test_utils::WaitForBrowserToClose();
387 EXPECT_EQ(1, unload_results.get_successes());
388 EXPECT_EQ(0, unload_results.get_aborts());
391 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserListForceCloseNoUnloadListeners) {
392 NavigateToDataURL(NOLISTENERS_HTML, "nolisteners");
394 CloseBrowsersVerifyUnloadSuccess(true);
397 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserListForceCloseWithBeforeUnload) {
398 NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
400 CloseBrowsersVerifyUnloadSuccess(true);
403 // Tests closing the browser by BrowserList::CloseAllBrowsersWithProfile, with a
404 // beforeunload handler and clicking Stay in the beforeunload confirm dialog.
405 // TODO(crbug.com/1372484): Flaky on Mac.
406 #if BUILDFLAG(IS_MAC)
407 #define MAYBE_BrowserListCloseBeforeUnloadCancel \
408 DISABLED_BrowserListCloseBeforeUnloadCancel
410 #define MAYBE_BrowserListCloseBeforeUnloadCancel \
411 BrowserListCloseBeforeUnloadCancel
413 IN_PROC_BROWSER_TEST_F(UnloadTest, MAYBE_BrowserListCloseBeforeUnloadCancel) {
414 NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
415 PrepareForDialog(browser());
417 UnloadResults unload_results;
418 BrowserList::CloseAllBrowsersWithProfile(
419 browser()->profile(),
420 base::BindRepeating(&UnloadResults::AddSuccess,
421 base::Unretained(&unload_results)),
422 base::BindRepeating(&UnloadResults::AddAbort,
423 base::Unretained(&unload_results)),
426 // We wait for the title to change after cancelling the closure of browser
427 // window, to ensure that in-flight IPCs from the renderer reach the browser.
428 // Otherwise the browser won't put up the beforeunload dialog because it's
429 // waiting for an ack from the renderer.
430 std::u16string expected_title = u"cancelled";
431 content::TitleWatcher title_watcher(
432 browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
433 ClickModalDialogButton(false);
434 ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
436 EXPECT_EQ(0, unload_results.get_successes());
437 EXPECT_EQ(1, unload_results.get_aborts());
439 ManuallyCloseWindow();
442 // Tests double calls to BrowserList::CloseAllBrowsersWithProfile, with a
443 // beforeunload handler and clicking Leave in the beforeunload confirm dialog.
444 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserListDoubleCloseBeforeUnloadOK) {
445 NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
446 PrepareForDialog(browser());
448 UnloadResults unload_results;
449 BrowserList::CloseAllBrowsersWithProfile(
450 browser()->profile(),
451 base::BindRepeating(&UnloadResults::AddSuccess,
452 base::Unretained(&unload_results)),
453 base::BindRepeating(&UnloadResults::AddAbort,
454 base::Unretained(&unload_results)),
456 BrowserList::CloseAllBrowsersWithProfile(
457 browser()->profile(),
458 base::BindRepeating(&UnloadResults::AddSuccess,
459 base::Unretained(&unload_results)),
460 base::BindRepeating(&UnloadResults::AddAbort,
461 base::Unretained(&unload_results)),
463 ClickModalDialogButton(true);
464 ui_test_utils::WaitForBrowserToClose();
465 EXPECT_EQ(1, unload_results.get_successes());
466 EXPECT_EQ(0, unload_results.get_aborts());
469 // Tests double calls to BrowserList::CloseAllBrowsersWithProfile, with a
470 // beforeunload handler and clicking Stay in the beforeunload confirm dialog.
471 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserListDoubleCloseBeforeUnloadCancel) {
472 NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
473 PrepareForDialog(browser());
475 UnloadResults unload_results;
476 BrowserList::CloseAllBrowsersWithProfile(
477 browser()->profile(),
478 base::BindRepeating(&UnloadResults::AddSuccess,
479 base::Unretained(&unload_results)),
480 base::BindRepeating(&UnloadResults::AddAbort,
481 base::Unretained(&unload_results)),
483 BrowserList::CloseAllBrowsersWithProfile(
484 browser()->profile(),
485 base::BindRepeating(&UnloadResults::AddSuccess,
486 base::Unretained(&unload_results)),
487 base::BindRepeating(&UnloadResults::AddAbort,
488 base::Unretained(&unload_results)),
491 // We wait for the title to change after cancelling the closure of browser
492 // window, to ensure that in-flight IPCs from the renderer reach the browser.
493 // Otherwise the browser won't put up the beforeunload dialog because it's
494 // waiting for an ack from the renderer.
495 std::u16string expected_title = u"cancelled";
496 content::TitleWatcher title_watcher(
497 browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
498 ClickModalDialogButton(false);
499 ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
501 EXPECT_EQ(0, unload_results.get_successes());
502 EXPECT_EQ(1, unload_results.get_aborts());
504 ManuallyCloseWindow();
507 // Tests closing the browser by BrowserList::CloseAllBrowsersWithProfile, with
508 // a null success callback, a beforeunload handler and clicking Leave in the
509 // beforeunload confirm dialog. The test succeed if no crash happens.
510 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserListCloseBeforeUnloadNullCallbackOk) {
511 NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
512 PrepareForDialog(browser());
514 UnloadResults unload_results;
515 BrowserList::CloseAllBrowsersWithProfile(browser()->profile(),
516 BrowserList::CloseCallback(),
517 BrowserList::CloseCallback(), false);
518 ClickModalDialogButton(true);
519 ui_test_utils::WaitForBrowserToClose();
522 // Tests closing the browser by BrowserList::CloseAllBrowsersWithProfile, with
523 // a null failure callback, a beforeunload handler and clicking Stay in the
524 // beforeunload confirm dialog. The test succeed if no crash happens.
525 IN_PROC_BROWSER_TEST_F(UnloadTest,
526 BrowserListCloseBeforeUnloadNullCallbackCancel) {
527 NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
528 PrepareForDialog(browser());
530 UnloadResults unload_results;
531 BrowserList::CloseAllBrowsersWithProfile(browser()->profile(),
532 BrowserList::CloseCallback(),
533 BrowserList::CloseCallback(), false);
535 // We wait for the title to change after cancelling the closure of browser
536 // window, to ensure that in-flight IPCs from the renderer reach the browser.
537 // Otherwise the browser won't put up the beforeunload dialog because it's
538 // waiting for an ack from the renderer.
539 std::u16string expected_title = u"cancelled";
540 content::TitleWatcher title_watcher(
541 browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
542 ClickModalDialogButton(false);
543 ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
545 ManuallyCloseWindow();
548 // Tests terminating the browser with a beforeunload handler.
549 // Currently only ChromeOS shuts down gracefully.
550 #if BUILDFLAG(IS_CHROMEOS_ASH)
551 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserTerminateBeforeUnload) {
552 NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
553 EXPECT_EQ(kill(base::GetCurrentProcessHandle(), SIGTERM), 0);
557 // Tests closing the browser and clicking OK in the beforeunload confirm dialog
558 // if an inner frame has the focus.
559 // If this flakes, use http://crbug.com/32615 and http://crbug.com/45675
560 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseWithInnerFocusedFrame) {
561 NavigateToDataURL(INNER_FRAME_WITH_FOCUS_HTML, "innerframewithfocus");
562 PrepareForDialog(browser());
564 ManuallyCloseWindow();
567 // Tests closing the browser with a beforeunload handler that takes forever
568 // by running an infinite loop.
569 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseInfiniteBeforeUnload) {
570 LoadUrlAndQuitBrowser(INFINITE_BEFORE_UNLOAD_HTML,
571 "infinitebeforeunload");
574 // Tests closing the browser on a page with an unload listener registered where
575 // the unload handler has an infinite loop.
576 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseInfiniteUnload) {
577 // Tests makes no sense in single-process mode since the renderer is hung.
578 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
579 switches::kSingleProcess))
582 LoadUrlAndQuitBrowser(INFINITE_UNLOAD_HTML, "infiniteunload");
585 // Tests closing the browser on a page with an unload listener registered where
586 // the unload handler has an infinite loop followed by an alert.
587 // If this flakes, use http://crbug.com/86469
588 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseInfiniteUnloadAlert) {
589 // Tests makes no sense in single-process mode since the renderer is hung.
590 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
591 switches::kSingleProcess))
594 LoadUrlAndQuitBrowser(INFINITE_UNLOAD_ALERT_HTML, "infiniteunloadalert");
597 // Tests closing the browser with a beforeunload handler that hangs then
599 // If this flakes, use http://crbug.com/78803 and http://crbug.com/86469.
600 IN_PROC_BROWSER_TEST_F(UnloadTest,
601 DISABLED_BrowserCloseInfiniteBeforeUnloadAlert) {
602 // Tests makes no sense in single-process mode since the renderer is hung.
603 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
604 switches::kSingleProcess))
607 LoadUrlAndQuitBrowser(INFINITE_BEFORE_UNLOAD_ALERT_HTML,
608 "infinitebeforeunloadalert");
611 // Tests closing the browser on a page with an unload listener registered where
612 // the unload handler has an 2 second long loop followed by an alert.
613 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseTwoSecondUnloadAlert) {
614 LoadUrlAndQuitBrowser(TWO_SECOND_UNLOAD_ALERT_HTML, "twosecondunloadalert");
617 // Tests closing the browser with a beforeunload handler that takes
618 // two seconds to run then pops up an alert.
619 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseTwoSecondBeforeUnloadAlert) {
620 LoadUrlAndQuitBrowser(TWO_SECOND_BEFORE_UNLOAD_ALERT_HTML,
621 "twosecondbeforeunloadalert");
624 // Tests that if there's a renderer process with two tabs, one of which has an
625 // unload handler, and the other doesn't, the tab that doesn't have an unload
626 // handler can be closed.
627 // If this flakes, see http://crbug.com/45162, http://crbug.com/45281 and
628 // http://crbug.com/86769.
629 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseTabWhenOtherTabHasListener) {
630 NavigateToDataURL(CLOSE_TAB_WHEN_OTHER_TAB_HAS_LISTENER, "only_one_unload");
632 // Simulate a click to force user_gesture to true; if we don't, the resulting
633 // popup will be constrained, which isn't what we want to test.
635 ui_test_utils::TabAddedWaiter tab_add(browser());
636 content::SimulateMouseClick(
637 browser()->tab_strip_model()->GetActiveWebContents(), 0,
638 blink::WebMouseEvent::Button::kLeft);
640 // Need to wait for the title, because the initial page (about:blank) can stop
641 // loading before the click handler calls document.write.
642 CheckTitle("popup", true);
644 content::WebContentsDestroyedWatcher destroyed_watcher(
645 browser()->tab_strip_model()->GetActiveWebContents());
646 chrome::CloseTab(browser());
647 destroyed_watcher.Wait();
649 CheckTitle("only_one_unload");
652 // Tests that visibilitychange is only dispatched once on tab close.
653 IN_PROC_BROWSER_TEST_F(UnloadTest, VisibilityChangeOnlyDispatchedOnce) {
654 EXPECT_TRUE(embedded_test_server()->Start());
655 // Start on a.com and open a popup to another page in a.com.
656 GURL opener_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
657 ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), opener_url));
658 content::WebContents* opener_contents =
659 browser()->tab_strip_model()->GetActiveWebContents();
661 GURL popup_url(embedded_test_server()->GetURL("a.com", "/title2.html"));
662 content::TestNavigationObserver popup_observer(nullptr);
663 popup_observer.StartWatchingNewWebContents();
665 ExecJs(opener_contents, "window.open('" + popup_url.spec() + "');"));
666 popup_observer.Wait();
667 ASSERT_EQ(2, browser()->tab_strip_model()->count());
668 content::WebContents* popup_contents =
669 browser()->tab_strip_model()->GetActiveWebContents();
670 ASSERT_NE(opener_contents, popup_contents);
671 content::RenderFrameHost* popup_rfh = popup_contents->GetPrimaryMainFrame();
673 // In the popup, add a visibilitychange handler that ensures we only see the
674 // visibilitychange event fired once on tab close.
675 EXPECT_TRUE(ExecJs(popup_rfh, R"(
676 localStorage.setItem('visibilitychange_storage', 'not_dispatched');
677 var dispatched_visibilitychange = false;
678 document.onvisibilitychange = function(e) {
679 if (dispatched_visibilitychange) {
680 // We shouldn't dispatch visibilitychange more than once.
681 localStorage.setItem('visibilitychange_storage',
682 'dispatched_more_than_once');
683 } else if (document.visibilityState != 'hidden') {
684 // We should dispatch the event when the visibilityState is
686 localStorage.setItem('visibilitychange_storage', 'not_hidden');
688 localStorage.setItem('visibilitychange_storage',
691 dispatched_visibilitychange = true;
695 content::WebContentsDestroyedWatcher destroyed_watcher(popup_contents);
696 EXPECT_TRUE(ExecJs(popup_contents, "window.close();"));
697 destroyed_watcher.Wait();
699 // Check that we've only dispatched visibilitychange once.
700 EXPECT_EQ("dispatched_once",
701 EvalJs(opener_contents,
702 "localStorage.getItem('visibilitychange_storage')"));
705 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserListForceCloseAfterNormalClose) {
706 NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
708 UnloadResults unload_results;
709 BrowserList::CloseAllBrowsersWithProfile(
710 browser()->profile(),
711 base::BindRepeating(&UnloadResults::AddSuccess,
712 base::Unretained(&unload_results)),
713 base::BindRepeating(&UnloadResults::AddAbort,
714 base::Unretained(&unload_results)),
716 BrowserList::CloseAllBrowsersWithProfile(
717 browser()->profile(),
718 base::BindRepeating(&UnloadResults::AddSuccess,
719 base::Unretained(&unload_results)),
720 base::BindRepeating(&UnloadResults::AddAbort,
721 base::Unretained(&unload_results)),
723 ui_test_utils::WaitForBrowserToClose();
724 EXPECT_EQ(1, unload_results.get_successes());
725 EXPECT_EQ(0, unload_results.get_aborts());
728 // Tests that a cross-site iframe runs its beforeunload handler when closing
729 // the browser. See https://crbug.com/853021.
730 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseWithCrossSiteIframe) {
731 ASSERT_TRUE(embedded_test_server()->Start());
733 // Navigate to a page with an iframe.
734 GURL main_url(embedded_test_server()->GetURL("a.com", "/iframe.html"));
735 ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
737 // Navigate iframe cross-site.
738 GURL frame_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
739 content::WebContents* web_contents =
740 browser()->tab_strip_model()->GetActiveWebContents();
741 EXPECT_TRUE(NavigateIframeToURL(web_contents, "test", frame_url));
743 // Install a dialog-showing beforeunload handler in the iframe.
744 content::RenderFrameHost* child =
745 ChildFrameAt(web_contents->GetPrimaryMainFrame(), 0);
746 EXPECT_TRUE(ExecJs(child, "window.onbeforeunload = () => { return 'x' };"));
748 // Close the browser and make sure the beforeunload dialog is shown and can
750 PrepareForDialog(browser());
751 ManuallyCloseWindow();
754 // Tests that a same-site iframe runs its beforeunload handler when closing the
755 // browser. See https://crbug.com/1010456.
756 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseWithSameSiteIframe) {
757 ASSERT_TRUE(embedded_test_server()->Start());
759 // Navigate to a page with a same-site iframe.
760 GURL main_url(embedded_test_server()->GetURL("a.com", "/iframe.html"));
761 ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
762 content::WebContents* web_contents =
763 browser()->tab_strip_model()->GetActiveWebContents();
764 content::RenderFrameHost* child =
765 ChildFrameAt(web_contents->GetPrimaryMainFrame(), 0);
766 EXPECT_EQ(child->GetSiteInstance(),
767 web_contents->GetPrimaryMainFrame()->GetSiteInstance());
769 // Install a dialog-showing beforeunload handler in the iframe.
770 EXPECT_TRUE(ExecJs(child, "window.onbeforeunload = () => { return 'x' };"));
772 // Close the browser and make sure the beforeunload dialog is shown and can
774 PrepareForDialog(browser());
775 ManuallyCloseWindow();
778 // Tests closing the browser with onbeforeunload handler and
779 // event.preventDefault() will prompt confirmation dialog
780 IN_PROC_BROWSER_TEST_F(UnloadTest, OnBeforeUnloadCancelByPreventDefault) {
782 GenerateDataURL("event.preventDefault()", /*is_onbeforeunload=*/true);
783 NavigateToDataURL(html.c_str(), "beforeunload");
784 PrepareForDialog(browser());
785 chrome::CloseWindow(browser());
787 // We wait for the title to change after cancelling the closure of browser
788 // window, to ensure that in-flight IPCs from the renderer reach the browser.
789 // Otherwise the browser won't put up the beforeunload dialog because it's
790 // waiting for an ack from the renderer.
791 std::u16string expected_title = u"cancelled";
792 content::TitleWatcher title_watcher(
793 browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
794 ClickModalDialogButton(false);
795 ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
797 ManuallyCloseWindow();
800 // Tests closing the browser with onbeforeunload handler and
801 // setting returnValue will prompt confirmation dialog
802 IN_PROC_BROWSER_TEST_F(UnloadTest, OnBeforeUnloadCancelByReturnValue) {
803 std::string html = GenerateDataURL("event.returnValue = 'hello world'",
804 /*is_onbeforeunload=*/true);
805 NavigateToDataURL(html.c_str(), "beforeunload");
806 PrepareForDialog(browser());
807 chrome::CloseWindow(browser());
809 // We wait for the title to change after cancelling the closure of browser
810 // window, to ensure that in-flight IPCs from the renderer reach the browser.
811 // Otherwise the browser won't put up the beforeunload dialog because it's
812 // waiting for an ack from the renderer.
813 std::u16string expected_title = u"cancelled";
814 content::TitleWatcher title_watcher(
815 browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
816 ClickModalDialogButton(false);
817 ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
819 ManuallyCloseWindow();
822 // Tests closing the browser with onbeforeunload handler and
823 // setting returnValue empty string will not prompt confirmation dialog
824 IN_PROC_BROWSER_TEST_F(UnloadTest, OnBeforeUnloadCancelByReturnValueEmpty) {
825 std::string html = GenerateDataURL("event.returnValue = ''",
826 /*is_onbeforeunload=*/true);
827 NavigateToDataURL(html.c_str(), "beforeunload");
829 CloseBrowsersVerifyUnloadSuccess(false);
832 // Tests closing the browser with onbeforeunload handler and
833 // having return value will prompt confirmation dialog
834 IN_PROC_BROWSER_TEST_F(UnloadTest, OnBeforeUnloadCancelByReturn) {
836 GenerateDataURL("return 'hello world'", /*is_onbeforeunload=*/true);
837 NavigateToDataURL(html.c_str(), "beforeunload");
838 PrepareForDialog(browser());
839 chrome::CloseWindow(browser());
841 // We wait for the title to change after cancelling the closure of browser
842 // window, to ensure that in-flight IPCs from the renderer reach the browser.
843 // Otherwise the browser won't put up the beforeunload dialog because it's
844 // waiting for an ack from the renderer.
845 std::u16string expected_title = u"cancelled";
846 content::TitleWatcher title_watcher(
847 browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
848 ClickModalDialogButton(false);
849 ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
851 ManuallyCloseWindow();
854 // Tests closing the browser with onbeforeunload handler and
855 // returning empty string will prompt confirmation dialog
856 IN_PROC_BROWSER_TEST_F(UnloadTest, OnBeforeUnloadCancelByReturnEmpty) {
857 std::string html = GenerateDataURL("return ''",
858 /*is_onbeforeunload=*/true);
859 NavigateToDataURL(html.c_str(), "beforeunload");
860 PrepareForDialog(browser());
861 chrome::CloseWindow(browser());
863 // We wait for the title to change after cancelling the closure of browser
864 // window, to ensure that in-flight IPCs from the renderer reach the browser.
865 // Otherwise the browser won't put up the beforeunload dialog because it's
866 // waiting for an ack from the renderer.
867 std::u16string expected_title = u"cancelled";
868 content::TitleWatcher title_watcher(
869 browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
870 ClickModalDialogButton(false);
871 ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
873 ManuallyCloseWindow();
876 // Tests closing the browser with addEventListener('beforeunload') handler and
877 // event.preventDefault() will prompt confirmation dialog
878 IN_PROC_BROWSER_TEST_F(UnloadTest, BeforeUnloadListenerCancelByPreventDefault) {
880 GenerateDataURL("event.preventDefault()", /*is_onbeforeunload=*/false);
881 NavigateToDataURL(html.c_str(), "beforeunload");
882 PrepareForDialog(browser());
883 chrome::CloseWindow(browser());
885 // We wait for the title to change after cancelling the closure of browser
886 // window, to ensure that in-flight IPCs from the renderer reach the browser.
887 // Otherwise the browser won't put up the beforeunload dialog because it's
888 // waiting for an ack from the renderer.
889 std::u16string expected_title = u"cancelled";
890 content::TitleWatcher title_watcher(
891 browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
892 ClickModalDialogButton(false);
893 ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
895 ManuallyCloseWindow();
898 // Tests closing the browser with addEventListener('beforeunload') handler and
899 // setting returnValue will prompt confirmation dialog
900 IN_PROC_BROWSER_TEST_F(UnloadTest, BeforeUnloadListenerCancelByReturnValue) {
901 std::string html = GenerateDataURL("event.returnValue = 'hello world'",
902 /*is_onbeforeunload=*/false);
903 NavigateToDataURL(html.c_str(), "beforeunload");
904 PrepareForDialog(browser());
905 chrome::CloseWindow(browser());
907 // We wait for the title to change after cancelling the closure of browser
908 // window, to ensure that in-flight IPCs from the renderer reach the browser.
909 // Otherwise the browser won't put up the beforeunload dialog because it's
910 // waiting for an ack from the renderer.
911 std::u16string expected_title = u"cancelled";
912 content::TitleWatcher title_watcher(
913 browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
914 ClickModalDialogButton(false);
915 ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
917 ManuallyCloseWindow();
920 // Tests closing the browser with addEventListener('beforeunload') handler and
921 // setting returnValue empty string will not prompt confirmation dialog
922 IN_PROC_BROWSER_TEST_F(UnloadTest,
923 BeforeUnloadListenerCancelByReturnValueEmpty) {
924 std::string html = GenerateDataURL("event.returnValue = ''",
925 /*is_onbeforeunload=*/false);
926 NavigateToDataURL(html.c_str(), "beforeunload");
928 CloseBrowsersVerifyUnloadSuccess(false);
931 // Tests closing the browser with addEventListener('beforeunload') handler and
932 // having return value will _not_ prompt confirmation dialog
933 // TODO(crbug/809277) Change this test if spec changes
934 IN_PROC_BROWSER_TEST_F(UnloadTest, BeforeUnloadListenerCancelByReturn) {
936 GenerateDataURL("return 'hello world'", /*is_onbeforeunload=*/false);
937 NavigateToDataURL(html.c_str(), "beforeunload");
939 CloseBrowsersVerifyUnloadSuccess(false);
942 // Tests closing the browser with addEventListener('beforeunload') handler and
943 // returning empty string will not prompt confirmation dialog
944 IN_PROC_BROWSER_TEST_F(UnloadTest, BeforeUnloadListenerCancelByReturnEmpty) {
945 std::string html = GenerateDataURL("return ''",
946 /*is_onbeforeunload=*/false);
947 NavigateToDataURL(html.c_str(), "beforeunload");
949 CloseBrowsersVerifyUnloadSuccess(false);
952 // TODO(crbug/866818): Remove below test when feature
953 // BeforeunloadEventCancelByPreventDefault is fully stable.
954 class UnloadTestCancelByPreventDefaultDisabled : public UnloadTest {
956 UnloadTestCancelByPreventDefaultDisabled() {
957 scoped_feature_list.InitAndDisableFeature(
958 blink::features::kBeforeunloadEventCancelByPreventDefault);
962 base::test::ScopedFeatureList scoped_feature_list;
965 // Tests closing the browser with onbeforeunload handler and
966 // event.preventDefault() will not prompt confirmation dialog when
967 // BeforeunloadEventCancelByPreventDefault is disabled.
968 IN_PROC_BROWSER_TEST_F(UnloadTestCancelByPreventDefaultDisabled,
969 OnBeforeUnloadPreventDefault) {
971 GenerateDataURL("event.preventDefault()", /*is_onbeforeunload=*/true);
972 NavigateToDataURL(html.c_str(), "beforeunload");
974 CloseBrowsersVerifyUnloadSuccess(false);
977 // Tests closing the browser with onbeforeunload handler and
978 // event.returnValue = "" will prompt confirmation dialog when
979 // BeforeunloadEventCancelByPreventDefault is disabled.
980 IN_PROC_BROWSER_TEST_F(UnloadTestCancelByPreventDefaultDisabled,
981 OnBeforeUnloadEmptyString) {
982 std::string html = GenerateDataURL("event.returnValue = ''",
983 /*is_onbeforeunload=*/true);
984 NavigateToDataURL(html.c_str(), "beforeunload");
985 PrepareForDialog(browser());
986 chrome::CloseWindow(browser());
988 // We wait for the title to change after cancelling the closure of browser
989 // window, to ensure that in-flight IPCs from the renderer reach the browser.
990 // Otherwise the browser won't put up the beforeunload dialog because it's
991 // waiting for an ack from the renderer.
992 std::u16string expected_title = u"cancelled";
993 content::TitleWatcher title_watcher(
994 browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
995 ClickModalDialogButton(false);
996 ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
998 ManuallyCloseWindow();
1001 // Tests closing the browser with addEventListener('beforeunload') handler and
1002 // event.preventDefault() will not prompt confirmation dialog when
1003 // BeforeunloadEventCancelByPreventDefault is disabled.
1004 IN_PROC_BROWSER_TEST_F(UnloadTestCancelByPreventDefaultDisabled,
1005 BeforeUnloadPreventDefault) {
1007 GenerateDataURL("event.preventDefault()", /*is_onbeforeunload=*/true);
1008 NavigateToDataURL(html.c_str(), "beforeunload");
1010 CloseBrowsersVerifyUnloadSuccess(false);
1013 // Tests closing the browser with addEventListener('beforeunload') handler and
1014 // event.returnValue = "" will prompt confirmation dialog when
1015 // BeforeunloadEventCancelByPreventDefault is disabled.
1016 IN_PROC_BROWSER_TEST_F(UnloadTestCancelByPreventDefaultDisabled,
1017 BeforeUnloadEmptyString) {
1018 std::string html = GenerateDataURL("event.returnValue = ''",
1019 /*is_onbeforeunload=*/false);
1020 NavigateToDataURL(html.c_str(), "beforeunload");
1021 PrepareForDialog(browser());
1022 chrome::CloseWindow(browser());
1024 // We wait for the title to change after cancelling the closure of browser
1025 // window, to ensure that in-flight IPCs from the renderer reach the browser.
1026 // Otherwise the browser won't put up the beforeunload dialog because it's
1027 // waiting for an ack from the renderer.
1028 std::u16string expected_title = u"cancelled";
1029 content::TitleWatcher title_watcher(
1030 browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
1031 ClickModalDialogButton(false);
1032 ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
1034 ManuallyCloseWindow();
1037 // TODO(ojan): Add tests for unload/beforeunload that have multiple tabs
1038 // and multiple windows.