Bug 294260 - Safe Mode: Auto detect previous start-up failure and offer to start in safe mode r=bsmedberg,Mossop
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Mon, 07 Nov 2011 21:20:42 -0800
changeset 86410 bcdc5493e6a118a9829359640216508f3127ec84
parent 86409 21cc56ed5fe6c2b8eac9fd030c96b41c5370084c
child 86411 a7f28072d882d4c0ea8b60a3dbdb47c0377b3739
push id5835
push usermozilla@noorenberghe.ca
push dateWed, 08 Feb 2012 07:58:21 +0000
treeherdermozilla-inbound@36b986ef4b5b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg, Mossop
bugs294260
milestone13.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 294260 - Safe Mode: Auto detect previous start-up failure and offer to start in safe mode r=bsmedberg,Mossop
browser/app/profile/firefox.js
browser/base/content/aboutDialog.js
browser/base/content/browser.js
browser/base/content/safeMode.js
browser/base/content/safeMode.xul
browser/components/nsBrowserGlue.js
browser/locales/en-US/chrome/browser/safeMode.dtd
modules/libpref/public/Preferences.h
modules/libpref/src/Preferences.cpp
profile/dirserviceprovider/src/nsProfileLock.cpp
profile/dirserviceprovider/src/nsProfileLock.h
testing/xpcshell/xpcshell.ini
toolkit/components/startup/Makefile.in
toolkit/components/startup/nsAppStartup.cpp
toolkit/components/startup/nsAppStartup.h
toolkit/components/startup/public/nsIAppStartup.idl
toolkit/components/startup/tests/browser/Makefile.in
toolkit/components/startup/tests/browser/beforeunload.html
toolkit/components/startup/tests/browser/browser_crash_detection.js
toolkit/components/startup/tests/unit/head_startup.js
toolkit/components/startup/tests/unit/test_startup_crash.js
toolkit/components/startup/tests/unit/xpcshell.ini
toolkit/content/aboutSupport.js
toolkit/profile/nsIToolkitProfile.idl
toolkit/profile/nsToolkitProfileService.cpp
toolkit/xre/nsAppRunner.cpp
toolkit/xre/nsXREDirProvider.cpp
xpcom/system/nsIXULRuntime.idl
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1120,8 +1120,13 @@ pref("browser.panorama.animate_zoom", tr
 // Defines the url to be used for new tabs.
 pref("browser.newtab.url", "about:newtab");
 
 // Toggles the content of 'about:newtab'. Shows the grid when enabled.
 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);
