michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsAppStartup.h" michael@0: michael@0: #include "nsIAppShellService.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIInterfaceRequestor.h" michael@0: #include "nsIFile.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIPrefBranch.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsIPromptService.h" michael@0: #include "nsIStringBundle.h" michael@0: #include "nsISupportsPrimitives.h" michael@0: #include "nsIWebBrowserChrome.h" michael@0: #include "nsIWindowMediator.h" michael@0: #include "nsIWindowWatcher.h" michael@0: #include "nsIXULRuntime.h" michael@0: #include "nsIXULWindow.h" michael@0: #include "nsNativeCharsetUtils.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsString.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "GeckoProfiler.h" michael@0: michael@0: #include "prprf.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsWidgetsCID.h" michael@0: #include "nsAppShellCID.h" michael@0: #include "nsXPCOMCIDInternal.h" michael@0: #include "mozilla/Services.h" michael@0: #include "nsIXPConnect.h" michael@0: #include "jsapi.h" michael@0: #include "prenv.h" michael@0: #include "nsAppDirectoryServiceDefs.h" michael@0: michael@0: #if defined(XP_WIN) michael@0: // Prevent collisions with nsAppStartup::GetStartupInfo() michael@0: #undef GetStartupInfo michael@0: #endif michael@0: michael@0: #include "mozilla/IOInterposer.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/StartupTimeline.h" michael@0: michael@0: static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); michael@0: michael@0: #define kPrefLastSuccess "toolkit.startup.last_success" michael@0: #define kPrefMaxResumedCrashes "toolkit.startup.max_resumed_crashes" michael@0: #define kPrefRecentCrashes "toolkit.startup.recent_crashes" michael@0: #define kPrefAlwaysUseSafeMode "toolkit.startup.always_use_safe_mode" michael@0: michael@0: #if defined(XP_WIN) michael@0: #include "mozilla/perfprobe.h" michael@0: /** michael@0: * Events sent to the system for profiling purposes michael@0: */ michael@0: //Keep them syncronized with the .mof file michael@0: michael@0: //Process-wide GUID, used by the OS to differentiate sources michael@0: // {509962E0-406B-46F4-99BA-5A009F8D2225} michael@0: //Keep it synchronized with the .mof file michael@0: #define NS_APPLICATION_TRACING_CID \ michael@0: { 0x509962E0, 0x406B, 0x46F4, \ michael@0: { 0x99, 0xBA, 0x5A, 0x00, 0x9F, 0x8D, 0x22, 0x25} } michael@0: michael@0: //Event-specific GUIDs, used by the OS to differentiate events michael@0: // {A3DA04E0-57D7-482A-A1C1-61DA5F95BACB} michael@0: #define NS_PLACES_INIT_COMPLETE_EVENT_CID \ michael@0: { 0xA3DA04E0, 0x57D7, 0x482A, \ michael@0: { 0xA1, 0xC1, 0x61, 0xDA, 0x5F, 0x95, 0xBA, 0xCB} } michael@0: // {917B96B1-ECAD-4DAB-A760-8D49027748AE} michael@0: #define NS_SESSION_STORE_WINDOW_RESTORED_EVENT_CID \ michael@0: { 0x917B96B1, 0xECAD, 0x4DAB, \ michael@0: { 0xA7, 0x60, 0x8D, 0x49, 0x02, 0x77, 0x48, 0xAE} } michael@0: // {26D1E091-0AE7-4F49-A554-4214445C505C} michael@0: #define NS_XPCOM_SHUTDOWN_EVENT_CID \ michael@0: { 0x26D1E091, 0x0AE7, 0x4F49, \ michael@0: { 0xA5, 0x54, 0x42, 0x14, 0x44, 0x5C, 0x50, 0x5C} } michael@0: michael@0: static NS_DEFINE_CID(kApplicationTracingCID, michael@0: NS_APPLICATION_TRACING_CID); michael@0: static NS_DEFINE_CID(kPlacesInitCompleteCID, michael@0: NS_PLACES_INIT_COMPLETE_EVENT_CID); michael@0: static NS_DEFINE_CID(kSessionStoreWindowRestoredCID, michael@0: NS_SESSION_STORE_WINDOW_RESTORED_EVENT_CID); michael@0: static NS_DEFINE_CID(kXPCOMShutdownCID, michael@0: NS_XPCOM_SHUTDOWN_EVENT_CID); michael@0: #endif //defined(XP_WIN) michael@0: michael@0: using namespace mozilla; michael@0: michael@0: uint32_t gRestartMode = 0; michael@0: michael@0: class nsAppExitEvent : public nsRunnable { michael@0: private: michael@0: nsRefPtr mService; michael@0: michael@0: public: michael@0: nsAppExitEvent(nsAppStartup *service) : mService(service) {} michael@0: michael@0: NS_IMETHOD Run() { michael@0: // Tell the appshell to exit michael@0: mService->mAppShell->Exit(); michael@0: michael@0: mService->mRunning = false; michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Computes an approximation of the absolute time represented by @a stamp michael@0: * which is comparable to those obtained via PR_Now(). If the current absolute michael@0: * time varies a lot (e.g. DST adjustments) since the first call then the michael@0: * resulting times may be inconsistent. michael@0: * michael@0: * @param stamp The timestamp to be converted michael@0: * @returns The converted timestamp michael@0: */ michael@0: uint64_t ComputeAbsoluteTimestamp(PRTime prnow, TimeStamp now, TimeStamp stamp) michael@0: { michael@0: static PRTime sAbsoluteNow = PR_Now(); michael@0: static TimeStamp sMonotonicNow = TimeStamp::Now(); michael@0: michael@0: return sAbsoluteNow - (sMonotonicNow - stamp).ToMicroseconds(); michael@0: } michael@0: michael@0: // michael@0: // nsAppStartup michael@0: // michael@0: michael@0: nsAppStartup::nsAppStartup() : michael@0: mConsiderQuitStopper(0), michael@0: mRunning(false), michael@0: mShuttingDown(false), michael@0: mStartingUp(true), michael@0: mAttemptingQuit(false), michael@0: mRestart(false), michael@0: mInterrupted(false), michael@0: mIsSafeModeNecessary(false), michael@0: mStartupCrashTrackingEnded(false), michael@0: mRestartTouchEnvironment(false) michael@0: { } michael@0: michael@0: michael@0: nsresult michael@0: nsAppStartup::Init() michael@0: { michael@0: nsresult rv; michael@0: michael@0: // Create widget application shell michael@0: mAppShell = do_GetService(kAppShellCID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr os = michael@0: mozilla::services::GetObserverService(); michael@0: if (!os) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: os->AddObserver(this, "quit-application-forced", true); michael@0: os->AddObserver(this, "sessionstore-init-started", true); michael@0: os->AddObserver(this, "sessionstore-windows-restored", true); michael@0: os->AddObserver(this, "profile-change-teardown", true); michael@0: os->AddObserver(this, "xul-window-registered", true); michael@0: os->AddObserver(this, "xul-window-destroyed", true); michael@0: os->AddObserver(this, "xpcom-shutdown", true); michael@0: michael@0: #if defined(XP_WIN) michael@0: os->AddObserver(this, "places-init-complete", true); michael@0: // This last event is only interesting to us for xperf-based measures michael@0: michael@0: // Initialize interaction with profiler michael@0: mProbesManager = michael@0: new ProbeManager( michael@0: kApplicationTracingCID, michael@0: NS_LITERAL_CSTRING("Application startup probe")); michael@0: // Note: The operation is meant mostly for in-house profiling. michael@0: // Therefore, we do not warn if probes manager cannot be initialized michael@0: michael@0: if (mProbesManager) { michael@0: mPlacesInitCompleteProbe = michael@0: mProbesManager-> michael@0: GetProbe(kPlacesInitCompleteCID, michael@0: NS_LITERAL_CSTRING("places-init-complete")); michael@0: NS_WARN_IF_FALSE(mPlacesInitCompleteProbe, michael@0: "Cannot initialize probe 'places-init-complete'"); michael@0: michael@0: mSessionWindowRestoredProbe = michael@0: mProbesManager-> michael@0: GetProbe(kSessionStoreWindowRestoredCID, michael@0: NS_LITERAL_CSTRING("sessionstore-windows-restored")); michael@0: NS_WARN_IF_FALSE(mSessionWindowRestoredProbe, michael@0: "Cannot initialize probe 'sessionstore-windows-restored'"); michael@0: michael@0: mXPCOMShutdownProbe = michael@0: mProbesManager-> michael@0: GetProbe(kXPCOMShutdownCID, michael@0: NS_LITERAL_CSTRING("xpcom-shutdown")); michael@0: NS_WARN_IF_FALSE(mXPCOMShutdownProbe, michael@0: "Cannot initialize probe 'xpcom-shutdown'"); michael@0: michael@0: rv = mProbesManager->StartSession(); michael@0: NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), michael@0: "Cannot initialize system probe manager"); michael@0: } michael@0: #endif //defined(XP_WIN) michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // michael@0: // nsAppStartup->nsISupports michael@0: // michael@0: michael@0: NS_IMPL_ISUPPORTS(nsAppStartup, michael@0: nsIAppStartup, michael@0: nsIWindowCreator, michael@0: nsIWindowCreator2, michael@0: nsIObserver, michael@0: nsISupportsWeakReference) michael@0: michael@0: michael@0: // michael@0: // nsAppStartup->nsIAppStartup michael@0: // michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::CreateHiddenWindow() michael@0: { michael@0: #ifdef MOZ_WIDGET_GONK michael@0: return NS_OK; michael@0: #else michael@0: nsCOMPtr appShellService michael@0: (do_GetService(NS_APPSHELLSERVICE_CONTRACTID)); michael@0: NS_ENSURE_TRUE(appShellService, NS_ERROR_FAILURE); michael@0: michael@0: return appShellService->CreateHiddenWindow(); michael@0: #endif michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::DestroyHiddenWindow() michael@0: { michael@0: #ifdef MOZ_WIDGET_GONK michael@0: return NS_OK; michael@0: #else michael@0: nsCOMPtr appShellService michael@0: (do_GetService(NS_APPSHELLSERVICE_CONTRACTID)); michael@0: NS_ENSURE_TRUE(appShellService, NS_ERROR_FAILURE); michael@0: michael@0: return appShellService->DestroyHiddenWindow(); michael@0: #endif michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::Run(void) michael@0: { michael@0: NS_ASSERTION(!mRunning, "Reentrant appstartup->Run()"); michael@0: michael@0: // If we have no windows open and no explicit calls to michael@0: // enterLastWindowClosingSurvivalArea, or somebody has explicitly called michael@0: // quit, don't bother running the event loop which would probably leave us michael@0: // with a zombie process. michael@0: michael@0: if (!mShuttingDown && mConsiderQuitStopper != 0) { michael@0: #ifdef XP_MACOSX michael@0: EnterLastWindowClosingSurvivalArea(); michael@0: #endif michael@0: michael@0: mRunning = true; michael@0: michael@0: nsresult rv = mAppShell->Run(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: michael@0: nsresult retval = NS_OK; michael@0: if (mRestartTouchEnvironment) { michael@0: retval = NS_SUCCESS_RESTART_METRO_APP; michael@0: } else if (mRestart) { michael@0: retval = NS_SUCCESS_RESTART_APP; michael@0: } michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::Quit(uint32_t aMode) michael@0: { michael@0: uint32_t ferocity = (aMode & 0xF); michael@0: michael@0: // Quit the application. We will asynchronously call the appshell's michael@0: // Exit() method via nsAppExitEvent to allow one last pass michael@0: // through any events in the queue. This guarantees a tidy cleanup. michael@0: nsresult rv = NS_OK; michael@0: bool postedExitEvent = false; michael@0: michael@0: if (mShuttingDown) michael@0: return NS_OK; michael@0: michael@0: // If we're considering quitting, we will only do so if: michael@0: if (ferocity == eConsiderQuit) { michael@0: #ifdef XP_MACOSX michael@0: nsCOMPtr appShell michael@0: (do_GetService(NS_APPSHELLSERVICE_CONTRACTID)); michael@0: bool hasHiddenPrivateWindow = false; michael@0: if (appShell) { michael@0: appShell->GetHasHiddenPrivateWindow(&hasHiddenPrivateWindow); michael@0: } michael@0: int32_t suspiciousCount = hasHiddenPrivateWindow ? 2 : 1; michael@0: #endif michael@0: michael@0: if (mConsiderQuitStopper == 0) { michael@0: // there are no windows... michael@0: ferocity = eAttemptQuit; michael@0: } michael@0: #ifdef XP_MACOSX michael@0: else if (mConsiderQuitStopper == suspiciousCount) { michael@0: // ... or there is only a hiddenWindow left, and it's useless: michael@0: michael@0: // Failure shouldn't be fatal, but will abort quit attempt: michael@0: if (!appShell) michael@0: return NS_OK; michael@0: michael@0: bool usefulHiddenWindow; michael@0: appShell->GetApplicationProvidedHiddenWindow(&usefulHiddenWindow); michael@0: nsCOMPtr hiddenWindow; michael@0: appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow)); michael@0: // If the remaining windows are useful, we won't quit: michael@0: nsCOMPtr hiddenPrivateWindow; michael@0: if (hasHiddenPrivateWindow) { michael@0: appShell->GetHiddenPrivateWindow(getter_AddRefs(hiddenPrivateWindow)); michael@0: if ((!hiddenWindow && !hiddenPrivateWindow) || usefulHiddenWindow) michael@0: return NS_OK; michael@0: } else if (!hiddenWindow || usefulHiddenWindow) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: ferocity = eAttemptQuit; michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: nsCOMPtr obsService; michael@0: if (ferocity == eAttemptQuit || ferocity == eForceQuit) { michael@0: michael@0: nsCOMPtr windowEnumerator; michael@0: nsCOMPtr mediator (do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); michael@0: if (mediator) { michael@0: mediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator)); michael@0: if (windowEnumerator) { michael@0: bool more; michael@0: while (windowEnumerator->HasMoreElements(&more), more) { michael@0: nsCOMPtr window; michael@0: windowEnumerator->GetNext(getter_AddRefs(window)); michael@0: nsCOMPtr domWindow(do_QueryInterface(window)); michael@0: if (domWindow) { michael@0: if (!domWindow->CanClose()) michael@0: return NS_OK; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: PROFILER_MARKER("Shutdown start"); michael@0: mozilla::RecordShutdownStartTimeStamp(); michael@0: mShuttingDown = true; michael@0: if (!mRestart) { michael@0: mRestart = (aMode & eRestart) != 0; michael@0: gRestartMode = (aMode & 0xF0); michael@0: } michael@0: michael@0: if (!mRestartTouchEnvironment) { michael@0: mRestartTouchEnvironment = (aMode & eRestartTouchEnvironment) != 0; michael@0: gRestartMode = (aMode & 0xF0); michael@0: } michael@0: michael@0: if (mRestart || mRestartTouchEnvironment) { michael@0: // Mark the next startup as a restart. michael@0: PR_SetEnv("MOZ_APP_RESTART=1"); michael@0: michael@0: /* Firefox-restarts reuse the process so regular process start-time isn't michael@0: a useful indicator of startup time anymore. */ michael@0: TimeStamp::RecordProcessRestart(); michael@0: } michael@0: michael@0: obsService = mozilla::services::GetObserverService(); michael@0: michael@0: if (!mAttemptingQuit) { michael@0: mAttemptingQuit = true; michael@0: #ifdef XP_MACOSX michael@0: // now even the Mac wants to quit when the last window is closed michael@0: ExitLastWindowClosingSurvivalArea(); michael@0: #endif michael@0: if (obsService) michael@0: obsService->NotifyObservers(nullptr, "quit-application-granted", nullptr); michael@0: } michael@0: michael@0: /* Enumerate through each open window and close it. It's important to do michael@0: this before we forcequit because this can control whether we really quit michael@0: at all. e.g. if one of these windows has an unload handler that michael@0: opens a new window. Ugh. I know. */ michael@0: CloseAllWindows(); michael@0: michael@0: if (mediator) { michael@0: if (ferocity == eAttemptQuit) { michael@0: ferocity = eForceQuit; // assume success michael@0: michael@0: /* Were we able to immediately close all windows? if not, eAttemptQuit michael@0: failed. This could happen for a variety of reasons; in fact it's michael@0: very likely. Perhaps we're being called from JS and the window->Close michael@0: method hasn't had a chance to wrap itself up yet. So give up. michael@0: We'll return (with eConsiderQuit) as the remaining windows are michael@0: closed. */ michael@0: mediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator)); michael@0: if (windowEnumerator) { michael@0: bool more; michael@0: while (windowEnumerator->HasMoreElements(&more), more) { michael@0: /* we can't quit immediately. we'll try again as the last window michael@0: finally closes. */ michael@0: ferocity = eAttemptQuit; michael@0: nsCOMPtr window; michael@0: windowEnumerator->GetNext(getter_AddRefs(window)); michael@0: nsCOMPtr domWindow = do_QueryInterface(window); michael@0: if (domWindow) { michael@0: bool closed = false; michael@0: domWindow->GetClosed(&closed); michael@0: if (!closed) { michael@0: rv = NS_ERROR_FAILURE; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (ferocity == eForceQuit) { michael@0: // do it! michael@0: michael@0: // No chance of the shutdown being cancelled from here on; tell people michael@0: // we're shutting down for sure while all services are still available. michael@0: if (obsService) { michael@0: NS_NAMED_LITERAL_STRING(shutdownStr, "shutdown"); michael@0: NS_NAMED_LITERAL_STRING(restartStr, "restart"); michael@0: obsService->NotifyObservers(nullptr, "quit-application", michael@0: (mRestart || mRestartTouchEnvironment) ? michael@0: restartStr.get() : shutdownStr.get()); michael@0: } michael@0: michael@0: if (!mRunning) { michael@0: postedExitEvent = true; michael@0: } michael@0: else { michael@0: // no matter what, make sure we send the exit event. If michael@0: // worst comes to worst, we'll do a leaky shutdown but we WILL michael@0: // shut down. Well, assuming that all *this* stuff works ;-). michael@0: nsCOMPtr event = new nsAppExitEvent(this); michael@0: rv = NS_DispatchToCurrentThread(event); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: postedExitEvent = true; michael@0: } michael@0: else { michael@0: NS_WARNING("failed to dispatch nsAppExitEvent"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // turn off the reentrancy check flag, but not if we have michael@0: // more asynchronous work to do still. michael@0: if (!postedExitEvent) michael@0: mShuttingDown = false; michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsAppStartup::CloseAllWindows() michael@0: { michael@0: nsCOMPtr mediator michael@0: (do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); michael@0: michael@0: nsCOMPtr windowEnumerator; michael@0: michael@0: mediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator)); michael@0: michael@0: if (!windowEnumerator) michael@0: return; michael@0: michael@0: bool more; michael@0: while (NS_SUCCEEDED(windowEnumerator->HasMoreElements(&more)) && more) { michael@0: nsCOMPtr isupports; michael@0: if (NS_FAILED(windowEnumerator->GetNext(getter_AddRefs(isupports)))) michael@0: break; michael@0: michael@0: nsCOMPtr window = do_QueryInterface(isupports); michael@0: NS_ASSERTION(window, "not an nsPIDOMWindow"); michael@0: if (window) michael@0: window->ForceClose(); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::EnterLastWindowClosingSurvivalArea(void) michael@0: { michael@0: ++mConsiderQuitStopper; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::ExitLastWindowClosingSurvivalArea(void) michael@0: { michael@0: NS_ASSERTION(mConsiderQuitStopper > 0, "consider quit stopper out of bounds"); michael@0: --mConsiderQuitStopper; michael@0: michael@0: if (mRunning) michael@0: Quit(eConsiderQuit); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // nsAppStartup->nsIAppStartup2 michael@0: // michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::GetShuttingDown(bool *aResult) michael@0: { michael@0: *aResult = mShuttingDown; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::GetStartingUp(bool *aResult) michael@0: { michael@0: *aResult = mStartingUp; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::DoneStartingUp() michael@0: { michael@0: // This must be called once at most michael@0: MOZ_ASSERT(mStartingUp); michael@0: michael@0: mStartingUp = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::GetRestarting(bool *aResult) michael@0: { michael@0: *aResult = mRestart; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::GetWasRestarted(bool *aResult) michael@0: { michael@0: char *mozAppRestart = PR_GetEnv("MOZ_APP_RESTART"); michael@0: michael@0: /* When calling PR_SetEnv() with an empty value the existing variable may michael@0: * be unset or set to the empty string depending on the underlying platform michael@0: * thus we have to check if the variable is present and not empty. */ michael@0: *aResult = mozAppRestart && (strcmp(mozAppRestart, "") != 0); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::GetRestartingTouchEnvironment(bool *aResult) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aResult); michael@0: *aResult = mRestartTouchEnvironment; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::SetInterrupted(bool aInterrupted) michael@0: { michael@0: mInterrupted = aInterrupted; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::GetInterrupted(bool *aInterrupted) michael@0: { michael@0: *aInterrupted = mInterrupted; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // nsAppStartup->nsIWindowCreator michael@0: // michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::CreateChromeWindow(nsIWebBrowserChrome *aParent, michael@0: uint32_t aChromeFlags, michael@0: nsIWebBrowserChrome **_retval) michael@0: { michael@0: bool cancel; michael@0: return CreateChromeWindow2(aParent, aChromeFlags, 0, 0, &cancel, _retval); michael@0: } michael@0: michael@0: michael@0: // michael@0: // nsAppStartup->nsIWindowCreator2 michael@0: // michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::CreateChromeWindow2(nsIWebBrowserChrome *aParent, michael@0: uint32_t aChromeFlags, michael@0: uint32_t aContextFlags, michael@0: nsIURI *aURI, michael@0: bool *aCancel, michael@0: nsIWebBrowserChrome **_retval) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aCancel); michael@0: NS_ENSURE_ARG_POINTER(_retval); michael@0: *aCancel = false; michael@0: *_retval = 0; michael@0: michael@0: // Non-modal windows cannot be opened if we are attempting to quit michael@0: if (mAttemptingQuit && (aChromeFlags & nsIWebBrowserChrome::CHROME_MODAL) == 0) michael@0: return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; michael@0: michael@0: nsCOMPtr newWindow; michael@0: michael@0: if (aParent) { michael@0: nsCOMPtr xulParent(do_GetInterface(aParent)); michael@0: NS_ASSERTION(xulParent, "window created using non-XUL parent. that's unexpected, but may work."); michael@0: michael@0: if (xulParent) michael@0: xulParent->CreateNewWindow(aChromeFlags, getter_AddRefs(newWindow)); michael@0: // And if it fails, don't try again without a parent. It could fail michael@0: // intentionally (bug 115969). michael@0: } else { // try using basic methods: michael@0: /* You really shouldn't be making dependent windows without a parent. michael@0: But unparented modal (and therefore dependent) windows happen michael@0: in our codebase, so we allow it after some bellyaching: */ michael@0: if (aChromeFlags & nsIWebBrowserChrome::CHROME_DEPENDENT) michael@0: NS_WARNING("dependent window created without a parent"); michael@0: michael@0: nsCOMPtr appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID)); michael@0: if (!appShell) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: appShell->CreateTopLevelWindow(0, 0, aChromeFlags, michael@0: nsIAppShellService::SIZE_TO_CONTENT, michael@0: nsIAppShellService::SIZE_TO_CONTENT, michael@0: getter_AddRefs(newWindow)); michael@0: } michael@0: michael@0: // if anybody gave us anything to work with, use it michael@0: if (newWindow) { michael@0: newWindow->SetContextFlags(aContextFlags); michael@0: nsCOMPtr thing(do_QueryInterface(newWindow)); michael@0: if (thing) michael@0: CallGetInterface(thing.get(), _retval); michael@0: } michael@0: michael@0: return *_retval ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: michael@0: // michael@0: // nsAppStartup->nsIObserver michael@0: // michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::Observe(nsISupports *aSubject, michael@0: const char *aTopic, const char16_t *aData) michael@0: { michael@0: NS_ASSERTION(mAppShell, "appshell service notified before appshell built"); michael@0: if (!strcmp(aTopic, "quit-application-forced")) { michael@0: mShuttingDown = true; michael@0: } michael@0: else if (!strcmp(aTopic, "profile-change-teardown")) { michael@0: if (!mShuttingDown) { michael@0: EnterLastWindowClosingSurvivalArea(); michael@0: CloseAllWindows(); michael@0: ExitLastWindowClosingSurvivalArea(); michael@0: } michael@0: } else if (!strcmp(aTopic, "xul-window-registered")) { michael@0: EnterLastWindowClosingSurvivalArea(); michael@0: } else if (!strcmp(aTopic, "xul-window-destroyed")) { michael@0: ExitLastWindowClosingSurvivalArea(); michael@0: } else if (!strcmp(aTopic, "sessionstore-windows-restored")) { michael@0: StartupTimeline::Record(StartupTimeline::SESSION_RESTORED); michael@0: IOInterposer::EnteringNextStage(); michael@0: #if defined(XP_WIN) michael@0: if (mSessionWindowRestoredProbe) { michael@0: mSessionWindowRestoredProbe->Trigger(); michael@0: } michael@0: } else if (!strcmp(aTopic, "places-init-complete")) { michael@0: if (mPlacesInitCompleteProbe) { michael@0: mPlacesInitCompleteProbe->Trigger(); michael@0: } michael@0: #endif //defined(XP_WIN) michael@0: } else if (!strcmp(aTopic, "sessionstore-init-started")) { michael@0: StartupTimeline::Record(StartupTimeline::SESSION_RESTORE_INIT); michael@0: } else if (!strcmp(aTopic, "xpcom-shutdown")) { michael@0: IOInterposer::EnteringNextStage(); michael@0: #if defined(XP_WIN) michael@0: if (mXPCOMShutdownProbe) { michael@0: mXPCOMShutdownProbe->Trigger(); michael@0: } michael@0: #endif // defined(XP_WIN) michael@0: } else { michael@0: NS_ERROR("Unexpected observer topic."); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::GetStartupInfo(JSContext* aCx, JS::MutableHandle aRetval) michael@0: { michael@0: JS::Rooted obj(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr())); michael@0: michael@0: aRetval.setObject(*obj); michael@0: michael@0: TimeStamp procTime = StartupTimeline::Get(StartupTimeline::PROCESS_CREATION); michael@0: TimeStamp now = TimeStamp::Now(); michael@0: PRTime absNow = PR_Now(); michael@0: michael@0: if (procTime.IsNull()) { michael@0: bool error = false; michael@0: michael@0: procTime = TimeStamp::ProcessCreation(error); michael@0: michael@0: if (error) { michael@0: Telemetry::Accumulate(Telemetry::STARTUP_MEASUREMENT_ERRORS, michael@0: StartupTimeline::PROCESS_CREATION); michael@0: } michael@0: michael@0: StartupTimeline::Record(StartupTimeline::PROCESS_CREATION, procTime); michael@0: } michael@0: michael@0: for (int i = StartupTimeline::PROCESS_CREATION; michael@0: i < StartupTimeline::MAX_EVENT_ID; michael@0: ++i) michael@0: { michael@0: StartupTimeline::Event ev = static_cast(i); michael@0: TimeStamp stamp = StartupTimeline::Get(ev); michael@0: michael@0: if (stamp.IsNull() && (ev == StartupTimeline::MAIN)) { michael@0: // Always define main to aid with bug 689256. michael@0: stamp = procTime; michael@0: MOZ_ASSERT(!stamp.IsNull()); michael@0: Telemetry::Accumulate(Telemetry::STARTUP_MEASUREMENT_ERRORS, michael@0: StartupTimeline::MAIN); michael@0: } michael@0: michael@0: if (!stamp.IsNull()) { michael@0: if (stamp >= procTime) { michael@0: PRTime prStamp = ComputeAbsoluteTimestamp(absNow, now, stamp) michael@0: / PR_USEC_PER_MSEC; michael@0: JS::Rooted date(aCx, JS_NewDateObjectMsec(aCx, prStamp)); michael@0: JS_DefineProperty(aCx, obj, StartupTimeline::Describe(ev), date, JSPROP_ENUMERATE); michael@0: } else { michael@0: Telemetry::Accumulate(Telemetry::STARTUP_MEASUREMENT_ERRORS, ev); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::GetAutomaticSafeModeNecessary(bool *_retval) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(_retval); michael@0: michael@0: bool alwaysSafe = false; michael@0: Preferences::GetBool(kPrefAlwaysUseSafeMode, &alwaysSafe); michael@0: michael@0: if (!alwaysSafe) { michael@0: #if DEBUG michael@0: mIsSafeModeNecessary = false; michael@0: #else michael@0: mIsSafeModeNecessary &= !PR_GetEnv("MOZ_DISABLE_AUTO_SAFE_MODE"); michael@0: #endif michael@0: } michael@0: michael@0: *_retval = mIsSafeModeNecessary; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::TrackStartupCrashBegin(bool *aIsSafeModeNecessary) michael@0: { michael@0: const int32_t MAX_TIME_SINCE_STARTUP = 6 * 60 * 60 * 1000; michael@0: const int32_t MAX_STARTUP_BUFFER = 10; michael@0: nsresult rv; michael@0: michael@0: mStartupCrashTrackingEnded = false; michael@0: michael@0: StartupTimeline::Record(StartupTimeline::STARTUP_CRASH_DETECTION_BEGIN); michael@0: michael@0: bool hasLastSuccess = Preferences::HasUserValue(kPrefLastSuccess); michael@0: if (!hasLastSuccess) { michael@0: // Clear so we don't get stuck with SafeModeNecessary returning true if we michael@0: // have had too many recent crashes and the last success pref is missing. michael@0: Preferences::ClearUser(kPrefRecentCrashes); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: bool inSafeMode = false; michael@0: nsCOMPtr xr = do_GetService(XULRUNTIME_SERVICE_CONTRACTID); michael@0: NS_ENSURE_TRUE(xr, NS_ERROR_FAILURE); michael@0: michael@0: xr->GetInSafeMode(&inSafeMode); michael@0: michael@0: PRTime replacedLockTime; michael@0: rv = xr->GetReplacedLockTime(&replacedLockTime); michael@0: michael@0: if (NS_FAILED(rv) || !replacedLockTime) { michael@0: if (!inSafeMode) michael@0: Preferences::ClearUser(kPrefRecentCrashes); michael@0: GetAutomaticSafeModeNecessary(aIsSafeModeNecessary); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // check whether safe mode is necessary michael@0: int32_t maxResumedCrashes = -1; michael@0: rv = Preferences::GetInt(kPrefMaxResumedCrashes, &maxResumedCrashes); michael@0: NS_ENSURE_SUCCESS(rv, NS_OK); michael@0: michael@0: int32_t recentCrashes = 0; michael@0: Preferences::GetInt(kPrefRecentCrashes, &recentCrashes); michael@0: mIsSafeModeNecessary = (recentCrashes > maxResumedCrashes && maxResumedCrashes != -1); michael@0: michael@0: // Bug 731613 - Don't check if the last startup was a crash if XRE_PROFILE_PATH is set. After michael@0: // profile manager, the profile lock's mod. time has been changed so can't be used on this startup. michael@0: // After a restart, it's safe to assume the last startup was successful. michael@0: char *xreProfilePath = PR_GetEnv("XRE_PROFILE_PATH"); michael@0: if (xreProfilePath) { michael@0: GetAutomaticSafeModeNecessary(aIsSafeModeNecessary); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // time of last successful startup michael@0: int32_t lastSuccessfulStartup; michael@0: rv = Preferences::GetInt(kPrefLastSuccess, &lastSuccessfulStartup); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: int32_t lockSeconds = (int32_t)(replacedLockTime / PR_MSEC_PER_SEC); michael@0: michael@0: // started close enough to good startup so call it good michael@0: if (lockSeconds <= lastSuccessfulStartup + MAX_STARTUP_BUFFER michael@0: && lockSeconds >= lastSuccessfulStartup - MAX_STARTUP_BUFFER) { michael@0: GetAutomaticSafeModeNecessary(aIsSafeModeNecessary); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // sanity check that the pref set at last success is not greater than the current time michael@0: if (PR_Now() / PR_USEC_PER_SEC <= lastSuccessfulStartup) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // The last startup was a crash so include it in the count regardless of when it happened. michael@0: Telemetry::Accumulate(Telemetry::STARTUP_CRASH_DETECTED, true); michael@0: michael@0: if (inSafeMode) { michael@0: GetAutomaticSafeModeNecessary(aIsSafeModeNecessary); michael@0: return NS_OK; michael@0: } michael@0: michael@0: PRTime now = (PR_Now() / PR_USEC_PER_MSEC); michael@0: // if the last startup attempt which crashed was in the last 6 hours michael@0: if (replacedLockTime >= now - MAX_TIME_SINCE_STARTUP) { michael@0: NS_WARNING("Last startup was detected as a crash."); michael@0: recentCrashes++; michael@0: rv = Preferences::SetInt(kPrefRecentCrashes, recentCrashes); michael@0: } else { michael@0: // Otherwise ignore that crash and all previous since it may not be applicable anymore michael@0: // and we don't want someone to get stuck in safe mode if their prefs are read-only. michael@0: rv = Preferences::ClearUser(kPrefRecentCrashes); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // recalculate since recent crashes count may have changed above michael@0: mIsSafeModeNecessary = (recentCrashes > maxResumedCrashes && maxResumedCrashes != -1); michael@0: michael@0: nsCOMPtr prefs = Preferences::GetService(); michael@0: rv = prefs->SavePrefFile(nullptr); // flush prefs to disk since we are tracking crashes michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: GetAutomaticSafeModeNecessary(aIsSafeModeNecessary); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::TrackStartupCrashEnd() michael@0: { michael@0: bool inSafeMode = false; michael@0: nsCOMPtr xr = do_GetService(XULRUNTIME_SERVICE_CONTRACTID); michael@0: if (xr) michael@0: xr->GetInSafeMode(&inSafeMode); michael@0: michael@0: // return if we already ended or we're restarting into safe mode michael@0: if (mStartupCrashTrackingEnded || (mIsSafeModeNecessary && !inSafeMode)) michael@0: return NS_OK; michael@0: mStartupCrashTrackingEnded = true; michael@0: michael@0: StartupTimeline::Record(StartupTimeline::STARTUP_CRASH_DETECTION_END); michael@0: michael@0: // Use the timestamp of XRE_main as an approximation for the lock file timestamp. michael@0: // See MAX_STARTUP_BUFFER for the buffer time period. michael@0: TimeStamp mainTime = StartupTimeline::Get(StartupTimeline::MAIN); michael@0: TimeStamp now = TimeStamp::Now(); michael@0: PRTime prNow = PR_Now(); michael@0: nsresult rv; michael@0: michael@0: if (mainTime.IsNull()) { michael@0: NS_WARNING("Could not get StartupTimeline::MAIN time."); michael@0: } else { michael@0: uint64_t lockFileTime = ComputeAbsoluteTimestamp(prNow, now, mainTime); michael@0: michael@0: rv = Preferences::SetInt(kPrefLastSuccess, michael@0: (int32_t)(lockFileTime / PR_USEC_PER_SEC)); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: NS_WARNING("Could not set startup crash detection pref."); michael@0: } michael@0: michael@0: if (inSafeMode && mIsSafeModeNecessary) { michael@0: // On a successful startup in automatic safe mode, allow the user one more crash michael@0: // in regular mode before returning to safe mode. michael@0: int32_t maxResumedCrashes = 0; michael@0: int32_t prefType; michael@0: rv = Preferences::GetDefaultRootBranch()->GetPrefType(kPrefMaxResumedCrashes, &prefType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (prefType == nsIPrefBranch::PREF_INT) { michael@0: rv = Preferences::GetInt(kPrefMaxResumedCrashes, &maxResumedCrashes); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: rv = Preferences::SetInt(kPrefRecentCrashes, maxResumedCrashes); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else if (!inSafeMode) { michael@0: // clear the count of recent crashes after a succesful startup when not in safe mode michael@0: rv = Preferences::ClearUser(kPrefRecentCrashes); michael@0: if (NS_FAILED(rv)) NS_WARNING("Could not clear startup crash count."); michael@0: } michael@0: nsCOMPtr prefs = Preferences::GetService(); michael@0: rv = prefs->SavePrefFile(nullptr); // flush prefs to disk since we are tracking crashes michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppStartup::RestartInSafeMode(uint32_t aQuitMode) michael@0: { michael@0: PR_SetEnv("MOZ_SAFE_MODE_RESTART=1"); michael@0: this->Quit(aQuitMode | nsIAppStartup::eRestart); michael@0: michael@0: return NS_OK; michael@0: }