mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-01 19:39:13 +00:00
Bug 294260 - Safe Mode: Auto detect previous start-up failure and offer to start in safe mode r=bsmedberg,Mossop
--HG-- rename : toolkit/components/startup/tests/Makefile.in => toolkit/components/startup/tests/browser/Makefile.in extra : rebase_source : 8df6b163173362ae80832eb3667e04bb817280b3
This commit is contained in:
parent
f53850fe68
commit
f018d13961
@ -1125,3 +1125,8 @@ pref("browser.newtabpage.enabled", true);
|
||||
|
||||
// Enable the DOM full-screen API.
|
||||
pref("full-screen-api.enabled", true);
|
||||
|
||||
// Startup Crash Tracking
|
||||
// number of startup crashes that can occur before starting into safe mode automatically
|
||||
// (this pref has no effect if more than 6 hours have passed since the last crash)
|
||||
pref("toolkit.startup.max_resumed_crashes", 2);
|
||||
|
@ -265,17 +265,17 @@ appUpdater.prototype =
|
||||
if (cancelQuit.data)
|
||||
return;
|
||||
|
||||
let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"].
|
||||
getService(Components.interfaces.nsIAppStartup);
|
||||
|
||||
// If already in safe mode restart in safe mode (bug 327119)
|
||||
if (Services.appinfo.inSafeMode) {
|
||||
let env = Components.classes["@mozilla.org/process/environment;1"].
|
||||
getService(Components.interfaces.nsIEnvironment);
|
||||
env.set("MOZ_SAFE_MODE_RESTART", "1");
|
||||
appStartup.restartInSafeMode(Components.interfaces.nsIAppStartup.eAttemptQuit);
|
||||
return;
|
||||
}
|
||||
|
||||
Components.classes["@mozilla.org/toolkit/app-startup;1"].
|
||||
getService(Components.interfaces.nsIAppStartup).
|
||||
quit(Components.interfaces.nsIAppStartup.eAttemptQuit |
|
||||
Components.interfaces.nsIAppStartup.eRestart);
|
||||
appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit |
|
||||
Components.interfaces.nsIAppStartup.eRestart);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1805,6 +1805,17 @@ function delayedStartup(isLoadingBlank, mustLoadSidebar) {
|
||||
window.addEventListener("mousemove", MousePosTracker, false);
|
||||
window.addEventListener("dragover", MousePosTracker, false);
|
||||
|
||||
// End startup crash tracking after a delay to catch crashes while restoring
|
||||
// tabs and to postpone saving the pref to disk.
|
||||
try {
|
||||
let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].
|
||||
getService(Ci.nsIAppStartup);
|
||||
const startupCrashEndDelay = 30 * 1000;
|
||||
setTimeout(appStartup.trackStartupCrashEnd, startupCrashEndDelay);
|
||||
} catch (ex) {
|
||||
Cu.reportError("Could not end startup crash tracking: " + ex);
|
||||
}
|
||||
|
||||
Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "");
|
||||
TelemetryTimestamps.add("delayedStartupFinished");
|
||||
}
|
||||
@ -8993,10 +9004,9 @@ function safeModeRestart()
|
||||
buttonFlags, restartText, null, null,
|
||||
null, {});
|
||||
if (rv == 0) {
|
||||
let environment = Components.classes["@mozilla.org/process/environment;1"].
|
||||
getService(Components.interfaces.nsIEnvironment);
|
||||
environment.set("MOZ_SAFE_MODE_RESTART", "1");
|
||||
Application.restart();
|
||||
let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].
|
||||
getService(Ci.nsIAppStartup);
|
||||
appStartup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,6 +138,14 @@ function onCancel() {
|
||||
}
|
||||
|
||||
function onLoad() {
|
||||
var appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
|
||||
.getService(Components.interfaces.nsIAppStartup);
|
||||
|
||||
if (appStartup.automaticSafeModeNecessary) {
|
||||
document.getElementById("autoSafeMode").hidden = false;
|
||||
document.getElementById("manualSafeMode").hidden = true;
|
||||
}
|
||||
|
||||
document.getElementById("tasks")
|
||||
.addEventListener("CheckboxStateChange", UpdateOKButtonState, false);
|
||||
}
|
||||
|
@ -71,7 +71,12 @@
|
||||
|
||||
<stringbundle id="preferencesBundle" src="chrome://browser/locale/preferences/preferences.properties"/>
|
||||
|
||||
<description>&safeModeDescription.label;</description>
|
||||
<vbox id="autoSafeMode" hidden="true">
|
||||
<label class="header plain" value="&autoSafeModeIntro.label;"/>
|
||||
<description>&autoSafeModeDescription.label;</description>
|
||||
</vbox>
|
||||
|
||||
<description id="manualSafeMode">&safeModeDescription.label;</description>
|
||||
|
||||
<separator class="thin"/>
|
||||
|
||||
|
@ -188,6 +188,13 @@ BrowserGlue.prototype = {
|
||||
// This pref must be set here because SessionStore will use its value
|
||||
// on quit-application.
|
||||
this._setPrefToSaveSession();
|
||||
try {
|
||||
let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].
|
||||
getService(Ci.nsIAppStartup);
|
||||
appStartup.trackStartupCrashEnd();
|
||||
} catch (e) {
|
||||
Cu.reportError("Could not end startup crash tracking in quit-application-granted: " + e);
|
||||
}
|
||||
break;
|
||||
#ifdef OBSERVE_LASTWINDOW_CLOSE_TOPICS
|
||||
case "browser-lastwindow-close-requested":
|
||||
|
@ -39,6 +39,9 @@
|
||||
<!ENTITY safeModeDialog.title "&brandShortName; Safe Mode">
|
||||
<!ENTITY window.width "37em">
|
||||
|
||||
<!ENTITY autoSafeModeIntro.label "&brandShortName; Closed Unexpectedly While Starting">
|
||||
<!ENTITY autoSafeModeDescription.label "We sincerely apologize for the inconvenience. &brandShortName; is now running in Safe Mode, which temporarily disables your custom settings, themes, and extensions.">
|
||||
|
||||
<!ENTITY safeModeDescription.label "&brandShortName; is now running in Safe Mode, which temporarily disables your custom settings, themes, and extensions.">
|
||||
<!ENTITY safeModeDescription2.label "You can make some or all of these changes permanent:">
|
||||
|
||||
|
@ -81,6 +81,11 @@ public:
|
||||
|
||||
nsresult Init();
|
||||
|
||||
/**
|
||||
* Reset loaded user prefs then read them
|
||||
*/
|
||||
static nsresult ResetAndReadUserPrefs();
|
||||
|
||||
/**
|
||||
* Returns the singleton instance which is addreffed.
|
||||
*/
|
||||
|
@ -351,14 +351,19 @@ Preferences::Init()
|
||||
|
||||
rv = observerService->AddObserver(this, "profile-before-change", true);
|
||||
|
||||
if (NS_SUCCEEDED(rv))
|
||||
rv = observerService->AddObserver(this, "profile-do-change", true);
|
||||
|
||||
observerService->AddObserver(this, "load-extension-defaults", true);
|
||||
|
||||
return(rv);
|
||||
}
|
||||
|
||||
// static
|
||||
nsresult
|
||||
Preferences::ResetAndReadUserPrefs()
|
||||
{
|
||||
sPreferences->ResetUserPrefs();
|
||||
return sPreferences->ReadUserPrefs(nsnull);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
Preferences::Observe(nsISupports *aSubject, const char *aTopic,
|
||||
const PRUnichar *someData)
|
||||
@ -377,9 +382,6 @@ Preferences::Observe(nsISupports *aSubject, const char *aTopic,
|
||||
} else {
|
||||
rv = SavePrefFile(nsnull);
|
||||
}
|
||||
} else if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
|
||||
ResetUserPrefs();
|
||||
rv = ReadUserPrefs(nsnull);
|
||||
} else if (!strcmp(aTopic, "load-extension-defaults")) {
|
||||
pref_LoadPrefsInDirList(NS_EXT_PREFS_DEFAULTS_DIR_LIST);
|
||||
} else if (!nsCRT::strcmp(aTopic, "reload-default-prefs")) {
|
||||
|
@ -76,7 +76,8 @@ static bool sDisableSignalHandling = false;
|
||||
#endif
|
||||
|
||||
nsProfileLock::nsProfileLock() :
|
||||
mHaveLock(false)
|
||||
mHaveLock(false),
|
||||
mReplacedLockTime(0)
|
||||
#if defined (XP_WIN)
|
||||
,mLockFileHandle(INVALID_HANDLE_VALUE)
|
||||
#elif defined (XP_OS2)
|
||||
@ -231,10 +232,19 @@ void nsProfileLock::FatalSignalHandler(int signo
|
||||
_exit(signo);
|
||||
}
|
||||
|
||||
nsresult nsProfileLock::LockWithFcntl(const nsACString& lockFilePath)
|
||||
nsresult nsProfileLock::LockWithFcntl(nsILocalFile *aLockFile)
|
||||
{
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
nsCAutoString lockFilePath;
|
||||
rv = aLockFile->GetNativePath(lockFilePath);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_ERROR("Could not get native path");
|
||||
return rv;
|
||||
}
|
||||
|
||||
aLockFile->GetLastModifiedTime(&mReplacedLockTime);
|
||||
|
||||
mLockFileDesc = open(PromiseFlatCString(lockFilePath).get(),
|
||||
O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
||||
if (mLockFileDesc != -1)
|
||||
@ -332,9 +342,19 @@ static bool IsSymlinkStaleLock(struct in_addr* aAddr, const char* aFileName,
|
||||
return true;
|
||||
}
|
||||
|
||||
nsresult nsProfileLock::LockWithSymlink(const nsACString& lockFilePath, bool aHaveFcntlLock)
|
||||
nsresult nsProfileLock::LockWithSymlink(nsILocalFile *aLockFile, bool aHaveFcntlLock)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCAutoString lockFilePath;
|
||||
rv = aLockFile->GetNativePath(lockFilePath);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_ERROR("Could not get native path");
|
||||
return rv;
|
||||
}
|
||||
|
||||
// don't replace an existing lock time if fcntl already got one
|
||||
if (!mReplacedLockTime)
|
||||
aLockFile->GetLastModifiedTimeOfLink(&mReplacedLockTime);
|
||||
|
||||
struct in_addr inaddr;
|
||||
inaddr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
@ -443,6 +463,11 @@ PR_BEGIN_MACRO \
|
||||
}
|
||||
#endif /* XP_UNIX */
|
||||
|
||||
nsresult nsProfileLock::GetReplacedLockTime(PRInt64 *aResult) {
|
||||
*aResult = mReplacedLockTime;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult nsProfileLock::Lock(nsILocalFile* aProfileDir,
|
||||
nsIProfileUnlocker* *aUnlocker)
|
||||
{
|
||||
@ -481,17 +506,13 @@ nsresult nsProfileLock::Lock(nsILocalFile* aProfileDir,
|
||||
#if defined(XP_MACOSX)
|
||||
// First, try locking using fcntl. It is more reliable on
|
||||
// a local machine, but may not be supported by an NFS server.
|
||||
nsCAutoString filePath;
|
||||
rv = lockFile->GetNativePath(filePath);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
rv = LockWithFcntl(filePath);
|
||||
rv = LockWithFcntl(lockFile);
|
||||
if (NS_FAILED(rv) && (rv != NS_ERROR_FILE_ACCESS_DENIED))
|
||||
{
|
||||
// If that failed for any reason other than NS_ERROR_FILE_ACCESS_DENIED,
|
||||
// assume we tried an NFS that does not support it. Now, try with symlink.
|
||||
rv = LockWithSymlink(filePath, false);
|
||||
rv = LockWithSymlink(lockFile, false);
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(rv))
|
||||
@ -543,33 +564,24 @@ nsresult nsProfileLock::Lock(nsILocalFile* aProfileDir,
|
||||
rv = NS_OK; // Don't propagate error from OpenNSPRFileDesc.
|
||||
}
|
||||
#elif defined(XP_UNIX)
|
||||
nsCAutoString filePath;
|
||||
rv = lockFile->GetNativePath(filePath);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
// Get the old lockfile name
|
||||
nsCOMPtr<nsIFile> oldLockFile;
|
||||
rv = aProfileDir->Clone(getter_AddRefs(oldLockFile));
|
||||
nsCOMPtr<nsILocalFile> oldLockFile;
|
||||
rv = aProfileDir->Clone((nsIFile **)((void **)getter_AddRefs(oldLockFile)));
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
rv = oldLockFile->Append(OLD_LOCKFILE_NAME);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
nsCAutoString oldFilePath;
|
||||
rv = oldLockFile->GetNativePath(oldFilePath);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
// First, try locking using fcntl. It is more reliable on
|
||||
// a local machine, but may not be supported by an NFS server.
|
||||
rv = LockWithFcntl(filePath);
|
||||
rv = LockWithFcntl(lockFile);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
// Check to see whether there is a symlink lock held by an older
|
||||
// Firefox build, and also place our own symlink lock --- but
|
||||
// mark it "obsolete" so that other newer builds can break the lock
|
||||
// if they obtain the fcntl lock
|
||||
rv = LockWithSymlink(oldFilePath, true);
|
||||
rv = LockWithSymlink(oldLockFile, true);
|
||||
|
||||
// If the symlink failed for some reason other than it already
|
||||
// exists, then something went wrong e.g. the file system
|
||||
@ -586,7 +598,7 @@ nsresult nsProfileLock::Lock(nsILocalFile* aProfileDir,
|
||||
// If that failed for any reason other than NS_ERROR_FILE_ACCESS_DENIED,
|
||||
// assume we tried an NFS that does not support it. Now, try with symlink
|
||||
// using the old symlink path
|
||||
rv = LockWithSymlink(oldFilePath, false);
|
||||
rv = LockWithSymlink(oldLockFile, false);
|
||||
}
|
||||
|
||||
#elif defined(XP_WIN)
|
||||
@ -595,12 +607,16 @@ nsresult nsProfileLock::Lock(nsILocalFile* aProfileDir,
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
lockFile->GetLastModifiedTime(&mReplacedLockTime);
|
||||
|
||||
// always create the profile lock and never delete it so we can use its
|
||||
// modification timestamp to detect startup crashes
|
||||
mLockFileHandle = CreateFileW(filePath.get(),
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0, // no sharing - of course
|
||||
nsnull,
|
||||
OPEN_ALWAYS,
|
||||
FILE_FLAG_DELETE_ON_CLOSE,
|
||||
CREATE_ALWAYS,
|
||||
nsnull,
|
||||
nsnull);
|
||||
if (mLockFileHandle == INVALID_HANDLE_VALUE) {
|
||||
// XXXbsmedberg: provide a profile-unlocker here!
|
||||
@ -612,6 +628,8 @@ nsresult nsProfileLock::Lock(nsILocalFile* aProfileDir,
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
lockFile->GetLastModifiedTime(&mReplacedLockTime);
|
||||
|
||||
ULONG ulAction = 0;
|
||||
APIRET rc;
|
||||
rc = DosOpen(filePath.get(),
|
||||
@ -633,6 +651,8 @@ nsresult nsProfileLock::Lock(nsILocalFile* aProfileDir,
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
lockFile->GetLastModifiedTime(&mReplacedLockTime);
|
||||
|
||||
mLockFileDesc = open_noshr(filePath.get(), O_CREAT, 0666);
|
||||
if (mLockFileDesc == -1)
|
||||
{
|
||||
|
@ -89,9 +89,15 @@ public:
|
||||
* signal, set aFatalSignal to true.
|
||||
*/
|
||||
nsresult Unlock(bool aFatalSignal = false);
|
||||
|
||||
|
||||
/**
|
||||
* Get the modification time of a replaced profile lock, otherwise 0.
|
||||
*/
|
||||
nsresult GetReplacedLockTime(PRInt64* aResult);
|
||||
|
||||
private:
|
||||
bool mHaveLock;
|
||||
PRInt64 mReplacedLockTime;
|
||||
|
||||
#if defined (XP_WIN)
|
||||
HANDLE mLockFileHandle;
|
||||
@ -114,13 +120,13 @@ private:
|
||||
);
|
||||
static PRCList mPidLockList;
|
||||
|
||||
nsresult LockWithFcntl(const nsACString& lockFilePath);
|
||||
nsresult LockWithFcntl(nsILocalFile *aLockFile);
|
||||
|
||||
/**
|
||||
* @param aHaveFcntlLock if true, we've already acquired an fcntl lock so this
|
||||
* lock is merely an "obsolete" lock to keep out old Firefoxes
|
||||
*/
|
||||
nsresult LockWithSymlink(const nsACString& lockFilePath, bool aHaveFcntlLock);
|
||||
nsresult LockWithSymlink(nsILocalFile *aLockFile, bool aHaveFcntlLock);
|
||||
|
||||
char* mPidLockFileName;
|
||||
int mLockFileDesc;
|
||||
|
@ -34,6 +34,7 @@ skip-if = os == "android"
|
||||
[include:toolkit/components/satchel/test/unit/xpcshell.ini]
|
||||
[include:toolkit/components/downloads/test/unit/xpcshell.ini]
|
||||
[include:toolkit/components/downloads/test/schema_migration/xpcshell.ini]
|
||||
[include:toolkit/components/startup/tests/unit/xpcshell.ini]
|
||||
[include:toolkit/components/telemetry/tests/unit/xpcshell.ini]
|
||||
[include:toolkit/content/tests/unit/xpcshell.ini]
|
||||
[include:toolkit/mozapps/downloads/tests/unit/xpcshell.ini]
|
||||
|
@ -38,6 +38,7 @@ DEPTH = ../../..
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = toolkit/components/startup
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
@ -70,6 +71,8 @@ endif
|
||||
endif
|
||||
endif
|
||||
|
||||
XPCSHELL_TESTS = tests/unit
|
||||
|
||||
ifdef ENABLE_TESTS
|
||||
ifneq (mobile,$(MOZ_BUILD_APP))
|
||||
DIRS += tests/browser
|
||||
|
@ -59,17 +59,20 @@
|
||||
#include "nsIWebBrowserChrome.h"
|
||||
#include "nsIWindowMediator.h"
|
||||
#include "nsIWindowWatcher.h"
|
||||
#include "nsIXULRuntime.h"
|
||||
#include "nsIXULWindow.h"
|
||||
#include "nsNativeCharsetUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsStringGlue.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
|
||||
#include "prprf.h"
|
||||
#include "nsCRT.h"
|
||||
#include "nsIInterfaceRequestorUtils.h"
|
||||
#include "nsWidgetsCID.h"
|
||||
#include "nsAppShellCID.h"
|
||||
#include "nsXPCOMCIDInternal.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/FunctionTimer.h"
|
||||
#include "nsIXPConnect.h"
|
||||
@ -99,6 +102,10 @@
|
||||
|
||||
static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
|
||||
|
||||
#define kPrefLastSuccess "toolkit.startup.last_success"
|
||||
#define kPrefMaxResumedCrashes "toolkit.startup.max_resumed_crashes"
|
||||
#define kPrefRecentCrashes "toolkit.startup.recent_crashes"
|
||||
|
||||
#if defined(XP_WIN)
|
||||
#include "mozilla/perfprobe.h"
|
||||
/**
|
||||
@ -163,7 +170,9 @@ nsAppStartup::nsAppStartup() :
|
||||
mShuttingDown(false),
|
||||
mAttemptingQuit(false),
|
||||
mRestart(false),
|
||||
mInterrupted(false)
|
||||
mInterrupted(false),
|
||||
mIsSafeModeNecessary(false),
|
||||
mStartupCrashTrackingEnded(false)
|
||||
{ }
|
||||
|
||||
|
||||
@ -809,3 +818,157 @@ nsAppStartup::GetStartupInfo(JSContext* aCx, JS::Value* aRetval)
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAppStartup::GetAutomaticSafeModeNecessary(bool *_retval)
|
||||
{
|
||||
*_retval = mIsSafeModeNecessary;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAppStartup::TrackStartupCrashBegin(bool *aIsSafeModeNecessary)
|
||||
{
|
||||
const PRInt32 MAX_TIME_SINCE_STARTUP = 6 * 60 * 60 * 1000;
|
||||
const PRInt32 MAX_STARTUP_BUFFER = 10;
|
||||
nsresult rv;
|
||||
|
||||
mStartupCrashTrackingEnded = false;
|
||||
|
||||
bool hasLastSuccess = Preferences::HasUserValue(kPrefLastSuccess);
|
||||
if (!hasLastSuccess) {
|
||||
// Clear so we don't get stuck with SafeModeNecessary returning true if we
|
||||
// have had too many recent crashes and the last success pref is missing.
|
||||
Preferences::ClearUser(kPrefRecentCrashes);
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
bool inSafeMode = false;
|
||||
nsCOMPtr<nsIXULRuntime> xr = do_GetService(XULRUNTIME_SERVICE_CONTRACTID);
|
||||
NS_ENSURE_TRUE(xr, NS_ERROR_FAILURE);
|
||||
|
||||
xr->GetInSafeMode(&inSafeMode);
|
||||
|
||||
PRInt64 replacedLockTime;
|
||||
rv = xr->GetReplacedLockTime(&replacedLockTime);
|
||||
|
||||
if (NS_FAILED(rv) || !replacedLockTime) {
|
||||
if (!inSafeMode)
|
||||
Preferences::ClearUser(kPrefRecentCrashes);
|
||||
GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// check whether safe mode is necessary
|
||||
PRInt32 maxResumedCrashes = -1;
|
||||
rv = Preferences::GetInt(kPrefMaxResumedCrashes, &maxResumedCrashes);
|
||||
NS_ENSURE_SUCCESS(rv, NS_OK);
|
||||
|
||||
PRInt32 recentCrashes = 0;
|
||||
Preferences::GetInt(kPrefRecentCrashes, &recentCrashes);
|
||||
mIsSafeModeNecessary = (recentCrashes > maxResumedCrashes && maxResumedCrashes != -1);
|
||||
|
||||
// time of last successful startup
|
||||
PRInt32 lastSuccessfulStartup;
|
||||
rv = Preferences::GetInt(kPrefLastSuccess, &lastSuccessfulStartup);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
PRInt32 lockSeconds = (PRInt32)(replacedLockTime / PR_MSEC_PER_SEC);
|
||||
|
||||
// started close enough to good startup so call it good
|
||||
if (lockSeconds <= lastSuccessfulStartup + MAX_STARTUP_BUFFER
|
||||
&& lockSeconds >= lastSuccessfulStartup - MAX_STARTUP_BUFFER) {
|
||||
GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// sanity check that the pref set at last success is not greater than the current time
|
||||
if (PR_Now() / PR_USEC_PER_SEC <= lastSuccessfulStartup)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
if (inSafeMode) {
|
||||
GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
PRTime now = (PR_Now() / PR_USEC_PER_MSEC);
|
||||
// if the last startup attempt which crashed was in the last 6 hours
|
||||
if (replacedLockTime >= now - MAX_TIME_SINCE_STARTUP) {
|
||||
NS_WARNING("Last startup was detected as a crash.");
|
||||
recentCrashes++;
|
||||
rv = Preferences::SetInt(kPrefRecentCrashes, recentCrashes);
|
||||
} else {
|
||||
// Otherwise ignore that crash and all previous since it may not be applicable anymore
|
||||
// and we don't want someone to get stuck in safe mode if their prefs are read-only.
|
||||
rv = Preferences::ClearUser(kPrefRecentCrashes);
|
||||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// recalculate since recent crashes count may have changed above
|
||||
mIsSafeModeNecessary = (recentCrashes > maxResumedCrashes && maxResumedCrashes != -1);
|
||||
|
||||
nsCOMPtr<nsIPrefService> prefs = Preferences::GetService();
|
||||
rv = prefs->SavePrefFile(nsnull); // flush prefs to disk since we are tracking crashes
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAppStartup::TrackStartupCrashEnd()
|
||||
{
|
||||
bool inSafeMode = false;
|
||||
nsCOMPtr<nsIXULRuntime> xr = do_GetService(XULRUNTIME_SERVICE_CONTRACTID);
|
||||
if (xr)
|
||||
xr->GetInSafeMode(&inSafeMode);
|
||||
|
||||
// return if we already ended or we're restarting into safe mode
|
||||
if (mStartupCrashTrackingEnded || (mIsSafeModeNecessary && !inSafeMode))
|
||||
return NS_OK;
|
||||
mStartupCrashTrackingEnded = true;
|
||||
|
||||
// Use the timestamp of XRE_main as an approximation for the lock file timestamp.
|
||||
// See MAX_STARTUP_BUFFER for the buffer time period.
|
||||
nsresult rv;
|
||||
PRTime mainTime = StartupTimeline::Get(StartupTimeline::MAIN);
|
||||
if (mainTime <= 0) {
|
||||
NS_WARNING("Could not get StartupTimeline::MAIN time.");
|
||||
} else {
|
||||
PRInt32 lockFileTime = (PRInt32)(mainTime / PR_USEC_PER_SEC);
|
||||
rv = Preferences::SetInt(kPrefLastSuccess, lockFileTime);
|
||||
if (NS_FAILED(rv)) NS_WARNING("Could not set startup crash detection pref.");
|
||||
}
|
||||
|
||||
if (inSafeMode && mIsSafeModeNecessary) {
|
||||
// On a successful startup in automatic safe mode, allow the user one more crash
|
||||
// in regular mode before returning to safe mode.
|
||||
PRInt32 maxResumedCrashes = 0;
|
||||
PRInt32 prefType;
|
||||
rv = Preferences::GetDefaultRootBranch()->GetPrefType(kPrefMaxResumedCrashes, &prefType);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (prefType == nsIPrefBranch::PREF_INT) {
|
||||
rv = Preferences::GetInt(kPrefMaxResumedCrashes, &maxResumedCrashes);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
rv = Preferences::SetInt(kPrefRecentCrashes, maxResumedCrashes);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else if (!inSafeMode) {
|
||||
// clear the count of recent crashes after a succesful startup when not in safe mode
|
||||
rv = Preferences::ClearUser(kPrefRecentCrashes);
|
||||
if (NS_FAILED(rv)) NS_WARNING("Could not clear startup crash count.");
|
||||
}
|
||||
nsCOMPtr<nsIPrefService> prefs = Preferences::GetService();
|
||||
rv = prefs->SavePrefFile(nsnull); // flush prefs to disk since we are tracking crashes
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAppStartup::RestartInSafeMode(PRUint32 aQuitMode)
|
||||
{
|
||||
PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
|
||||
this->Quit(aQuitMode | nsIAppStartup::eRestart);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -93,6 +93,8 @@ private:
|
||||
bool mAttemptingQuit; // Quit(eAttemptQuit) still trying
|
||||
bool mRestart; // Quit(eRestart)
|
||||
bool mInterrupted; // Was startup interrupted by an interactive prompt?
|
||||
bool mIsSafeModeNecessary; // Whether safe mode is necessary
|
||||
bool mStartupCrashTrackingEnded; // Whether startup crash tracking has already ended
|
||||
|
||||
#if defined(XP_WIN)
|
||||
//Interaction with OS-provided profiling probes
|
||||
|
@ -76,6 +76,45 @@ interface nsIAppStartup : nsISupports
|
||||
void enterLastWindowClosingSurvivalArea();
|
||||
void exitLastWindowClosingSurvivalArea();
|
||||
|
||||
/**
|
||||
* Startup Crash Detection
|
||||
*
|
||||
* Keeps track of application startup begining and success using flags to
|
||||
* determine whether the application is crashing on startup.
|
||||
* When the number of crashes crosses the acceptable threshold, safe mode
|
||||
* or other repair procedures are performed.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Whether automatic safe mode is necessary at this time. This gets set
|
||||
* in trackStartupCrashBegin.
|
||||
*
|
||||
* @see trackStartupCrashBegin
|
||||
*/
|
||||
readonly attribute boolean automaticSafeModeNecessary;
|
||||
|
||||
/**
|
||||
* Restart the application in safe mode
|
||||
* @param aQuitMode
|
||||
* This parameter modifies how the app is shutdown.
|
||||
* @see nsIAppStartup::quit
|
||||
*/
|
||||
void restartInSafeMode(in PRUint32 aQuitMode);
|
||||
|
||||
/**
|
||||
* If the last startup crashed then increment a counter.
|
||||
* Set a flag so on next startup we can detect whether TrackStartupCrashEnd
|
||||
* was called (and therefore the application crashed).
|
||||
* @return whether safe mode is necessary
|
||||
*/
|
||||
bool trackStartupCrashBegin();
|
||||
|
||||
/**
|
||||
* We have succesfully started without crashing. Clear flags that were
|
||||
* tracking past crashes.
|
||||
*/
|
||||
void trackStartupCrashEnd();
|
||||
|
||||
/**
|
||||
* The following flags may be passed as the aMode parameter to the quit
|
||||
* method. One and only one of the "Quit" flags must be specified. The
|
||||
|
@ -47,6 +47,7 @@ include $(topsrcdir)/config/rules.mk
|
||||
_BROWSER_FILES = \
|
||||
browser_bug511456.js \
|
||||
browser_bug537449.js \
|
||||
browser_crash_detection.js \
|
||||
beforeunload.html \
|
||||
$(NULL)
|
||||
|
||||
|
@ -0,0 +1,23 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
function test() {
|
||||
function checkLastSuccess() {
|
||||
let lastSuccess = Services.prefs.getIntPref("toolkit.startup.last_success");
|
||||
let si = Services.startup.getStartupInfo();
|
||||
is(lastSuccess, parseInt(si["main"].getTime() / 1000, 10),
|
||||
"Startup tracking pref should be set after a delay at the end of startup");
|
||||
finish();
|
||||
}
|
||||
|
||||
if (Services.prefs.getPrefType("toolkit.startup.max_resumed_crashes") == Services.prefs.PREF_INVALID) {
|
||||
info("Skipping this test since startup crash detection is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
const startupCrashEndDelay = 35 * 1000;
|
||||
waitForExplicitFinish();
|
||||
requestLongerTimeout(2);
|
||||
setTimeout(checkLastSuccess, startupCrashEndDelay);
|
||||
}
|
57
toolkit/components/startup/tests/unit/head_startup.js
Normal file
57
toolkit/components/startup/tests/unit/head_startup.js
Normal file
@ -0,0 +1,57 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const XULRUNTIME_CONTRACTID = "@mozilla.org/xre/runtime;1";
|
||||
const XULRUNTIME_CID = Components.ID("7685dac8-3637-4660-a544-928c5ec0e714}");
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
let gAppInfo = null;
|
||||
|
||||
function createAppInfo(id, name, version, platformVersion) {
|
||||
gAppInfo = {
|
||||
// nsIXULAppInfo
|
||||
vendor: "Mozilla",
|
||||
name: name,
|
||||
ID: id,
|
||||
version: version,
|
||||
appBuildID: "2007010101",
|
||||
platformVersion: platformVersion ? platformVersion : "1.0",
|
||||
platformBuildID: "2007010101",
|
||||
|
||||
// nsIXULRuntime
|
||||
inSafeMode: false,
|
||||
logConsoleErrors: true,
|
||||
OS: "XPCShell",
|
||||
replacedLockTime: 0,
|
||||
XPCOMABI: "noarch-spidermonkey",
|
||||
invalidateCachesOnRestart: function invalidateCachesOnRestart() {
|
||||
// Do nothing
|
||||
},
|
||||
|
||||
// nsICrashReporter
|
||||
annotations: {
|
||||
},
|
||||
|
||||
annotateCrashReport: function(key, data) {
|
||||
this.annotations[key] = data;
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIXULAppInfo,
|
||||
Ci.nsIXULRuntime,
|
||||
Ci.nsICrashReporter,
|
||||
Ci.nsISupports])
|
||||
};
|
||||
|
||||
let XULAppInfoFactory = {
|
||||
createInstance: function (outer, iid) {
|
||||
if (outer != null)
|
||||
throw Components.results.NS_ERROR_NO_AGGREGATION;
|
||||
return gAppInfo.QueryInterface(iid);
|
||||
}
|
||||
};
|
||||
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
registrar.registerFactory(XULRUNTIME_CID, "XULRuntime",
|
||||
XULRUNTIME_CONTRACTID, XULAppInfoFactory);
|
||||
|
||||
}
|
295
toolkit/components/startup/tests/unit/test_startup_crash.js
Normal file
295
toolkit/components/startup/tests/unit/test_startup_crash.js
Normal file
@ -0,0 +1,295 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "10.0");
|
||||
|
||||
let prefService = Services.prefs;
|
||||
let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].
|
||||
getService(Ci.nsIAppStartup);
|
||||
|
||||
const pref_last_success = "toolkit.startup.last_success";
|
||||
const pref_recent_crashes = "toolkit.startup.recent_crashes";
|
||||
const pref_max_resumed_crashes = "toolkit.startup.max_resumed_crashes";
|
||||
|
||||
function run_test() {
|
||||
resetTestEnv(0);
|
||||
|
||||
test_trackStartupCrashBegin();
|
||||
test_trackStartupCrashEnd();
|
||||
test_trackStartupCrashBegin_safeMode();
|
||||
test_trackStartupCrashEnd_safeMode();
|
||||
test_maxResumed();
|
||||
resetTestEnv(0);
|
||||
}
|
||||
|
||||
// reset prefs to default
|
||||
function resetTestEnv(replacedLockTime) {
|
||||
try {
|
||||
// call begin to reset mStartupCrashTrackingEnded
|
||||
appStartup.trackStartupCrashBegin();
|
||||
} catch (x) { }
|
||||
prefService.setIntPref(pref_max_resumed_crashes, 2);
|
||||
prefService.clearUserPref(pref_recent_crashes);
|
||||
gAppInfo.replacedLockTime = replacedLockTime;
|
||||
prefService.clearUserPref(pref_last_success);
|
||||
}
|
||||
|
||||
function now_seconds() {
|
||||
return ms_to_s(Date.now());
|
||||
}
|
||||
|
||||
function ms_to_s(ms) {
|
||||
return Math.floor(ms / 1000);
|
||||
}
|
||||
|
||||
function test_trackStartupCrashBegin() {
|
||||
let max_resumed = prefService.getIntPref(pref_max_resumed_crashes);
|
||||
do_check_false(gAppInfo.inSafeMode);
|
||||
|
||||
// first run with startup crash tracking - existing profile lock
|
||||
let replacedLockTime = Date.now();
|
||||
resetTestEnv(replacedLockTime);
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_false(prefService.prefHasUserValue(pref_last_success));
|
||||
do_check_eq(replacedLockTime, gAppInfo.replacedLockTime);
|
||||
try {
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
do_throw("Should have thrown since last_success is not set");
|
||||
} catch (x) { }
|
||||
|
||||
do_check_false(prefService.prefHasUserValue(pref_last_success));
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// first run with startup crash tracking - no existing profile lock
|
||||
replacedLockTime = 0;
|
||||
resetTestEnv(replacedLockTime);
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_false(prefService.prefHasUserValue(pref_last_success));
|
||||
do_check_eq(replacedLockTime, gAppInfo.replacedLockTime);
|
||||
try {
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
do_throw("Should have thrown since last_success is not set");
|
||||
} catch (x) { }
|
||||
|
||||
do_check_false(prefService.prefHasUserValue(pref_last_success));
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// normal startup - last startup was success
|
||||
replacedLockTime = Date.now();
|
||||
resetTestEnv(replacedLockTime);
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime));
|
||||
do_check_eq(ms_to_s(replacedLockTime), prefService.getIntPref(pref_last_success));
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
do_check_eq(ms_to_s(replacedLockTime), prefService.getIntPref(pref_last_success));
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// normal startup with 1 recent crash
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_recent_crashes, 1);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime));
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
do_check_eq(ms_to_s(replacedLockTime), prefService.getIntPref(pref_last_success));
|
||||
do_check_eq(1, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// normal startup with max_resumed_crashes crash
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_recent_crashes, max_resumed);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime));
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
do_check_eq(ms_to_s(replacedLockTime), prefService.getIntPref(pref_last_success));
|
||||
do_check_eq(max_resumed, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// normal startup with too many recent crashes
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_recent_crashes, max_resumed + 1);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime));
|
||||
do_check_true(appStartup.trackStartupCrashBegin());
|
||||
// should remain the same since the last startup was not a crash
|
||||
do_check_eq(max_resumed + 1, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_true(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// normal startup with too many recent crashes and startup crash tracking disabled
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_max_resumed_crashes, -1);
|
||||
prefService.setIntPref(pref_recent_crashes, max_resumed + 1);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime));
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
// should remain the same since the last startup was not a crash
|
||||
do_check_eq(max_resumed + 1, prefService.getIntPref(pref_recent_crashes));
|
||||
// returns false when disabled
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
do_check_eq(-1, prefService.getIntPref(pref_max_resumed_crashes));
|
||||
|
||||
// normal startup after 1 non-recent crash (1 year ago), no other recent
|
||||
replacedLockTime = Date.now() - 365 * 24 * 60 * 60 * 1000;
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime) - 365 * 24 * 60 * 60);
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
// recent crash count pref should be unset since the last crash was not recent
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// normal startup after 1 crash (1 minute ago), no other recent
|
||||
replacedLockTime = Date.now() - 60 * 1000;
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime) - 60 * 60); // last success - 1 hour ago
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
// recent crash count pref should be created with value 1
|
||||
do_check_eq(1, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// normal startup after another crash (1 minute ago), 1 already
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime) - 60 * 60); // last success - 1 hour ago
|
||||
replacedLockTime = Date.now() - 60 * 1000;
|
||||
gAppInfo.replacedLockTime = replacedLockTime;
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
// recent crash count pref should be incremented by 1
|
||||
do_check_eq(2, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// normal startup after another crash (1 minute ago), 2 already
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime) - 60 * 60); // last success - 1 hour ago
|
||||
do_check_true(appStartup.trackStartupCrashBegin());
|
||||
// recent crash count pref should be incremented by 1
|
||||
do_check_eq(3, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_true(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// normal startup after 1 non-recent crash (1 year ago), 3 crashes already
|
||||
replacedLockTime = Date.now() - 365 * 24 * 60 * 60 * 1000;
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime) - 60 * 60); // last success - 1 hour ago
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
// recent crash count should be unset since the last crash was not recent
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
}
|
||||
|
||||
function test_trackStartupCrashEnd() {
|
||||
// successful startup with no last_success (upgrade test)
|
||||
let replacedLockTime = Date.now() - 10 * 1000; // 10s ago
|
||||
resetTestEnv(replacedLockTime);
|
||||
try {
|
||||
appStartup.trackStartupCrashBegin(); // required to be called before end
|
||||
do_throw("Should have thrown since last_success is not set");
|
||||
} catch (x) { }
|
||||
appStartup.trackStartupCrashEnd();
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_false(prefService.prefHasUserValue(pref_last_success));
|
||||
|
||||
// successful startup - should set last_success
|
||||
replacedLockTime = Date.now() - 10 * 1000; // 10s ago
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime));
|
||||
appStartup.trackStartupCrashBegin(); // required to be called before end
|
||||
appStartup.trackStartupCrashEnd();
|
||||
// ensure last_success was set since we have declared a succesful startup
|
||||
// main timestamp doesn't get set in XPCShell so approximate with now
|
||||
do_check_true(prefService.getIntPref(pref_last_success) <= now_seconds());
|
||||
do_check_true(prefService.getIntPref(pref_last_success) >= now_seconds() - 4 * 60 * 60);
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
|
||||
// successful startup with 1 recent crash
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime));
|
||||
prefService.setIntPref(pref_recent_crashes, 1);
|
||||
appStartup.trackStartupCrashBegin(); // required to be called before end
|
||||
appStartup.trackStartupCrashEnd();
|
||||
// ensure recent_crashes was cleared since we have declared a succesful startup
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
}
|
||||
|
||||
function test_trackStartupCrashBegin_safeMode() {
|
||||
gAppInfo.inSafeMode = true;
|
||||
resetTestEnv(0);
|
||||
let max_resumed = prefService.getIntPref(pref_max_resumed_crashes);
|
||||
|
||||
// check manual safe mode doesn't change prefs without crash
|
||||
let replacedLockTime = Date.now() - 10 * 1000; // 10s ago
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime));
|
||||
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_true(prefService.prefHasUserValue(pref_last_success));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_true(prefService.prefHasUserValue(pref_last_success));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// check forced safe mode doesn't change prefs without crash
|
||||
replacedLockTime = Date.now() - 10 * 1000; // 10s ago
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime));
|
||||
prefService.setIntPref(pref_recent_crashes, max_resumed + 1);
|
||||
|
||||
do_check_eq(max_resumed + 1, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_true(prefService.prefHasUserValue(pref_last_success));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
do_check_true(appStartup.trackStartupCrashBegin());
|
||||
do_check_eq(max_resumed + 1, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_true(prefService.prefHasUserValue(pref_last_success));
|
||||
do_check_true(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// check forced safe mode after old crash
|
||||
replacedLockTime = Date.now() - 365 * 24 * 60 * 60 * 1000;
|
||||
resetTestEnv(replacedLockTime);
|
||||
// one year ago
|
||||
let last_success = ms_to_s(replacedLockTime) - 365 * 24 * 60 * 60;
|
||||
prefService.setIntPref(pref_last_success, last_success);
|
||||
prefService.setIntPref(pref_recent_crashes, max_resumed + 1);
|
||||
do_check_eq(max_resumed + 1, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_true(prefService.prefHasUserValue(pref_last_success));
|
||||
do_check_true(appStartup.automaticSafeModeNecessary);
|
||||
do_check_true(appStartup.trackStartupCrashBegin());
|
||||
do_check_eq(max_resumed + 1, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_eq(last_success, prefService.getIntPref(pref_last_success));
|
||||
do_check_true(appStartup.automaticSafeModeNecessary);
|
||||
}
|
||||
|
||||
function test_trackStartupCrashEnd_safeMode() {
|
||||
gAppInfo.inSafeMode = true;
|
||||
let replacedLockTime = Date.now();
|
||||
resetTestEnv(replacedLockTime);
|
||||
let max_resumed = prefService.getIntPref(pref_max_resumed_crashes);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime) - 24 * 60 * 60);
|
||||
|
||||
// ensure recent_crashes were not cleared in manual safe mode
|
||||
prefService.setIntPref(pref_recent_crashes, 1);
|
||||
appStartup.trackStartupCrashBegin(); // required to be called before end
|
||||
appStartup.trackStartupCrashEnd();
|
||||
do_check_eq(1, prefService.getIntPref(pref_recent_crashes));
|
||||
|
||||
// recent_crashes should be set to max_resumed in forced safe mode to allow the user
|
||||
// to try and start in regular mode after making changes.
|
||||
prefService.setIntPref(pref_recent_crashes, max_resumed + 1);
|
||||
appStartup.trackStartupCrashBegin(); // required to be called before end
|
||||
appStartup.trackStartupCrashEnd();
|
||||
do_check_eq(max_resumed, prefService.getIntPref(pref_recent_crashes));
|
||||
}
|
||||
|
||||
function test_maxResumed() {
|
||||
resetTestEnv(0);
|
||||
gAppInfo.inSafeMode = false;
|
||||
let max_resumed = prefService.getIntPref(pref_max_resumed_crashes);
|
||||
let replacedLockTime = Date.now();
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_max_resumed_crashes, -1);
|
||||
|
||||
prefService.setIntPref(pref_recent_crashes, max_resumed + 1);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime) - 24 * 60 * 60);
|
||||
appStartup.trackStartupCrashBegin();
|
||||
// should remain the same since the last startup was not a crash
|
||||
do_check_eq(max_resumed + 2, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
}
|
5
toolkit/components/startup/tests/unit/xpcshell.ini
Normal file
5
toolkit/components/startup/tests/unit/xpcshell.ini
Normal file
@ -0,0 +1,5 @@
|
||||
[DEFAULT]
|
||||
head = head_startup.js
|
||||
tail =
|
||||
|
||||
[test_startup_crash.js]
|
@ -84,6 +84,7 @@ const PREFS_WHITELIST = [
|
||||
"privacy.",
|
||||
"security.",
|
||||
"svg.",
|
||||
"toolkit.startup.recent_crashes",
|
||||
"webgl."
|
||||
];
|
||||
|
||||
|
@ -63,6 +63,11 @@ interface nsIProfileLock : nsISupports
|
||||
*/
|
||||
readonly attribute nsILocalFile localDirectory;
|
||||
|
||||
/**
|
||||
* The timestamp of an existing profile lock at lock time.
|
||||
*/
|
||||
readonly attribute PRInt64 replacedLockTime;
|
||||
|
||||
/**
|
||||
* Unlock the profile.
|
||||
*/
|
||||
|
@ -362,6 +362,13 @@ nsToolkitProfileLock::Unlock()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsToolkitProfileLock::GetReplacedLockTime(PRInt64 *aResult)
|
||||
{
|
||||
mLock.GetReplacedLockTime(aResult);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsToolkitProfileLock::~nsToolkitProfileLock()
|
||||
{
|
||||
if (mDirectory) {
|
||||
|
@ -234,6 +234,8 @@ char **gArgv;
|
||||
static const char gToolkitVersion[] = NS_STRINGIFY(GRE_MILESTONE);
|
||||
static const char gToolkitBuildID[] = NS_STRINGIFY(GRE_BUILDID);
|
||||
|
||||
static nsIProfileLock* gProfileLock;
|
||||
|
||||
static int gRestartArgc;
|
||||
static char **gRestartArgv;
|
||||
|
||||
@ -828,6 +830,15 @@ nsXULAppInfo::InvalidateCachesOnRestart()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsXULAppInfo::GetReplacedLockTime(PRInt64 *aReplacedLockTime)
|
||||
{
|
||||
if (!gProfileLock)
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
gProfileLock->GetReplacedLockTime(aReplacedLockTime);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
#ifdef XP_WIN
|
||||
// Matches the enum in WinNT.h for the Vista SDK but renamed so that we can
|
||||
// safely build with the Vista SDK and without it.
|
||||
@ -3199,6 +3210,7 @@ XRE_main(int argc, char* argv[], const nsXREAppData* aAppData)
|
||||
ProfileMissingDialog(nativeApp);
|
||||
return 1;
|
||||
}
|
||||
gProfileLock = profileLock;
|
||||
|
||||
nsCOMPtr<nsILocalFile> profD;
|
||||
rv = profileLock->GetDirectory(getter_AddRefs(profD));
|
||||
@ -3583,6 +3595,7 @@ XRE_main(int argc, char* argv[], const nsXREAppData* aAppData)
|
||||
// unlock the profile after ScopedXPCOMStartup object (xpcom)
|
||||
// has gone out of scope. see bug #386739 for more details
|
||||
profileLock->Unlock();
|
||||
gProfileLock = nsnull;
|
||||
|
||||
#if defined(MOZ_WIDGET_QT)
|
||||
nsQAppInstance::Release();
|
||||
|
@ -40,11 +40,13 @@
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "nsAppRunner.h"
|
||||
#include "nsToolkitCompsCID.h"
|
||||
#include "nsXREDirProvider.h"
|
||||
|
||||
#include "jsapi.h"
|
||||
|
||||
#include "nsIJSContextStack.h"
|
||||
#include "nsIAppStartup.h"
|
||||
#include "nsIDirectoryEnumerator.h"
|
||||
#include "nsILocalFile.h"
|
||||
#include "nsIObserver.h"
|
||||
@ -733,6 +735,26 @@ nsXREDirProvider::DoStartup()
|
||||
|
||||
mProfileNotified = true;
|
||||
|
||||
/*
|
||||
Setup prefs before profile-do-change to be able to use them to track
|
||||
crashes and because we want to begin crash tracking before other code run
|
||||
from this notification since they may cause crashes.
|
||||
*/
|
||||
nsresult rv = mozilla::Preferences::ResetAndReadUserPrefs();
|
||||
if (NS_FAILED(rv)) NS_WARNING("Failed to setup pref service.");
|
||||
|
||||
bool safeModeNecessary = false;
|
||||
nsCOMPtr<nsIAppStartup> appStartup (do_GetService(NS_APPSTARTUP_CONTRACTID));
|
||||
if (appStartup) {
|
||||
rv = appStartup->TrackStartupCrashBegin(&safeModeNecessary);
|
||||
if (NS_FAILED(rv)) NS_WARNING("Error while beginning startup crash tracking");
|
||||
|
||||
if (!gSafeMode && safeModeNecessary) {
|
||||
appStartup->RestartInSafeMode(nsIAppStartup::eForceQuit);
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
static const PRUnichar kStartup[] = {'s','t','a','r','t','u','p','\0'};
|
||||
obsSvc->NotifyObservers(nsnull, "profile-do-change", kStartup);
|
||||
// Init the Extension Manager
|
||||
@ -753,6 +775,11 @@ nsXREDirProvider::DoStartup()
|
||||
(void)NS_CreateServicesFromCategory("profile-after-change", nsnull,
|
||||
"profile-after-change");
|
||||
|
||||
if (gSafeMode && safeModeNecessary) {
|
||||
static const PRUnichar kCrashed[] = {'c','r','a','s','h','e','d','\0'};
|
||||
obsSvc->NotifyObservers(nsnull, "safemode-forced", kCrashed);
|
||||
}
|
||||
|
||||
obsSvc->NotifyObservers(nsnull, "profile-initial-state", nsnull);
|
||||
}
|
||||
return NS_OK;
|
||||
|
@ -115,4 +115,11 @@ interface nsIXULRuntime : nsISupports
|
||||
* @throw NS_ERROR_NOT_AVAILABLE if not available.
|
||||
*/
|
||||
void ensureContentProcess();
|
||||
|
||||
/**
|
||||
* Modification time of the profile lock before the profile was locked on
|
||||
* this startup. Used to know the last time the profile was used and not
|
||||
* closed cleanly. This is set to 0 if there was no existing profile lock.
|
||||
*/
|
||||
readonly attribute PRInt64 replacedLockTime;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user