--- a/browser/base/content/aboutDialog.js
+++ b/browser/base/content/aboutDialog.js
@@ -260,27 +260,27 @@ appUpdater.prototype =
       let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"].
                        createInstance(Components.interfaces.nsISupportsPRBool);
       Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
 
       // Something aborted the quit process.
       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;
     }
 
     const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul";
     // Firefox no longer displays a license for updates and the licenseURL check
     // is just in case a distibution does.
     if (this.update && (this.update.billboardURL || this.update.licenseURL ||
         this.addons.length != 0)) {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1800,16 +1800,27 @@ function delayedStartup(isLoadingBlank, 
       appMenuOpening = null;
       Services.telemetry.getHistogramById("FX_APP_MENU_OPEN_MS").add(duration);
     }, false);
   }
 
   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");
 }
 
 function BrowserShutdown() {
   // In certain scenarios it's possible for unload to be fired before onload,
   // (e.g. if the window is being closed after browser.js loads but before the
   // load completes). In that case, there's nothing to do here.
@@ -8988,20 +8999,19 @@ function safeModeRestart()
                     (Services.prompt.BUTTON_POS_1 *
                      Services.prompt.BUTTON_TITLE_CANCEL) +
                     Services.prompt.BUTTON_POS_0_DEFAULT;
 
   let rv = Services.prompt.confirmEx(window, promptTitle, promptMessage,
                                      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);
   }
 }
 
 /* duplicateTabIn duplicates tab in a place specified by the parameter |where|.
  *
  * |where| can be:
  *  "tab"         new tab
  *  "tabshifted"  same as "tab" but in background if default is to select new
--- a/browser/base/content/safeMode.js
+++ b/browser/base/content/safeMode.js
@@ -133,16 +133,24 @@ function onOK() {
 
 function onCancel() {
   var appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
                              .getService(Components.interfaces.nsIAppStartup);
   appStartup.quit(appStartup.eForceQuit);
 }
 
 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);
 }
 
 function UpdateOKButtonState() {
   document.documentElement.getButton("accept").disabled = 
     !document.getElementById("resetUserPrefs").checked &&
     !document.getElementById("deleteBookmarks").checked &&
--- a/browser/base/content/safeMode.xul
+++ b/browser/base/content/safeMode.xul
@@ -66,17 +66,22 @@
             ondialogextra1="window.close()"
             onload="onLoad();"
             buttondisabledaccept="true">
 
   <script type="application/javascript" src="chrome://browser/content/safeMode.js"/>
 
   <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"/>
 
   <label value="&safeModeDescription2.label;"/>
   <vbox id="tasks">
     <checkbox id="disableAddons"  label="&disableAddons.label;"  accesskey="&disableAddons.accesskey;"/>
     <checkbox id="resetToolbars"  label="&resetToolbars.label;"  accesskey="&resetToolbars.accesskey;"/>
     <checkbox id="deleteBookmarks" label="&deleteBookmarks.label;" accesskey="&deleteBookmarks.accesskey;"/>
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -183,16 +183,23 @@ BrowserGlue.prototype = {
         break;
       case "quit-application-requested":
         this._onQuitRequest(subject, data);
         break;
       case "quit-application-granted":
         // 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":
         // The application is not actually quitting, but the last full browser
         // window is about to be closed.
         this._onQuitRequest(subject, "lastwindow");
         break;
       case "browser-lastwindow-close-granted":
--- a/browser/locales/en-US/chrome/browser/safeMode.dtd
+++ b/browser/locales/en-US/chrome/browser/safeMode.dtd
@@ -34,16 +34,19 @@
    - the terms of any one of the MPL, the GPL or the LGPL.
    -
 #endif
    - ***** END LICENSE BLOCK ***** -->
 
 <!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:">
 
 <!ENTITY disableAddons.label          "Disable all add-ons">
 <!ENTITY disableAddons.accesskey      "D">
 
 <!ENTITY resetToolbars.label          "Reset toolbars and controls">
 <!ENTITY resetToolbars.accesskey      "R">
--- a/modules/libpref/public/Preferences.h
+++ b/modules/libpref/public/Preferences.h
@@ -77,16 +77,21 @@ public:
   NS_DECL_NSIOBSERVER
 
   Preferences();
   virtual ~Preferences();
 
   nsresult Init();
 
   /**
+   * Reset loaded user prefs then read them
+   */
+  static nsresult ResetAndReadUserPrefs();
+
+  /**
    * Returns the singleton instance which is addreffed.
    */
   static Preferences* GetInstanceForService();
 
   /**
    * Finallizes global members.
    */
   static void Shutdown();
--- a/modules/libpref/src/Preferences.cpp
+++ b/modules/libpref/src/Preferences.cpp
@@ -346,24 +346,29 @@ Preferences::Init()
 
   nsCOMPtr<nsIObserverService> observerService =
     mozilla::services::GetObserverService();
   if (!observerService)
     return NS_ERROR_FAILURE;
 
   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)
 {
   if (XRE_GetProcessType() == GeckoProcessType_Content)
     return NS_ERROR_NOT_AVAILABLE;
 
   nsresult rv = NS_OK;
@@ -372,19 +377,16 @@ Preferences::Observe(nsISupports *aSubje
     if (!nsCRT::strcmp(someData, NS_LITERAL_STRING("shutdown-cleanse").get())) {
       if (mCurrentFile) {
         mCurrentFile->Remove(false);
         mCurrentFile = nsnull;
       }
     } 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")) {
     // Reload the default prefs from file.
     pref_InitInitialObjects();
   }
   return rv;
 }
--- a/profile/dirserviceprovider/src/nsProfileLock.cpp
+++ b/profile/dirserviceprovider/src/nsProfileLock.cpp
@@ -71,17 +71,18 @@
 // This code was moved from profile/src/nsProfileAccess.
 // **********************************************************************
 
 #if defined (XP_UNIX)
 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)
     ,mLockFileHandle(-1)
 #elif defined (XP_UNIX)
     ,mPidLockFileName(nsnull)
     ,mLockFileDesc(-1)
 #endif
