Bug 1455707: Detect when running an older version than previously ran with the selected profile. r=froydnj, r=mconley, r=Gijs

Use the information in compatibility.ini to detect that the current running
application is an older version than previously ran with the profile and in
that case open a UI allowing the user to launch the profile manager, launch
the previous instance of the application or quit.

Also includes the patch from bug 1523725.

--HG--
rename : browser/themes/shared/information.svg => toolkit/themes/shared/profile/information.svg
extra : rebase_source : 3bf8b329eb5ea9e71fe2f0ed34a7e44dfdc434fd
extra : intermediate-source : 21a801ca5f6d435509f93e1dee187cb6ca868c8f
extra : source : c9d89812bc226ca593119bf440cb4f5e50ac2ace
This commit is contained in:
Dave Townsend 2019-01-30 14:56:30 -08:00
parent 5d41d30a08
commit 1e3b008bd7
13 changed files with 396 additions and 34 deletions

View File

@ -56,7 +56,6 @@
skin/classic/browser/identity-icon.svg (../shared/identity-block/identity-icon.svg)
skin/classic/browser/identity-icon-notice.svg (../shared/identity-block/identity-icon-notice.svg)
skin/classic/browser/info.svg (../shared/info.svg)
skin/classic/browser/information.svg (../shared/information.svg)
skin/classic/browser/newInstall.css (../shared/newInstall.css)
skin/classic/browser/newInstallPage.css (../shared/newInstallPage.css)

View File

