application_lifetime.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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#include "chrome/browser/lifetime/application_lifetime.h"
6
7#include "ash/shell.h"
8#include "base/bind.h"
9#include "base/command_line.h"
10#include "base/debug/trace_event.h"
11#include "base/logging.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/message_loop/message_loop.h"
14#include "base/prefs/pref_service.h"
15#include "build/build_config.h"
16#include "chrome/browser/browser_process.h"
17#include "chrome/browser/browser_process_platform_part.h"
18#include "chrome/browser/browser_shutdown.h"
19#include "chrome/browser/chrome_notification_types.h"
20#include "chrome/browser/download/download_service.h"
21#include "chrome/browser/lifetime/browser_close_manager.h"
22#include "chrome/browser/metrics/thread_watcher.h"
23#include "chrome/browser/profiles/profile.h"
24#include "chrome/browser/profiles/profile_manager.h"
25#include "chrome/browser/ui/browser.h"
26#include "chrome/browser/ui/browser_finder.h"
27#include "chrome/browser/ui/browser_iterator.h"
28#include "chrome/browser/ui/browser_tabstrip.h"
29#include "chrome/browser/ui/browser_window.h"
30#include "chrome/browser/ui/tabs/tab_strip_model.h"
31#include "chrome/common/chrome_switches.h"
32#include "chrome/common/pref_names.h"
33#include "content/public/browser/browser_shutdown.h"
34#include "content/public/browser/browser_thread.h"
35#include "content/public/browser/navigation_details.h"
36#include "content/public/browser/notification_service.h"
37
38#if defined(OS_CHROMEOS)
39#include "base/sys_info.h"
40#include "chrome/browser/chromeos/boot_times_loader.h"
41#include "chrome/browser/chromeos/login/user_manager.h"
42#include "chromeos/dbus/dbus_thread_manager.h"
43#include "chromeos/dbus/session_manager_client.h"
44#include "chromeos/dbus/update_engine_client.h"
45#endif
46
47#if defined(OS_WIN)
48#include "base/win/win_util.h"
49#endif
50
51namespace chrome {
52namespace {
53
54// Returns true if all browsers can be closed without user interaction.
55// This currently checks if there is pending download, or if it needs to
56// handle unload handler.
57bool AreAllBrowsersCloseable() {
58  chrome::BrowserIterator browser_it;
59  if (browser_it.done())
60    return true;
61
62  // If there are any downloads active, all browsers are not closeable.
63  if (DownloadService::DownloadCountAllProfiles() > 0)
64    return false;
65
66  // Check TabsNeedBeforeUnloadFired().
67  for (; !browser_it.done(); browser_it.Next()) {
68    if (browser_it->TabsNeedBeforeUnloadFired())
69      return false;
70  }
71  return true;
72}
73
74int g_keep_alive_count = 0;
75
76#if defined(OS_CHROMEOS)
77// Whether a session manager requested to shutdown.
78bool g_session_manager_requested_shutdown = true;
79#endif
80
81}  // namespace
82
83void MarkAsCleanShutdown() {
84  // TODO(beng): Can this use ProfileManager::GetLoadedProfiles() instead?
85  for (chrome::BrowserIterator it; !it.done(); it.Next())
86    it->profile()->SetExitType(Profile::EXIT_NORMAL);
87}
88
89void AttemptExitInternal() {
90  content::NotificationService::current()->Notify(
91      chrome::NOTIFICATION_CLOSE_ALL_BROWSERS_REQUEST,
92      content::NotificationService::AllSources(),
93      content::NotificationService::NoDetails());
94
95  g_browser_process->platform_part()->AttemptExit();
96}
97
98void CloseAllBrowsers() {
99  // If there are no browsers, send the APP_TERMINATING action here. Otherwise,
100  // it will be sent by RemoveBrowser() when the last browser has closed.
101  if (browser_shutdown::ShuttingDownWithoutClosingBrowsers() ||
102      chrome::GetTotalBrowserCount() == 0) {
103    // Tell everyone that we are shutting down.
104    browser_shutdown::SetTryingToQuit(true);
105
106#if defined(ENABLE_SESSION_SERVICE)
107    // If ShuttingDownWithoutClosingBrowsers() returns true, the session
108    // services may not get a chance to shut down normally, so explicitly shut
109    // them down here to ensure they have a chance to persist their data.
110    ProfileManager::ShutdownSessionServices();
111#endif
112
113    chrome::NotifyAndTerminate(true);
114    chrome::OnAppExiting();
115    return;
116  }
117
118#if defined(OS_CHROMEOS)
119  chromeos::BootTimesLoader::Get()->AddLogoutTimeMarker(
120      "StartedClosingWindows", false);
121#endif
122  scoped_refptr<BrowserCloseManager> browser_close_manager =
123      new BrowserCloseManager;
124  browser_close_manager->StartClosingBrowsers();
125}
126
127void AttemptUserExit() {
128#if defined(OS_CHROMEOS)
129  StartShutdownTracing();
130  chromeos::BootTimesLoader::Get()->AddLogoutTimeMarker("LogoutStarted", false);
131  // Write /tmp/uptime-logout-started as well.
132  const char kLogoutStarted[] = "logout-started";
133  chromeos::BootTimesLoader::Get()->RecordCurrentStats(kLogoutStarted);
134
135  // Login screen should show up in owner's locale.
136  PrefService* state = g_browser_process->local_state();
137  if (state) {
138    std::string owner_locale = state->GetString(prefs::kOwnerLocale);
139    if (!owner_locale.empty() &&
140        state->GetString(prefs::kApplicationLocale) != owner_locale &&
141        !state->IsManagedPreference(prefs::kApplicationLocale)) {
142      state->SetString(prefs::kApplicationLocale, owner_locale);
143      TRACE_EVENT0("shutdown", "CommitPendingWrite");
144      state->CommitPendingWrite();
145    }
146  }
147  g_session_manager_requested_shutdown = false;
148  // On ChromeOS, always terminate the browser, regardless of the result of
149  // AreAllBrowsersCloseable(). See crbug.com/123107.
150  chrome::NotifyAndTerminate(true);
151#else
152  // Reset the restart bit that might have been set in cancelled restart
153  // request.
154  PrefService* pref_service = g_browser_process->local_state();
155  pref_service->SetBoolean(prefs::kRestartLastSessionOnShutdown, false);
156  AttemptExitInternal();
157#endif
158}
159
160void StartShutdownTracing() {
161  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
162  if (command_line.HasSwitch(switches::kTraceShutdown)) {
163    base::debug::CategoryFilter category_filter(
164        command_line.GetSwitchValueASCII(switches::kTraceShutdown));
165    base::debug::TraceLog::GetInstance()->SetEnabled(
166        category_filter,
167        base::debug::TraceLog::RECORD_UNTIL_FULL);
168  }
169  TRACE_EVENT0("shutdown", "StartShutdownTracing");
170}
171
172// The Android implementation is in application_lifetime_android.cc
173#if !defined(OS_ANDROID)
174void AttemptRestart() {
175  // TODO(beng): Can this use ProfileManager::GetLoadedProfiles instead?
176  for (chrome::BrowserIterator it; !it.done(); it.Next())
177    content::BrowserContext::SaveSessionState(it->profile());
178
179  PrefService* pref_service = g_browser_process->local_state();
180  pref_service->SetBoolean(prefs::kWasRestarted, true);
181
182#if defined(OS_CHROMEOS)
183  // For CrOS instead of browser restart (which is not supported) perform a full
184  // sign out. Session will be only restored if user has that setting set.
185  // Same session restore behavior happens in case of full restart after update.
186  AttemptUserExit();
187#else
188  // Set the flag to restore state after the restart.
189  pref_service->SetBoolean(prefs::kRestartLastSessionOnShutdown, true);
190  AttemptExit();
191#endif
192}
193#endif
194
195#if defined(OS_WIN)
196void AttemptRestartWithModeSwitch() {
197  // The kRestartSwitchMode preference does not exists for Windows 7 and older
198  // operating systems so there is no need for OS version check.
199  PrefService* prefs = g_browser_process->local_state();
200  prefs->SetBoolean(prefs::kRestartSwitchMode, true);
201  AttemptRestart();
202}
203#endif
204
205void AttemptExit() {
206  // If we know that all browsers can be closed without blocking,
207  // don't notify users of crashes beyond this point.
208  // Note that MarkAsCleanShutdown() does not set UMA's exit cleanly bit
209  // so crashes during shutdown are still reported in UMA.
210#if !defined(OS_ANDROID)
211  // Android doesn't use Browser.
212  if (AreAllBrowsersCloseable())
213    MarkAsCleanShutdown();
214#endif
215  AttemptExitInternal();
216}
217
218#if defined(OS_CHROMEOS)
219// A function called when SIGTERM is received.
220void ExitCleanly() {
221  // We always mark exit cleanly because SessionManager may kill
222  // chrome in 3 seconds after SIGTERM.
223  g_browser_process->EndSession();
224
225  // Don't block when SIGTERM is received. AreaAllBrowsersCloseable()
226  // can be false in following cases. a) power-off b) signout from
227  // screen locker.
228  if (!AreAllBrowsersCloseable())
229    browser_shutdown::OnShutdownStarting(browser_shutdown::END_SESSION);
230  AttemptExitInternal();
231}
232#endif
233
234void SessionEnding() {
235  // This is a time-limited shutdown where we need to write as much to
236  // disk as we can as soon as we can, and where we must kill the
237  // process within a hang timeout to avoid user prompts.
238
239  // Start watching for hang during shutdown, and crash it if takes too long.
240  // We disarm when |shutdown_watcher| object is destroyed, which is when we
241  // exit this function.
242  ShutdownWatcherHelper shutdown_watcher;
243  shutdown_watcher.Arm(base::TimeDelta::FromSeconds(90));
244
245  // EndSession is invoked once per frame. Only do something the first time.
246  static bool already_ended = false;
247  // We may get called in the middle of shutdown, e.g. http://crbug.com/70852
248  // In this case, do nothing.
249  if (already_ended || !content::NotificationService::current())
250    return;
251  already_ended = true;
252
253  browser_shutdown::OnShutdownStarting(browser_shutdown::END_SESSION);
254
255  content::NotificationService::current()->Notify(
256      chrome::NOTIFICATION_CLOSE_ALL_BROWSERS_REQUEST,
257      content::NotificationService::AllSources(),
258      content::NotificationService::NoDetails());
259
260  // Write important data first.
261  g_browser_process->EndSession();
262
263  CloseAllBrowsers();
264
265  // Send out notification. This is used during testing so that the test harness
266  // can properly shutdown before we exit.
267  content::NotificationService::current()->Notify(
268      chrome::NOTIFICATION_SESSION_END,
269      content::NotificationService::AllSources(),
270      content::NotificationService::NoDetails());
271
272#if defined(OS_WIN)
273  base::win::SetShouldCrashOnProcessDetach(false);
274#endif
275  // This will end by terminating the process.
276  content::ImmediateShutdownAndExitProcess();
277}
278
279void StartKeepAlive() {
280  // Increment the browser process refcount as long as we're keeping the
281  // application alive.
282  if (!WillKeepAlive())
283    g_browser_process->AddRefModule();
284  ++g_keep_alive_count;
285}
286
287void EndKeepAlive() {
288  DCHECK_GT(g_keep_alive_count, 0);
289  --g_keep_alive_count;
290
291  DCHECK(g_browser_process);
292  // Although we should have a browser process, if there is none,
293  // there is nothing to do.
294  if (!g_browser_process) return;
295
296  // Allow the app to shutdown again.
297  if (!WillKeepAlive()) {
298    g_browser_process->ReleaseModule();
299    // If there are no browsers open and we aren't already shutting down,
300    // initiate a shutdown. Also skips shutdown if this is a unit test
301    // (MessageLoop::current() == null).
302    if (chrome::GetTotalBrowserCount() == 0 &&
303        !browser_shutdown::IsTryingToQuit() &&
304        base::MessageLoop::current()) {
305      CloseAllBrowsers();
306    }
307  }
308}
309
310bool WillKeepAlive() {
311  return g_keep_alive_count > 0;
312}
313
314void NotifyAppTerminating() {
315  static bool notified = false;
316  if (notified)
317    return;
318  notified = true;
319  content::NotificationService::current()->Notify(
320      chrome::NOTIFICATION_APP_TERMINATING,
321      content::NotificationService::AllSources(),
322      content::NotificationService::NoDetails());
323}
324
325void NotifyAndTerminate(bool fast_path) {
326#if defined(OS_CHROMEOS)
327  static bool notified = false;
328  // Return if a shutdown request has already been sent.
329  if (notified)
330    return;
331  notified = true;
332#endif
333
334  if (fast_path)
335    NotifyAppTerminating();
336
337#if defined(OS_CHROMEOS)
338  if (base::SysInfo::IsRunningOnChromeOS()) {
339    // If we're on a ChromeOS device, reboot if an update has been applied,
340    // or else signal the session manager to log out.
341    chromeos::UpdateEngineClient* update_engine_client
342        = chromeos::DBusThreadManager::Get()->GetUpdateEngineClient();
343    if (update_engine_client->GetLastStatus().status ==
344        chromeos::UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT) {
345      update_engine_client->RebootAfterUpdate();
346    } else if (!g_session_manager_requested_shutdown) {
347      // Don't ask SessionManager to stop session if the shutdown request comes
348      // from session manager.
349      chromeos::DBusThreadManager::Get()->GetSessionManagerClient()
350          ->StopSession();
351    }
352  } else {
353    // If running the Chrome OS build, but we're not on the device, act
354    // as if we received signal from SessionManager.
355    content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
356                                     base::Bind(&ExitCleanly));
357  }
358#endif
359}
360
361void OnAppExiting() {
362  static bool notified = false;
363  if (notified)
364    return;
365  notified = true;
366  HandleAppExitingForPlatform();
367}
368
369bool ShouldStartShutdown(Browser* browser) {
370  if (BrowserList::GetInstance(browser->host_desktop_type())->size() > 1)
371    return false;
372#if defined(OS_WIN) && defined(USE_AURA)
373  // On Windows 8 the desktop and ASH environments could be active
374  // at the same time.
375  // We should not start the shutdown process in the following cases:-
376  // 1. If the desktop type of the browser going away is ASH and there
377  //    are browser windows open in the desktop.
378  // 2. If the desktop type of the browser going away is desktop and the ASH
379  //    environment is still active.
380  if (browser->host_desktop_type() == chrome::HOST_DESKTOP_TYPE_NATIVE)
381    return !ash::Shell::HasInstance();
382  else if (browser->host_desktop_type() == chrome::HOST_DESKTOP_TYPE_ASH)
383    return BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_NATIVE)->empty();
384#endif
385  return true;
386}
387
388}  // namespace chrome
389