@@ -226,20 +227,29 @@ void nsProfileLock::FatalSignalHandler(i
             oldact->sa_handler(signo);
         }
     }
 
     // Backstop exit call, just in case.
     _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)
     {
         struct flock lock;
         lock.l_start = 0;
         lock.l_len = 0; // len = 0 means entire file
         lock.l_type = F_WRLCK;
@@ -327,19 +337,29 @@ static bool IsSymlinkStaleLock(struct in
                     }
                 }
             }
         }
     }
     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);
 
     char hostname[256];
     PRStatus status = PR_GetSystemInfo(PR_SI_HOSTNAME, hostname, sizeof hostname);
     if (status == PR_SUCCESS)
     {
@@ -438,16 +458,21 @@ PR_BEGIN_MACRO                          
         printf("symlink() failed. errno = %d\n", errno);
 #endif
         rv = NS_ERROR_FAILURE;
     }
     return rv;
 }
 #endif /* XP_UNIX */
 
+nsresult nsProfileLock::GetReplacedLockTime(PRInt64 *aResult) {
+    *aResult = mReplacedLockTime;
+    return NS_OK;
+}
+
 nsresult nsProfileLock::Lock(nsILocalFile* aProfileDir,
                              nsIProfileUnlocker* *aUnlocker)
 {
 #if defined (XP_MACOSX)
     NS_NAMED_LITERAL_STRING(LOCKFILE_NAME, ".parentlock");
     NS_NAMED_LITERAL_STRING(OLD_LOCKFILE_NAME, "parent.lock");
 #elif defined (XP_UNIX)
     NS_NAMED_LITERAL_STRING(OLD_LOCKFILE_NAME, "lock");
@@ -476,27 +501,23 @@ nsresult nsProfileLock::Lock(nsILocalFil
 
     rv = lockFile->Append(LOCKFILE_NAME);
     if (NS_FAILED(rv))
         return rv;
         
 #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))
     {
         // Check for the old-style lock used by pre-mozilla 1.3 builds.
         // Those builds used an earlier check to prevent the application
         // from launching if another instance was already running. Because
         // of that, we don't need to create an old-style lock as well.
@@ -538,85 +559,82 @@ nsresult nsProfileLock::Lock(nsILocalFil
             else
             {
                 NS_WARNING("Could not read lock file - ignoring lock");
             }
         }
         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
         // doesn't support symlinks, or we don't have permission to
         // create a symlink there.  In such cases we should just
         // continue because it's unlikely there is an old build
         // running with a symlink there and we've already successfully
         // placed a fcntl lock.
         if (rv != NS_ERROR_FILE_ACCESS_DENIED)
             rv = NS_OK;
     }
     else if (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
         // using the old symlink path
-        rv = LockWithSymlink(oldFilePath, false);
+        rv = LockWithSymlink(oldLockFile, false);
     }
 
 #elif defined(XP_WIN)
     nsAutoString filePath;
     rv = lockFile->GetPath(filePath);
     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!
         return NS_ERROR_FILE_ACCESS_DENIED;
     }
 #elif defined(XP_OS2)
     nsCAutoString filePath;
     rv = lockFile->GetNativePath(filePath);
     if (NS_FAILED(rv))
         return rv;
 
+    lockFile->GetLastModifiedTime(&mReplacedLockTime);
+
     ULONG   ulAction = 0;
     APIRET  rc;
     rc = DosOpen(filePath.get(),
                   &mLockFileHandle,
                   &ulAction,
                   0,
                   FILE_NORMAL,
                   OPEN_ACTION_CREATE_IF_NEW | OPEN_ACTION_OPEN_IF_EXISTS,
@@ -628,16 +646,18 @@ nsresult nsProfileLock::Lock(nsILocalFil
         return NS_ERROR_FILE_ACCESS_DENIED;
     }
 #elif defined(VMS)
     nsCAutoString filePath;
     rv = lockFile->GetNativePath(filePath);
     if (NS_FAILED(rv))
         return rv;
 
+    lockFile->GetLastModifiedTime(&mReplacedLockTime);
+
     mLockFileDesc = open_noshr(filePath.get(), O_CREAT, 0666);
     if (mLockFileDesc == -1)
     {
         if ((errno == EVMSERR) && (vaxc$errno == RMS$_FLK))
         {
             return NS_ERROR_FILE_ACCESS_DENIED;
         }
         else
--- a/profile/dirserviceprovider/src/nsProfileLock.h
+++ b/profile/dirserviceprovider/src/nsProfileLock.h
@@ -84,19 +84,25 @@ public:
     nsresult                Lock(nsILocalFile* aProfileDir, nsIProfileUnlocker* *aUnlocker);
 
     /**
      * Unlock a profile directory.  If you're unlocking the directory because
      * the application is in the process of shutting down because of a fatal
      * 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;
 #elif defined (XP_OS2)
     LHANDLE                 mLockFileHandle;
 #elif defined (XP_UNIX)
 
     struct RemovePidLockFilesExiting {
@@ -109,23 +115,23 @@ private:
     static void             RemovePidLockFiles(bool aFatalSignal);
     static void             FatalSignalHandler(int signo
 #ifdef SA_SIGINFO
                                                , siginfo_t *info, void *context
 #endif
                                                );
     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;
 #endif
 
 };
 
 #endif /* __nsProfileLock_h___ */
--- a/testing/xpcshell/xpcshell.ini
+++ b/testing/xpcshell/xpcshell.ini
@@ -29,16 +29,17 @@ skip-if = os == "android"
 [include:toolkit/components/places/tests/unit/xpcshell.ini]
 [include:toolkit/components/places/tests/network/xpcshell.ini]
 [include:toolkit/components/urlformatter/tests/unit/xpcshell.ini]
 [include:toolkit/components/ctypes/tests/unit/xpcshell.ini]
 [include:toolkit/components/autocomplete/tests/unit/xpcshell.ini]
 [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]
 [include:toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini]
 [include:toolkit/mozapps/extensions/test/xpcshell-unpack/xpcshell.ini]
 [include:toolkit/mozapps/update/test_timermanager/unit/xpcshell.ini]
 [include:toolkit/mozapps/update/test_svc/unit/xpcshell.ini]
 [include:toolkit/mozapps/update/test/unit/xpcshell.ini]
--- a/toolkit/components/startup/Makefile.in
+++ b/toolkit/components/startup/Makefile.in
@@ -33,16 +33,17 @@
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 
 DEPTH      = ../../..
 topsrcdir  = @top_srcdir@
 srcdir     = @srcdir@
 VPATH      = @srcdir@
+relativesrcdir = toolkit/components/startup
 
 include $(DEPTH)/config/autoconf.mk
 
 DIRS = public
 
 MODULE = toolkitcomps
 LIBRARY_NAME = appstartup_s
 FORCE_STATIC_LIB = 1
@@ -65,15 +66,17 @@ else
 ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 CMMSRCS += nsUserInfoMac.mm
 else
 CPPSRCS += nsUserInfoUnix.cpp
 endif
 endif
 endif
 
+XPCSHELL_TESTS = tests/unit
+
 ifdef ENABLE_TESTS
 ifneq (mobile,$(MOZ_BUILD_APP))
 DIRS += tests/browser
 endif
 endif
 
 include $(topsrcdir)/config/rules.mk
--- a/toolkit/components/startup/nsAppStartup.cpp
+++ b/toolkit/components/startup/nsAppStartup.cpp
@@ -54,27 +54,30 @@
 #include "nsIPrefService.h"
 #include "nsIProfileChangeStatus.h"
 #include "nsIPromptService.h"
 #include "nsIStringBundle.h"
 #include "nsISupportsPrimitives.h"
 #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"
 #include "jsapi.h"
 #include "prenv.h"
 
 #if defined(XP_WIN)
 #include <windows.h>
@@ -94,16 +97,20 @@
 #include <sys/sysctl.h>
 #endif
 
 #include "mozilla/Telemetry.h"
 #include "mozilla/StartupTimeline.h"
 
 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"
 /**
  * Events sent to the system for profiling purposes
  */
 //Keep them syncronized with the .mof file
 
 //Process-wide GUID, used by the OS to differentiate sources
@@ -158,17 +165,19 @@ public:
 //
 
 nsAppStartup::nsAppStartup() :
   mConsiderQuitStopper(0),
   mRunning(false),
   mShuttingDown(false),
   mAttemptingQuit(false),
   mRestart(false),
-  mInterrupted(false)
+  mInterrupted(false),
+  mIsSafeModeNecessary(false),
+  mStartupCrashTrackingEnded(false)
 { }
 
 
 nsresult
 nsAppStartup::Init()
 {
   NS_TIME_FUNCTION;
   nsresult rv;
@@ -804,8 +813,162 @@ nsAppStartup::GetStartupInfo(JSContext* 
         JSObject *date = JS_NewDateObjectMsec(aCx, StartupTimeline::Get(ev) / PR_USEC_PER_MSEC);
         JS_DefineProperty(aCx, obj, StartupTimeline::Describe(ev), OBJECT_TO_JSVAL(date), NULL, NULL, JSPROP_ENUMERATE);
       }
     }
   }
 
   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;
+}
--- a/toolkit/components/startup/nsAppStartup.h
+++ b/toolkit/components/startup/nsAppStartup.h
@@ -88,16 +88,18 @@ private:
   nsCOMPtr<nsIAppShell> mAppShell;
 
   PRInt32      mConsiderQuitStopper; // if > 0, Quit(eConsiderQuit) fails
   bool mRunning;        // Have we started the main event loop?
   bool mShuttingDown;   // Quit method reentrancy check
   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
   typedef mozilla::probes::ProbeManager ProbeManager;
   typedef mozilla::probes::Probe        Probe;
   nsRefPtr<ProbeManager> mProbesManager;
   nsRefPtr<Probe> mPlacesInitCompleteProbe;
   nsRefPtr<Probe> mSessionWindowRestoredProbe;
--- a/toolkit/components/startup/public/nsIAppStartup.idl
+++ b/toolkit/components/startup/public/nsIAppStartup.idl
@@ -72,16 +72,55 @@ interface nsIAppStartup : nsISupports
      * closed but we don't want to take this as a signal to quit the
      * app. Bracket the code where the last window could close with
      * these.
      */
     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
      * eRestart flag may be bit-wise combined with one of the "Quit" flags to
      * cause the application to restart after it quits.
      */
 
     /**
      * Attempt to quit if all windows are closed.
--- a/toolkit/components/startup/tests/browser/Makefile.in
+++ b/toolkit/components/startup/tests/browser/Makefile.in
@@ -42,13 +42,14 @@ VPATH		= @srcdir@
 relativesrcdir  = toolkit/components/startup/tests/browser
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_FILES = \
 	browser_bug511456.js \
 	browser_bug537449.js \
+	browser_crash_detection.js \
 	beforeunload.html \
 	$(NULL)
 
 libs:: $(_BROWSER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/toolkit/components/startup/tests/browser/browser_crash_detection.js
@@ -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);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/startup/tests/unit/head_startup.js
@@ -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);
+
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/startup/tests/unit/test_startup_crash.js
@@ -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);
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/components/startup/tests/unit/xpcshell.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+head = head_startup.js
+tail =
+
+[test_startup_crash.js]
--- a/toolkit/content/aboutSupport.js
+++ b/toolkit/content/aboutSupport.js
@@ -79,16 +79,17 @@ const PREFS_WHITELIST = [
   "permissions.default.image",
   "places.",
   "plugin.",
   "plugins.",
   "print.",
   "privacy.",
   "security.",
   "svg.",
+  "toolkit.startup.recent_crashes",
   "webgl."
 ];
 
 // The blacklist, unlike the whitelist, is a list of regular expressions.
 const PREFS_BLACKLIST = [
   /^network[.]proxy[.]/,
   /[.]print_to_filename$/,
 ];
--- a/toolkit/profile/nsIToolkitProfile.idl
+++ b/toolkit/profile/nsIToolkitProfile.idl
@@ -59,16 +59,21 @@ interface nsIProfileLock : nsISupports
      * files or other data files that may not represent critical user data.
      * (e.g., this directory may not be included as part of a backup scheme.)
      *
      * In some cases, this directory may just be the main profile directory.
      */
     readonly attribute nsILocalFile localDirectory;
 
     /**
+     * The timestamp of an existing profile lock at lock time.
+     */
+    readonly attribute PRInt64 replacedLockTime;
+
+    /**
      * Unlock the profile.
      */
     void unlock();
 };
 
 /**
  * A interface representing a profile.
  * @note THIS INTERFACE SHOULD BE IMPLEMENTED BY THE TOOLKIT CODE ONLY! DON'T
--- a/toolkit/profile/nsToolkitProfileService.cpp
+++ b/toolkit/profile/nsToolkitProfileService.cpp
@@ -357,16 +357,23 @@ nsToolkitProfileLock::Unlock()
         mProfile = nsnull;
     }
     mDirectory = nsnull;
     mLocalDirectory = nsnull;
 
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsToolkitProfileLock::GetReplacedLockTime(PRInt64 *aResult)
+{
+    mLock.GetReplacedLockTime(aResult);
+    return NS_OK;
+}
+
 nsToolkitProfileLock::~nsToolkitProfileLock()
 {
     if (mDirectory) {
         Unlock();
     }
 }
 
 nsToolkitProfileService*
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -229,16 +229,18 @@ extern void InstallSignalHandlers(const 
 #define FILE_INVALIDATE_CACHES NS_LITERAL_CSTRING(".purgecaches")
 
 int    gArgc;
 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;
 
 #ifdef MOZ_WIDGET_QT
 static int    gQtOnlyArgc;
 static char **gQtOnlyArgv;
 #endif
 
@@ -823,16 +825,25 @@ nsXULAppInfo::InvalidateCachesOnRestart(
     PR_Close(fd);
     
     if (NS_FAILED(rv))
       return rv;
   }
   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.
 typedef enum 
 {
   VistaTokenElevationTypeDefault = 1,
   VistaTokenElevationTypeFull,
   VistaTokenElevationTypeLimited
@@ -3194,16 +3205,17 @@ XRE_main(int argc, char* argv[], const n
     if (rv == NS_ERROR_LAUNCHED_CHILD_PROCESS ||
         rv == NS_ERROR_ABORT) return 0;
 
     if (NS_FAILED(rv)) {
       // We failed to choose or create profile - notify user and quit
       ProfileMissingDialog(nativeApp);
       return 1;
     }
+    gProfileLock = profileLock;
 
     nsCOMPtr<nsILocalFile> profD;
     rv = profileLock->GetDirectory(getter_AddRefs(profD));
     NS_ENSURE_SUCCESS(rv, 1);
 
     nsCOMPtr<nsILocalFile> profLD;
     rv = profileLock->GetLocalDirectory(getter_AddRefs(profLD));
     NS_ENSURE_SUCCESS(rv, 1);
@@ -3578,16 +3590,17 @@ XRE_main(int argc, char* argv[], const n
         }
 
       }
     }
 
     // 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();
 #endif
 
     // Restart the app after XPCOM has been shut down cleanly. 
     if (appInitiatedRestart) {
       RestoreStateForAppInitiatedRestart();
--- a/toolkit/xre/nsXREDirProvider.cpp
+++ b/toolkit/xre/nsXREDirProvider.cpp
@@ -35,21 +35,23 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** 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"
 #include "nsIObserverService.h"
 #include "nsIProfileChangeStatus.h"
 #include "nsISimpleEnumerator.h"
 #include "nsIToolkitChromeRegistry.h"
 
@@ -728,16 +730,36 @@ nsXREDirProvider::DoStartup()
 {
   if (!mProfileNotified) {
     nsCOMPtr<nsIObserverService> obsSvc =
       mozilla::services::GetObserverService();
     if (!obsSvc) return NS_ERROR_FAILURE;
 
     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
     nsCOMPtr<nsIObserver> em = do_GetService("@mozilla.org/addons/integration;1");
     if (em) {
       em->Observe(nsnull, "addons-startup", nsnull);
     } else {
       NS_WARNING("Failed to create Addons Manager.");
@@ -748,16 +770,21 @@ nsXREDirProvider::DoStartup()
     obsSvc->NotifyObservers(nsnull, "load-extension-defaults", nsnull);
     obsSvc->NotifyObservers(nsnull, "profile-after-change", kStartup);
 
     // Any component that has registered for the profile-after-change category
     // should also be created at this time.
     (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;
 }
 
 class ProfileChangeStatusImpl : public nsIProfileChangeStatus
 {
 public:
--- a/xpcom/system/nsIXULRuntime.idl
+++ b/xpcom/system/nsIXULRuntime.idl
@@ -110,9 +110,16 @@ interface nsIXULRuntime : nsISupports
   /**
    * Starts a child process. This method is intented to pre-start a
    * content child process so that when it is actually needed, it is
    * ready to go.
    *
    * @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;
 };