Bug 394984: Enable any admin user on OSX to update Firefox, front-end and updater changes. r=rstrong

This commit is contained in:
Stephen A Pohl 2016-05-24 22:25:11 -04:00
Родитель e084e2d68f
Коммит f38b8146dd
14 изменённых файлов: 783 добавлений и 203 удалений

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

@ -10,6 +10,9 @@ Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
"resource://gre/modules/UpdateUtils.jsm");
const PREF_APP_UPDATE_CANCELATIONS_OSX = "app.update.cancelations.osx";
const PREF_APP_UPDATE_ELEVATE_NEVER = "app.update.elevate.never";
var gAppUpdater;
function onUnload(aEvent) {
@ -76,7 +79,8 @@ function appUpdater()
// update checks, but also in the About dialog, by presenting a
// "Check for updates" button.
// If updates are found, the user is then asked if he wants to "Update to <version>".
if (!this.updateEnabled) {
if (!this.updateEnabled ||
Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
this.selectPanel("checkForUpdates");
return;
}
@ -98,11 +102,13 @@ appUpdater.prototype =
get isPending() {
if (this.update) {
return this.update.state == "pending" ||
this.update.state == "pending-service";
this.update.state == "pending-service" ||
this.update.state == "pending-elevate";
}
return this.um.activeUpdate &&
(this.um.activeUpdate.state == "pending" ||
this.um.activeUpdate.state == "pending-service");
this.um.activeUpdate.state == "pending-service" ||
this.um.activeUpdate.state == "pending-elevate");
},
// true when there is an update already installed in the background.
@ -183,6 +189,13 @@ appUpdater.prototype =
* Check for updates
*/
checkForUpdates: function() {
// Clear prefs that could prevent a user from discovering available updates.
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS_OSX)) {
Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
}
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
}
this.selectPanel("checkingForUpdates");
this.isChecking = true;
this.checker.checkForUpdates(this.updateCheckListener, true);
@ -194,30 +207,32 @@ appUpdater.prototype =
* which is presented after the download has been downloaded.
*/
buttonRestartAfterDownload: function() {
if (!this.isPending && !this.isApplied)
if (!this.isPending && !this.isApplied) {
return;
}
// Notify all windows that an application quit has been requested.
let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"].
createInstance(Components.interfaces.nsISupportsPRBool);
Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
// Notify all windows that an application quit has been requested.
let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"].
createInstance(Components.interfaces.nsISupportsPRBool);
Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
// Something aborted the quit process.
if (cancelQuit.data)
return;
// Something aborted the quit process.
if (cancelQuit.data) {
return;
}
let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"].
getService(Components.interfaces.nsIAppStartup);
let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"].
getService(Components.interfaces.nsIAppStartup);
// If already in safe mode restart in safe mode (bug 327119)
if (Services.appinfo.inSafeMode) {
appStartup.restartInSafeMode(Components.interfaces.nsIAppStartup.eAttemptQuit);
return;
}
// If already in safe mode restart in safe mode (bug 327119)
if (Services.appinfo.inSafeMode) {
appStartup.restartInSafeMode(Components.interfaces.nsIAppStartup.eAttemptQuit);
return;
}
appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit |
Components.interfaces.nsIAppStartup.eRestart);
},
appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit |
Components.interfaces.nsIAppStartup.eRestart);
},
/**
* Handles oncommand for the "Apply Update…" button
@ -371,7 +386,8 @@ appUpdater.prototype =
// Update the UI when the background updater is finished
let status = aData;
if (status == "applied" || status == "applied-service" ||
status == "pending" || status == "pending-service") {
status == "pending" || status == "pending-service" ||
status == "pending-elevate") {
// If the update is successfully applied, or if the updater has
// fallen back to non-staged updates, show the "Restart to Update"
// button.

24
browser/base/content/browser.js Normal file → Executable file
Просмотреть файл

@ -2566,6 +2566,7 @@ var gMenuButtonUpdateBadge = {
enabled: false,
badgeWaitTime: 0,
timer: null,
cancelObserverRegistered: false,
init: function () {
try {
@ -2590,6 +2591,10 @@ var gMenuButtonUpdateBadge = {
Services.obs.removeObserver(this, "update-downloaded");
this.enabled = false;
}
if (this.cancelObserverRegistered) {
Services.obs.removeObserver(this, "update-canceled");
this.cancelObserverRegistered = false;
}
},
onMenuPanelCommand: function(event) {
@ -2610,11 +2615,15 @@ var gMenuButtonUpdateBadge = {
},
observe: function (subject, topic, status) {
if (topic == "update-canceled") {
this.reset();
return;
}
if (status == "failed") {
// Background update has failed, let's show the UI responsible for
// prompting the user to update manually.
this.displayBadge(false);
this.uninit();
this.displayBadge(false);
return;
}
@ -2629,8 +2638,8 @@ var gMenuButtonUpdateBadge = {
// If the update is successfully applied, or if the updater has fallen back
// to non-staged updates, add a badge to the hamburger menu to indicate an
// update will be applied once the browser restarts.
this.displayBadge(true);
this.uninit();
this.displayBadge(true);
},
displayBadge: function (succeeded) {
@ -2646,6 +2655,8 @@ var gMenuButtonUpdateBadge = {
stringId = "appmenu.restartNeeded.description";
updateButtonText = gNavigatorBundle.getFormattedString(stringId,
[brandShortName]);
Services.obs.addObserver(this, "update-canceled", false);
this.cancelObserverRegistered = true;
} else {
stringId = "appmenu.updateFailed.description";
updateButtonText = gNavigatorBundle.getString(stringId);
@ -2655,6 +2666,15 @@ var gMenuButtonUpdateBadge = {
updateButton.setAttribute("label", updateButtonText);
updateButton.setAttribute("update-status", status);
updateButton.hidden = false;
},
reset: function () {
gMenuButtonBadgeManager.removeBadge(
gMenuButtonBadgeManager.BADGEID_APPUPDATE);
let updateButton = document.getElementById("PanelUI-update-status");
updateButton.hidden = true;
this.uninit();
this.init();
}
};

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

@ -75,6 +75,10 @@
<!-- LOCALIZATION NOTE (finishedBackground.more): This string describes the button labels defined by restartNowButton and restartLaterButton in updates.properties. -->
<!ENTITY finishedBackground.more "The update will be installed the next time &brandShortName; starts. You
can restart &brandShortName; now, or continue working and restart later.">
<!ENTITY finishedBackground.moreElevated "This update requires administrator privileges. The update will be
installed the next time &brandShortName; starts. You can restart
&brandShortName; now, continue working and restart later, or decline this
update.">
<!ENTITY installed.title "Update Installed">
<!ENTITY installed.intro "The update was successfully installed.">

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

@ -70,6 +70,7 @@ updatesfound_major.title=New Version Available
installSuccess=The Update was successfully installed
installPending=Install Pending
patchApplyFailure=The Update could not be installed (patch apply failed)
elevationFailure=You dont have the permissions necessary to install this update. Please contact your system administrator.
# LOCALIZATION NOTE: %S is the amount downloaded so far
# example: Paused — 879 KB of 2.1 MB

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

@ -103,6 +103,12 @@ this.AUSTLMY = {
CHK_INVALID_USER_OVERRIDE_URL: 33,
// Invalid url for app.update.url.override user preference (no notification)
CHK_INVALID_DEFAULT_OVERRIDE_URL: 34,
// Update elevation failures or cancelations threshold reached for this
// version, OSX only (no notification)
CHK_ELEVATION_DISABLED_FOR_VERSION: 35,
// User opted out of elevated updates for the available update version, OSX
// only (no notification)
CHK_ELEVATION_OPTOUT_FOR_VERSION: 36,
/**
* Submit a telemetry ping for the update check result code or a telemetry

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

@ -18,6 +18,7 @@ const XMLNS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul
const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors";
const PREF_APP_UPDATE_BILLBOARD_TEST_URL = "app.update.billboard.test_url";
const PREF_APP_UPDATE_CERT_ERRORS = "app.update.cert.errors";
const PREF_APP_UPDATE_ELEVATE_NEVER = "app.update.elevate.never";
const PREF_APP_UPDATE_ENABLED = "app.update.enabled";
const PREF_APP_UPDATE_LOG = "app.update.log";
const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED = "app.update.notifiedUnsupported";
@ -34,10 +35,11 @@ const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properti
const STATE_DOWNLOADING = "downloading";
const STATE_PENDING = "pending";
const STATE_PENDING_SVC = "pending-service";
const STATE_PENDING_SERVICE = "pending-service";
const STATE_PENDING_ELEVATE = "pending-elevate";
const STATE_APPLYING = "applying";
const STATE_APPLIED = "applied";
const STATE_APPLIED_SVC = "applied-service";
const STATE_APPLIED_SERVICE = "applied-service";
const STATE_SUCCEEDED = "succeeded";
const STATE_DOWNLOAD_FAILED = "download-failed";
const STATE_FAILED = "failed";
@ -234,9 +236,19 @@ var gUpdates = {
never: function () {
// If the user clicks "No Thanks", we should not prompt them to update to
// this version again unless they manually select "Check for Updates..."
// which will clear all of the "never" prefs.
var neverPrefName = PREFBRANCH_APP_UPDATE_NEVER + this.update.appVersion;
// which will clear all of the "never" prefs. There are currently two
// "never" prefs: the older PREFBRANCH_APP_UPDATE_NEVER as well as the
// OSX-only PREF_APP_UPDATE_ELEVATE_NEVER. We set both of these prefs (if
// applicable) to ensure that we don't prompt the user regardless of which
// pref is checked.
let neverPrefName = PREFBRANCH_APP_UPDATE_NEVER + this.update.appVersion;
Services.prefs.setBoolPref(neverPrefName, true);
let aus = CoC["@mozilla.org/updates/update-service;1"].
getService(CoI.nsIApplicationUpdateService);
if (aus.elevationRequired) {
Services.prefs.setCharPref(PREF_APP_UPDATE_ELEVATE_NEVER,
this.update.appVersion);
}
},
/**
@ -414,9 +426,10 @@ var gUpdates = {
// the Update.
switch (state) {
case STATE_PENDING:
case STATE_PENDING_SVC:
case STATE_PENDING_SERVICE:
case STATE_PENDING_ELEVATE:
case STATE_APPLIED:
case STATE_APPLIED_SVC:
case STATE_APPLIED_SERVICE:
this.sourceEvent = SRCEVT_BACKGROUND;
aCallback("finishedBackground");
return;
@ -525,16 +538,21 @@ var gCheckingPage = {
// then canceled. If we don't clear the "never" prefs future
// notifications will never happen.
Services.prefs.deleteBranch(PREFBRANCH_APP_UPDATE_NEVER);
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
}
// The user will be notified if there is an error so clear the background
// check error count.
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS))
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS)) {
Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS);
}
// The preference will be set back to true if the system is still
// unsupported.
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED))
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED)) {
Services.prefs.clearUserPref(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED);
}
this._checker = CoC["@mozilla.org/updates/update-checker;1"].
createInstance(CoI.nsIUpdateChecker);
@ -568,7 +586,7 @@ var gCheckingPage = {
return;
}
if (!aus.canApplyUpdates) {
if (!aus.canApplyUpdates || gUpdates.update.elevationFailure) {
// Prevent multiple notifications for the same update when the user is
// unable to apply updates.
gUpdates.never();
@ -1296,9 +1314,10 @@ var gDownloadingPage = {
}
this.cleanUp();
if (aData == STATE_APPLIED ||
aData == STATE_APPLIED_SVC ||
aData == STATE_APPLIED_SERVICE ||
aData == STATE_PENDING ||
aData == STATE_PENDING_SVC) {
aData == STATE_PENDING_SERVICE ||
aData == STATE_PENDING_ELEVATE) {
// If the update is successfully applied, or if the updater has
// fallen back to non-staged updates, go to the finish page.
gUpdates.wiz.goTo("finished");
@ -1399,7 +1418,7 @@ var gErrorPatchingPage = {
onWizardNext: function() {
switch (gUpdates.update.selectedPatch.state) {
case STATE_PENDING:
case STATE_PENDING_SVC:
case STATE_PENDING_SERVICE:
gUpdates.wiz.goTo("finished");
break;
case STATE_DOWNLOADING:
@ -1421,8 +1440,17 @@ var gFinishedPage = {
* Initialize
*/
onPageShow: function() {
gUpdates.setButtons("restartLaterButton", null, "restartNowButton",
true);
let aus = CoC["@mozilla.org/updates/update-service;1"].
getService(CoI.nsIApplicationUpdateService);
if (aus.elevationRequired) {
LOG("gFinishedPage", "elevationRequired");
gUpdates.setButtons("restartLaterButton", "noThanksButton",
"restartNowButton", true);
} else {
LOG("gFinishedPage", "not elevationRequired");
gUpdates.setButtons("restartLaterButton", null, "restartNowButton",
true);
}
gUpdates.wiz.getButton("finish").focus();
},
@ -1431,18 +1459,36 @@ var gFinishedPage = {
*/
onPageShowBackground: function() {
this.onPageShow();
var updateFinishedName = document.getElementById("updateFinishedName");
let updateFinishedName = document.getElementById("updateFinishedName");
updateFinishedName.value = gUpdates.update.name;
var link = document.getElementById("finishedBackgroundLink");
let link = document.getElementById("finishedBackgroundLink");
if (gUpdates.update.detailsURL) {
link.setAttribute("url", gUpdates.update.detailsURL);
// The details link is stealing focus so it is disabled by default and
// should only be enabled after onPageShow has been called.
link.disabled = false;
}
else
} else {
link.hidden = true;
}
let aus = CoC["@mozilla.org/updates/update-service;1"].
getService(CoI.nsIApplicationUpdateService);
if (aus.elevationRequired) {
let more = document.getElementById("finishedBackgroundMore");
more.setAttribute("hidden", "true");
let moreElevated =
document.getElementById("finishedBackgroundMoreElevated");
moreElevated.setAttribute("hidden", "false");
let moreElevatedLink =
document.getElementById("finishedBackgroundMoreElevatedLink");
moreElevatedLink.setAttribute("hidden", "false");
let moreElevatedLinkLabel =
document.getElementById("finishedBackgroundMoreElevatedLinkLabel");
let manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_MANUAL);
moreElevatedLinkLabel.value = manualURL;
moreElevatedLinkLabel.setAttribute("url", manualURL);
moreElevatedLinkLabel.setAttribute("hidden", "false");
}
if (getPref("getBoolPref", PREF_APP_UPDATE_TEST_LOOP, false)) {
setTimeout(function () {
@ -1459,6 +1505,16 @@ var gFinishedPage = {
// Do the restart
LOG("gFinishedPage" , "onWizardFinish - restarting the application");
let aus = CoC["@mozilla.org/updates/update-service;1"].
getService(CoI.nsIApplicationUpdateService);
if (aus.elevationRequired) {
let um = CoC["@mozilla.org/updates/update-manager;1"].
getService(CoI.nsIUpdateManager);
if (um) {
um.elevationOptedIn();
}
}
// disable the "finish" (Restart) and "extra1" (Later) buttons
// because the Software Update wizard is still up at the point,
// and will remain up until we return and we close the
@ -1499,7 +1555,19 @@ var gFinishedPage = {
*/
onExtra1: function() {
gUpdates.wiz.cancel();
}
},
/**
* When elevation is required and the user clicks "No Thanks" in the wizard.
*/
onExtra2: Task.async(function*() {
Services.obs.notifyObservers(null, "update-canceled", null);
let um = CoC["@mozilla.org/updates/update-manager;1"].
getService(CoI.nsIUpdateManager);
um.cleanupActiveUpdate();
gUpdates.never();
gUpdates.wiz.cancel();
}),
};
/**

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

@ -205,6 +205,7 @@
<wizardpage id="finishedBackground" pageid="finishedBackground"
object="gFinishedPage" onextra1="gFinishedPage.onExtra1()"
onextra2="gFinishedPage.onExtra2()"
onpageshow="gFinishedPage.onPageShowBackground();">
<updateheader label="&finishedPage.title;"/>
<vbox class="update-content" flex="1">
@ -218,6 +219,14 @@
</hbox>
<spacer flex="1"/>
<label id="finishedBackgroundMore">&finishedBackground.more;</label>
<label id="finishedBackgroundMoreElevated"
hidden="true">&finishedBackground.moreElevated;</label>
<label id="finishedBackgroundMoreElevatedLink"
hidden="true">&errorManual.label;</label>
<hbox>
<label class="text-link" id="finishedBackgroundMoreElevatedLinkLabel"
value="" onclick="openUpdateURL(event);" hidden="true"/>
</hbox>
</vbox>
</wizardpage>

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

@ -87,7 +87,7 @@ interface nsIUpdatePatch : nsISupports
* that the front end and other application services can use to learn more
* about what is going on.
*/
[scriptable, uuid(6b0b7721-6746-443d-8cb0-c6199d7f28a6)]
[scriptable, uuid(e094c045-f4ff-41fd-92da-cd2effd2c7c9)]
interface nsIUpdate : nsISupports
{
/**
@ -221,6 +221,7 @@ interface nsIUpdate : nsISupports
* "downloading" The update is being downloaded.
* "pending" The update is ready to be applied.
* "pending-service" The update is ready to be applied with the service.
* "pending-elevate" The update is ready to be applied but requires elevation.
* "applying" The update is being applied.
* "applied" The update is ready to be switched to.
* "applied-os" The update is OS update and to be installed.
@ -242,6 +243,11 @@ interface nsIUpdate : nsISupports
*/
attribute long errorCode;
/**
* Whether an elevation failure has been encountered for this update.
*/
attribute boolean elevationFailure;
/**
* The number of patches supplied by this update.
*/
@ -344,7 +350,7 @@ interface nsIUpdateChecker : nsISupports
* background update checks and provides utilities for selecting and
* downloading update patches.
*/
[scriptable, uuid(9f9b51f5-340e-47ce-85ae-9eb077c6cd39)]
[scriptable, uuid(1107d207-a263-403a-b268-05772ec10757)]
interface nsIApplicationUpdateService : nsISupports
{
/**
@ -423,6 +429,12 @@ interface nsIApplicationUpdateService : nsISupports
*/
readonly attribute boolean canCheckForUpdates;
/**
* Whether or not the installation requires elevation. Currently only
* implemented on OSX, returns false on other platforms.
*/
readonly attribute boolean elevationRequired;
/**
* Whether or not the Update Service can download and install updates.
* On Windows, this is a function of whether or not the maintenance service
@ -468,7 +480,7 @@ interface nsIUpdateProcessor : nsISupports
* An interface describing a global application service that maintains a list
* of updates previously performed as well as the current active update.
*/
[scriptable, uuid(f8371237-10a6-46a5-b23f-f6f7684e9d71)]
[scriptable, uuid(0f1098e9-a447-4af9-b030-6f8f35c85f89)]
interface nsIUpdateManager : nsISupports
{
/**
@ -498,13 +510,24 @@ interface nsIUpdateManager : nsISupports
* Refresh the update status based on the information in update.status.
*/
void refreshUpdateStatus();
/**
* The user agreed to proceed with an elevated update and we are now
* permitted to show an elevation prompt.
*/
void elevationOptedIn();
/**
* Clean up and remove the active update without applying it.
*/
void cleanupActiveUpdate();
};
/**
* An interface describing an object that can show various kinds of Update
* notification UI to the user.
*/
[scriptable, uuid(599fd3c6-ec68-4499-ada5-2997739c97a6)]
[scriptable, uuid(cee3bd60-c564-42ff-a2bf-d442cb15f75c)]
interface nsIUpdatePrompt : nsISupports
{
/**
@ -562,4 +585,11 @@ interface nsIUpdatePrompt : nsISupports
* An nsIDOMWindow to set as the parent for this window. Can be null.
*/
void showUpdateHistory(in nsIDOMWindow parent);
/**
* Shows the application update downloaded user interface advising that an
* update, which requires elevation, has now been downloaded and a restart is
* necessary to complete the update.
*/
void showUpdateElevationRequired();
};

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

@ -18,36 +18,40 @@ Cu.import("resource://gre/modules/AppConstants.jsm", this);
const UPDATESERVICE_CID = Components.ID("{B3C290A6-3943-4B89-8BBE-C01EB7B3B311}");
const UPDATESERVICE_CONTRACTID = "@mozilla.org/updates/update-service;1";
const PREF_APP_UPDATE_ALTWINDOWTYPE = "app.update.altwindowtype";
const PREF_APP_UPDATE_AUTO = "app.update.auto";
const PREF_APP_UPDATE_BACKGROUND_INTERVAL = "app.update.download.backgroundInterval";
const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors";
const PREF_APP_UPDATE_BACKGROUNDMAXERRORS = "app.update.backgroundMaxErrors";
const PREF_APP_UPDATE_CANCELATIONS = "app.update.cancelations";
const PREF_APP_UPDATE_CERTS_BRANCH = "app.update.certs.";
const PREF_APP_UPDATE_CERT_CHECKATTRS = "app.update.cert.checkAttributes";
const PREF_APP_UPDATE_CERT_ERRORS = "app.update.cert.errors";
const PREF_APP_UPDATE_CERT_MAXERRORS = "app.update.cert.maxErrors";
const PREF_APP_UPDATE_CERT_REQUIREBUILTIN = "app.update.cert.requireBuiltIn";
const PREF_APP_UPDATE_ENABLED = "app.update.enabled";
const PREF_APP_UPDATE_IDLETIME = "app.update.idletime";
const PREF_APP_UPDATE_INTERVAL = "app.update.interval";
const PREF_APP_UPDATE_LOG = "app.update.log";
const PREF_APP_UPDATE_NEVER_BRANCH = "app.update.never.";
const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED = "app.update.notifiedUnsupported";
const PREF_APP_UPDATE_POSTUPDATE = "app.update.postupdate";
const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime";
const PREF_APP_UPDATE_SHOW_INSTALLED_UI = "app.update.showInstalledUI";
const PREF_APP_UPDATE_SILENT = "app.update.silent";
const PREF_APP_UPDATE_STAGING_ENABLED = "app.update.staging.enabled";
const PREF_APP_UPDATE_URL = "app.update.url";
const PREF_APP_UPDATE_URL_DETAILS = "app.update.url.details";
const PREF_APP_UPDATE_URL_OVERRIDE = "app.update.url.override";
const PREF_APP_UPDATE_SERVICE_ENABLED = "app.update.service.enabled";
const PREF_APP_UPDATE_SERVICE_ERRORS = "app.update.service.errors";
const PREF_APP_UPDATE_SERVICE_MAX_ERRORS = "app.update.service.maxErrors";
const PREF_APP_UPDATE_SOCKET_ERRORS = "app.update.socket.maxErrors";
const PREF_APP_UPDATE_RETRY_TIMEOUT = "app.update.socket.retryTimeout";
const PREF_APP_UPDATE_ALTWINDOWTYPE = "app.update.altwindowtype";
const PREF_APP_UPDATE_AUTO = "app.update.auto";
const PREF_APP_UPDATE_BACKGROUND_INTERVAL = "app.update.download.backgroundInterval";
const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors";
const PREF_APP_UPDATE_BACKGROUNDMAXERRORS = "app.update.backgroundMaxErrors";
const PREF_APP_UPDATE_CANCELATIONS = "app.update.cancelations";
const PREF_APP_UPDATE_CANCELATIONS_OSX = "app.update.cancelations.osx";
const PREF_APP_UPDATE_CERTS_BRANCH = "app.update.certs.";
const PREF_APP_UPDATE_CERT_CHECKATTRS = "app.update.cert.checkAttributes";
const PREF_APP_UPDATE_CERT_ERRORS = "app.update.cert.errors";
const PREF_APP_UPDATE_CERT_MAXERRORS = "app.update.cert.maxErrors";
const PREF_APP_UPDATE_CERT_REQUIREBUILTIN = "app.update.cert.requireBuiltIn";
const PREF_APP_UPDATE_ELEVATE_NEVER = "app.update.elevate.never";
const PREF_APP_UPDATE_ELEVATE_VERSION = "app.update.elevate.version";
const PREF_APP_UPDATE_ENABLED = "app.update.enabled";
const PREF_APP_UPDATE_IDLETIME = "app.update.idletime";
const PREF_APP_UPDATE_INTERVAL = "app.update.interval";
const PREF_APP_UPDATE_LOG = "app.update.log";
const PREF_APP_UPDATE_MAX_OSX_CANCELATIONS = "app.update.cancelations.osx.max";
const PREF_APP_UPDATE_NEVER_BRANCH = "app.update.never.";
const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED = "app.update.notifiedUnsupported";
const PREF_APP_UPDATE_POSTUPDATE = "app.update.postupdate";
const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime";
const PREF_APP_UPDATE_SHOW_INSTALLED_UI = "app.update.showInstalledUI";
const PREF_APP_UPDATE_SILENT = "app.update.silent";
const PREF_APP_UPDATE_STAGING_ENABLED = "app.update.staging.enabled";
const PREF_APP_UPDATE_URL = "app.update.url";
const PREF_APP_UPDATE_URL_DETAILS = "app.update.url.details";
const PREF_APP_UPDATE_URL_OVERRIDE = "app.update.url.override";
const PREF_APP_UPDATE_SERVICE_ENABLED = "app.update.service.enabled";
const PREF_APP_UPDATE_SERVICE_ERRORS = "app.update.service.errors";
const PREF_APP_UPDATE_SERVICE_MAX_ERRORS = "app.update.service.maxErrors";
const PREF_APP_UPDATE_SOCKET_ERRORS = "app.update.socket.maxErrors";
const PREF_APP_UPDATE_RETRY_TIMEOUT = "app.update.socket.retryTimeout";
const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul";
const URI_UPDATE_HISTORY_DIALOG = "chrome://mozapps/content/update/history.xul";
@ -79,11 +83,12 @@ const FILE_BACKUP_LOG = "backup-update.log";
const STATE_NONE = "null";
const STATE_DOWNLOADING = "downloading";
const STATE_PENDING = "pending";
const STATE_PENDING_SVC = "pending-service";
const STATE_PENDING_SERVICE = "pending-service";
const STATE_PENDING_ELEVATE = "pending-elevate";
const STATE_APPLYING = "applying";
const STATE_APPLIED = "applied";
const STATE_APPLIED_OS = "applied-os";
const STATE_APPLIED_SVC = "applied-service";
const STATE_APPLIED_SERVICE = "applied-service";
const STATE_SUCCEEDED = "succeeded";
const STATE_DOWNLOAD_FAILED = "download-failed";
const STATE_FAILED = "failed";
@ -183,6 +188,10 @@ const DEFAULT_SOCKET_MAX_ERRORS = 10;
// The number of milliseconds to wait before retrying a connection error.
const DEFAULT_UPDATE_RETRY_TIMEOUT = 2000;
// Default maximum number of elevation cancelations per update version before
// giving up.
const DEFAULT_MAX_OSX_CANCELATIONS = 3;
// This maps app IDs to their respective notification topic which signals when
// the application's user interface has been displayed.
const APPID_TO_TOPIC = {
@ -347,6 +356,66 @@ function hasUpdateMutex() {
return !!gUpdateMutexHandle;
}
/**
* Determines whether or not all descendants of a directory are writeable.
* Note: Does not check the root directory itself for writeability.
*
* @return true if all descendants are writeable, false otherwise
*/
function areDirectoryEntriesWriteable(aDir) {
let items = aDir.directoryEntries;
while (items.hasMoreElements()) {
let item = items.getNext().QueryInterface(Ci.nsIFile);
if (!item.isWritable()) {
LOG("areDirectoryEntriesWriteable - unable to write to " + item.path);
return false;
}
if (item.isDirectory() && !areDirectoryEntriesWriteable(item)) {
return false;
}
}
return true;
}
/**
* OSX only function to determine if the user requires elevation to be able to
* write to the application bundle.
*
* @return true if elevation is required, false otherwise
*/
function getElevationRequired() {
if (AppConstants.platform != "macosx") {
return false;
}
try {
// Recursively check that the application bundle (and its descendants) can
// be written to.
LOG("getElevationRequired - recursively testing write access on " +
getInstallDirRoot().path);
if (!getInstallDirRoot().isWritable() ||
!areDirectoryEntriesWriteable(getInstallDirRoot())) {
LOG("getElevationRequired - unable to write to application bundle, " +
"elevation required");
return true;
}
} catch (ex) {
LOG("getElevationRequired - unable to write to application bundle, " +
"elevation required. Exception: " + ex);
return true;
}
LOG("getElevationRequired - able to write to application bundle, elevation " +
"not required");
return false;
}
/**
* Determines whether or not an update can be applied. This is always true on
* Windows when the service is used. Also, this is always true on OSX because we
* offer users the option to perform an elevated update when necessary.
*
* @return true if an update can be applied, false otherwise
*/
function getCanApplyUpdates() {
let useService = false;
if (shouldUseService()) {
@ -356,37 +425,27 @@ function getCanApplyUpdates() {
useService = true;
}
if (!useService) {
if (!useService && AppConstants.platform != "macosx") {
try {
let updateTestFile = getUpdateFile([FILE_PERMS_TEST]);
LOG("getCanApplyUpdates - testing write access " + updateTestFile.path);
testWriteAccess(updateTestFile, false);
if (AppConstants.platform == "macosx") {
// Check that the application bundle can be written to.
let appDirTestFile = getAppBaseDir();
appDirTestFile.append(FILE_PERMS_TEST);
LOG("getCanApplyUpdates - testing write access " + appDirTestFile.path);
if (appDirTestFile.exists()) {
appDirTestFile.remove(false);
}
appDirTestFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
appDirTestFile.remove(false);
} else if (AppConstants.platform == "win") {
if (AppConstants.platform == "win") {
// Example windowsVersion: Windows XP == 5.1
let windowsVersion = Services.sysinfo.getProperty("version");
LOG("getCanApplyUpdates - windowsVersion = " + windowsVersion);
/**
* For Vista, updates can be performed to a location requiring admin
* privileges by requesting elevation via the UAC prompt when launching
* updater.exe if the appDir is under the Program Files directory
* (e.g. C:\Program Files\) and UAC is turned on and we can elevate
* (e.g. user has a split token).
*
* Note: this does note attempt to handle the case where UAC is turned on
* and the installation directory is in a restricted location that
* requires admin privileges to update other than Program Files.
*/
/**
* For Vista, updates can be performed to a location requiring admin
* privileges by requesting elevation via the UAC prompt when launching
* updater.exe if the appDir is under the Program Files directory
* (e.g. C:\Program Files\) and UAC is turned on and we can elevate
* (e.g. user has a split token).
*
* Note: this does note attempt to handle the case where UAC is turned on
* and the installation directory is in a restricted location that
* requires admin privileges to update other than Program Files.
*/
let userCanElevate = false;
if (parseFloat(windowsVersion) >= 6) {
@ -458,9 +517,20 @@ function getCanApplyUpdates() {
*/
XPCOMUtils.defineLazyGetter(this, "gCanStageUpdatesSession",
function aus_gCanStageUpdatesSession() {
if (getElevationRequired()) {
LOG("gCanStageUpdatesSession - unable to stage updates because elevation " +
"is required.");
return false;
}
try {
let updateTestFile = getInstallDirRoot();
updateTestFile.append(FILE_PERMS_TEST);
let updateTestFile;
if (AppConstants.platform == "macosx") {
updateTestFile = getUpdateFile([FILE_PERMS_TEST]);
} else {
updateTestFile = getInstallDirRoot();
updateTestFile.append(FILE_PERMS_TEST);
}
LOG("gCanStageUpdatesSession - testing write access " +
updateTestFile.path);
testWriteAccess(updateTestFile, true);
@ -1223,16 +1293,44 @@ function handleUpdateFailure(update, errorCode) {
}
if (update.errorCode == ELEVATION_CANCELED) {
writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
let cancelations = getPref("getIntPref", PREF_APP_UPDATE_CANCELATIONS, 0);
cancelations++;
Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS, cancelations);
if (AppConstants.platform == "macosx") {
let osxCancelations = getPref("getIntPref",
PREF_APP_UPDATE_CANCELATIONS_OSX, 0);
osxCancelations++;
Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS_OSX,
osxCancelations);
let maxCancels = getPref("getIntPref",
PREF_APP_UPDATE_MAX_OSX_CANCELATIONS,
DEFAULT_MAX_OSX_CANCELATIONS);
if (osxCancelations >= DEFAULT_MAX_OSX_CANCELATIONS) {
cleanupActiveUpdate();
} else {
writeStatusFile(getUpdatesDir(),
update.state = STATE_PENDING_ELEVATE);
}
update.statusText = gUpdateBundle.GetStringFromName("elevationFailure");
let oldType = update.selectedPatch ? update.selectedPatch.type
: "complete";
update.QueryInterface(Ci.nsIWritablePropertyBag);
update.setProperty("patchingFailed", oldType);
let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
createInstance(Ci.nsIUpdatePrompt);
prompter.showUpdateError(update);
} else {
writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
}
return true;
}
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS)) {
Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS);
}
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS_OSX)) {
Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
}
// Replace with Array.prototype.includes when it has stabilized.
if (SERVICE_ERRORS.indexOf(update.errorCode) != -1) {
@ -1320,7 +1418,7 @@ function pingStateAndStatusCodes(aUpdate, aStartup, aStatus) {
case STATE_PENDING:
stateCode = 4;
break;
case STATE_PENDING_SVC:
case STATE_PENDING_SERVICE:
stateCode = 5;
break;
case STATE_APPLYING:
@ -1332,7 +1430,7 @@ function pingStateAndStatusCodes(aUpdate, aStartup, aStatus) {
case STATE_APPLIED_OS:
stateCode = 8;
break;
case STATE_APPLIED_SVC:
case STATE_APPLIED_SERVICE:
stateCode = 9;
break;
case STATE_SUCCEEDED:
@ -1344,6 +1442,9 @@ function pingStateAndStatusCodes(aUpdate, aStartup, aStatus) {
case STATE_FAILED:
stateCode = 12;
break;
case STATE_PENDING_ELEVATE:
stateCode = 13;
break;
default:
stateCode = 1;
}
@ -2018,7 +2119,8 @@ UpdateService.prototype = {
// If it's "applying", we know that we've already been here once, so
// we really want to start from a clean state.
if (update &&
(update.state == STATE_PENDING || update.state == STATE_PENDING_SVC)) {
(update.state == STATE_PENDING ||
update.state == STATE_PENDING_SERVICE)) {
LOG("UpdateService:_postUpdateProcessing - patch found in applying " +
"state for the first time");
update.state = STATE_APPLYING;
@ -2084,8 +2186,10 @@ UpdateService.prototype = {
// Done with this update. Clean it up.
cleanupActiveUpdate();
}
else {
} else if (status == STATE_PENDING_ELEVATE) {
prompter.showUpdateElevationRequired();
return;
} else {
// If we hit an error, then the error code will be included in the status
// string following a colon and a space. If we had an I/O error, then we
// assume that the patch is not invalid, and we re-stage the patch so that
@ -2308,13 +2412,21 @@ UpdateService.prototype = {
AUSTLMY.pingBoolPref("UPDATE_NOT_PREF_UPDATE_STAGING_ENABLED_" +
this._pingSuffix,
PREF_APP_UPDATE_STAGING_ENABLED, true, true);
if (AppConstants.platform == "win") {
if (AppConstants.platform == "win" || AppConstants.platform == "macosx") {
// Histogram IDs:
// UPDATE_PREF_UPDATE_CANCELATIONS_EXTERNAL
// UPDATE_PREF_UPDATE_CANCELATIONS_NOTIFY
AUSTLMY.pingIntPref("UPDATE_PREF_UPDATE_CANCELATIONS_" + this._pingSuffix,
PREF_APP_UPDATE_CANCELATIONS, 0, 0);
}
if (AppConstants.platform == "macosx") {
// Histogram IDs:
// UPDATE_PREF_UPDATE_CANCELATIONS_OSX_EXTERNAL
// UPDATE_PREF_UPDATE_CANCELATIONS_OSX_NOTIFY
AUSTLMY.pingIntPref("UPDATE_PREF_UPDATE_CANCELATIONS_OSX_" +
this._pingSuffix,
PREF_APP_UPDATE_CANCELATIONS_OSX, 0, 0);
}
if (AppConstants.MOZ_MAINTENANCE_SERVICE) {
// Histogram IDs:
// UPDATE_NOT_PREF_UPDATE_SERVICE_ENABLED_EXTERNAL
@ -2353,7 +2465,8 @@ UpdateService.prototype = {
if (this._downloader && this._downloader.patchIsStaged) {
let readState = readStatusFile(getUpdatesDir());
if (readState == STATE_PENDING || readState == STATE_PENDING_SVC) {
if (readState == STATE_PENDING || readState == STATE_PENDING_SVC ||
readState == STATE_PENDING_ELEVATE) {
AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_IS_DOWNLOADED);
} else {
AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_IS_STAGED);
@ -2470,8 +2583,66 @@ UpdateService.prototype = {
}
});
var update = minorUpdate || majorUpdate;
if (!update) {
let update = minorUpdate || majorUpdate;
if (AppConstants.platform == "macosx" && update) {
if (getElevationRequired()) {
let installAttemptVersion = getPref("getCharPref",
PREF_APP_UPDATE_ELEVATE_VERSION,
null);
if (vc.compare(installAttemptVersion, update.appVersion) != 0) {
Services.prefs.setCharPref(PREF_APP_UPDATE_ELEVATE_VERSION,
update.appVersion);
if (Services.prefs.prefHasUserValue(
PREF_APP_UPDATE_CANCELATIONS_OSX)) {
Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
}
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
}
} else {
let numCancels = getPref("getIntPref",
PREF_APP_UPDATE_CANCELATIONS_OSX,
0);
let rejectedVersion = getPref("getCharPref",
PREF_APP_UPDATE_ELEVATE_NEVER, "");
let maxCancels = getPref("getIntPref",
PREF_APP_UPDATE_MAX_OSX_CANCELATIONS,
DEFAULT_MAX_OSX_CANCELATIONS);
if (numCancels >= maxCancels) {
LOG("UpdateService:selectUpdate - the user requires elevation to " +
"install this update, but the user has exceeded the max " +
"number of elevation attempts.");
update.elevationFailure = true;
AUSTLMY.pingCheckCode(
this._pingSuffix,
AUSTLMY.CHK_ELEVATION_DISABLED_FOR_VERSION);
} else if (vc.compare(rejectedVersion, update.appVersion) == 0) {
LOG("UpdateService:selectUpdate - the user requires elevation to " +
"install this update, but elevation is disabled for this " +
"version.");
update.elevationFailure = true;
AUSTLMY.pingCheckCode(this._pingSuffix,
AUSTLMY.CHK_ELEVATION_OPTOUT_FOR_VERSION);
} else {
LOG("UpdateService:selectUpdate - the user requires elevation to " +
"install the update.");
}
}
} else {
// Clear elevation-related prefs since they no longer apply (the user
// may have gained write access to the Firefox directory or an update
// was executed with a different profile).
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_VERSION)) {
Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_VERSION);
}
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS_OSX)) {
Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
}
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
}
}
} else if (!update) {
AUSTLMY.pingCheckCode(this._pingSuffix, lastCheckCode);
}
@ -2508,7 +2679,7 @@ UpdateService.prototype = {
}
var update = this.selectUpdate(updates, updates.length);
if (!update) {
if (!update || update.elevationFailure) {
return;
}
@ -2599,6 +2770,13 @@ UpdateService.prototype = {
return gCanCheckForUpdates && hasUpdateMutex();
},
/**
* See nsIUpdateService.idl
*/
get elevationRequired() {
return getElevationRequired();
},
/**
* See nsIUpdateService.idl
*/
@ -3045,8 +3223,9 @@ UpdateManager.prototype = {
for (let i = updates.length - 1; i >= 0; --i) {
let state = updates[i].state;
if (state == STATE_NONE || state == STATE_DOWNLOADING ||
state == STATE_APPLIED || state == STATE_APPLIED_SVC ||
state == STATE_PENDING || state == STATE_PENDING_SVC) {
state == STATE_APPLIED || state == STATE_APPLIED_SERVICE ||
state == STATE_PENDING || state == STATE_PENDING_SERVICE ||
state == STATE_PENDING_ELEVATE) {
updates.splice(i, 1);
}
}
@ -3077,13 +3256,15 @@ UpdateManager.prototype = {
}
}
if (update.state == STATE_APPLIED && shouldUseService()) {
writeStatusFile(getUpdatesDir(), update.state = STATE_APPLIED_SVC);
writeStatusFile(getUpdatesDir(), update.state = STATE_APPLIED_SERVICE);
}
var um = Cc["@mozilla.org/updates/update-manager;1"].
getService(Ci.nsIUpdateManager);
um.saveUpdates();
if (update.state != STATE_PENDING && update.state != STATE_PENDING_SVC) {
if (update.state != STATE_PENDING &&
update.state != STATE_PENDING_SERVICE &&
update.state != STATE_PENDING_ELEVATE) {
// Destroy the updates directory, since we're done with it.
// Make sure to not do this when the updater has fallen back to
// non-staged updates.
@ -3118,16 +3299,52 @@ UpdateManager.prototype = {
return;
}
if (update.state == STATE_APPLIED || update.state == STATE_APPLIED_SVC ||
update.state == STATE_PENDING || update.state == STATE_PENDING_SVC) {
// Notify the user that an update has been staged and is ready for
// installation (i.e. that they should restart the application).
if (update.state == STATE_APPLIED ||
update.state == STATE_APPLIED_SERVICE ||
update.state == STATE_PENDING ||
update.state == STATE_PENDING_SERVICE ||
update.state == STATE_PENDING_ELEVATE) {
// Notify the user that an update has been staged and is ready for
// installation (i.e. that they should restart the application).
let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
createInstance(Ci.nsIUpdatePrompt);
prompter.showUpdateDownloaded(update, true);
}
},
/**
* See nsIUpdateService.idl
*/
elevationOptedIn: function UM_elevationOptedIn() {
// The user has been been made aware that the update requires elevation.
let update = this._activeUpdate;
if (!update) {
return;
}
let status = readStatusFile(getUpdatesDir());
let parts = status.split(":");
update.state = parts[0];
if (update.state == STATE_PENDING_ELEVATE) {
// Proceed with the pending update.
// Note: STATE_PENDING_ELEVATE stands for "pending user's approval to
// proceed with an elevated update". As long as we see this state, we will
// notify the user of the availability of an update that requires
// elevation. |elevationOptedIn| (this function) is called when the user
// gives us approval to proceed, so we want to switch to STATE_PENDING.
// The updater then detects whether or not elevation is required and
// displays the elevation prompt if necessary. This last step does not
// depend on the state in the status file.
writeStatusFile(getUpdatesDir(), STATE_PENDING);
}
},
/**
* See nsIUpdateService.idl
*/
cleanupActiveUpdate: function UM_cleanupActiveUpdate() {
cleanupActiveUpdate();
},
classID: Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateManager, Ci.nsIObserver])
};
@ -3473,8 +3690,9 @@ Downloader.prototype = {
// Note that if we decide to download and apply new updates after another
// update has been successfully applied in the background, we need to stop
// checking for the APPLIED state here.
return readState == STATE_PENDING || readState == STATE_PENDING_SVC ||
readState == STATE_APPLIED || readState == STATE_APPLIED_SVC;
return readState == STATE_PENDING || readState == STATE_PENDING_SERVICE ||
readState == STATE_PENDING_ELEVATE ||
readState == STATE_APPLIED || readState == STATE_APPLIED_SERVICE;
},
/**
@ -3600,7 +3818,8 @@ Downloader.prototype = {
LOG("Downloader:_selectPatch - already downloaded and staged");
return null;
}
} else if (state == STATE_PENDING || state == STATE_PENDING_SVC) {
} else if (state == STATE_PENDING || state == STATE_PENDING_SERVICE ||
state == STATE_PENDING_ELEVATE) {
LOG("Downloader:_selectPatch - already downloaded and staged");
return null;
}
@ -3725,7 +3944,11 @@ Downloader.prototype = {
if (patchFile.fileSize == this._patch.size) {
LOG("Downloader:downloadUpdate - patchFile appears to be fully downloaded");
// Bump the status along so that we don't try to redownload again.
status = STATE_PENDING;
if (getElevationRequired()) {
status = STATE_PENDING_ELEVATE;
} else {
status = STATE_PENDING;
}
}
} else {
LOG("Downloader:downloadUpdate - patchFile " + patchFile.path +
@ -3738,7 +3961,11 @@ Downloader.prototype = {
// It looks like the patch was downloaded, but got interrupted while it
// was being verified or applied. So we'll fake the downloading portion.
writeStatusFile(updateDir, STATE_PENDING);
if (getElevationRequired()) {
writeStatusFile(updateDir, STATE_PENDING_ELEVATE);
} else {
writeStatusFile(updateDir, STATE_PENDING);
}
// Since the code expects the onStopRequest callback to happen
// asynchronously (And you have to call AUS_addDownloadListener
@ -3967,7 +4194,13 @@ Downloader.prototype = {
"max fail: " + maxFail + ", " + "retryTimeout: " + retryTimeout);
if (Components.isSuccessCode(status)) {
if (this._verifyDownload()) {
state = shouldUseService() ? STATE_PENDING_SVC : STATE_PENDING;
if (shouldUseService()) {
state = STATE_PENDING_SERVICE;
} else if (getElevationRequired()) {
state = STATE_PENDING_ELEVATE;
} else {
state = STATE_PENDING;
}
if (this.background) {
shouldShowPrompt = !getCanStageUpdates();
}
@ -4130,7 +4363,8 @@ Downloader.prototype = {
return;
}
if (state == STATE_PENDING || state == STATE_PENDING_SVC) {
if (state == STATE_PENDING || state == STATE_PENDING_SERVICE ||
state == STATE_PENDING_ELEVATE) {
if (getCanStageUpdates()) {
LOG("Downloader:onStopRequest - attempting to stage update: " +
this._update.name);
@ -4227,8 +4461,9 @@ UpdatePrompt.prototype = {
*/
showUpdateAvailable: function UP_showUpdateAvailable(update) {
if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
this._getUpdateWindow() || this._getAltUpdateWindow())
this._getUpdateWindow() || this._getAltUpdateWindow()) {
return;
}
this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
UPDATE_WINDOW_NAME, "updatesavailable", update);
@ -4262,23 +4497,15 @@ UpdatePrompt.prototype = {
showUpdateInstalled: function UP_showUpdateInstalled() {
if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
!getPref("getBoolPref", PREF_APP_UPDATE_SHOW_INSTALLED_UI, false) ||
this._getUpdateWindow())
this._getUpdateWindow()) {
return;
}
var page = "installed";
var win = this._getUpdateWindow();
if (win) {
if (page && "setCurrentPage" in win)
win.setCurrentPage(page);
win.focus();
}
else {
var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
var arg = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
arg.data = page;
Services.ww.openWindow(null, URI_UPDATE_PROMPT_DIALOG, null, openFeatures, arg);
}
let openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
let arg = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
arg.data = "installed";
Services.ww.openWindow(null, URI_UPDATE_PROMPT_DIALOG, null, openFeatures, arg);
},
/**
@ -4326,6 +4553,21 @@ UpdatePrompt.prototype = {
"Update:History", null, null);
},
/**
* See nsIUpdateService.idl
*/
showUpdateElevationRequired: function UP_showUpdateElevationRequired() {
if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
this._getAltUpdateWindow()) {
return;
}
let um = Cc["@mozilla.org/updates/update-manager;1"].
getService(Ci.nsIUpdateManager);
this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null,
UPDATE_WINDOW_NAME, "finishedBackground", um.activeUpdate);
},
/**
* Returns the update window if present.
*/

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

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.mozilla.updater</string>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>

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

@ -24,6 +24,9 @@ int InitProgressUI(int *argc, NS_tchar ***argv);
// Called on the main thread at startup
int ShowProgressUI(bool indeterminate = false, bool initUIStrings = true);
int InitProgressUIStrings();
#elif defined(XP_MACOSX)
// Called on the main thread at startup
int ShowProgressUI(bool indeterminate = false);
#else
// Called on the main thread at startup
int ShowProgressUI();

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

@ -14,7 +14,8 @@
#define TIMER_INTERVAL 0.2
static float sProgressVal; // between 0 and 100
static BOOL sQuit = FALSE;
static BOOL sQuit = NO;
static BOOL sIndeterminate = NO;
static StringTable sLabels;
static const char *sUpdatePath;
@ -30,7 +31,7 @@ static const char *sUpdatePath;
-(void)awakeFromNib
{
NSWindow *w = [progressBar window];
[w setTitle:[NSString stringWithUTF8String:sLabels.title]];
[progressTextField setStringValue:[NSString stringWithUTF8String:sLabels.info]];
@ -48,7 +49,7 @@ static const char *sUpdatePath;
[w center];
[progressBar setIndeterminate:NO];
[progressBar setIndeterminate:sIndeterminate];
[progressBar setDoubleValue:0.0];
[[NSTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self
@ -98,13 +99,13 @@ InitProgressUI(int *pargc, char ***pargv)
}
int
ShowProgressUI()
ShowProgressUI(bool indeterminate)
{
// Only show the Progress UI if the process is taking a significant amount of
// time where a significant amount of time is defined as .5 seconds after
// ShowProgressUI is called sProgress is less than 70.
usleep(500000);
if (sQuit || sProgressVal > 70.0f)
return 0;
@ -118,7 +119,8 @@ ShowProgressUI()
if (!(strlen(sLabels.title) < MAX_TEXT_LEN - 1 &&
strlen(sLabels.info) < MAX_TEXT_LEN - 1))
return -1;
sIndeterminate = indeterminate;
[NSApplication sharedApplication];
[NSBundle loadNibNamed:@"MainMenu" owner:NSApp];
[NSApp run];
@ -130,7 +132,7 @@ ShowProgressUI()
void
QuitProgressUI()
{
sQuit = TRUE;
sQuit = YES;
}
// Called on a background thread

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

@ -53,6 +53,9 @@
#include <algorithm>
#include "updatelogging.h"
#ifdef XP_MACOSX
#include "updaterfileutils_osx.h"
#endif // XP_MACOSX
#include "mozilla/Compiler.h"
#include "mozilla/Types.h"
@ -73,8 +76,19 @@
#if defined(XP_MACOSX)
// These functions are defined in launchchild_osx.mm
void LaunchChild(int argc, char **argv);
void CleanupElevatedMacUpdate(bool aFailureOccurred);
bool IsOwnedByGroupAdmin(const char* aAppBundle);
bool IsRecursivelyWritable(const char* aPath);
void LaunchChild(int argc, const char** argv);
void LaunchMacPostProcess(const char* aAppBundle);
bool ObtainUpdaterArguments(int* argc, char*** argv);
bool ServeElevatedUpdate(int argc, const char** argv);
void SetGroupOwnershipAndPermissions(const char* aAppBundle);
struct UpdateServerThreadArgs
{
int argc;
const NS_tchar** argv;
};
#endif
#ifndef _O_BINARY
@ -1995,7 +2009,7 @@ LaunchCallbackApp(const NS_tchar *workingDir,
#if defined(USE_EXECV)
execv(argv[0], argv);
#elif defined(XP_MACOSX)
LaunchChild(argc, argv);
LaunchChild(argc, (const char**)argv);
#elif defined(XP_WIN)
// Do not allow the callback to run when running an update through the
// service as session 0. The unelevated updater.exe will do the launching.
@ -2518,8 +2532,93 @@ UpdateThreadFunc(void *param)
QuitProgressUI();
}
#ifdef XP_MACOSX
static void
ServeElevatedUpdateThreadFunc(void* param)
{
UpdateServerThreadArgs* threadArgs = (UpdateServerThreadArgs*)param;
gSucceeded = ServeElevatedUpdate(threadArgs->argc, threadArgs->argv);
if (!gSucceeded) {
WriteStatusFile(ELEVATION_CANCELED);
}
QuitProgressUI();
}
void freeArguments(int argc, char** argv)
{
for (int i = 0; i < argc; i++) {
free(argv[i]);
}
free(argv);
}
#endif
int LaunchCallbackAndPostProcessApps(int argc, NS_tchar** argv,
int callbackIndex
#ifdef XP_WIN
, const WCHAR* elevatedLockFilePath
, HANDLE updateLockFileHandle
#elif XP_MACOSX
, bool isElevated
#endif
)
{
if (argc > callbackIndex) {
#if defined(XP_WIN)
if (gSucceeded) {
if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) {
fprintf(stderr, "The post update process was not launched");
}
// The service update will only be executed if it is already installed.
// For first time installs of the service, the install will happen from
// the PostUpdate process. We do the service update process here
// because it's possible we are updating with updater.exe without the
// service if the service failed to apply the update. We want to update
// the service to a newer version in that case. If we are not running
// through the service, then MOZ_USING_SERVICE will not exist.
if (!sUsingService) {
StartServiceUpdate(gInstallDirPath);
}
}
EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0);
#elif XP_MACOSX
if (!isElevated) {
if (gSucceeded) {
LaunchMacPostProcess(gInstallDirPath);
}
#endif
LaunchCallbackApp(argv[5],
argc - callbackIndex,
argv + callbackIndex,
sUsingService);
#ifdef XP_MACOSX
} // if (!isElevated)
#endif /* XP_MACOSX */
}
return 0;
}
int NS_main(int argc, NS_tchar **argv)
{
// The callback is the remaining arguments starting at callbackIndex.
// The argument specified by callbackIndex is the callback executable and the
// argument prior to callbackIndex is the working directory.
const int callbackIndex = 6;
#ifdef XP_MACOSX
bool isElevated =
strstr(argv[0], "/Library/PrivilegedHelperTools/org.mozilla.updater") != 0;
if (isElevated) {
if (!ObtainUpdaterArguments(&argc, &argv)) {
// Won't actually get here because ObtainUpdaterArguments will terminate
// the current process on failure.
return 1;
}
}
#endif
#if defined(MOZ_WIDGET_GONK)
if (EnvHasValue("LD_PRELOAD")) {
// If the updater is launched with LD_PRELOAD set, then we wind up
@ -2552,7 +2651,13 @@ int NS_main(int argc, NS_tchar **argv)
}
#endif
InitProgressUI(&argc, &argv);
#ifdef XP_MACOSX
if (!isElevated) {
#endif
InitProgressUI(&argc, &argv);
#ifdef XP_MACOSX
}
#endif
// To process an update the updater command line must at a minimum have the
// directory path containing the updater.mar file to process as the first
@ -2570,11 +2675,18 @@ int NS_main(int argc, NS_tchar **argv)
// launched.
if (argc < 4) {
fprintf(stderr, "Usage: updater patch-dir install-dir apply-to-dir [wait-pid [callback-working-dir callback-path args...]]\n");
#ifdef XP_MACOSX
if (isElevated) {
freeArguments(argc, argv);
CleanupElevatedMacUpdate(true);
}
#endif
return 1;
}
// The directory containing the update information.
gPatchDirPath = argv[1];
// The directory we're going to update to.
// We copy this string because we need to remove trailing slashes. The C++
// standard says that it's always safe to write to strings pointed to by argv
@ -2655,6 +2767,27 @@ int NS_main(int argc, NS_tchar **argv)
*slash = NS_T('\0');
}
#ifdef XP_MACOSX
if (!isElevated && !IsRecursivelyWritable(argv[2])) {
// If the app directory isn't recursively writeable, an elevated update is
// required.
UpdateServerThreadArgs threadArgs;
threadArgs.argc = argc;
threadArgs.argv = const_cast<const NS_tchar**>(argv);
Thread t1;
if (t1.Run(ServeElevatedUpdateThreadFunc, &threadArgs) == 0) {
// Show an indeterminate progress bar while an elevated update is in
// progress.
ShowProgressUI(true);
}
t1.Join();
LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex, false);
return gSucceeded ? 0 : 1;
}
#endif
if (EnvHasValue("MOZ_OS_UPDATE")) {
sIsOSUpdate = true;
putenv(const_cast<char*>("MOZ_OS_UPDATE="));
@ -2683,6 +2816,12 @@ int NS_main(int argc, NS_tchar **argv)
if (!WriteStatusFile("applying")) {
LOG(("failed setting status to 'applying'"));
#ifdef XP_MACOSX
if (isElevated) {
freeArguments(argc, argv);
CleanupElevatedMacUpdate(true);
}
#endif
return 1;
}
@ -2778,11 +2917,6 @@ int NS_main(int argc, NS_tchar **argv)
#endif
}
// The callback is the remaining arguments starting at callbackIndex.
// The argument specified by callbackIndex is the callback executable and the
// argument prior to callbackIndex is the working directory.
const int callbackIndex = 6;
#if defined(XP_WIN)
#ifdef MOZ_MAINTENANCE_SERVICE
sUsingService = EnvHasValue("MOZ_USING_SERVICE");
@ -3118,10 +3252,22 @@ int NS_main(int argc, NS_tchar **argv)
// Try changing the current directory again
if (NS_tchdir(gWorkingDirPath) != 0) {
// OK, time to give up!
#ifdef XP_MACOSX
if (isElevated) {
freeArguments(argc, argv);
CleanupElevatedMacUpdate(true);
}
#endif
return 1;
}
} else {
// Failed to create the directory, bail out
#ifdef XP_MACOSX
if (isElevated) {
freeArguments(argc, argv);
CleanupElevatedMacUpdate(true);
}
#endif
return 1;
}
}
@ -3334,12 +3480,17 @@ int NS_main(int argc, NS_tchar **argv)
}
#endif /* XP_WIN */
// Run update process on a background thread. ShowProgressUI may return
// Run update process on a background thread. ShowProgressUI may return
// before QuitProgressUI has been called, so wait for UpdateThreadFunc to
// terminate. Avoid showing the progress UI when staging an update.
// terminate. Avoid showing the progress UI when staging an update, or if this
// is an elevated process on OSX.
Thread t;
if (t.Run(UpdateThreadFunc, nullptr) == 0) {
if (!sStagedUpdate && !sReplaceRequest) {
if (!sStagedUpdate && !sReplaceRequest
#ifdef XP_MACOSX
&& !isElevated
#endif
) {
ShowProgressUI();
}
}
@ -3418,43 +3569,32 @@ int NS_main(int argc, NS_tchar **argv)
}
}
}
if (isElevated) {
SetGroupOwnershipAndPermissions(gInstallDirPath);
freeArguments(argc, argv);
CleanupElevatedMacUpdate(false);
} else if (IsOwnedByGroupAdmin(gInstallDirPath)) {
// If the group ownership of the Firefox .app bundle was set to the "admin"
// group during a previous elevated update, we need to ensure that all files
// in the bundle have group ownership of "admin" as well as write permission
// for the group to not break updates in the future.
SetGroupOwnershipAndPermissions(gInstallDirPath);
}
#endif /* XP_MACOSX */
LogFinish();
if (argc > callbackIndex) {
#if defined(XP_WIN)
if (gSucceeded) {
if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) {
fprintf(stderr, "The post update process was not launched");
}
int retVal = LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex
#ifdef XP_WIN
, elevatedLockFilePath
, updateLockFileHandle
#elif XP_MACOSX
, isElevated
#endif
);
// The service update will only be executed if it is already installed.
// For first time installs of the service, the install will happen from
// the PostUpdate process. We do the service update process here
// because it's possible we are updating with updater.exe without the
// service if the service failed to apply the update. We want to update
// the service to a newer version in that case. If we are not running
// through the service, then MOZ_USING_SERVICE will not exist.
if (!sUsingService) {
StartServiceUpdate(gInstallDirPath);
}
}
EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0);
#endif /* XP_WIN */
#ifdef XP_MACOSX
if (gSucceeded) {
LaunchMacPostProcess(gInstallDirPath);
}
#endif /* XP_MACOSX */
LaunchCallbackApp(argv[5],
argc - callbackIndex,
argv + callbackIndex,
sUsingService);
}
return gSucceeded ? 0 : 1;
return retVal ? retVal : (gSucceeded ? 0 : 1);
}
class ActionList

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

