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/. */
5 
6 #include "nsAppStartup.h"
7 
8 #include "nsComponentManagerUtils.h"
9 #include "nsIAppShellService.h"
10 #include "nsPIDOMWindow.h"
11 #include "nsIInterfaceRequestor.h"
12 #include "nsIFile.h"
13 #include "nsIObserverService.h"
14 #include "nsIPrefBranch.h"
15 #include "nsIPrefService.h"
16 #include "nsIProcess.h"
17 #include "nsIToolkitProfile.h"
18 #include "nsIWebBrowserChrome.h"
19 #include "nsIWindowMediator.h"
20 #include "nsIXULRuntime.h"
21 #include "nsIAppWindow.h"
22 #include "nsNativeCharsetUtils.h"
23 #include "nsThreadUtils.h"
24 #include "nsString.h"
25 #include "mozilla/AppShutdown.h"
26 #include "mozilla/Preferences.h"
27 #include "mozilla/ProfilerMarkers.h"
28 #include "mozilla/ResultExtensions.h"
29 #include "mozilla/Unused.h"
30 
31 #include "GeckoProfiler.h"
32 #include "prprf.h"
33 #include "nsIInterfaceRequestorUtils.h"
34 #include "nsWidgetsCID.h"
35 #include "nsAppRunner.h"
36 #include "nsAppShellCID.h"
37 #include "nsXPCOMCIDInternal.h"
38 #include "mozilla/Services.h"
39 #include "jsapi.h"
40 #include "js/Date.h"
41 #include "prenv.h"
42 #include "nsAppDirectoryServiceDefs.h"
43 
44 #if defined(XP_WIN)
45 // Prevent collisions with nsAppStartup::GetStartupInfo()
46 #  undef GetStartupInfo
47 
48 #  include <windows.h>
49 #elif defined(XP_DARWIN)
50 #  include <mach/mach_time.h>
51 #endif
52 
53 #include "mozilla/IOInterposer.h"
54 #include "mozilla/Telemetry.h"
55 #include "mozilla/StartupTimeline.h"
56 
57 static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
58 
59 #define kPrefLastSuccess "toolkit.startup.last_success"
60 #define kPrefMaxResumedCrashes "toolkit.startup.max_resumed_crashes"
61 #define kPrefRecentCrashes "toolkit.startup.recent_crashes"
62 #define kPrefAlwaysUseSafeMode "toolkit.startup.always_use_safe_mode"
63 
64 #define kNanosecondsPerSecond 1000000000.0
65 
66 #if defined(XP_WIN)
67 #  include "mozilla/PreXULSkeletonUI.h"
68 
69 #  include "mozilla/perfprobe.h"
70 /**
71  * Events sent to the system for profiling purposes
72  */
73 // Keep them syncronized with the .mof file
74 
75 // Process-wide GUID, used by the OS to differentiate sources
76 // {509962E0-406B-46F4-99BA-5A009F8D2225}
77 // Keep it synchronized with the .mof file
78 #  define NS_APPLICATION_TRACING_CID                   \
79     {                                                  \
80       0x509962E0, 0x406B, 0x46F4, {                    \
81         0x99, 0xBA, 0x5A, 0x00, 0x9F, 0x8D, 0x22, 0x25 \
82       }                                                \
83     }
84 
85 // Event-specific GUIDs, used by the OS to differentiate events
86 // {A3DA04E0-57D7-482A-A1C1-61DA5F95BACB}
87 #  define NS_PLACES_INIT_COMPLETE_EVENT_CID            \
88     {                                                  \
89       0xA3DA04E0, 0x57D7, 0x482A, {                    \
90         0xA1, 0xC1, 0x61, 0xDA, 0x5F, 0x95, 0xBA, 0xCB \
91       }                                                \
92     }
93 // {917B96B1-ECAD-4DAB-A760-8D49027748AE}
94 #  define NS_SESSION_STORE_WINDOW_RESTORED_EVENT_CID   \
95     {                                                  \
96       0x917B96B1, 0xECAD, 0x4DAB, {                    \
97         0xA7, 0x60, 0x8D, 0x49, 0x02, 0x77, 0x48, 0xAE \
98       }                                                \
99     }
100 // {26D1E091-0AE7-4F49-A554-4214445C505C}
101 #  define NS_XPCOM_SHUTDOWN_EVENT_CID                  \
102     {                                                  \
103       0x26D1E091, 0x0AE7, 0x4F49, {                    \
104         0xA5, 0x54, 0x42, 0x14, 0x44, 0x5C, 0x50, 0x5C \
105       }                                                \
106     }
107 
108 static NS_DEFINE_CID(kApplicationTracingCID, NS_APPLICATION_TRACING_CID);
109 static NS_DEFINE_CID(kPlacesInitCompleteCID, NS_PLACES_INIT_COMPLETE_EVENT_CID);
110 static NS_DEFINE_CID(kSessionStoreWindowRestoredCID,
111                      NS_SESSION_STORE_WINDOW_RESTORED_EVENT_CID);
112 static NS_DEFINE_CID(kXPCOMShutdownCID, NS_XPCOM_SHUTDOWN_EVENT_CID);
113 #endif  // defined(XP_WIN)
114 
115 using namespace mozilla;
116 
117 class nsAppExitEvent : public mozilla::Runnable {
118  private:
119   RefPtr<nsAppStartup> mService;
120 
121  public:
nsAppExitEvent(nsAppStartup * service)122   explicit nsAppExitEvent(nsAppStartup* service)
123       : mozilla::Runnable("nsAppExitEvent"), mService(service) {}
124 
Run()125   NS_IMETHOD Run() override {
126     // Tell the appshell to exit
127     mService->mAppShell->Exit();
128 
129     mService->mRunning = false;
130     return NS_OK;
131   }
132 };
133 
134 /**
135  * Computes an approximation of the absolute time represented by @a stamp
136  * which is comparable to those obtained via PR_Now(). If the current absolute
137  * time varies a lot (e.g. DST adjustments) since the first call then the
138  * resulting times may be inconsistent.
139  *
140  * @param stamp The timestamp to be converted
141  * @returns The converted timestamp
142  */
ComputeAbsoluteTimestamp(TimeStamp stamp)143 static uint64_t ComputeAbsoluteTimestamp(TimeStamp stamp) {
144   static PRTime sAbsoluteNow = PR_Now();
145   static TimeStamp sMonotonicNow = TimeStamp::Now();
146 
147   return sAbsoluteNow - (sMonotonicNow - stamp).ToMicroseconds();
148 }
149 
150 //
151 // nsAppStartup
152 //
153 
nsAppStartup()154 nsAppStartup::nsAppStartup()
155     : mConsiderQuitStopper(0),
156       mRunning(false),
157       mShuttingDown(false),
158       mStartingUp(true),
159       mAttemptingQuit(false),
160       mInterrupted(false),
161       mIsSafeModeNecessary(false),
162       mStartupCrashTrackingEnded(false) {}
163 
Init()164 nsresult nsAppStartup::Init() {
165   nsresult rv;
166 
167   // Create widget application shell
168   mAppShell = do_GetService(kAppShellCID, &rv);
169   NS_ENSURE_SUCCESS(rv, rv);
170 
171   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
172   if (!os) return NS_ERROR_FAILURE;
173 
174   os->AddObserver(this, "quit-application", true);
175   os->AddObserver(this, "quit-application-forced", true);
176   os->AddObserver(this, "sessionstore-init-started", true);
177   os->AddObserver(this, "sessionstore-windows-restored", true);
178   os->AddObserver(this, "profile-change-teardown", true);
179   os->AddObserver(this, "xul-window-registered", true);
180   os->AddObserver(this, "xul-window-destroyed", true);
181   os->AddObserver(this, "profile-before-change", true);
182   os->AddObserver(this, "xpcom-shutdown", true);
183 
184 #if defined(XP_WIN)
185   os->AddObserver(this, "places-init-complete", true);
186   // This last event is only interesting to us for xperf-based measures
187 
188   // Initialize interaction with profiler
189   mProbesManager =
190       new ProbeManager(kApplicationTracingCID, "Application startup probe"_ns);
191   // Note: The operation is meant mostly for in-house profiling.
192   // Therefore, we do not warn if probes manager cannot be initialized
193 
194   if (mProbesManager) {
195     mPlacesInitCompleteProbe = mProbesManager->GetProbe(
196         kPlacesInitCompleteCID, "places-init-complete"_ns);
197     NS_WARNING_ASSERTION(mPlacesInitCompleteProbe,
198                          "Cannot initialize probe 'places-init-complete'");
199 
200     mSessionWindowRestoredProbe = mProbesManager->GetProbe(
201         kSessionStoreWindowRestoredCID, "sessionstore-windows-restored"_ns);
202     NS_WARNING_ASSERTION(
203         mSessionWindowRestoredProbe,
204         "Cannot initialize probe 'sessionstore-windows-restored'");
205 
206     mXPCOMShutdownProbe =
207         mProbesManager->GetProbe(kXPCOMShutdownCID, "xpcom-shutdown"_ns);
208     NS_WARNING_ASSERTION(mXPCOMShutdownProbe,
209                          "Cannot initialize probe 'xpcom-shutdown'");
210 
211     rv = mProbesManager->StartSession();
212     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
213                          "Cannot initialize system probe manager");
214   }
215 #endif  // defined(XP_WIN)
216 
217   return NS_OK;
218 }
219 
220 //
221 // nsAppStartup->nsISupports
222 //
223 
NS_IMPL_ISUPPORTS(nsAppStartup,nsIAppStartup,nsIWindowCreator,nsIObserver,nsISupportsWeakReference)224 NS_IMPL_ISUPPORTS(nsAppStartup, nsIAppStartup, nsIWindowCreator, nsIObserver,
225                   nsISupportsWeakReference)
226 
227 //
228 // nsAppStartup->nsIAppStartup
229 //
230 
231 NS_IMETHODIMP
232 nsAppStartup::CreateHiddenWindow() {
233 #if defined(MOZ_WIDGET_UIKIT)
234   return NS_OK;
235 #else
236   nsCOMPtr<nsIAppShellService> appShellService(
237       do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
238   NS_ENSURE_TRUE(appShellService, NS_ERROR_FAILURE);
239 
240   return appShellService->CreateHiddenWindow();
241 #endif
242 }
243 
244 NS_IMETHODIMP
DestroyHiddenWindow()245 nsAppStartup::DestroyHiddenWindow() {
246 #if defined(MOZ_WIDGET_UIKIT)
247   return NS_OK;
248 #else
249   nsCOMPtr<nsIAppShellService> appShellService(
250       do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
251   NS_ENSURE_TRUE(appShellService, NS_ERROR_FAILURE);
252 
253   return appShellService->DestroyHiddenWindow();
254 #endif
255 }
256 
257 NS_IMETHODIMP
Run(void)258 nsAppStartup::Run(void) {
259   NS_ASSERTION(!mRunning, "Reentrant appstartup->Run()");
260 
261   // If we have no windows open and no explicit calls to
262   // enterLastWindowClosingSurvivalArea, or somebody has explicitly called
263   // quit, don't bother running the event loop which would probably leave us
264   // with a zombie process.
265 
266   if (!mShuttingDown && mConsiderQuitStopper != 0) {
267 #ifdef XP_MACOSX
268     EnterLastWindowClosingSurvivalArea();
269 #endif
270 
271     mRunning = true;
272 
273     nsresult rv = mAppShell->Run();
274     if (NS_FAILED(rv)) return rv;
275   }
276 
277   // Make sure that the appropriate quit notifications have been dispatched
278   // regardless of whether the event loop has spun or not. Note that this call
279   // is a no-op if Quit has already been called previously.
280   bool userAllowedQuit = true;
281   Quit(eForceQuit, 0, &userAllowedQuit);
282 
283   nsresult retval = NS_OK;
284   if (mozilla::AppShutdown::IsRestarting()) {
285     retval = NS_SUCCESS_RESTART_APP;
286   }
287 
288   return retval;
289 }
290 
291 NS_IMETHODIMP
Quit(uint32_t aMode,int aExitCode,bool * aUserAllowedQuit)292 nsAppStartup::Quit(uint32_t aMode, int aExitCode, bool* aUserAllowedQuit) {
293   uint32_t ferocity = (aMode & 0xF);
294 
295   // If the shutdown was cancelled due to a hidden window or
296   // because one of the windows was not permitted to be closed,
297   // return NS_OK with |aUserAllowedQuit| = false.
298   *aUserAllowedQuit = false;
299 
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;
305 
306   if (mShuttingDown) return NS_OK;
307 
308   // If we're considering quitting, we will only do so if:
309   if (ferocity == eConsiderQuit) {
310 #ifdef XP_MACOSX
311     nsCOMPtr<nsIAppShellService> appShell(
312         do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
313     int32_t suspiciousCount = 1;
314 #endif
315 
316     if (mConsiderQuitStopper == 0) {
317       // there are no windows...
318       ferocity = eAttemptQuit;
319     }
320 #ifdef XP_MACOSX
321     else if (mConsiderQuitStopper == suspiciousCount) {
322       // ... or there is only a hiddenWindow left, and it's useless:
323 
324       // Failure shouldn't be fatal, but will abort quit attempt:
325       if (!appShell) return NS_OK;
326 
327       bool usefulHiddenWindow;
328       appShell->GetApplicationProvidedHiddenWindow(&usefulHiddenWindow);
329       nsCOMPtr<nsIAppWindow> hiddenWindow;
330       appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow));
331       // If the remaining windows are useful, we won't quit:
332       if (!hiddenWindow || usefulHiddenWindow) {
333         return NS_OK;
334       }
335 
336       ferocity = eAttemptQuit;
337     }
338 #endif
339   }
340 
341   nsCOMPtr<nsIObserverService> obsService;
342   if (ferocity == eAttemptQuit || ferocity == eForceQuit) {
343     nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
344     nsCOMPtr<nsIWindowMediator> mediator(
345         do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
346     if (mediator) {
347       mediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator));
348       if (windowEnumerator) {
349         bool more;
350         windowEnumerator->HasMoreElements(&more);
351         // If we reported no windows, we definitely shouldn't be
352         // iterating any here.
353         MOZ_ASSERT_IF(!mConsiderQuitStopper, !more);
354 
355         while (more) {
356           nsCOMPtr<nsISupports> window;
357           windowEnumerator->GetNext(getter_AddRefs(window));
358           nsCOMPtr<nsPIDOMWindowOuter> domWindow(do_QueryInterface(window));
359           if (domWindow) {
360             if (!domWindow->CanClose()) {
361               return NS_OK;
362             }
363           }
364           windowEnumerator->HasMoreElements(&more);
365         }
366       }
367     }
368 
369     PROFILER_MARKER_UNTYPED("Shutdown start", OTHER);
370     mozilla::RecordShutdownStartTimeStamp();
371 
372     *aUserAllowedQuit = true;
373     mShuttingDown = true;
374     auto shutdownMode = ((aMode & eRestart) != 0)
375                             ? mozilla::AppShutdownMode::Restart
376                             : mozilla::AppShutdownMode::Normal;
377     mozilla::AppShutdown::Init(shutdownMode, aExitCode);
378 
379     if (mozilla::AppShutdown::IsRestarting()) {
380       // Mark the next startup as a restart.
381       PR_SetEnv("MOZ_APP_RESTART=1");
382 
383       /* Firefox-restarts reuse the process so regular process start-time isn't
384          a useful indicator of startup time anymore. */
385       TimeStamp::RecordProcessRestart();
386     }
387 
388     obsService = mozilla::services::GetObserverService();
389 
390     if (!mAttemptingQuit) {
391       mAttemptingQuit = true;
392 #ifdef XP_MACOSX
393       // now even the Mac wants to quit when the last window is closed
394       ExitLastWindowClosingSurvivalArea();
395 #endif
396       if (obsService)
397         obsService->NotifyObservers(nullptr, "quit-application-granted",
398                                     nullptr);
399     }
400 
401     /* Enumerate through each open window and close it. It's important to do
402        this before we forcequit because this can control whether we really quit
403        at all. e.g. if one of these windows has an unload handler that
404        opens a new window. Ugh. I know. */
405     CloseAllWindows();
406 
407     if (mediator) {
408       if (ferocity == eAttemptQuit) {
409         ferocity = eForceQuit;  // assume success
410 
411         /* Were we able to immediately close all windows? if not, eAttemptQuit
412            failed. This could happen for a variety of reasons; in fact it's
413            very likely. Perhaps we're being called from JS and the window->Close
414            method hasn't had a chance to wrap itself up yet. So give up.
415            We'll return (with eConsiderQuit) as the remaining windows are
416            closed. */
417         mediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator));
418         if (windowEnumerator) {
419           bool more;
420           while (NS_SUCCEEDED(windowEnumerator->HasMoreElements(&more)) &&
421                  more) {
422             /* we can't quit immediately. we'll try again as the last window
423                finally closes. */
424             ferocity = eAttemptQuit;
425             nsCOMPtr<nsISupports> window;
426             windowEnumerator->GetNext(getter_AddRefs(window));
427             nsCOMPtr<nsPIDOMWindowOuter> domWindow = do_QueryInterface(window);
428             if (domWindow) {
429               if (!domWindow->Closed()) {
430                 rv = NS_ERROR_FAILURE;
431                 break;
432               }
433             }
434           }
435         }
436       }
437     }
438   }
439 
440   if (ferocity == eForceQuit) {
441     // do it!
442     mozilla::AppShutdown::OnShutdownConfirmed();
443 
444     // No chance of the shutdown being cancelled from here on; tell people
445     // we're shutting down for sure while all services are still available.
446     bool isRestarting = mozilla::AppShutdown::IsRestarting();
447     mozilla::AppShutdown::AdvanceShutdownPhase(
448         mozilla::ShutdownPhase::AppShutdownConfirmed,
449         isRestarting ? u"restart" : u"shutdown");
450 
451     if (!mRunning) {
452       postedExitEvent = true;
453     } else {
454       // no matter what, make sure we send the exit event.  If
455       // worst comes to worst, we'll do a leaky shutdown but we WILL
456       // shut down. Well, assuming that all *this* stuff works ;-).
457       nsCOMPtr<nsIRunnable> event = new nsAppExitEvent(this);
458       rv = NS_DispatchToCurrentThread(event);
459       if (NS_SUCCEEDED(rv)) {
460         postedExitEvent = true;
461       } else {
462         NS_WARNING("failed to dispatch nsAppExitEvent");
463       }
464     }
465   }
466 
467   // turn off the reentrancy check flag, but not if we have
468   // more asynchronous work to do still.
469   if (!postedExitEvent) {
470     mShuttingDown = false;
471   }
472   return rv;
473 }
474 
475 // Ensure ShutdownPhase.h and nsIAppStartup.idl are in sync.
476 static_assert(int(nsIAppStartup::SHUTDOWN_PHASE_NOTINSHUTDOWN) ==
477                       int(mozilla::ShutdownPhase::NotInShutdown) &&
478                   int(nsIAppStartup::SHUTDOWN_PHASE_APPSHUTDOWNCONFIRMED) ==
479                       int(mozilla::ShutdownPhase::AppShutdownConfirmed) &&
480                   int(nsIAppStartup::SHUTDOWN_PHASE_APPSHUTDOWNNETTEARDOWN) ==
481                       int(mozilla::ShutdownPhase::AppShutdownNetTeardown) &&
482                   int(nsIAppStartup::SHUTDOWN_PHASE_APPSHUTDOWNTEARDOWN) ==
483                       int(mozilla::ShutdownPhase::AppShutdownTeardown) &&
484                   int(nsIAppStartup::SHUTDOWN_PHASE_APPSHUTDOWN) ==
485                       int(mozilla::ShutdownPhase::AppShutdown) &&
486                   int(nsIAppStartup::SHUTDOWN_PHASE_APPSHUTDOWNQM) ==
487                       int(mozilla::ShutdownPhase::AppShutdownQM) &&
488                   int(nsIAppStartup::SHUTDOWN_PHASE_APPSHUTDOWNRELEMETRY) ==
489                       int(mozilla::ShutdownPhase::AppShutdownTelemetry) &&
490                   int(nsIAppStartup::SHUTDOWN_PHASE_XPCOMWILLSHUTDOWN) ==
491                       int(mozilla::ShutdownPhase::XPCOMWillShutdown) &&
492                   int(nsIAppStartup::SHUTDOWN_PHASE_XPCOMSHUTDOWN) ==
493                       int(mozilla::ShutdownPhase::XPCOMShutdown),
494               "IDLShutdownPhase values are as expected");
495 
496 // Helper for safe conversion to native shutdown phases.
IDLShutdownPhaseToNative(nsAppStartup::IDLShutdownPhase aPhase)497 Result<ShutdownPhase, nsresult> IDLShutdownPhaseToNative(
498     nsAppStartup::IDLShutdownPhase aPhase) {
499   if (uint8_t(aPhase) <= nsIAppStartup::SHUTDOWN_PHASE_XPCOMSHUTDOWN) {
500     return ShutdownPhase(aPhase);
501   }
502   return Err(NS_ERROR_ILLEGAL_VALUE);
503 }
504 
505 NS_IMETHODIMP
AdvanceShutdownPhase(IDLShutdownPhase aPhase)506 nsAppStartup::AdvanceShutdownPhase(IDLShutdownPhase aPhase) {
507   ShutdownPhase nativePhase;
508   MOZ_TRY_VAR(nativePhase, IDLShutdownPhaseToNative(aPhase));
509   AppShutdown::AdvanceShutdownPhase(nativePhase);
510   return NS_OK;
511 }
512 
513 NS_IMETHODIMP
IsInOrBeyondShutdownPhase(IDLShutdownPhase aPhase,bool * aIsInOrBeyond)514 nsAppStartup::IsInOrBeyondShutdownPhase(IDLShutdownPhase aPhase,
515                                         bool* aIsInOrBeyond) {
516   ShutdownPhase nativePhase;
517   MOZ_TRY_VAR(nativePhase, IDLShutdownPhaseToNative(aPhase));
518   *aIsInOrBeyond = AppShutdown::IsInOrBeyond(nativePhase);
519   return NS_OK;
520 }
521 
CloseAllWindows()522 void nsAppStartup::CloseAllWindows() {
523   nsCOMPtr<nsIWindowMediator> mediator(
524       do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
525 
526   nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
527 
528   mediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator));
529 
530   if (!windowEnumerator) return;
531 
532   bool more;
533   while (NS_SUCCEEDED(windowEnumerator->HasMoreElements(&more)) && more) {
534     nsCOMPtr<nsISupports> isupports;
535     if (NS_FAILED(windowEnumerator->GetNext(getter_AddRefs(isupports)))) break;
536 
537     nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(isupports);
538     NS_ASSERTION(window, "not an nsPIDOMWindow");
539     if (window) {
540       window->ForceClose();
541     }
542   }
543 }
544 
545 NS_IMETHODIMP
EnterLastWindowClosingSurvivalArea(void)546 nsAppStartup::EnterLastWindowClosingSurvivalArea(void) {
547   ++mConsiderQuitStopper;
548   return NS_OK;
549 }
550 
551 NS_IMETHODIMP
ExitLastWindowClosingSurvivalArea(void)552 nsAppStartup::ExitLastWindowClosingSurvivalArea(void) {
553   NS_ASSERTION(mConsiderQuitStopper > 0, "consider quit stopper out of bounds");
554   --mConsiderQuitStopper;
555 
556   if (mRunning) {
557     bool userAllowedQuit = false;
558 
559     // A previous call to Quit may have told all windows to close and then
560     // bailed out waiting for that to happen. This is how we get back into Quit
561     // after each window closes so the exit process can continue when ready.
562     // Make sure to pass along the exit code that was initially passed to Quit.
563     Quit(eConsiderQuit, mozilla::AppShutdown::GetExitCode(), &userAllowedQuit);
564   }
565 
566   return NS_OK;
567 }
568 
569 //
570 // nsAppStartup->nsIAppStartup2
571 //
572 
573 NS_IMETHODIMP
GetShuttingDown(bool * aResult)574 nsAppStartup::GetShuttingDown(bool* aResult) {
575   *aResult = mShuttingDown;
576   return NS_OK;
577 }
578 
579 NS_IMETHODIMP
GetStartingUp(bool * aResult)580 nsAppStartup::GetStartingUp(bool* aResult) {
581   *aResult = mStartingUp;
582   return NS_OK;
583 }
584 
585 NS_IMETHODIMP
DoneStartingUp()586 nsAppStartup::DoneStartingUp() {
587   // This must be called once at most
588   MOZ_ASSERT(mStartingUp);
589 
590   mStartingUp = false;
591   return NS_OK;
592 }
593 
594 NS_IMETHODIMP
GetRestarting(bool * aResult)595 nsAppStartup::GetRestarting(bool* aResult) {
596   *aResult = mozilla::AppShutdown::IsRestarting();
597   return NS_OK;
598 }
599 
600 NS_IMETHODIMP
GetWasRestarted(bool * aResult)601 nsAppStartup::GetWasRestarted(bool* aResult) {
602   char* mozAppRestart = PR_GetEnv("MOZ_APP_RESTART");
603 
604   /* When calling PR_SetEnv() with an empty value the existing variable may
605    * be unset or set to the empty string depending on the underlying platform
606    * thus we have to check if the variable is present and not empty. */
607   *aResult = mozAppRestart && (strcmp(mozAppRestart, "") != 0);
608 
609   return NS_OK;
610 }
611 
612 NS_IMETHODIMP
GetSecondsSinceLastOSRestart(int64_t * aResult)613 nsAppStartup::GetSecondsSinceLastOSRestart(int64_t* aResult) {
614 #if defined(XP_WIN)
615   *aResult = int64_t(GetTickCount64() / 1000ull);
616   return NS_OK;
617 #elif defined(XP_DARWIN)
618   uint64_t absTime = mach_absolute_time();
619   mach_timebase_info_data_t timebaseInfo;
620   mach_timebase_info(&timebaseInfo);
621   double toNanoseconds =
622       double(timebaseInfo.numer) / double(timebaseInfo.denom);
623   *aResult =
624       std::llround(double(absTime) * toNanoseconds / kNanosecondsPerSecond);
625   return NS_OK;
626 #else
627   return NS_ERROR_NOT_IMPLEMENTED;
628 #endif
629 }
630 
631 NS_IMETHODIMP
GetShowedPreXULSkeletonUI(bool * aResult)632 nsAppStartup::GetShowedPreXULSkeletonUI(bool* aResult) {
633 #if defined(XP_WIN)
634   *aResult = GetPreXULSkeletonUIWasShown();
635 #else
636   *aResult = false;
637 #endif
638   return NS_OK;
639 }
640 
641 NS_IMETHODIMP
SetInterrupted(bool aInterrupted)642 nsAppStartup::SetInterrupted(bool aInterrupted) {
643   mInterrupted = aInterrupted;
644   return NS_OK;
645 }
646 
647 NS_IMETHODIMP
GetInterrupted(bool * aInterrupted)648 nsAppStartup::GetInterrupted(bool* aInterrupted) {
649   *aInterrupted = mInterrupted;
650   return NS_OK;
651 }
652 
653 //
654 // nsAppStartup->nsIWindowCreator
655 //
656 
657 NS_IMETHODIMP
CreateChromeWindow(nsIWebBrowserChrome * aParent,uint32_t aChromeFlags,nsIOpenWindowInfo * aOpenWindowInfo,bool * aCancel,nsIWebBrowserChrome ** _retval)658 nsAppStartup::CreateChromeWindow(nsIWebBrowserChrome* aParent,
659                                  uint32_t aChromeFlags,
660                                  nsIOpenWindowInfo* aOpenWindowInfo,
661                                  bool* aCancel, nsIWebBrowserChrome** _retval) {
662   NS_ENSURE_ARG_POINTER(aCancel);
663   NS_ENSURE_ARG_POINTER(_retval);
664   *aCancel = false;
665   *_retval = 0;
666 
667   // Non-modal windows cannot be opened if we are attempting to quit
668   if (mAttemptingQuit &&
669       (aChromeFlags & nsIWebBrowserChrome::CHROME_MODAL) == 0)
670     return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
671 
672   // Fission windows must also be marked as remote
673   if ((aChromeFlags & nsIWebBrowserChrome::CHROME_FISSION_WINDOW) &&
674       !(aChromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW)) {
675     NS_WARNING("Cannot create non-remote fission window!");
676     return NS_ERROR_FAILURE;
677   }
678 
679   nsCOMPtr<nsIAppWindow> newWindow;
680 
681   if (aParent) {
682     nsCOMPtr<nsIAppWindow> appParent(do_GetInterface(aParent));
683     NS_ASSERTION(appParent,
684                  "window created using non-app parent. that's unexpected, but "
685                  "may work.");
686 
687     if (appParent)
688       appParent->CreateNewWindow(aChromeFlags, aOpenWindowInfo,
689                                  getter_AddRefs(newWindow));
690     // And if it fails, don't try again without a parent. It could fail
691     // intentionally (bug 115969).
692   } else {  // try using basic methods:
693     MOZ_RELEASE_ASSERT(!aOpenWindowInfo,
694                        "Unexpected aOpenWindowInfo, we shouldn't ever have an "
695                        "nsIOpenWindowInfo without a parent");
696 
697     /* You really shouldn't be making dependent windows without a parent.
698       But unparented modal (and therefore dependent) windows happen
699       in our codebase, so we allow it after some bellyaching: */
700     if (aChromeFlags & nsIWebBrowserChrome::CHROME_DEPENDENT)
701       NS_WARNING("dependent window created without a parent");
702 
703     nsCOMPtr<nsIAppShellService> appShell(
704         do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
705     if (!appShell) return NS_ERROR_FAILURE;
706 
707     appShell->CreateTopLevelWindow(
708         0, 0, aChromeFlags, nsIAppShellService::SIZE_TO_CONTENT,
709         nsIAppShellService::SIZE_TO_CONTENT, getter_AddRefs(newWindow));
710   }
711 
712   // if anybody gave us anything to work with, use it
713   if (newWindow) {
714     nsCOMPtr<nsIInterfaceRequestor> thing(do_QueryInterface(newWindow));
715     if (thing) CallGetInterface(thing.get(), _retval);
716   }
717 
718   return *_retval ? NS_OK : NS_ERROR_FAILURE;
719 }
720 
721 //
722 // nsAppStartup->nsIObserver
723 //
724 
725 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)726 nsAppStartup::Observe(nsISupports* aSubject, const char* aTopic,
727                       const char16_t* aData) {
728   NS_ASSERTION(mAppShell, "appshell service notified before appshell built");
729   if (!strcmp(aTopic, "quit-application-forced")) {
730     mShuttingDown = true;
731   } else if (!strcmp(aTopic, "profile-change-teardown")) {
732     if (!mShuttingDown) {
733       EnterLastWindowClosingSurvivalArea();
734       CloseAllWindows();
735       ExitLastWindowClosingSurvivalArea();
736     }
737   } else if (!strcmp(aTopic, "xul-window-registered")) {
738     EnterLastWindowClosingSurvivalArea();
739   } else if (!strcmp(aTopic, "xul-window-destroyed")) {
740     ExitLastWindowClosingSurvivalArea();
741   } else if (!strcmp(aTopic, "sessionstore-windows-restored")) {
742     StartupTimeline::Record(StartupTimeline::SESSION_RESTORED);
743     IOInterposer::EnteringNextStage();
744 #if defined(XP_WIN)
745     if (mSessionWindowRestoredProbe) {
746       mSessionWindowRestoredProbe->Trigger();
747     }
748   } else if (!strcmp(aTopic, "places-init-complete")) {
749     if (mPlacesInitCompleteProbe) {
750       mPlacesInitCompleteProbe->Trigger();
751     }
752 #endif  // defined(XP_WIN)
753   } else if (!strcmp(aTopic, "sessionstore-init-started")) {
754     StartupTimeline::Record(StartupTimeline::SESSION_RESTORE_INIT);
755   } else if (!strcmp(aTopic, "xpcom-shutdown")) {
756     IOInterposer::EnteringNextStage();
757 #if defined(XP_WIN)
758     if (mXPCOMShutdownProbe) {
759       mXPCOMShutdownProbe->Trigger();
760     }
761 #endif  // defined(XP_WIN)
762   } else if (!strcmp(aTopic, "quit-application")) {
763     StartupTimeline::Record(StartupTimeline::QUIT_APPLICATION);
764   } else if (!strcmp(aTopic, "profile-before-change")) {
765     StartupTimeline::Record(StartupTimeline::PROFILE_BEFORE_CHANGE);
766   } else {
767     NS_ERROR("Unexpected observer topic.");
768   }
769 
770   return NS_OK;
771 }
772 
773 NS_IMETHODIMP
GetStartupInfo(JSContext * aCx,JS::MutableHandle<JS::Value> aRetval)774 nsAppStartup::GetStartupInfo(JSContext* aCx,
775                              JS::MutableHandle<JS::Value> aRetval) {
776   JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
777 
778   aRetval.setObject(*obj);
779 
780   TimeStamp procTime = StartupTimeline::Get(StartupTimeline::PROCESS_CREATION);
781 
782   if (procTime.IsNull()) {
783     bool error = false;
784 
785     procTime = TimeStamp::ProcessCreation(&error);
786 
787     StartupTimeline::Record(StartupTimeline::PROCESS_CREATION, procTime);
788   }
789 
790   for (int i = StartupTimeline::PROCESS_CREATION;
791        i < StartupTimeline::MAX_EVENT_ID; ++i) {
792     StartupTimeline::Event ev = static_cast<StartupTimeline::Event>(i);
793     TimeStamp stamp = StartupTimeline::Get(ev);
794 
795     if (stamp.IsNull() && (ev == StartupTimeline::MAIN)) {
796       // Always define main to aid with bug 689256.
797       stamp = procTime;
798       MOZ_ASSERT(!stamp.IsNull());
799     }
800 
801     if (!stamp.IsNull()) {
802       if (stamp >= procTime) {
803         PRTime prStamp = ComputeAbsoluteTimestamp(stamp) / PR_USEC_PER_MSEC;
804         JS::Rooted<JSObject*> date(
805             aCx, JS::NewDateObject(aCx, JS::TimeClip(prStamp)));
806         JS_DefineProperty(aCx, obj, StartupTimeline::Describe(ev), date,
807                           JSPROP_ENUMERATE);
808       }
809     }
810   }
811 
812   return NS_OK;
813 }
814 
815 NS_IMETHODIMP
GetAutomaticSafeModeNecessary(bool * _retval)816 nsAppStartup::GetAutomaticSafeModeNecessary(bool* _retval) {
817   NS_ENSURE_ARG_POINTER(_retval);
818 
819   bool alwaysSafe = false;
820   Preferences::GetBool(kPrefAlwaysUseSafeMode, &alwaysSafe);
821 
822   if (!alwaysSafe) {
823 #if DEBUG
824     mIsSafeModeNecessary = false;
825 #else
826     mIsSafeModeNecessary &= !PR_GetEnv("MOZ_DISABLE_AUTO_SAFE_MODE");
827 #endif
828   }
829 
830   *_retval = mIsSafeModeNecessary;
831   return NS_OK;
832 }
833 
834 NS_IMETHODIMP
TrackStartupCrashBegin(bool * aIsSafeModeNecessary)835 nsAppStartup::TrackStartupCrashBegin(bool* aIsSafeModeNecessary) {
836   const int32_t MAX_TIME_SINCE_STARTUP = 6 * 60 * 60 * 1000;
837   const int32_t MAX_STARTUP_BUFFER = 10;
838   nsresult rv;
839 
840   mStartupCrashTrackingEnded = false;
841 
842   StartupTimeline::Record(StartupTimeline::STARTUP_CRASH_DETECTION_BEGIN);
843 
844   bool hasLastSuccess = Preferences::HasUserValue(kPrefLastSuccess);
845   if (!hasLastSuccess) {
846     // Clear so we don't get stuck with SafeModeNecessary returning true if we
847     // have had too many recent crashes and the last success pref is missing.
848     Preferences::ClearUser(kPrefRecentCrashes);
849     return NS_ERROR_NOT_AVAILABLE;
850   }
851 
852   bool inSafeMode = false;
853   nsCOMPtr<nsIXULRuntime> xr = do_GetService(XULRUNTIME_SERVICE_CONTRACTID);
854   NS_ENSURE_TRUE(xr, NS_ERROR_FAILURE);
855 
856   xr->GetInSafeMode(&inSafeMode);
857 
858   PRTime replacedLockTime;
859   rv = xr->GetReplacedLockTime(&replacedLockTime);
860 
861   if (NS_FAILED(rv) || !replacedLockTime) {
862     if (!inSafeMode) Preferences::ClearUser(kPrefRecentCrashes);
863     GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
864     return NS_OK;
865   }
866 
867   // check whether safe mode is necessary
868   int32_t maxResumedCrashes = -1;
869   rv = Preferences::GetInt(kPrefMaxResumedCrashes, &maxResumedCrashes);
870   NS_ENSURE_SUCCESS(rv, NS_OK);
871 
872   int32_t recentCrashes = 0;
873   Preferences::GetInt(kPrefRecentCrashes, &recentCrashes);
874   mIsSafeModeNecessary =
875       (recentCrashes > maxResumedCrashes && maxResumedCrashes != -1);
876 
877   // Bug 731613 - Don't check if the last startup was a crash if
878   // XRE_PROFILE_PATH is set.  After profile manager, the profile lock's mod.
879   // time has been changed so can't be used on this startup. After a restart,
880   // it's safe to assume the last startup was successful.
881   char* xreProfilePath = PR_GetEnv("XRE_PROFILE_PATH");
882   if (xreProfilePath) {
883     GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
884     return NS_ERROR_NOT_AVAILABLE;
885   }
886 
887   // time of last successful startup
888   int32_t lastSuccessfulStartup;
889   rv = Preferences::GetInt(kPrefLastSuccess, &lastSuccessfulStartup);
890   NS_ENSURE_SUCCESS(rv, rv);
891 
892   int32_t lockSeconds = (int32_t)(replacedLockTime / PR_MSEC_PER_SEC);
893 
894   // started close enough to good startup so call it good
895   if (lockSeconds <= lastSuccessfulStartup + MAX_STARTUP_BUFFER &&
896       lockSeconds >= lastSuccessfulStartup - MAX_STARTUP_BUFFER) {
897     GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
898     return NS_OK;
899   }
900 
901   // sanity check that the pref set at last success is not greater than the
902   // current time
903   if (PR_Now() / PR_USEC_PER_SEC <= lastSuccessfulStartup)
904     return NS_ERROR_FAILURE;
905 
906   // The last startup was a crash so include it in the count regardless of when
907   // it happened.
908   Telemetry::Accumulate(Telemetry::STARTUP_CRASH_DETECTED, true);
909 
910   if (inSafeMode) {
911     GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
912     return NS_OK;
913   }
914 
915   PRTime now = (PR_Now() / PR_USEC_PER_MSEC);
916   // if the last startup attempt which crashed was in the last 6 hours
917   if (replacedLockTime >= now - MAX_TIME_SINCE_STARTUP) {
918     NS_WARNING("Last startup was detected as a crash.");
919     recentCrashes++;
920     rv = Preferences::SetInt(kPrefRecentCrashes, recentCrashes);
921   } else {
922     // Otherwise ignore that crash and all previous since it may not be
923     // applicable anymore and we don't want someone to get stuck in safe mode if
924     // their prefs are read-only.
925     rv = Preferences::ClearUser(kPrefRecentCrashes);
926   }
927   NS_ENSURE_SUCCESS(rv, rv);
928 
929   // recalculate since recent crashes count may have changed above
930   mIsSafeModeNecessary =
931       (recentCrashes > maxResumedCrashes && maxResumedCrashes != -1);
932 
933   nsCOMPtr<nsIPrefService> prefs = Preferences::GetService();
934   rv = static_cast<Preferences*>(prefs.get())
935            ->SavePrefFileBlocking();  // flush prefs to disk since we are
936                                       // tracking crashes
937   NS_ENSURE_SUCCESS(rv, rv);
938 
939   GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
940   return rv;
941 }
942 
RemoveIncompleteStartupFile()943 static nsresult RemoveIncompleteStartupFile() {
944   nsCOMPtr<nsIFile> file;
945   MOZ_TRY(NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
946                                  getter_AddRefs(file)));
947 
948   return NS_DispatchBackgroundTask(NS_NewRunnableFunction(
949       "RemoveIncompleteStartupFile", [file = std::move(file)] {
950         auto incompleteStartup =
951             mozilla::startup::GetIncompleteStartupFile(file);
952         if (NS_WARN_IF(incompleteStartup.isErr())) {
953           return;
954         }
955         Unused << NS_WARN_IF(
956             NS_FAILED(incompleteStartup.unwrap()->Remove(false)));
957       }));
958 }
959 
960 NS_IMETHODIMP
TrackStartupCrashEnd()961 nsAppStartup::TrackStartupCrashEnd() {
962   bool inSafeMode = false;
963   nsCOMPtr<nsIXULRuntime> xr = do_GetService(XULRUNTIME_SERVICE_CONTRACTID);
964   if (xr) xr->GetInSafeMode(&inSafeMode);
965 
966   // return if we already ended or we're restarting into safe mode
967   if (mStartupCrashTrackingEnded || (mIsSafeModeNecessary && !inSafeMode))
968     return NS_OK;
969   mStartupCrashTrackingEnded = true;
970 
971   StartupTimeline::Record(StartupTimeline::STARTUP_CRASH_DETECTION_END);
972 
973   // Remove the incomplete startup canary file, so the next startup doesn't
974   // detect a recent startup crash.
975   Unused << NS_WARN_IF(NS_FAILED(RemoveIncompleteStartupFile()));
976 
977   // Use the timestamp of XRE_main as an approximation for the lock file
978   // timestamp. See MAX_STARTUP_BUFFER for the buffer time period.
979   TimeStamp mainTime = StartupTimeline::Get(StartupTimeline::MAIN);
980   nsresult rv;
981 
982   if (mainTime.IsNull()) {
983     NS_WARNING("Could not get StartupTimeline::MAIN time.");
984   } else {
985     uint64_t lockFileTime = ComputeAbsoluteTimestamp(mainTime);
986 
987     rv = Preferences::SetInt(kPrefLastSuccess,
988                              (int32_t)(lockFileTime / PR_USEC_PER_SEC));
989 
990     if (NS_FAILED(rv))
991       NS_WARNING("Could not set startup crash detection pref.");
992   }
993 
994   if (inSafeMode && mIsSafeModeNecessary) {
995     // On a successful startup in automatic safe mode, allow the user one more
996     // crash in regular mode before returning to safe mode.
997     int32_t maxResumedCrashes = 0;
998     int32_t prefType;
999     rv = Preferences::GetRootBranch(PrefValueKind::Default)
1000              ->GetPrefType(kPrefMaxResumedCrashes, &prefType);
1001     NS_ENSURE_SUCCESS(rv, rv);
1002     if (prefType == nsIPrefBranch::PREF_INT) {
1003       rv = Preferences::GetInt(kPrefMaxResumedCrashes, &maxResumedCrashes);
1004       NS_ENSURE_SUCCESS(rv, rv);
1005     }
1006     rv = Preferences::SetInt(kPrefRecentCrashes, maxResumedCrashes);
1007     NS_ENSURE_SUCCESS(rv, rv);
1008   } else if (!inSafeMode) {
1009     // clear the count of recent crashes after a succesful startup when not in
1010     // safe mode
1011     rv = Preferences::ClearUser(kPrefRecentCrashes);
1012     if (NS_FAILED(rv)) NS_WARNING("Could not clear startup crash count.");
1013   }
1014   nsCOMPtr<nsIPrefService> prefs = Preferences::GetService();
1015   // save prefs to disk since we are tracking crashes.  This may be
1016   // asynchronous, so a crash could sneak in that we would mistake for
1017   // a start up crash. See bug 789945 and bug 1361262.
1018   rv = prefs->SavePrefFile(nullptr);
1019 
1020   return rv;
1021 }
1022 
1023 NS_IMETHODIMP
RestartInSafeMode(uint32_t aQuitMode)1024 nsAppStartup::RestartInSafeMode(uint32_t aQuitMode) {
1025   PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
1026   bool userAllowedQuit = false;
1027   this->Quit(aQuitMode | nsIAppStartup::eRestart, 0, &userAllowedQuit);
1028 
1029   return NS_OK;
1030 }
1031 
1032 NS_IMETHODIMP
CreateInstanceWithProfile(nsIToolkitProfile * aProfile)1033 nsAppStartup::CreateInstanceWithProfile(nsIToolkitProfile* aProfile) {
1034   if (NS_WARN_IF(!aProfile)) {
1035     return NS_ERROR_FAILURE;
1036   }
1037 
1038   if (NS_WARN_IF(gAbsoluteArgv0Path.IsEmpty())) {
1039     return NS_ERROR_FAILURE;
1040   }
1041 
1042   nsCOMPtr<nsIFile> execPath;
1043   nsresult rv =
1044       NS_NewLocalFile(gAbsoluteArgv0Path, true, getter_AddRefs(execPath));
1045   if (NS_WARN_IF(NS_FAILED(rv))) {
1046     return rv;
1047   }
1048 
1049   nsCOMPtr<nsIProcess> process = do_CreateInstance(NS_PROCESS_CONTRACTID, &rv);
1050   if (NS_WARN_IF(NS_FAILED(rv))) {
1051     return rv;
1052   }
1053 
1054   rv = process->Init(execPath);
1055   if (NS_WARN_IF(NS_FAILED(rv))) {
1056     return rv;
1057   }
1058 
1059   nsAutoCString profileName;
1060   rv = aProfile->GetName(profileName);
1061   if (NS_WARN_IF(NS_FAILED(rv))) {
1062     return rv;
1063   }
1064 
1065   NS_ConvertUTF8toUTF16 wideName(profileName);
1066 
1067   const char16_t* args[] = {u"-P", wideName.get()};
1068   rv = process->Runw(false, args, 2);
1069   if (NS_WARN_IF(NS_FAILED(rv))) {
1070     return rv;
1071   }
1072 
1073   return NS_OK;
1074 }
1075