Bumping manifests a=b2g-bump
[gecko.git] / toolkit / components / startup / nsAppStartup.cpp
blobdff063e621e12d816382a26a70cf9f81b21a5165
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsAppStartup.h"
8 #include "nsIAppShellService.h"
9 #include "nsPIDOMWindow.h"
10 #include "nsIInterfaceRequestor.h"
11 #include "nsIFile.h"
12 #include "nsIObserverService.h"
13 #include "nsIPrefBranch.h"
14 #include "nsIPrefService.h"
15 #include "nsIPromptService.h"
16 #include "nsIStringBundle.h"
17 #include "nsISupportsPrimitives.h"
18 #include "nsIWebBrowserChrome.h"
19 #include "nsIWindowMediator.h"
20 #include "nsIWindowWatcher.h"
21 #include "nsIXULRuntime.h"
22 #include "nsIXULWindow.h"
23 #include "nsNativeCharsetUtils.h"
24 #include "nsThreadUtils.h"
25 #include "nsAutoPtr.h"
26 #include "nsString.h"
27 #include "mozilla/Preferences.h"
28 #include "GeckoProfiler.h"
30 #include "prprf.h"
31 #include "nsIInterfaceRequestorUtils.h"
32 #include "nsWidgetsCID.h"
33 #include "nsAppShellCID.h"
34 #include "nsXPCOMCIDInternal.h"
35 #include "mozilla/Services.h"
36 #include "nsIXPConnect.h"
37 #include "jsapi.h"
38 #include "prenv.h"
39 #include "nsAppDirectoryServiceDefs.h"
41 #if defined(XP_WIN)
42 // Prevent collisions with nsAppStartup::GetStartupInfo()
43 #undef GetStartupInfo
44 #endif
46 #include "mozilla/IOInterposer.h"
47 #include "mozilla/Telemetry.h"
48 #include "mozilla/StartupTimeline.h"
50 static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
52 #define kPrefLastSuccess "toolkit.startup.last_success"
53 #define kPrefMaxResumedCrashes "toolkit.startup.max_resumed_crashes"
54 #define kPrefRecentCrashes "toolkit.startup.recent_crashes"
55 #define kPrefAlwaysUseSafeMode "toolkit.startup.always_use_safe_mode"
57 #if defined(XP_WIN)
58 #include "mozilla/perfprobe.h"
59 /**
60 * Events sent to the system for profiling purposes
62 //Keep them syncronized with the .mof file
64 //Process-wide GUID, used by the OS to differentiate sources
65 // {509962E0-406B-46F4-99BA-5A009F8D2225}
66 //Keep it synchronized with the .mof file
67 #define NS_APPLICATION_TRACING_CID \
68 { 0x509962E0, 0x406B, 0x46F4, \
69 { 0x99, 0xBA, 0x5A, 0x00, 0x9F, 0x8D, 0x22, 0x25} }
71 //Event-specific GUIDs, used by the OS to differentiate events
72 // {A3DA04E0-57D7-482A-A1C1-61DA5F95BACB}
73 #define NS_PLACES_INIT_COMPLETE_EVENT_CID \
74 { 0xA3DA04E0, 0x57D7, 0x482A, \
75 { 0xA1, 0xC1, 0x61, 0xDA, 0x5F, 0x95, 0xBA, 0xCB} }
76 // {917B96B1-ECAD-4DAB-A760-8D49027748AE}
77 #define NS_SESSION_STORE_WINDOW_RESTORED_EVENT_CID \
78 { 0x917B96B1, 0xECAD, 0x4DAB, \
79 { 0xA7, 0x60, 0x8D, 0x49, 0x02, 0x77, 0x48, 0xAE} }
80 // {26D1E091-0AE7-4F49-A554-4214445C505C}
81 #define NS_XPCOM_SHUTDOWN_EVENT_CID \
82 { 0x26D1E091, 0x0AE7, 0x4F49, \
83 { 0xA5, 0x54, 0x42, 0x14, 0x44, 0x5C, 0x50, 0x5C} }
85 static NS_DEFINE_CID(kApplicationTracingCID,
86 NS_APPLICATION_TRACING_CID);
87 static NS_DEFINE_CID(kPlacesInitCompleteCID,
88 NS_PLACES_INIT_COMPLETE_EVENT_CID);
89 static NS_DEFINE_CID(kSessionStoreWindowRestoredCID,
90 NS_SESSION_STORE_WINDOW_RESTORED_EVENT_CID);
91 static NS_DEFINE_CID(kXPCOMShutdownCID,
92 NS_XPCOM_SHUTDOWN_EVENT_CID);
93 #endif //defined(XP_WIN)
95 using namespace mozilla;
97 uint32_t gRestartMode = 0;
99 class nsAppExitEvent : public nsRunnable {
100 private:
101 nsRefPtr<nsAppStartup> mService;
103 public:
104 nsAppExitEvent(nsAppStartup *service) : mService(service) {}
106 NS_IMETHOD Run() {
107 // Tell the appshell to exit
108 mService->mAppShell->Exit();
110 mService->mRunning = false;
111 return NS_OK;
116 * Computes an approximation of the absolute time represented by @a stamp
117 * which is comparable to those obtained via PR_Now(). If the current absolute
118 * time varies a lot (e.g. DST adjustments) since the first call then the
119 * resulting times may be inconsistent.
121 * @param stamp The timestamp to be converted
122 * @returns The converted timestamp
124 uint64_t ComputeAbsoluteTimestamp(PRTime prnow, TimeStamp now, TimeStamp stamp)
126 static PRTime sAbsoluteNow = PR_Now();
127 static TimeStamp sMonotonicNow = TimeStamp::Now();
129 return sAbsoluteNow - (sMonotonicNow - stamp).ToMicroseconds();
133 // nsAppStartup
136 nsAppStartup::nsAppStartup() :
137 mConsiderQuitStopper(0),
138 mRunning(false),
139 mShuttingDown(false),
140 mStartingUp(true),
141 mAttemptingQuit(false),
142 mRestart(false),
143 mInterrupted(false),
144 mIsSafeModeNecessary(false),
145 mStartupCrashTrackingEnded(false),
146 mRestartTouchEnvironment(false)
150 nsresult
151 nsAppStartup::Init()
153 nsresult rv;
155 // Create widget application shell
156 mAppShell = do_GetService(kAppShellCID, &rv);
157 NS_ENSURE_SUCCESS(rv, rv);
159 nsCOMPtr<nsIObserverService> os =
160 mozilla::services::GetObserverService();
161 if (!os)
162 return NS_ERROR_FAILURE;
164 os->AddObserver(this, "quit-application", true);
165 os->AddObserver(this, "quit-application-forced", true);
166 os->AddObserver(this, "sessionstore-init-started", true);
167 os->AddObserver(this, "sessionstore-windows-restored", true);
168 os->AddObserver(this, "profile-change-teardown", true);
169 os->AddObserver(this, "xul-window-registered", true);
170 os->AddObserver(this, "xul-window-destroyed", true);
171 os->AddObserver(this, "profile-before-change", true);
172 os->AddObserver(this, "xpcom-shutdown", true);
174 #if defined(XP_WIN)
175 os->AddObserver(this, "places-init-complete", true);
176 // This last event is only interesting to us for xperf-based measures
178 // Initialize interaction with profiler
179 mProbesManager =
180 new ProbeManager(
181 kApplicationTracingCID,
182 NS_LITERAL_CSTRING("Application startup probe"));
183 // Note: The operation is meant mostly for in-house profiling.
184 // Therefore, we do not warn if probes manager cannot be initialized
186 if (mProbesManager) {
187 mPlacesInitCompleteProbe =
188 mProbesManager->
189 GetProbe(kPlacesInitCompleteCID,
190 NS_LITERAL_CSTRING("places-init-complete"));
191 NS_WARN_IF_FALSE(mPlacesInitCompleteProbe,
192 "Cannot initialize probe 'places-init-complete'");
194 mSessionWindowRestoredProbe =
195 mProbesManager->
196 GetProbe(kSessionStoreWindowRestoredCID,
197 NS_LITERAL_CSTRING("sessionstore-windows-restored"));
198 NS_WARN_IF_FALSE(mSessionWindowRestoredProbe,
199 "Cannot initialize probe 'sessionstore-windows-restored'");
201 mXPCOMShutdownProbe =
202 mProbesManager->
203 GetProbe(kXPCOMShutdownCID,
204 NS_LITERAL_CSTRING("xpcom-shutdown"));
205 NS_WARN_IF_FALSE(mXPCOMShutdownProbe,
206 "Cannot initialize probe 'xpcom-shutdown'");
208 rv = mProbesManager->StartSession();
209 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
210 "Cannot initialize system probe manager");
212 #endif //defined(XP_WIN)
214 return NS_OK;
219 // nsAppStartup->nsISupports
222 NS_IMPL_ISUPPORTS(nsAppStartup,
223 nsIAppStartup,
224 nsIWindowCreator,
225 nsIWindowCreator2,
226 nsIObserver,
227 nsISupportsWeakReference)
231 // nsAppStartup->nsIAppStartup
234 NS_IMETHODIMP
235 nsAppStartup::CreateHiddenWindow()
237 #ifdef MOZ_WIDGET_GONK
238 return NS_OK;
239 #else
240 nsCOMPtr<nsIAppShellService> appShellService
241 (do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
242 NS_ENSURE_TRUE(appShellService, NS_ERROR_FAILURE);
244 return appShellService->CreateHiddenWindow();
245 #endif
249 NS_IMETHODIMP
250 nsAppStartup::DestroyHiddenWindow()
252 #ifdef MOZ_WIDGET_GONK
253 return NS_OK;
254 #else
255 nsCOMPtr<nsIAppShellService> appShellService
256 (do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
257 NS_ENSURE_TRUE(appShellService, NS_ERROR_FAILURE);
259 return appShellService->DestroyHiddenWindow();
260 #endif
263 NS_IMETHODIMP
264 nsAppStartup::Run(void)
266 NS_ASSERTION(!mRunning, "Reentrant appstartup->Run()");
268 // If we have no windows open and no explicit calls to
269 // enterLastWindowClosingSurvivalArea, or somebody has explicitly called
270 // quit, don't bother running the event loop which would probably leave us
271 // with a zombie process.
273 if (!mShuttingDown && mConsiderQuitStopper != 0) {
274 #ifdef XP_MACOSX
275 EnterLastWindowClosingSurvivalArea();
276 #endif
278 mRunning = true;
280 nsresult rv = mAppShell->Run();
281 if (NS_FAILED(rv))
282 return rv;
285 nsresult retval = NS_OK;
286 if (mRestartTouchEnvironment) {
287 retval = NS_SUCCESS_RESTART_METRO_APP;
288 } else if (mRestart) {
289 retval = NS_SUCCESS_RESTART_APP;
292 return retval;
297 NS_IMETHODIMP
298 nsAppStartup::Quit(uint32_t aMode)
300 uint32_t ferocity = (aMode & 0xF);
302 // Quit the application. We will asynchronously call the appshell's
303 // Exit() method via nsAppExitEvent to allow one last pass
304 // through any events in the queue. This guarantees a tidy cleanup.
305 nsresult rv = NS_OK;
306 bool postedExitEvent = false;
308 if (mShuttingDown)
309 return NS_OK;
311 // If we're considering quitting, we will only do so if:
312 if (ferocity == eConsiderQuit) {
313 #ifdef XP_MACOSX
314 nsCOMPtr<nsIAppShellService> appShell
315 (do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
316 bool hasHiddenPrivateWindow = false;
317 if (appShell) {
318 appShell->GetHasHiddenPrivateWindow(&hasHiddenPrivateWindow);
320 int32_t suspiciousCount = hasHiddenPrivateWindow ? 2 : 1;
321 #endif
323 if (mConsiderQuitStopper == 0) {
324 // there are no windows...
325 ferocity = eAttemptQuit;
327 #ifdef XP_MACOSX
328 else if (mConsiderQuitStopper == suspiciousCount) {
329 // ... or there is only a hiddenWindow left, and it's useless:
331 // Failure shouldn't be fatal, but will abort quit attempt:
332 if (!appShell)
333 return NS_OK;
335 bool usefulHiddenWindow;
336 appShell->GetApplicationProvidedHiddenWindow(&usefulHiddenWindow);
337 nsCOMPtr<nsIXULWindow> hiddenWindow;
338 appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow));
339 // If the remaining windows are useful, we won't quit:
340 nsCOMPtr<nsIXULWindow> hiddenPrivateWindow;
341 if (hasHiddenPrivateWindow) {
342 appShell->GetHiddenPrivateWindow(getter_AddRefs(hiddenPrivateWindow));
343 if ((!hiddenWindow && !hiddenPrivateWindow) || usefulHiddenWindow)
344 return NS_OK;
345 } else if (!hiddenWindow || usefulHiddenWindow) {
346 return NS_OK;
349 ferocity = eAttemptQuit;
351 #endif
354 nsCOMPtr<nsIObserverService> obsService;
355 if (ferocity == eAttemptQuit || ferocity == eForceQuit) {
357 nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
358 nsCOMPtr<nsIWindowMediator> mediator (do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
359 if (mediator) {
360 mediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator));
361 if (windowEnumerator) {
362 bool more;
363 while (windowEnumerator->HasMoreElements(&more), more) {
364 nsCOMPtr<nsISupports> window;
365 windowEnumerator->GetNext(getter_AddRefs(window));
366 nsCOMPtr<nsPIDOMWindow> domWindow(do_QueryInterface(window));
367 if (domWindow) {
368 MOZ_ASSERT(domWindow->IsOuterWindow());
369 if (!domWindow->CanClose())
370 return NS_OK;
376 PROFILER_MARKER("Shutdown start");
377 mozilla::RecordShutdownStartTimeStamp();
378 mShuttingDown = true;
379 if (!mRestart) {
380 mRestart = (aMode & eRestart) != 0;
381 gRestartMode = (aMode & 0xF0);
384 if (!mRestartTouchEnvironment) {
385 mRestartTouchEnvironment = (aMode & eRestartTouchEnvironment) != 0;
386 gRestartMode = (aMode & 0xF0);
389 if (mRestart || mRestartTouchEnvironment) {
390 // Mark the next startup as a restart.
391 PR_SetEnv("MOZ_APP_RESTART=1");
393 /* Firefox-restarts reuse the process so regular process start-time isn't
394 a useful indicator of startup time anymore. */
395 TimeStamp::RecordProcessRestart();
398 obsService = mozilla::services::GetObserverService();
400 if (!mAttemptingQuit) {
401 mAttemptingQuit = true;
402 #ifdef XP_MACOSX
403 // now even the Mac wants to quit when the last window is closed
404 ExitLastWindowClosingSurvivalArea();
405 #endif
406 if (obsService)
407 obsService->NotifyObservers(nullptr, "quit-application-granted", nullptr);
410 /* Enumerate through each open window and close it. It's important to do
411 this before we forcequit because this can control whether we really quit
412 at all. e.g. if one of these windows has an unload handler that
413 opens a new window. Ugh. I know. */
414 CloseAllWindows();
416 if (mediator) {
417 if (ferocity == eAttemptQuit) {
418 ferocity = eForceQuit; // assume success
420 /* Were we able to immediately close all windows? if not, eAttemptQuit
421 failed. This could happen for a variety of reasons; in fact it's
422 very likely. Perhaps we're being called from JS and the window->Close
423 method hasn't had a chance to wrap itself up yet. So give up.
424 We'll return (with eConsiderQuit) as the remaining windows are
425 closed. */
426 mediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator));
427 if (windowEnumerator) {
428 bool more;
429 while (windowEnumerator->HasMoreElements(&more), more) {
430 /* we can't quit immediately. we'll try again as the last window
431 finally closes. */
432 ferocity = eAttemptQuit;
433 nsCOMPtr<nsISupports> window;
434 windowEnumerator->GetNext(getter_AddRefs(window));
435 nsCOMPtr<nsIDOMWindow> domWindow = do_QueryInterface(window);
436 if (domWindow) {
437 bool closed = false;
438 domWindow->GetClosed(&closed);
439 if (!closed) {
440 rv = NS_ERROR_FAILURE;
441 break;
450 if (ferocity == eForceQuit) {
451 // do it!
453 // No chance of the shutdown being cancelled from here on; tell people
454 // we're shutting down for sure while all services are still available.
455 if (obsService) {
456 NS_NAMED_LITERAL_STRING(shutdownStr, "shutdown");
457 NS_NAMED_LITERAL_STRING(restartStr, "restart");
458 obsService->NotifyObservers(nullptr, "quit-application",
459 (mRestart || mRestartTouchEnvironment) ?
460 restartStr.get() : shutdownStr.get());
463 if (!mRunning) {
464 postedExitEvent = true;
466 else {
467 // no matter what, make sure we send the exit event. If
468 // worst comes to worst, we'll do a leaky shutdown but we WILL
469 // shut down. Well, assuming that all *this* stuff works ;-).
470 nsCOMPtr<nsIRunnable> event = new nsAppExitEvent(this);
471 rv = NS_DispatchToCurrentThread(event);
472 if (NS_SUCCEEDED(rv)) {
473 postedExitEvent = true;
475 else {
476 NS_WARNING("failed to dispatch nsAppExitEvent");
481 // turn off the reentrancy check flag, but not if we have
482 // more asynchronous work to do still.
483 if (!postedExitEvent)
484 mShuttingDown = false;
485 return rv;
489 void
490 nsAppStartup::CloseAllWindows()
492 nsCOMPtr<nsIWindowMediator> mediator
493 (do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
495 nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
497 mediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator));
499 if (!windowEnumerator)
500 return;
502 bool more;
503 while (NS_SUCCEEDED(windowEnumerator->HasMoreElements(&more)) && more) {
504 nsCOMPtr<nsISupports> isupports;
505 if (NS_FAILED(windowEnumerator->GetNext(getter_AddRefs(isupports))))
506 break;
508 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(isupports);
509 NS_ASSERTION(window, "not an nsPIDOMWindow");
510 if (window) {
511 MOZ_ASSERT(window->IsOuterWindow());
512 window->ForceClose();
517 NS_IMETHODIMP
518 nsAppStartup::EnterLastWindowClosingSurvivalArea(void)
520 ++mConsiderQuitStopper;
521 return NS_OK;
525 NS_IMETHODIMP
526 nsAppStartup::ExitLastWindowClosingSurvivalArea(void)
528 NS_ASSERTION(mConsiderQuitStopper > 0, "consider quit stopper out of bounds");
529 --mConsiderQuitStopper;
531 if (mRunning)
532 Quit(eConsiderQuit);
534 return NS_OK;
538 // nsAppStartup->nsIAppStartup2
541 NS_IMETHODIMP
542 nsAppStartup::GetShuttingDown(bool *aResult)
544 *aResult = mShuttingDown;
545 return NS_OK;
548 NS_IMETHODIMP
549 nsAppStartup::GetStartingUp(bool *aResult)
551 *aResult = mStartingUp;
552 return NS_OK;
555 NS_IMETHODIMP
556 nsAppStartup::DoneStartingUp()
558 // This must be called once at most
559 MOZ_ASSERT(mStartingUp);
561 mStartingUp = false;
562 return NS_OK;
565 NS_IMETHODIMP
566 nsAppStartup::GetRestarting(bool *aResult)
568 *aResult = mRestart;
569 return NS_OK;
572 NS_IMETHODIMP
573 nsAppStartup::GetWasRestarted(bool *aResult)
575 char *mozAppRestart = PR_GetEnv("MOZ_APP_RESTART");
577 /* When calling PR_SetEnv() with an empty value the existing variable may
578 * be unset or set to the empty string depending on the underlying platform
579 * thus we have to check if the variable is present and not empty. */
580 *aResult = mozAppRestart && (strcmp(mozAppRestart, "") != 0);
582 return NS_OK;
585 NS_IMETHODIMP
586 nsAppStartup::GetRestartingTouchEnvironment(bool *aResult)
588 NS_ENSURE_ARG_POINTER(aResult);
589 *aResult = mRestartTouchEnvironment;
590 return NS_OK;
593 NS_IMETHODIMP
594 nsAppStartup::SetInterrupted(bool aInterrupted)
596 mInterrupted = aInterrupted;
597 return NS_OK;
600 NS_IMETHODIMP
601 nsAppStartup::GetInterrupted(bool *aInterrupted)
603 *aInterrupted = mInterrupted;
604 return NS_OK;
608 // nsAppStartup->nsIWindowCreator
611 NS_IMETHODIMP
612 nsAppStartup::CreateChromeWindow(nsIWebBrowserChrome *aParent,
613 uint32_t aChromeFlags,
614 nsIWebBrowserChrome **_retval)
616 bool cancel;
617 return CreateChromeWindow2(aParent, aChromeFlags, 0, 0, nullptr, &cancel, _retval);
622 // nsAppStartup->nsIWindowCreator2
625 NS_IMETHODIMP
626 nsAppStartup::CreateChromeWindow2(nsIWebBrowserChrome *aParent,
627 uint32_t aChromeFlags,
628 uint32_t aContextFlags,
629 nsIURI *aURI,
630 nsITabParent *aOpeningTab,
631 bool *aCancel,
632 nsIWebBrowserChrome **_retval)
634 NS_ENSURE_ARG_POINTER(aCancel);
635 NS_ENSURE_ARG_POINTER(_retval);
636 *aCancel = false;
637 *_retval = 0;
639 // Non-modal windows cannot be opened if we are attempting to quit
640 if (mAttemptingQuit && (aChromeFlags & nsIWebBrowserChrome::CHROME_MODAL) == 0)
641 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
643 nsCOMPtr<nsIXULWindow> newWindow;
645 if (aParent) {
646 nsCOMPtr<nsIXULWindow> xulParent(do_GetInterface(aParent));
647 NS_ASSERTION(xulParent, "window created using non-XUL parent. that's unexpected, but may work.");
649 if (xulParent)
650 xulParent->CreateNewWindow(aChromeFlags, aOpeningTab, getter_AddRefs(newWindow));
651 // And if it fails, don't try again without a parent. It could fail
652 // intentionally (bug 115969).
653 } else { // try using basic methods:
654 /* You really shouldn't be making dependent windows without a parent.
655 But unparented modal (and therefore dependent) windows happen
656 in our codebase, so we allow it after some bellyaching: */
657 if (aChromeFlags & nsIWebBrowserChrome::CHROME_DEPENDENT)
658 NS_WARNING("dependent window created without a parent");
660 nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
661 if (!appShell)
662 return NS_ERROR_FAILURE;
664 appShell->CreateTopLevelWindow(0, 0, aChromeFlags,
665 nsIAppShellService::SIZE_TO_CONTENT,
666 nsIAppShellService::SIZE_TO_CONTENT,
667 aOpeningTab,
668 getter_AddRefs(newWindow));
671 // if anybody gave us anything to work with, use it
672 if (newWindow) {
673 newWindow->SetContextFlags(aContextFlags);
674 nsCOMPtr<nsIInterfaceRequestor> thing(do_QueryInterface(newWindow));
675 if (thing)
676 CallGetInterface(thing.get(), _retval);
679 return *_retval ? NS_OK : NS_ERROR_FAILURE;
684 // nsAppStartup->nsIObserver
687 NS_IMETHODIMP
688 nsAppStartup::Observe(nsISupports *aSubject,
689 const char *aTopic, const char16_t *aData)
691 NS_ASSERTION(mAppShell, "appshell service notified before appshell built");
692 if (!strcmp(aTopic, "quit-application-forced")) {
693 mShuttingDown = true;
695 else if (!strcmp(aTopic, "profile-change-teardown")) {
696 if (!mShuttingDown) {
697 EnterLastWindowClosingSurvivalArea();
698 CloseAllWindows();
699 ExitLastWindowClosingSurvivalArea();
701 } else if (!strcmp(aTopic, "xul-window-registered")) {
702 EnterLastWindowClosingSurvivalArea();
703 } else if (!strcmp(aTopic, "xul-window-destroyed")) {
704 ExitLastWindowClosingSurvivalArea();
705 } else if (!strcmp(aTopic, "sessionstore-windows-restored")) {
706 StartupTimeline::Record(StartupTimeline::SESSION_RESTORED);
707 IOInterposer::EnteringNextStage();
708 #if defined(XP_WIN)
709 if (mSessionWindowRestoredProbe) {
710 mSessionWindowRestoredProbe->Trigger();
712 } else if (!strcmp(aTopic, "places-init-complete")) {
713 if (mPlacesInitCompleteProbe) {
714 mPlacesInitCompleteProbe->Trigger();
716 #endif //defined(XP_WIN)
717 } else if (!strcmp(aTopic, "sessionstore-init-started")) {
718 StartupTimeline::Record(StartupTimeline::SESSION_RESTORE_INIT);
719 } else if (!strcmp(aTopic, "xpcom-shutdown")) {
720 IOInterposer::EnteringNextStage();
721 #if defined(XP_WIN)
722 if (mXPCOMShutdownProbe) {
723 mXPCOMShutdownProbe->Trigger();
725 #endif // defined(XP_WIN)
726 } else if (!strcmp(aTopic, "quit-application")) {
727 StartupTimeline::Record(StartupTimeline::QUIT_APPLICATION);
728 } else if (!strcmp(aTopic, "profile-before-change")) {
729 StartupTimeline::Record(StartupTimeline::PROFILE_BEFORE_CHANGE);
730 } else {
731 NS_ERROR("Unexpected observer topic.");
734 return NS_OK;
737 NS_IMETHODIMP
738 nsAppStartup::GetStartupInfo(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval)
740 JS::Rooted<JSObject*> obj(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr()));
742 aRetval.setObject(*obj);
744 TimeStamp procTime = StartupTimeline::Get(StartupTimeline::PROCESS_CREATION);
745 TimeStamp now = TimeStamp::Now();
746 PRTime absNow = PR_Now();
748 if (procTime.IsNull()) {
749 bool error = false;
751 procTime = TimeStamp::ProcessCreation(error);
753 if (error) {
754 Telemetry::Accumulate(Telemetry::STARTUP_MEASUREMENT_ERRORS,
755 StartupTimeline::PROCESS_CREATION);
758 StartupTimeline::Record(StartupTimeline::PROCESS_CREATION, procTime);
761 for (int i = StartupTimeline::PROCESS_CREATION;
762 i < StartupTimeline::MAX_EVENT_ID;
763 ++i)
765 StartupTimeline::Event ev = static_cast<StartupTimeline::Event>(i);
766 TimeStamp stamp = StartupTimeline::Get(ev);
768 if (stamp.IsNull() && (ev == StartupTimeline::MAIN)) {
769 // Always define main to aid with bug 689256.
770 stamp = procTime;
771 MOZ_ASSERT(!stamp.IsNull());
772 Telemetry::Accumulate(Telemetry::STARTUP_MEASUREMENT_ERRORS,
773 StartupTimeline::MAIN);
776 if (!stamp.IsNull()) {
777 if (stamp >= procTime) {
778 PRTime prStamp = ComputeAbsoluteTimestamp(absNow, now, stamp)
779 / PR_USEC_PER_MSEC;
780 JS::Rooted<JSObject*> date(aCx, JS_NewDateObjectMsec(aCx, prStamp));
781 JS_DefineProperty(aCx, obj, StartupTimeline::Describe(ev), date, JSPROP_ENUMERATE);
782 } else {
783 Telemetry::Accumulate(Telemetry::STARTUP_MEASUREMENT_ERRORS, ev);
788 return NS_OK;
791 NS_IMETHODIMP
792 nsAppStartup::GetAutomaticSafeModeNecessary(bool *_retval)
794 NS_ENSURE_ARG_POINTER(_retval);
796 bool alwaysSafe = false;
797 Preferences::GetBool(kPrefAlwaysUseSafeMode, &alwaysSafe);
799 if (!alwaysSafe) {
800 #if DEBUG
801 mIsSafeModeNecessary = false;
802 #else
803 mIsSafeModeNecessary &= !PR_GetEnv("MOZ_DISABLE_AUTO_SAFE_MODE");
804 #endif
807 *_retval = mIsSafeModeNecessary;
808 return NS_OK;
811 NS_IMETHODIMP
812 nsAppStartup::TrackStartupCrashBegin(bool *aIsSafeModeNecessary)
814 const int32_t MAX_TIME_SINCE_STARTUP = 6 * 60 * 60 * 1000;
815 const int32_t MAX_STARTUP_BUFFER = 10;
816 nsresult rv;
818 mStartupCrashTrackingEnded = false;
820 StartupTimeline::Record(StartupTimeline::STARTUP_CRASH_DETECTION_BEGIN);
822 bool hasLastSuccess = Preferences::HasUserValue(kPrefLastSuccess);
823 if (!hasLastSuccess) {
824 // Clear so we don't get stuck with SafeModeNecessary returning true if we
825 // have had too many recent crashes and the last success pref is missing.
826 Preferences::ClearUser(kPrefRecentCrashes);
827 return NS_ERROR_NOT_AVAILABLE;
830 bool inSafeMode = false;
831 nsCOMPtr<nsIXULRuntime> xr = do_GetService(XULRUNTIME_SERVICE_CONTRACTID);
832 NS_ENSURE_TRUE(xr, NS_ERROR_FAILURE);
834 xr->GetInSafeMode(&inSafeMode);
836 PRTime replacedLockTime;
837 rv = xr->GetReplacedLockTime(&replacedLockTime);
839 if (NS_FAILED(rv) || !replacedLockTime) {
840 if (!inSafeMode)
841 Preferences::ClearUser(kPrefRecentCrashes);
842 GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
843 return NS_OK;
846 // check whether safe mode is necessary
847 int32_t maxResumedCrashes = -1;
848 rv = Preferences::GetInt(kPrefMaxResumedCrashes, &maxResumedCrashes);
849 NS_ENSURE_SUCCESS(rv, NS_OK);
851 int32_t recentCrashes = 0;
852 Preferences::GetInt(kPrefRecentCrashes, &recentCrashes);
853 mIsSafeModeNecessary = (recentCrashes > maxResumedCrashes && maxResumedCrashes != -1);
855 // Bug 731613 - Don't check if the last startup was a crash if XRE_PROFILE_PATH is set. After
856 // profile manager, the profile lock's mod. time has been changed so can't be used on this startup.
857 // After a restart, it's safe to assume the last startup was successful.
858 char *xreProfilePath = PR_GetEnv("XRE_PROFILE_PATH");
859 if (xreProfilePath) {
860 GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
861 return NS_ERROR_NOT_AVAILABLE;
864 // time of last successful startup
865 int32_t lastSuccessfulStartup;
866 rv = Preferences::GetInt(kPrefLastSuccess, &lastSuccessfulStartup);
867 NS_ENSURE_SUCCESS(rv, rv);
869 int32_t lockSeconds = (int32_t)(replacedLockTime / PR_MSEC_PER_SEC);
871 // started close enough to good startup so call it good
872 if (lockSeconds <= lastSuccessfulStartup + MAX_STARTUP_BUFFER
873 && lockSeconds >= lastSuccessfulStartup - MAX_STARTUP_BUFFER) {
874 GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
875 return NS_OK;
878 // sanity check that the pref set at last success is not greater than the current time
879 if (PR_Now() / PR_USEC_PER_SEC <= lastSuccessfulStartup)
880 return NS_ERROR_FAILURE;
882 // The last startup was a crash so include it in the count regardless of when it happened.
883 Telemetry::Accumulate(Telemetry::STARTUP_CRASH_DETECTED, true);
885 if (inSafeMode) {
886 GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
887 return NS_OK;
890 PRTime now = (PR_Now() / PR_USEC_PER_MSEC);
891 // if the last startup attempt which crashed was in the last 6 hours
892 if (replacedLockTime >= now - MAX_TIME_SINCE_STARTUP) {
893 NS_WARNING("Last startup was detected as a crash.");
894 recentCrashes++;
895 rv = Preferences::SetInt(kPrefRecentCrashes, recentCrashes);
896 } else {
897 // Otherwise ignore that crash and all previous since it may not be applicable anymore
898 // and we don't want someone to get stuck in safe mode if their prefs are read-only.
899 rv = Preferences::ClearUser(kPrefRecentCrashes);
901 NS_ENSURE_SUCCESS(rv, rv);
903 // recalculate since recent crashes count may have changed above
904 mIsSafeModeNecessary = (recentCrashes > maxResumedCrashes && maxResumedCrashes != -1);
906 nsCOMPtr<nsIPrefService> prefs = Preferences::GetService();
907 rv = prefs->SavePrefFile(nullptr); // flush prefs to disk since we are tracking crashes
908 NS_ENSURE_SUCCESS(rv, rv);
910 GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
911 return rv;
914 NS_IMETHODIMP
915 nsAppStartup::TrackStartupCrashEnd()
917 bool inSafeMode = false;
918 nsCOMPtr<nsIXULRuntime> xr = do_GetService(XULRUNTIME_SERVICE_CONTRACTID);
919 if (xr)
920 xr->GetInSafeMode(&inSafeMode);
922 // return if we already ended or we're restarting into safe mode
923 if (mStartupCrashTrackingEnded || (mIsSafeModeNecessary && !inSafeMode))
924 return NS_OK;
925 mStartupCrashTrackingEnded = true;
927 StartupTimeline::Record(StartupTimeline::STARTUP_CRASH_DETECTION_END);
929 // Use the timestamp of XRE_main as an approximation for the lock file timestamp.
930 // See MAX_STARTUP_BUFFER for the buffer time period.
931 TimeStamp mainTime = StartupTimeline::Get(StartupTimeline::MAIN);
932 TimeStamp now = TimeStamp::Now();
933 PRTime prNow = PR_Now();
934 nsresult rv;
936 if (mainTime.IsNull()) {
937 NS_WARNING("Could not get StartupTimeline::MAIN time.");
938 } else {
939 uint64_t lockFileTime = ComputeAbsoluteTimestamp(prNow, now, mainTime);
941 rv = Preferences::SetInt(kPrefLastSuccess,
942 (int32_t)(lockFileTime / PR_USEC_PER_SEC));
944 if (NS_FAILED(rv))
945 NS_WARNING("Could not set startup crash detection pref.");
948 if (inSafeMode && mIsSafeModeNecessary) {
949 // On a successful startup in automatic safe mode, allow the user one more crash
950 // in regular mode before returning to safe mode.
951 int32_t maxResumedCrashes = 0;
952 int32_t prefType;
953 rv = Preferences::GetDefaultRootBranch()->GetPrefType(kPrefMaxResumedCrashes, &prefType);
954 NS_ENSURE_SUCCESS(rv, rv);
955 if (prefType == nsIPrefBranch::PREF_INT) {
956 rv = Preferences::GetInt(kPrefMaxResumedCrashes, &maxResumedCrashes);
957 NS_ENSURE_SUCCESS(rv, rv);
959 rv = Preferences::SetInt(kPrefRecentCrashes, maxResumedCrashes);
960 NS_ENSURE_SUCCESS(rv, rv);
961 } else if (!inSafeMode) {
962 // clear the count of recent crashes after a succesful startup when not in safe mode
963 rv = Preferences::ClearUser(kPrefRecentCrashes);
964 if (NS_FAILED(rv)) NS_WARNING("Could not clear startup crash count.");
966 nsCOMPtr<nsIPrefService> prefs = Preferences::GetService();
967 rv = prefs->SavePrefFile(nullptr); // flush prefs to disk since we are tracking crashes
969 return rv;
972 NS_IMETHODIMP
973 nsAppStartup::RestartInSafeMode(uint32_t aQuitMode)
975 PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
976 this->Quit(aQuitMode | nsIAppStartup::eRestart);
978 return NS_OK;