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 86413 bcdc5493e6a118a9829359640216508f3127ec84
parent 86412 21cc56ed5fe6c2b8eac9fd030c96b41c5370084c
child 86414 a7f28072d882d4c0ea8b60a3dbdb47c0377b3739
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbsmedberg, Mossop
bugs294260
milestone13.0a1
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;
 };