@ -10,7 +10,7 @@ window {
width: 32px;
height: 32px;
margin-inline-end: 8px;
list-style-image: url("chrome://browser/skin/information.svg");
list-style-image: url("chrome://mozapps/skin/profile/information.svg");
}
description {

View File

@ -0,0 +1,20 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- LOCALIZATION NOTE:
This UI can be most easily shown by modifying the version in compatibility.ini
to a newer version and then starting Firefox.
For this feature, "installation" is used to mean "this discrete download of
Firefox" and "version" is used to mean "the specific revision number of a
given Firefox channel". These terms are not synonymous.
-->
<!ENTITY window.title "Youve launched an older version of Firefox">
<!ENTITY window.style "width: 490px;">
<!ENTITY window.nosync "Using an older version of Firefox can corrupt bookmarks and browsing history already saved to an existing Firefox profile. To protect your information, create a new profile for this installation of &brandShortName;.">
<!ENTITY window.sync "Using an older version of Firefox can corrupt bookmarks and browsing history already saved to an existing Firefox profile. To protect your information, create a new profile for this installation of &brandShortName;. You can always sign in with a &syncBrand.fxAccount.label; to sync your bookmarks and browsing history between profiles.">
<!ENTITY window.create "Create New Profile">
<!ENTITY window.quit-win "Exit">
<!ENTITY window.quit-nonwin "Quit">

View File

@ -83,6 +83,9 @@
locale/@AB_CD@/mozapps/profile/createProfileWizard.dtd (%chrome/mozapps/profile/createProfileWizard.dtd)
locale/@AB_CD@/mozapps/profile/profileSelection.properties (%chrome/mozapps/profile/profileSelection.properties)
locale/@AB_CD@/mozapps/profile/profileSelection.dtd (%chrome/mozapps/profile/profileSelection.dtd)
#ifdef MOZ_BLOCK_PROFILE_DOWNGRADE
locale/@AB_CD@/mozapps/profile/profileDowngrade.dtd (%chrome/mozapps/profile/profileDowngrade.dtd)
#endif
#ifndef MOZ_FENNEC
locale/@AB_CD@/mozapps/update/updates.dtd (%chrome/mozapps/update/updates.dtd)
locale/@AB_CD@/mozapps/update/updates.properties (%chrome/mozapps/update/updates.properties)

View File

@ -0,0 +1,29 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
let gParams;
function init() {
/*
* The C++ code passes a dialog param block using its integers as in and out
* arguments for this UI. The following are the uses of the integers:
*
* 0: A set of flags from nsIToolkitProfileService.downgradeUIFlags.
* 1: A return argument, one of nsIToolkitProfileService.downgradeUIChoice.
*/
gParams = window.arguments[0].QueryInterface(Ci.nsIDialogParamBlock);
let hasSync = gParams.GetInt(0) & Ci.nsIToolkitProfileService.hasSync;
document.getElementById("sync").hidden = !hasSync;
document.getElementById("nosync").hidden = hasSync;
}
function quit() {
gParams.SetInt(1, Ci.nsIToolkitProfileService.quit);
}
function createProfile() {
gParams.SetInt(1, Ci.nsIToolkitProfileService.createNewProfile);
window.close();
}

View File

@ -0,0 +1,39 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/skin/profile/profileDowngrade.css" type="text/css"?>
<!DOCTYPE dialog [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%brandDTD;
<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
%syncBrandDTD;
<!ENTITY % profileDTD SYSTEM "chrome://mozapps/locale/profile/profileDowngrade.dtd">
%profileDTD;
]>
<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="&window.title;" onload="init()" style="&window.style;"
buttonlabelextra1="&window.create;" ondialogextra1="createProfile()"
#ifdef XP_WIN
buttonlabelaccept="&window.quit-win;"
#else
buttonlabelaccept="&window.quit-nonwin;"
#endif
ondialogaccept="quit()" ondialogcancel="quit()"
buttons="accept,extra1" buttonpack="end">
<script type="application/javascript" src="profileDowngrade.js"/>
<hbox flex="1" align="start">
<image id="info" role="presentation"/>
<vbox flex="1">
<description id="nosync">&window.nosync;</description>
<description id="sync">&window.sync;</description>
</vbox>
</hbox>
</dialog>

View File

@ -7,3 +7,7 @@ toolkit.jar:
* content/mozapps/profile/createProfileWizard.xul (content/createProfileWizard.xul)
content/mozapps/profile/profileSelection.js (content/profileSelection.js)
content/mozapps/profile/profileSelection.xul (content/profileSelection.xul)
#ifdef MOZ_BLOCK_PROFILE_DOWNGRADE
content/mozapps/profile/profileDowngrade.js (content/profileDowngrade.js)
* content/mozapps/profile/profileDowngrade.xul (content/profileDowngrade.xul)
#endif

View File

@ -13,6 +13,23 @@ interface nsIProfileLock;
[scriptable, builtinclass, uuid(1947899b-f369-48fa-89da-f7c37bb1e6bc)]
interface nsIToolkitProfileService : nsISupports
{
/**
* When a downgrade is detected UI is presented to the user to ask how to
* proceed. These flags are used to pass some information to the UI.
*/
cenum downgradeUIFlags: 8 {
hasSync = 1,
};
/**
* When a downgrade is detected UI is presented to the user to ask how to
* proceed. These are the possible options the user can choose.
*/
cenum downgradeUIChoice: 8 {
quit = 0,
createNewProfile = 1,
};
attribute boolean startWithLastProfile;
readonly attribute nsISimpleEnumerator /*nsIToolkitProfile*/ profiles;
@ -74,7 +91,10 @@ interface nsIToolkitProfileService : nsISupports
*
* The profile temporary directory will be chosen based on where the
* profile directory is located.
*
*
* If a profile with the given name already exists it will be returned
* instead of creating a new profile.
*
* @param aRootDir
* The profile directory. May be null, in which case a suitable
* default will be chosen based on the profile name.
@ -84,6 +104,23 @@ interface nsIToolkitProfileService : nsISupports
nsIToolkitProfile createProfile(in nsIFile aRootDir,
in AUTF8String aName);
/**
* Create a new profile with a unique name.
*
* As above however the name given will be altered to make it a unique
* profile name.
*
* @param aRootDir
* The profile directory. May be null, in which case a suitable
* default will be chosen based on the profile name.
* @param aNamePrefix
* The prefix to use for the profile name. If unused this will be
* used as the profile name otherwise additional characters will be
* added to make the name unique.
*/
nsIToolkitProfile createUniqueProfile(in nsIFile aRootDir,
in AUTF8String aNamePrefix);
/**
* Returns the number of profiles.
* @return the number of profiles.

View File

@ -1066,7 +1066,7 @@ nsresult nsToolkitProfileService::SelectStartupProfile(
name.AssignLiteral(DEFAULT_NAME);
}
rv = CreateProfile(nullptr, name, getter_AddRefs(mCurrent));
rv = CreateUniqueProfile(nullptr, name, getter_AddRefs(mCurrent));
if (NS_SUCCEEDED(rv)) {
if (mUseDedicatedProfile) {
SetDefaultProfile(mCurrent);
@ -1261,6 +1261,28 @@ static void SaltProfileName(nsACString& aName) {
aName.Insert(salt, 0, 9);
}
NS_IMETHODIMP
nsToolkitProfileService::CreateUniqueProfile(nsIFile* aRootDir,
const nsACString& aNamePrefix,
nsIToolkitProfile** aResult) {
nsCOMPtr<nsIToolkitProfile> profile;
nsresult rv = GetProfileByName(aNamePrefix, getter_AddRefs(profile));
if (NS_FAILED(rv)) {
return CreateProfile(aRootDir, aNamePrefix, aResult);
}
uint32_t suffix = 1;
while (true) {
nsPrintfCString name("%s-%d", PromiseFlatCString(aNamePrefix).get(),
suffix);
rv = GetProfileByName(name, getter_AddRefs(profile));
if (NS_FAILED(rv)) {
return CreateProfile(aRootDir, name, aResult);
}
suffix++;
}
}
NS_IMETHODIMP
nsToolkitProfileService::CreateProfile(nsIFile* aRootDir,
const nsACString& aName,

View File

@ -28,6 +28,8 @@
skin/classic/mozapps/aboutProfiles.css (../../shared/aboutProfiles.css)
#endif
skin/classic/mozapps/aboutServiceWorkers.css (../../shared/aboutServiceWorkers.css)
skin/classic/mozapps/profile/profileDowngrade.css (../../shared/profile/profileDowngrade.css)
skin/classic/mozapps/profile/information.svg (../../shared/profile/information.svg)
% override chrome://mozapps/skin/extensions/category-plugins.svg chrome://global/skin/plugins/pluginGeneric.svg
% override chrome://mozapps/skin/extensions/category-extensions.svg chrome://mozapps/skin/extensions/extensionGeneric.svg

View File

Before

Width:  |  Height:  |  Size: 552 B

After

Width:  |  Height:  |  Size: 552 B

View File

@ -0,0 +1,13 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
spacer[anonid="spacer"] {
display: none;
}
#info {
list-style-image: url("chrome://mozapps/skin/profile/information.svg");
width: 32px;
height: 32px;
}

View File

@ -1494,6 +1494,9 @@ static void DumpHelp() {
"instance.\n"
" --UILocale <locale> Start with <locale> resources as UI Locale.\n"
" --safe-mode Disables extensions and themes for this session.\n"
#ifdef MOZ_BLOCK_PROFILE_DOWNGRADE
" --allow-downgrade Allows downgrading a profile.\n"
#endif
" -MOZ_LOG=<modules> Treated as MOZ_LOG=<modules> environment variable, "
"overrides it.\n"
" -MOZ_LOG_FILE=<file> Treated as MOZ_LOG_FILE=<file> environment "
@ -2230,18 +2233,188 @@ static nsresult SelectProfile(nsIProfileLock** aResult,
return ProfileLockedDialog(rootDir, localDir, unlocker, aNative, aResult);
}
#ifdef MOZ_BLOCK_PROFILE_DOWNGRADE
static const char kProfileDowngradeURL[] =
"chrome://mozapps/content/profile/profileDowngrade.xul";
static ReturnAbortOnError CheckDowngrade(
nsIFile* aProfileDir, nsIFile* aProfileLocalDir, nsACString& aProfileName,
nsINativeAppSupport* aNative, nsIToolkitProfileService* aProfileSvc) {
int32_t result = 0;
nsresult rv;
{
if (gfxPlatform::IsHeadless()) {
// TODO: make a way to turn off all dialogs when headless.
Output(true,
"This profile was last used with a newer version of this "
"application. Please create a new profile.\n");
return NS_ERROR_ABORT;
}
ScopedXPCOMStartup xpcom;
rv = xpcom.Initialize();
NS_ENSURE_SUCCESS(rv, rv);
rv = xpcom.SetWindowCreator(aNative);
NS_ENSURE_SUCCESS(rv, rv);
{ // extra scoping is needed so we release these components before xpcom
// shutdown
bool hasSync = false;
nsCOMPtr<nsIPrefService> prefSvc =
do_GetService("@mozilla.org/preferences-service;1");
NS_ENSURE_TRUE(prefSvc, rv);
nsCOMPtr<nsIFile> prefsFile;
rv = aProfileDir->Clone(getter_AddRefs(prefsFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = prefsFile->Append(NS_LITERAL_STRING("prefs.js"));
NS_ENSURE_SUCCESS(rv, rv);
rv = prefSvc->ReadUserPrefsFromFile(prefsFile);
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(prefSvc);
rv = prefBranch->PrefHasUserValue("services.sync.username", &hasSync);
NS_ENSURE_SUCCESS(rv, rv);
}
nsCOMPtr<nsIWindowWatcher> windowWatcher =
do_GetService(NS_WINDOWWATCHER_CONTRACTID);
NS_ENSURE_TRUE(windowWatcher, NS_ERROR_ABORT);
nsCOMPtr<nsIAppStartup> appStartup(components::AppStartup::Service());
NS_ENSURE_TRUE(appStartup, NS_ERROR_FAILURE);
nsCOMPtr<nsIDialogParamBlock> paramBlock =
do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID);
NS_ENSURE_TRUE(paramBlock, NS_ERROR_ABORT);
uint8_t flags = 0;
if (hasSync) {
flags |= nsIToolkitProfileService::hasSync;
}
paramBlock->SetInt(0, flags);
nsCOMPtr<mozIDOMWindowProxy> newWindow;
rv = windowWatcher->OpenWindow(nullptr, kProfileDowngradeURL, "_blank",
"centerscreen,chrome,modal,titlebar",
paramBlock, getter_AddRefs(newWindow));
NS_ENSURE_SUCCESS(rv, rv);
paramBlock->GetInt(1, &result);
}
}
if (result == nsIToolkitProfileService::createNewProfile) {
// Create a new profile and start it.
nsCString profileName;
profileName.AssignLiteral("default");
# ifdef MOZ_DEDICATED_PROFILES
profileName.Append("-" NS_STRINGIFY(MOZ_UPDATE_CHANNEL));
# endif
nsCOMPtr<nsIToolkitProfile> newProfile;
rv = aProfileSvc->CreateUniqueProfile(nullptr, profileName,
getter_AddRefs(newProfile));
NS_ENSURE_SUCCESS(rv, rv);
rv = aProfileSvc->SetDefaultProfile(newProfile);
NS_ENSURE_SUCCESS(rv, rv);
rv = aProfileSvc->Flush();
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> profD, profLD;
rv = newProfile->GetRootDir(getter_AddRefs(profD));
NS_ENSURE_SUCCESS(rv, rv);
rv = newProfile->GetLocalDir(getter_AddRefs(profLD));
NS_ENSURE_SUCCESS(rv, rv);
SaveFileToEnv("XRE_PROFILE_PATH", profD);
SaveFileToEnv("XRE_PROFILE_LOCAL_PATH", profLD);
return LaunchChild(aNative);
}
// Cancel
return NS_ERROR_ABORT;
}
#endif
/**
* The version string used in compatibility.ini is in the form
* <appversion>_<appbuildid>/<aplatformbuildidid>. The build IDs are in the form
* <year><month><day><hour><minute><second>. We need to be able to turn this
* into something that can be compared by the version comparator. Build IDs are
* numeric so normally we could just use those as part of the version but they
* are larger than 32-bit integers so we must split them into two parts.
* So we generate a version string of the format:
* <appversion>.<appbuilddate>.<appbuildtime>.<platformbuilddate>.<platformbuildtime>
* This doesn't compare correctly so
* we have to make the build ids separate version parts instead. We also have
* to split up the build ids as they are too large for the version comparator's
* brain.
*/
// Build ID dates are 8 digits, build ID times are 6 digits.
#define BUILDID_DATE_LENGTH 8
#define BUILDID_TIME_LENGTH 6
#define BUILDID_LENGTH BUILDID_DATE_LENGTH + BUILDID_TIME_LENGTH
static void GetBuildIDVersions(const nsACString& aFullVersion, int32_t aPos,
nsACString& aBuildVersions) {
// Extract the date part then the time part.
aBuildVersions.Assign(Substring(aFullVersion, aPos, BUILDID_DATE_LENGTH) +
NS_LITERAL_CSTRING(".") +
Substring(aFullVersion, aPos + BUILDID_DATE_LENGTH, BUILDID_TIME_LENGTH));
}
static Version GetComparableVersion(const nsCString& aVersionStr) {
// We'll find the position of the '_' and '/' characters. The length from the
// '_' character to the '/' character will be the length of a build ID plus
// one for the '_' character. Similarly the length from the '/' character to
// the end of the string will be the same.
const uint32_t kExpectedLength = BUILDID_LENGTH + 1;
int32_t underscorePos = aVersionStr.FindChar('_');
int32_t slashPos = aVersionStr.FindChar('/');
if (underscorePos == kNotFound || slashPos == kNotFound ||
(slashPos - underscorePos) != kExpectedLength ||
(aVersionStr.Length() - slashPos) != kExpectedLength) {
NS_WARNING("compatibility.ini Version string does not match the expected format.");
return Version(aVersionStr.get());
}
nsCString appBuild, platBuild;
NS_NAMED_LITERAL_CSTRING(dot, ".");
const nsACString& version = Substring(aVersionStr, 0, underscorePos);
GetBuildIDVersions(aVersionStr, underscorePos + 1, /* outparam */ appBuild);
GetBuildIDVersions(aVersionStr, slashPos + 1, /* outparam */ platBuild);
const nsACString& fullVersion = version + dot + appBuild + dot + platBuild;
return Version(PromiseFlatCString(fullVersion).get());
}
/**
* Checks the compatibility.ini file to see if we have updated our application
* or otherwise invalidated our caches. If the application has been updated,
* we return false; otherwise, we return true. We also write the status
* of the caches (valid/invalid) into the return param aCachesOK. The aCachesOK
* is always invalid if the application has been updated.
* is always invalid if the application has been updated. aIsDowngrade is set to
* true if the current application is older than that previously used by the
* profile.
*/
static bool CheckCompatibility(nsIFile* aProfileDir, const nsCString& aVersion,
const nsCString& aOSABI, nsIFile* aXULRunnerDir,
nsIFile* aAppDir, nsIFile* aFlagFile,
bool* aCachesOK) {
bool* aCachesOK, bool* aIsDowngrade) {
*aCachesOK = false;
*aIsDowngrade = false;
nsCOMPtr<nsIFile> file;
aProfileDir->Clone(getter_AddRefs(file));
if (!file) return false;
@ -2253,7 +2426,16 @@ static bool CheckCompatibility(nsIFile* aProfileDir, const nsCString& aVersion,
nsAutoCString buf;
rv = parser.GetString("Compatibility", "LastVersion", buf);
if (NS_FAILED(rv) || !aVersion.Equals(buf)) return false;
if (NS_FAILED(rv)) {
return false;
}
if (!aVersion.Equals(buf)) {
Version current = GetComparableVersion(aVersion);
Version last = GetComparableVersion(buf);
*aIsDowngrade = last > current;
return false;
}
rv = parser.GetString("Compatibility", "LastOSABI", buf);
if (NS_FAILED(rv) || !aOSABI.Equals(buf)) return false;
@ -3840,30 +4022,6 @@ int XREMain::XRE_mainStartup(bool* aExitFlag) {
rv = mProfileLock->GetLocalDirectory(getter_AddRefs(mProfLD));
NS_ENSURE_SUCCESS(rv, 1);
rv = mDirProvider.SetProfile(mProfD, mProfLD);
NS_ENSURE_SUCCESS(rv, 1);
//////////////////////// NOW WE HAVE A PROFILE ////////////////////////
mozilla::Telemetry::SetProfileDir(mProfD);
if (mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER)
MakeOrSetMinidumpPath(mProfD);
CrashReporter::SetProfileDirectory(mProfD);
#ifdef MOZ_ASAN_REPORTER
// In ASan reporter builds, we need to set ASan's log_path as early as
// possible, so it dumps its errors into files there instead of using
// the default stderr location. Since this is crucial for ASan reporter
// to work at all (and we don't want people to use a non-functional
// ASan reporter build), all failures while setting log_path are fatal.
setASanReporterPath(mProfD);
// Export to env for child processes
SaveFileToEnv("ASAN_REPORTER_PATH", mProfD);
#endif
nsAutoCString version;
BuildVersion(version);
@ -3891,9 +4049,45 @@ int XREMain::XRE_mainStartup(bool* aExitFlag) {
}
bool cachesOK;
bool versionOK =
CheckCompatibility(mProfD, version, osABI, mDirProvider.GetGREDir(),
mAppData->directory, flagFile, &cachesOK);
bool isDowngrade;
bool versionOK = CheckCompatibility(
mProfD, version, osABI, mDirProvider.GetGREDir(), mAppData->directory,
flagFile, &cachesOK, &isDowngrade);
#ifdef MOZ_BLOCK_PROFILE_DOWNGRADE
if (isDowngrade && !CheckArg("allow-downgrade")) {
rv = CheckDowngrade(mProfD, mProfLD, mProfileName, mNativeApp, mProfileSvc);
if (rv == NS_ERROR_LAUNCHED_CHILD_PROCESS || rv == NS_ERROR_ABORT) {
*aExitFlag = true;
return 0;
}
}
#endif
rv = mDirProvider.SetProfile(mProfD, mProfLD);
NS_ENSURE_SUCCESS(rv, 1);
//////////////////////// NOW WE HAVE A PROFILE ////////////////////////
mozilla::Telemetry::SetProfileDir(mProfD);
if (mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER) {
MakeOrSetMinidumpPath(mProfD);
}
CrashReporter::SetProfileDirectory(mProfD);
#ifdef MOZ_ASAN_REPORTER
// In ASan reporter builds, we need to set ASan's log_path as early as
// possible, so it dumps its errors into files there instead of using
// the default stderr location. Since this is crucial for ASan reporter
// to work at all (and we don't want people to use a non-functional
// ASan reporter build), all failures while setting log_path are fatal.
setASanReporterPath(mProfD);
// Export to env for child processes
SaveFileToEnv("ASAN_REPORTER_PATH", mProfD);
#endif
bool lastStartupWasCrash = CheckLastStartupWasCrash().unwrapOr(false);