Bug 1527704: Store install information in profiles.ini and use installs.ini as a backup in case an earlier Firefox throws it away. r=froydnj

Originally we stored the new information about installation defaults in
installs.ini since older versions of Firefox would throw away any new data in
profiles.ini any time they made changes to the profiles. That does however mean
we have to load two files on startup.

This changes things so that we save all the data in profiles.ini as well as a
version tag and still save the install data into installs.ini. An older version
will throw away the install data and version tag from profiles.ini but leave
installs.ini alone. On startup if the version tag is gone from profiles.ini then
we reload the install data from installs.ini and put it back into profiles.ini.

At some point in the future where we don't care about supporting older versions
of Firefox we can just drop installs.ini entirely.

A lot of the changes here involve moving to loading profiles.ini into an
in-memory ini, keeping it up to date and flushing it to disk. This means that we
no longer throw away any information in the ini file that this version does not
understand allowing the possibility of adding new data to this file in the
future.

Differential Revision: https://phabricator.services.mozilla.com/D22576

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Dave Townsend 2019-03-26 00:16:59 +00:00
Родитель 6d1edba8ae
Коммит ca3a12dacd
28 изменённых файлов: 669 добавлений и 322 удалений

Просмотреть файл

@ -46,7 +46,7 @@ interface nsIProfileLock : nsISupports
* @note THIS INTERFACE SHOULD BE IMPLEMENTED BY THE TOOLKIT CODE ONLY! DON'T
* EVEN THINK ABOUT IMPLEMENTING THIS IN JAVASCRIPT!
*/
[scriptable, uuid(7422b090-4a86-4407-972e-75468a625388)]
[scriptable, builtinclass, uuid(7422b090-4a86-4407-972e-75468a625388)]
interface nsIToolkitProfile : nsISupports
{
/**

Просмотреть файл

@ -52,20 +52,67 @@ using namespace mozilla;
#define DEV_EDITION_NAME "dev-edition-default"
#define DEFAULT_NAME "default"
#define COMPAT_FILE NS_LITERAL_STRING("compatibility.ini")
#define PROFILE_DB_VERSION "2"
#define INSTALL_PREFIX "Install"
#define INSTALL_PREFIX_LENGTH 7
struct KeyValue {
KeyValue(const char* aKey, const char* aValue) : key(aKey), value(aValue) {}
nsCString key;
nsCString value;
};
static bool GetStrings(const char* aString, const char* aValue,
void* aClosure) {
nsTArray<UniquePtr<KeyValue>>* array =
static_cast<nsTArray<UniquePtr<KeyValue>>*>(aClosure);
array->AppendElement(MakeUnique<KeyValue>(aString, aValue));
return true;
}
/**
* Returns an array of the strings inside a section of an ini file.
*/
nsTArray<UniquePtr<KeyValue>> GetSectionStrings(nsINIParser* aParser,
const char* aSection) {
nsTArray<UniquePtr<KeyValue>> result;
aParser->GetStrings(aSection, &GetStrings, &result);
return result;
}
nsToolkitProfile::nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir,
nsIFile* aLocalDir, nsToolkitProfile* aPrev)
: mPrev(aPrev),
mName(aName),
nsIFile* aLocalDir, bool aFromDB)
: mName(aName),
mRootDir(aRootDir),
mLocalDir(aLocalDir),
mLock(nullptr) {
mLock(nullptr),
mIndex(0),
mSection("Profile") {
NS_ASSERTION(aRootDir, "No file!");
if (aPrev) {
aPrev->mNext = this;
} else {
nsToolkitProfileService::gService->mFirst = this;
RefPtr<nsToolkitProfile> prev =
nsToolkitProfileService::gService->mProfiles.getLast();
if (prev) {
mIndex = prev->mIndex + 1;
}
mSection.AppendInt(mIndex);
nsToolkitProfileService::gService->mProfiles.insertBack(this);
// If this profile isn't in the database already add it.
if (!aFromDB) {
nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB;
db->SetString(mSection.get(), "Name", mName.get());
bool isRelative = false;
nsCString descriptor;
nsToolkitProfileService::gService->GetProfileDescriptor(this, descriptor,
&isRelative);
db->SetString(mSection.get(), "IsRelative", isRelative ? "1" : "0");
db->SetString(mSection.get(), "Path", descriptor.get());
}
}
@ -106,6 +153,10 @@ nsToolkitProfile::SetName(const nsACString& aName) {
mName = aName;
nsresult rv = nsToolkitProfileService::gService->mProfileDB.SetString(
mSection.get(), "Name", mName.get());
NS_ENSURE_SUCCESS(rv, rv);
// Setting the name to the dev-edition default profile name will cause this
// profile to become the dev-edition default.
if (aName.EqualsLiteral(DEV_EDITION_NAME) &&
@ -122,8 +173,9 @@ nsresult nsToolkitProfile::RemoveInternal(bool aRemoveFiles,
if (mLock) return NS_ERROR_FILE_IS_LOCKED;
if (!mPrev && !mNext && nsToolkitProfileService::gService->mFirst != this)
if (!isInList()) {
return NS_ERROR_NOT_INITIALIZED;
}
if (aRemoveFiles) {
// Check if another instance is using this profile.
@ -160,15 +212,29 @@ nsresult nsToolkitProfile::RemoveInternal(bool aRemoveFiles,
}
}
if (mPrev)
mPrev->mNext = mNext;
else
nsToolkitProfileService::gService->mFirst = mNext;
nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB;
db->DeleteSection(mSection.get());
if (mNext) mNext->mPrev = mPrev;
// We make some assumptions that the profile's index in the database is based
// on its position in the linked list. Removing a profile means we have to fix
// the index of later profiles in the list. The easiest way to do that is just
// to move the last profile into the profile's position and just update its
// index.
RefPtr<nsToolkitProfile> last =
nsToolkitProfileService::gService->mProfiles.getLast();
if (last != this) {
// Update the section in the db.
last->mIndex = mIndex;
db->RenameSection(last->mSection.get(), mSection.get());
last->mSection = mSection;
mPrev = nullptr;
mNext = nullptr;
if (last != getNext()) {
last->remove();
setNext(last);
}
}
remove();
if (nsToolkitProfileService::gService->mNormalDefault == this) {
nsToolkitProfileService::gService->mNormalDefault = nullptr;
@ -313,7 +379,10 @@ nsToolkitProfileService::nsToolkitProfileService()
gService = this;
}
nsToolkitProfileService::~nsToolkitProfileService() { gService = nullptr; }
nsToolkitProfileService::~nsToolkitProfileService() {
gService = nullptr;
mProfiles.clear();
}
void nsToolkitProfileService::CompleteStartup() {
if (!mStartupProfileSelected) {
@ -335,7 +404,7 @@ void nsToolkitProfileService::CompleteStartup() {
NS_ENSURE_SUCCESS_VOID(rv);
if (isDefaultApp) {
mInstallData.SetString(mInstallHash.get(), "Locked", "1");
mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
Flush();
}
}
@ -440,7 +509,7 @@ bool nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile(
const nsCString& install = installs[i];
nsCString path;
rv = mInstallData.GetString(install.get(), "Default", path);
rv = mProfileDB.GetString(install.get(), "Default", path);
if (NS_FAILED(rv)) {
continue;
}
@ -452,7 +521,7 @@ bool nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile(
// Is this profile locked to this other install?
nsCString isLocked;
rv = mInstallData.GetString(install.get(), "Locked", isLocked);
rv = mProfileDB.GetString(install.get(), "Locked", isLocked);
if (NS_SUCCEEDED(rv) && isLocked.Equals("1")) {
return false;
}
@ -465,7 +534,7 @@ bool nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile(
for (uint32_t i = 0; i < inUseInstalls.Length(); i++) {
// Removing the default setting entirely will make the install go through
// the first run process again at startup and create itself a new profile.
mInstallData.DeleteString(inUseInstalls[i].get(), "Default");
mProfileDB.DeleteString(inUseInstalls[i].get(), "Default");
}
// Set this as the default profile for this install.
@ -474,7 +543,7 @@ bool nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile(
// SetDefaultProfile will have locked this profile to this install so no
// other installs will steal it, but this was auto-selected so we want to
// unlock it so that other installs can potentially take it.
mInstallData.DeleteString(mInstallHash.get(), "Locked");
mProfileDB.DeleteString(mInstallSection.get(), "Locked");
// Persist the changes.
Flush();
@ -486,6 +555,33 @@ bool nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile(
return true;
}
struct ImportInstallsClosure {
nsINIParser* backupData;
nsINIParser* profileDB;
};
static bool ImportInstalls(const char* aSection, void* aClosure) {
ImportInstallsClosure* closure =
static_cast<ImportInstallsClosure*>(aClosure);
nsTArray<UniquePtr<KeyValue>> strings =
GetSectionStrings(closure->backupData, aSection);
if (strings.IsEmpty()) {
return true;
}
nsCString newSection(INSTALL_PREFIX);
newSection.Append(aSection);
nsCString buffer;
for (uint32_t i = 0; i < strings.Length(); i++) {
closure->profileDB->SetString(newSection.get(), strings[i]->key.get(),
strings[i]->value.get());
}
return true;
}
nsresult nsToolkitProfileService::Init() {
NS_ASSERTION(gDirServiceProvider, "No dirserviceprovider!");
nsresult rv;
@ -496,54 +592,76 @@ nsresult nsToolkitProfileService::Init() {
rv = nsXREDirProvider::GetUserLocalDataDirectory(getter_AddRefs(mTempData));
NS_ENSURE_SUCCESS(rv, rv);
nsCString installProfilePath;
if (mUseDedicatedProfile) {
// Load the dedicated profiles database.
rv = mAppData->Clone(getter_AddRefs(mInstallFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = mInstallFile->AppendNative(NS_LITERAL_CSTRING("installs.ini"));
NS_ENSURE_SUCCESS(rv, rv);
nsString installHash;
rv = gDirServiceProvider->GetInstallHash(installHash);
NS_ENSURE_SUCCESS(rv, rv);
CopyUTF16toUTF8(installHash, mInstallHash);
rv = mInstallData.Init(mInstallFile);
if (NS_SUCCEEDED(rv)) {
// Try to find the descriptor for the default profile for this install.
rv = mInstallData.GetString(mInstallHash.get(), "Default",
installProfilePath);
// Not having a value means this install doesn't appear in installs.ini so
// this is the first run for this install.
mIsFirstRun = NS_FAILED(rv);
}
}
rv = mAppData->Clone(getter_AddRefs(mListFile));
rv = mAppData->Clone(getter_AddRefs(mProfileDBFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = mListFile->AppendNative(NS_LITERAL_CSTRING("profiles.ini"));
rv = mProfileDBFile->AppendNative(NS_LITERAL_CSTRING("profiles.ini"));
NS_ENSURE_SUCCESS(rv, rv);
nsINIParser parser;
rv = mAppData->Clone(getter_AddRefs(mInstallDBFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = mInstallDBFile->AppendNative(NS_LITERAL_CSTRING("installs.ini"));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString buffer;
bool exists;
rv = mListFile->IsFile(&exists);
rv = mProfileDBFile->IsFile(&exists);
if (NS_SUCCEEDED(rv) && exists) {
rv = parser.Init(mListFile);
rv = mProfileDB.Init(mProfileDBFile);
// Init does not fail on parsing errors, only on OOM/really unexpected
// conditions.
if (NS_FAILED(rv)) {
return rv;
}
rv = mProfileDB.GetString("General", "StartWithLastProfile", buffer);
if (NS_SUCCEEDED(rv)) {
mStartWithLast = !buffer.EqualsLiteral("0");
}
rv = mProfileDB.GetString("General", "Version", buffer);
if (NS_FAILED(rv)) {
// This is a profiles.ini written by an older version. We must restore
// any install data from the backup.
nsINIParser installDB;
rv = mInstallDBFile->IsFile(&exists);
if (NS_SUCCEEDED(rv) && exists &&
NS_SUCCEEDED(installDB.Init(mInstallDBFile))) {
// There is install data to import.
ImportInstallsClosure closure = {&installDB, &mProfileDB};
installDB.GetSections(&ImportInstalls, &closure);
}
rv = mProfileDB.SetString("General", "Version", PROFILE_DB_VERSION);
NS_ENSURE_SUCCESS(rv, rv);
}
} else {
rv = mProfileDB.SetString("General", "StartWithLastProfile",
mStartWithLast ? "1" : "0");
NS_ENSURE_SUCCESS(rv, rv);
rv = mProfileDB.SetString("General", "Version", PROFILE_DB_VERSION);
NS_ENSURE_SUCCESS(rv, rv);
}
nsAutoCString buffer;
rv = parser.GetString("General", "StartWithLastProfile", buffer);
if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("0")) mStartWithLast = false;
nsCString installProfilePath;
if (mUseDedicatedProfile) {
nsString installHash;
rv = gDirServiceProvider->GetInstallHash(installHash);
NS_ENSURE_SUCCESS(rv, rv);
CopyUTF16toUTF8(installHash, mInstallSection);
mInstallSection.Insert(INSTALL_PREFIX, 0);
// Try to find the descriptor for the default profile for this install.
rv = mProfileDB.GetString(mInstallSection.get(), "Default",
installProfilePath);
// Not having a value means this install doesn't appear in installs.ini so
// this is the first run for this install.
mIsFirstRun = NS_FAILED(rv);
}
nsToolkitProfile* currentProfile = nullptr;
@ -575,14 +693,14 @@ nsresult nsToolkitProfileService::Init() {
nsAutoCString profileID("Profile");
profileID.AppendInt(c);
rv = parser.GetString(profileID.get(), "IsRelative", buffer);
rv = mProfileDB.GetString(profileID.get(), "IsRelative", buffer);
if (NS_FAILED(rv)) break;
bool isRelative = buffer.EqualsLiteral("1");
nsAutoCString filePath;
rv = parser.GetString(profileID.get(), "Path", filePath);
rv = mProfileDB.GetString(profileID.get(), "Path", filePath);
if (NS_FAILED(rv)) {
NS_ERROR("Malformed profiles.ini: Path= not found");
continue;
@ -590,7 +708,7 @@ nsresult nsToolkitProfileService::Init() {
nsAutoCString name;
rv = parser.GetString(profileID.get(), "Name", name);
rv = mProfileDB.GetString(profileID.get(), "Name", name);
if (NS_FAILED(rv)) {
NS_ERROR("Malformed profiles.ini: Name= not found");
continue;
@ -618,11 +736,10 @@ nsresult nsToolkitProfileService::Init() {
localDir = rootDir;
}
currentProfile =
new nsToolkitProfile(name, rootDir, localDir, currentProfile);
currentProfile = new nsToolkitProfile(name, rootDir, localDir, true);
NS_ENSURE_TRUE(currentProfile, NS_ERROR_OUT_OF_MEMORY);
rv = parser.GetString(profileID.get(), "Default", buffer);
rv = mProfileDB.GetString(profileID.get(), "Default", buffer);
if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("1")) {
mNormalDefault = currentProfile;
}
@ -644,7 +761,7 @@ nsresult nsToolkitProfileService::Init() {
// If there is only one non-dev-edition profile then mark it as the default.
if (!mNormalDefault && nonDevEditionProfiles == 1) {
mNormalDefault = autoSelectProfile;
SetNormalDefault(autoSelectProfile);
}
if (!mUseDedicatedProfile) {
@ -664,6 +781,9 @@ nsresult nsToolkitProfileService::Init() {
NS_IMETHODIMP
nsToolkitProfileService::SetStartWithLastProfile(bool aValue) {
if (mStartWithLast != aValue) {
nsresult rv = mProfileDB.SetString("General", "StartWithLastProfile",
mStartWithLast ? "1" : "0");
NS_ENSURE_SUCCESS(rv, rv);
mStartWithLast = aValue;
}
return NS_OK;
@ -677,7 +797,7 @@ nsToolkitProfileService::GetStartWithLastProfile(bool* aResult) {
NS_IMETHODIMP
nsToolkitProfileService::GetProfiles(nsISimpleEnumerator** aResult) {
*aResult = new ProfileEnumerator(this->mFirst);
*aResult = new ProfileEnumerator(mProfiles.getFirst());
if (!*aResult) return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*aResult);
@ -696,7 +816,7 @@ nsToolkitProfileService::ProfileEnumerator::GetNext(nsISupports** aResult) {
NS_ADDREF(*aResult = mCurrent);
mCurrent = mCurrent->mNext;
mCurrent = mCurrent->getNext();
return NS_OK;
}
@ -722,6 +842,26 @@ nsToolkitProfileService::GetDefaultProfile(nsIToolkitProfile** aResult) {
return NS_OK;
}
void nsToolkitProfileService::SetNormalDefault(nsIToolkitProfile* aProfile) {
if (mNormalDefault == aProfile) {
return;
}
if (mNormalDefault) {
nsToolkitProfile* profile =
static_cast<nsToolkitProfile*>(mNormalDefault.get());
mProfileDB.DeleteString(profile->mSection.get(), "Default");
}
mNormalDefault = aProfile;
if (mNormalDefault) {
nsToolkitProfile* profile =
static_cast<nsToolkitProfile*>(mNormalDefault.get());
mProfileDB.SetString(profile->mSection.get(), "Default", "1");
}
}
NS_IMETHODIMP
nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile) {
if (mUseDedicatedProfile) {
@ -730,20 +870,20 @@ nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile) {
// Setting this to the empty string means no profile will be found on
// startup but we'll recognise that this install has been used
// previously.
mInstallData.SetString(mInstallHash.get(), "Default", "");
mProfileDB.SetString(mInstallSection.get(), "Default", "");
} else {
nsCString profilePath;
nsresult rv = GetProfileDescriptor(aProfile, profilePath, nullptr);
NS_ENSURE_SUCCESS(rv, rv);
mInstallData.SetString(mInstallHash.get(), "Default",
profilePath.get());
mProfileDB.SetString(mInstallSection.get(), "Default",
profilePath.get());
}
mDedicatedProfile = aProfile;
// Some kind of choice has happened here, lock this profile to this
// install.
mInstallData.SetString(mInstallHash.get(), "Locked", "1");
mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
}
return NS_OK;
}
@ -753,7 +893,8 @@ nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile) {
return NS_ERROR_FAILURE;
}
mNormalDefault = aProfile;
SetNormalDefault(aProfile);
return NS_OK;
}
@ -813,7 +954,7 @@ nsresult nsToolkitProfileService::CreateDefaultProfile(
} else if (mUseDevEditionProfile) {
mDevEditionDefault = mCurrent;
} else {
mNormalDefault = mCurrent;
SetNormalDefault(mCurrent);
}
return NS_OK;
@ -842,9 +983,9 @@ nsToolkitProfileService::SelectStartupProfile(
argv[argc] = nullptr;
bool wasDefault;
nsresult rv = SelectStartupProfile(&argc, argv.get(), aIsResetting, aRootDir,
aLocalDir, aProfile, aDidCreate,
&wasDefault);
nsresult rv =
SelectStartupProfile(&argc, argv.get(), aIsResetting, aRootDir, aLocalDir,
aProfile, aDidCreate, &wasDefault);
// Since we were called outside of the normal startup path complete any
// startup tasks.
@ -1163,10 +1304,12 @@ nsresult nsToolkitProfileService::SelectStartupProfile(
// older versions of Firefox use then we must create a default profile
// for older versions of Firefox to avoid the existing profile being
// auto-selected.
if ((mUseDedicatedProfile || mUseDevEditionProfile) && mFirst &&
!mFirst->mNext) {
if ((mUseDedicatedProfile || mUseDevEditionProfile) &&
mProfiles.getFirst() == mProfiles.getLast()) {
nsCOMPtr<nsIToolkitProfile> newProfile;
CreateProfile(nullptr, NS_LITERAL_CSTRING(DEFAULT_NAME),
getter_AddRefs(mNormalDefault));
getter_AddRefs(newProfile));
SetNormalDefault(newProfile);
}
Flush();
@ -1249,14 +1392,14 @@ nsresult nsToolkitProfileService::ApplyResetProfile(
// If the old profile would have been the default for old installs then mark
// the new profile as such.
if (mNormalDefault == aOldProfile) {
mNormalDefault = mCurrent;
SetNormalDefault(mCurrent);
}
if (mUseDedicatedProfile && mDedicatedProfile == aOldProfile) {
bool wasLocked = false;
nsCString val;
if (NS_SUCCEEDED(
mInstallData.GetString(mInstallHash.get(), "Locked", val))) {
mProfileDB.GetString(mInstallSection.get(), "Locked", val))) {
wasLocked = val.Equals("1");
}
@ -1264,7 +1407,7 @@ nsresult nsToolkitProfileService::ApplyResetProfile(
// Make the locked state match if necessary.
if (!wasLocked) {
mInstallData.DeleteString(mInstallHash.get(), "Locked");
mProfileDB.DeleteString(mInstallSection.get(), "Locked");
}
}
@ -1286,13 +1429,11 @@ nsresult nsToolkitProfileService::ApplyResetProfile(
NS_IMETHODIMP
nsToolkitProfileService::GetProfileByName(const nsACString& aName,
nsIToolkitProfile** aResult) {
nsToolkitProfile* curP = mFirst;
while (curP) {
if (curP->mName.Equals(aName)) {
NS_ADDREF(*aResult = curP);
for (RefPtr<nsToolkitProfile> profile : mProfiles) {
if (profile->mName.Equals(aName)) {
NS_ADDREF(*aResult = profile);
return NS_OK;
}
curP = curP->mNext;
}
return NS_ERROR_FAILURE;
@ -1305,18 +1446,16 @@ nsToolkitProfileService::GetProfileByName(const nsACString& aName,
void nsToolkitProfileService::GetProfileByDir(nsIFile* aRootDir,
nsIFile* aLocalDir,
nsIToolkitProfile** aResult) {
nsToolkitProfile* curP = mFirst;
while (curP) {
for (RefPtr<nsToolkitProfile> profile : mProfiles) {
bool equal;
nsresult rv = curP->mRootDir->Equals(aRootDir, &equal);
nsresult rv = profile->mRootDir->Equals(aRootDir, &equal);
if (NS_SUCCEEDED(rv) && equal) {
rv = curP->mLocalDir->Equals(aLocalDir, &equal);
rv = profile->mLocalDir->Equals(aLocalDir, &equal);
if (NS_SUCCEEDED(rv) && equal) {
NS_ADDREF(*aResult = curP);
NS_ADDREF(*aResult = profile);
return;
}
}
curP = curP->mNext;
}
}
@ -1449,15 +1588,8 @@ nsToolkitProfileService::CreateProfile(nsIFile* aRootDir,
rv = CreateTimesInternal(rootDir);
NS_ENSURE_SUCCESS(rv, rv);
nsToolkitProfile* last = mFirst.get();
if (last) {
while (last->mNext) {
last = last->mNext;
}
}
nsCOMPtr<nsIToolkitProfile> profile =
new nsToolkitProfile(aName, rootDir, localDir, last);
new nsToolkitProfile(aName, rootDir, localDir, false);
if (!profile) return NS_ERROR_OUT_OF_MEMORY;
if (aName.Equals(DEV_EDITION_NAME)) {
@ -1496,6 +1628,11 @@ struct FindInstallsClosure {
static bool FindInstalls(const char* aSection, void* aClosure) {
FindInstallsClosure* closure = static_cast<FindInstallsClosure*>(aClosure);
// Check if the section starts with "Install"
if (strncmp(aSection, INSTALL_PREFIX, INSTALL_PREFIX_LENGTH) != 0) {
return true;
}
nsCString install(aSection);
closure->installs->AppendElement(install);
@ -1504,9 +1641,9 @@ static bool FindInstalls(const char* aSection, void* aClosure) {
nsTArray<nsCString> nsToolkitProfileService::GetKnownInstalls() {
nsTArray<nsCString> result;
FindInstallsClosure closure = {&mInstallData, &result};
FindInstallsClosure closure = {&mProfileDB, &result};
mInstallData.GetSections(&FindInstalls, &closure);
mProfileDB.GetSections(&FindInstalls, &closure);
return result;
}
@ -1545,10 +1682,8 @@ nsresult nsToolkitProfileService::CreateTimesInternal(nsIFile* aProfileDir) {
NS_IMETHODIMP
nsToolkitProfileService::GetProfileCount(uint32_t* aResult) {
*aResult = 0;
nsToolkitProfile* profile = mFirst;
while (profile) {
for (RefPtr<nsToolkitProfile> profile : mProfiles) {
(*aResult)++;
profile = profile->mNext;
}
return NS_OK;
@ -1558,71 +1693,59 @@ NS_IMETHODIMP
nsToolkitProfileService::Flush() {
nsresult rv;
// If we aren't using dedicated profiles then nothing about the list of
// installs can have changed, so no need to update the backup.
if (mUseDedicatedProfile) {
rv = mInstallData.WriteToFile(mInstallFile);
NS_ENSURE_SUCCESS(rv, rv);
}
// Export the installs to the backup.
nsTArray<nsCString> installs = GetKnownInstalls();
// Errors during writing might cause unhappy semi-written files.
// To avoid this, write the entire thing to a buffer, then write
// that buffer to disk.
if (!installs.IsEmpty()) {
nsCString data;
nsCString buffer;
uint32_t pCount = 0;
nsToolkitProfile* cur;
for (uint32_t i = 0; i < installs.Length(); i++) {
nsTArray<UniquePtr<KeyValue>> strings =
GetSectionStrings(&mProfileDB, installs[i].get());
if (strings.IsEmpty()) {
continue;
}
for (cur = mFirst; cur != nullptr; cur = cur->mNext) ++pCount;
// Strip "Install" from the start.
const nsDependentCSubstring& install =
Substring(installs[i], INSTALL_PREFIX_LENGTH);
data.AppendPrintf("[%s]\n", PromiseFlatCString(install).get());
uint32_t length;
const int bufsize = 100 + MAXPATHLEN * pCount;
auto buffer = MakeUnique<char[]>(bufsize);
for (uint32_t j = 0; j < strings.Length(); j++) {
data.AppendPrintf("%s=%s\n", strings[j]->key.get(),
strings[j]->value.get());
}
char* pos = buffer.get();
char* end = pos + bufsize;
data.Append("\n");
}
pos += snprintf(pos, end - pos,
"[General]\n"
"StartWithLastProfile=%s\n\n",
mStartWithLast ? "1" : "0");
FILE* writeFile;
rv = mInstallDBFile->OpenANSIFileDesc("w", &writeFile);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString path;
cur = mFirst;
pCount = 0;
uint32_t length = data.Length();
if (fwrite(data.get(), sizeof(char), length, writeFile) != length) {
fclose(writeFile);
return NS_ERROR_UNEXPECTED;
}
while (cur) {
bool isRelative;
nsresult rv = GetProfileDescriptor(cur, path, &isRelative);
NS_ENSURE_SUCCESS(rv, rv);
pos +=
snprintf(pos, end - pos,
"[Profile%u]\n"
"Name=%s\n"
"IsRelative=%s\n"
"Path=%s\n",
pCount, cur->mName.get(), isRelative ? "1" : "0", path.get());
if (cur == mNormalDefault) {
pos += snprintf(pos, end - pos, "Default=1\n");
fclose(writeFile);
} else {
rv = mInstallDBFile->Remove(false);
if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
rv != NS_ERROR_FILE_NOT_FOUND) {
return rv;
}
}
pos += snprintf(pos, end - pos, "\n");
cur = cur->mNext;
++pCount;
}
FILE* writeFile;
rv = mListFile->OpenANSIFileDesc("w", &writeFile);
rv = mProfileDB.WriteToFile(mProfileDBFile);
NS_ENSURE_SUCCESS(rv, rv);
length = pos - buffer.get();
if (fwrite(buffer.get(), sizeof(char), length, writeFile) != length) {
fclose(writeFile);
return NS_ERROR_UNEXPECTED;
}
fclose(writeFile);
return NS_OK;
}

Просмотреть файл

@ -15,20 +15,20 @@
#include "nsProfileLock.h"
#include "nsINIParser.h"
class nsToolkitProfile final : public nsIToolkitProfile {
class nsToolkitProfile final
: public nsIToolkitProfile,
public mozilla::LinkedListElement<RefPtr<nsToolkitProfile>> {
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);
nsIFile* aLocalDir, bool aFromDB);
nsresult RemoveInternal(bool aRemoveFiles, bool aInBackground);
@ -38,6 +38,8 @@ class nsToolkitProfile final : public nsIToolkitProfile {
nsCOMPtr<nsIFile> mRootDir;
nsCOMPtr<nsIFile> mLocalDir;
nsIProfileLock* mLock;
uint32_t mIndex;
nsCString mSection;
};
class nsToolkitProfileLock final : public nsIProfileLock {
@ -103,6 +105,7 @@ class nsToolkitProfileService final : public nsIToolkitProfileService {
bool MaybeMakeDefaultDedicatedProfile(nsIToolkitProfile* aProfile);
bool IsSnapEnvironment();
nsresult CreateDefaultProfile(nsIToolkitProfile** aResult);
void SetNormalDefault(nsIToolkitProfile* aProfile);
// Returns the known install hashes from the installs database. Modifying the
// installs database is safe while iterating the returned array.
@ -110,8 +113,8 @@ class nsToolkitProfileService final : public nsIToolkitProfileService {
// Tracks whether SelectStartupProfile has been called.
bool mStartupProfileSelected;
// The first profile in a linked list of profiles loaded from profiles.ini.
RefPtr<nsToolkitProfile> mFirst;
// The profiles loaded from profiles.ini.
mozilla::LinkedList<RefPtr<nsToolkitProfile>> mProfiles;
// The profile selected for use at startup, if it exists in profiles.ini.
nsCOMPtr<nsIToolkitProfile> mCurrent;
// The profile selected for this install in installs.ini.
@ -126,13 +129,13 @@ class nsToolkitProfileService final : public nsIToolkitProfileService {
// The directory that holds the cache files for profiles.
nsCOMPtr<nsIFile> mTempData;
// The location of profiles.ini.
nsCOMPtr<nsIFile> mListFile;
nsCOMPtr<nsIFile> mProfileDBFile;
// The location of installs.ini.
nsCOMPtr<nsIFile> mInstallFile;
// The data loaded from installs.ini.
nsINIParser mInstallData;
// The install hash for the currently running install.
nsCString mInstallHash;
nsCOMPtr<nsIFile> mInstallDBFile;
// The data loaded from profiles.ini.
nsINIParser mProfileDB;
// The section in the profiles db for the current install.
nsCString mInstallSection;
// Whether to start with the selected profile by default.
bool mStartWithLast;
// True if during startup it appeared that this is the first run.

Просмотреть файл

@ -169,7 +169,7 @@ function writeProfilesIni(profileData) {
getService(Ci.nsIINIParserFactory);
let ini = factory.createINIParser().QueryInterface(Ci.nsIINIParserWriter);
const { options = {}, profiles = [] } = profileData;
const { options = {}, profiles = [], installs = null } = profileData;
let { startWithLastProfile = true } = options;
ini.setString("General", "StartWithLastProfile", startWithLastProfile ? "1" : "0");
@ -187,6 +187,21 @@ function writeProfilesIni(profileData) {
}
}
if (installs) {
ini.setString("General", "Version", "2");
for (let hash of Object.keys(installs)) {
ini.setString(`Install${hash}`, "Default", installs[hash].default);
if ("locked" in installs[hash]) {
ini.setString(`Install${hash}`, "Locked", installs[hash].locked ? "1" : "0");
}
}
writeInstallsIni({ installs });
} else {
writeInstallsIni(null);
}
ini.writeFile(target);
}
@ -205,6 +220,7 @@ function readProfilesIni() {
startWithLastProfile: true,
},
profiles: [],
installs: null,
};
if (!target.exists()) {
@ -216,29 +232,52 @@ function readProfilesIni() {
let ini = factory.createINIParser(target);
profileData.options.startWithLastProfile = safeGet(ini, "General", "StartWithLastProfile") == "1";
if (safeGet(ini, "General", "Version") == "2") {
profileData.installs = {};
}
for (let i = 0; true; i++) {
let section = `Profile${i}`;
let sections = ini.getSections();
while (sections.hasMore()) {
let section = sections.getNext();
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;
if (section == "General") {
continue;
}
profileData.profiles.push(profile);
if (section.startsWith("Profile")) {
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);
}
if (section.startsWith("Install")) {
Assert.ok(profileData.installs, "Should only see an install section if the ini version was correct.");
profileData.installs[section.substring(7)] = {
default: safeGet(ini, section, "Default"),
};
let locked = safeGet(ini, section, "Locked");
if (locked !== null) {
profileData.installs[section.substring(7)].locked = locked;
}
}
}
profileData.profiles.sort((a, b) => a.name.localeCompare(b.name));
@ -255,6 +294,14 @@ function writeInstallsIni(installData) {
let target = gDataHome.clone();
target.append("installs.ini");
if (!installData) {
try {
target.remove(false);
} catch (e) {
}
return;
}
const { installs = {} } = installData;
let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
@ -283,6 +330,7 @@ function readInstallsIni() {
};
if (!target.exists()) {
dump("Missing installs.ini\n");
return installData;
}
@ -296,19 +344,37 @@ function readInstallsIni() {
if (hash != "General") {
installData.installs[hash] = {
default: safeGet(ini, hash, "Default"),
locked: safeGet(ini, hash, "Locked") == 1,
};
let locked = safeGet(ini, hash, "Locked");
if (locked !== null) {
installData.installs[hash].locked = locked;
}
}
}
return installData;
}
/**
* Check that the backup data in installs.ini matches the install data in
* profiles.ini.
*/
function checkBackup(profileData = readProfilesIni(), installData = readInstallsIni()) {
if (!profileData.installs) {
// If the profiles db isn't of the right version we wouldn't expect the
// backup to be accurate.
return;
}
Assert.deepEqual(profileData.installs, installData.installs, "Backup installs.ini should match installs in profiles.ini");
}
/**
* 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(), installData = readInstallsIni()) {
function checkProfileService(profileData = readProfilesIni(), verifyBackup = true) {
let service = getProfileService();
let serviceProfiles = Array.from(service.profiles);
@ -320,7 +386,8 @@ function checkProfileService(profileData = readProfilesIni(), installData = read
profileData.profiles.sort((a, b) => a.name.localeCompare(b.name));
let hash = xreDirProvider.getInstallHash();
let defaultPath = hash in installData.installs ? installData.installs[hash].default : null;
let defaultPath = (profileData.installs && hash in profileData.installs) ?
profileData.installs[hash].default : null;
let dedicatedProfile = null;
let snapProfile = null;
@ -352,6 +419,10 @@ function checkProfileService(profileData = readProfilesIni(), installData = read
} else {
Assert.equal(service.defaultProfile, dedicatedProfile, "Should have seen the right profile selected.");
}
if (verifyBackup) {
checkBackup(profileData);
}
}
/**

Просмотреть файл

@ -0,0 +1,41 @@
/*
* Tests that when the profiles DB is missing the install data we reload it.
*/
add_task(async () => {
let hash = xreDirProvider.getInstallHash();
let profileData = {
options: {
startWithLastProfile: true,
},
profiles: [{
name: "Profile1",
path: "Path1",
}, {
name: "Profile2",
path: "Path2",
}],
};
let installs = {
[hash]: {
default: "Path2",
},
};
writeProfilesIni(profileData);
writeInstallsIni({ installs });
let { profile, didCreate } = selectStartupProfile();
checkStartupReason("default");
let service = getProfileService();
// Should have added the backup data to the service, check that is true.
profileData.installs = installs;
checkProfileService(profileData);
Assert.ok(!didCreate, "Should not have created a new profile.");
Assert.equal(profile.name, "Profile2", "Should have selected the right profile");
Assert.ok(!service.createdAlternateProfile, "Should not have created an alternate profile.");
});

Просмотреть файл

@ -14,10 +14,6 @@ add_task(async () => {
path: defaultProfile.leafName,
default: true,
}],
});
let hash = xreDirProvider.getInstallHash();
writeProfilesIni({
installs: {
other: {
default: defaultProfile.leafName,
@ -29,16 +25,16 @@ add_task(async () => {
let { profile: selectedProfile, didCreate } = selectStartupProfile();
let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 2, "Should have the right number of profiles.");
Assert.equal(Object.keys(installData.installs).length, 1, "Should be two known installs.");
Assert.notEqual(installData.installs[hash].default, defaultProfile.leafName, "Should not have marked the original default profile as the default for this install.");
Assert.ok(installData.installs[hash].locked, "Should have locked as we created this profile for this install.");
let hash = xreDirProvider.getInstallHash();
Assert.equal(Object.keys(profileData.installs).length, 2, "Should be two known installs.");
Assert.notEqual(profileData.installs[hash].default, defaultProfile.leafName, "Should not have marked the original default profile as the default for this install.");
Assert.ok(profileData.installs[hash].locked, "Should have locked as we created this profile for this install.");
checkProfileService(profileData, installData);
checkProfileService(profileData);
Assert.ok(didCreate, "Should have created a new profile.");
Assert.ok(!selectedProfile.rootDir.equals(defaultProfile), "Should be using a different directory.");

Просмотреть файл

@ -18,7 +18,6 @@ add_task(async () => {
service.flush();
let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
@ -28,15 +27,14 @@ add_task(async () => {
Assert.ok(!profile.default, "Should not be marked as the old-style default.");
// The new profile hasn't been marked as the default yet!
Assert.equal(Object.keys(installData.installs).length, 0, "Should be no defaults for installs yet.");
Assert.equal(Object.keys(profileData.installs).length, 0, "Should be no defaults for installs yet.");
checkProfileService(profileData, installData);
checkProfileService(profileData);
service.defaultProfile = newProfile;
service.flush();
profileData = readProfilesIni();
installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
@ -46,10 +44,10 @@ add_task(async () => {
Assert.ok(!profile.default, "Should not be marked as the old-style default.");
let hash = xreDirProvider.getInstallHash();
Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install.");
Assert.equal(installData.installs[hash].default, profileData.profiles[0].path, "Should have marked the new profile as the default for this install.");
Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
Assert.equal(profileData.installs[hash].default, profileData.profiles[0].path, "Should have marked the new profile as the default for this install.");
checkProfileService(profileData, installData);
checkProfileService(profileData);
let otherProfile = service.createProfile(null, "another");
service.defaultProfile = otherProfile;
@ -57,7 +55,6 @@ add_task(async () => {
service.flush();
profileData = readProfilesIni();
installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 2, "Should have the right number of profiles.");
@ -70,16 +67,15 @@ add_task(async () => {
Assert.equal(profile.name, "dedicated", "Should have the right name.");
Assert.ok(!profile.default, "Should not be marked as the old-style default.");
Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install.");
Assert.equal(installData.installs[hash].default, profileData.profiles[0].path, "Should have marked the new profile as the default for this install.");
Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
Assert.equal(profileData.installs[hash].default, profileData.profiles[0].path, "Should have marked the new profile as the default for this install.");
checkProfileService(profileData, installData);
checkProfileService(profileData);
newProfile.remove(true);
service.flush();
profileData = readProfilesIni();
installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
@ -88,23 +84,22 @@ add_task(async () => {
Assert.equal(profile.name, "another", "Should have the right name.");
Assert.ok(!profile.default, "Should not be marked as the old-style default.");
Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install.");
Assert.equal(installData.installs[hash].default, profileData.profiles[0].path, "Should have marked the new profile as the default for this install.");
Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
Assert.equal(profileData.installs[hash].default, profileData.profiles[0].path, "Should have marked the new profile as the default for this install.");
checkProfileService(profileData, installData);
checkProfileService(profileData);
otherProfile.remove(true);
service.flush();
profileData = readProfilesIni();
installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 0, "Should have the right number of profiles.");
// We leave a reference to the missing profile to stop us trying to steal the
// old-style default profile on next startup.
Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install.");
Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
checkProfileService(profileData, installData);
checkProfileService(profileData);
});

Просмотреть файл

@ -9,8 +9,7 @@ add_task(async () => {
checkStartupReason("firstrun-created-default");
let profileData = readProfilesIni();
let installData = readInstallsIni();
checkProfileService(profileData, installData);
checkProfileService(profileData);
Assert.ok(didCreate, "Should have created a new profile.");
Assert.equal(profile, service.defaultProfile, "Should now be the default profile.");
@ -29,5 +28,5 @@ add_task(async () => {
Assert.ok(!profile.default, "Should not be marked as the old-style default.");
let hash = xreDirProvider.getInstallHash();
Assert.ok(installData.installs[hash].locked, "Should have locked the profile");
Assert.ok(profileData.installs[hash].locked, "Should have locked the profile");
});

Просмотреть файл

@ -21,7 +21,6 @@ add_task(async () => {
let hash = xreDirProvider.getInstallHash();
let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
@ -31,11 +30,11 @@ add_task(async () => {
Assert.equal(profile.path, defaultProfile.leafName, "Should be the original default profile.");
Assert.ok(profile.default, "Should be marked as the old-style default.");
Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install.");
Assert.equal(installData.installs[hash].default, defaultProfile.leafName, "Should have marked the original default profile as the default for this install.");
Assert.ok(installData.installs[hash].locked, "Should have locked as we're the default app.");
Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
Assert.equal(profileData.installs[hash].default, defaultProfile.leafName, "Should have marked the original default profile as the default for this install.");
Assert.ok(profileData.installs[hash].locked, "Should have locked as we're the default app.");
checkProfileService(profileData, installData);
checkProfileService(profileData);
Assert.ok(!didCreate, "Should not have created a new profile.");
Assert.ok(selectedProfile.rootDir.equals(defaultProfile), "Should be using the right directory.");

Просмотреть файл

@ -0,0 +1,37 @@
/**
* When profiles.ini is missing there isn't any point in restoring from any
* installs.ini, the profiles it refers to are gone anyway.
*/
add_task(async () => {
let hash = xreDirProvider.getInstallHash();
let installs = {
[hash]: {
default: "Path2",
},
otherhash: {
default: "foo",
},
anotherhash: {
default: "bar",
},
};
writeInstallsIni({ installs });
let { profile, didCreate } = selectStartupProfile();
checkStartupReason("firstrun-created-default");
let service = getProfileService();
Assert.ok(didCreate, "Should have created a new profile.");
Assert.equal(profile.name, DEDICATED_NAME, "Should have created the right profile");
Assert.ok(!service.createdAlternateProfile, "Should not have created an alternate profile.");
let profilesData = readProfilesIni();
Assert.equal(Object.keys(profilesData.installs).length, 1, "Should be only one known install");
Assert.ok(hash in profilesData.installs, "Should be the expected install.");
Assert.notEqual(profilesData.installs[hash].default, "Path2", "Didn't import the previous data.");
checkProfileService(profilesData);
});

Просмотреть файл

@ -31,7 +31,6 @@ add_task(async () => {
let hash = xreDirProvider.getInstallHash();
let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 3, "Should have the right number of profiles.");
@ -51,16 +50,16 @@ add_task(async () => {
Assert.equal(profile.path, mydefaultProfile.leafName, "Should be the original default profile.");
Assert.ok(profile.default, "Should be marked as the old-style default.");
Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install.");
Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
if (AppConstants.MOZ_DEV_EDITION) {
Assert.equal(installData.installs[hash].default, devDefaultProfile.leafName, "Should have marked the original dev default profile as the default for this install.");
Assert.equal(profileData.installs[hash].default, devDefaultProfile.leafName, "Should have marked the original dev default profile as the default for this install.");
} else {
Assert.equal(installData.installs[hash].default, mydefaultProfile.leafName, "Should have marked the original default profile as the default for this install.");
Assert.equal(profileData.installs[hash].default, mydefaultProfile.leafName, "Should have marked the original default profile as the default for this install.");
}
Assert.ok(!installData.installs[hash].locked, "Should not be locked as we're not the default app.");
Assert.ok(!profileData.installs[hash].locked, "Should not be locked as we're not the default app.");
checkProfileService(profileData, installData);
checkProfileService(profileData);
Assert.ok(!didCreate, "Should not have created a new profile.");
if (AppConstants.MOZ_DEV_EDITION) {

Просмотреть файл

@ -16,9 +16,6 @@ add_task(async () => {
path: defaultProfile.leafName,
default: true,
}],
});
writeInstallsIni({
installs: {
[hash]: {
default: "foobar",
@ -30,7 +27,6 @@ add_task(async () => {
testStartsProfileManager();
let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
@ -42,7 +38,7 @@ add_task(async () => {
Assert.ok(profile.default, "Should be marked as the old-style default.");
// We keep the data here so we don't steal on the next reboot...
Assert.equal(Object.keys(installData.installs).length, 1, "Still list the broken reference.");
Assert.equal(Object.keys(profileData.installs).length, 1, "Still list the broken reference.");
checkProfileService(profileData, installData);
checkProfileService(profileData);
});

Просмотреть файл

@ -0,0 +1,87 @@
/*
* Tests adding and removing functions correctly.
*/
function compareLists(service, knownProfiles) {
Assert.equal(service.profileCount, knownProfiles.length, "profileCount should be correct.");
let serviceProfiles = Array.from(service.profiles);
Assert.equal(serviceProfiles.length, knownProfiles.length, "Enumerator length should be correct.");
for (let i = 0; i < knownProfiles.length; i++) {
Assert.strictEqual(serviceProfiles[i], knownProfiles[i], `Should have the right profile in position ${i}.`);
}
}
function removeProfile(profiles, position) {
dump(`Removing profile in position ${position}.`);
Assert.greaterOrEqual(position, 0, "Should be removing a valid position.");
Assert.less(position, profiles.length, "Should be removing a valid position.");
let last = profiles.pop();
if (profiles.length == position) {
// We were asked to remove the last profile.
last.remove(false);
return;
}
profiles[position].remove(false);
profiles[position] = last;
}
add_task(async () => {
let service = getProfileService();
let profiles = [];
compareLists(service, profiles);
profiles.push(service.createProfile(null, "profile1"));
profiles.push(service.createProfile(null, "profile2"));
profiles.push(service.createProfile(null, "profile3"));
profiles.push(service.createProfile(null, "profile4"));
profiles.push(service.createProfile(null, "profile5"));
profiles.push(service.createProfile(null, "profile6"));
profiles.push(service.createProfile(null, "profile7"));
profiles.push(service.createProfile(null, "profile8"));
profiles.push(service.createProfile(null, "profile9"));
compareLists(service, profiles);
// Test removing the first profile.
removeProfile(profiles, 0);
compareLists(service, profiles);
// And the last profile.
removeProfile(profiles, profiles.length - 1);
compareLists(service, profiles);
// Last but one...
removeProfile(profiles, profiles.length - 2);
compareLists(service, profiles);
// Second one...
removeProfile(profiles, 1);
compareLists(service, profiles);
// Something in the middle.
removeProfile(profiles, 2);
compareLists(service, profiles);
let expectedNames = [
"profile9",
"profile7",
"profile5",
"profile4",
];
let serviceProfiles = Array.from(service.profiles);
for (let i = 0; i < expectedNames.length; i++) {
Assert.equal(serviceProfiles[i].name, expectedNames[i]);
}
removeProfile(profiles, 0);
removeProfile(profiles, 0);
removeProfile(profiles, 0);
removeProfile(profiles, 0);
Assert.equal(Array.from(service.profiles).length, 0, "All profiles gone.");
Assert.equal(service.profileCount, 0, "All profiles gone.");
});

Просмотреть файл

@ -13,27 +13,23 @@ add_task(async () => {
path: defaultProfile.leafName,
default: true,
}],
};
writeProfilesIni(profilesIni);
let installsIni = {
installs: {
[hash]: {
default: defaultProfile.leafName,
},
},
};
writeInstallsIni(installsIni);
writeProfilesIni(profilesIni);
let service = getProfileService();
checkProfileService(profilesIni, installsIni);
checkProfileService(profilesIni);
let { profile, didCreate } = selectStartupProfile();
Assert.ok(!didCreate, "Should have not created a new profile.");
Assert.equal(profile.name, "default", "Should have selected the default profile.");
Assert.equal(profile, service.defaultProfile, "Should have selected the default profile.");
checkProfileService(profilesIni, installsIni);
checkProfileService(profilesIni);
// In an actual run of Firefox we wouldn't be able to delete the profile in
// use because it would be locked. But we don't actually lock the profile in
@ -45,9 +41,10 @@ add_task(async () => {
// These are the modifications that should have been made.
profilesIni.profiles.pop();
installsIni.installs[hash].default = "";
profilesIni.installs[hash].default = "";
checkProfileService(profilesIni, installsIni);
// The data isn't flushed to disk so don't check the backup here.
checkProfileService(profilesIni, false);
service.flush();
@ -56,6 +53,6 @@ add_task(async () => {
// checkProfileService doesn't differentiate between a blank default profile
// for the install and a missing install.
let installs = readInstallsIni();
Assert.equal(installs.installs[hash].default, "", "Should be a blank default profile.");
profilesIni = readProfilesIni();
Assert.equal(profilesIni.installs[hash].default, "", "Should be a blank default profile.");
});

Просмотреть файл

@ -16,8 +16,6 @@ add_task(async () => {
name: "Profile3",
path: "Path3",
}],
};
let installData = {
installs: {
[hash]: {
default: "Path2",
@ -43,7 +41,6 @@ add_task(async () => {
}
writeProfilesIni(profileData);
writeInstallsIni(installData);
let { profile, didCreate } = selectStartupProfile();
checkStartupReason("default");

Просмотреть файл

@ -22,7 +22,6 @@ add_task(async () => {
let hash = xreDirProvider.getInstallHash();
let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
@ -32,11 +31,11 @@ add_task(async () => {
Assert.equal(profile.path, defaultProfile.leafName, "Should be the original default profile.");
Assert.ok(profile.default, "Should be marked as the old-style default.");
Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install.");
Assert.equal(installData.installs[hash].default, defaultProfile.leafName, "Should have marked the original default profile as the default for this install.");
Assert.ok(!installData.installs[hash].locked, "Should not have locked as we're not the default app.");
Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
Assert.equal(profileData.installs[hash].default, defaultProfile.leafName, "Should have marked the original default profile as the default for this install.");
Assert.ok(!profileData.installs[hash].locked, "Should not have locked as we're not the default app.");
checkProfileService(profileData, installData);
checkProfileService(profileData);
Assert.ok(!didCreate, "Should not have created a new profile.");
let service = getProfileService();

Просмотреть файл

@ -23,7 +23,6 @@ add_task(async () => {
let service = getProfileService();
let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
@ -33,9 +32,9 @@ add_task(async () => {
Assert.equal(profile.path, defaultProfile.leafName, "Should be the original default profile.");
Assert.ok(!profile.default, "Should not be marked as the old-style default.");
Assert.equal(Object.keys(installData.installs).length, 0, "Should be no defaults for installs yet.");
Assert.ok(!profileData.installs, "Should be no defaults for installs yet.");
checkProfileService(profileData, installData);
checkProfileService(profileData);
let { profile: selectedProfile, didCreate } = selectStartupProfile();
checkStartupReason("firstrun-skipped-default");

Просмотреть файл

@ -26,10 +26,7 @@ add_task(async () => {
name: "Profile3",
path: "Path3",
}],
};
// Another install is using the profile and it is locked.
let installData = {
// Another install is using the profile and it is locked.
installs: {
otherinstall: {
default: root.leafName,
@ -39,8 +36,7 @@ add_task(async () => {
};
writeProfilesIni(profileData);
writeInstallsIni(installData);
checkProfileService(profileData, installData);
checkProfileService(profileData);
let env = Cc["@mozilla.org/process/environment;1"].
getService(Ci.nsIEnvironment);
@ -73,10 +69,9 @@ add_task(async () => {
Assert.ok(!profileData.profiles[1].default, "Should not be the old default profile.");
let hash = xreDirProvider.getInstallHash();
installData = readInstallsIni();
Assert.equal(Object.keys(installData.installs).length, 2, "Should be one known install.");
Assert.notEqual(installData.installs[hash].default, root.leafName, "Should have marked the original default profile as the default for this install.");
Assert.ok(installData.installs[hash].locked, "Should have locked as we created the profile for this install.");
Assert.equal(installData.installs.otherinstall.default, root.leafName, "Should have left the other profile as the default for the other install.");
Assert.ok(installData.installs[hash].locked, "Should still be locked to the other install.");
Assert.equal(Object.keys(profileData.installs).length, 2, "Should be one known install.");
Assert.notEqual(profileData.installs[hash].default, root.leafName, "Should have marked the original default profile as the default for this install.");
Assert.ok(profileData.installs[hash].locked, "Should have locked as we created the profile for this install.");
Assert.equal(profileData.installs.otherinstall.default, root.leafName, "Should have left the other profile as the default for the other install.");
Assert.ok(profileData.installs[hash].locked, "Should still be locked to the other install.");
});

Просмотреть файл

@ -26,10 +26,7 @@ add_task(async () => {
name: "Profile3",
path: "Path3",
}],
};
// Another install is using the profile but it isn't locked.
let installData = {
// Another install is using the profile but it isn't locked.
installs: {
otherinstall: {
default: root.leafName,
@ -38,8 +35,7 @@ add_task(async () => {
};
writeProfilesIni(profileData);
writeInstallsIni(installData);
checkProfileService(profileData, installData);
checkProfileService(profileData);
let env = Cc["@mozilla.org/process/environment;1"].
getService(Ci.nsIEnvironment);
@ -64,9 +60,8 @@ add_task(async () => {
Assert.ok(profileData.profiles[0].default, "Should still be the old default profile.");
let hash = xreDirProvider.getInstallHash();
installData = readInstallsIni();
// The info about the other install will have been removed so it goes through first run on next startup.
Assert.equal(Object.keys(installData.installs).length, 1, "Should be one known install.");
Assert.equal(installData.installs[hash].default, root.leafName, "Should have marked the original default profile as the default for this install.");
Assert.ok(!installData.installs[hash].locked, "Should not have locked as we're not the default app.");
Assert.equal(Object.keys(profileData.installs).length, 1, "Should be one known install.");
Assert.equal(profileData.installs[hash].default, root.leafName, "Should have marked the original default profile as the default for this install.");
Assert.ok(!profileData.installs[hash].locked, "Should not have locked as we're not the default app.");
});

Просмотреть файл

@ -28,10 +28,7 @@ add_task(async () => {
name: "Profile3",
path: "Path3",
}],
};
// Another install is using the profile but it isn't locked.
let installData = {
// Another install is using the profile but it isn't locked.
installs: {
otherinstall: {
default: root.leafName,
@ -40,8 +37,7 @@ add_task(async () => {
};
writeProfilesIni(profileData);
writeInstallsIni(installData);
checkProfileService(profileData, installData);
checkProfileService(profileData);
let env = Cc["@mozilla.org/process/environment;1"].
getService(Ci.nsIEnvironment);
@ -66,9 +62,8 @@ add_task(async () => {
Assert.ok(profileData.profiles[0].default, "Should still be the old default profile.");
let hash = xreDirProvider.getInstallHash();
installData = readInstallsIni();
// The info about the other install will have been removed so it goes through first run on next startup.
Assert.equal(Object.keys(installData.installs).length, 1, "Should be one known install.");
Assert.equal(installData.installs[hash].default, root.leafName, "Should have marked the original default profile as the default for this install.");
Assert.ok(installData.installs[hash].locked, "Should have locked as we're the default app.");
Assert.equal(Object.keys(profileData.installs).length, 1, "Should be one known install.");
Assert.equal(profileData.installs[hash].default, root.leafName, "Should have marked the original default profile as the default for this install.");
Assert.ok(profileData.installs[hash].locked, "Should have locked as we're the default app.");
});

Просмотреть файл

@ -14,8 +14,6 @@ add_task(async () => {
path: defaultProfile.leafName,
default: true,
}],
});
writeInstallsIni({
installs: {
otherhash: {
default: defaultProfile.leafName,
@ -29,7 +27,6 @@ add_task(async () => {
let hash = xreDirProvider.getInstallHash();
let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
@ -39,11 +36,11 @@ add_task(async () => {
Assert.equal(profile.path, defaultProfile.leafName, "Should be the original default profile.");
Assert.ok(profile.default, "Should be marked as the old-style default.");
Assert.equal(Object.keys(installData.installs).length, 1, "Should only be one known installs.");
Assert.equal(installData.installs[hash].default, defaultProfile.leafName, "Should have taken the original default profile as the default for the current install.");
Assert.ok(!installData.installs[hash].locked, "Should not have locked as we're not the default app.");
Assert.equal(Object.keys(profileData.installs).length, 1, "Should only be one known installs.");
Assert.equal(profileData.installs[hash].default, defaultProfile.leafName, "Should have taken the original default profile as the default for the current install.");
Assert.ok(!profileData.installs[hash].locked, "Should not have locked as we're not the default app.");
checkProfileService(profileData, installData);
checkProfileService(profileData);
Assert.ok(!didCreate, "Should not have created a new profile.");
Assert.ok(!service.createdAlternateProfile, "Should not have created an alternate profile.");

Просмотреть файл

@ -22,7 +22,6 @@ add_task(async () => {
let hash = xreDirProvider.getInstallHash();
let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
@ -32,11 +31,11 @@ add_task(async () => {
Assert.equal(profile.path, defaultProfile.leafName, "Should be the original default profile.");
Assert.ok(profile.default, "Should be marked as the old-style default.");
Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install.");
Assert.equal(installData.installs[hash].default, defaultProfile.leafName, "Should have marked the original default profile as the default for this install.");
Assert.ok(!installData.installs[hash].locked, "Should not have locked as we're not the default app.");
Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
Assert.equal(profileData.installs[hash].default, defaultProfile.leafName, "Should have marked the original default profile as the default for this install.");
Assert.ok(!profileData.installs[hash].locked, "Should not have locked as we're not the default app.");
checkProfileService(profileData, installData);
checkProfileService(profileData);
Assert.ok(!didCreate, "Should not have created a new profile.");
Assert.ok(!service.createdAlternateProfile, "Should not have created an alternate profile.");

Просмотреть файл

@ -21,7 +21,6 @@ add_task(async () => {
checkStartupReason("firstrun-created-default");
let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 2, "Should have the right number of profiles.");
@ -39,11 +38,11 @@ add_task(async () => {
Assert.notEqual(profile.path, defaultProfile.leafName, "Should not be the original default profile.");
Assert.ok(!profile.default, "Should not be marked as the old-style default.");
Assert.equal(Object.keys(installData.installs).length, 1, "Should be a default for installs.");
Assert.equal(installData.installs[hash].default, profile.path, "Should have the right default profile.");
Assert.ok(installData.installs[hash].locked, "Should have locked as we created this profile for this install.");
Assert.equal(Object.keys(profileData.installs).length, 1, "Should be a default for installs.");
Assert.equal(profileData.installs[hash].default, profile.path, "Should have the right default profile.");
Assert.ok(profileData.installs[hash].locked, "Should have locked as we created this profile for this install.");
checkProfileService(profileData, installData);
checkProfileService(profileData);
Assert.ok(didCreate, "Should have created a new profile.");
Assert.ok(!service.createdAlternateProfile, "Should not have created an alternate profile.");

Просмотреть файл

@ -25,7 +25,6 @@ add_task(async () => {
checkStartupReason("firstrun-skipped-default");
let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 2, "Should have the right number of profiles.");
@ -43,11 +42,11 @@ add_task(async () => {
Assert.notEqual(profile.path, defaultProfile.leafName, "Should not be the original default profile.");
Assert.ok(!profile.default, "Should not be marked as the old-style default.");
Assert.equal(Object.keys(installData.installs).length, 1, "Should be a default for this install.");
Assert.equal(installData.installs[hash].default, profile.path, "Should have marked the new profile as the default for this install.");
Assert.ok(installData.installs[hash].locked, "Should have locked as we created this profile for this install.");
Assert.equal(Object.keys(profileData.installs).length, 1, "Should be a default for this install.");
Assert.equal(profileData.installs[hash].default, profile.path, "Should have marked the new profile as the default for this install.");
Assert.ok(profileData.installs[hash].locked, "Should have locked as we created this profile for this install.");
checkProfileService(profileData, installData);
checkProfileService(profileData);
Assert.ok(didCreate, "Should have created a new profile.");
Assert.ok(service.createdAlternateProfile, "Should have created an alternate profile.");

Просмотреть файл

@ -23,9 +23,6 @@ add_task(async () => {
name: "dev-edition-default",
path: devProfile.leafName,
}],
});
writeInstallsIni({
installs: {
[hash]: {
default: dedicatedProfile.leafName,
@ -40,7 +37,6 @@ add_task(async () => {
checkStartupReason("default");
let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 3, "Should have the right number of profiles.");
@ -55,11 +51,11 @@ add_task(async () => {
Assert.equal(profile.path, defaultProfile.leafName, "Should be the original default profile.");
Assert.ok(profile.default, "Should be marked as the old-style default.");
Assert.equal(Object.keys(installData.installs).length, 2, "Should be two known installs.");
Assert.equal(installData.installs[hash].default, dedicatedProfile.leafName, "Should have kept the default for this install.");
Assert.equal(installData.installs.otherhash.default, "foobar", "Should have kept the default for the other install.");
Assert.equal(Object.keys(profileData.installs).length, 2, "Should be two known installs.");
Assert.equal(profileData.installs[hash].default, dedicatedProfile.leafName, "Should have kept the default for this install.");
Assert.equal(profileData.installs.otherhash.default, "foobar", "Should have kept the default for the other install.");
checkProfileService(profileData, installData);
checkProfileService(profileData);
Assert.ok(!didCreate, "Should not have created a new profile.");
Assert.ok(selectedProfile.rootDir.equals(dedicatedProfile), "Should be using the right directory.");

Просмотреть файл

@ -32,3 +32,6 @@ skip-if = devedition
[test_snatch_environment.js]
[test_skip_locked_environment.js]
[test_snatch_environment_default.js]
[test_check_backup.js]
[test_missing_profilesini.js]
[test_remove.js]

Просмотреть файл

@ -270,6 +270,26 @@ nsresult nsINIParser::DeleteSection(const char* aSection) {
return NS_OK;
}
nsresult nsINIParser::RenameSection(const char* aSection,
const char* aNewName) {
if (!IsValidSection(aSection) || !IsValidSection(aNewName)) {
return NS_ERROR_INVALID_ARG;
}
if (mSections.Get(aNewName, nullptr)) {
return NS_ERROR_ILLEGAL_VALUE;
}
nsAutoPtr<INIValue> val;
if (mSections.Remove(aSection, &val)) {
mSections.Put(aNewName, val.forget());
} else {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
nsresult nsINIParser::WriteToFile(nsIFile* aFile) {
nsCString buffer;

Просмотреть файл

@ -116,6 +116,17 @@ class nsINIParser {
*/
nsresult DeleteSection(const char* aSection);
/**
* Renames the specified section.
*
* @param aSection section name
* @param aNewName new section name
*
* @throws NS_ERROR_FAILURE if the section did not exist.
* @throws NS_ERROR_ILLEGAL_VALUE if the new section name already exists.
*/
nsresult RenameSection(const char* aSection, const char* aNewName);
/**
* Writes the ini data to disk.
* @param aFile the file to write to