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:
Matthew Noorenberghe 2011-11-07 21:20:42 -08:00
parent f53850fe68
commit f018d13961
27 changed files with 767 additions and 47 deletions

View File

@ -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);

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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"/>

View File

@ -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":

View File

@ -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:">

View File

@ -81,6 +81,11 @@ public:
nsresult Init();
/**
* Reset loaded user prefs then read them
*/
static nsresult ResetAndReadUserPrefs();
/**
* Returns the singleton instance which is addreffed.
*/

View File

@ -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")) {

View File

@ -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)
{

View File

@ -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;

View File

@ -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]

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -47,6 +47,7 @@ include $(topsrcdir)/config/rules.mk
_BROWSER_FILES = \
browser_bug511456.js \
browser_bug537449.js \
browser_crash_detection.js \
beforeunload.html \
$(NULL)

View File

@ -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);
}

View 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);
}

View 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);
}

View File

@ -0,0 +1,5 @@
[DEFAULT]
head = head_startup.js
tail =
[test_startup_crash.js]

View File

@ -84,6 +84,7 @@ const PREFS_WHITELIST = [
"privacy.",
"security.",
"svg.",
"toolkit.startup.recent_crashes",
"webgl."
];

View File

@ -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.
*/

View File

@ -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) {

View File

@ -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();

View File

@ -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;

View File

@ -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;
};