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