Bug 1529879: Block changing the profile list when another process has changed it. r=froydnj,Gijs,flod

On startup we record the size and modified time of the profile lists. If
changed we refuse to flush any new changes to disk. Also adds a getter to check
if they've changed so the UI can do something sensible.

All attempts to flush are now checked for success. In some cases in early
startup the failure mode isn't great, we just quit startup. The assumption
though is that it's extremely unlikely that the files will have changed on disk
in the time between when they are read and when profile selection occurs, likely
less than a second later.

The profile reset flow is changed to only delete the old profile and flush once
all the migration has completed, so if something fails the user gets back to
their old profile.

In testing I ended up having to fix bug 1522584 so background file deletions on
a background thread are safer.

I haven't implemented any UI tests right now since making modifications to the
profiles means modifying the actual user's profiles which I'm not keen to do.
See bug 1539868.

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

--HG--
extra : rebase_source : b9fb01c5f2faaf7d534800b700bb02b8c88af023
extra : source : ad5ac4d5c8f7240809a205be2960924813f1e705
This commit is contained in:
Dave Townsend 2019-03-05 12:51:44 -08:00
Родитель 7c8109a2b6
Коммит 2f0f64f3fb
47 изменённых файлов: 580 добавлений и 109 удалений

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

@ -240,3 +240,6 @@ XPC_MSG_DEF(NS_ERROR_BLOCKED_URI , "The URI is blocked")
XPC_MSG_DEF(NS_ERROR_HARMFUL_URI , "The URI is harmful") XPC_MSG_DEF(NS_ERROR_HARMFUL_URI , "The URI is harmful")
XPC_MSG_DEF(NS_ERROR_FINGERPRINTING_URI , "The URI is fingerprinting") XPC_MSG_DEF(NS_ERROR_FINGERPRINTING_URI , "The URI is fingerprinting")
XPC_MSG_DEF(NS_ERROR_CRYPTOMINING_URI , "The URI is cryptomining") XPC_MSG_DEF(NS_ERROR_CRYPTOMINING_URI , "The URI is cryptomining")
/* Profile manager error codes */
XPC_MSG_DEF(NS_ERROR_DATABASE_CHANGED , "Flushing the profiles to disk would have overwritten changes made elsewhere.")

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

