blob: 3f0b76e2977f22d8a748b1673e4eada87c9b060a [file] [log] [blame]
[email protected]2e6389f2012-05-18 19:41:251// 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
[email protected]313fce12013-01-30 17:09:045#include "chrome/browser/lifetime/application_lifetime.h"
[email protected]2e6389f2012-05-18 19:41:256
dcheng4af48582016-04-19 00:29:357#include <memory>
manzagopb5f74db2016-11-28 18:05:528#include <string>
dcheng4af48582016-04-19 00:29:359
[email protected]2e9d79f2013-08-16 05:45:5610#include "base/bind.h"
[email protected]2e6389f2012-05-18 19:41:2511#include "base/logging.h"
rvargas486b2f562015-03-18 01:36:3312#include "base/process/process.h"
rvargas079d1842014-10-17 22:32:1613#include "base/process/process_handle.h"
Eric Seckler8652dcd52018-09-20 10:42:2814#include "base/task/post_task.h"
Greg Thompsonf9928d982019-11-21 14:35:5615#include "base/util/type_safety/strong_alias.h"
[email protected]2e6389f2012-05-18 19:41:2516#include "build/build_config.h"
17#include "chrome/browser/browser_process.h"
[email protected]612d3bf2013-05-22 10:10:4618#include "chrome/browser/browser_process_platform_part.h"
[email protected]9ea0cd32013-07-12 01:50:3619#include "chrome/browser/chrome_notification_types.h"
peterccb33e82017-05-02 19:00:4420#include "chrome/browser/download/download_core_service.h"
[email protected]2e9d79f2013-08-16 05:45:5621#include "chrome/browser/lifetime/browser_close_manager.h"
Avi Drissmand30927342018-05-22 15:04:2722#include "chrome/browser/lifetime/browser_shutdown.h"
[email protected]2e6389f2012-05-18 19:41:2523#include "chrome/browser/metrics/thread_watcher.h"
[email protected]2e6389f2012-05-18 19:41:2524#include "chrome/browser/profiles/profile.h"
25#include "chrome/browser/profiles/profile_manager.h"
Scott Violet6200d332018-02-23 21:29:2326#include "chrome/common/buildflags.h"
[email protected]2e6389f2012-05-18 19:41:2527#include "chrome/common/pref_names.h"
Michael Giuffrida2dbce0d12017-09-02 03:30:5928#include "components/keep_alive_registry/keep_alive_registry.h"
Ran Ji73f3b432018-07-15 22:10:2729#include "components/language/core/browser/pref_names.h"
Claudio Magnie0a80bb2018-01-31 02:52:5830#include "components/language/core/common/locale_util.h"
brettwb1fc1b82016-02-02 00:19:0831#include "components/prefs/pref_service.h"
Eric Seckler8652dcd52018-09-20 10:42:2832#include "content/public/browser/browser_task_traits.h"
[email protected]2e6389f2012-05-18 19:41:2533#include "content/public/browser/browser_thread.h"
34#include "content/public/browser/navigation_details.h"
35#include "content/public/browser/notification_service.h"
36
thestigea81c77b52017-04-17 23:19:1137#if !defined(OS_ANDROID)
38#include "chrome/browser/lifetime/termination_notification.h"
39#include "chrome/browser/ui/browser.h"
40#include "chrome/browser/ui/browser_finder.h"
41#include "chrome/browser/ui/browser_list.h"
42#include "chrome/browser/ui/browser_tabstrip.h"
43#include "chrome/browser/ui/browser_window.h"
44#endif
45
[email protected]2e6389f2012-05-18 19:41:2546#if defined(OS_CHROMEOS)
tfarina0923ac52015-01-07 03:21:2247#include "chrome/browser/chromeos/boot_times_recorder.h"
pmarko8c3ffb52017-02-08 12:18:3548#include "chrome/browser/chromeos/settings/cros_settings.h"
[email protected]2e6389f2012-05-18 19:41:2549#include "chromeos/dbus/dbus_thread_manager.h"
Steven Bennetts3330b9f2019-03-15 20:24:1350#include "chromeos/dbus/power/power_policy_controller.h"
Daniel Erat03de51e22017-09-09 00:51:5151#include "third_party/cros_system_api/dbus/service_constants.h"
[email protected]2e6389f2012-05-18 19:41:2552#endif
53
Mike Wasserman0fc30aa42018-02-08 18:43:2054#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
55#include "chrome/browser/ui/user_manager.h"
56#endif
57
[email protected]e88332a2013-08-20 11:42:3858#if defined(OS_WIN)
59#include "base/win/win_util.h"
[email protected]e88332a2013-08-20 11:42:3860#endif
61
[email protected]0c98ab652013-02-18 00:39:3762namespace chrome {
Greg Thompsonae8a5b12019-11-21 12:35:3663
[email protected]2e6389f2012-05-18 19:41:2564namespace {
65
[email protected]1d51882f2013-11-12 01:59:0266#if !defined(OS_ANDROID)
[email protected]2e6389f2012-05-18 19:41:2567// Returns true if all browsers can be closed without user interaction.
68// This currently checks if there is pending download, or if it needs to
69// handle unload handler.
70bool AreAllBrowsersCloseable() {
scottmg8abbff832016-01-28 22:57:3771 if (BrowserList::GetInstance()->empty())
[email protected]2e6389f2012-05-18 19:41:2572 return true;
73
74 // If there are any downloads active, all browsers are not closeable.
[email protected]422a7d12013-10-21 12:10:4275 // However, this does not block for malicious downloads.
peterccb33e82017-05-02 19:00:4476 if (DownloadCoreService::NonMaliciousDownloadCountAllProfiles() > 0)
[email protected]2e6389f2012-05-18 19:41:2577 return false;
78
79 // Check TabsNeedBeforeUnloadFired().
scottmg8abbff832016-01-28 22:57:3780 for (auto* browser : *BrowserList::GetInstance()) {
81 if (browser->TabsNeedBeforeUnloadFired())
[email protected]2e6389f2012-05-18 19:41:2582 return false;
83 }
84 return true;
85}
tzika2f1ff92016-02-10 22:59:5486#endif // !defined(OS_ANDROID)
[email protected]2e6389f2012-05-18 19:41:2587
88#if defined(OS_CHROMEOS)
pmarko8c3ffb52017-02-08 12:18:3589// Sets kApplicationLocale in |local_state| for the login screen on the next
90// application start, if it is forced to a specific value due to enterprise
91// policy or the owner's locale. Returns true if any pref has been modified.
92bool SetLocaleForNextStart(PrefService* local_state) {
93 // If a policy mandates the login screen locale, use it.
94 chromeos::CrosSettings* cros_settings = chromeos::CrosSettings::Get();
95 const base::ListValue* login_screen_locales = nullptr;
96 std::string login_screen_locale;
97 if (cros_settings->GetList(chromeos::kDeviceLoginScreenLocales,
98 &login_screen_locales) &&
99 !login_screen_locales->empty() &&
100 login_screen_locales->GetString(0, &login_screen_locale)) {
Ran Ji73f3b432018-07-15 22:10:27101 local_state->SetString(language::prefs::kApplicationLocale,
102 login_screen_locale);
pmarko8c3ffb52017-02-08 12:18:35103 return true;
104 }
105
106 // Login screen should show up in owner's locale.
107 std::string owner_locale = local_state->GetString(prefs::kOwnerLocale);
Ran Ji73f3b432018-07-15 22:10:27108 std::string pref_locale =
109 local_state->GetString(language::prefs::kApplicationLocale);
Claudio Magnie0a80bb2018-01-31 02:52:58110 language::ConvertToActualUILocale(&pref_locale);
111 if (!owner_locale.empty() && pref_locale != owner_locale &&
Ran Ji73f3b432018-07-15 22:10:27112 !local_state->IsManagedPreference(language::prefs::kApplicationLocale)) {
113 local_state->SetString(language::prefs::kApplicationLocale, owner_locale);
pmarko8c3ffb52017-02-08 12:18:35114 return true;
115 }
116
117 return false;
118}
119
[email protected]7df2c032014-01-22 10:42:01120// Whether chrome should send stop request to a session manager.
121bool g_send_stop_request_to_session_manager = false;
[email protected]2e6389f2012-05-18 19:41:25122#endif
123
Greg Thompsonf9928d982019-11-21 14:35:56124#if !defined(OS_ANDROID)
125using IgnoreUnloadHandlers =
126 util::StrongAlias<class IgnoreUnloadHandlersTag, bool>;
127
128void AttemptRestartInternal(IgnoreUnloadHandlers ignore_unload_handlers) {
129 // TODO(beng): Can this use ProfileManager::GetLoadedProfiles instead?
130 for (auto* browser : *BrowserList::GetInstance())
131 content::BrowserContext::SaveSessionState(browser->profile());
132
133 PrefService* pref_service = g_browser_process->local_state();
134 pref_service->SetBoolean(prefs::kWasRestarted, true);
135
136#if defined(OS_CHROMEOS)
137 chromeos::BootTimesRecorder::Get()->set_restart_requested();
138
139 DCHECK(!g_send_stop_request_to_session_manager);
140 // Make sure we don't send stop request to the session manager.
141 g_send_stop_request_to_session_manager = false;
142 // Run exit process in clean stack.
143 base::PostTask(FROM_HERE, {content::BrowserThread::UI},
144 base::BindOnce(&ExitIgnoreUnloadHandlers));
145#else
146 // Set the flag to restore state after the restart.
147 pref_service->SetBoolean(prefs::kRestartLastSessionOnShutdown, true);
148 if (ignore_unload_handlers)
149 ExitIgnoreUnloadHandlers();
150 else
151 AttemptExit();
152#endif
153}
154#endif // !defined(OS_ANDROID)
155
[email protected]2e6389f2012-05-18 19:41:25156} // namespace
157
thestig7987e9762016-04-13 21:52:17158#if !defined(OS_ANDROID)
[email protected]2e6389f2012-05-18 19:41:25159void MarkAsCleanShutdown() {
160 // TODO(beng): Can this use ProfileManager::GetLoadedProfiles() instead?
scottmg8abbff832016-01-28 22:57:37161 for (auto* browser : *BrowserList::GetInstance())
162 browser->profile()->SetExitType(Profile::EXIT_NORMAL);
[email protected]2e6389f2012-05-18 19:41:25163}
thestig7987e9762016-04-13 21:52:17164#endif
[email protected]2e6389f2012-05-18 19:41:25165
[email protected]0c95faf42013-10-28 06:27:20166void AttemptExitInternal(bool try_to_quit_application) {
167 // On Mac, the platform-specific part handles setting this.
168#if !defined(OS_MACOSX)
169 if (try_to_quit_application)
170 browser_shutdown::SetTryingToQuit(true);
171#endif
172
[email protected]2e6389f2012-05-18 19:41:25173 content::NotificationService::current()->Notify(
thestigea81c77b52017-04-17 23:19:11174 NOTIFICATION_CLOSE_ALL_BROWSERS_REQUEST,
[email protected]2e6389f2012-05-18 19:41:25175 content::NotificationService::AllSources(),
176 content::NotificationService::NoDetails());
177
Avi Drissmanf19bb472018-11-29 19:51:28178 g_browser_process->platform_part()->AttemptExit(try_to_quit_application);
[email protected]2e6389f2012-05-18 19:41:25179}
180
tzika2f1ff92016-02-10 22:59:54181#if !defined(OS_ANDROID)
[email protected]0c95faf42013-10-28 06:27:20182void CloseAllBrowsersAndQuit() {
183 browser_shutdown::SetTryingToQuit(true);
184 CloseAllBrowsers();
185}
186
dgnfe075c82016-03-18 11:25:35187void ShutdownIfNoBrowsers() {
thestigea81c77b52017-04-17 23:19:11188 if (GetTotalBrowserCount() > 0)
dgnfe075c82016-03-18 11:25:35189 return;
190
191 // Tell everyone that we are shutting down.
192 browser_shutdown::SetTryingToQuit(true);
193
brettw9e85ef42016-11-01 21:01:24194#if BUILDFLAG(ENABLE_SESSION_SERVICE)
dgnfe075c82016-03-18 11:25:35195 // If ShuttingDownWithoutClosingBrowsers() returns true, the session
196 // services may not get a chance to shut down normally, so explicitly shut
197 // them down here to ensure they have a chance to persist their data.
198 ProfileManager::ShutdownSessionServices();
199#endif
200
thestigea81c77b52017-04-17 23:19:11201 browser_shutdown::NotifyAndTerminate(true /* fast_path */);
202 OnAppExiting();
dgnfe075c82016-03-18 11:25:35203}
204
[email protected]2e6389f2012-05-18 19:41:25205void CloseAllBrowsers() {
[email protected]0c95faf42013-10-28 06:27:20206 // If there are no browsers and closing the last browser would quit the
207 // application, send the APP_TERMINATING action here. Otherwise, it will be
208 // sent by RemoveBrowser() when the last browser has closed.
thestigea81c77b52017-04-17 23:19:11209 if (GetTotalBrowserCount() == 0 &&
dgn02377782016-03-12 00:58:38210 (browser_shutdown::IsTryingToQuit() ||
211 !KeepAliveRegistry::GetInstance()->IsKeepingAlive())) {
dgnfe075c82016-03-18 11:25:35212 ShutdownIfNoBrowsers();
[email protected]2e6389f2012-05-18 19:41:25213 return;
214 }
215
216#if defined(OS_CHROMEOS)
tfarina0923ac52015-01-07 03:21:22217 chromeos::BootTimesRecorder::Get()->AddLogoutTimeMarker(
[email protected]2e6389f2012-05-18 19:41:25218 "StartedClosingWindows", false);
219#endif
[email protected]2e9d79f2013-08-16 05:45:56220 scoped_refptr<BrowserCloseManager> browser_close_manager =
221 new BrowserCloseManager;
222 browser_close_manager->StartClosingBrowsers();
[email protected]2e6389f2012-05-18 19:41:25223}
tzika2f1ff92016-02-10 22:59:54224#endif // !defined(OS_ANDROID)
[email protected]2e6389f2012-05-18 19:41:25225
226void AttemptUserExit() {
227#if defined(OS_CHROMEOS)
achuith9d97f2c92016-08-19 08:03:06228 VLOG(1) << "AttemptUserExit";
tfarina0923ac52015-01-07 03:21:22229 chromeos::BootTimesRecorder::Get()->AddLogoutTimeMarker("LogoutStarted",
230 false);
[email protected]2e6389f2012-05-18 19:41:25231
[email protected]2e6389f2012-05-18 19:41:25232 PrefService* state = g_browser_process->local_state();
233 if (state) {
tfarina0923ac52015-01-07 03:21:22234 chromeos::BootTimesRecorder::Get()->OnLogoutStarted(state);
[email protected]b01b9e22014-06-03 22:20:19235
pmarko8c3ffb52017-02-08 12:18:35236 if (SetLocaleForNextStart(state)) {
[email protected]89af4002013-09-06 07:47:07237 TRACE_EVENT0("shutdown", "CommitPendingWrite");
[email protected]2e6389f2012-05-18 19:41:25238 state->CommitPendingWrite();
239 }
240 }
[email protected]7df2c032014-01-22 10:42:01241 g_send_stop_request_to_session_manager = true;
[email protected]2e6389f2012-05-18 19:41:25242 // On ChromeOS, always terminate the browser, regardless of the result of
243 // AreAllBrowsersCloseable(). See crbug.com/123107.
thestigea81c77b52017-04-17 23:19:11244 browser_shutdown::NotifyAndTerminate(true /* fast_path */);
[email protected]2e6389f2012-05-18 19:41:25245#else
246 // Reset the restart bit that might have been set in cancelled restart
247 // request.
thestig7987e9762016-04-13 21:52:17248#if !defined(OS_ANDROID)
nomsd710bc832014-10-15 20:27:37249 UserManager::Hide();
thestig7987e9762016-04-13 21:52:17250#endif
[email protected]2e6389f2012-05-18 19:41:25251 PrefService* pref_service = g_browser_process->local_state();
252 pref_service->SetBoolean(prefs::kRestartLastSessionOnShutdown, false);
[email protected]0c95faf42013-10-28 06:27:20253 AttemptExitInternal(false);
thestig7987e9762016-04-13 21:52:17254#endif // defined(OS_CHROMEOS)
[email protected]2e6389f2012-05-18 19:41:25255}
256
[email protected]4052d832013-01-16 05:31:01257// The Android implementation is in application_lifetime_android.cc
258#if !defined(OS_ANDROID)
[email protected]2e6389f2012-05-18 19:41:25259void AttemptRestart() {
Greg Thompsonf9928d982019-11-21 14:35:56260 AttemptRestartInternal(IgnoreUnloadHandlers(false));
[email protected]2e6389f2012-05-18 19:41:25261}
dpapad615a9562016-06-07 02:10:29262#endif // !defined(OS_ANDROID)
[email protected]2e6389f2012-05-18 19:41:25263
dpapad374368fd2016-06-09 04:04:42264void AttemptRelaunch() {
265#if defined(OS_CHROMEOS)
Evan Stade523f7fc2019-03-02 19:20:51266 chromeos::PowerManagerClient::Get()->RequestRestart(
Daniel Erat03de51e22017-09-09 00:51:51267 power_manager::REQUEST_RESTART_OTHER, "Chrome relaunch");
dpapad374368fd2016-06-09 04:04:42268 // If running the Chrome OS build, but we're not on the device, fall through.
269#endif
thestigea81c77b52017-04-17 23:19:11270 AttemptRestart();
dpapad374368fd2016-06-09 04:04:42271}
272
Greg Thompsonf9928d982019-11-21 14:35:56273#if !defined(OS_ANDROID)
274void RelaunchIgnoreUnloadHandlers() {
275#if defined(OS_CHROMEOS)
276 chromeos::PowerManagerClient::Get()->RequestRestart(
277 power_manager::REQUEST_RESTART_OTHER, "Chrome relaunch");
278 // If running the Chrome OS build, but we're not on the device, fall through.
279#endif
280 AttemptRestartInternal(IgnoreUnloadHandlers(true));
281}
282#endif
283
[email protected]2e6389f2012-05-18 19:41:25284void AttemptExit() {
[email protected]7df2c032014-01-22 10:42:01285#if defined(OS_CHROMEOS)
286 // On ChromeOS, user exit and system exits are the same.
287 AttemptUserExit();
288#else
[email protected]2e6389f2012-05-18 19:41:25289 // If we know that all browsers can be closed without blocking,
290 // don't notify users of crashes beyond this point.
[email protected]6c0ca7fc2012-10-05 16:27:22291 // Note that MarkAsCleanShutdown() does not set UMA's exit cleanly bit
[email protected]2e6389f2012-05-18 19:41:25292 // so crashes during shutdown are still reported in UMA.
[email protected]4052d832013-01-16 05:31:01293#if !defined(OS_ANDROID)
294 // Android doesn't use Browser.
[email protected]2e6389f2012-05-18 19:41:25295 if (AreAllBrowsersCloseable())
296 MarkAsCleanShutdown();
[email protected]4052d832013-01-16 05:31:01297#endif
[email protected]0c95faf42013-10-28 06:27:20298 AttemptExitInternal(true);
[email protected]7df2c032014-01-22 10:42:01299#endif
[email protected]2e6389f2012-05-18 19:41:25300}
301
Andrey Lushnikovc08ae9e2018-12-21 01:32:40302void ExitIgnoreUnloadHandlers() {
303 VLOG(1) << "ExitIgnoreUnloadHandlers";
304#if !defined(OS_ANDROID)
hashimoto16a569a2015-04-09 11:25:28305 // We always mark exit cleanly.
306 MarkAsCleanShutdown();
[email protected]2e6389f2012-05-18 19:41:25307
Greg Thompsonf9928d982019-11-21 14:35:56308#if defined(OS_CHROMEOS)
Andrey Lushnikovc08ae9e2018-12-21 01:32:40309 // On ChromeOS ExitIgnoreUnloadHandlers() is used to handle SIGTERM.
310 // In this case, AreAllBrowsersCloseable()
[email protected]2e6389f2012-05-18 19:41:25311 // can be false in following cases. a) power-off b) signout from
312 // screen locker.
Greg Thompsonae8a5b12019-11-21 12:35:36313 browser_shutdown::OnShutdownStarting(
314 AreAllBrowsersCloseable() ? browser_shutdown::ShutdownType::kBrowserExit
315 : browser_shutdown::ShutdownType::kEndSession);
Greg Thompsonf9928d982019-11-21 14:35:56316#else // defined(OS_CHROMEOS)
317 // For desktop browsers, always perform a silent exit.
318 browser_shutdown::OnShutdownStarting(
319 browser_shutdown::ShutdownType::kSilentExit);
320#endif // defined(OS_CHROMEOS)
321#endif // !defined(OS_ANDROID)
[email protected]0c95faf42013-10-28 06:27:20322 AttemptExitInternal(true);
[email protected]2e6389f2012-05-18 19:41:25323}
thestigea81c77b52017-04-17 23:19:11324
Andrey Lushnikovc08ae9e2018-12-21 01:32:40325#if defined(OS_CHROMEOS)
thestigea81c77b52017-04-17 23:19:11326bool IsAttemptingShutdown() {
327 return g_send_stop_request_to_session_manager;
328}
[email protected]2e6389f2012-05-18 19:41:25329#endif
330
tzika2f1ff92016-02-10 22:59:54331#if !defined(OS_ANDROID)
[email protected]2e6389f2012-05-18 19:41:25332void SessionEnding() {
333 // This is a time-limited shutdown where we need to write as much to
334 // disk as we can as soon as we can, and where we must kill the
335 // process within a hang timeout to avoid user prompts.
336
[email protected]2e6389f2012-05-18 19:41:25337 // EndSession is invoked once per frame. Only do something the first time.
338 static bool already_ended = false;
339 // We may get called in the middle of shutdown, e.g. http://crbug.com/70852
340 // In this case, do nothing.
341 if (already_ended || !content::NotificationService::current())
342 return;
343 already_ended = true;
344
sky0e07a142016-03-25 21:27:31345 // ~ShutdownWatcherHelper uses IO (it joins a thread). We'll only trigger that
346 // if Terminate() fails, which leaves us in a weird state, or the OS is going
347 // to kill us soon. Either way we don't care about that here.
348 base::ThreadRestrictions::ScopedAllowIO allow_io;
349
350 // Start watching for hang during shutdown, and crash it if takes too long.
351 // We disarm when |shutdown_watcher| object is destroyed, which is when we
352 // exit this function.
353 ShutdownWatcherHelper shutdown_watcher;
354 shutdown_watcher.Arm(base::TimeDelta::FromSeconds(90));
sky0e07a142016-03-25 21:27:31355
Greg Thompsonae8a5b12019-11-21 12:35:36356 browser_shutdown::OnShutdownStarting(
357 browser_shutdown::ShutdownType::kEndSession);
[email protected]2e6389f2012-05-18 19:41:25358
hashimotodc347b72016-09-16 07:23:40359 // In a clean shutdown, browser_shutdown::OnShutdownStarting sets
360 // g_shutdown_type, and browser_shutdown::ShutdownPreThreadsStop calls
361 // RecordShutdownInfoPrefs to update the pref with the value. However, here
362 // the process is going to exit without calling ShutdownPreThreadsStop.
363 // Instead, here we call RecordShutdownInfoPrefs to record the shutdown info.
364 browser_shutdown::RecordShutdownInfoPrefs();
365
[email protected]2e6389f2012-05-18 19:41:25366 content::NotificationService::current()->Notify(
thestigea81c77b52017-04-17 23:19:11367 NOTIFICATION_CLOSE_ALL_BROWSERS_REQUEST,
[email protected]2e6389f2012-05-18 19:41:25368 content::NotificationService::AllSources(),
369 content::NotificationService::NoDetails());
370
371 // Write important data first.
372 g_browser_process->EndSession();
373
[email protected]7a6af7e2014-07-08 20:11:13374#if defined(OS_WIN)
375 base::win::SetShouldCrashOnProcessDetach(false);
[email protected]cc2ebc72014-07-15 23:57:36376#endif
Greg Thompsonae8a5b12019-11-21 12:35:36377
siggi5ed6480a2014-12-05 15:44:26378 // On Windows 7 and later, the system will consider the process ripe for
379 // termination as soon as it hides or destroys its windows. Since any
380 // execution past that point will be non-deterministically cut short, we
381 // might as well put ourselves out of that misery deterministically.
Wez9a0f7232018-02-08 17:14:29382 base::Process::TerminateCurrentProcessImmediately(0);
[email protected]2e6389f2012-05-18 19:41:25383}
384
dgnfe075c82016-03-18 11:25:35385void ShutdownIfNeeded() {
oshimafc74eba22017-04-04 23:36:30386 if (browser_shutdown::IsTryingToQuit())
387 return;
388
389 ShutdownIfNoBrowsers();
skyostil0becb332015-04-27 17:59:37390}
391
[email protected]313fce12013-01-30 17:09:04392void OnAppExiting() {
393 static bool notified = false;
394 if (notified)
395 return;
396 notified = true;
397 HandleAppExitingForPlatform();
398}
tzika2f1ff92016-02-10 22:59:54399#endif // !defined(OS_ANDROID)
skyostil0becb332015-04-27 17:59:37400
[email protected]313fce12013-01-30 17:09:04401} // namespace chrome