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.
4 
5 #if defined(OS_POSIX)
6 #include <signal.h>
7 #endif
8 
9 #include "base/bind.h"
10 #include "base/command_line.h"
11 #include "base/run_loop.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/task/post_task.h"
14 #include "build/build_config.h"
15 #include "chrome/browser/profiles/profile.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_switches.h"
21 #include "chrome/test/base/in_process_browser_test.h"
22 #include "chrome/test/base/ui_test_utils.h"
23 #include "components/embedder_support/switches.h"
24 #include "components/javascript_dialogs/app_modal_dialog_controller.h"
25 #include "components/javascript_dialogs/app_modal_dialog_view.h"
26 #include "content/public/browser/browser_task_traits.h"
27 #include "content/public/browser/browser_thread.h"
28 #include "content/public/browser/render_frame_host.h"
29 #include "content/public/browser/web_contents.h"
30 #include "content/public/common/content_switches.h"
31 #include "content/public/test/browser_test.h"
32 #include "content/public/test/browser_test_utils.h"
33 #include "net/dns/mock_host_resolver.h"
34 #include "net/test/embedded_test_server/embedded_test_server.h"
35 
36 using base::TimeDelta;
37 using content::BrowserThread;
38 
39 const char NOLISTENERS_HTML[] =
40     "<html><head><title>nolisteners</title></head><body></body></html>";
41 
42 const char UNLOAD_HTML[] =
43     "<html><head><title>unload</title></head><body>"
44     "<script>window.onunload=function(e){}</script></body></html>";
45 
46 const char BEFORE_UNLOAD_HTML[] =
47     "<html><head><title>beforeunload</title></head><body>"
48     "<script>window.onbeforeunload=function(e){"
49     "setTimeout('document.title=\"cancelled\"', 0);return 'foo'}</script>"
50     "</body></html>";
51 
52 const char INNER_FRAME_WITH_FOCUS_HTML[] =
53     "<html><head><title>innerframewithfocus</title></head><body>"
54     "<script>window.onbeforeunload=function(e){return 'foo'}</script>"
55     "<iframe src=\"data:text/html,<html><head><script>window.onload="
56     "function(){document.getElementById('box').focus()}</script>"
57     "<body><input id='box'></input></body></html>\"></iframe>"
58     "</body></html>";
59 
60 const char INFINITE_UNLOAD_HTML[] =
61     "<html><head><title>infiniteunload</title></head><body>"
62     "<script>window.onunload=function(e){while(true){}}</script>"
63     "</body></html>";
64 
65 const char INFINITE_BEFORE_UNLOAD_HTML[] =
66     "<html><head><title>infinitebeforeunload</title></head><body>"
67     "<script>window.onbeforeunload=function(e){while(true){}}</script>"
68     "</body></html>";
69 
70 const char INFINITE_UNLOAD_ALERT_HTML[] =
71     "<html><head><title>infiniteunloadalert</title></head><body>"
72     "<script>window.onunload=function(e){"
73     "while(true){}"
74     "alert('foo');"
75     "}</script></body></html>";
76 
77 const char INFINITE_BEFORE_UNLOAD_ALERT_HTML[] =
78     "<html><head><title>infinitebeforeunloadalert</title></head><body>"
79     "<script>window.onbeforeunload=function(e){"
80     "while(true){}"
81     "alert('foo');"
82     "}</script></body></html>";
83 
84 const char TWO_SECOND_UNLOAD_ALERT_HTML[] =
85     "<html><head><title>twosecondunloadalert</title></head><body>"
86     "<script>window.onunload=function(e){"
87     "var start = new Date().getTime();"
88     "while(new Date().getTime() - start < 2000){}"
89     "alert('foo');"
90     "}</script></body></html>";
91 
92 const char TWO_SECOND_BEFORE_UNLOAD_ALERT_HTML[] =
93     "<html><head><title>twosecondbeforeunloadalert</title></head><body>"
94     "<script>window.onbeforeunload=function(e){"
95     "var start = new Date().getTime();"
96     "while(new Date().getTime() - start < 2000){}"
97     "alert('foo');"
98     "}</script></body></html>";
99 
100 const char CLOSE_TAB_WHEN_OTHER_TAB_HAS_LISTENER[] =
101     "<html><head><title>only_one_unload</title></head>"
102     "<script>"
103     "function openPopup() {"
104     "  var w = window.open('about:blank');"
105     "  w.document.write('<html><head><title>popup</title></head></body>');"
106     "}"
107     "</script>"
108     "<body onclick='openPopup()' onbeforeunload='return;'>"
109     "</body></html>";
110 
111 class UnloadResults {
112  public:
UnloadResults()113   UnloadResults() : successes_(0), aborts_(0) {}
114 
AddSuccess(const base::FilePath &)115   void AddSuccess(const base::FilePath&) { successes_++; }
AddAbort(const base::FilePath &)116   void AddAbort(const base::FilePath&) { aborts_++; }
AddError(const base::FilePath &)117   void AddError(const base::FilePath&) {
118     ADD_FAILURE() << "AddError should not be called.";
119   }
120 
get_successes()121   int get_successes() { return successes_; }
get_aborts()122   int get_aborts() { return aborts_; }
123 
124  private:
125   int successes_;
126   int aborts_;
127 };
128 
129 class UnloadTest : public InProcessBrowserTest {
130  public:
SetUpCommandLine(base::CommandLine * command_line)131   void SetUpCommandLine(base::CommandLine* command_line) override {
132     const testing::TestInfo* const test_info =
133         testing::UnitTest::GetInstance()->current_test_info();
134     if (strstr(test_info->name(), "BrowserCloseTabWhenOtherTabHasListener") !=
135         nullptr) {
136       command_line->AppendSwitch(embedder_support::kDisablePopupBlocking);
137     } else if (strstr(test_info->name(), "BrowserTerminateBeforeUnload") !=
138                nullptr) {
139 #if defined(OS_POSIX)
140       DisableSIGTERMHandling();
141 #endif
142     }
143   }
144 
SetUpOnMainThread()145   void SetUpOnMainThread() override {
146     host_resolver()->AddRule("*", "127.0.0.1");
147   }
148 
CheckTitle(const char * expected_title,bool wait=false)149   void CheckTitle(const char* expected_title, bool wait = false) {
150     auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents();
151     base::string16 expected = base::ASCIIToUTF16(expected_title);
152     base::string16 actual;
153     if (wait)
154       actual = content::TitleWatcher(web_contents, expected).WaitAndGetTitle();
155     else
156       actual = web_contents->GetTitle();
157     EXPECT_EQ(expected, actual);
158   }
159 
NavigateToDataURL(const char * html_content,const char * expected_title)160   void NavigateToDataURL(const char* html_content, const char* expected_title) {
161     ui_test_utils::NavigateToURL(
162         browser(), GURL(std::string("data:text/html,") + html_content));
163     CheckTitle(expected_title);
164   }
165 
NavigateToNolistenersFileTwice()166   void NavigateToNolistenersFileTwice() {
167     ASSERT_TRUE(embedded_test_server()->Start());
168     GURL url(embedded_test_server()->GetURL("/title2.html"));
169     ui_test_utils::NavigateToURL(browser(), url);
170     CheckTitle("Title Of Awesomeness");
171     ui_test_utils::NavigateToURL(browser(), url);
172     CheckTitle("Title Of Awesomeness");
173   }
174 
175   // Navigates to a URL asynchronously, then again synchronously. The first
176   // load is purposely async to test the case where the user loads another
177   // page without waiting for the first load to complete.
NavigateToNolistenersFileTwiceAsync()178   void NavigateToNolistenersFileTwiceAsync() {
179     ASSERT_TRUE(embedded_test_server()->Start());
180     GURL url(embedded_test_server()->GetURL("/title2.html"));
181     ui_test_utils::NavigateToURLWithDisposition(
182         browser(), url, WindowOpenDisposition::CURRENT_TAB, 0);
183     ui_test_utils::NavigateToURL(browser(), url);
184     CheckTitle("Title Of Awesomeness");
185   }
186 
LoadUrlAndQuitBrowser(const char * html_content,const char * expected_title)187   void LoadUrlAndQuitBrowser(const char* html_content,
188                              const char* expected_title) {
189     NavigateToDataURL(html_content, expected_title);
190     CloseBrowserSynchronously(browser());
191   }
192 
193   // If |accept| is true, simulates user clicking OK, otherwise simulates
194   // clicking Cancel.
ClickModalDialogButton(bool accept)195   void ClickModalDialogButton(bool accept) {
196     javascript_dialogs::AppModalDialogController* dialog =
197         ui_test_utils::WaitForAppModalDialog();
198     if (accept)
199       dialog->view()->AcceptAppModalDialog();
200     else
201       dialog->view()->CancelAppModalDialog();
202   }
203 
PrepareForDialog(Browser * browser)204   void PrepareForDialog(Browser* browser) {
205     for (int i = 0; i < browser->tab_strip_model()->count(); i++) {
206       content::PrepContentsForBeforeUnloadTest(
207           browser->tab_strip_model()->GetWebContentsAt(i));
208     }
209   }
210 
CloseBrowsersVerifyUnloadSuccess(bool force)211   void CloseBrowsersVerifyUnloadSuccess(bool force) {
212     UnloadResults unload_results;
213     BrowserList::CloseAllBrowsersWithProfile(
214         browser()->profile(),
215         base::BindRepeating(&UnloadResults::AddSuccess,
216                             base::Unretained(&unload_results)),
217         base::BindRepeating(&UnloadResults::AddAbort,
218                             base::Unretained(&unload_results)),
219         force);
220     ui_test_utils::WaitForBrowserToClose();
221     EXPECT_EQ(1, unload_results.get_successes());
222     EXPECT_EQ(0, unload_results.get_aborts());
223   }
224 
225   // The test harness cannot close the window automatically, because it requires
226   // confirmation. We close the window manually instead.
ManuallyCloseWindow()227   void ManuallyCloseWindow() {
228     chrome::CloseWindow(browser());
229     ClickModalDialogButton(true);
230     ui_test_utils::WaitForBrowserToClose();
231   }
232 };
233 
234 // Navigate to a page with an infinite unload handler.
235 // Then two async crosssite requests to ensure
236 // we don't get confused and think we're closing the tab.
237 //
238 // This test is flaky on the valgrind UI bots. http://crbug.com/39057
IN_PROC_BROWSER_TEST_F(UnloadTest,CrossSiteInfiniteUnloadAsync)239 IN_PROC_BROWSER_TEST_F(UnloadTest, CrossSiteInfiniteUnloadAsync) {
240   // Tests makes no sense in single-process mode since the renderer is hung.
241   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
242           switches::kSingleProcess))
243     return;
244 
245   NavigateToDataURL(INFINITE_UNLOAD_HTML, "infiniteunload");
246   // Must navigate to a non-data URL to trigger cross-site codepath.
247   NavigateToNolistenersFileTwiceAsync();
248 }
249 
250 // Navigate to a page with an infinite unload handler.
251 // Then two sync crosssite requests to ensure
252 // we correctly nav to each one.
IN_PROC_BROWSER_TEST_F(UnloadTest,CrossSiteInfiniteUnloadSync)253 IN_PROC_BROWSER_TEST_F(UnloadTest, CrossSiteInfiniteUnloadSync) {
254   // Tests makes no sense in single-process mode since the renderer is hung.
255   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
256           switches::kSingleProcess))
257     return;
258 
259   NavigateToDataURL(INFINITE_UNLOAD_HTML, "infiniteunload");
260   // Must navigate to a non-data URL to trigger cross-site codepath.
261   NavigateToNolistenersFileTwice();
262 }
263 
264 // Navigate to a page with an infinite beforeunload handler.
265 // Then two two async crosssite requests to ensure
266 // we don't get confused and think we're closing the tab.
267 // This test is flaky on the valgrind UI bots. http://crbug.com/39057 and
268 // http://crbug.com/86469
IN_PROC_BROWSER_TEST_F(UnloadTest,CrossSiteInfiniteBeforeUnloadAsync)269 IN_PROC_BROWSER_TEST_F(UnloadTest, CrossSiteInfiniteBeforeUnloadAsync) {
270   // Tests makes no sense in single-process mode since the renderer is hung.
271   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
272           switches::kSingleProcess))
273     return;
274 
275   NavigateToDataURL(INFINITE_BEFORE_UNLOAD_HTML, "infinitebeforeunload");
276   // Must navigate to a non-data URL to trigger cross-site codepath.
277   NavigateToNolistenersFileTwiceAsync();
278 }
279 
280 // Navigate to a page with an infinite beforeunload handler.
281 // Then two two sync crosssite requests to ensure
282 // we correctly nav to each one.
283 // Flaky on Win, Linux, and Mac; http://crbug.com/462671.
IN_PROC_BROWSER_TEST_F(UnloadTest,DISABLED_CrossSiteInfiniteBeforeUnloadSync)284 IN_PROC_BROWSER_TEST_F(UnloadTest, DISABLED_CrossSiteInfiniteBeforeUnloadSync) {
285   // Tests makes no sense in single-process mode since the renderer is hung.
286   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
287           switches::kSingleProcess))
288     return;
289 
290   NavigateToDataURL(INFINITE_BEFORE_UNLOAD_HTML, "infinitebeforeunload");
291   // Must navigate to a non-data URL to trigger cross-site codepath.
292   NavigateToNolistenersFileTwice();
293 }
294 
295 // Tests closing the browser on a page with no unload listeners registered.
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserCloseNoUnloadListeners)296 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseNoUnloadListeners) {
297   LoadUrlAndQuitBrowser(NOLISTENERS_HTML, "nolisteners");
298 }
299 
300 // Tests closing the browser on a page with an unload listener registered.
301 // Test marked as flaky in http://crbug.com/51698
IN_PROC_BROWSER_TEST_F(UnloadTest,DISABLED_BrowserCloseUnload)302 IN_PROC_BROWSER_TEST_F(UnloadTest, DISABLED_BrowserCloseUnload) {
303   LoadUrlAndQuitBrowser(UNLOAD_HTML, "unload");
304 }
305 
306 // Tests closing the browser with a beforeunload handler and clicking
307 // OK in the beforeunload confirm dialog.
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserCloseBeforeUnloadOK)308 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseBeforeUnloadOK) {
309   NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
310   PrepareForDialog(browser());
311 
312   chrome::CloseWindow(browser());
313   ClickModalDialogButton(true);
314   ui_test_utils::WaitForBrowserToClose();
315 }
316 
317 // Tests closing the browser with a beforeunload handler and clicking
318 // CANCEL in the beforeunload confirm dialog.
319 // If this test flakes, reopen http://crbug.com/123110
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserCloseBeforeUnloadCancel)320 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseBeforeUnloadCancel) {
321   NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
322   PrepareForDialog(browser());
323   chrome::CloseWindow(browser());
324 
325   // We wait for the title to change after cancelling the closure of browser
326   // window, to ensure that in-flight IPCs from the renderer reach the browser.
327   // Otherwise the browser won't put up the beforeunload dialog because it's
328   // waiting for an ack from the renderer.
329   base::string16 expected_title = base::ASCIIToUTF16("cancelled");
330   content::TitleWatcher title_watcher(
331       browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
332   ClickModalDialogButton(false);
333   ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
334 
335   ManuallyCloseWindow();
336 }
337 
338 // Tests closing the browser by BrowserList::CloseAllBrowsersWithProfile,
339 // on a page with no unload listeners registered.
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserListCloseNoUnloadListeners)340 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserListCloseNoUnloadListeners) {
341   NavigateToDataURL(NOLISTENERS_HTML, "nolisteners");
342 
343   CloseBrowsersVerifyUnloadSuccess(false);
344 }
345 
346 // Tests closing the browser by BrowserList::CloseAllBrowsersWithProfile, with a
347 // beforeunload handler and clicking Leave in the beforeunload confirm dialog.
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserListCloseBeforeUnloadOK)348 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserListCloseBeforeUnloadOK) {
349   NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
350   PrepareForDialog(browser());
351 
352   UnloadResults unload_results;
353   BrowserList::CloseAllBrowsersWithProfile(
354       browser()->profile(),
355       base::BindRepeating(&UnloadResults::AddSuccess,
356                           base::Unretained(&unload_results)),
357       base::BindRepeating(&UnloadResults::AddAbort,
358                           base::Unretained(&unload_results)),
359       false);
360   ClickModalDialogButton(true);
361   ui_test_utils::WaitForBrowserToClose();
362   EXPECT_EQ(1, unload_results.get_successes());
363   EXPECT_EQ(0, unload_results.get_aborts());
364 }
365 
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserListForceCloseNoUnloadListeners)366 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserListForceCloseNoUnloadListeners) {
367   NavigateToDataURL(NOLISTENERS_HTML, "nolisteners");
368 
369   CloseBrowsersVerifyUnloadSuccess(true);
370 }
371 
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserListForceCloseWithBeforeUnload)372 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserListForceCloseWithBeforeUnload) {
373   NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
374 
375   CloseBrowsersVerifyUnloadSuccess(true);
376 }
377 
378 // Tests closing the browser by BrowserList::CloseAllBrowsersWithProfile, with a
379 // beforeunload handler and clicking Stay in the beforeunload confirm dialog.
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserListCloseBeforeUnloadCancel)380 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserListCloseBeforeUnloadCancel) {
381   NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
382   PrepareForDialog(browser());
383 
384   UnloadResults unload_results;
385   BrowserList::CloseAllBrowsersWithProfile(
386       browser()->profile(),
387       base::BindRepeating(&UnloadResults::AddSuccess,
388                           base::Unretained(&unload_results)),
389       base::BindRepeating(&UnloadResults::AddAbort,
390                           base::Unretained(&unload_results)),
391       false);
392 
393   // We wait for the title to change after cancelling the closure of browser
394   // window, to ensure that in-flight IPCs from the renderer reach the browser.
395   // Otherwise the browser won't put up the beforeunload dialog because it's
396   // waiting for an ack from the renderer.
397   base::string16 expected_title = base::ASCIIToUTF16("cancelled");
398   content::TitleWatcher title_watcher(
399       browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
400   ClickModalDialogButton(false);
401   ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
402 
403   EXPECT_EQ(0, unload_results.get_successes());
404   EXPECT_EQ(1, unload_results.get_aborts());
405 
406   ManuallyCloseWindow();
407 }
408 
409 // Tests double calls to BrowserList::CloseAllBrowsersWithProfile, with a
410 // beforeunload handler and clicking Leave in the beforeunload confirm dialog.
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserListDoubleCloseBeforeUnloadOK)411 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserListDoubleCloseBeforeUnloadOK) {
412   NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
413   PrepareForDialog(browser());
414 
415   UnloadResults unload_results;
416   BrowserList::CloseAllBrowsersWithProfile(
417       browser()->profile(),
418       base::BindRepeating(&UnloadResults::AddSuccess,
419                           base::Unretained(&unload_results)),
420       base::BindRepeating(&UnloadResults::AddAbort,
421                           base::Unretained(&unload_results)),
422       false);
423   BrowserList::CloseAllBrowsersWithProfile(
424       browser()->profile(),
425       base::BindRepeating(&UnloadResults::AddSuccess,
426                           base::Unretained(&unload_results)),
427       base::BindRepeating(&UnloadResults::AddAbort,
428                           base::Unretained(&unload_results)),
429       false);
430   ClickModalDialogButton(true);
431   ui_test_utils::WaitForBrowserToClose();
432   EXPECT_EQ(1, unload_results.get_successes());
433   EXPECT_EQ(0, unload_results.get_aborts());
434 }
435 
436 // Tests double calls to BrowserList::CloseAllBrowsersWithProfile, with a
437 // beforeunload handler and clicking Stay in the beforeunload confirm dialog.
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserListDoubleCloseBeforeUnloadCancel)438 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserListDoubleCloseBeforeUnloadCancel) {
439   NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
440   PrepareForDialog(browser());
441 
442   UnloadResults unload_results;
443   BrowserList::CloseAllBrowsersWithProfile(
444       browser()->profile(),
445       base::BindRepeating(&UnloadResults::AddSuccess,
446                           base::Unretained(&unload_results)),
447       base::BindRepeating(&UnloadResults::AddAbort,
448                           base::Unretained(&unload_results)),
449       false);
450   BrowserList::CloseAllBrowsersWithProfile(
451       browser()->profile(),
452       base::BindRepeating(&UnloadResults::AddSuccess,
453                           base::Unretained(&unload_results)),
454       base::BindRepeating(&UnloadResults::AddAbort,
455                           base::Unretained(&unload_results)),
456       false);
457 
458   // We wait for the title to change after cancelling the closure of browser
459   // window, to ensure that in-flight IPCs from the renderer reach the browser.
460   // Otherwise the browser won't put up the beforeunload dialog because it's
461   // waiting for an ack from the renderer.
462   base::string16 expected_title = base::ASCIIToUTF16("cancelled");
463   content::TitleWatcher title_watcher(
464       browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
465   ClickModalDialogButton(false);
466   ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
467 
468   EXPECT_EQ(0, unload_results.get_successes());
469   EXPECT_EQ(1, unload_results.get_aborts());
470 
471   ManuallyCloseWindow();
472 }
473 
474 // Tests closing the browser by BrowserList::CloseAllBrowsersWithProfile, with
475 // a null success callback, a beforeunload handler and clicking Leave in the
476 // beforeunload confirm dialog. The test succeed if no crash happens.
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserListCloseBeforeUnloadNullCallbackOk)477 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserListCloseBeforeUnloadNullCallbackOk) {
478   NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
479   PrepareForDialog(browser());
480 
481   UnloadResults unload_results;
482   BrowserList::CloseAllBrowsersWithProfile(browser()->profile(),
483                                            BrowserList::CloseCallback(),
484                                            BrowserList::CloseCallback(), false);
485   ClickModalDialogButton(true);
486   ui_test_utils::WaitForBrowserToClose();
487 }
488 
489 // Tests closing the browser by BrowserList::CloseAllBrowsersWithProfile, with
490 // a null failure callback, a beforeunload handler and clicking Stay in the
491 // beforeunload confirm dialog. The test succeed if no crash happens.
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserListCloseBeforeUnloadNullCallbackCancel)492 IN_PROC_BROWSER_TEST_F(UnloadTest,
493                        BrowserListCloseBeforeUnloadNullCallbackCancel) {
494   NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
495   PrepareForDialog(browser());
496 
497   UnloadResults unload_results;
498   BrowserList::CloseAllBrowsersWithProfile(browser()->profile(),
499                                            BrowserList::CloseCallback(),
500                                            BrowserList::CloseCallback(), false);
501 
502   // We wait for the title to change after cancelling the closure of browser
503   // window, to ensure that in-flight IPCs from the renderer reach the browser.
504   // Otherwise the browser won't put up the beforeunload dialog because it's
505   // waiting for an ack from the renderer.
506   base::string16 expected_title = base::ASCIIToUTF16("cancelled");
507   content::TitleWatcher title_watcher(
508       browser()->tab_strip_model()->GetActiveWebContents(), expected_title);
509   ClickModalDialogButton(false);
510   ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
511 
512   ManuallyCloseWindow();
513 }
514 
515 // Tests terminating the browser with a beforeunload handler.
516 // Currently only ChromeOS shuts down gracefully.
517 #if defined(OS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserTerminateBeforeUnload)518 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserTerminateBeforeUnload) {
519   NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
520   EXPECT_EQ(kill(base::GetCurrentProcessHandle(), SIGTERM), 0);
521 }
522 #endif
523 
524 // Tests closing the browser and clicking OK in the beforeunload confirm dialog
525 // if an inner frame has the focus.
526 // If this flakes, use http://crbug.com/32615 and http://crbug.com/45675
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserCloseWithInnerFocusedFrame)527 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseWithInnerFocusedFrame) {
528   NavigateToDataURL(INNER_FRAME_WITH_FOCUS_HTML, "innerframewithfocus");
529   PrepareForDialog(browser());
530 
531   ManuallyCloseWindow();
532 }
533 
534 // Tests closing the browser with a beforeunload handler that takes forever
535 // by running an infinite loop.
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserCloseInfiniteBeforeUnload)536 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseInfiniteBeforeUnload) {
537   LoadUrlAndQuitBrowser(INFINITE_BEFORE_UNLOAD_HTML,
538                         "infinitebeforeunload");
539 }
540 
541 // Tests closing the browser on a page with an unload listener registered where
542 // the unload handler has an infinite loop.
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserCloseInfiniteUnload)543 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseInfiniteUnload) {
544   // Tests makes no sense in single-process mode since the renderer is hung.
545   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
546           switches::kSingleProcess))
547     return;
548 
549   LoadUrlAndQuitBrowser(INFINITE_UNLOAD_HTML, "infiniteunload");
550 }
551 
552 // Tests closing the browser on a page with an unload listener registered where
553 // the unload handler has an infinite loop followed by an alert.
554 // If this flakes, use http://crbug.com/86469
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserCloseInfiniteUnloadAlert)555 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseInfiniteUnloadAlert) {
556   // Tests makes no sense in single-process mode since the renderer is hung.
557   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
558           switches::kSingleProcess))
559     return;
560 
561   LoadUrlAndQuitBrowser(INFINITE_UNLOAD_ALERT_HTML, "infiniteunloadalert");
562 }
563 
564 // Tests closing the browser with a beforeunload handler that hangs then
565 // pops up an alert.
566 // If this flakes, use http://crbug.com/78803 and http://crbug.com/86469.
IN_PROC_BROWSER_TEST_F(UnloadTest,DISABLED_BrowserCloseInfiniteBeforeUnloadAlert)567 IN_PROC_BROWSER_TEST_F(UnloadTest,
568                        DISABLED_BrowserCloseInfiniteBeforeUnloadAlert) {
569   // Tests makes no sense in single-process mode since the renderer is hung.
570   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
571           switches::kSingleProcess))
572     return;
573 
574   LoadUrlAndQuitBrowser(INFINITE_BEFORE_UNLOAD_ALERT_HTML,
575                         "infinitebeforeunloadalert");
576 }
577 
578 // Tests closing the browser on a page with an unload listener registered where
579 // the unload handler has an 2 second long loop followed by an alert.
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserCloseTwoSecondUnloadAlert)580 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseTwoSecondUnloadAlert) {
581   LoadUrlAndQuitBrowser(TWO_SECOND_UNLOAD_ALERT_HTML, "twosecondunloadalert");
582 }
583 
584 // Tests closing the browser with a beforeunload handler that takes
585 // two seconds to run then pops up an alert.
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserCloseTwoSecondBeforeUnloadAlert)586 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseTwoSecondBeforeUnloadAlert) {
587   LoadUrlAndQuitBrowser(TWO_SECOND_BEFORE_UNLOAD_ALERT_HTML,
588                         "twosecondbeforeunloadalert");
589 }
590 
591 // Tests that if there's a renderer process with two tabs, one of which has an
592 // unload handler, and the other doesn't, the tab that doesn't have an unload
593 // handler can be closed.
594 // If this flakes, see http://crbug.com/45162, http://crbug.com/45281 and
595 // http://crbug.com/86769.
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserCloseTabWhenOtherTabHasListener)596 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseTabWhenOtherTabHasListener) {
597   NavigateToDataURL(CLOSE_TAB_WHEN_OTHER_TAB_HAS_LISTENER, "only_one_unload");
598 
599   // Simulate a click to force user_gesture to true; if we don't, the resulting
600   // popup will be constrained, which isn't what we want to test.
601 
602   ui_test_utils::TabAddedWaiter tab_add(browser());
603   content::SimulateMouseClick(
604       browser()->tab_strip_model()->GetActiveWebContents(), 0,
605       blink::WebMouseEvent::Button::kLeft);
606   tab_add.Wait();
607   // Need to wait for the title, because the initial page (about:blank) can stop
608   // loading before the click handler calls document.write.
609   CheckTitle("popup", true);
610 
611   content::WebContentsDestroyedWatcher destroyed_watcher(
612       browser()->tab_strip_model()->GetActiveWebContents());
613   chrome::CloseTab(browser());
614   destroyed_watcher.Wait();
615 
616   CheckTitle("only_one_unload");
617 }
618 
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserListForceCloseAfterNormalClose)619 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserListForceCloseAfterNormalClose) {
620   NavigateToDataURL(BEFORE_UNLOAD_HTML, "beforeunload");
621 
622   UnloadResults unload_results;
623   BrowserList::CloseAllBrowsersWithProfile(
624       browser()->profile(),
625       base::BindRepeating(&UnloadResults::AddSuccess,
626                           base::Unretained(&unload_results)),
627       base::BindRepeating(&UnloadResults::AddAbort,
628                           base::Unretained(&unload_results)),
629       false);
630   BrowserList::CloseAllBrowsersWithProfile(
631       browser()->profile(),
632       base::BindRepeating(&UnloadResults::AddSuccess,
633                           base::Unretained(&unload_results)),
634       base::BindRepeating(&UnloadResults::AddAbort,
635                           base::Unretained(&unload_results)),
636       true);
637   ui_test_utils::WaitForBrowserToClose();
638   EXPECT_EQ(1, unload_results.get_successes());
639   EXPECT_EQ(0, unload_results.get_aborts());
640 }
641 
642 // Tests that a cross-site iframe runs its beforeunload handler when closing
643 // the browser.  See https://crbug.com/853021.
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserCloseWithCrossSiteIframe)644 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseWithCrossSiteIframe) {
645   ASSERT_TRUE(embedded_test_server()->Start());
646 
647   // Navigate to a page with an iframe.
648   GURL main_url(embedded_test_server()->GetURL("a.com", "/iframe.html"));
649   ui_test_utils::NavigateToURL(browser(), main_url);
650 
651   // Navigate iframe cross-site.
652   GURL frame_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
653   content::WebContents* web_contents =
654       browser()->tab_strip_model()->GetActiveWebContents();
655   EXPECT_TRUE(NavigateIframeToURL(web_contents, "test", frame_url));
656 
657   // Install a dialog-showing beforeunload handler in the iframe.
658   content::RenderFrameHost* child =
659       ChildFrameAt(web_contents->GetMainFrame(), 0);
660   EXPECT_TRUE(
661       ExecuteScript(child, "window.onbeforeunload = () => { return 'x' };"));
662 
663   // Close the browser and make sure the beforeunload dialog is shown and can
664   // be clicked.
665   PrepareForDialog(browser());
666   ManuallyCloseWindow();
667 }
668 
669 // Tests that a same-site iframe runs its beforeunload handler when closing the
670 // browser.  See https://crbug.com/1010456.
IN_PROC_BROWSER_TEST_F(UnloadTest,BrowserCloseWithSameSiteIframe)671 IN_PROC_BROWSER_TEST_F(UnloadTest, BrowserCloseWithSameSiteIframe) {
672   ASSERT_TRUE(embedded_test_server()->Start());
673 
674   // Navigate to a page with a same-site iframe.
675   GURL main_url(embedded_test_server()->GetURL("a.com", "/iframe.html"));
676   ui_test_utils::NavigateToURL(browser(), main_url);
677   content::WebContents* web_contents =
678       browser()->tab_strip_model()->GetActiveWebContents();
679   content::RenderFrameHost* child =
680       ChildFrameAt(web_contents->GetMainFrame(), 0);
681   EXPECT_EQ(child->GetSiteInstance(),
682             web_contents->GetMainFrame()->GetSiteInstance());
683 
684   // Install a dialog-showing beforeunload handler in the iframe.
685   EXPECT_TRUE(
686       ExecuteScript(child, "window.onbeforeunload = () => { return 'x' };"));
687 
688   // Close the browser and make sure the beforeunload dialog is shown and can
689   // be clicked.
690   PrepareForDialog(browser());
691   ManuallyCloseWindow();
692 }
693 
694 // TODO(ojan): Add tests for unload/beforeunload that have multiple tabs
695 // and multiple windows.
696