Bug 1518587: Move startup profile selection to nsToolkitProfileService. r=froydnj
authorDave Townsend <dtownsend@oxymoronical.com>
Mon, 14 Jan 2019 17:27:34 +0000
changeset 453758 394b490d3e2deb8c31c71214320ebfc16a8db008
parent 453757 1b5c466a1c669657e323b52592edd468015e42e6
child 453759 8844751cb1698c69c2fead027fb31e791bc0ab00
push id35372
push usercbrindusan@mozilla.com
push dateMon, 14 Jan 2019 21:49:33 +0000
treeherdermozilla-central@50b3268954b1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1518587
milestone66.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 1518587: Move startup profile selection to nsToolkitProfileService. r=froydnj Currently nsAppRunner is responsible for choosing or creating a profile to use at startup. It then has to create a reset profile if necessary and lock the selected profile directories. But these latter things are done in different places of the selection code and done in different ways, sometimes we delay while trying to get the lock, sometimes we don't. This patch moves the profile selection part of the code to its own function so that then we only have to have one place that does the profile reset and locking logic. It makes a lot of sense to have the selection code live in the profile service. It can use information from the database load to help make the choices and it also means that we can expose the profile selection code through xpcom allowing it to be easily automatically tested. It will also be more important for future patches for the dedicated profiles feature. Differential Revision: https://phabricator.services.mozilla.com/D16116
browser/components/migration/tests/marionette/test_refresh_firefox.py
toolkit/profile/moz.build
toolkit/profile/nsIToolkitProfileService.idl
toolkit/profile/nsToolkitProfileService.cpp
toolkit/profile/nsToolkitProfileService.h
toolkit/profile/xpcshell/.eslintrc.js
toolkit/profile/xpcshell/head.js
toolkit/profile/xpcshell/test_create_default.js
toolkit/profile/xpcshell/test_profile_reset.js
toolkit/profile/xpcshell/test_select_default.js
toolkit/profile/xpcshell/test_select_environment.js
toolkit/profile/xpcshell/test_select_environment_named.js
toolkit/profile/xpcshell/test_select_missing.js
toolkit/profile/xpcshell/test_select_named.js
toolkit/profile/xpcshell/test_select_noname.js
toolkit/profile/xpcshell/test_select_profilemanager.js
toolkit/profile/xpcshell/xpcshell.ini
toolkit/xre/CmdLineAndEnvUtils.cpp
toolkit/xre/CmdLineAndEnvUtils.h
toolkit/xre/ProfileReset.cpp
toolkit/xre/ProfileReset.h
toolkit/xre/moz.build
toolkit/xre/nsAppRunner.cpp
toolkit/xre/nsAppRunner.h
toolkit/xre/nsXREDirProvider.cpp
xpcom/base/ErrorList.py
--- a/browser/components/migration/tests/marionette/test_refresh_firefox.py
+++ b/browser/components/migration/tests/marionette/test_refresh_firefox.py
@@ -536,17 +536,16 @@ class TestFirefoxRefresh(MarionetteTestC
           prefsToKeep.push("datareporting.policy.dataSubmissionPolicyBypassNotification");
           let prefObj = {};
           for (let pref of prefsToKeep) {
             prefObj[pref] = global.Preferences.get(pref);
           }
           env.set("MOZ_MARIONETTE_PREF_STATE_ACROSS_RESTARTS", JSON.stringify(prefObj));
           env.set("MOZ_RESET_PROFILE_RESTART", "1");
           env.set("XRE_PROFILE_PATH", arguments[0]);
-          env.set("XRE_PROFILE_NAME", profileName);
         """, script_args=(self.marionette.instance.profile.profile, profileName,))
 
         profileLeafName = os.path.basename(os.path.normpath(
             self.marionette.instance.profile.profile))
 
         # Now restart the browser to get it reset:
         self.marionette.restart(clean=False, in_app=True)
         self.setUpScriptData()
--- a/toolkit/profile/moz.build
+++ b/toolkit/profile/moz.build
@@ -1,16 +1,18 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
 
+XPCSHELL_TESTS_MANIFESTS += ['xpcshell/xpcshell.ini']
+
 if CONFIG['ENABLE_TESTS']:
     DIRS += ['gtest']
 
 XPIDL_SOURCES += [
     'nsIProfileMigrator.idl',
     'nsIProfileUnlocker.idl',
     'nsIToolkitProfile.idl',
     'nsIToolkitProfileService.idl',
--- a/toolkit/profile/nsIToolkitProfileService.idl
+++ b/toolkit/profile/nsIToolkitProfileService.idl
@@ -5,17 +5,17 @@
 
 #include "nsISupports.idl"
 
 interface nsISimpleEnumerator;
 interface nsIFile;
 interface nsIToolkitProfile;
 interface nsIProfileLock;
 
-[scriptable, uuid(1947899b-f369-48fa-89da-f7c37bb1e6bc)]
+[scriptable, builtinclass, uuid(1947899b-f369-48fa-89da-f7c37bb1e6bc)]
 interface nsIToolkitProfileService : nsISupports
 {
     attribute boolean startWithLastProfile;
 
     readonly attribute nsISimpleEnumerator /*nsIToolkitProfile*/ profiles;
 
     /**
      * The currently selected profile (the one used or about to be used by the
@@ -33,16 +33,38 @@ interface nsIToolkitProfileService : nsI
      * default profile (which it creates if it doesn't exist), unless a special
      * empty file named "ignore-dev-edition-profile" is present next to
      * profiles.ini. In that case Developer Edition behaves the same as any
      * other build of Firefox.
      */
     attribute nsIToolkitProfile defaultProfile;
 
     /**
+     * Selects or creates a profile to use based on the profiles database, any
+     * environment variables and any command line arguments. Will not create
+     * a profile if aIsResetting is true. The profile is selected based on this
+     * order of preference:
+     * * Environment variables (set when restarting the application).
+     * * --profile command line argument.
+     * * --createprofile command line argument (this also causes the app to exit).
+     * * -p command line argument.
+     * * A new profile created if this is the first run of the application.
+     * * The default profile.
+     * aRootDir and aLocalDir are set to the data and local directories for the
+     * profile data. If a profile from the database was selected it will be
+     * returned in aProfile.
+     * This returns true if a new profile was created.
+     * This method is primarily for testing. It can be called only once.
+     */
+    bool selectStartupProfile(in Array<ACString> aArgv,
+                              in boolean aIsResetting,
+                              out nsIFile aRootDir, out nsIFile aLocalDir,
+                              out nsIToolkitProfile aProfile);
+
+    /**
      * Get a profile by name. This is mainly for use by the -P
      * commandline flag.
      *
      * @param aName The profile name to find.
      */
     nsIToolkitProfile getProfileByName(in AUTF8String aName);
 
     /**
--- a/toolkit/profile/nsToolkitProfileService.cpp
+++ b/toolkit/profile/nsToolkitProfileService.cpp
@@ -5,31 +5,28 @@
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/UniquePtr.h"
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <prprf.h>
 #include <prtime.h>
-#include "nsProfileLock.h"
 
 #ifdef XP_WIN
 #include <windows.h>
 #include <shlobj.h>
 #endif
 #ifdef XP_UNIX
 #include <unistd.h>
 #endif
 
-#include "nsIToolkitProfileService.h"
-#include "nsIToolkitProfile.h"
-#include "nsIFactory.h"
+#include "nsToolkitProfileService.h"
+#include "CmdLineAndEnvUtils.h"
 #include "nsIFile.h"
-#include "nsSimpleEnumerator.h"
 
 #ifdef XP_MACOSX
 #include <CoreFoundation/CoreFoundation.h>
 #include "nsILocalFileMac.h"
 #endif
 
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsNetCID.h"
@@ -43,111 +40,17 @@
 #include "nsString.h"
 #include "nsReadableUtils.h"
 #include "nsNativeCharsetUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Sprintf.h"
 
 using namespace mozilla;
 
-class nsToolkitProfile final : public nsIToolkitProfile {
- public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSITOOLKITPROFILE
-
-  friend class nsToolkitProfileService;
-  RefPtr<nsToolkitProfile> mNext;
-  nsToolkitProfile* mPrev;
-
- private:
-  ~nsToolkitProfile() {}
-
-  nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir,
-                   nsIFile* aLocalDir, nsToolkitProfile* aPrev);
-
-  nsresult RemoveInternal(bool aRemoveFiles, bool aInBackground);
-
-  friend class nsToolkitProfileLock;
-
-  nsCString mName;
-  nsCOMPtr<nsIFile> mRootDir;
-  nsCOMPtr<nsIFile> mLocalDir;
-  nsIProfileLock* mLock;
-};
-
-class nsToolkitProfileLock final : public nsIProfileLock {
- public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIPROFILELOCK
-
-  nsresult Init(nsToolkitProfile* aProfile, nsIProfileUnlocker** aUnlocker);
-  nsresult Init(nsIFile* aDirectory, nsIFile* aLocalDirectory,
-                nsIProfileUnlocker** aUnlocker);
-
-  nsToolkitProfileLock() {}
-
- private:
-  ~nsToolkitProfileLock();
-
-  RefPtr<nsToolkitProfile> mProfile;
-  nsCOMPtr<nsIFile> mDirectory;
-  nsCOMPtr<nsIFile> mLocalDirectory;
-
-  nsProfileLock mLock;
-};
-
-class nsToolkitProfileFactory final : public nsIFactory {
-  ~nsToolkitProfileFactory() {}
-
- public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIFACTORY
-};
-
-class nsToolkitProfileService final : public nsIToolkitProfileService {
- public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSITOOLKITPROFILESERVICE
-
- private:
-  friend class nsToolkitProfile;
-  friend class nsToolkitProfileFactory;
-  friend nsresult NS_NewToolkitProfileService(nsIToolkitProfileService**);
-
-  nsToolkitProfileService() : mStartWithLast(true) { gService = this; }
-  ~nsToolkitProfileService() { gService = nullptr; }
-
-  nsresult Init();
-
-  nsresult CreateTimesInternal(nsIFile* profileDir);
-
-  RefPtr<nsToolkitProfile> mFirst;
-  nsCOMPtr<nsIToolkitProfile> mChosen;
-  nsCOMPtr<nsIToolkitProfile> mDefault;
-  nsCOMPtr<nsIFile> mAppData;
-  nsCOMPtr<nsIFile> mTempData;
-  nsCOMPtr<nsIFile> mListFile;
-  bool mStartWithLast;
-
-  static nsToolkitProfileService* gService;
-
-  class ProfileEnumerator final : public nsSimpleEnumerator {
-   public:
-    NS_DECL_NSISIMPLEENUMERATOR
-
-    const nsID& DefaultInterface() override {
-      return NS_GET_IID(nsIToolkitProfile);
-    }
-
-    explicit ProfileEnumerator(nsToolkitProfile* first) { mCurrent = first; }
-
-   private:
-    RefPtr<nsToolkitProfile> mCurrent;
-  };
-};
+#define DEV_EDITION_NAME "dev-edition-default"
 
 nsToolkitProfile::nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir,
                                    nsIFile* aLocalDir, nsToolkitProfile* aPrev)
     : mPrev(aPrev),
       mName(aName),
       mRootDir(aRootDir),
       mLocalDir(aLocalDir),
       mLock(nullptr) {
@@ -355,16 +258,23 @@ nsToolkitProfileLock::~nsToolkitProfileL
     Unlock();
   }
 }
 
 nsToolkitProfileService* nsToolkitProfileService::gService = nullptr;
 
 NS_IMPL_ISUPPORTS(nsToolkitProfileService, nsIToolkitProfileService)
 
+nsToolkitProfileService::nsToolkitProfileService()
+    : mStartupProfileSelected(false), mStartWithLast(true), mIsFirstRun(true) {
+  gService = this;
+}
+
+nsToolkitProfileService::~nsToolkitProfileService() { gService = nullptr; }
+
 nsresult nsToolkitProfileService::Init() {
   NS_ASSERTION(gDirServiceProvider, "No dirserviceprovider!");
   nsresult rv;
 
   rv = nsXREDirProvider::GetUserAppDataDirectory(getter_AddRefs(mAppData));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = nsXREDirProvider::GetUserLocalDataDirectory(getter_AddRefs(mTempData));
@@ -409,18 +319,20 @@ nsresult nsToolkitProfileService::Init()
       NS_LITERAL_CSTRING("ignore-dev-edition-profile"));
   if (NS_FAILED(rv)) return rv;
 
   bool shouldIgnoreSeparateProfile;
   rv = ignoreSeparateProfile->Exists(&shouldIgnoreSeparateProfile);
   if (NS_FAILED(rv)) return rv;
 #endif
 
+  nsCOMPtr<nsIToolkitProfile> autoSelectProfile;
+
+  unsigned int nonDevEditionProfiles = 0;
   unsigned int c = 0;
-  bool foundAuroraDefault = false;
   for (c = 0; true; ++c) {
     nsAutoCString profileID("Profile");
     profileID.AppendInt(c);
 
     rv = parser.GetString(profileID.get(), "IsRelative", buffer);
     if (NS_FAILED(rv)) break;
 
     bool isRelative = buffer.EqualsLiteral("1");
@@ -463,51 +375,54 @@ nsresult nsToolkitProfileService::Init()
       localDir = rootDir;
     }
 
     currentProfile =
         new nsToolkitProfile(name, rootDir, localDir, currentProfile);
     NS_ENSURE_TRUE(currentProfile, NS_ERROR_OUT_OF_MEMORY);
 
     rv = parser.GetString(profileID.get(), "Default", buffer);
-    if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("1") && !foundAuroraDefault) {
-      mChosen = currentProfile;
-      this->SetDefaultProfile(currentProfile);
+    if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("1")) {
+      mDefault = currentProfile;
     }
+
+    if (name.EqualsLiteral(DEV_EDITION_NAME)) {
 #ifdef MOZ_DEV_EDITION
-    // Use the dev-edition-default profile if this is an Aurora build and
-    // ignore-dev-edition-profile is not present.
-    if (name.EqualsLiteral("dev-edition-default") &&
-        !shouldIgnoreSeparateProfile) {
-      mChosen = currentProfile;
-      foundAuroraDefault = true;
+      // Use the dev-edition-default profile if this is an Aurora build and
+      // ignore-dev-edition-profile is not present.
+      if (!shouldIgnoreSeparateProfile) {
+        mChosen = currentProfile;
+      }
+#endif
+    } else {
+      nonDevEditionProfiles++;
+      autoSelectProfile = currentProfile;
     }
-#endif
   }
 
+  // If there is only one non-dev-edition profile then mark it as the default.
+  if (!mDefault && nonDevEditionProfiles == 1) {
+    mDefault = autoSelectProfile;
+  }
+
+  // Normally having no non-dev-edition builds suggests this is the first run.
+  mIsFirstRun = nonDevEditionProfiles == 0;
+
 #ifdef MOZ_DEV_EDITION
-  if (!foundAuroraDefault && !shouldIgnoreSeparateProfile) {
-    // If a single profile exists, it may not be already marked as default.
-    // Do it now to avoid problems when we create the dev-edition-default
-    // profile.
-    if (!mChosen && mFirst && !mFirst->mNext) this->SetDefaultProfile(mFirst);
-
-    // Create a default profile for aurora, if none was found.
-    nsCOMPtr<nsIToolkitProfile> profile;
-    rv = CreateProfile(nullptr, NS_LITERAL_CSTRING("dev-edition-default"),
-                       getter_AddRefs(profile));
-    if (NS_FAILED(rv)) return rv;
-    mChosen = profile;
-    rv = Flush();
-    if (NS_FAILED(rv)) return rv;
+  if (!shouldIgnoreSeparateProfile) {
+    // Except when using the separate dev-edition profile, in which case not
+    // finding it means this is a first run.
+    mIsFirstRun = !mChosen;
+  } else {
+    mChosen = mDefault;
   }
+#else
+  mChosen = mDefault;
 #endif
 
-  if (!mChosen && mFirst && !mFirst->mNext)  // only one profile
-    mChosen = mFirst;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsToolkitProfileService::SetStartWithLastProfile(bool aValue) {
   if (mStartWithLast != aValue) {
     mStartWithLast = aValue;
   }
@@ -575,31 +490,315 @@ nsToolkitProfileService::GetDefaultProfi
 NS_IMETHODIMP
 nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile) {
   if (mDefault != aProfile) {
     mDefault = aProfile;
   }
   return NS_OK;
 }
 
+/**
+ * An implementation of SelectStartupProfile callable from JavaScript via XPCOM.
+ * See nsIToolkitProfileService.idl.
+ */
+NS_IMETHODIMP
+nsToolkitProfileService::SelectStartupProfile(
+    const nsTArray<nsCString>& aArgv, bool aIsResetting, nsIFile** aRootDir,
+    nsIFile** aLocalDir, nsIToolkitProfile** aProfile, bool* aDidCreate) {
+  int argc = aArgv.Length();
+  // Our command line handling expects argv to be null-terminated so construct
+  // an appropriate array.
+  auto argv = MakeUnique<char*[]>(argc + 1);
+  // Also, our command line handling removes things from the array without
+  // freeing them so keep track of what we've created separately.
+  auto allocated = MakeUnique<UniqueFreePtr<char>[]>(argc);
+
+  for (int i = 0; i < argc; i++) {
+    allocated[i].reset(ToNewCString(aArgv[i]));
+    argv[i] = allocated[i].get();
+  }
+  argv[argc] = nullptr;
+
+  nsresult rv = SelectStartupProfile(&argc, argv.get(), aIsResetting, aRootDir,
+                                     aLocalDir, aProfile, aDidCreate);
+
+  return rv;
+}
+
+/**
+ * Selects or creates a profile to use based on the profiles database, any
+ * environment variables and any command line arguments. Will not create
+ * a profile if aIsResetting is true. The profile is selected based on this
+ * order of preference:
+ * * Environment variables (set when restarting the application).
+ * * --profile command line argument.
+ * * --createprofile command line argument (this also causes the app to exit).
+ * * -p command line argument.
+ * * A new profile created if this is the first run of the application.
+ * * The default profile.
+ * aRootDir and aLocalDir are set to the data and local directories for the
+ * profile data. If a profile from the database was selected it will be
+ * returned in aProfile.
+ * aDidCreate will be set to true if a new profile was created.
+ * This function should be called once at startup and will fail if called again.
+ * aArgv should be an array of aArgc + 1 strings, the last element being null.
+ * Both aArgv and aArgc will be mutated.
+ */
+nsresult nsToolkitProfileService::SelectStartupProfile(
+    int* aArgc, char* aArgv[], bool aIsResetting, nsIFile** aRootDir,
+    nsIFile** aLocalDir, nsIToolkitProfile** aProfile, bool* aDidCreate) {
+  if (mStartupProfileSelected) {
+    return NS_ERROR_ALREADY_INITIALIZED;
+  }
+
+  mStartupProfileSelected = true;
+  *aDidCreate = false;
+
+  nsresult rv;
+  const char* arg;
+  nsCOMPtr<nsIToolkitProfile> profile;
+
+  // Use the profile specified in the environment variables (generally from an
+  // app initiated restart).
+  nsCOMPtr<nsIFile> lf = GetFileFromEnv("XRE_PROFILE_PATH");
+  if (lf) {
+    nsCOMPtr<nsIFile> localDir = GetFileFromEnv("XRE_PROFILE_LOCAL_PATH");
+    if (!localDir) {
+      localDir = lf;
+    }
+
+    // Clear out flags that we handled (or should have handled!) last startup.
+    const char* dummy;
+    CheckArg(*aArgc, aArgv, "p", &dummy);
+    CheckArg(*aArgc, aArgv, "profile", &dummy);
+    CheckArg(*aArgc, aArgv, "profilemanager");
+
+    GetProfileByDir(lf, localDir, aProfile);
+    lf.forget(aRootDir);
+    localDir.forget(aLocalDir);
+    return NS_OK;
+  }
+
+  // Check the -profile command line argument. It accepts a single argument that
+  // gives the path to use for the profile.
+  ArgResult ar = CheckArg(*aArgc, aArgv, "profile", &arg,
+                          CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg);
+  if (ar == ARG_BAD) {
+    PR_fprintf(PR_STDERR, "Error: argument --profile requires a path\n");
+    return NS_ERROR_FAILURE;
+  }
+  if (ar) {
+    nsCOMPtr<nsIFile> lf;
+    rv = XRE_GetFileFromPath(arg, getter_AddRefs(lf));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Make sure that the profile path exists and it's a directory.
+    bool exists;
+    rv = lf->Exists(&exists);
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (!exists) {
+      rv = lf->Create(nsIFile::DIRECTORY_TYPE, 0700);
+      NS_ENSURE_SUCCESS(rv, rv);
+    } else {
+      bool isDir;
+      rv = lf->IsDirectory(&isDir);
+      NS_ENSURE_SUCCESS(rv, rv);
+      if (!isDir) {
+        PR_fprintf(
+            PR_STDERR,
+            "Error: argument --profile requires a path to a directory\n");
+        return NS_ERROR_FAILURE;
+      }
+    }
+
+    // If a profile path is specified directly on the command line, then
+    // assume that the temp directory is the same as the given directory.
+    GetProfileByDir(lf, lf, aProfile);
+    NS_ADDREF(*aRootDir = lf);
+    lf.forget(aLocalDir);
+    return NS_OK;
+  }
+
+  // Check the -createprofile command line argument. It accepts a single
+  // argument that is either the name for the new profile or the name followed
+  // by the path to use.
+  ar = CheckArg(*aArgc, aArgv, "createprofile", &arg,
+                CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg);
+  if (ar == ARG_BAD) {
+    PR_fprintf(PR_STDERR,
+               "Error: argument --createprofile requires a profile name\n");
+    return NS_ERROR_FAILURE;
+  }
+  if (ar) {
+    const char* delim = strchr(arg, ' ');
+    if (delim) {
+      nsCOMPtr<nsIFile> lf;
+      rv = NS_NewNativeLocalFile(nsDependentCString(delim + 1), true,
+                                 getter_AddRefs(lf));
+      if (NS_FAILED(rv)) {
+        PR_fprintf(PR_STDERR, "Error: profile path not valid.\n");
+        return rv;
+      }
+
+      // As with --profile, assume that the given path will be used for the
+      // main profile directory.
+      rv = CreateProfile(lf, nsDependentCSubstring(arg, delim),
+                         getter_AddRefs(profile));
+    } else {
+      rv = CreateProfile(nullptr, nsDependentCString(arg),
+                         getter_AddRefs(profile));
+    }
+    // Some pathological arguments can make it this far
+    if (NS_FAILED(rv)) {
+      PR_fprintf(PR_STDERR, "Error creating profile.\n");
+      return rv;
+    }
+    rv = NS_ERROR_ABORT;
+    Flush();
+
+    return rv;
+  }
+
+  // Check the -p command line argument. It either accepts a profile name and
+  // uses that named profile or without a name it opens the profile manager.
+  ar = CheckArg(*aArgc, aArgv, "p", &arg);
+  if (ar == ARG_BAD) {
+    ar = CheckArg(*aArgc, aArgv, "osint");
+    if (ar == ARG_FOUND) {
+      PR_fprintf(
+          PR_STDERR,
+          "Error: argument -p is invalid when argument --osint is specified\n");
+      return NS_ERROR_FAILURE;
+    }
+
+    return NS_ERROR_SHOW_PROFILE_MANAGER;
+  }
+  if (ar) {
+    ar = CheckArg(*aArgc, aArgv, "osint");
+    if (ar == ARG_FOUND) {
+      PR_fprintf(
+          PR_STDERR,
+          "Error: argument -p is invalid when argument --osint is specified\n");
+      return NS_ERROR_FAILURE;
+    }
+
+    rv = GetProfileByName(nsDependentCString(arg), getter_AddRefs(profile));
+    if (NS_SUCCEEDED(rv)) {
+      profile->GetRootDir(aRootDir);
+      profile->GetLocalDir(aLocalDir);
+      profile.forget(aProfile);
+      return NS_OK;
+    }
+
+    return NS_ERROR_SHOW_PROFILE_MANAGER;
+  }
+
+  ar = CheckArg(*aArgc, aArgv, "profilemanager", (const char**)nullptr,
+                CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg);
+  if (ar == ARG_BAD) {
+    PR_fprintf(PR_STDERR,
+               "Error: argument --profilemanager is invalid when argument "
+               "--osint is specified\n");
+    return NS_ERROR_FAILURE;
+  }
+  if (ar == ARG_FOUND) {
+    return NS_ERROR_SHOW_PROFILE_MANAGER;
+  }
+
+  // If this is a first run then create a new profile.
+  if (mIsFirstRun) {
+    if (aIsResetting) {
+      // We don't want to create a fresh profile when we're attempting a
+      // profile reset so just bail out here, the calling code will handle it.
+      *aProfile = nullptr;
+      return NS_OK;
+    }
+
+    // create a default profile
+    nsresult rv = CreateProfile(nullptr,  // choose a default dir for us
+#ifdef MOZ_DEV_EDITION
+                                NS_LITERAL_CSTRING(DEV_EDITION_NAME),
+#else
+                                NS_LITERAL_CSTRING("default"),
+#endif
+                                getter_AddRefs(mChosen));
+    if (NS_SUCCEEDED(rv)) {
+#ifndef MOZ_DEV_EDITION
+      SetDefaultProfile(mChosen);
+#endif
+      Flush();
+
+      mChosen->GetRootDir(aRootDir);
+      mChosen->GetLocalDir(aLocalDir);
+      NS_ADDREF(*aProfile = mChosen);
+
+      *aDidCreate = true;
+      return NS_OK;
+    }
+  }
+
+  // There are multiple profiles available.
+  if (!mStartWithLast) {
+    return NS_ERROR_SHOW_PROFILE_MANAGER;
+  }
+
+  // GetSelectedProfile will auto-select the only profile if there's just one
+  GetSelectedProfile(getter_AddRefs(profile));
+
+  // None of the profiles was marked as default (generally only happens if the
+  // user modifies profiles.ini manually). Let the user choose.
+  if (!profile) {
+    return NS_ERROR_SHOW_PROFILE_MANAGER;
+  }
+
+  // Use the selected profile.
+  profile->GetRootDir(aRootDir);
+  profile->GetLocalDir(aLocalDir);
+  profile.forget(aProfile);
+
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 nsToolkitProfileService::GetProfileByName(const nsACString& aName,
                                           nsIToolkitProfile** aResult) {
   nsToolkitProfile* curP = mFirst;
   while (curP) {
     if (curP->mName.Equals(aName)) {
       NS_ADDREF(*aResult = curP);
       return NS_OK;
     }
     curP = curP->mNext;
   }
 
   return NS_ERROR_FAILURE;
 }
 
+/**
+ * Finds a profile from the database that uses the given root and local
+ * directories.
+ */
+void nsToolkitProfileService::GetProfileByDir(nsIFile* aRootDir,
+                                              nsIFile* aLocalDir,
+                                              nsIToolkitProfile** aResult) {
+  nsToolkitProfile* curP = mFirst;
+  while (curP) {
+    bool equal;
+    nsresult rv = curP->mRootDir->Equals(aRootDir, &equal);
+    if (NS_SUCCEEDED(rv) && equal) {
+      rv = curP->mLocalDir->Equals(aLocalDir, &equal);
+      if (NS_SUCCEEDED(rv) && equal) {
+        NS_ADDREF(*aResult = curP);
+        return;
+      }
+    }
+    curP = curP->mNext;
+  }
+}
+
 nsresult NS_LockProfilePath(nsIFile* aPath, nsIFile* aTempPath,
                             nsIProfileUnlocker** aUnlocker,
                             nsIProfileLock** aResult) {
   RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
   if (!lock) return NS_ERROR_OUT_OF_MEMORY;
 
   nsresult rv = lock->Init(aPath, aTempPath, aUnlocker);
   if (NS_FAILED(rv)) return rv;
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/nsToolkitProfileService.h
@@ -0,0 +1,121 @@
+
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef nsToolkitProfileService_h
+#define nsToolkitProfileService_h
+
+#include "nsIToolkitProfileService.h"
+#include "nsIToolkitProfile.h"
+#include "nsIFactory.h"
+#include "nsSimpleEnumerator.h"
+#include "nsProfileLock.h"
+
+class nsToolkitProfile final : public nsIToolkitProfile {
+ public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSITOOLKITPROFILE
+
+  friend class nsToolkitProfileService;
+  RefPtr<nsToolkitProfile> mNext;
+  nsToolkitProfile* mPrev;
+
+ private:
+  ~nsToolkitProfile() = default;
+
+  nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir,
+                   nsIFile* aLocalDir, nsToolkitProfile* aPrev);
+
+  nsresult RemoveInternal(bool aRemoveFiles, bool aInBackground);
+
+  friend class nsToolkitProfileLock;
+
+  nsCString mName;
+  nsCOMPtr<nsIFile> mRootDir;
+  nsCOMPtr<nsIFile> mLocalDir;
+  nsIProfileLock* mLock;
+};
+
+class nsToolkitProfileLock final : public nsIProfileLock {
+ public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPROFILELOCK
+
+  nsresult Init(nsToolkitProfile* aProfile, nsIProfileUnlocker** aUnlocker);
+  nsresult Init(nsIFile* aDirectory, nsIFile* aLocalDirectory,
+                nsIProfileUnlocker** aUnlocker);
+
+  nsToolkitProfileLock() = default;
+
+ private:
+  ~nsToolkitProfileLock();
+
+  RefPtr<nsToolkitProfile> mProfile;
+  nsCOMPtr<nsIFile> mDirectory;
+  nsCOMPtr<nsIFile> mLocalDirectory;
+
+  nsProfileLock mLock;
+};
+
+class nsToolkitProfileFactory final : public nsIFactory {
+  ~nsToolkitProfileFactory() = default;
+
+ public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIFACTORY
+};
+
+class nsToolkitProfileService final : public nsIToolkitProfileService {
+ public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSITOOLKITPROFILESERVICE
+
+  nsresult SelectStartupProfile(int* aArgc, char* aArgv[], bool aIsResetting,
+                                nsIFile** aRootDir, nsIFile** aLocalDir,
+                                nsIToolkitProfile** aProfile, bool* aDidCreate);
+
+ private:
+  friend class nsToolkitProfile;
+  friend class nsToolkitProfileFactory;
+  friend nsresult NS_NewToolkitProfileService(nsIToolkitProfileService**);
+
+  nsToolkitProfileService();
+  ~nsToolkitProfileService();
+
+  nsresult Init();
+
+  nsresult CreateTimesInternal(nsIFile* profileDir);
+  void GetProfileByDir(nsIFile* aRootDir, nsIFile* aLocalDir,
+                       nsIToolkitProfile** aResult);
+
+  bool mStartupProfileSelected;
+  RefPtr<nsToolkitProfile> mFirst;
+  nsCOMPtr<nsIToolkitProfile> mChosen;
+  nsCOMPtr<nsIToolkitProfile> mDefault;
+  nsCOMPtr<nsIFile> mAppData;
+  nsCOMPtr<nsIFile> mTempData;
+  nsCOMPtr<nsIFile> mListFile;
+  bool mStartWithLast;
+  bool mIsFirstRun;
+
+  static nsToolkitProfileService* gService;
+
+  class ProfileEnumerator final : public nsSimpleEnumerator {
+   public:
+    NS_DECL_NSISIMPLEENUMERATOR
+
+    const nsID& DefaultInterface() override {
+      return NS_GET_IID(nsIToolkitProfile);
+    }
+
+    explicit ProfileEnumerator(nsToolkitProfile* first) { mCurrent = first; }
+
+   private:
+    RefPtr<nsToolkitProfile> mCurrent;
+  };
+};
+
+#endif
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/xpcshell/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/xpcshell-test"
+  ]
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/xpcshell/head.js
@@ -0,0 +1,221 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
+const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm", {});
+const { FileUtils } = ChromeUtils.import("resource://gre/modules/FileUtils.jsm", {});
+const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm", {});
+const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
+
+const NS_ERROR_START_PROFILE_MANAGER = 0x805800c9;
+
+let gProfD = do_get_profile();
+let gDataHome = gProfD.clone();
+gDataHome.append("data");
+gDataHome.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+let gDataHomeLocal = gProfD.clone();
+gDataHomeLocal.append("local");
+gDataHomeLocal.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+
+let xreDirProvider = Cc["@mozilla.org/xre/directory-provider;1"].
+                     getService(Ci.nsIXREDirProvider);
+xreDirProvider.setUserDataDirectory(gDataHome, false);
+xreDirProvider.setUserDataDirectory(gDataHomeLocal, true);
+
+function getProfileService() {
+  return Cc["@mozilla.org/toolkit/profile-service;1"].
+         getService(Ci.nsIToolkitProfileService);
+}
+
+let PROFILE_DEFAULT = "default";
+if (AppConstants.MOZ_DEV_EDITION) {
+  PROFILE_DEFAULT = "dev-edition-default";
+}
+
+/**
+ * Creates a random profile path for use.
+ */
+function makeRandomProfileDir(name) {
+  let file = gDataHome.clone();
+  file.append(name);
+  file.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+  return file;
+}
+
+/**
+ * A wrapper around nsIToolkitProfileService.selectStartupProfile to make it
+ * a bit nicer to use from JS.
+ */
+function selectStartupProfile(args = [], isResetting = false) {
+  let rootDir = {};
+  let localDir = {};
+  let profile = {};
+  let didCreate = getProfileService().selectStartupProfile(["xpcshell", ...args], isResetting,
+                                                           rootDir, localDir, profile);
+
+  if (profile.value) {
+    Assert.ok(rootDir.value.equals(profile.value.rootDir), "Should have matched the root dir.");
+    Assert.ok(localDir.value.equals(profile.value.localDir), "Should have matched the local dir.");
+  }
+
+  return {
+    rootDir: rootDir.value,
+    localDir: localDir.value,
+    profile: profile.value,
+    didCreate,
+  };
+}
+
+function testStartsProfileManager(args = [], isResetting = false) {
+  try {
+    selectStartupProfile(args, isResetting);
+    Assert.ok(false, "Should have started the profile manager");
+  } catch (e) {
+    Assert.equal(e.result, NS_ERROR_START_PROFILE_MANAGER, "Should have started the profile manager");
+  }
+}
+
+function safeGet(ini, section, key) {
+  try {
+    return ini.getString(section, key);
+  } catch (e) {
+    return null;
+  }
+}
+
+/**
+ * Writes a profiles.ini based on the passed profile data.
+ * profileData should contain two properties, options and profiles.
+ * options contains a single property, startWithLastProfile.
+ * profiles is an array of profiles each containing name, path and default
+ * properties.
+ */
+function writeProfilesIni(profileData) {
+  let target = gDataHome.clone();
+  target.append("profiles.ini");
+
+  let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
+                getService(Ci.nsIINIParserFactory);
+  let ini = factory.createINIParser().QueryInterface(Ci.nsIINIParserWriter);
+
+  const { options = {}, profiles = [] } = profileData;
+
+  let { startWithLastProfile = true } = options;
+  ini.setString("General", "StartWithLastProfile", startWithLastProfile ? "1" : "0");
+
+  for (let i = 0; i < profiles.length; i++) {
+    let profile = profiles[i];
+    let section = `Profile${i}`;
+
+    ini.setString(section, "Name", profile.name);
+    ini.setString(section, "IsRelative", 1);
+    ini.setString(section, "Path", profile.path);
+
+    if (profile.default) {
+      ini.setString(section, "Default", "1");
+    }
+  }
+
+  ini.writeFile(target);
+}
+
+/**
+ * Reads the existing profiles.ini into the same structure as that accepted by
+ * writeProfilesIni above. The profiles property is sorted according to name
+ * because the order is irrelevant and it makes testing easier if we can make
+ * that assumption.
+ */
+function readProfilesIni() {
+  let target = gDataHome.clone();
+  target.append("profiles.ini");
+
+  let profileData = {
+    options: {},
+    profiles: [],
+  };
+
+  if (!target.exists()) {
+    return profileData;
+  }
+
+  let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
+                getService(Ci.nsIINIParserFactory);
+  let ini = factory.createINIParser(target);
+
+  profileData.options.startWithLastProfile = safeGet(ini, "General", "StartWithLastProfile") == "1";
+
+  for (let i = 0; true; i++) {
+    let section = `Profile${i}`;
+
+    let isRelative = safeGet(ini, section, "IsRelative");
+    if (isRelative === null) {
+      break;
+    }
+    Assert.equal(isRelative, "1", "Paths should always be relative in these tests.");
+
+    let profile = {
+      name: safeGet(ini, section, "Name"),
+      path: safeGet(ini, section, "Path"),
+    };
+
+    try {
+      profile.default = ini.getString(section, "Default") == "1";
+      Assert.ok(profile.default, "The Default value is only written when true.");
+    } catch (e) {
+      profile.default = false;
+    }
+
+    profileData.profiles.push(profile);
+  }
+
+  profileData.profiles.sort((a, b) => a.name.localeCompare(b.name));
+
+  return profileData;
+}
+
+/**
+ * Checks that the profile service seems to have the right data in it compared
+ * to profile and install data structured as in the above functions.
+ */
+function checkProfileService(profileData = readProfilesIni()) {
+  let service = getProfileService();
+
+  let serviceProfiles = Array.from(service.profiles);
+
+  Assert.equal(serviceProfiles.length, profileData.profiles.length, "Should be the same number of profiles.");
+
+  // Sort to make matching easy.
+  serviceProfiles.sort((a, b) => a.name.localeCompare(b.name));
+  profileData.profiles.sort((a, b) => a.name.localeCompare(b.name));
+
+  let defaultProfile = null;
+
+  for (let i = 0; i < serviceProfiles.length; i++) {
+    let serviceProfile = serviceProfiles[i];
+    let expectedProfile = profileData.profiles[i];
+
+    Assert.equal(serviceProfile.name, expectedProfile.name, "Should have the same name.");
+
+    let expectedPath = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+    expectedPath.setRelativeDescriptor(gDataHome, expectedProfile.path);
+    Assert.equal(serviceProfile.rootDir.path, expectedPath.path, "Should have the same path.");
+
+    if (AppConstants.MOZ_DEV_EDITION) {
+      if (expectedProfile.name == PROFILE_DEFAULT) {
+        defaultProfile = serviceProfile;
+      }
+    } else if (expectedProfile.default) {
+      defaultProfile = serviceProfile;
+    }
+  }
+
+  let selectedProfile = null;
+  try {
+    selectedProfile = service.selectedProfile;
+  } catch (e) {
+    // GetSelectedProfile throws when there are no profiles.
+  }
+
+  Assert.equal(selectedProfile, defaultProfile, "Should have seen the right profile selected.");
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_create_default.js
@@ -0,0 +1,15 @@
+/*
+ * Tests that from an empty database a default profile is created.
+ */
+
+add_task(async () => {
+  let service = getProfileService();
+
+  let { profile, didCreate } = selectStartupProfile();
+  checkProfileService();
+
+  Assert.ok(didCreate, "Should have created a new profile.");
+  Assert.equal(service.profileCount, 1, "Should be only one profile.");
+  Assert.equal(profile, service.selectedProfile, "Should now be the selected profile.");
+  Assert.equal(profile.name, PROFILE_DEFAULT, "Should have created a new profile with the right name.");
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_profile_reset.js
@@ -0,0 +1,14 @@
+/*
+ * Tests that from an empty database profile reset doesn't create a new profile.
+ */
+
+add_task(async () => {
+  let service = getProfileService();
+
+  let { profile, didCreate } = selectStartupProfile([], true);
+  checkProfileService();
+
+  Assert.ok(!didCreate, "Should not have created a new profile.");
+  Assert.ok(!profile, "Should not be a returned profile.");
+  Assert.equal(service.profileCount, 0, "Still should be no profiles.");
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_default.js
@@ -0,0 +1,46 @@
+/*
+ * Tests that from a database of profiles the default profile is selected.
+ */
+
+add_task(async () => {
+  let profileData = {
+    options: {
+      startWithLastProfile: true,
+    },
+    profiles: [{
+      name: "Profile1",
+      path: "Path1",
+    }, {
+      name: "Profile3",
+      path: "Path3",
+    }],
+  };
+
+  if (AppConstants.MOZ_DEV_EDITION) {
+    profileData.profiles.push({
+      name: "default",
+      path: "Path2",
+      default: true,
+    }, {
+      name: PROFILE_DEFAULT,
+      path: "Path4",
+    });
+  } else {
+    profileData.profiles.push({
+      name: PROFILE_DEFAULT,
+      path: "Path2",
+      default: true,
+    });
+  }
+
+  writeProfilesIni(profileData);
+
+  let service = getProfileService();
+  checkProfileService(profileData);
+
+  let { profile, didCreate } = selectStartupProfile();
+
+  Assert.ok(!didCreate, "Should not have created a new profile.");
+  Assert.equal(profile, service.selectedProfile, "Should have returned the selected profile.");
+  Assert.equal(profile.name, PROFILE_DEFAULT, "Should have selected the right profile");
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_environment.js
@@ -0,0 +1,39 @@
+/*
+ * Tests that the environment variables are used to select a profile.
+ */
+
+add_task(async () => {
+  let dir = makeRandomProfileDir("foo");
+
+  let profileData = {
+    options: {
+      startWithLastProfile: true,
+    },
+    profiles: [{
+      name: "Profile1",
+      path: dir.leafName,
+    }, {
+      name: "Profile2",
+      path: "Path2",
+      default: true,
+    }, {
+      name: "Profile3",
+      path: "Path3",
+    }],
+  };
+
+  writeProfilesIni(profileData);
+  checkProfileService(profileData);
+
+  let env = Cc["@mozilla.org/process/environment;1"].
+            getService(Ci.nsIEnvironment);
+  env.set("XRE_PROFILE_PATH", dir.path);
+  env.set("XRE_PROFILE_LOCAL_PATH", dir.path);
+
+  let { rootDir, localDir, profile, didCreate } = selectStartupProfile();
+
+  Assert.ok(!didCreate, "Should not have created a new profile.");
+  Assert.ok(rootDir.equals(dir), "Should have selected the right root dir.");
+  Assert.ok(localDir.equals(dir), "Should have selected the right local dir.");
+  Assert.ok(!profile, "No named profile matches this.");
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_environment_named.js
@@ -0,0 +1,42 @@
+/*
+ * Tests that the environment variables are used to select a profile.
+ */
+
+add_task(async () => {
+  let root = makeRandomProfileDir("foo");
+  let local = gDataHomeLocal.clone();
+  local.append("foo");
+
+  let profileData = {
+    options: {
+      startWithLastProfile: true,
+    },
+    profiles: [{
+      name: "Profile1",
+      path: root.leafName,
+    }, {
+      name: "Profile2",
+      path: "Path2",
+      default: true,
+    }, {
+      name: "Profile3",
+      path: "Path3",
+    }],
+  };
+
+  writeProfilesIni(profileData);
+  checkProfileService(profileData);
+
+  let env = Cc["@mozilla.org/process/environment;1"].
+            getService(Ci.nsIEnvironment);
+  env.set("XRE_PROFILE_PATH", root.path);
+  env.set("XRE_PROFILE_LOCAL_PATH", local.path);
+
+  let { rootDir, localDir, profile, didCreate } = selectStartupProfile(["-P", "Profile3"]);
+
+  Assert.ok(!didCreate, "Should not have created a new profile.");
+  Assert.ok(rootDir.equals(root), "Should have selected the right root dir.");
+  Assert.ok(localDir.equals(local), "Should have selected the right local dir.");
+  Assert.ok(profile, "A named profile matches this.");
+  Assert.equal(profile.name, "Profile1", "The right profile was matched.");
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_missing.js
@@ -0,0 +1,27 @@
+/*
+ * Tests that when choosing an unknown profile the profile manager is shown.
+ */
+
+add_task(async () => {
+  let profileData = {
+    options: {
+      startWithLastProfile: true,
+    },
+    profiles: [{
+      name: "Profile1",
+      path: "Path1",
+    }, {
+      name: "Profile2",
+      path: "Path2",
+      default: true,
+    }, {
+      name: "Profile3",
+      path: "Path3",
+    }],
+  };
+
+  writeProfilesIni(profileData);
+  checkProfileService(profileData);
+
+  testStartsProfileManager(["-P", "foo"]);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_named.js
@@ -0,0 +1,31 @@
+/*
+ * Tests that from a database of profiles the correct profile is selected.
+ */
+
+add_task(async () => {
+  let profileData = {
+    options: {
+      startWithLastProfile: true,
+    },
+    profiles: [{
+      name: "Profile1",
+      path: "Path1",
+    }, {
+      name: "Profile2",
+      path: "Path2",
+      default: true,
+    }, {
+      name: "Profile3",
+      path: "Path3",
+    }],
+  };
+
+  writeProfilesIni(profileData);
+
+  checkProfileService(profileData);
+
+  let { profile, didCreate } = selectStartupProfile(["-P", "Profile1"]);
+
+  Assert.ok(!didCreate, "Should not have created a new profile.");
+  Assert.equal(profile.name, "Profile1", "Should have chosen the right profile");
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_noname.js
@@ -0,0 +1,28 @@
+/*
+ * Tests that when passing the -P command line argument and not passing a
+ * profile name the profile manager is opened.
+ */
+
+add_task(async () => {
+  let profileData = {
+    options: {
+      startWithLastProfile: true,
+    },
+    profiles: [{
+      name: "Profile1",
+      path: "Path1",
+    }, {
+      name: "Profile2",
+      path: "Path2",
+      default: true,
+    }, {
+      name: "Profile3",
+      path: "Path3",
+    }],
+  };
+
+  writeProfilesIni(profileData);
+  checkProfileService(profileData);
+
+  testStartsProfileManager(["-P"]);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_profilemanager.js
@@ -0,0 +1,27 @@
+/*
+ * Tests that when requested the profile manager is shown.
+ */
+
+add_task(async () => {
+  let profileData = {
+    options: {
+      startWithLastProfile: true,
+    },
+    profiles: [{
+      name: "Profile1",
+      path: "Path1",
+    }, {
+      name: "Profile2",
+      path: "Path2",
+      default: true,
+    }, {
+      name: "Profile3",
+      path: "Path3",
+    }],
+  };
+
+  writeProfilesIni(profileData);
+  checkProfileService(profileData);
+
+  testStartsProfileManager(["-profilemanager"]);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/xpcshell/xpcshell.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+head = head.js
+skip-if = toolkit == 'android'
+
+[test_select_default.js]
+[test_select_profilemanager.js]
+[test_select_named.js]
+[test_select_missing.js]
+[test_select_noname.js]
+[test_create_default.js]
+[test_select_environment.js]
+[test_select_environment_named.js]
+[test_profile_reset.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/xre/CmdLineAndEnvUtils.cpp
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "prenv.h"
+
+namespace mozilla {
+
+// Load the path of a file saved with SaveFileToEnv
+already_AddRefed<nsIFile> GetFileFromEnv(const char* name) {
+  nsresult rv;
+  nsCOMPtr<nsIFile> file;
+
+#ifdef XP_WIN
+  WCHAR path[_MAX_PATH];
+  if (!GetEnvironmentVariableW(NS_ConvertASCIItoUTF16(name).get(), path,
+                               _MAX_PATH))
+    return nullptr;
+
+  rv = NS_NewLocalFile(nsDependentString(path), true, getter_AddRefs(file));
+  if (NS_FAILED(rv)) return nullptr;
+
+  return file.forget();
+#else
+  const char* arg = PR_GetEnv(name);
+  if (!arg || !*arg) {
+    return nullptr;
+  }
+
+  rv = NS_NewNativeLocalFile(nsDependentCString(arg), true,
+                             getter_AddRefs(file));
+  if (NS_FAILED(rv)) {
+    return nullptr;
+  }
+
+  return file.forget();
+#endif
+}
+
+}  // namespace mozilla
--- a/toolkit/xre/CmdLineAndEnvUtils.h
+++ b/toolkit/xre/CmdLineAndEnvUtils.h
@@ -27,16 +27,21 @@
 #endif  // defined(XP_WIN)
 
 #include "mozilla/MemoryChecking.h"
 #include "mozilla/TypedEnumBits.h"
 
 #include <ctype.h>
 #include <stdint.h>
 
+#ifndef NS_NO_XPCOM
+#include "nsIFile.h"
+#include "mozilla/AlreadyAddRefed.h"
+#endif
+
 // Undo X11/X.h's definition of None
 #undef None
 
 namespace mozilla {
 
 enum ArgResult {
   ARG_NONE = 0,
   ARG_FOUND = 1,
@@ -118,17 +123,18 @@ MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Ch
  * @param aArgv The original argv.
  * @param aArg the parameter to check. Must be lowercase.
  * @param aParam if non-null, the -arg <data> will be stored in this pointer.
  *        This is *not* allocated, but rather a pointer to the argv data.
  * @param aFlags Flags @see CheckArgFlag
  */
 template <typename CharT>
 inline ArgResult CheckArg(int& aArgc, CharT** aArgv, const CharT* aArg,
-                          const CharT** aParam, CheckArgFlag aFlags) {
+                          const CharT** aParam = nullptr,
+                          CheckArgFlag aFlags = CheckArgFlag::RemoveArg) {
   MOZ_ASSERT(aArgv && aArg);
 
   CharT** curarg = aArgv + 1;  // skip argv[0]
   ArgResult ar = ARG_NONE;
 
   while (*curarg) {
     CharT* arg = curarg[0];
 
@@ -423,11 +429,15 @@ inline bool EnvHasValue(const char* aVar
   // This is the same as the NSPR implementation
   const char* val = getenv(aVarName);
   return val && *val;
 #else
 #error "Not implemented for this configuration"
 #endif
 }
 
+#ifndef NS_NO_XPCOM
+already_AddRefed<nsIFile> GetFileFromEnv(const char* name);
+#endif
+
 }  // namespace mozilla
 
 #endif  // mozilla_CmdLineAndEnvUtils_h
--- a/toolkit/xre/ProfileReset.cpp
+++ b/toolkit/xre/ProfileReset.cpp
@@ -30,26 +30,29 @@ extern const XREAppData* gAppData;
 
 static const char kProfileProperties[] =
     "chrome://mozapps/locale/profile/profileSelection.properties";
 
 /**
  * Creates a new profile with a timestamp in the name to use for profile reset.
  */
 nsresult CreateResetProfile(nsIToolkitProfileService* aProfileSvc,
-                            const nsACString& aOldProfileName,
+                            nsIToolkitProfile* aOldProfile,
                             nsIToolkitProfile** aNewProfile) {
   MOZ_ASSERT(aProfileSvc, "NULL profile service");
 
+  nsAutoCString oldProfileName;
+  aOldProfile->GetName(oldProfileName);
+
   nsCOMPtr<nsIToolkitProfile> newProfile;
   // Make the new profile the old profile (or "default-") + the time in seconds
   // since epoch for uniqueness.
   nsAutoCString newProfileName;
-  if (!aOldProfileName.IsEmpty()) {
-    newProfileName.Assign(aOldProfileName);
+  if (!oldProfileName.IsEmpty()) {
+    newProfileName.Assign(oldProfileName);
     newProfileName.Append("-");
   } else {
     newProfileName.AssignLiteral("default-");
   }
   newProfileName.Append(nsPrintfCString("%" PRId64, PR_Now() / 1000));
   nsresult rv =
       aProfileSvc->CreateProfile(nullptr,  // choose a default dir for us
                                  newProfileName, getter_AddRefs(newProfile));
--- a/toolkit/xre/ProfileReset.h
+++ b/toolkit/xre/ProfileReset.h
@@ -7,17 +7,17 @@
 #include "nsIFile.h"
 #include "nsThreadUtils.h"
 
 static bool gProfileResetCleanupCompleted = false;
 static const char kResetProgressURL[] =
     "chrome://global/content/resetProfileProgress.xul";
 
 nsresult CreateResetProfile(nsIToolkitProfileService* aProfileSvc,
-                            const nsACString& aOldProfileName,
+                            nsIToolkitProfile* aOldProfile,
                             nsIToolkitProfile** aNewProfile);
 
 nsresult ProfileResetCleanup(nsIToolkitProfile* aOldProfile);
 
 class ProfileResetCleanupResultTask : public mozilla::Runnable {
  public:
   ProfileResetCleanupResultTask()
       : mozilla::Runnable("ProfileResetCleanupResultTask"),
--- a/toolkit/xre/moz.build
+++ b/toolkit/xre/moz.build
@@ -101,16 +101,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'andr
     UNIFIED_SOURCES += [
         'nsAndroidStartup.cpp',
     ]
 
 UNIFIED_SOURCES += [
     '/toolkit/mozapps/update/common/commonupdatedir.cpp',
     'AutoSQLiteLifetime.cpp',
     'Bootstrap.cpp',
+    'CmdLineAndEnvUtils.cpp',
     'CreateAppData.cpp',
     'nsAppStartupNotifier.cpp',
     'nsConsoleWriter.cpp',
     'nsEmbeddingModule.cpp',
     'nsNativeAppSupportBase.cpp',
     'nsSigHandlers.cpp',
     'nsXREDirProvider.cpp',
 ]
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -75,17 +75,17 @@
 #include "nsIProcess.h"
 #include "nsIProfileUnlocker.h"
 #include "nsIPromptService.h"
 #include "nsIServiceManager.h"
 #include "nsIStringBundle.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIToolkitChromeRegistry.h"
 #include "nsIToolkitProfile.h"
-#include "nsIToolkitProfileService.h"
+#include "nsToolkitProfileService.h"
 #include "nsIURI.h"
 #include "nsIURL.h"
 #include "nsIWindowCreator.h"
 #include "nsIWindowMediator.h"
 #include "nsIWindowWatcher.h"
 #include "nsIXULAppInfo.h"
 #include "nsIXULRuntime.h"
 #include "nsPIDOMWindow.h"
@@ -335,53 +335,16 @@ static void SaveFileToEnv(const char* na
   SetEnvironmentVariableW(NS_ConvertASCIItoUTF16(name).get(), path.get());
 #else
   nsAutoCString path;
   file->GetNativePath(path);
   SaveWordToEnv(name, path);
 #endif
 }
 
-// Load the path of a file saved with SaveFileToEnv
-#ifndef MOZ_ASAN_REPORTER
-static
-#endif
-    already_AddRefed<nsIFile>
-    GetFileFromEnv(const char* name) {
-  nsresult rv;
-  nsCOMPtr<nsIFile> file;
-
-#ifdef XP_WIN
-  WCHAR path[_MAX_PATH];
-  if (!GetEnvironmentVariableW(NS_ConvertASCIItoUTF16(name).get(), path,
-                               _MAX_PATH))
-    return nullptr;
-
-  rv = NS_NewLocalFile(nsDependentString(path), true, getter_AddRefs(file));
-  if (NS_FAILED(rv)) return nullptr;
-
-  return file.forget();
-#else
-  const char* arg = PR_GetEnv(name);
-  if (!arg || !*arg) return nullptr;
-
-  rv = NS_NewNativeLocalFile(nsDependentCString(arg), true,
-                             getter_AddRefs(file));
-  if (NS_FAILED(rv)) return nullptr;
-
-  return file.forget();
-#endif
-}
-
-// Save the path of the given word to the specified environment variable
-// provided the environment variable does not have a value.
-static void SaveWordToEnvIfUnset(const char* name, const nsACString& word) {
-  if (!EnvHasValue(name)) SaveWordToEnv(name, word);
-}
-
 // Save the path of the given file to the specified environment variable
 // provided the environment variable does not have a value.
 static void SaveFileToEnvIfUnset(const char* name, nsIFile* file) {
   if (!EnvHasValue(name)) SaveFileToEnv(name, file);
 }
 
 static bool gIsExpectedExit = false;
 
@@ -2017,17 +1980,16 @@ static ReturnAbortOnError ShowProfileMan
       free(profileNamePtr);
 
       lock->Unlock();
     }
   }
 
   SaveFileToEnv("XRE_PROFILE_PATH", profD);
   SaveFileToEnv("XRE_PROFILE_LOCAL_PATH", profLD);
-  SaveWordToEnv("XRE_PROFILE_NAME", profileName);
 
   if (offline) {
     SaveToEnv("XRE_START_OFFLINE=1");
   }
   if (gRestartedByOS) {
     // Re-add this argument when actually starting the application.
     char** newArgv =
         (char**)realloc(gRestartArgv, sizeof(char*) * (gRestartArgc + 2));
@@ -2072,17 +2034,17 @@ static nsresult GetCurrentProfile(nsIToo
     }
     rv = profiles->GetNext(getter_AddRefs(supports));
   }
   return rv;
 }
 
 static bool gDoMigration = false;
 static bool gDoProfileReset = false;
-static nsAutoCString gResetOldProfileName;
+static nsCOMPtr<nsIToolkitProfile> gResetOldProfile;
 
 // 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
@@ -2090,17 +2052,16 @@ static nsAutoCString gResetOldProfileNam
 static nsresult SelectProfile(nsIProfileLock** aResult,
                               nsIToolkitProfileService* aProfileSvc,
                               nsINativeAppSupport* aNative, bool* aStartOffline,
                               nsACString* aProfileName) {
   StartupTimeline::Record(StartupTimeline::SELECT_PROFILE);
 
   nsresult rv;
   ArgResult ar;
-  const char* arg;
   *aResult = nullptr;
   *aStartOffline = false;
 
   ar = CheckArg("offline", nullptr,
                 CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg);
   if (ar == ARG_BAD) {
     PR_fprintf(PR_STDERR,
                "Error: argument --offline is invalid when argument --osint is "
@@ -2144,359 +2105,121 @@ static nsresult SelectProfile(nsIProfile
                "Error: argument --migration is invalid when argument --osint "
                "is specified\n");
     return NS_ERROR_FAILURE;
   }
   if (ar == ARG_FOUND) {
     gDoMigration = true;
   }
 
-  nsCOMPtr<nsIFile> lf = GetFileFromEnv("XRE_PROFILE_PATH");
-  if (lf) {
-    nsCOMPtr<nsIFile> localDir = GetFileFromEnv("XRE_PROFILE_LOCAL_PATH");
-    if (!localDir) {
-      localDir = lf;
-    }
-
-    arg = PR_GetEnv("XRE_PROFILE_NAME");
-    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", &dummy);
-    CheckArg("profile", &dummy);
-    CheckArg("profilemanager");
-
-    if (gDoProfileReset) {
-      // If we're resetting a profile, create a new one and use it to startup.
-      nsCOMPtr<nsIToolkitProfile> newProfile;
-      rv = CreateResetProfile(aProfileSvc, gResetOldProfileName,
-                              getter_AddRefs(newProfile));
-      if (NS_SUCCEEDED(rv)) {
-        rv = newProfile->GetRootDir(getter_AddRefs(lf));
-        NS_ENSURE_SUCCESS(rv, rv);
-        SaveFileToEnv("XRE_PROFILE_PATH", lf);
-
-        rv = newProfile->GetLocalDir(getter_AddRefs(localDir));
-        NS_ENSURE_SUCCESS(rv, rv);
-        SaveFileToEnv("XRE_PROFILE_LOCAL_PATH", localDir);
-
-        rv = newProfile->GetName(*aProfileName);
-        if (NS_FAILED(rv)) aProfileName->Truncate(0);
-        SaveWordToEnv("XRE_PROFILE_NAME", *aProfileName);
-      } else {
-        NS_WARNING("Profile reset failed.");
-        gDoProfileReset = false;
-      }
-    }
-
-    return NS_LockProfilePath(lf, localDir, nullptr, aResult);
-  }
-
-  ar = CheckArg("profile", &arg,
-                CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg);
-  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 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;
-
-    // Check if the profile path exists and it's a directory.
-    bool exists;
-    lf->Exists(&exists);
-    if (!exists) {
-      rv = lf->Create(nsIFile::DIRECTORY_TYPE, 0700);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-
-    // If a profile path is specified directory on the command line, then
-    // assume that the temp directory is the same as the given directory.
-    rv = NS_LockProfilePath(lf, lf, getter_AddRefs(unlocker), aResult);
-    if (NS_SUCCEEDED(rv)) return rv;
-
-    return ProfileLockedDialog(lf, lf, unlocker, aNative, aResult);
-  }
-
-  ar = CheckArg("createprofile", &arg,
-                CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg);
-  if (ar == ARG_BAD) {
-    PR_fprintf(PR_STDERR,
-               "Error: argument --createprofile requires a profile name\n");
-    return NS_ERROR_FAILURE;
-  }
-  if (ar) {
-    nsCOMPtr<nsIToolkitProfile> profile;
-
-    const char* delim = strchr(arg, ' ');
-    if (delim) {
-      nsCOMPtr<nsIFile> lf;
-      rv = NS_NewNativeLocalFile(nsDependentCString(delim + 1), true,
-                                 getter_AddRefs(lf));
-      if (NS_FAILED(rv)) {
-        PR_fprintf(PR_STDERR, "Error: profile path not valid.\n");
-        return rv;
-      }
-
-      // As with --profile, assume that the given path will be used for the
-      // main profile directory.
-      rv = aProfileSvc->CreateProfile(lf, nsDependentCSubstring(arg, delim),
-                                      getter_AddRefs(profile));
-    } else {
-      rv = aProfileSvc->CreateProfile(nullptr, nsDependentCString(arg),
-                                      getter_AddRefs(profile));
-    }
-    // Some pathological arguments can make it this far
-    if (NS_FAILED(rv)) {
-      PR_fprintf(PR_STDERR, "Error creating profile.\n");
-      return rv;
-    }
-    rv = NS_ERROR_ABORT;
-    aProfileSvc->Flush();
-
-    // XXXben need to ensure prefs.js exists here so the tinderboxes will
-    //        not go orange.
-    nsCOMPtr<nsIFile> prefsJSFile;
-    profile->GetRootDir(getter_AddRefs(prefsJSFile));
-    prefsJSFile->AppendNative(NS_LITERAL_CSTRING("prefs.js"));
-    PR_fprintf(PR_STDERR, "Success: created profile '%s' at '%s'\n", arg,
-               prefsJSFile->HumanReadablePath().get());
-    bool exists;
-    prefsJSFile->Exists(&exists);
-    if (!exists) {
-      // Ignore any errors; we're about to return NS_ERROR_ABORT anyway.
-      Unused << prefsJSFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
-    }
-    // XXXdarin perhaps 0600 would be better?
-
-    return rv;
-  }
-
-  uint32_t count;
-  rv = aProfileSvc->GetProfileCount(&count);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  ar = CheckArg("p", &arg);
-  if (ar == ARG_BAD) {
-    ar = CheckArg("osint");
-    if (ar == ARG_FOUND) {
-      PR_fprintf(
-          PR_STDERR,
-          "Error: argument -p is invalid when argument --osint is specified\n");
-      return NS_ERROR_FAILURE;
-    }
-
+  nsCOMPtr<nsIFile> rootDir;
+  nsCOMPtr<nsIFile> localDir;
+  nsCOMPtr<nsIToolkitProfile> profile;
+  // Ask the profile manager to select the profile directories to use.
+  nsToolkitProfileService* service =
+      static_cast<nsToolkitProfileService*>(aProfileSvc);
+  bool didCreate = false;
+  rv = service->SelectStartupProfile(
+      &gArgc, gArgv, gDoProfileReset, getter_AddRefs(rootDir),
+      getter_AddRefs(localDir), getter_AddRefs(profile), &didCreate);
+
+  if (rv == NS_ERROR_SHOW_PROFILE_MANAGER) {
     return ShowProfileManager(aProfileSvc, aNative);
   }
-  if (ar) {
-    ar = CheckArg("osint");
-    if (ar == ARG_FOUND) {
-      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) {
-        {
-          // Check that the source profile is not in use by temporarily
-          // acquiring its lock.
-          nsIProfileLock* tempProfileLock;
-          nsCOMPtr<nsIProfileUnlocker> unlocker;
-          rv = profile->Lock(getter_AddRefs(unlocker), &tempProfileLock);
-          if (NS_FAILED(rv))
-            return ProfileLockedDialog(profile, unlocker, aNative,
-                                       &tempProfileLock);
-        }
-
-        nsresult gotName = profile->GetName(gResetOldProfileName);
-        if (NS_SUCCEEDED(gotName)) {
-          nsCOMPtr<nsIToolkitProfile> newProfile;
-          rv = CreateResetProfile(aProfileSvc, gResetOldProfileName,
-                                  getter_AddRefs(newProfile));
-          if (NS_FAILED(rv)) {
-            NS_WARNING("Failed to create a profile to reset to.");
-            gDoProfileReset = false;
-          } else {
-            profile = newProfile;
-          }
-        } else {
-          NS_WARNING(
-              "Failed to get the name of the profile we're resetting, so "
-              "aborting reset.");
-          gResetOldProfileName.Truncate(0);
-          gDoProfileReset = false;
-        }
-      }
-
-      nsCOMPtr<nsIProfileUnlocker> unlocker;
-      rv = profile->Lock(getter_AddRefs(unlocker), aResult);
-      if (NS_SUCCEEDED(rv)) {
-        if (aProfileName) aProfileName->Assign(nsDependentCString(arg));
-        return NS_OK;
-      }
-
-      return ProfileLockedDialog(profile, unlocker, aNative, aResult);
-    }
-
-    return ShowProfileManager(aProfileSvc, aNative);
-  }
-
-  ar = CheckArg("profilemanager", nullptr,
-                CheckArgFlag::CheckOSInt | CheckArgFlag::RemoveArg);
-  if (ar == ARG_BAD) {
-    PR_fprintf(PR_STDERR,
-               "Error: argument --profilemanager is invalid when argument "
-               "--osint is specified\n");
-    return NS_ERROR_FAILURE;
-  }
-  if (ar == ARG_FOUND) {
-    return ShowProfileManager(aProfileSvc, aNative);
-  }
-
-#ifndef MOZ_DEV_EDITION
-  // If the only existing profile is the dev-edition-profile and this is not
-  // Developer Edition, then no valid profiles were found.
-  if (count == 1) {
-    nsCOMPtr<nsIToolkitProfile> deProfile;
-    // GetSelectedProfile will auto-select the only profile if there's just one
-    aProfileSvc->GetSelectedProfile(getter_AddRefs(deProfile));
-    nsAutoCString profileName;
-    deProfile->GetName(profileName);
-    if (profileName.EqualsLiteral("dev-edition-default")) {
-      count = 0;
-    }
-  }
-#endif
-
-  if (!count) {
+
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (didCreate) {
     // For a fresh install, we would like to let users decide
     // to do profile migration on their own later after using.
+    gDoProfileReset = false;
     gDoMigration = false;
-    gDoProfileReset = false;
-
-    // create a default profile
-    nsCOMPtr<nsIToolkitProfile> profile;
-    nsresult rv =
-        aProfileSvc->CreateProfile(nullptr,  // choose a default dir for us
-#ifdef MOZ_DEV_EDITION
-                                   NS_LITERAL_CSTRING("dev-edition-default"),
-#else
-                                   NS_LITERAL_CSTRING("default"),
-#endif
-                                   getter_AddRefs(profile));
+  }
+
+  if (gDoProfileReset) {
+    if (!profile) {
+      NS_WARNING("Profile reset is only supported for named profiles.");
+      return NS_ERROR_ABORT;
+    }
+
+    {
+      // Check that the source profile is not in use by temporarily
+      // acquiring its lock.
+      nsIProfileLock* tempProfileLock;
+      nsCOMPtr<nsIProfileUnlocker> unlocker;
+      rv = profile->Lock(getter_AddRefs(unlocker), &tempProfileLock);
+      if (NS_FAILED(rv)) {
+        return ProfileLockedDialog(profile, unlocker, aNative,
+                                   &tempProfileLock);
+      }
+    }
+
+    // If we're resetting a profile, create a new one and use it to startup.
+    gResetOldProfile = profile;
+    rv = CreateResetProfile(aProfileSvc, gResetOldProfile,
+                            getter_AddRefs(profile));
     if (NS_SUCCEEDED(rv)) {
-#ifndef MOZ_DEV_EDITION
-      aProfileSvc->SetDefaultProfile(profile);
-#endif
-      aProfileSvc->Flush();
-      rv = profile->Lock(nullptr, aResult);
-      if (NS_SUCCEEDED(rv)) {
-        if (aProfileName)
-#ifdef MOZ_DEV_EDITION
-          aProfileName->AssignLiteral("dev-edition-default");
-#else
-          aProfileName->AssignLiteral("default");
-#endif
-        return NS_OK;
+      rv = profile->GetRootDir(getter_AddRefs(rootDir));
+      NS_ENSURE_SUCCESS(rv, rv);
+      SaveFileToEnv("XRE_PROFILE_PATH", rootDir);
+
+      rv = profile->GetLocalDir(getter_AddRefs(localDir));
+      NS_ENSURE_SUCCESS(rv, rv);
+      SaveFileToEnv("XRE_PROFILE_LOCAL_PATH", localDir);
+
+      rv = profile->GetName(*aProfileName);
+      if (NS_FAILED(rv)) {
+        aProfileName->Truncate(0);
       }
+    } else {
+      NS_WARNING("Profile reset failed.");
+      return NS_ERROR_ABORT;
     }
   }
 
-  bool useDefault = true;
-  if (count > 1) {
-    aProfileSvc->GetStartWithLastProfile(&useDefault);
+  // No profile could be found. This generally shouldn't happen, a new profile
+  // should be created in all cases except for profile reset which is covered
+  // above, but just in case...
+  if (!rootDir) {
+    return NS_ERROR_ABORT;
   }
 
-  if (useDefault) {
-    nsCOMPtr<nsIToolkitProfile> profile;
-    // GetSelectedProfile will auto-select the only profile if there's just one
-    aProfileSvc->GetSelectedProfile(getter_AddRefs(profile));
+  // 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;
+  static const int kLockRetrySleepMS = 100;
+
+  nsCOMPtr<nsIProfileUnlocker> unlocker;
+  const TimeStamp start = TimeStamp::Now();
+  do {
     if (profile) {
-      // If we're resetting a profile, create a new one and use it to startup.
-      if (gDoProfileReset) {
-        {
-          // Check that the source profile is not in use by temporarily
-          // acquiring its lock.
-          nsIProfileLock* tempProfileLock;
-          nsCOMPtr<nsIProfileUnlocker> unlocker;
-          rv = profile->Lock(getter_AddRefs(unlocker), &tempProfileLock);
-          if (NS_FAILED(rv))
-            return ProfileLockedDialog(profile, unlocker, aNative,
-                                       &tempProfileLock);
-        }
-
-        nsresult gotName = profile->GetName(gResetOldProfileName);
-        if (NS_SUCCEEDED(gotName)) {
-          nsCOMPtr<nsIToolkitProfile> newProfile;
-          rv = CreateResetProfile(aProfileSvc, gResetOldProfileName,
-                                  getter_AddRefs(newProfile));
-          if (NS_FAILED(rv)) {
-            NS_WARNING("Failed to create a profile to reset to.");
-            gDoProfileReset = false;
-          } else {
-            profile = newProfile;
-          }
-        } else {
-          NS_WARNING(
-              "Failed to get the name of the profile we're resetting, so "
-              "aborting reset.");
-          gResetOldProfileName.Truncate(0);
-          gDoProfileReset = false;
+      rv = profile->Lock(getter_AddRefs(unlocker), aResult);
+    } else {
+      rv = NS_LockProfilePath(rootDir, localDir, getter_AddRefs(unlocker),
+                              aResult);
+    }
+    if (NS_SUCCEEDED(rv)) {
+      StartupTimeline::Record(StartupTimeline::AFTER_PROFILE_LOCKED);
+      // Try to grab the profile name.
+      if (aProfileName && profile) {
+        rv = profile->GetName(*aProfileName);
+        if (NS_FAILED(rv)) {
+          aProfileName->Truncate(0);
         }
       }
-
-      // 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;
-      static const int kLockRetrySleepMS = 100;
-
-      nsCOMPtr<nsIProfileUnlocker> unlocker;
-      const TimeStamp start = TimeStamp::Now();
-      do {
-        rv = profile->Lock(getter_AddRefs(unlocker), aResult);
-        if (NS_SUCCEEDED(rv)) {
-          StartupTimeline::Record(StartupTimeline::AFTER_PROFILE_LOCKED);
-          // Try to grab the profile name.
-          if (aProfileName) {
-            rv = profile->GetName(*aProfileName);
-            if (NS_FAILED(rv)) aProfileName->Truncate(0);
-          }
-          return NS_OK;
-        }
-        PR_Sleep(kLockRetrySleepMS);
-      } while (TimeStamp::Now() - start <
-               TimeDuration::FromSeconds(kLockRetrySeconds));
-
-      return ProfileLockedDialog(profile, unlocker, aNative, aResult);
+      return NS_OK;
     }
-  }
-
-  return ShowProfileManager(aProfileSvc, aNative);
+    PR_Sleep(kLockRetrySleepMS);
+  } while (TimeStamp::Now() - start <
+           TimeDuration::FromSeconds(kLockRetrySeconds));
+
+  return ProfileLockedDialog(rootDir, localDir, unlocker, aNative, aResult);
 }
 
 /**
  * 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.
@@ -4351,67 +4074,58 @@ nsresult XREMain::XRE_mainRun() {
         if (buf[0] == '0' || buf[0] == 'f' || buf[0] == 'F') {
           gDoMigration = false;
         }
       }
     }
   }
 
   {
-    nsCOMPtr<nsIToolkitProfile> profileBeingReset;
     bool profileWasSelected = false;
     if (gDoProfileReset) {
-      if (gResetOldProfileName.IsEmpty()) {
-        NS_WARNING("Not resetting profile as the profile has no name.");
-        gDoProfileReset = false;
-      } 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;
-        }
+      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 == gResetOldProfile;
       }
     }
 
     // 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;
+        nsAutoCString aName;
         if (gDoProfileReset) {
           // Automatically migrate from the current application if we just
           // reset the profile.
           aKey = MOZ_APP_NAME;
+          gResetOldProfile->GetName(aName);
         }
-        pm->Migrate(&mDirProvider, aKey, gResetOldProfileName);
+        pm->Migrate(&mDirProvider, aKey, aName);
       }
     }
 
     if (gDoProfileReset) {
-      nsresult backupCreated = ProfileResetCleanup(profileBeingReset);
+      nsresult backupCreated = ProfileResetCleanup(gResetOldProfile);
       if (NS_FAILED(backupCreated))
         NS_WARNING("Could not cleanup the profile that was reset");
 
       nsCOMPtr<nsIToolkitProfile> newProfile;
       rv = GetCurrentProfile(mProfileSvc, mProfD, getter_AddRefs(newProfile));
       if (NS_SUCCEEDED(rv)) {
-        newProfile->SetName(gResetOldProfileName);
-        mProfileName.Assign(gResetOldProfileName);
+        nsAutoCString name;
+        gResetOldProfile->GetName(name);
+        newProfile->SetName(name);
+        mProfileName.Assign(name);
         // 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) {
           rv = mProfileSvc->SetDefaultProfile(newProfile);
           if (NS_FAILED(rv))
             NS_WARNING("Could not set current profile as the default");
         }
       } else {
@@ -4500,17 +4214,16 @@ nsresult XREMain::XRE_mainRun() {
 #endif
 
   SaveStateForAppInitiatedRestart();
 
   // clear out any environment variables which may have been set
   // during the relaunch process now that we know we won't be relaunching.
   SaveToEnv("XRE_PROFILE_PATH=");
   SaveToEnv("XRE_PROFILE_LOCAL_PATH=");
-  SaveToEnv("XRE_PROFILE_NAME=");
   SaveToEnv("XRE_START_OFFLINE=");
   SaveToEnv("XUL_APP_FILE=");
   SaveToEnv("XRE_BINARY_PATH=");
 
   if (!mShuttingDown) {
     rv = appStartup->CreateHiddenWindow();
     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 
@@ -4795,17 +4508,16 @@ int XREMain::XRE_main(int argc, char* ar
   // Restart the app after XPCOM has been shut down cleanly.
   if (appInitiatedRestart) {
     RestoreStateForAppInitiatedRestart();
 
     if (rv != NS_SUCCESS_RESTART_APP_NOT_SAME_PROFILE) {
       // Ensure that these environment variables are set:
       SaveFileToEnvIfUnset("XRE_PROFILE_PATH", mProfD);
       SaveFileToEnvIfUnset("XRE_PROFILE_LOCAL_PATH", mProfLD);
-      SaveWordToEnvIfUnset("XRE_PROFILE_NAME", mProfileName);
     }
 
 #ifdef MOZ_WIDGET_GTK
     if (!gfxPlatform::IsHeadless()) {
       MOZ_gdk_display_close(mGdkDisplay);
     }
 #endif
 
--- a/toolkit/xre/nsAppRunner.h
+++ b/toolkit/xre/nsAppRunner.h
@@ -121,13 +121,11 @@ const char* PlatformBuildID();
  */
 void SetupErrorHandling(const char* progname);
 
 #ifdef MOZ_ASAN_REPORTER
 extern "C" {
 void MOZ_EXPORT __sanitizer_set_report_path(const char* path);
 }
 void setASanReporterPath(nsIFile* aDir);
-
-already_AddRefed<nsIFile> GetFileFromEnv(const char* name);
 #endif
 
 #endif  // nsAppRunner_h__
--- a/toolkit/xre/nsXREDirProvider.cpp
+++ b/toolkit/xre/nsXREDirProvider.cpp
@@ -14,16 +14,17 @@
 #include "nsIDirectoryEnumerator.h"
 #include "nsIFile.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsISimpleEnumerator.h"
 #include "nsIToolkitChromeRegistry.h"
 #include "nsIToolkitProfileService.h"
 #include "nsIXULRuntime.h"
+#include "commonupdatedir.h"
 
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsXULAppAPI.h"
 #include "nsCategoryManagerUtils.h"
 
 #include "nsDependentString.h"
@@ -42,17 +43,16 @@
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 
 #include <stdlib.h>
 
 #ifdef XP_WIN
 #include <windows.h>
 #include <shlobj.h>
-#include "commonupdatedir.h"
 #endif
 #ifdef XP_MACOSX
 #include "nsILocalFileMac.h"
 // for chflags()
 #include <sys/stat.h>
 #include <unistd.h>
 #endif
 #ifdef XP_UNIX
@@ -93,29 +93,24 @@ static already_AddRefed<nsIFile> CreateP
 #endif
 
 nsXREDirProvider* gDirServiceProvider = nullptr;
 nsIFile* gDataDirHomeLocal = nullptr;
 nsIFile* gDataDirHome = nullptr;
 
 // These are required to allow nsXREDirProvider to be usable in xpcshell tests.
 // where gAppData is null.
-static const char* GetAppProfile() {
-  if (gAppData) {
-    return gAppData->profile;
-  }
-  return nullptr;
-}
-
+#if defined(XP_MACOSX) || defined(XP_WIN)
 static const char* GetAppName() {
   if (gAppData) {
     return gAppData->name;
   }
   return nullptr;
 }
+#endif
 
 static const char* GetAppVendor() {
   if (gAppData) {
     return gAppData->vendor;
   }
   return nullptr;
 }
 
@@ -1603,24 +1598,31 @@ nsresult nsXREDirProvider::AppendSysUser
 #error "Don't know how to get XRE system extension dev path on your platform"
 #endif
   return NS_OK;
 }
 
 nsresult nsXREDirProvider::AppendProfilePath(nsIFile* aFile, bool aLocal) {
   NS_ASSERTION(aFile, "Null pointer!");
 
+  // If there is no XREAppData then there is no information to use to build
+  // the profile path so just do nothing. This should only happen in xpcshell
+  // tests.
+  if (!gAppData) {
+    return NS_OK;
+  }
+
   nsAutoCString profile;
   nsAutoCString appName;
   nsAutoCString vendor;
-  if (GetAppProfile()) {
-    profile = GetAppProfile();
+  if (gAppData->profile) {
+    profile = gAppData->profile;
   } else {
-    appName = GetAppName();
-    vendor = GetAppVendor();
+    appName = gAppData->name;
+    vendor = gAppData->vendor;
   }
 
   nsresult rv;
 
 #if defined(XP_MACOSX)
   if (!profile.IsEmpty()) {
     rv = AppendProfileString(aFile, profile.get());
   } else {
--- a/xpcom/base/ErrorList.py
+++ b/xpcom/base/ErrorList.py
@@ -739,16 +739,17 @@ with modules["XPCONNECT"]:
     # any new errors here should have an associated entry added in xpc.msg
 
 
 # =======================================================================
 # 19: NS_ERROR_MODULE_PROFILE
 # =======================================================================
 with modules["PROFILE"]:
     errors["NS_ERROR_LAUNCHED_CHILD_PROCESS"] = FAILURE(200)
+    errors["NS_ERROR_SHOW_PROFILE_MANAGER"] = FAILURE(201)
 
 
 # =======================================================================
 # 21: NS_ERROR_MODULE_SECURITY
 # =======================================================================
 with modules["SECURITY"]:
     # Error code for CSP
     errors["NS_ERROR_CSP_FORM_ACTION_VIOLATION"] = FAILURE(98)