Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
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
61 */
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;
112 }
113 };
115 /**
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.
120 *
121 * @param stamp The timestamp to be converted
122 * @returns The converted timestamp
123 */
124 uint64_t ComputeAbsoluteTimestamp(PRTime prnow, TimeStamp now, TimeStamp stamp)
125 {
126 static PRTime sAbsoluteNow = PR_Now();
127 static TimeStamp sMonotonicNow = TimeStamp::Now();
129 return sAbsoluteNow - (sMonotonicNow - stamp).ToMicroseconds();
130 }
132 //
133 // nsAppStartup
134 //
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)
147 { }
150 nsresult
151 nsAppStartup::Init()
152 {
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-forced", true);
165 os->AddObserver(this, "sessionstore-init-started", true);
166 os->AddObserver(this, "sessionstore-windows-restored", true);
167 os->AddObserver(this, "profile-change-teardown", true);
168 os->AddObserver(this, "xul-window-registered", true);
169 os->AddObserver(this, "xul-window-destroyed", true);
170 os->AddObserver(this, "xpcom-shutdown", true);
172 #if defined(XP_WIN)
173 os->AddObserver(this, "places-init-complete", true);
174 // This last event is only interesting to us for xperf-based measures
176 // Initialize interaction with profiler
177 mProbesManager =
178 new ProbeManager(
179 kApplicationTracingCID,
180 NS_LITERAL_CSTRING("Application startup probe"));
181 // Note: The operation is meant mostly for in-house profiling.
182 // Therefore, we do not warn if probes manager cannot be initialized
184 if (mProbesManager) {
185 mPlacesInitCompleteProbe =
186 mProbesManager->
187 GetProbe(kPlacesInitCompleteCID,
188 NS_LITERAL_CSTRING("places-init-complete"));
189 NS_WARN_IF_FALSE(mPlacesInitCompleteProbe,
190 "Cannot initialize probe 'places-init-complete'");
192 mSessionWindowRestoredProbe =
193 mProbesManager->
194 GetProbe(kSessionStoreWindowRestoredCID,
195 NS_LITERAL_CSTRING("sessionstore-windows-restored"));
196 NS_WARN_IF_FALSE(mSessionWindowRestoredProbe,
197 "Cannot initialize probe 'sessionstore-windows-restored'");
199 mXPCOMShutdownProbe =
200 mProbesManager->
201 GetProbe(kXPCOMShutdownCID,
202 NS_LITERAL_CSTRING("xpcom-shutdown"));
203 NS_WARN_IF_FALSE(mXPCOMShutdownProbe,
204 "Cannot initialize probe 'xpcom-shutdown'");
206 rv = mProbesManager->StartSession();
207 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
208 "Cannot initialize system probe manager");
209 }
210 #endif //defined(XP_WIN)
212 return NS_OK;
213 }
216 //
217 // nsAppStartup->nsISupports
218 //
220 NS_IMPL_ISUPPORTS(nsAppStartup,
221 nsIAppStartup,
222 nsIWindowCreator,
223 nsIWindowCreator2,
224 nsIObserver,
225 nsISupportsWeakReference)
228 //
229 // nsAppStartup->nsIAppStartup
230 //
232 NS_IMETHODIMP
233 nsAppStartup::CreateHiddenWindow()
234 {
235 #ifdef MOZ_WIDGET_GONK
236 return NS_OK;
237 #else
238 nsCOMPtr<nsIAppShellService> appShellService
239 (do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
240 NS_ENSURE_TRUE(appShellService, NS_ERROR_FAILURE);
242 return appShellService->CreateHiddenWindow();
243 #endif
244 }
247 NS_IMETHODIMP
248 nsAppStartup::DestroyHiddenWindow()
249 {
250 #ifdef MOZ_WIDGET_GONK
251 return NS_OK;
252 #else
253 nsCOMPtr<nsIAppShellService> appShellService
254 (do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
255 NS_ENSURE_TRUE(appShellService, NS_ERROR_FAILURE);
257 return appShellService->DestroyHiddenWindow();
258 #endif
259 }
261 NS_IMETHODIMP
262 nsAppStartup::Run(void)
263 {
264 NS_ASSERTION(!mRunning, "Reentrant appstartup->Run()");
266 // If we have no windows open and no explicit calls to
267 // enterLastWindowClosingSurvivalArea, or somebody has explicitly called
268 // quit, don't bother running the event loop which would probably leave us
269 // with a zombie process.
271 if (!mShuttingDown && mConsiderQuitStopper != 0) {
272 #ifdef XP_MACOSX
273 EnterLastWindowClosingSurvivalArea();
274 #endif
276 mRunning = true;
278 nsresult rv = mAppShell->Run();
279 if (NS_FAILED(rv))
280 return rv;
281 }
283 nsresult retval = NS_OK;
284 if (mRestartTouchEnvironment) {
285 retval = NS_SUCCESS_RESTART_METRO_APP;
286 } else if (mRestart) {
287 retval = NS_SUCCESS_RESTART_APP;
288 }
290 return retval;
291 }
295 NS_IMETHODIMP
296 nsAppStartup::Quit(uint32_t aMode)
297 {
298 uint32_t ferocity = (aMode & 0xF);
300 // Quit the application. We will asynchronously call the appshell's
301 // Exit() method via nsAppExitEvent to allow one last pass
302 // through any events in the queue. This guarantees a tidy cleanup.
303 nsresult rv = NS_OK;
304 bool postedExitEvent = false;
306 if (mShuttingDown)
307 return NS_OK;
309 // If we're considering quitting, we will only do so if:
310 if (ferocity == eConsiderQuit) {
311 #ifdef XP_MACOSX
312 nsCOMPtr<nsIAppShellService> appShell
313 (do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
314 bool hasHiddenPrivateWindow = false;
315 if (appShell) {
316 appShell->GetHasHiddenPrivateWindow(&hasHiddenPrivateWindow);
317 }
318 int32_t suspiciousCount = hasHiddenPrivateWindow ? 2 : 1;
319 #endif
321 if (mConsiderQuitStopper == 0) {
322 // there are no windows...
323 ferocity = eAttemptQuit;
324 }
325 #ifdef XP_MACOSX
326 else if (mConsiderQuitStopper == suspiciousCount) {
327 // ... or there is only a hiddenWindow left, and it's useless:
329 // Failure shouldn't be fatal, but will abort quit attempt:
330 if (!appShell)
331 return NS_OK;
333 bool usefulHiddenWindow;
334 appShell->GetApplicationProvidedHiddenWindow(&usefulHiddenWindow);
335 nsCOMPtr<nsIXULWindow> hiddenWindow;
336 appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow));
337 // If the remaining windows are useful, we won't quit:
338 nsCOMPtr<nsIXULWindow> hiddenPrivateWindow;
339 if (hasHiddenPrivateWindow) {
340 appShell->GetHiddenPrivateWindow(getter_AddRefs(hiddenPrivateWindow));
341 if ((!hiddenWindow && !hiddenPrivateWindow) || usefulHiddenWindow)
342 return NS_OK;
343 } else if (!hiddenWindow || usefulHiddenWindow) {
344 return NS_OK;
345 }
347 ferocity = eAttemptQuit;
348 }
349 #endif
350 }
352 nsCOMPtr<nsIObserverService> obsService;
353 if (ferocity == eAttemptQuit || ferocity == eForceQuit) {
355 nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
356 nsCOMPtr<nsIWindowMediator> mediator (do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
357 if (mediator) {
358 mediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator));
359 if (windowEnumerator) {
360 bool more;
361 while (windowEnumerator->HasMoreElements(&more), more) {
362 nsCOMPtr<nsISupports> window;
363 windowEnumerator->GetNext(getter_AddRefs(window));
364 nsCOMPtr<nsPIDOMWindow> domWindow(do_QueryInterface(window));
365 if (domWindow) {
366 if (!domWindow->CanClose())
367 return NS_OK;
368 }
369 }
370 }
371 }
373 PROFILER_MARKER("Shutdown start");
374 mozilla::RecordShutdownStartTimeStamp();
375 mShuttingDown = true;
376 if (!mRestart) {
377 mRestart = (aMode & eRestart) != 0;
378 gRestartMode = (aMode & 0xF0);
379 }
381 if (!mRestartTouchEnvironment) {
382 mRestartTouchEnvironment = (aMode & eRestartTouchEnvironment) != 0;
383 gRestartMode = (aMode & 0xF0);
384 }
386 if (mRestart || mRestartTouchEnvironment) {
387 // Mark the next startup as a restart.
388 PR_SetEnv("MOZ_APP_RESTART=1");
390 /* Firefox-restarts reuse the process so regular process start-time isn't
391 a useful indicator of startup time anymore. */
392 TimeStamp::RecordProcessRestart();
393 }
395 obsService = mozilla::services::GetObserverService();
397 if (!mAttemptingQuit) {
398 mAttemptingQuit = true;
399 #ifdef XP_MACOSX
400 // now even the Mac wants to quit when the last window is closed
401 ExitLastWindowClosingSurvivalArea();
402 #endif
403 if (obsService)
404 obsService->NotifyObservers(nullptr, "quit-application-granted", nullptr);
405 }
407 /* Enumerate through each open window and close it. It's important to do
408 this before we forcequit because this can control whether we really quit
409 at all. e.g. if one of these windows has an unload handler that
410 opens a new window. Ugh. I know. */
411 CloseAllWindows();
413 if (mediator) {
414 if (ferocity == eAttemptQuit) {
415 ferocity = eForceQuit; // assume success
417 /* Were we able to immediately close all windows? if not, eAttemptQuit
418 failed. This could happen for a variety of reasons; in fact it's
419 very likely. Perhaps we're being called from JS and the window->Close
420 method hasn't had a chance to wrap itself up yet. So give up.
421 We'll return (with eConsiderQuit) as the remaining windows are
422 closed. */
423 mediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator));
424 if (windowEnumerator) {
425 bool more;
426 while (windowEnumerator->HasMoreElements(&more), more) {
427 /* we can't quit immediately. we'll try again as the last window
428 finally closes. */
429 ferocity = eAttemptQuit;
430 nsCOMPtr<nsISupports> window;
431 windowEnumerator->GetNext(getter_AddRefs(window));
432 nsCOMPtr<nsIDOMWindow> domWindow = do_QueryInterface(window);
433 if (domWindow) {
434 bool closed = false;
435 domWindow->GetClosed(&closed);
436 if (!closed) {
437 rv = NS_ERROR_FAILURE;
438 break;
439 }
440 }
441 }
442 }
443 }
444 }
445 }
447 if (ferocity == eForceQuit) {
448 // do it!
450 // No chance of the shutdown being cancelled from here on; tell people
451 // we're shutting down for sure while all services are still available.
452 if (obsService) {
453 NS_NAMED_LITERAL_STRING(shutdownStr, "shutdown");
454 NS_NAMED_LITERAL_STRING(restartStr, "restart");
455 obsService->NotifyObservers(nullptr, "quit-application",
456 (mRestart || mRestartTouchEnvironment) ?
457 restartStr.get() : shutdownStr.get());
458 }
460 if (!mRunning) {
461 postedExitEvent = true;
462 }
463 else {
464 // no matter what, make sure we send the exit event. If
465 // worst comes to worst, we'll do a leaky shutdown but we WILL
466 // shut down. Well, assuming that all *this* stuff works ;-).
467 nsCOMPtr<nsIRunnable> event = new nsAppExitEvent(this);
468 rv = NS_DispatchToCurrentThread(event);
469 if (NS_SUCCEEDED(rv)) {
470 postedExitEvent = true;
471 }
472 else {
473 NS_WARNING("failed to dispatch nsAppExitEvent");
474 }
475 }
476 }
478 // turn off the reentrancy check flag, but not if we have
479 // more asynchronous work to do still.
480 if (!postedExitEvent)
481 mShuttingDown = false;
482 return rv;
483 }
486 void
487 nsAppStartup::CloseAllWindows()
488 {
489 nsCOMPtr<nsIWindowMediator> mediator
490 (do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
492 nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
494 mediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator));
496 if (!windowEnumerator)
497 return;
499 bool more;
500 while (NS_SUCCEEDED(windowEnumerator->HasMoreElements(&more)) && more) {
501 nsCOMPtr<nsISupports> isupports;
502 if (NS_FAILED(windowEnumerator->GetNext(getter_AddRefs(isupports))))
503 break;
505 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(isupports);
506 NS_ASSERTION(window, "not an nsPIDOMWindow");
507 if (window)
508 window->ForceClose();
509 }
510 }
512 NS_IMETHODIMP
513 nsAppStartup::EnterLastWindowClosingSurvivalArea(void)
514 {
515 ++mConsiderQuitStopper;
516 return NS_OK;
517 }
520 NS_IMETHODIMP
521 nsAppStartup::ExitLastWindowClosingSurvivalArea(void)
522 {
523 NS_ASSERTION(mConsiderQuitStopper > 0, "consider quit stopper out of bounds");
524 --mConsiderQuitStopper;
526 if (mRunning)
527 Quit(eConsiderQuit);
529 return NS_OK;
530 }
532 //
533 // nsAppStartup->nsIAppStartup2
534 //
536 NS_IMETHODIMP
537 nsAppStartup::GetShuttingDown(bool *aResult)
538 {
539 *aResult = mShuttingDown;
540 return NS_OK;
541 }
543 NS_IMETHODIMP
544 nsAppStartup::GetStartingUp(bool *aResult)
545 {
546 *aResult = mStartingUp;
547 return NS_OK;
548 }
550 NS_IMETHODIMP
551 nsAppStartup::DoneStartingUp()
552 {
553 // This must be called once at most
554 MOZ_ASSERT(mStartingUp);
556 mStartingUp = false;
557 return NS_OK;
558 }
560 NS_IMETHODIMP
561 nsAppStartup::GetRestarting(bool *aResult)
562 {
563 *aResult = mRestart;
564 return NS_OK;
565 }
567 NS_IMETHODIMP
568 nsAppStartup::GetWasRestarted(bool *aResult)
569 {
570 char *mozAppRestart = PR_GetEnv("MOZ_APP_RESTART");
572 /* When calling PR_SetEnv() with an empty value the existing variable may
573 * be unset or set to the empty string depending on the underlying platform
574 * thus we have to check if the variable is present and not empty. */
575 *aResult = mozAppRestart && (strcmp(mozAppRestart, "") != 0);
577 return NS_OK;
578 }
580 NS_IMETHODIMP
581 nsAppStartup::GetRestartingTouchEnvironment(bool *aResult)
582 {
583 NS_ENSURE_ARG_POINTER(aResult);
584 *aResult = mRestartTouchEnvironment;
585 return NS_OK;
586 }
588 NS_IMETHODIMP
589 nsAppStartup::SetInterrupted(bool aInterrupted)
590 {
591 mInterrupted = aInterrupted;
592 return NS_OK;
593 }
595 NS_IMETHODIMP
596 nsAppStartup::GetInterrupted(bool *aInterrupted)
597 {
598 *aInterrupted = mInterrupted;
599 return NS_OK;
600 }
602 //
603 // nsAppStartup->nsIWindowCreator
604 //
606 NS_IMETHODIMP
607 nsAppStartup::CreateChromeWindow(nsIWebBrowserChrome *aParent,
608 uint32_t aChromeFlags,
609 nsIWebBrowserChrome **_retval)
610 {
611 bool cancel;
612 return CreateChromeWindow2(aParent, aChromeFlags, 0, 0, &cancel, _retval);
613 }
616 //
617 // nsAppStartup->nsIWindowCreator2
618 //
620 NS_IMETHODIMP
621 nsAppStartup::CreateChromeWindow2(nsIWebBrowserChrome *aParent,
622 uint32_t aChromeFlags,
623 uint32_t aContextFlags,
624 nsIURI *aURI,
625 bool *aCancel,
626 nsIWebBrowserChrome **_retval)
627 {
628 NS_ENSURE_ARG_POINTER(aCancel);
629 NS_ENSURE_ARG_POINTER(_retval);
630 *aCancel = false;
631 *_retval = 0;
633 // Non-modal windows cannot be opened if we are attempting to quit
634 if (mAttemptingQuit && (aChromeFlags & nsIWebBrowserChrome::CHROME_MODAL) == 0)
635 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
637 nsCOMPtr<nsIXULWindow> newWindow;
639 if (aParent) {
640 nsCOMPtr<nsIXULWindow> xulParent(do_GetInterface(aParent));
641 NS_ASSERTION(xulParent, "window created using non-XUL parent. that's unexpected, but may work.");
643 if (xulParent)
644 xulParent->CreateNewWindow(aChromeFlags, getter_AddRefs(newWindow));
645 // And if it fails, don't try again without a parent. It could fail
646 // intentionally (bug 115969).
647 } else { // try using basic methods:
648 /* You really shouldn't be making dependent windows without a parent.
649 But unparented modal (and therefore dependent) windows happen
650 in our codebase, so we allow it after some bellyaching: */
651 if (aChromeFlags & nsIWebBrowserChrome::CHROME_DEPENDENT)
652 NS_WARNING("dependent window created without a parent");
654 nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
655 if (!appShell)
656 return NS_ERROR_FAILURE;
658 appShell->CreateTopLevelWindow(0, 0, aChromeFlags,
659 nsIAppShellService::SIZE_TO_CONTENT,
660 nsIAppShellService::SIZE_TO_CONTENT,
661 getter_AddRefs(newWindow));
662 }
664 // if anybody gave us anything to work with, use it
665 if (newWindow) {
666 newWindow->SetContextFlags(aContextFlags);
667 nsCOMPtr<nsIInterfaceRequestor> thing(do_QueryInterface(newWindow));
668 if (thing)
669 CallGetInterface(thing.get(), _retval);
670 }
672 return *_retval ? NS_OK : NS_ERROR_FAILURE;
673 }
676 //
677 // nsAppStartup->nsIObserver
678 //
680 NS_IMETHODIMP
681 nsAppStartup::Observe(nsISupports *aSubject,
682 const char *aTopic, const char16_t *aData)
683 {
684 NS_ASSERTION(mAppShell, "appshell service notified before appshell built");
685 if (!strcmp(aTopic, "quit-application-forced")) {
686 mShuttingDown = true;
687 }
688 else if (!strcmp(aTopic, "profile-change-teardown")) {
689 if (!mShuttingDown) {
690 EnterLastWindowClosingSurvivalArea();
691 CloseAllWindows();
692 ExitLastWindowClosingSurvivalArea();
693 }
694 } else if (!strcmp(aTopic, "xul-window-registered")) {
695 EnterLastWindowClosingSurvivalArea();
696 } else if (!strcmp(aTopic, "xul-window-destroyed")) {
697 ExitLastWindowClosingSurvivalArea();
698 } else if (!strcmp(aTopic, "sessionstore-windows-restored")) {
699 StartupTimeline::Record(StartupTimeline::SESSION_RESTORED);
700 IOInterposer::EnteringNextStage();
701 #if defined(XP_WIN)
702 if (mSessionWindowRestoredProbe) {
703 mSessionWindowRestoredProbe->Trigger();
704 }
705 } else if (!strcmp(aTopic, "places-init-complete")) {
706 if (mPlacesInitCompleteProbe) {
707 mPlacesInitCompleteProbe->Trigger();
708 }
709 #endif //defined(XP_WIN)
710 } else if (!strcmp(aTopic, "sessionstore-init-started")) {
711 StartupTimeline::Record(StartupTimeline::SESSION_RESTORE_INIT);
712 } else if (!strcmp(aTopic, "xpcom-shutdown")) {
713 IOInterposer::EnteringNextStage();
714 #if defined(XP_WIN)
715 if (mXPCOMShutdownProbe) {
716 mXPCOMShutdownProbe->Trigger();
717 }
718 #endif // defined(XP_WIN)
719 } else {
720 NS_ERROR("Unexpected observer topic.");
721 }
723 return NS_OK;
724 }
726 NS_IMETHODIMP
727 nsAppStartup::GetStartupInfo(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval)
728 {
729 JS::Rooted<JSObject*> obj(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr()));
731 aRetval.setObject(*obj);
733 TimeStamp procTime = StartupTimeline::Get(StartupTimeline::PROCESS_CREATION);
734 TimeStamp now = TimeStamp::Now();
735 PRTime absNow = PR_Now();
737 if (procTime.IsNull()) {
738 bool error = false;
740 procTime = TimeStamp::ProcessCreation(error);
742 if (error) {
743 Telemetry::Accumulate(Telemetry::STARTUP_MEASUREMENT_ERRORS,
744 StartupTimeline::PROCESS_CREATION);
745 }
747 StartupTimeline::Record(StartupTimeline::PROCESS_CREATION, procTime);
748 }
750 for (int i = StartupTimeline::PROCESS_CREATION;
751 i < StartupTimeline::MAX_EVENT_ID;
752 ++i)
753 {
754 StartupTimeline::Event ev = static_cast<StartupTimeline::Event>(i);
755 TimeStamp stamp = StartupTimeline::Get(ev);
757 if (stamp.IsNull() && (ev == StartupTimeline::MAIN)) {
758 // Always define main to aid with bug 689256.
759 stamp = procTime;
760 MOZ_ASSERT(!stamp.IsNull());
761 Telemetry::Accumulate(Telemetry::STARTUP_MEASUREMENT_ERRORS,
762 StartupTimeline::MAIN);
763 }
765 if (!stamp.IsNull()) {
766 if (stamp >= procTime) {
767 PRTime prStamp = ComputeAbsoluteTimestamp(absNow, now, stamp)
768 / PR_USEC_PER_MSEC;
769 JS::Rooted<JSObject*> date(aCx, JS_NewDateObjectMsec(aCx, prStamp));
770 JS_DefineProperty(aCx, obj, StartupTimeline::Describe(ev), date, JSPROP_ENUMERATE);
771 } else {
772 Telemetry::Accumulate(Telemetry::STARTUP_MEASUREMENT_ERRORS, ev);
773 }
774 }
775 }
777 return NS_OK;
778 }
780 NS_IMETHODIMP
781 nsAppStartup::GetAutomaticSafeModeNecessary(bool *_retval)
782 {
783 NS_ENSURE_ARG_POINTER(_retval);
785 bool alwaysSafe = false;
786 Preferences::GetBool(kPrefAlwaysUseSafeMode, &alwaysSafe);
788 if (!alwaysSafe) {
789 #if DEBUG
790 mIsSafeModeNecessary = false;
791 #else
792 mIsSafeModeNecessary &= !PR_GetEnv("MOZ_DISABLE_AUTO_SAFE_MODE");
793 #endif
794 }
796 *_retval = mIsSafeModeNecessary;
797 return NS_OK;
798 }
800 NS_IMETHODIMP
801 nsAppStartup::TrackStartupCrashBegin(bool *aIsSafeModeNecessary)
802 {
803 const int32_t MAX_TIME_SINCE_STARTUP = 6 * 60 * 60 * 1000;
804 const int32_t MAX_STARTUP_BUFFER = 10;
805 nsresult rv;
807 mStartupCrashTrackingEnded = false;
809 StartupTimeline::Record(StartupTimeline::STARTUP_CRASH_DETECTION_BEGIN);
811 bool hasLastSuccess = Preferences::HasUserValue(kPrefLastSuccess);
812 if (!hasLastSuccess) {
813 // Clear so we don't get stuck with SafeModeNecessary returning true if we
814 // have had too many recent crashes and the last success pref is missing.
815 Preferences::ClearUser(kPrefRecentCrashes);
816 return NS_ERROR_NOT_AVAILABLE;
817 }
819 bool inSafeMode = false;
820 nsCOMPtr<nsIXULRuntime> xr = do_GetService(XULRUNTIME_SERVICE_CONTRACTID);
821 NS_ENSURE_TRUE(xr, NS_ERROR_FAILURE);
823 xr->GetInSafeMode(&inSafeMode);
825 PRTime replacedLockTime;
826 rv = xr->GetReplacedLockTime(&replacedLockTime);
828 if (NS_FAILED(rv) || !replacedLockTime) {
829 if (!inSafeMode)
830 Preferences::ClearUser(kPrefRecentCrashes);
831 GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
832 return NS_OK;
833 }
835 // check whether safe mode is necessary
836 int32_t maxResumedCrashes = -1;
837 rv = Preferences::GetInt(kPrefMaxResumedCrashes, &maxResumedCrashes);
838 NS_ENSURE_SUCCESS(rv, NS_OK);
840 int32_t recentCrashes = 0;
841 Preferences::GetInt(kPrefRecentCrashes, &recentCrashes);
842 mIsSafeModeNecessary = (recentCrashes > maxResumedCrashes && maxResumedCrashes != -1);
844 // Bug 731613 - Don't check if the last startup was a crash if XRE_PROFILE_PATH is set. After
845 // profile manager, the profile lock's mod. time has been changed so can't be used on this startup.
846 // After a restart, it's safe to assume the last startup was successful.
847 char *xreProfilePath = PR_GetEnv("XRE_PROFILE_PATH");
848 if (xreProfilePath) {
849 GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
850 return NS_ERROR_NOT_AVAILABLE;
851 }
853 // time of last successful startup
854 int32_t lastSuccessfulStartup;
855 rv = Preferences::GetInt(kPrefLastSuccess, &lastSuccessfulStartup);
856 NS_ENSURE_SUCCESS(rv, rv);
858 int32_t lockSeconds = (int32_t)(replacedLockTime / PR_MSEC_PER_SEC);
860 // started close enough to good startup so call it good
861 if (lockSeconds <= lastSuccessfulStartup + MAX_STARTUP_BUFFER
862 && lockSeconds >= lastSuccessfulStartup - MAX_STARTUP_BUFFER) {
863 GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
864 return NS_OK;
865 }
867 // sanity check that the pref set at last success is not greater than the current time
868 if (PR_Now() / PR_USEC_PER_SEC <= lastSuccessfulStartup)
869 return NS_ERROR_FAILURE;
871 // The last startup was a crash so include it in the count regardless of when it happened.
872 Telemetry::Accumulate(Telemetry::STARTUP_CRASH_DETECTED, true);
874 if (inSafeMode) {
875 GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
876 return NS_OK;
877 }
879 PRTime now = (PR_Now() / PR_USEC_PER_MSEC);
880 // if the last startup attempt which crashed was in the last 6 hours
881 if (replacedLockTime >= now - MAX_TIME_SINCE_STARTUP) {
882 NS_WARNING("Last startup was detected as a crash.");
883 recentCrashes++;
884 rv = Preferences::SetInt(kPrefRecentCrashes, recentCrashes);
885 } else {
886 // Otherwise ignore that crash and all previous since it may not be applicable anymore
887 // and we don't want someone to get stuck in safe mode if their prefs are read-only.
888 rv = Preferences::ClearUser(kPrefRecentCrashes);
889 }
890 NS_ENSURE_SUCCESS(rv, rv);
892 // recalculate since recent crashes count may have changed above
893 mIsSafeModeNecessary = (recentCrashes > maxResumedCrashes && maxResumedCrashes != -1);
895 nsCOMPtr<nsIPrefService> prefs = Preferences::GetService();
896 rv = prefs->SavePrefFile(nullptr); // flush prefs to disk since we are tracking crashes
897 NS_ENSURE_SUCCESS(rv, rv);
899 GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
900 return rv;
901 }
903 NS_IMETHODIMP
904 nsAppStartup::TrackStartupCrashEnd()
905 {
906 bool inSafeMode = false;
907 nsCOMPtr<nsIXULRuntime> xr = do_GetService(XULRUNTIME_SERVICE_CONTRACTID);
908 if (xr)
909 xr->GetInSafeMode(&inSafeMode);
911 // return if we already ended or we're restarting into safe mode
912 if (mStartupCrashTrackingEnded || (mIsSafeModeNecessary && !inSafeMode))
913 return NS_OK;
914 mStartupCrashTrackingEnded = true;
916 StartupTimeline::Record(StartupTimeline::STARTUP_CRASH_DETECTION_END);
918 // Use the timestamp of XRE_main as an approximation for the lock file timestamp.
919 // See MAX_STARTUP_BUFFER for the buffer time period.
920 TimeStamp mainTime = StartupTimeline::Get(StartupTimeline::MAIN);
921 TimeStamp now = TimeStamp::Now();
922 PRTime prNow = PR_Now();
923 nsresult rv;
925 if (mainTime.IsNull()) {
926 NS_WARNING("Could not get StartupTimeline::MAIN time.");
927 } else {
928 uint64_t lockFileTime = ComputeAbsoluteTimestamp(prNow, now, mainTime);
930 rv = Preferences::SetInt(kPrefLastSuccess,
931 (int32_t)(lockFileTime / PR_USEC_PER_SEC));
933 if (NS_FAILED(rv))
934 NS_WARNING("Could not set startup crash detection pref.");
935 }
937 if (inSafeMode && mIsSafeModeNecessary) {
938 // On a successful startup in automatic safe mode, allow the user one more crash
939 // in regular mode before returning to safe mode.
940 int32_t maxResumedCrashes = 0;
941 int32_t prefType;
942 rv = Preferences::GetDefaultRootBranch()->GetPrefType(kPrefMaxResumedCrashes, &prefType);
943 NS_ENSURE_SUCCESS(rv, rv);
944 if (prefType == nsIPrefBranch::PREF_INT) {
945 rv = Preferences::GetInt(kPrefMaxResumedCrashes, &maxResumedCrashes);
946 NS_ENSURE_SUCCESS(rv, rv);
947 }
948 rv = Preferences::SetInt(kPrefRecentCrashes, maxResumedCrashes);
949 NS_ENSURE_SUCCESS(rv, rv);
950 } else if (!inSafeMode) {
951 // clear the count of recent crashes after a succesful startup when not in safe mode
952 rv = Preferences::ClearUser(kPrefRecentCrashes);
953 if (NS_FAILED(rv)) NS_WARNING("Could not clear startup crash count.");
954 }
955 nsCOMPtr<nsIPrefService> prefs = Preferences::GetService();
956 rv = prefs->SavePrefFile(nullptr); // flush prefs to disk since we are tracking crashes
958 return rv;
959 }
961 NS_IMETHODIMP
962 nsAppStartup::RestartInSafeMode(uint32_t aQuitMode)
963 {
964 PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
965 this->Quit(aQuitMode | nsIAppStartup::eRestart);
967 return NS_OK;
968 }