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