@ -14,6 +14,30 @@ XPCOMUtils.defineLazyServiceGetter(
"nsIToolkitProfileService" "nsIToolkitProfileService"
); );
async function flush() {
try {
ProfileService.flush();
refreshUI();
} catch (e) {
let [title, msg, button] = await document.l10n.formatValues([
{ id: "profiles-flush-fail-title" },
{ id: e.result == Cr.NS_ERROR_DATABASE_CHANGED ?
"profiles-flush-conflict" :
"profiles-flush-failed" },
{ id: "profiles-flush-restart-button" },
]);
const PS = Ci.nsIPromptService;
let result = Services.prompt.confirmEx(window, title, msg,
(PS.BUTTON_POS_0 * PS.BUTTON_TITLE_CANCEL) +
(PS.BUTTON_POS_1 * PS.BUTTON_TITLE_IS_STRING),
null, button, null, null, {});
if (result == 1) {
restart(false);
}
}
}
function refreshUI() { function refreshUI() {
let parent = document.getElementById("profiles"); let parent = document.getElementById("profiles");
while (parent.firstChild) { while (parent.firstChild) {
@ -210,8 +234,7 @@ async function renameProfile(profile) {
return; return;
} }
ProfileService.flush(); flush();
refreshUI();
} }
} }
@ -280,14 +303,13 @@ async function removeProfile(profile) {
return; return;
} }
ProfileService.flush(); flush();
refreshUI();
} }
async function defaultProfile(profile) { async function defaultProfile(profile) {
try { try {
ProfileService.defaultProfile = profile; ProfileService.defaultProfile = profile;
ProfileService.flush(); flush();
} catch (e) { } catch (e) {
// This can happen on dev-edition. // This can happen on dev-edition.
let [title, msg] = await document.l10n.formatValues([ let [title, msg] = await document.l10n.formatValues([
@ -297,7 +319,6 @@ async function defaultProfile(profile) {
Services.prompt.alert(window, title, msg); Services.prompt.alert(window, title, msg);
} }
refreshUI();
} }
function openProfile(profile) { function openProfile(profile) {
@ -331,5 +352,10 @@ function restart(safeMode) {
} }
window.addEventListener("DOMContentLoaded", function() { window.addEventListener("DOMContentLoaded", function() {
if (ProfileService.isListOutdated) {
document.getElementById("owned").hidden = true;
} else {
document.getElementById("conflict").hidden = true;
refreshUI(); refreshUI();
}
}, {once: true}); }, {once: true});

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

@ -23,6 +23,11 @@
</div> </div>
<h1 data-l10n-id="profiles-title"></h1> <h1 data-l10n-id="profiles-title"></h1>
<div id="conflict">
<p data-l10n-id="profiles-conflict" />
</div>
<div id="owned">
<div data-l10n-id="profiles-subtitle"></div> <div data-l10n-id="profiles-subtitle"></div>
<div> <div>
@ -30,5 +35,6 @@
</div> </div>
<div id="profiles" class="tab"></div> <div id="profiles" class="tab"></div>
</div>
</body> </body>
</html> </html>

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

@ -51,3 +51,11 @@ profileDeletionFailedTitle=Deletion Failed
# Profile reset # Profile reset
# LOCALIZATION NOTE (resetBackupDirectory): Directory name for the profile directory backup created during reset. This directory is placed in a location users will see it (ie. their desktop). %S is the application name. # LOCALIZATION NOTE (resetBackupDirectory): Directory name for the profile directory backup created during reset. This directory is placed in a location users will see it (ie. their desktop). %S is the application name.
resetBackupDirectory=Old %S Data resetBackupDirectory=Old %S Data
flushFailTitle=Changes not saved
# LOCALIZATION NOTE (conflictMessage): %1$S is brandProductName, %2$S is brandShortName.
conflictMessage=Another copy of %1$S has made changes to profiles. You must restart %2$S before making more changes.
flushFailMessage=An unexpected error has prevented your changes from being saved.
# LOCALIZATION NOTE (flushFailRestartButton): $S is brandShortName.
flushFailRestartButton=Restart %S
flushFailExitButton=Exit

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

@ -9,6 +9,11 @@ profiles-create = Create a New Profile
profiles-restart-title = Restart profiles-restart-title = Restart
profiles-restart-in-safe-mode = Restart with Add-ons Disabled… profiles-restart-in-safe-mode = Restart with Add-ons Disabled…
profiles-restart-normal = Restart normally… profiles-restart-normal = Restart normally…
profiles-conflict = Another copy of { -brand-product-name } has made changes to profiles. You must restart { -brand-short-name } before making more changes.
profiles-flush-fail-title = Changes not saved
profiles-flush-conflict = { profiles-conflict }
profiles-flush-failed = An unexpected error has prevented your changes from being saved.
profiles-flush-restart-button = Restart { -brand-short-name }
# Variables: # Variables:
# $name (String) - Name of the profile # $name (String) - Name of the profile

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

@ -16,6 +16,7 @@ var gDialogParams;
var gProfileManagerBundle; var gProfileManagerBundle;
var gBrandBundle; var gBrandBundle;
var gProfileService; var gProfileService;
var gNeedsFlush = false;
function startup() { function startup() {
try { try {
@ -59,6 +60,48 @@ function startup() {
document.addEventListener("dialogcancel", exitDialog); document.addEventListener("dialogcancel", exitDialog);
} }
function flush(cancelled) {
updateStartupPrefs();
gDialogParams.SetInt(1, document.getElementById("offlineState").checked ? 1 : 0);
if (gNeedsFlush) {
try {
gProfileService.flush();
} catch (e) {
let productName = gBrandBundle.getString("brandProductName");
let appName = gBrandBundle.getString("brandShortName");
let title = gProfileManagerBundle.getString("flushFailTitle");
let restartButton = gProfileManagerBundle.getFormattedString("flushFailRestartButton",
[appName]);
let exitButton = gProfileManagerBundle.getString("flushFailExitButton");
let message;
if (e.result == undefined) {
message = gProfileManagerBundle.getFormattedString("conflictMessage",
[productName, appName]);
} else {
message = gProfileManagerBundle.getString("flushFailMessage");
}
const PS = Ci.nsIPromptService;
let result = Services.prompt.confirmEx(window, title, message,
(PS.BUTTON_POS_0 * PS.BUTTON_TITLE_IS_STRING) +
(PS.BUTTON_POS_1 * PS.BUTTON_TITLE_IS_STRING),
restartButton, exitButton, null, null, {});
gDialogParams.SetInt(0, result == 0 ? Ci.nsIToolkitProfileService.restart
: Ci.nsIToolkitProfileService.exit);
return;
}
gNeedsFlush = false;
}
gDialogParams.SetInt(0, cancelled ? Ci.nsIToolkitProfileService.exit
: Ci.nsIToolkitProfileService.launchWithProfile);
}
function acceptDialog(event) { function acceptDialog(event) {
var appName = gBrandBundle.getString("brandShortName"); var appName = gBrandBundle.getString("brandShortName");
@ -76,26 +119,28 @@ function acceptDialog(event) {
gDialogParams.objects.insertElementAt(selectedProfile.profile.rootDir, 0); gDialogParams.objects.insertElementAt(selectedProfile.profile.rootDir, 0);
gDialogParams.objects.insertElementAt(selectedProfile.profile.localDir, 1); gDialogParams.objects.insertElementAt(selectedProfile.profile.localDir, 1);
if (gProfileService.defaultProfile != selectedProfile.profile) {
try { try {
gProfileService.defaultProfile = selectedProfile.profile; gProfileService.defaultProfile = selectedProfile.profile;
gNeedsFlush = true;
} catch (e) { } catch (e) {
// This can happen on dev-edition. We'll still restart with the selected // This can happen on dev-edition. We'll still restart with the selected
// profile based on the lock's directories. // profile based on the lock's directories.
} }
updateStartupPrefs(); }
flush(false);
gDialogParams.SetInt(0, 1);
/* Bug 257777 */
gDialogParams.SetInt(1, document.getElementById("offlineState").checked ? 1 : 0);
} }
function exitDialog() { function exitDialog() {
updateStartupPrefs(); flush(true);
} }
function updateStartupPrefs() { function updateStartupPrefs() {
var autoSelectLastProfile = document.getElementById("autoSelectLastProfile"); var autoSelectLastProfile = document.getElementById("autoSelectLastProfile");
if (gProfileService.startWithLastProfile != autoSelectLastProfile.checked) {
gProfileService.startWithLastProfile = autoSelectLastProfile.checked; gProfileService.startWithLastProfile = autoSelectLastProfile.checked;
gNeedsFlush = true;
}
} }
// handle key event on listboxes // handle key event on listboxes
@ -139,6 +184,8 @@ function CreateProfile(aProfile) {
profilesElement.ensureElementIsVisible(listitem); profilesElement.ensureElementIsVisible(listitem);
profilesElement.selectItem(listitem); profilesElement.selectItem(listitem);
gNeedsFlush = true;
} }
// rename the selected profile // rename the selected profile
@ -167,6 +214,7 @@ function RenameProfile() {
try { try {
selectedProfile.name = newName; selectedProfile.name = newName;
gNeedsFlush = true;
} catch (e) { } catch (e) {
var alTitle = gProfileManagerBundle.getString("profileNameInvalidTitle"); var alTitle = gProfileManagerBundle.getString("profileNameInvalidTitle");
var alMsg = gProfileManagerBundle.getFormattedString("profileNameInvalid", [newName]); var alMsg = gProfileManagerBundle.getFormattedString("profileNameInvalid", [newName]);
@ -220,6 +268,7 @@ function ConfirmDelete() {
try { try {
selectedProfile.remove(deleteFiles); selectedProfile.remove(deleteFiles);
gNeedsFlush = true;
} catch (e) { } catch (e) {
let title = gProfileManagerBundle.getString("profileDeletionFailedTitle"); let title = gProfileManagerBundle.getString("profileDeletionFailedTitle");
let msg = gProfileManagerBundle.getString("profileDeletionFailed"); let msg = gProfileManagerBundle.getString("profileDeletionFailed");

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

@ -13,6 +13,12 @@ interface nsIProfileLock;
[scriptable, builtinclass, uuid(1947899b-f369-48fa-89da-f7c37bb1e6bc)] [scriptable, builtinclass, uuid(1947899b-f369-48fa-89da-f7c37bb1e6bc)]
interface nsIToolkitProfileService : nsISupports interface nsIToolkitProfileService : nsISupports
{ {
/**
* Tests whether the profile lists on disk have changed since they were
* loaded. When this is true attempts to flush changes to disk will fail.
*/
[infallible] readonly attribute boolean isListOutdated;
/** /**
* When a downgrade is detected UI is presented to the user to ask how to * When a downgrade is detected UI is presented to the user to ask how to
* proceed. These flags are used to pass some information to the UI. * proceed. These flags are used to pass some information to the UI.
@ -30,6 +36,12 @@ interface nsIToolkitProfileService : nsISupports
createNewProfile = 1, createNewProfile = 1,
}; };
cenum profileManagerResult: 8 {
exit = 0,
launchWithProfile = 1,
restart = 2,
};
attribute boolean startWithLastProfile; attribute boolean startWithLastProfile;
readonly attribute nsISimpleEnumerator /*nsIToolkitProfile*/ profiles; readonly attribute nsISimpleEnumerator /*nsIToolkitProfile*/ profiles;
@ -128,7 +140,9 @@ interface nsIToolkitProfileService : nsISupports
readonly attribute unsigned long profileCount; readonly attribute unsigned long profileCount;
/** /**
* Flush the profiles list file. * Flush the profiles list file. This will fail with
* NS_ERROR_DATABASE_CHANGED if the files on disk have changed since the
* profiles were loaded.
*/ */
void flush(); void flush();
}; };

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

@ -46,6 +46,7 @@
#include "mozilla/UniquePtr.h" #include "mozilla/UniquePtr.h"
#include "nsIToolkitShellService.h" #include "nsIToolkitShellService.h"
#include "mozilla/Telemetry.h" #include "mozilla/Telemetry.h"
#include "nsProxyRelease.h"
using namespace mozilla; using namespace mozilla;
@ -82,6 +83,52 @@ nsTArray<UniquePtr<KeyValue>> GetSectionStrings(nsINIParser* aParser,
return result; return result;
} }
void RemoveProfileFiles(nsIToolkitProfile* aProfile, bool aInBackground) {
nsCOMPtr<nsIFile> rootDir;
aProfile->GetRootDir(getter_AddRefs(rootDir));
nsCOMPtr<nsIFile> localDir;
aProfile->GetLocalDir(getter_AddRefs(localDir));
// Just lock the directories, don't mark the profile as locked or the lock
// will attempt to release its reference to the profile on the background
// thread which will assert.
nsCOMPtr<nsIProfileLock> lock;
nsresult rv =
NS_LockProfilePath(rootDir, localDir, nullptr, getter_AddRefs(lock));
NS_ENSURE_SUCCESS_VOID(rv);
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
"nsToolkitProfile::RemoveProfileFiles",
[rootDir, localDir, lock]() mutable {
bool equals;
nsresult rv = rootDir->Equals(localDir, &equals);
// The root dir might contain the temp dir, so remove
// the temp dir first.
if (NS_SUCCEEDED(rv) && !equals) {
localDir->Remove(true);
}
// Ideally we'd unlock after deleting but since the lock is a file
// in the profile we must unlock before removing.
lock->Unlock();
// nsIProfileLock is not threadsafe so release our reference to it on
// the main thread.
NS_ReleaseOnMainThreadSystemGroup(
"nsToolkitProfile::RemoveProfileFiles::Unlock", lock.forget());
rv = rootDir->Remove(true);
NS_ENSURE_SUCCESS_VOID(rv);
});
if (aInBackground) {
nsCOMPtr<nsIEventTarget> target =
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
target->Dispatch(runnable, NS_DISPATCH_NORMAL);
} else {
runnable->Run();
}
}
nsToolkitProfile::nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir, nsToolkitProfile::nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir,
nsIFile* aLocalDir, bool aFromDB) nsIFile* aLocalDir, bool aFromDB)
: mName(aName), : mName(aName),
@ -178,38 +225,7 @@ nsresult nsToolkitProfile::RemoveInternal(bool aRemoveFiles,
} }
if (aRemoveFiles) { if (aRemoveFiles) {
// Check if another instance is using this profile. RemoveProfileFiles(this, aInBackground);
nsCOMPtr<nsIProfileLock> lock;
nsresult rv = Lock(nullptr, getter_AddRefs(lock));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> rootDir(mRootDir);
nsCOMPtr<nsIFile> localDir(mLocalDir);
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
"nsToolkitProfile::RemoveInternal", [rootDir, localDir, lock]() {
bool equals;
nsresult rv = rootDir->Equals(localDir, &equals);
// The root dir might contain the temp dir, so remove
// the temp dir first.
if (NS_SUCCEEDED(rv) && !equals) {
localDir->Remove(true);
}
// Ideally we'd unlock after deleting but since the lock is a file
// in the profile we must unlock before removing.
lock->Unlock();
rootDir->Remove(true);
});
if (aInBackground) {
nsCOMPtr<nsIEventTarget> target =
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
target->Dispatch(runnable, NS_DISPATCH_NORMAL);
} else {
runnable->Run();
}
} }
nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB; nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB;
@ -373,7 +389,13 @@ nsToolkitProfileService::nsToolkitProfileService()
mCreatedAlternateProfile(false), mCreatedAlternateProfile(false),
mStartupReason(NS_LITERAL_STRING("unknown")), mStartupReason(NS_LITERAL_STRING("unknown")),
mMaybeLockProfile(false), mMaybeLockProfile(false),
mUpdateChannel(NS_STRINGIFY(MOZ_UPDATE_CHANNEL)) { mUpdateChannel(NS_STRINGIFY(MOZ_UPDATE_CHANNEL)),
mProfileDBExists(false),
mProfileDBFileSize(0),
mProfileDBModifiedTime(0),
mInstallDBExists(false),
mInstallDBFileSize(0),
mInstallDBModifiedTime(0) {
#ifdef MOZ_DEV_EDITION #ifdef MOZ_DEV_EDITION
mUseDevEditionProfile = true; mUseDevEditionProfile = true;
#endif #endif
@ -406,7 +428,13 @@ void nsToolkitProfileService::CompleteStartup() {
if (isDefaultApp) { if (isDefaultApp) {
mProfileDB.SetString(mInstallSection.get(), "Locked", "1"); mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
Flush();
// There is a very small chance that this could fail if something else
// overwrote the profiles database since we started up, probably less than
// a second ago. There isn't really a sane response here, all the other
// profile changes are already flushed so whether we fail to flush here or
// force quit the app makes no difference.
NS_ENSURE_SUCCESS_VOID(Flush());
} }
} }
} }
@ -484,20 +512,22 @@ bool nsToolkitProfileService::IsProfileForCurrentInstall(
* default install or the profile has been explicitely chosen by some other * default install or the profile has been explicitely chosen by some other
* means then we won't use it. * means then we won't use it.
* *
* Returns true if we chose to make the profile the new dedicated default. * aResult will be set to true if we chose to make the profile the new dedicated
* default.
*/ */
bool nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile( nsresult nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile(
nsIToolkitProfile* aProfile) { nsIToolkitProfile* aProfile, bool* aResult) {
nsresult rv; nsresult rv;
*aResult = false;
// If the profile was last used by a different install then we won't use it. // If the profile was last used by a different install then we won't use it.
if (!IsProfileForCurrentInstall(aProfile)) { if (!IsProfileForCurrentInstall(aProfile)) {
return false; return NS_OK;
} }
nsCString descriptor; nsCString descriptor;
rv = GetProfileDescriptor(aProfile, descriptor, nullptr); rv = GetProfileDescriptor(aProfile, descriptor, nullptr);
NS_ENSURE_SUCCESS(rv, false); NS_ENSURE_SUCCESS(rv, rv);
// Get a list of all the installs. // Get a list of all the installs.
nsTArray<nsCString> installs = GetKnownInstalls(); nsTArray<nsCString> installs = GetKnownInstalls();
@ -524,7 +554,7 @@ bool nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile(
nsCString isLocked; nsCString isLocked;
rv = mProfileDB.GetString(install.get(), "Locked", isLocked); rv = mProfileDB.GetString(install.get(), "Locked", isLocked);
if (NS_SUCCEEDED(rv) && isLocked.Equals("1")) { if (NS_SUCCEEDED(rv) && isLocked.Equals("1")) {
return false; return NS_OK;
} }
inUseInstalls.AppendElement(install); inUseInstalls.AppendElement(install);
@ -547,15 +577,94 @@ bool nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile(
mProfileDB.DeleteString(mInstallSection.get(), "Locked"); mProfileDB.DeleteString(mInstallSection.get(), "Locked");
// Persist the changes. // Persist the changes.
Flush(); rv = Flush();
NS_ENSURE_SUCCESS(rv, rv);
// Once XPCOM is available check if this is the default application and if so // Once XPCOM is available check if this is the default application and if so
// lock the profile again. // lock the profile again.
mMaybeLockProfile = true; mMaybeLockProfile = true;
*aResult = true;
return NS_OK;
}
bool
IsFileOutdated(nsIFile* aFile, bool aExists, PRTime aLastModified,
int64_t aLastSize) {
nsCOMPtr<nsIFile> file;
nsresult rv = aFile->Clone(getter_AddRefs(file));
if (NS_FAILED(rv)) {
return false;
}
bool exists;
rv = aFile->Exists(&exists);
if (NS_FAILED(rv) || exists != aExists) {
return true; return true;
} }
if (!exists) {
return false;
}
int64_t size;
rv = aFile->GetFileSize(&size);
if (NS_FAILED(rv) || size != aLastSize) {
return true;
}
PRTime time;
rv = aFile->GetLastModifiedTime(&time);
if (NS_FAILED(rv) || time != aLastModified) {
return true;
}
return false;
}
nsresult
UpdateFileStats(nsIFile* aFile, bool* aExists, PRTime* aLastModified,
int64_t* aLastSize) {
nsCOMPtr<nsIFile> file;
nsresult rv = aFile->Clone(getter_AddRefs(file));
NS_ENSURE_SUCCESS(rv, rv);
rv = file->Exists(aExists);
NS_ENSURE_SUCCESS(rv, rv);
if (!(*aExists)) {
*aLastModified = 0;
*aLastSize = 0;
return NS_OK;
}
rv = file->GetFileSize(aLastSize);
NS_ENSURE_SUCCESS(rv, rv);
rv = file->GetLastModifiedTime(aLastModified);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
nsToolkitProfileService::GetIsListOutdated(bool *aResult) {
if (IsFileOutdated(mProfileDBFile, mProfileDBExists, mProfileDBModifiedTime,
mProfileDBFileSize)) {
*aResult = true;
return NS_OK;
}
if (IsFileOutdated(mInstallDBFile, mInstallDBExists, mInstallDBModifiedTime,
mInstallDBFileSize)) {
*aResult = true;
return NS_OK;
}
*aResult = false;
return NS_OK;
}
struct ImportInstallsClosure { struct ImportInstallsClosure {
nsINIParser* backupData; nsINIParser* backupData;
nsINIParser* profileDB; nsINIParser* profileDB;
@ -605,11 +714,18 @@ nsresult nsToolkitProfileService::Init() {
rv = mInstallDBFile->AppendNative(NS_LITERAL_CSTRING("installs.ini")); rv = mInstallDBFile->AppendNative(NS_LITERAL_CSTRING("installs.ini"));
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
rv = UpdateFileStats(mInstallDBFile, &mInstallDBExists,
&mInstallDBModifiedTime, &mInstallDBFileSize);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString buffer; nsAutoCString buffer;
bool exists; rv = UpdateFileStats(mProfileDBFile, &mProfileDBExists,
rv = mProfileDBFile->IsFile(&exists); &mProfileDBModifiedTime, &mProfileDBFileSize);
if (NS_SUCCEEDED(rv) && exists) { if (NS_SUCCEEDED(rv) && mProfileDBExists) {
mProfileDBFile->GetFileSize(&mProfileDBFileSize);
mProfileDBFile->GetLastModifiedTime(&mProfileDBModifiedTime);
rv = mProfileDB.Init(mProfileDBFile); rv = mProfileDB.Init(mProfileDBFile);
// Init does not fail on parsing errors, only on OOM/really unexpected // Init does not fail on parsing errors, only on OOM/really unexpected
// conditions. // conditions.
@ -628,9 +744,7 @@ nsresult nsToolkitProfileService::Init() {
// any install data from the backup. // any install data from the backup.
nsINIParser installDB; nsINIParser installDB;
rv = mInstallDBFile->IsFile(&exists); if (mInstallDBExists && NS_SUCCEEDED(installDB.Init(mInstallDBFile))) {
if (NS_SUCCEEDED(rv) && exists &&
NS_SUCCEEDED(installDB.Init(mInstallDBFile))) {
// There is install data to import. // There is install data to import.
ImportInstallsClosure closure = {&installDB, &mProfileDB}; ImportInstallsClosure closure = {&installDB, &mProfileDB};
installDB.GetSections(&ImportInstalls, &closure); installDB.GetSections(&ImportInstalls, &closure);
@ -1059,7 +1173,10 @@ nsresult nsToolkitProfileService::SelectStartupProfile(
// profile is the previous default so we should either make it the // profile is the previous default so we should either make it the
// default profile for this install or push the user to a new profile. // default profile for this install or push the user to a new profile.
if (MaybeMakeDefaultDedicatedProfile(profile)) { bool result;
rv = MaybeMakeDefaultDedicatedProfile(profile, &result);
NS_ENSURE_SUCCESS(rv, rv);
if (result) {
mStartupReason = NS_LITERAL_STRING("restart-claimed-default"); mStartupReason = NS_LITERAL_STRING("restart-claimed-default");
mCurrent = profile; mCurrent = profile;
@ -1078,7 +1195,8 @@ nsresult nsToolkitProfileService::SelectStartupProfile(
return rv; return rv;
} }
Flush(); rv = Flush();
NS_ENSURE_SUCCESS(rv, rv);
mStartupReason = NS_LITERAL_STRING("restart-skipped-default"); mStartupReason = NS_LITERAL_STRING("restart-skipped-default");
*aDidCreate = true; *aDidCreate = true;
@ -1182,14 +1300,10 @@ nsresult nsToolkitProfileService::SelectStartupProfile(
getter_AddRefs(profile)); getter_AddRefs(profile));
} }
// Some pathological arguments can make it this far // Some pathological arguments can make it this far
if (NS_FAILED(rv)) { if (NS_FAILED(rv) || NS_FAILED(Flush())) {
PR_fprintf(PR_STDERR, "Error creating profile.\n"); PR_fprintf(PR_STDERR, "Error creating profile.\n");
return rv;
} }
rv = NS_ERROR_ABORT; return NS_ERROR_ABORT;
Flush();
return rv;
} }
// Check the -p command line argument. It either accepts a profile name and // Check the -p command line argument. It either accepts a profile name and
@ -1283,7 +1397,10 @@ nsresult nsToolkitProfileService::SelectStartupProfile(
// generated by bug 1518591) or it is from an ancient version. We'll opt // generated by bug 1518591) or it is from an ancient version. We'll opt
// to leave it for older versions in this case. // to leave it for older versions in this case.
if (exists) { if (exists) {
if (MaybeMakeDefaultDedicatedProfile(profile)) { bool result;
rv = MaybeMakeDefaultDedicatedProfile(profile, &result);
NS_ENSURE_SUCCESS(rv, rv);
if (result) {
mStartupReason = NS_LITERAL_STRING("firstrun-claimed-default"); mStartupReason = NS_LITERAL_STRING("firstrun-claimed-default");
mCurrent = profile; mCurrent = profile;
@ -1316,7 +1433,8 @@ nsresult nsToolkitProfileService::SelectStartupProfile(
SetNormalDefault(newProfile); SetNormalDefault(newProfile);
} }
Flush(); rv = Flush();
NS_ENSURE_SUCCESS(rv, rv);
if (mCreatedAlternateProfile) { if (mCreatedAlternateProfile) {
mStartupReason = NS_LITERAL_STRING("firstrun-skipped-default"); mStartupReason = NS_LITERAL_STRING("firstrun-skipped-default");
@ -1377,12 +1495,11 @@ nsresult nsToolkitProfileService::CreateResetProfile(
newProfileName, getter_AddRefs(newProfile)); newProfileName, getter_AddRefs(newProfile));
if (NS_FAILED(rv)) return rv; if (NS_FAILED(rv)) return rv;
rv = Flush();
if (NS_FAILED(rv)) return rv;
mCurrent = newProfile; mCurrent = newProfile;
newProfile.forget(aNewProfile); newProfile.forget(aNewProfile);
// Don't flush the changes yet. That will happen once the migration
// successfully completes.
return NS_OK; return NS_OK;
} }
@ -1419,6 +1536,8 @@ nsresult nsToolkitProfileService::ApplyResetProfile(
nsresult rv = aOldProfile->GetName(name); nsresult rv = aOldProfile->GetName(name);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
// Don't remove the old profile's files until after we've successfully flushed
// the profile changes to disk.
rv = aOldProfile->Remove(false); rv = aOldProfile->Remove(false);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
@ -1427,7 +1546,15 @@ nsresult nsToolkitProfileService::ApplyResetProfile(
rv = mCurrent->SetName(name); rv = mCurrent->SetName(name);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
return Flush(); rv = Flush();
NS_ENSURE_SUCCESS(rv, rv);
// Now that the profile changes are flushed, try to remove the old profile's
// files. If we fail the worst that will happen is that an orphan directory is
// left. Let this run in the background while we start up.
RemoveProfileFiles(aOldProfile, true);
return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
@ -1696,6 +1823,10 @@ nsToolkitProfileService::GetProfileCount(uint32_t* aResult) {
NS_IMETHODIMP NS_IMETHODIMP
nsToolkitProfileService::Flush() { nsToolkitProfileService::Flush() {
if (GetIsListOutdated()) {
return NS_ERROR_DATABASE_CHANGED;
}
nsresult rv; nsresult rv;
// If we aren't using dedicated profiles then nothing about the list of // If we aren't using dedicated profiles then nothing about the list of
@ -1739,18 +1870,27 @@ nsToolkitProfileService::Flush() {
} }
fclose(writeFile); fclose(writeFile);
rv = UpdateFileStats(mInstallDBFile, &mInstallDBExists,
&mInstallDBModifiedTime, &mInstallDBFileSize);
NS_ENSURE_SUCCESS(rv, rv);
} else { } else {
rv = mInstallDBFile->Remove(false); rv = mInstallDBFile->Remove(false);
if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
rv != NS_ERROR_FILE_NOT_FOUND) { rv != NS_ERROR_FILE_NOT_FOUND) {
return rv; return rv;
} }
mInstallDBExists = false;
} }
} }
rv = mProfileDB.WriteToFile(mProfileDBFile); rv = mProfileDB.WriteToFile(mProfileDBFile);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
rv = UpdateFileStats(mProfileDBFile, &mProfileDBExists,
&mProfileDBModifiedTime, &mProfileDBFileSize);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK; return NS_OK;
} }

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

@ -102,7 +102,8 @@ class nsToolkitProfileService final : public nsIToolkitProfileService {
nsACString& aDescriptor, bool* aIsRelative); nsACString& aDescriptor, bool* aIsRelative);
bool IsProfileForCurrentInstall(nsIToolkitProfile* aProfile); bool IsProfileForCurrentInstall(nsIToolkitProfile* aProfile);
void ClearProfileFromOtherInstalls(nsIToolkitProfile* aProfile); void ClearProfileFromOtherInstalls(nsIToolkitProfile* aProfile);
bool MaybeMakeDefaultDedicatedProfile(nsIToolkitProfile* aProfile); nsresult MaybeMakeDefaultDedicatedProfile(nsIToolkitProfile* aProfile,
bool* aResult);
bool IsSnapEnvironment(); bool IsSnapEnvironment();
nsresult CreateDefaultProfile(nsIToolkitProfile** aResult); nsresult CreateDefaultProfile(nsIToolkitProfile** aResult);
void SetNormalDefault(nsIToolkitProfile* aProfile); void SetNormalDefault(nsIToolkitProfile* aProfile);
@ -149,10 +150,17 @@ class nsToolkitProfileService final : public nsIToolkitProfileService {
bool mCreatedAlternateProfile; bool mCreatedAlternateProfile;
nsString mStartupReason; nsString mStartupReason;
bool mMaybeLockProfile; bool mMaybeLockProfile;
// Holds the current application update channel. This is only really held // Holds the current application update channel. This is only really held
// so it can be overriden in tests. // so it can be overriden in tests.
nsCString mUpdateChannel; nsCString mUpdateChannel;
// Keep track of some attributes of the databases so we can tell if another
// process has changed them.
bool mProfileDBExists;
int64_t mProfileDBFileSize;
PRTime mProfileDBModifiedTime;
bool mInstallDBExists;
int64_t mInstallDBFileSize;
PRTime mInstallDBModifiedTime;
static nsToolkitProfileService* gService; static nsToolkitProfileService* gService;

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

@ -1,6 +1,5 @@
/* Any copyright is dedicated to the Public Domain. /* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ http://creativecommons.org/publicdomain/zero/1.0/ */
*/
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm"); const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
@ -103,7 +102,7 @@ function selectStartupProfile(args = [], isResetting = false) {
if (profile.value) { if (profile.value) {
Assert.ok(rootDir.value.equals(profile.value.rootDir), "Should have matched the root dir."); 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."); Assert.ok(localDir.value.equals(profile.value.localDir), "Should have matched the local dir.");
Assert.equal(service.currentProfile, profile.value, "Should have marked the profile as the current profile."); Assert.ok(service.currentProfile === profile.value, "Should have marked the profile as the current profile.");
} else { } else {
Assert.ok(!service.currentProfile, "Should be no current profile."); Assert.ok(!service.currentProfile, "Should be no current profile.");
} }
@ -333,7 +332,6 @@ function readInstallsIni() {
}; };
if (!target.exists()) { if (!target.exists()) {
dump("Missing installs.ini\n");
return installData; return installData;
} }

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that when the profiles DB is missing the install data we reload it. * Tests that when the profiles DB is missing the install data we reload it.
*/ */

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that an old-style default profile already locked to a different install * Tests that an old-style default profile already locked to a different install
* isn't claimed by this install. * isn't claimed by this install.

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests from a clean state. * Tests from a clean state.
* Then does some testing that creating new profiles and marking them as * Then does some testing that creating new profiles and marking them as

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

@ -0,0 +1,52 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* Tests that the profile service refuses to flush when the install.ini file
* has been modified.
*/
function check_unchanged(service) {
Assert.ok(!service.isListOutdated, "Should not have detected a modification.");
try {
service.flush();
Assert.ok(true, "Should have flushed.");
} catch (e) {
Assert.ok(false, "Should have succeeded flushing.");
}
}
function check_outdated(service) {
Assert.ok(service.isListOutdated, "Should have detected a modification.");
try {
service.flush();
Assert.ok(false, "Should have failed to flush.");
} catch (e) {
Assert.equal(e.result, Cr.NS_ERROR_DATABASE_CHANGED, "Should have refused to flush.");
}
}
add_task(async () => {
let service = getProfileService();
Assert.ok(!service.isListOutdated, "Should not be modified yet.");
let installsini = gDataHome.clone();
installsini.append("installs.ini");
Assert.ok(!installsini.exists(), "File should not exist yet.");
installsini.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
check_outdated(service);
installsini.remove(false);
// We have to do profile selection to actually have any install data.
selectStartupProfile();
check_unchanged(service);
let oldTime = installsini.lastModifiedTime;
installsini.lastModifiedTime = oldTime - 10000;
check_outdated(service);
// We can't reset the modification time back to exactly what it was, so I
// guess we can't do much more here :(
});

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

@ -0,0 +1,50 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* Tests that the profile service refuses to flush when the profiles.ini file
* has been modified.
*/
function check_unchanged(service) {
Assert.ok(!service.isListOutdated, "Should not have detected a modification.");
try {
service.flush();
Assert.ok(true, "Should have flushed.");
} catch (e) {
Assert.ok(false, "Should have succeeded flushing.");
}
}
function check_outdated(service) {
Assert.ok(service.isListOutdated, "Should have detected a modification.");
try {
service.flush();
Assert.ok(false, "Should have failed to flush.");
} catch (e) {
Assert.equal(e.result, Cr.NS_ERROR_DATABASE_CHANGED, "Should have refused to flush.");
}
}
add_task(async () => {
let service = getProfileService();
Assert.ok(!service.isListOutdated, "Should not be modified yet.");
let profilesini = gDataHome.clone();
profilesini.append("profiles.ini");
Assert.ok(!profilesini.exists(), "File should not exist yet.");
profilesini.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
check_outdated(service);
profilesini.remove(false);
check_unchanged(service);
let oldTime = profilesini.lastModifiedTime;
profilesini.lastModifiedTime = oldTime - 10000;
check_outdated(service);
// We can't reset the modification time back to exactly what it was, so I
// guess we can't do much more here :(
});

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that from an empty database a default profile is created. * Tests that from an empty database a default profile is created.
*/ */

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that when the default application claims the old-style default profile * Tests that when the default application claims the old-style default profile
* it locks it to itself. * it locks it to itself.

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/** /**
* When profiles.ini is missing there isn't any point in restoring from any * When profiles.ini is missing there isn't any point in restoring from any
* installs.ini, the profiles it refers to are gone anyway. * installs.ini, the profiles it refers to are gone anyway.

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that an old-style default profile previously used by this build gets * Tests that an old-style default profile previously used by this build gets
* updated to a dedicated profile for this build. * updated to a dedicated profile for this build.

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/** /**
* If install.ini lists a default profile for this build but that profile no * If install.ini lists a default profile for this build but that profile no
* longer exists don't try to steal the old-style default even if it was used * longer exists don't try to steal the old-style default even if it was used

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that from an empty database profile reset doesn't create a new profile. * Tests that from an empty database profile reset doesn't create a new profile.
*/ */

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests adding and removing functions correctly. * Tests adding and removing functions correctly.
*/ */

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/** /**
* Tests that calling nsIToolkitProfile.remove on the default profile correctly * Tests that calling nsIToolkitProfile.remove on the default profile correctly
* removes the profile. * removes the profile.

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that from a database of profiles the default profile is selected. * Tests that from a database of profiles the default profile is selected.
*/ */

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that the environment variables are used to select a profile. * Tests that the environment variables are used to select a profile.
*/ */

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that the environment variables are used to select a profile. * Tests that the environment variables are used to select a profile.
*/ */

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that when choosing an unknown profile the profile manager is shown. * Tests that when choosing an unknown profile the profile manager is shown.
*/ */

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that from a database of profiles the correct profile is selected. * Tests that from a database of profiles the correct profile is selected.
*/ */

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that when passing the -P command line argument and not passing a * Tests that when passing the -P command line argument and not passing a
* profile name the profile manager is opened. * profile name the profile manager is opened.

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that when requested the profile manager is shown. * Tests that when requested the profile manager is shown.
*/ */

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Previous versions of Firefox automatically used a single profile even if it * Previous versions of Firefox automatically used a single profile even if it
* wasn't marked as the default. So we should try to upgrade that one if it was * wasn't marked as the default. So we should try to upgrade that one if it was

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Previous versions of Firefox automatically used a single profile even if it * Previous versions of Firefox automatically used a single profile even if it
* wasn't marked as the default. So we should try to upgrade that one if it was * wasn't marked as the default. So we should try to upgrade that one if it was

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that the environment variables are used to select a profile and that * Tests that the environment variables are used to select a profile and that
* on the first run of a dedicated profile build we don't snatch it if it is * on the first run of a dedicated profile build we don't snatch it if it is

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that an old-style default profile not previously used by this build gets * Tests that an old-style default profile not previously used by this build gets
* used in a snap environment. * used in a snap environment.

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that from a clean slate snap builds create an appropriate profile. * Tests that from a clean slate snap builds create an appropriate profile.
*/ */

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that the environment variables are used to select a profile and that * Tests that the environment variables are used to select a profile and that
* on the first run of a dedicated profile build we snatch it if it was the * on the first run of a dedicated profile build we snatch it if it was the

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that the environment variables are used to select a profile and that * Tests that the environment variables are used to select a profile and that
* on the first run of a dedicated profile build we snatch it if it was the * on the first run of a dedicated profile build we snatch it if it was the
@ -50,12 +53,12 @@ add_task(async () => {
Assert.ok(!didCreate, "Should not have created a new profile."); Assert.ok(!didCreate, "Should not have created a new profile.");
Assert.ok(rootDir.equals(root), "Should have selected the right root dir."); 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(localDir.equals(local), "Should have selected the right local dir.");
Assert.ok(profile, "A named profile matches this."); Assert.ok(!!profile, "A named profile matches this.");
Assert.equal(profile.name, PROFILE_DEFAULT, "The right profile was matched."); Assert.equal(profile.name, PROFILE_DEFAULT, "The right profile was matched.");
let service = getProfileService(); let service = getProfileService();
Assert.equal(service.defaultProfile, profile, "Should be the default profile."); Assert.ok(service.defaultProfile === profile, "Should be the default profile.");
Assert.equal(service.currentProfile, profile, "Should be the current profile."); Assert.ok(service.currentProfile === profile, "Should be the current profile.");
profileData = readProfilesIni(); profileData = readProfilesIni();
Assert.equal(profileData.profiles[0].name, PROFILE_DEFAULT, "Should be the right profile."); Assert.equal(profileData.profiles[0].name, PROFILE_DEFAULT, "Should be the right profile.");

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that if profiles.ini is set to not start with the last profile then * Tests that if profiles.ini is set to not start with the last profile then
* we show the profile manager in preference to assigning the old default. * we show the profile manager in preference to assigning the old default.

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that an old-style default profile previously used by this build but * Tests that an old-style default profile previously used by this build but
* that has already been claimed by a different build gets stolen by this build. * that has already been claimed by a different build gets stolen by this build.

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that an old-style default profile previously used by this build gets * Tests that an old-style default profile previously used by this build gets
* updated to a dedicated profile for this build. * updated to a dedicated profile for this build.

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that an old-style default profile not previously used by any build * Tests that an old-style default profile not previously used by any build
* doesn't get updated to a dedicated profile for this build and we don't set * doesn't get updated to a dedicated profile for this build and we don't set

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* /*
* Tests that an old-style default profile not previously used by this build gets * Tests that an old-style default profile not previously used by this build gets
* ignored. * ignored.

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

@ -1,3 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/** /**
* Tests that if installs.ini lists a profile we use it as the default. * Tests that if installs.ini lists a profile we use it as the default.
*/ */

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

@ -35,3 +35,5 @@ skip-if = devedition
[test_check_backup.js] [test_check_backup.js]
[test_missing_profilesini.js] [test_missing_profilesini.js]
[test_remove.js] [test_remove.js]
[test_conflict_profiles.js]
[test_conflict_installs.js]

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

@ -47,10 +47,12 @@ class ProfileResetCleanupAsyncTask : public mozilla::Runnable {
* local profile dir. * local profile dir.
*/ */
NS_IMETHOD Run() override { NS_IMETHOD Run() override {
// Copy to the destination then delete the profile. A move doesn't follow // Copy profile's files to the destination. The profile folder will be
// links. // removed after the changes to the known profiles have been flushed to disk
// in nsToolkitProfileService::ApplyResetProfile which isn't called until
// after this thread finishes copying the files.
nsresult rv = mProfileDir->CopyToFollowingLinks(mTargetDir, mLeafName); nsresult rv = mProfileDir->CopyToFollowingLinks(mTargetDir, mLeafName);
if (NS_SUCCEEDED(rv)) rv = mProfileDir->Remove(true); // I guess we just warn if we fail to make the backup?
if (NS_WARN_IF(NS_FAILED(rv))) { if (NS_WARN_IF(NS_FAILED(rv))) {
NS_WARNING("Could not backup the root profile directory"); NS_WARNING("Could not backup the root profile directory");
} }
@ -62,9 +64,10 @@ class ProfileResetCleanupAsyncTask : public mozilla::Runnable {
nsresult rvLocal = mProfileDir->Equals(mProfileLocalDir, &sameDir); nsresult rvLocal = mProfileDir->Equals(mProfileLocalDir, &sameDir);
if (NS_SUCCEEDED(rvLocal) && !sameDir) { if (NS_SUCCEEDED(rvLocal) && !sameDir) {
rvLocal = mProfileLocalDir->Remove(true); rvLocal = mProfileLocalDir->Remove(true);
if (NS_FAILED(rvLocal)) if (NS_FAILED(rvLocal)) {
NS_WARNING("Could not remove the old local profile directory (cache)"); NS_WARNING("Could not remove the old local profile directory (cache)");
} }
}
gProfileResetCleanupCompleted = true; gProfileResetCleanupCompleted = true;
nsCOMPtr<nsIRunnable> resultRunnable = new ProfileResetCleanupResultTask(); nsCOMPtr<nsIRunnable> resultRunnable = new ProfileResetCleanupResultTask();

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

@ -1838,6 +1838,7 @@ static ReturnAbortOnError ShowProfileManager(
nsCOMPtr<nsIFile> profD, profLD; nsCOMPtr<nsIFile> profD, profLD;
bool offline = false; bool offline = false;
int32_t dialogReturn;
{ {
ScopedXPCOMStartup xpcom; ScopedXPCOMStartup xpcom;
@ -1879,11 +1880,10 @@ static ReturnAbortOnError ShowProfileManager(
NS_ENSURE_SUCCESS_LOG(rv, rv); NS_ENSURE_SUCCESS_LOG(rv, rv);
aProfileSvc->Flush(); rv = ioParamBlock->GetInt(0, &dialogReturn);
if (NS_FAILED(rv) || dialogReturn == nsIToolkitProfileService::exit) {
int32_t dialogConfirmed; return NS_ERROR_ABORT;
rv = ioParamBlock->GetInt(0, &dialogConfirmed); }
if (NS_FAILED(rv) || dialogConfirmed == 0) return NS_ERROR_ABORT;
int32_t startOffline; int32_t startOffline;
rv = ioParamBlock->GetInt(1, &startOffline); rv = ioParamBlock->GetInt(1, &startOffline);
@ -1899,13 +1899,21 @@ static ReturnAbortOnError ShowProfileManager(
} }
} }
SaveFileToEnv("XRE_PROFILE_PATH", profD);
SaveFileToEnv("XRE_PROFILE_LOCAL_PATH", profLD);
SaveToEnv("XRE_RESTARTED_BY_PROFILE_MANAGER=1");
if (offline) { if (offline) {
SaveToEnv("XRE_START_OFFLINE=1"); SaveToEnv("XRE_START_OFFLINE=1");
} }
// User requested that we restart back into the profile manager.
if (dialogReturn == nsIToolkitProfileService::restart) {
SaveToEnv("XRE_RESTART_TO_PROFILE_MANAGER=1");
SaveToEnv("XRE_RESTARTED_BY_PROFILE_MANAGER=1");
} else {
MOZ_ASSERT(dialogReturn == nsIToolkitProfileService::launchWithProfile);
SaveFileToEnv("XRE_PROFILE_PATH", profD);
SaveFileToEnv("XRE_PROFILE_LOCAL_PATH", profLD);
SaveToEnv("XRE_RESTARTED_BY_PROFILE_MANAGER=1");
}
if (gRestartedByOS) { if (gRestartedByOS) {
// Re-add this argument when actually starting the application. // Re-add this argument when actually starting the application.
char** newArgv = char** newArgv =
@ -2011,6 +2019,10 @@ static nsresult SelectProfile(nsToolkitProfileService* aProfileSvc,
gDoMigration = true; gDoMigration = true;
} }
if (EnvHasValue("XRE_RESTART_TO_PROFILE_MANAGER")) {
return ShowProfileManager(aProfileSvc, aNative);
}
// Ask the profile manager to select the profile directories to use. // Ask the profile manager to select the profile directories to use.
bool didCreate = false; bool didCreate = false;
rv = aProfileSvc->SelectStartupProfile(&gArgc, gArgv, gDoProfileReset, rv = aProfileSvc->SelectStartupProfile(&gArgc, gArgv, gDoProfileReset,
@ -4361,10 +4373,11 @@ nsresult XREMain::XRE_mainRun() {
if (gDoProfileReset) { if (gDoProfileReset) {
nsresult backupCreated = nsresult backupCreated =
ProfileResetCleanup(mProfileSvc, gResetOldProfile); ProfileResetCleanup(mProfileSvc, gResetOldProfile);
if (NS_FAILED(backupCreated)) if (NS_FAILED(backupCreated)) {
NS_WARNING("Could not cleanup the profile that was reset"); NS_WARNING("Could not cleanup the profile that was reset");
} }
} }
}
#ifndef XP_WIN #ifndef XP_WIN
nsCOMPtr<nsIFile> profileDir; nsCOMPtr<nsIFile> profileDir;

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

@ -756,6 +756,7 @@ with modules["XPCONNECT"]:
with modules["PROFILE"]: with modules["PROFILE"]:
errors["NS_ERROR_LAUNCHED_CHILD_PROCESS"] = FAILURE(200) errors["NS_ERROR_LAUNCHED_CHILD_PROCESS"] = FAILURE(200)
errors["NS_ERROR_SHOW_PROFILE_MANAGER"] = FAILURE(201) errors["NS_ERROR_SHOW_PROFILE_MANAGER"] = FAILURE(201)
errors["NS_ERROR_DATABASE_CHANGED"] = FAILURE(202)
# ======================================================================= # =======================================================================