Bug 1455707: Detect when running an older version than previously ran with the selected profile. r=froydnj, r=mconley, r=Gijs
authorDave Townsend <dtownsend@oxymoronical.com>
Wed, 30 Jan 2019 14:56:30 -0800
changeset 456135 250089b693374edda20126b5ae7f6e7dd282c817
parent 456134 a5ab8fa035ce41c1967caeb4f37aef1fc047f252
child 456136 86b7743d7a65558916f5bcdeded055c3ea4a32ec
push id111592
push userdtownsend@mozilla.com
push dateThu, 31 Jan 2019 03:35:12 +0000
treeherdermozilla-inbound@86b7743d7a65 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj, mconley, Gijs
bugs1455707, 1523725
milestone67.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 1455707: Detect when running an older version than previously ran with the selected profile. r=froydnj, r=mconley, r=Gijs Use the information in compatibility.ini to detect that the current running application is an older version than previously ran with the profile and in that case open a UI allowing the user to launch the profile manager, launch the previous instance of the application or quit. Also includes the patch from bug 1523725.
browser/themes/shared/information.svg
browser/themes/shared/jar.inc.mn
browser/themes/shared/newInstall.css
toolkit/locales/en-US/chrome/mozapps/profile/profileDowngrade.dtd
toolkit/locales/jar.mn
toolkit/profile/content/profileDowngrade.js
toolkit/profile/content/profileDowngrade.xul
toolkit/profile/jar.mn
toolkit/profile/nsIToolkitProfileService.idl
toolkit/profile/nsToolkitProfileService.cpp
toolkit/themes/shared/mozapps.inc.mn
toolkit/themes/shared/profile/information.svg
toolkit/themes/shared/profile/profileDowngrade.css
toolkit/xre/nsAppRunner.cpp
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -51,17 +51,16 @@
   skin/classic/browser/fullscreen/insecure.svg                 (../shared/fullscreen/insecure.svg)
   skin/classic/browser/fullscreen/secure.svg                   (../shared/fullscreen/secure.svg)
   skin/classic/browser/connection-secure.svg                   (../shared/identity-block/connection-secure.svg)
   skin/classic/browser/connection-mixed-passive-loaded.svg     (../shared/identity-block/connection-mixed-passive-loaded.svg)
   skin/classic/browser/connection-mixed-active-loaded.svg      (../shared/identity-block/connection-mixed-active-loaded.svg)
   skin/classic/browser/identity-icon.svg                       (../shared/identity-block/identity-icon.svg)
   skin/classic/browser/identity-icon-notice.svg                (../shared/identity-block/identity-icon-notice.svg)
   skin/classic/browser/info.svg                                (../shared/info.svg)
-  skin/classic/browser/information.svg                         (../shared/information.svg)
   skin/classic/browser/newInstall.css                          (../shared/newInstall.css)
   skin/classic/browser/newInstallPage.css                      (../shared/newInstallPage.css)
 
   skin/classic/browser/illustrations/error-session-restore.svg (../shared/illustrations/error-session-restore.svg)
 
   skin/classic/browser/notification-icons/autoplay-media.svg                (../shared/notification-icons/autoplay-media.svg)
   skin/classic/browser/notification-icons/autoplay-media-detailed.svg       (../shared/notification-icons/autoplay-media-detailed.svg)
   skin/classic/browser/notification-icons/autoplay-media-blocked.svg        (../shared/notification-icons/autoplay-media-blocked.svg)
--- a/browser/themes/shared/newInstall.css
+++ b/browser/themes/shared/newInstall.css
@@ -5,17 +5,17 @@
 window {
   padding: 20px;
 }
 
 #alert {
   width: 32px;
   height: 32px;
   margin-inline-end: 8px;
