Bug 1458308 - Move app.update.auto to be stored in the update directory on Windows only r=rstrong

This patch additionally includes support for automatic migration of the pref from its old location to its new location.

This patch does not fix telemetry reporting of app.update.auto - that will be addressed in another patch in the same series.

MozReview-Commit-ID: KjX1mmGVB8M

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Kirk Steuber 2018-10-29 21:35:25 +00:00
Родитель c53f1b6100
Коммит ac50067b73
4 изменённых файлов: 209 добавлений и 15 удалений

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

@ -134,8 +134,15 @@ pref("app.update.download.promptMaxAttempts", 2);
pref("app.update.elevation.promptMaxAttempts", 2);
// If set to true, the Update Service will automatically download updates if the
// user can apply updates.
// user can apply updates. This pref is no longer used on Windows, except as the
// default value to migrate to the new location that this data is now stored
// (which is in a file in the update directory). Because of this, this pref
// should no longer be used directly. Instead,
// nsIUpdateService::getAutoUpdateIsEnabled and
// nsIUpdateService::setAutoUpdateIsEnabled should be used.
#ifndef XP_WIN
pref("app.update.auto", true);
#endif
// If set to true, the Update Service will present no UI for any event.
pref("app.update.silent", false);

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

@ -40,6 +40,7 @@ function appUpdater(options = {}) {
this.options = options;
this.updateDeck = document.getElementById("updateDeck");
this.promiseAutoUpdateSetting = null;
// Hide the update deck when the update window is already open and it's not
// already applied, to avoid syncing issues between them. Applied updates
@ -81,6 +82,9 @@ function appUpdater(options = {}) {
return;
}
// We might need this value later, so start loading it from the disk now.
this.promiseAutoUpdateSetting = this.aus.getAutoUpdateIsEnabled();
// That leaves the options
// "Check for updates, but let me choose whether to install them", and
// "Automatically install updates".
@ -136,14 +140,6 @@ appUpdater.prototype =
gAppUpdater.aus.canStageUpdates;
},
// true when updating is automatic.
get updateAuto() {
try {
return Services.prefs.getBoolPref("app.update.auto");
} catch (e) { }
return true; // Firefox default is true
},
/**
* Sets the panel of the updateDeck.
*
@ -260,10 +256,16 @@ appUpdater.prototype =
return;
}
if (gAppUpdater.updateAuto) // automatically download and install
gAppUpdater.startDownload();
else // ask
gAppUpdater.selectPanel("downloadAndInstall");
if (this.promiseAutoUpdateSetting == null) {
this.promiseAutoUpdateSetting = this.aus.getAutoUpdateIsEnabled();
}
this.promiseAutoUpdateSetting.then(updateAuto => {
if (updateAuto) { // automatically download and install
gAppUpdater.startDownload();
} else { // ask
gAppUpdater.selectPanel("downloadAndInstall");
}
});
},
/**

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

@ -362,6 +362,48 @@ interface nsIApplicationUpdateService : nsISupports
*/
readonly attribute boolean canCheckForUpdates;
/**
* Determines whether or not the Update Service automatically downloads and
* installs updates. This corresponds to whether or not the user has selected
* "Automatically install updates" in about:preferences.
*
* On Windows, this setting is shared across all profiles for the
* installation. On other operating systems, this setting is stored in a pref
* and is thus a per-profile setting.
*
* Note: On Windows, this setting is stored in a file on the disk, so this
* operation involves reading that file.
*
* @return A Promise that resolves with a boolean.
*/
jsval getAutoUpdateIsEnabled();
/**
* Toggles whether the Update Service automatically downloads and installs
* updates. This effectively selects between the "Automatically install
* updates" and "Check for updates but let you choose to install them" options
* in about:preferences.
*
* On Windows, this setting is shared across all profiles for the
* installation. On other operating systems, this setting is stored in a pref
* and is thus a per-profile setting.
*
* Note: On Windows, this setting is stored in a file on disk, so this
* operation involves writing to that file.
*
* @param enabled If set to true, automatic download and installation of
* updates will be enabled. If set to false, this will be
* disabled.
* @return A Promise that, once the setting has been saved, resolves with the
* boolean value that was saved. If the setting could not be
* successfully saved, the Promise will reject.
* On Windows, where this setting is stored in a file, this Promise
* may reject with an I/O error.
* On other operating systems, this promise should not reject as
* this operation simply sets a pref.
*/
jsval setAutoUpdateIsEnabled(in boolean enabled);
/**
* Whether or not the installation requires elevation. Currently only
* implemented on OSX, returns false on other platforms.

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

@ -12,12 +12,19 @@ ChromeUtils.import("resource://gre/modules/Services.jsm", this);
ChromeUtils.import("resource://gre/modules/ctypes.jsm", this);
ChromeUtils.import("resource://gre/modules/UpdateTelemetry.jsm", this);
ChromeUtils.import("resource://gre/modules/AppConstants.jsm", this);
ChromeUtils.import("resource://gre/modules/osfile.jsm", this);
XPCOMUtils.defineLazyGlobalGetters(this, ["DOMParser", "XMLHttpRequest"]);
const UPDATESERVICE_CID = Components.ID("{B3C290A6-3943-4B89-8BBE-C01EB7B3B311}");
const PREF_APP_UPDATE_ALTWINDOWTYPE = "app.update.altwindowtype";
// Do not use PREF_APP_UPDATE_AUTO directly! Call getAutoUpdateIsEnabled or
// setAutoUpdateIsEnabled.
// See nsIApplicationUpdateService::getAutoUpdateIsEnabled and
// nsIApplicationUpdateService::setAutoUpdateIsEnabled in nsIUpdateService.idl
// for additional details.
const PREF_APP_UPDATE_AUTO = "app.update.auto";
const PREF_APP_UPDATE_AUTO_MIGRATED = "app.update.auto.migrated";
const PREF_APP_UPDATE_BACKGROUNDINTERVAL = "app.update.download.backgroundInterval";
const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors";
const PREF_APP_UPDATE_BACKGROUNDMAXERRORS = "app.update.backgroundMaxErrors";
@ -46,6 +53,14 @@ const PREF_APP_UPDATE_SOCKET_RETRYTIMEOUT = "app.update.socket.retryTimeout";
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";
// Note that although this value has the same format as those above, it is very
// different. It is not stored as part of the user's prefs. Instead it is stored
// in the file indicated by FILE_UPDATE_CONFIG_JSON , which will be located in
// the update directory. This allows it to be accessible from all Firefox
// profiles and from the Background Update Agent.
const CONFIG_APP_UPDATE_AUTO = "app.update.auto";
// The default value for the above setting
const DEFAULT_APP_UPDATE_AUTO = true;
const URI_BRAND_PROPERTIES = "chrome://branding/locale/brand.properties";
const URI_UPDATE_HISTORY_DIALOG = "chrome://mozapps/content/update/history.xul";
@ -62,6 +77,7 @@ const FILE_ACTIVE_UPDATE_XML = "active-update.xml";
const FILE_BACKUP_UPDATE_LOG = "backup-update.log";
const FILE_LAST_UPDATE_LOG = "last-update.log";
const FILE_UPDATES_XML = "updates.xml";
const FILE_UPDATE_CONFIG_JSON = "update-config.json";
const FILE_UPDATE_LOG = "update.log";
const FILE_UPDATE_MAR = "update.mar";
const FILE_UPDATE_STATUS = "update.status";
@ -214,6 +230,8 @@ XPCOMUtils.defineLazyGetter(this, "gUpdateBundle", function aus_gUpdateBundle()
return Services.strings.createBundle(URI_UPDATES_PROPERTIES);
});
XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => new TextDecoder());
/**
* Tests to make sure that we can write to a given directory.
*
@ -2296,7 +2314,7 @@ UpdateService.prototype = {
* @param updates
* An array of available updates
*/
_selectAndInstallUpdate: function AUS__selectAndInstallUpdate(updates) {
_selectAndInstallUpdate: async function AUS__selectAndInstallUpdate(updates) {
// Return early if there's an active update. The user is already aware and
// is downloading or performed some user action to prevent notification.
var um = Cc["@mozilla.org/updates/update-manager;1"].
@ -2359,7 +2377,8 @@ UpdateService.prototype = {
* Major Notify
* Minor Auto Install
*/
if (!Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO, true)) {
let updateAuto = await this.getAutoUpdateIsEnabled();
if (!updateAuto) {
LOG("UpdateService:_selectAndInstallUpdate - prompting because silent " +
"install is disabled. Notifying observers. topic: update-available, " +
"status: show-prompt");
@ -2442,6 +2461,130 @@ UpdateService.prototype = {
return true;
},
_updateAutoSettingCachedVal: null,
// Used for serializing reads and writes of the app update json config file.
// This is especially important for making sure writes don't happen out of
// order. It would be bad if a user double-toggling this setting resulted in a
// race condition. The last write must be the one that ultimately controls
// the setting's value.
_updateAutoIOPromise: Promise.resolve(),
_readUpdateAutoConfig: async function AUS__readUpdateAuto() {
let configFile = getUpdateFile([FILE_UPDATE_CONFIG_JSON ]);
let binaryData = await OS.File.read(configFile.path);
let jsonData = gTextDecoder.decode(binaryData);
let configData = JSON.parse(jsonData);
return !!configData[CONFIG_APP_UPDATE_AUTO];
},
_writeUpdateAutoConfig: async function AUS__writeUpdateAutoPref(enabledValue) {
let enabledBoolValue = !!enabledValue;
let configFile = getUpdateFile([FILE_UPDATE_CONFIG_JSON]);
let configObject = {[CONFIG_APP_UPDATE_AUTO]: enabledBoolValue};
await OS.File.writeAtomic(configFile.path, JSON.stringify(configObject));
return enabledBoolValue;
},
// If the value of app.update.auto has changed, notify observers. Returns the
// new value for app.update.auto.
_maybeUpdateAutoConfigChanged: function AUS__maybeUpdateAutoConfigChanged(newValue) {
if (newValue != this._updateAutoSettingCachedVal) {
this._updateAutoSettingCachedVal = newValue;
Services.obs.notifyObservers(null, "auto-update-config-change",
newValue.toString());
}
return newValue;
},
/**
* See nsIUpdateService.idl
*/
getAutoUpdateIsEnabled: function AUS_getAutoUpdateIsEnabled() {
if (AppConstants.platform != "win") {
// Only in Windows do we store the update config in the update directory
let prefValue = Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO,
DEFAULT_APP_UPDATE_AUTO);
return Promise.resolve(prefValue);
}
// Justification for the empty catch statement below:
// All promises returned by (get|set)AutoUpdateIsEnabled are part of a
// single promise chain in order to serialize disk operations. We don't want
// the entire promise chain to reject when one operation fails.
//
// There is only one situation when a promise in this chain should ever
// reject, which is when writing fails and the error is logged and
// re-thrown. All other possible exceptions are wrapped in try blocks, which
// also log any exception that may occur.
let readPromise = this._updateAutoIOPromise.catch(() => {}).then(async () => {
try {
let configValue = await this._readUpdateAutoConfig();
// If we read a value out of this file, don't later perform migration.
// If the file is deleted, we don't want some stale pref getting
// written to it just because a different profile performed migration.
Services.prefs.setBoolPref(PREF_APP_UPDATE_AUTO_MIGRATED, true);
return configValue;
} catch (e) {
LOG("UpdateService.getAutoUpdateIsEnabled - Unable to read app " +
"update configuration file. Exception: " + e);
let valueMigrated = Services.prefs.getBoolPref(
PREF_APP_UPDATE_AUTO_MIGRATED,
false);
if (!valueMigrated) {
LOG("UpdateService.getAutoUpdateIsEnabled - Attempting migration.");
Services.prefs.setBoolPref(PREF_APP_UPDATE_AUTO_MIGRATED, true);
let prefValue = Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO,
DEFAULT_APP_UPDATE_AUTO);
try {
return await this._writeUpdateAutoConfig(prefValue);
} catch (e) {
LOG("UpdateService.getAutoUpdateIsEnabled - Migration failed. " +
"Exception: " + e);
}
}
}
// Fallthrough for if the value could not be read or migrated.
return DEFAULT_APP_UPDATE_AUTO;
}).then(this._maybeUpdateAutoConfigChanged.bind(this));
this._updateAutoIOPromise = readPromise;
return readPromise;
},
/**
* See nsIUpdateService.idl
*/
setAutoUpdateIsEnabled: function AUS_setAutoUpdateIsEnabled(enabledValue) {
if (AppConstants.platform != "win") {
// Only in Windows do we store the update config in the update directory
let prefValue = !!enabledValue;
Services.prefs.setBoolPref(PREF_APP_UPDATE_AUTO, prefValue);
this._maybeUpdateAutoConfigChanged(prefValue);
return Promise.resolve(prefValue);
}
// Justification for the empty catch statement below:
// All promises returned by (get|set)AutoUpdateIsEnabled are part of a
// single promise chain in order to serialize disk operations. We don't want
// the entire promise chain to reject when one operation fails.
//
// There is only one situation when a promise in this chain should ever
// reject, which is when writing fails and the error is logged and
// re-thrown. All other possible exceptions are wrapped in try blocks, which
// also log any exception that may occur.
let writePromise = this._updateAutoIOPromise.catch(() => {}).then(async () => {
try {
return await this._writeUpdateAutoConfig(enabledValue);
} catch (e) {
LOG("UpdateService.setAutoUpdateIsEnabled - App update configuration " +
"file write failed. Exception: " + e);
// After logging, rethrow the error so the caller knows that writing the
// value in the app update config file failed.
throw e;
}
}).then(this._maybeUpdateAutoConfigChanged.bind(this));
this._updateAutoIOPromise = writePromise;
return writePromise;
},
/**
* See nsIUpdateService.idl
*/