@ -31,6 +31,7 @@
#include "nsILocalFileMac.h"
#include "nsCommandLineServiceMac.h"
#include "MacLaunchHelper.h"
#include "updaterfileutils_osx.h"
#endif
#if defined(XP_WIN)
@ -83,6 +84,8 @@ GetUpdateLog()
#ifdef XP_WIN
#define UPDATER_BIN "updater.exe"
#elif XP_MACOSX
#define UPDATER_BIN "org.mozilla.updater"
#else
#define UPDATER_BIN "updater"
#endif
@ -251,8 +254,9 @@ typedef enum {
eNoUpdateAction,
ePendingUpdate,
ePendingService,
ePendingElevate,
eAppliedUpdate,
eAppliedService
eAppliedService,
} UpdateStatus;
/**
@ -271,8 +275,12 @@ GetUpdateStatus(nsIFile* dir, nsCOMPtr<nsIFile> &statusFile)
if (GetStatusFileContents(statusFile, buf)) {
const char kPending[] = "pending";
const char kPendingService[] = "pending-service";
const char kPendingElevate[] = "pending-elevate";
const char kApplied[] = "applied";
const char kAppliedService[] = "applied-service";
if (!strncmp(buf, kPendingElevate, sizeof(kPendingElevate) - 1)) {
return ePendingElevate;
}
if (!strncmp(buf, kPendingService, sizeof(kPendingService) - 1)) {
return ePendingService;
}
@ -952,13 +960,21 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
_exit(0);
}
#elif defined(XP_MACOSX)
CommandLineServiceMac::SetupMacCommandLine(argc, argv, true);
// LaunchChildMac uses posix_spawnp and prefers the current
// architecture when launching. It doesn't require a
// null-terminated string but it doesn't matter if we pass one.
LaunchChildMac(argc, argv, 0, outpid);
if (restart) {
CommandLineServiceMac::SetupMacCommandLine(argc, argv, restart);
// We need to detect whether elevation is required for this update. This can
// occur when an admin user installs the application, but another admin
// user attempts to update (see bug 394984).
if (restart && !IsRecursivelyWritable(installDirPath.get())) {
if (!LaunchElevatedUpdate(argc, argv, 0, outpid)) {
LOG(("Failed to launch elevated update!"));
exit(1);
}
exit(0);
} else {
LaunchChildMac(argc, argv, 0, outpid);
if (restart) {
exit(0);
}
}
#else
*outpid = PR_CreateProcess(updaterPath.get(), argv, nullptr, nullptr);
@ -1023,6 +1039,19 @@ ProcessUpdates(nsIFile *greDir, nsIFile *appDir, nsIFile *updRootDir,
nsCOMPtr<nsIFile> statusFile;
UpdateStatus status = GetUpdateStatus(updatesDir, statusFile);
switch (status) {
case ePendingElevate: {
if (NS_IsMainThread()) {
// Only do this if we're called from the main thread.
nsCOMPtr<nsIUpdatePrompt> up =
do_GetService("@mozilla.org/updates/update-prompt;1");
if (up) {
up->ShowUpdateElevationRequired();
}
break;
}
// Intentional fallthrough to ePendingUpdate and ePendingService.
MOZ_FALLTHROUGH;
}
case ePendingUpdate:
case ePendingService: {
nsCOMPtr<nsIFile> versionFile;