-  list-style-image: url("chrome://browser/skin/information.svg");
+  list-style-image: url("chrome://mozapps/skin/profile/information.svg");
 }
 
 description {
   margin: 0 0 20px 0;
   padding: 0;
 }
 
 .main-text {
new file mode 100644
--- /dev/null
+++ b/toolkit/locales/en-US/chrome/mozapps/profile/profileDowngrade.dtd
@@ -0,0 +1,20 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!-- LOCALIZATION NOTE:
+ This UI can be most easily shown by modifying the version in compatibility.ini
+ to a newer version and then starting Firefox.
+ For this feature, "installation" is used to mean "this discrete download of
+ Firefox" and "version" is used to mean "the specific revision number of a
+ given Firefox channel". These terms are not synonymous.
+-->
+<!ENTITY window.title "You’ve launched an older version of Firefox">
+<!ENTITY window.style "width: 490px;">
+
+<!ENTITY window.nosync "Using an older version of Firefox can corrupt bookmarks and browsing history already saved to an existing Firefox profile. To protect your information, create a new profile for this installation of &brandShortName;.">
+<!ENTITY window.sync "Using an older version of Firefox can corrupt bookmarks and browsing history already saved to an existing Firefox profile. To protect your information, create a new profile for this installation of &brandShortName;. You can always sign in with a &syncBrand.fxAccount.label; to sync your bookmarks and browsing history between profiles.">
+
+<!ENTITY window.create "Create New Profile">
+<!ENTITY window.quit-win "Exit">
+<!ENTITY window.quit-nonwin "Quit">
--- a/toolkit/locales/jar.mn
+++ b/toolkit/locales/jar.mn
@@ -78,16 +78,19 @@
   locale/@AB_CD@/mozapps/extensions/extensions.properties         (%chrome/mozapps/extensions/extensions.properties)
   locale/@AB_CD@/mozapps/extensions/blocklist.dtd                 (%chrome/mozapps/extensions/blocklist.dtd)
 #endif
   locale/@AB_CD@/mozapps/handling/handling.dtd                    (%chrome/mozapps/handling/handling.dtd)
   locale/@AB_CD@/mozapps/handling/handling.properties             (%chrome/mozapps/handling/handling.properties)
   locale/@AB_CD@/mozapps/profile/createProfileWizard.dtd          (%chrome/mozapps/profile/createProfileWizard.dtd)
   locale/@AB_CD@/mozapps/profile/profileSelection.properties      (%chrome/mozapps/profile/profileSelection.properties)
   locale/@AB_CD@/mozapps/profile/profileSelection.dtd             (%chrome/mozapps/profile/profileSelection.dtd)
+#ifdef MOZ_BLOCK_PROFILE_DOWNGRADE
+  locale/@AB_CD@/mozapps/profile/profileDowngrade.dtd             (%chrome/mozapps/profile/profileDowngrade.dtd)
+#endif
 #ifndef MOZ_FENNEC
   locale/@AB_CD@/mozapps/update/updates.dtd                       (%chrome/mozapps/update/updates.dtd)
   locale/@AB_CD@/mozapps/update/updates.properties                (%chrome/mozapps/update/updates.properties)
 #endif
 % locale pluginproblem @AB_CD@ %locale/@AB_CD@/pluginproblem/
   locale/@AB_CD@/pluginproblem/pluginproblem.dtd                  (%chrome/pluginproblem/pluginproblem.dtd)
 % locale alerts @AB_CD@ %locale/@AB_CD@/alerts/
   locale/@AB_CD@/alerts/alert.dtd                                (%chrome/alerts/alert.dtd)
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/content/profileDowngrade.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let gParams;
+
+function init() {
+  /*
+   * The C++ code passes a dialog param block using its integers as in and out
+   * arguments for this UI. The following are the uses of the integers:
+   *
+   *  0: A set of flags from nsIToolkitProfileService.downgradeUIFlags.
+   *  1: A return argument, one of nsIToolkitProfileService.downgradeUIChoice.
+   */
+  gParams = window.arguments[0].QueryInterface(Ci.nsIDialogParamBlock);
+  let hasSync = gParams.GetInt(0) & Ci.nsIToolkitProfileService.hasSync;
+
+  document.getElementById("sync").hidden = !hasSync;
+  document.getElementById("nosync").hidden = hasSync;
+}
+
+function quit() {
+  gParams.SetInt(1, Ci.nsIToolkitProfileService.quit);
+}
+
+function createProfile() {
+  gParams.SetInt(1, Ci.nsIToolkitProfileService.createNewProfile);
+  window.close();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/content/profileDowngrade.xul
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/profile/profileDowngrade.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+%syncBrandDTD;
+<!ENTITY % profileDTD SYSTEM "chrome://mozapps/locale/profile/profileDowngrade.dtd">
+%profileDTD;
+]>
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        title="&window.title;" onload="init()" style="&window.style;"
+        buttonlabelextra1="&window.create;" ondialogextra1="createProfile()"
+#ifdef XP_WIN
+        buttonlabelaccept="&window.quit-win;"
+#else
+        buttonlabelaccept="&window.quit-nonwin;"
+#endif
+        ondialogaccept="quit()" ondialogcancel="quit()"
+        buttons="accept,extra1" buttonpack="end">
+
+  <script type="application/javascript" src="profileDowngrade.js"/>
+
+  <hbox flex="1" align="start">
+    <image id="info" role="presentation"/>
+    <vbox flex="1">
+      <description id="nosync">&window.nosync;</description>
+      <description id="sync">&window.sync;</description>
+    </vbox>
+  </hbox>
+
+</dialog>
--- a/toolkit/profile/jar.mn
+++ b/toolkit/profile/jar.mn
@@ -2,8 +2,12 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 toolkit.jar:
   content/mozapps/profile/createProfileWizard.js  (content/createProfileWizard.js)
 * content/mozapps/profile/createProfileWizard.xul (content/createProfileWizard.xul)
   content/mozapps/profile/profileSelection.js     (content/profileSelection.js)
   content/mozapps/profile/profileSelection.xul    (content/profileSelection.xul)
+#ifdef MOZ_BLOCK_PROFILE_DOWNGRADE
+  content/mozapps/profile/profileDowngrade.js     (content/profileDowngrade.js)
+* content/mozapps/profile/profileDowngrade.xul    (content/profileDowngrade.xul)
+#endif
--- a/toolkit/profile/nsIToolkitProfileService.idl
+++ b/toolkit/profile/nsIToolkitProfileService.idl
@@ -8,16 +8,33 @@
 interface nsISimpleEnumerator;
 interface nsIFile;
 interface nsIToolkitProfile;
 interface nsIProfileLock;
 
 [scriptable, builtinclass, uuid(1947899b-f369-48fa-89da-f7c37bb1e6bc)]
 interface nsIToolkitProfileService : nsISupports
 {
+    /**
+     * When a downgrade is detected UI is presented to the user to ask how to
+     * proceed. These flags are used to pass some information to the UI.
+     */
+    cenum downgradeUIFlags: 8 {
+        hasSync = 1,
+    };
+
+    /**
+     * When a downgrade is detected UI is presented to the user to ask how to
+     * proceed. These are the possible options the user can choose.
+     */
+    cenum downgradeUIChoice: 8 {
+        quit = 0,
+        createNewProfile = 1,
+    };
+
     attribute boolean startWithLastProfile;
 
     readonly attribute nsISimpleEnumerator /*nsIToolkitProfile*/ profiles;
 
     /**
      * The profile currently in use if it is a named profile. This will return
      * null if the current profile path doesn't match a profile in the database.
      */
@@ -69,27 +86,47 @@ interface nsIToolkitProfileService : nsI
      */
     nsIToolkitProfile getProfileByName(in AUTF8String aName);
 
     /**
      * Create a new profile.
      *
      * The profile temporary directory will be chosen based on where the
      * profile directory is located.
-     * 
+     *
+     * If a profile with the given name already exists it will be returned
+     * instead of creating a new profile.
+     *
      * @param aRootDir
      *        The profile directory. May be null, in which case a suitable
      *        default will be chosen based on the profile name.
      * @param aName
      *        The profile name.
      */
     nsIToolkitProfile createProfile(in nsIFile aRootDir,
                                     in AUTF8String aName);
 
     /**
+     * Create a new profile with a unique name.
+     *
+     * As above however the name given will be altered to make it a unique
+     * profile name.
+     *
+     * @param aRootDir
+     *        The profile directory. May be null, in which case a suitable
+     *        default will be chosen based on the profile name.
+     * @param aNamePrefix
+     *        The prefix to use for the profile name. If unused this will be
+     *        used as the profile name otherwise additional characters will be
+     *        added to make the name unique.
+     */
+    nsIToolkitProfile createUniqueProfile(in nsIFile aRootDir,
+                                          in AUTF8String aNamePrefix);
+
+    /**
      * Returns the number of profiles.
      * @return the number of profiles.
      */
     readonly attribute unsigned long profileCount;
 
     /**
      * Flush the profiles list file.
      */
--- a/toolkit/profile/nsToolkitProfileService.cpp
+++ b/toolkit/profile/nsToolkitProfileService.cpp
@@ -1061,17 +1061,17 @@ nsresult nsToolkitProfileService::Select
     if (mUseDedicatedProfile) {
       name.AssignLiteral("default-" NS_STRINGIFY(MOZ_UPDATE_CHANNEL));
     } else if (mUseDevEditionProfile) {
       name.AssignLiteral(DEV_EDITION_NAME);
     } else {
       name.AssignLiteral(DEFAULT_NAME);
     }
 
-    rv = CreateProfile(nullptr, name, getter_AddRefs(mCurrent));
+    rv = CreateUniqueProfile(nullptr, name, getter_AddRefs(mCurrent));
     if (NS_SUCCEEDED(rv)) {
       if (mUseDedicatedProfile) {
         SetDefaultProfile(mCurrent);
       } else if (mUseDevEditionProfile) {
         mDevEditionDefault = mCurrent;
       } else {
         mNormalDefault = mCurrent;
       }
@@ -1257,16 +1257,38 @@ static void SaltProfileName(nsACString& 
   char salt[9];
   NS_MakeRandomString(salt, 8);
   salt[8] = '.';
 
   aName.Insert(salt, 0, 9);
 }
 
 NS_IMETHODIMP
+nsToolkitProfileService::CreateUniqueProfile(nsIFile* aRootDir,
+                                             const nsACString& aNamePrefix,
+                                             nsIToolkitProfile** aResult) {
+  nsCOMPtr<nsIToolkitProfile> profile;
+  nsresult rv = GetProfileByName(aNamePrefix, getter_AddRefs(profile));
+  if (NS_FAILED(rv)) {
+    return CreateProfile(aRootDir, aNamePrefix, aResult);
+  }
+
+  uint32_t suffix = 1;
+  while (true) {
+    nsPrintfCString name("%s-%d", PromiseFlatCString(aNamePrefix).get(),
+                         suffix);
+    rv = GetProfileByName(name, getter_AddRefs(profile));
+    if (NS_FAILED(rv)) {
+      return CreateProfile(aRootDir, name, aResult);
+    }
+    suffix++;
+  }
+}
+
+NS_IMETHODIMP
 nsToolkitProfileService::CreateProfile(nsIFile* aRootDir,
                                        const nsACString& aName,
                                        nsIToolkitProfile** aResult) {
   nsresult rv = GetProfileByName(aName, aResult);
   if (NS_SUCCEEDED(rv)) {
     return rv;
   }
 
--- a/toolkit/themes/shared/mozapps.inc.mn
+++ b/toolkit/themes/shared/mozapps.inc.mn
@@ -23,16 +23,18 @@
   skin/classic/mozapps/extensions/alerticon-info-positive.svg (../../shared/extensions/alerticon-info-positive.svg)
   skin/classic/mozapps/extensions/alerticon-info-negative.svg (../../shared/extensions/alerticon-info-negative.svg)
   skin/classic/mozapps/extensions/category-legacy.svg        (../../shared/extensions/category-legacy.svg)
   skin/classic/mozapps/aboutNetworking.css                   (../../shared/aboutNetworking.css)
 #ifndef ANDROID
   skin/classic/mozapps/aboutProfiles.css                     (../../shared/aboutProfiles.css)
 #endif
   skin/classic/mozapps/aboutServiceWorkers.css               (../../shared/aboutServiceWorkers.css)
+  skin/classic/mozapps/profile/profileDowngrade.css          (../../shared/profile/profileDowngrade.css)
+  skin/classic/mozapps/profile/information.svg               (../../shared/profile/information.svg)
 
 % override chrome://mozapps/skin/extensions/category-plugins.svg          chrome://global/skin/plugins/pluginGeneric.svg
 % override chrome://mozapps/skin/extensions/category-extensions.svg       chrome://mozapps/skin/extensions/extensionGeneric.svg
 % override chrome://mozapps/skin/extensions/category-languages.svg        chrome://mozapps/skin/extensions/localeGeneric.svg
 % override chrome://mozapps/skin/extensions/category-themes.svg           chrome://mozapps/skin/extensions/themeGeneric.svg
 % override chrome://mozapps/skin/extensions/category-dictionaries.svg chrome://mozapps/skin/extensions/dictionaryGeneric.svg
 % override chrome://mozapps/skin/extensions/localeGeneric.svg         chrome://mozapps/skin/extensions/dictionaryGeneric.svg
 % override chrome://mozapps/skin/extensions/category-experiments.svg  chrome://mozapps/skin/extensions/experimentGeneric.svg
rename from browser/themes/shared/information.svg
rename to toolkit/themes/shared/profile/information.svg
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/shared/profile/profileDowngrade.css
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+spacer[anonid="spacer"] {
+  display: none;
+}
+
+#info {
+  list-style-image: url("chrome://mozapps/skin/profile/information.svg");
+  width: 32px;
+  height: 32px;
+}
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -1489,16 +1489,19 @@ static void DumpHelp() {
       "  --migration        Start with migration wizard.\n"
       "  --ProfileManager   Start with ProfileManager.\n"
       "  --no-remote        Do not accept or send remote commands; implies\n"
       "                     --new-instance.\n"
       "  --new-instance     Open new instance, not a new window in running "
       "instance.\n"
       "  --UILocale <locale> Start with <locale> resources as UI Locale.\n"
       "  --safe-mode        Disables extensions and themes for this session.\n"
+#ifdef MOZ_BLOCK_PROFILE_DOWNGRADE
+      "  --allow-downgrade  Allows downgrading a profile.\n"
+#endif
       "  -MOZ_LOG=<modules> Treated as MOZ_LOG=<modules> environment variable, "
       "overrides it.\n"
       "  -MOZ_LOG_FILE=<file> Treated as MOZ_LOG_FILE=<file> environment "
       "variable, overrides it.\n"
       "                     If MOZ_LOG_FILE is not specified as an argument or "
       "as an environment variable,\n"
       "                     logging will be written to stdout.\n",
       (const char*)gAppData->name);
@@ -2225,40 +2228,219 @@ static nsresult SelectProfile(nsIProfile
     }
     PR_Sleep(kLockRetrySleepMS);
   } while (TimeStamp::Now() - start <
            TimeDuration::FromSeconds(kLockRetrySeconds));
 
   return ProfileLockedDialog(rootDir, localDir, unlocker, aNative, aResult);
 }
 
+#ifdef MOZ_BLOCK_PROFILE_DOWNGRADE
+static const char kProfileDowngradeURL[] =
+    "chrome://mozapps/content/profile/profileDowngrade.xul";
+
+static ReturnAbortOnError CheckDowngrade(
+    nsIFile* aProfileDir, nsIFile* aProfileLocalDir, nsACString& aProfileName,
+    nsINativeAppSupport* aNative, nsIToolkitProfileService* aProfileSvc) {
+  int32_t result = 0;
+  nsresult rv;
+
+  {
+    if (gfxPlatform::IsHeadless()) {
+      // TODO: make a way to turn off all dialogs when headless.
+      Output(true,
+             "This profile was last used with a newer version of this "
+             "application. Please create a new profile.\n");
+      return NS_ERROR_ABORT;
+    }
+
+    ScopedXPCOMStartup xpcom;
+    rv = xpcom.Initialize();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = xpcom.SetWindowCreator(aNative);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    {  // extra scoping is needed so we release these components before xpcom
+       // shutdown
+      bool hasSync = false;
+      nsCOMPtr<nsIPrefService> prefSvc =
+          do_GetService("@mozilla.org/preferences-service;1");
+      NS_ENSURE_TRUE(prefSvc, rv);
+
+      nsCOMPtr<nsIFile> prefsFile;
+      rv = aProfileDir->Clone(getter_AddRefs(prefsFile));
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      rv = prefsFile->Append(NS_LITERAL_STRING("prefs.js"));
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      rv = prefSvc->ReadUserPrefsFromFile(prefsFile);
+      if (NS_SUCCEEDED(rv)) {
+        nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(prefSvc);
+
+        rv = prefBranch->PrefHasUserValue("services.sync.username", &hasSync);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      nsCOMPtr<nsIWindowWatcher> windowWatcher =
+          do_GetService(NS_WINDOWWATCHER_CONTRACTID);
+      NS_ENSURE_TRUE(windowWatcher, NS_ERROR_ABORT);
+
+      nsCOMPtr<nsIAppStartup> appStartup(components::AppStartup::Service());
+      NS_ENSURE_TRUE(appStartup, NS_ERROR_FAILURE);
+
+      nsCOMPtr<nsIDialogParamBlock> paramBlock =
+          do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID);
+      NS_ENSURE_TRUE(paramBlock, NS_ERROR_ABORT);
+
+      uint8_t flags = 0;
+      if (hasSync) {
+        flags |= nsIToolkitProfileService::hasSync;
+      }
+
+      paramBlock->SetInt(0, flags);
+
+      nsCOMPtr<mozIDOMWindowProxy> newWindow;
+      rv = windowWatcher->OpenWindow(nullptr, kProfileDowngradeURL, "_blank",
+                                     "centerscreen,chrome,modal,titlebar",
+                                     paramBlock, getter_AddRefs(newWindow));
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      paramBlock->GetInt(1, &result);
+    }
+  }
+
+  if (result == nsIToolkitProfileService::createNewProfile) {
+    // Create a new profile and start it.
+    nsCString profileName;
+    profileName.AssignLiteral("default");
+#  ifdef MOZ_DEDICATED_PROFILES
+    profileName.Append("-" NS_STRINGIFY(MOZ_UPDATE_CHANNEL));
+#  endif
+    nsCOMPtr<nsIToolkitProfile> newProfile;
+    rv = aProfileSvc->CreateUniqueProfile(nullptr, profileName,
+                                          getter_AddRefs(newProfile));
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = aProfileSvc->SetDefaultProfile(newProfile);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = aProfileSvc->Flush();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIFile> profD, profLD;
+    rv = newProfile->GetRootDir(getter_AddRefs(profD));
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = newProfile->GetLocalDir(getter_AddRefs(profLD));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    SaveFileToEnv("XRE_PROFILE_PATH", profD);
+    SaveFileToEnv("XRE_PROFILE_LOCAL_PATH", profLD);
+
+    return LaunchChild(aNative);
+  }
+
+  // Cancel
+  return NS_ERROR_ABORT;
+}
+#endif
+
+/**
+ * The version string used in compatibility.ini is in the form
+ * <appversion>_<appbuildid>/<aplatformbuildidid>. The build IDs are in the form
+ * <year><month><day><hour><minute><second>. We need to be able to turn this
+ * into something that can be compared by the version comparator. Build IDs are
+ * numeric so normally we could just use those as part of the version but they
+ * are larger than 32-bit integers so we must split them into two parts.
+ * So we generate a version string of the format:
+ * <appversion>.<appbuilddate>.<appbuildtime>.<platformbuilddate>.<platformbuildtime>
+ * This doesn't compare correctly so
+ * we have to make the build ids separate version parts instead. We also have
+ * to split up the build ids as they are too large for the version comparator's
+ * brain.
+ */
+
+// Build ID dates are 8 digits, build ID times are 6 digits.
+#define BUILDID_DATE_LENGTH 8
+#define BUILDID_TIME_LENGTH 6
+#define BUILDID_LENGTH BUILDID_DATE_LENGTH + BUILDID_TIME_LENGTH
+
+static void GetBuildIDVersions(const nsACString& aFullVersion, int32_t aPos,
+                               nsACString& aBuildVersions) {
+  // Extract the date part then the time part.
+  aBuildVersions.Assign(Substring(aFullVersion, aPos, BUILDID_DATE_LENGTH) +
+      NS_LITERAL_CSTRING(".") +
+      Substring(aFullVersion, aPos + BUILDID_DATE_LENGTH, BUILDID_TIME_LENGTH));
+}
+
+static Version GetComparableVersion(const nsCString& aVersionStr) {
+  // We'll find the position of the '_' and '/' characters. The length from the
+  // '_' character to the '/' character will be the length of a build ID plus
+  // one for the '_' character. Similarly the length from the '/' character to
+  // the end of the string will be the same.
+  const uint32_t kExpectedLength = BUILDID_LENGTH + 1;
+
+  int32_t underscorePos = aVersionStr.FindChar('_');
+  int32_t slashPos = aVersionStr.FindChar('/');
+  if (underscorePos == kNotFound || slashPos == kNotFound ||
+      (slashPos - underscorePos) != kExpectedLength ||
+      (aVersionStr.Length() - slashPos) != kExpectedLength) {
+    NS_WARNING("compatibility.ini Version string does not match the expected format.");
+    return Version(aVersionStr.get());
+  }
+
+  nsCString appBuild, platBuild;
+  NS_NAMED_LITERAL_CSTRING(dot, ".");
+
+  const nsACString& version = Substring(aVersionStr, 0, underscorePos);
+
+  GetBuildIDVersions(aVersionStr, underscorePos + 1, /* outparam */ appBuild);
+  GetBuildIDVersions(aVersionStr, slashPos + 1, /* outparam */ platBuild);
+
+  const nsACString& fullVersion = version + dot + appBuild + dot + platBuild;
+
+  return Version(PromiseFlatCString(fullVersion).get());
+}
+
 /**
  * Checks the compatibility.ini file to see if we have updated our application
  * or otherwise invalidated our caches. If the application has been updated,
  * we return false; otherwise, we return true. We also write the status
  * of the caches (valid/invalid) into the return param aCachesOK. The aCachesOK
- * is always invalid if the application has been updated.
+ * is always invalid if the application has been updated. aIsDowngrade is set to
+ * true if the current application is older than that previously used by the
+ * profile.
  */
 static bool CheckCompatibility(nsIFile* aProfileDir, const nsCString& aVersion,
                                const nsCString& aOSABI, nsIFile* aXULRunnerDir,
                                nsIFile* aAppDir, nsIFile* aFlagFile,
-                               bool* aCachesOK) {
+                               bool* aCachesOK, bool* aIsDowngrade) {
   *aCachesOK = false;
+  *aIsDowngrade = false;
+
   nsCOMPtr<nsIFile> file;
   aProfileDir->Clone(getter_AddRefs(file));
   if (!file) return false;
   file->AppendNative(FILE_COMPATIBILITY_INFO);
 
   nsINIParser parser;
   nsresult rv = parser.Init(file);
   if (NS_FAILED(rv)) return false;
 
   nsAutoCString buf;
   rv = parser.GetString("Compatibility", "LastVersion", buf);
-  if (NS_FAILED(rv) || !aVersion.Equals(buf)) return false;
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  if (!aVersion.Equals(buf)) {
+    Version current = GetComparableVersion(aVersion);
+    Version last = GetComparableVersion(buf);
+    *aIsDowngrade = last > current;
+    return false;
+  }
 
   rv = parser.GetString("Compatibility", "LastOSABI", buf);
   if (NS_FAILED(rv) || !aOSABI.Equals(buf)) return false;
 
   rv = parser.GetString("Compatibility", "LastPlatformDir", buf);
   if (NS_FAILED(rv)) return false;
 
   nsCOMPtr<nsIFile> lf;
@@ -3835,40 +4017,16 @@ int XREMain::XRE_mainStartup(bool* aExit
   gProfileLock = mProfileLock;
 
   rv = mProfileLock->GetDirectory(getter_AddRefs(mProfD));
   NS_ENSURE_SUCCESS(rv, 1);
 
   rv = mProfileLock->GetLocalDirectory(getter_AddRefs(mProfLD));
   NS_ENSURE_SUCCESS(rv, 1);
 
-  rv = mDirProvider.SetProfile(mProfD, mProfLD);
-  NS_ENSURE_SUCCESS(rv, 1);
-
-  //////////////////////// NOW WE HAVE A PROFILE ////////////////////////
-
-  mozilla::Telemetry::SetProfileDir(mProfD);
-
-  if (mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER)
-    MakeOrSetMinidumpPath(mProfD);
-
-  CrashReporter::SetProfileDirectory(mProfD);
-
-#ifdef MOZ_ASAN_REPORTER
-  // In ASan reporter builds, we need to set ASan's log_path as early as
-  // possible, so it dumps its errors into files there instead of using
-  // the default stderr location. Since this is crucial for ASan reporter
-  // to work at all (and we don't want people to use a non-functional
-  // ASan reporter build), all failures while setting log_path are fatal.
-  setASanReporterPath(mProfD);
-
-  // Export to env for child processes
-  SaveFileToEnv("ASAN_REPORTER_PATH", mProfD);
-#endif
-
   nsAutoCString version;
   BuildVersion(version);
 
 #ifdef TARGET_OS_ABI
   NS_NAMED_LITERAL_CSTRING(osABI, TARGET_OS_ABI);
 #else
   // No TARGET_XPCOM_ABI, but at least the OS is known
   NS_NAMED_LITERAL_CSTRING(osABI, OS_TARGET "_UNKNOWN");
@@ -3886,19 +4044,55 @@ int XREMain::XRE_mainStartup(bool* aExit
   if (mAppData->directory) {
     Unused << mAppData->directory->Clone(getter_AddRefs(flagFile));
   }
   if (flagFile) {
     flagFile->AppendNative(FILE_INVALIDATE_CACHES);
   }
 
   bool cachesOK;
-  bool versionOK =
-      CheckCompatibility(mProfD, version, osABI, mDirProvider.GetGREDir(),
-                         mAppData->directory, flagFile, &cachesOK);
+  bool isDowngrade;
+  bool versionOK = CheckCompatibility(
+      mProfD, version, osABI, mDirProvider.GetGREDir(), mAppData->directory,
+      flagFile, &cachesOK, &isDowngrade);
+
+#ifdef MOZ_BLOCK_PROFILE_DOWNGRADE
+  if (isDowngrade && !CheckArg("allow-downgrade")) {
+    rv = CheckDowngrade(mProfD, mProfLD, mProfileName, mNativeApp, mProfileSvc);
+    if (rv == NS_ERROR_LAUNCHED_CHILD_PROCESS || rv == NS_ERROR_ABORT) {
+      *aExitFlag = true;
+      return 0;
+    }
+  }
+#endif
+
+  rv = mDirProvider.SetProfile(mProfD, mProfLD);
+  NS_ENSURE_SUCCESS(rv, 1);
+
+  //////////////////////// NOW WE HAVE A PROFILE ////////////////////////
+
+  mozilla::Telemetry::SetProfileDir(mProfD);
+
+  if (mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER) {
+    MakeOrSetMinidumpPath(mProfD);
+  }
+
+  CrashReporter::SetProfileDirectory(mProfD);
+
+#ifdef MOZ_ASAN_REPORTER
+  // In ASan reporter builds, we need to set ASan's log_path as early as
+  // possible, so it dumps its errors into files there instead of using
+  // the default stderr location. Since this is crucial for ASan reporter
+  // to work at all (and we don't want people to use a non-functional
+  // ASan reporter build), all failures while setting log_path are fatal.
+  setASanReporterPath(mProfD);
+
+  // Export to env for child processes
+  SaveFileToEnv("ASAN_REPORTER_PATH", mProfD);
+#endif
 
   bool lastStartupWasCrash = CheckLastStartupWasCrash().unwrapOr(false);
 
   if (CheckArg("purgecaches") || PR_GetEnv("MOZ_PURGE_CACHES") ||
       lastStartupWasCrash) {
     cachesOK = false;
   }