Bug 1265368 - enable resetting non-default profiles, r?MattN draft
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Thu, 05 May 2016 21:38:49 +0100
changeset 363944 e1a6ce0a05ccd6ef94026c6fdeaf6421d5cffe03
parent 363790 29662e28a9c93ac67ee0b8ddfb65a9f29bbf73f5
child 363945 bfdd391e0254bd3b2b7a2da1ae87f7a439016392
push id17339
push usergijskruitbosch@gmail.com
push dateThu, 05 May 2016 20:43:07 +0000
reviewersMattN
bugs1265368
milestone49.0a1
Bug 1265368 - enable resetting non-default profiles, r?MattN MozReview-Commit-ID: KpW9JgghFkn
browser/components/migration/FirefoxProfileMigrator.js
browser/components/migration/MigrationUtils.jsm
browser/components/migration/content/migration.js
toolkit/modules/ResetProfile.jsm
toolkit/profile/nsIProfileMigrator.idl
toolkit/xre/nsAppRunner.cpp
--- a/browser/components/migration/FirefoxProfileMigrator.js
+++ b/browser/components/migration/FirefoxProfileMigrator.js
@@ -127,17 +127,17 @@ FirefoxProfileMigrator.prototype._getRes
   let bookmarksBackups = getFileResource(types.OTHERDATA,
     [PlacesBackups.profileRelativeFolderPath]);
   let dictionary = getFileResource(types.OTHERDATA, ["persdict.dat"]);
 
   let sessionCheckpoints = this._getFileObject(sourceProfileDir, "sessionCheckpoints.json");
   let sessionFile = this._getFileObject(sourceProfileDir, "sessionstore.js");
   let session;
   if (sessionFile) {
-    session = aProfile ? getFileResource(types.SESSION, ["sessionstore.js"]) : {
+    session = {
       type: types.SESSION,
       migrate: function(aCallback) {
         sessionCheckpoints.copyTo(currentProfileDir, "sessionCheckpoints.json");
         let newSessionFile = currentProfileDir.clone();
         newSessionFile.append("sessionstore.js");
         let migrationPromise = SessionMigration.migrate(sessionFile.path, newSessionFile.path);
         migrationPromise.then(function() {
           let buildID = Services.appinfo.platformBuildID;
@@ -153,17 +153,17 @@ FirefoxProfileMigrator.prototype._getRes
           let newPrefsFile = currentProfileDir.clone();
           newPrefsFile.append("prefs.js");
           Services.prefs.savePrefFile(newPrefsFile);
           aCallback(true);
         }, function() {
           aCallback(false);
         });
       }
-    }
+    };
   }
 
   // Telemetry related migrations.
   let times = {
     name: "times", // name is used only by tests.
     type: types.OTHERDATA,
     migrate: aCallback => {
       let file = this._getFileObject(sourceProfileDir, "times.json");
--- a/browser/components/migration/MigrationUtils.jsm
+++ b/browser/components/migration/MigrationUtils.jsm
@@ -547,16 +547,17 @@ this.MigrationUtils = Object.freeze({
    *        This is passed as-is for the params argument of
    *        nsIWindowWatcher.openWindow. The array elements we expect are, in
    *        order:
    *        - {Number} migration entry point constant (see below)
    *        - {String} source browser identifier
    *        - {nsIBrowserProfileMigrator} actual migrator object
    *        - {Boolean} whether this is a startup migration
    *        - {Boolean} whether to skip the 'source' page
+   *        - {String} an identifier for the profile to use when migrating
    *        NB: If you add new consumers, please add a migration entry point
    *        constant below, and specify at least the first element of the array
    *        (the migration entry point for purposes of telemetry).
    */
   showMigrationWizard:
   function MU_showMigrationWizard(aOpener, aParams) {
     let features = "chrome,dialog,modal,centerscreen,titlebar,resizable=no";
     if (AppConstants.platform == "macosx" && !this.isStartupMigration) {
@@ -629,21 +630,23 @@ this.MigrationUtils = Object.freeze({
    * @param [optional] aMigratorKey
    *        If set, the migration wizard will import from the corresponding
    *        migrator, bypassing the source-selection page.  Otherwise, the
    *        source-selection page will be displayed, either with the default
    *        browser selected, if it could be detected and if there is a
    *        migrator for it, or with the first option selected as a fallback
    *        (The first option is hardcoded to be the most common browser for
    *         the OS we run on.  See migration.xul).
+   * @param [optional] aProfileToMigrate
+   *        If set, the migration wizard will import from the profile indicated.
    * @throws if aMigratorKey is invalid or if it points to a non-existent
    *         source.
    */
   startupMigration:
-  function MU_startupMigrator(aProfileStartup, aMigratorKey) {
+  function MU_startupMigrator(aProfileStartup, aMigratorKey, aProfileToMigrate) {
     if (!aProfileStartup) {
       throw new Error("an profile-startup instance is required for startup-migration");
     }
     gProfileStartup = aProfileStartup;
 
     let skipSourcePage = false, migrator = null, migratorKey = "";
     if (aMigratorKey) {
       migrator = this.getMigrator(aMigratorKey);
@@ -683,17 +686,18 @@ this.MigrationUtils = Object.freeze({
       migrationEntryPoint = this.MIGRATION_ENTRYPOINT_FXREFRESH;
     }
 
     let params = [
       migrationEntryPoint,
       migratorKey,
       migrator,
       aProfileStartup,
-      skipSourcePage
+      skipSourcePage,
+      aProfileToMigrate,
     ];
     this.showMigrationWizard(null, params);
   },
 
   /**
    * Cleans up references to migrators and nsIProfileInstance instances.
    */
   finishMigration: function MU_finishMigration() {
--- a/browser/components/migration/content/migration.js
+++ b/browser/components/migration/content/migration.js
@@ -35,16 +35,20 @@ var MigrationWizard = {
     let entryPointId = args[0] || MigrationUtils.MIGRATION_ENTRYPOINT_UNKNOWN;
     Services.telemetry.getHistogramById("FX_MIGRATION_ENTRY_POINT").add(entryPointId);
 
     if (args.length > 1) {
       this._source = args[1];
       this._migrator = args[2] instanceof kIMig ?  args[2] : null;
       this._autoMigrate = args[3].QueryInterface(kIPStartup);
       this._skipImportSourcePage = args[4];
+      if (this._migrator && args[5]) {
+        let sourceProfiles = this._migrator.sourceProfiles;
+        this._selectedProfile = sourceProfiles.find(profile => profile.id == args[5]);
+      }
 
       if (this._autoMigrate) {
         // Show the "nothing" option in the automigrate case to provide an
         // easily identifiable way to avoid migration and create a new profile.
         document.getElementById("nothing").hidden = false;
       }
     }
 
--- a/toolkit/modules/ResetProfile.jsm
+++ b/toolkit/modules/ResetProfile.jsm
@@ -16,27 +16,32 @@ const MOZ_BUILD_APP = AppConstants.MOZ_B
 
 this.ResetProfile = {
   /**
    * Check if reset is supported for the currently running profile.
    *
    * @return boolean whether reset is supported.
    */
   resetSupported: function() {
+    // Reset is only supported if the self-migrator used for reset exists.
+    let migrator = "@mozilla.org/profile/migrator;1?app=" + MOZ_BUILD_APP +
+                   "&type=" + MOZ_APP_NAME;
+    if (!(migrator in Cc)) {
+      return false;
+    }
+    // We also need to be using a profile the profile manager knows about.
     let profileService = Cc["@mozilla.org/toolkit/profile-service;1"].
                          getService(Ci.nsIToolkitProfileService);
     let currentProfileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
-
-    // Reset is only supported for the default profile if the self-migrator used for reset exists.
-    try {
-      return currentProfileDir.equals(profileService.selectedProfile.rootDir) &&
-        ("@mozilla.org/profile/migrator;1?app=" + MOZ_BUILD_APP + "&type=" + MOZ_APP_NAME in Cc);
-    } catch (e) {
-      // Catch exception when there is no selected profile.
-      Cu.reportError(e);
+    let profileEnumerator = profileService.profiles;
+    while (profileEnumerator.hasMoreElements()) {
+      let profile = profileEnumerator.getNext().QueryInterface(Ci.nsIToolkitProfile);
+      if (profile.rootDir && profile.rootDir.equals(currentProfileDir)) {
+        return true;
+      }
     }
     return false;
   },
 
   /**
    * Ask the user if they wish to restart the application to reset the profile.
    */
   openConfirmationDialog: function(window) {
--- a/toolkit/profile/nsIProfileMigrator.idl
+++ b/toolkit/profile/nsIProfileMigrator.idl
@@ -51,17 +51,19 @@ interface nsIProfileMigrator : nsISuppor
    * profile. To figure out the directory of the "current" profile, use
    * aStartup.directory.
    *
    * If your migrator needs to access services that use the profile (to
    * set profile prefs or bookmarks, for example), use aStartup.doStartup.
    *
    * @param  aStartup nsIProfileStartup object to use during migration.
    * @param  aKey     optional key of a migrator to use to skip source selection.
+   * @param  aProfileName optional name of the profile to use for migration.
    *
    * @note The startup code ignores COM exceptions thrown from this method.
    */
-  void migrate(in nsIProfileStartup aStartup, in ACString aKey);
+  void migrate(in nsIProfileStartup aStartup, in ACString aKey,
+               [optional] in ACString aProfileName);
 };
 
 %{C++
 #define NS_PROFILEMIGRATOR_CONTRACTID "@mozilla.org/toolkit/profile-migrator;1"
 %}
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -2205,48 +2205,26 @@ ShowProfileManager(nsIToolkitProfileServ
   aProfileSvc->GetStartOffline(&offline);
   if (offline) {
     SaveToEnv("XRE_START_OFFLINE=1");
   }
 
   return LaunchChild(aNative);
 }
 
-static nsresult
-GetCurrentProfileIsDefault(nsIToolkitProfileService* aProfileSvc,
-                           nsIFile* aCurrentProfileRoot, bool *aResult)
-{
-  nsresult rv;
-  // Check that the profile to reset is the default since reset and the associated migration are
-  // only supported in that case.
-  nsCOMPtr<nsIToolkitProfile> selectedProfile;
-  nsCOMPtr<nsIFile> selectedProfileRoot;
-  rv = aProfileSvc->GetSelectedProfile(getter_AddRefs(selectedProfile));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = selectedProfile->GetRootDir(getter_AddRefs(selectedProfileRoot));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  bool currentIsSelected;
-  rv = aCurrentProfileRoot->Equals(selectedProfileRoot, &currentIsSelected);
-
-  *aResult = currentIsSelected;
-  return rv;
-}
-
 /**
  * Set the currently running profile as the default/selected one.
  *
  * @param aCurrentProfileRoot The root directory of the current profile.
  * @return an error if aCurrentProfileRoot is not found or the profile could not
  * be set as the default.
  */
 static nsresult
-SetCurrentProfileAsDefault(nsIToolkitProfileService* aProfileSvc,
-                           nsIFile* aCurrentProfileRoot)
+SetCurrentProfileAsSelected(nsIToolkitProfileService* aProfileSvc,
+                            nsIFile* aCurrentProfileRoot)
 {
   NS_ENSURE_ARG_POINTER(aProfileSvc);
 
   nsCOMPtr<nsISimpleEnumerator> profiles;
   nsresult rv = aProfileSvc->GetProfiles(getter_AddRefs(profiles));
   if (NS_FAILED(rv))
     return rv;
 
@@ -2266,16 +2244,17 @@ SetCurrentProfileAsDefault(nsIToolkitPro
     }
     rv = profiles->GetNext(getter_AddRefs(supports));
   }
   return rv;
 }
 
 static bool gDoMigration = false;
 static bool gDoProfileReset = false;
+static nsAutoCString gResetOldProfileName;
 
 // Pick a profile. We need to end up with a profile lock.
 //
 // 1) check for --profile <path>
 // 2) check for -P <name>
 // 3) check for --ProfileManager
 // 4) use the default profile, if there is one
 // 5) if there are *no* profiles, set up profile-migration
@@ -2328,37 +2307,30 @@ SelectProfile(nsIProfileLock* *aResult, 
   if (lf) {
     nsCOMPtr<nsIFile> localDir =
       GetFileFromEnv("XRE_PROFILE_LOCAL_PATH");
     if (!localDir) {
       localDir = lf;
     }
 
     arg = PR_GetEnv("XRE_PROFILE_NAME");
-    if (arg && *arg && aProfileName)
+    if (arg && *arg && aProfileName) {
       aProfileName->Assign(nsDependentCString(arg));
+      if (gDoProfileReset) {
+        gResetOldProfileName.Assign(*aProfileName);
+      }
+    }
 
     // Clear out flags that we handled (or should have handled!) last startup.
     const char *dummy;
     CheckArg("p", false, &dummy);
     CheckArg("profile", false, &dummy);
     CheckArg("profilemanager");
 
     if (gDoProfileReset) {
-      // Check that the profile to reset is the default since reset and migration are only
-      // supported in that case.
-      bool currentIsSelected;
-      rv = GetCurrentProfileIsDefault(aProfileSvc, lf, &currentIsSelected);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      if (!currentIsSelected) {
-        NS_WARNING("Profile reset is only supported for the default profile.");
-        gDoProfileReset = gDoMigration = false;
-      }
-
       // If we're resetting a profile, create a new one and use it to startup.
       nsCOMPtr<nsIToolkitProfile> newProfile;
       rv = CreateResetProfile(aProfileSvc, getter_AddRefs(newProfile));
       if (NS_SUCCEEDED(rv)) {
         rv = newProfile->GetRootDir(getter_AddRefs(lf));
         NS_ENSURE_SUCCESS(rv, rv);
         SaveFileToEnv("XRE_PROFILE_PATH", lf);
 
@@ -2381,17 +2353,17 @@ SelectProfile(nsIProfileLock* *aResult, 
 
   ar = CheckArg("profile", true, &arg);
   if (ar == ARG_BAD) {
     PR_fprintf(PR_STDERR, "Error: argument --profile requires a path\n");
     return NS_ERROR_FAILURE;
   }
   if (ar) {
     if (gDoProfileReset) {
-      NS_WARNING("Profile reset is only supported for the default profile.");
+      NS_WARNING("Profile reset is not supported in conjunction with --profile.");
       gDoProfileReset = false;
     }
 
     nsCOMPtr<nsIFile> lf;
     rv = XRE_GetFileFromPath(arg, getter_AddRefs(lf));
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIProfileUnlocker> unlocker;
@@ -2486,17 +2458,17 @@ SelectProfile(nsIProfileLock* *aResult, 
       PR_fprintf(PR_STDERR, "Error: argument -p is invalid when argument --osint is specified\n");
       return NS_ERROR_FAILURE;
     }
     nsCOMPtr<nsIToolkitProfile> profile;
     rv = aProfileSvc->GetProfileByName(nsDependentCString(arg),
                                       getter_AddRefs(profile));
     if (NS_SUCCEEDED(rv)) {
       if (gDoProfileReset) {
-        NS_WARNING("Profile reset is only supported for the default profile.");
+        NS_WARNING("Profile reset is not supported in conjunction with -p .");
         gDoProfileReset = false;
       }
 
       nsCOMPtr<nsIProfileUnlocker> unlocker;
       rv = profile->Lock(getter_AddRefs(unlocker), aResult);
       if (NS_SUCCEEDED(rv)) {
         if (aProfileName)
           aProfileName->Assign(nsDependentCString(arg));
@@ -2583,20 +2555,28 @@ SelectProfile(nsIProfileLock* *aResult, 
           nsCOMPtr<nsIProfileUnlocker> unlocker;
           rv = profile->Lock(getter_AddRefs(unlocker), &tempProfileLock);
           if (NS_FAILED(rv))
             return ProfileLockedDialog(profile, unlocker, aNative, &tempProfileLock);
         }
 
         nsCOMPtr<nsIToolkitProfile> newProfile;
         rv = CreateResetProfile(aProfileSvc, getter_AddRefs(newProfile));
-        if (NS_SUCCEEDED(rv))
-          profile = newProfile;
-        else
+        if (NS_FAILED(rv)) {
+          NS_WARNING("Failed to create a profile to reset to.");
           gDoProfileReset = false;
+        } else {
+          nsresult gotName = profile->GetName(gResetOldProfileName);
+          if (NS_SUCCEEDED(gotName)) {
+            profile = newProfile;
+          } else {
+            gResetOldProfileName.Truncate(0);
+            gDoProfileReset = false;
+          }
+        }
       }
 
       // If you close Firefox and very quickly reopen it, the old Firefox may
       // still be closing down. Rather than immediately showing the
       // "Firefox is running but is not responding" message, we spend a few
       // seconds retrying first.
 
       static const int kLockRetrySeconds = 5;
@@ -4172,48 +4152,66 @@ XREMain::XRE_mainRun()
         if (buf[0] == '0' || buf[0] == 'f' || buf[0] == 'F') {
           gDoMigration = false;
         }
       }
     }
   }
 
   {
-    nsCOMPtr<nsIToolkitProfile> selectedProfile;
+    nsCOMPtr<nsIToolkitProfile> profileBeingReset;
+    bool profileWasSelected = false;
     if (gDoProfileReset) {
-      // At this point we can be sure that profile reset is happening on the default profile.
-      rv = mProfileSvc->GetSelectedProfile(getter_AddRefs(selectedProfile));
-      if (NS_FAILED(rv)) {
+      if (gResetOldProfileName.IsEmpty()) {
+        NS_WARNING("Not resetting profile as the profile has no name.");
         gDoProfileReset = false;
-        return NS_ERROR_FAILURE;
+      } else {
+        rv = mProfileSvc->GetProfileByName(gResetOldProfileName,
+                                           getter_AddRefs(profileBeingReset));
+        if (NS_FAILED(rv)) {
+          gDoProfileReset = false;
+          return NS_ERROR_FAILURE;
+        }
+
+        nsCOMPtr<nsIToolkitProfile> defaultProfile;
+        // This can fail if there is no default profile.
+        // That shouldn't stop reset from proceeding.
+        nsresult gotSelected = mProfileSvc->GetSelectedProfile(getter_AddRefs(defaultProfile));
+        if (NS_SUCCEEDED(gotSelected)) {
+          profileWasSelected = defaultProfile == profileBeingReset;
+        }
       }
     }
 
     // Profile Migration
     if (mAppData->flags & NS_XRE_ENABLE_PROFILE_MIGRATOR && gDoMigration) {
       gDoMigration = false;
       nsCOMPtr<nsIProfileMigrator> pm(do_CreateInstance(NS_PROFILEMIGRATOR_CONTRACTID));
       if (pm) {
         nsAutoCString aKey;
         if (gDoProfileReset) {
           // Automatically migrate from the current application if we just
           // reset the profile.
           aKey = MOZ_APP_NAME;
         }
-        pm->Migrate(&mDirProvider, aKey);
+        pm->Migrate(&mDirProvider, aKey, gResetOldProfileName);
       }
     }
 
     if (gDoProfileReset) {
-      nsresult backupCreated = ProfileResetCleanup(selectedProfile);
+      nsresult backupCreated = ProfileResetCleanup(profileBeingReset);
       if (NS_FAILED(backupCreated)) NS_WARNING("Could not cleanup the profile that was reset");
 
-      // Set the new profile as the default after we're done cleaning up the old default.
-      rv = SetCurrentProfileAsDefault(mProfileSvc, mProfD);
-      if (NS_FAILED(rv)) NS_WARNING("Could not set current profile as the default");
+      // Set the new profile as the default after we're done cleaning up the old profile,
+      // iff that profile was already the default
+      if (profileWasSelected) {
+        //XXXgijs this is actually "broken" - see bug 1270615
+        rv = SetCurrentProfileAsSelected(mProfileSvc, mProfD);
+        if (NS_FAILED(rv)) NS_WARNING("Could not set current profile as the default");
+      }
     }
   }
 
   mDirProvider.DoStartup();
 
   OverrideDefaultLocaleIfNeeded();
 
 #ifdef MOZ_CRASHREPORTER