Bug 1875502 - Create standardized update init flow r=nalexander,settings-reviewers,application-update-reviewers

A number of changes are made as part of this patch:
 - A consistent way of initializing update is created. This is automatically run when methods that need it are invoked.
 - The "post-update-processing" notification has been removed. Post update processing is now done through the new `nsIApplicationUpdateService.init` or `nsIApplicationUpdateService.internal.postUpdateProcessing`.
 - Post update processing no longer waits for the sessionstore-windows-restored observer service notification
 - Post update processing is no longer invoked only when the `update.status` file exists. It is now run unconditionally.
 - Explicitly initialize before we potentially clean up updates from the update UI.

Note that the update service stub and a few consumers of it ought to be able to wait for post update processing to be done but they currently do not. That will be done later in this patch stack when we rework the stub.

Differential Revision: https://phabricator.services.mozilla.com/D209126
This commit is contained in:
Robin Steuber
2024-05-15 17:06:17 +00:00
parent 137f3934b2
commit 2b97ef7e76
5 changed files with 304 additions and 144 deletions

View File

@@ -2549,6 +2549,9 @@ var gMainPane = {
let um = Cc["@mozilla.org/updates/update-manager;1"].getService( let um = Cc["@mozilla.org/updates/update-manager;1"].getService(
Ci.nsIUpdateManager Ci.nsIUpdateManager
); );
// We don't want to see an idle state just because the updater hasn't
// initialized yet.
await aus.init();
if (aus.currentState == Ci.nsIApplicationUpdateService.STATE_IDLE) { if (aus.currentState == Ci.nsIApplicationUpdateService.STATE_IDLE) {
return; return;
} }

View File

@@ -47,6 +47,12 @@ XPCOMUtils.defineLazyServiceGetter(
"@mozilla.org/updates/update-checker;1", "@mozilla.org/updates/update-checker;1",
"nsIUpdateChecker" "nsIUpdateChecker"
); );
XPCOMUtils.defineLazyServiceGetter(
lazy,
"UpdateServiceStub",
"@mozilla.org/updates/update-service-stub;1",
"nsIApplicationUpdateServiceStub"
);
if (AppConstants.ENABLE_WEBDRIVER) { if (AppConstants.ENABLE_WEBDRIVER) {
XPCOMUtils.defineLazyServiceGetter( XPCOMUtils.defineLazyServiceGetter(
@@ -270,17 +276,6 @@ const DEFAULT_SOCKET_RETRYTIMEOUT = 2000;
// giving up. // giving up.
const DEFAULT_CANCELATIONS_OSX_MAX = 3; const DEFAULT_CANCELATIONS_OSX_MAX = 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 = {
// Firefox
"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}": "sessionstore-windows-restored",
// SeaMonkey
"{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": "sessionstore-windows-restored",
// Thunderbird
"{3550f703-e582-4d05-9a08-453d09bdfdc6}": "mail-startup-done",
};
// The interval for the update xml write deferred task. // The interval for the update xml write deferred task.
const XML_SAVER_INTERVAL_MS = 200; const XML_SAVER_INTERVAL_MS = 200;
@@ -1401,7 +1396,16 @@ function cleanUpReadyUpdateDir(aRemovePatchFiles = true) {
} }
if (aRemovePatchFiles) { if (aRemovePatchFiles) {
let dirEntries = updateDir.directoryEntries; let dirEntries;
try {
dirEntries = updateDir.directoryEntries;
} catch (ex) {
// If if doesn't exist, our job is already done.
if (ex.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
return;
}
throw ex;
}
while (dirEntries.hasMoreElements()) { while (dirEntries.hasMoreElements()) {
let file = dirEntries.nextFile; let file = dirEntries.nextFile;
// Now, recursively remove this file. The recursive removal is needed for // Now, recursively remove this file. The recursive removal is needed for
@@ -1434,7 +1438,16 @@ function cleanUpDownloadingUpdateDir() {
return; return;
} }
let dirEntries = updateDir.directoryEntries; let dirEntries;
try {
dirEntries = updateDir.directoryEntries;
} catch (ex) {
// If if doesn't exist, our job is already done.
if (ex.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
return;
}
throw ex;
}
while (dirEntries.hasMoreElements()) { while (dirEntries.hasMoreElements()) {
let file = dirEntries.nextFile; let file = dirEntries.nextFile;
// Now, recursively remove this file. // Now, recursively remove this file.
@@ -2653,6 +2666,8 @@ class Update {
} }
export class UpdateService { export class UpdateService {
#initPromise;
/** /**
* The downloader we are using to download updates. There is only ever one of * The downloader we are using to download updates. There is only ever one of
* these. * these.
@@ -2698,7 +2713,9 @@ export class UpdateService {
this._logStatus(); this._logStatus();
this.internal = { this.internal = {
init: async () => this.#init(),
downloadUpdate: async update => this.#downloadUpdate(update), downloadUpdate: async update => this.#downloadUpdate(update),
postUpdateProcessing: async () => this._postUpdateProcessing(),
stopDownload: async () => this.#stopDownload(), stopDownload: async () => this.#stopDownload(),
QueryInterface: ChromeUtils.generateQI([ QueryInterface: ChromeUtils.generateQI([
Ci.nsIApplicationUpdateServiceInternal, Ci.nsIApplicationUpdateServiceInternal,
@@ -2706,6 +2723,24 @@ export class UpdateService {
}; };
} }
/**
* See nsIUpdateService.idl
*/
async init() {
await lazy.UpdateServiceStub.initUpdate();
}
/**
* See nsIApplicationUpdateServiceInternal.init in nsIUpdateService.idl
*/
async #init() {
if (!this.#initPromise) {
this.#initPromise = this._postUpdateProcessing();
}
await this.#initPromise;
}
/** /**
* Handle Observer Service notifications * Handle Observer Service notifications
* @param subject * @param subject
@@ -2717,42 +2752,6 @@ export class UpdateService {
*/ */
async observe(subject, topic, data) { async observe(subject, topic, data) {
switch (topic) { switch (topic) {
case "post-update-processing":
// This pref was not cleared out of profiles after it stopped being used
// (Bug 1420514), so clear it out on the next update to avoid confusion
// regarding its use.
Services.prefs.clearUserPref("app.update.enabled");
Services.prefs.clearUserPref("app.update.BITS.inTrialGroup");
// Background tasks do not notify any delayed startup notifications.
if (
!lazy.gIsBackgroundTaskMode &&
Services.appinfo.ID in APPID_TO_TOPIC
) {
// Delay post-update processing to ensure that possible update
// dialogs are shown in front of the app window, if possible.
// See bug 311614.
Services.obs.addObserver(this, APPID_TO_TOPIC[Services.appinfo.ID]);
break;
}
// intentional fallthrough
case "sessionstore-windows-restored":
case "mail-startup-done":
// Background tasks do not notify any delayed startup notifications.
if (
!lazy.gIsBackgroundTaskMode &&
Services.appinfo.ID in APPID_TO_TOPIC
) {
Services.obs.removeObserver(
this,
APPID_TO_TOPIC[Services.appinfo.ID]
);
}
// intentional fallthrough
case "test-post-update-processing":
// Clean up any extant updates
await this._postUpdateProcessing();
break;
case "network:offline-status-changed": case "network:offline-status-changed":
await this._offlineStatusChanged(data); await this._offlineStatusChanged(data);
break; break;
@@ -2815,18 +2814,12 @@ export class UpdateService {
* from a previous application session - either report install failures (and * from a previous application session - either report install failures (and
* optionally attempt to fetch a different version if appropriate) or * optionally attempt to fetch a different version if appropriate) or
* notify the user of install success. * notify the user of install success.
*
* This will only be called during `UpdateService` initialization and should
* not be called again (with possible exceptions in testing).
*/ */
/* eslint-disable-next-line complexity */ /* eslint-disable-next-line complexity */
async _postUpdateProcessing() { async _postUpdateProcessing() {
if (this.disabled) {
// This function is a point when we can potentially enter the update
// system, even with update disabled. Make sure that we do not continue
// because update code can have side effects that are visible to the user
// and give the impression that updates are enabled. For example, if we
// can't write to the update directory, we might complain to the user that
// update is broken and they should reinstall.
return;
}
if (!this.canCheckForUpdates) { if (!this.canCheckForUpdates) {
LOG( LOG(
"UpdateService:_postUpdateProcessing - unable to check for " + "UpdateService:_postUpdateProcessing - unable to check for " +
@@ -2834,7 +2827,8 @@ export class UpdateService {
); );
return; return;
} }
let status = readStatusFile(getReadyUpdateDir()); const readyUpdateDir = getReadyUpdateDir();
let status = readStatusFile(readyUpdateDir);
LOG(`UpdateService:_postUpdateProcessing - status = "${status}"`); LOG(`UpdateService:_postUpdateProcessing - status = "${status}"`);
if (!this.canApplyUpdates) { if (!this.canApplyUpdates) {
@@ -2862,24 +2856,40 @@ export class UpdateService {
} }
if (status == STATE_NONE) { if (status == STATE_NONE) {
// A status of STATE_NONE in _postUpdateProcessing means that the // A status of STATE_NONE in _postUpdateProcessing means that the there
// update.status file is present but there isn't an update in progress. // isn't an update in progress. Let's make sure we initialize into a good
// This isn't an expected state, so if we find ourselves in it, we want // state.
// to just clean things up to go back to a good state. // Under some edgecases such as Windows system restore the
LOG( // active-update.xml will contain a pending update without the status
"UpdateService:_postUpdateProcessing - Cleaning up unexpected state." // file. We need to deal with that situation gracefully.
); LOG("UpdateService:_postUpdateProcessing - Initial cleanup");
if (!updates.length) {
updates.push(new Update(null)); const statusFile = readyUpdateDir.clone();
statusFile.append(FILE_UPDATE_STATUS);
if (statusFile.exists()) {
// This file existing but not having a state in it is unexpected. In
// addition to putting things in a good state, put a null update in the
// update history if we didn't start up with any.
LOG("UpdateService:_postUpdateProcessing - Status file is empty?");
if (!updates.length) {
updates.push(new Update(null));
}
} }
for (let update of updates) {
update.state = STATE_FAILED; // There shouldn't be any updates when the update status is null. Mark any
update.errorCode = ERR_UPDATE_STATE_NONE; // updates that are in there as having failed.
update.statusText = if (updates.length) {
lazy.gUpdateBundle.GetStringFromName("statusFailed"); for (let update of updates) {
update.state = STATE_FAILED;
update.errorCode = ERR_UPDATE_STATE_NONE;
update.statusText =
lazy.gUpdateBundle.GetStringFromName("statusFailed");
}
let newStatus = STATE_FAILED + ": " + ERR_UPDATE_STATE_NONE;
pingStateAndStatusCodes(updates[0], true, newStatus);
} }
let newStatus = STATE_FAILED + ": " + ERR_UPDATE_STATE_NONE;
pingStateAndStatusCodes(updates[0], true, newStatus);
cleanupActiveUpdates(); cleanupActiveUpdates();
return; return;
} }
@@ -3009,7 +3019,12 @@ export class UpdateService {
let result = await this.#downloadUpdate( let result = await this.#downloadUpdate(
lazy.UM.internal.downloadingUpdate lazy.UM.internal.downloadingUpdate
); );
if (result != Ci.nsIApplicationUpdateService.DOWNLOAD_SUCCESS) { if (
result != Ci.nsIApplicationUpdateService.DOWNLOAD_SUCCESS &&
result !=
Ci.nsIApplicationUpdateService
.DOWNLOAD_FAILURE_CANNOT_RESUME_IN_BACKGROUND
) {
LOG( LOG(
"UpdateService:_postUpdateProcessing - Failed to resume patch. " + "UpdateService:_postUpdateProcessing - Failed to resume patch. " +
"Cleaning up downloading update." "Cleaning up downloading update."
@@ -3398,6 +3413,8 @@ export class UpdateService {
* application update timer notification. * application update timer notification.
*/ */
async _checkForBackgroundUpdates(isNotify) { async _checkForBackgroundUpdates(isNotify) {
await this.init();
if (!this.disabled && AppConstants.NIGHTLY_BUILD) { if (!this.disabled && AppConstants.NIGHTLY_BUILD) {
// Scalar ID: update.suppress_prompts // Scalar ID: update.suppress_prompts
AUSTLMY.pingSuppressPrompts(); AUSTLMY.pingSuppressPrompts();
@@ -3770,6 +3787,7 @@ export class UpdateService {
* See nsIUpdateService.idl * See nsIUpdateService.idl
*/ */
async selectUpdate(updates) { async selectUpdate(updates) {
await this.init();
return this.#selectUpdate(updates); return this.#selectUpdate(updates);
} }
@@ -4093,6 +4111,7 @@ export class UpdateService {
* See nsIUpdateService.idl * See nsIUpdateService.idl
*/ */
async downloadUpdate(update) { async downloadUpdate(update) {
await this.init();
return this.#downloadUpdate(update); return this.#downloadUpdate(update);
} }
@@ -4164,6 +4183,7 @@ export class UpdateService {
* See nsIUpdateService.idl * See nsIUpdateService.idl
*/ */
async stopDownload() { async stopDownload() {
await this.init();
return this.#stopDownload(); return this.#stopDownload();
} }
@@ -4330,29 +4350,7 @@ export class UpdateManager {
} }
let status = readStatusFile(getReadyUpdateDir()); let status = readStatusFile(getReadyUpdateDir());
LOG(`UpdateManager:UpdateManager - status = "${status}"`); LOG(`UpdateManager:UpdateManager - status = "${status}"`);
// This check is performed here since UpdateService:_postUpdateProcessing if (status == STATE_DOWNLOADING) {
// won't be called when there isn't an update.status file.
if (status == STATE_NONE) {
// Under some edgecases such as Windows system restore the
// active-update.xml will contain a pending update without the status
// file. To recover from this situation clean the updates dir and move
// the active update to the update history.
LOG(
"UpdateManager:UpdateManager - Found update data with no status " +
"file. Cleaning up..."
);
this._readyUpdate.state = STATE_FAILED;
this._readyUpdate.errorCode = ERR_UPDATE_STATE_NONE;
this._readyUpdate.statusText =
lazy.gUpdateBundle.GetStringFromName("statusFailed");
let newStatus = STATE_FAILED + ": " + ERR_UPDATE_STATE_NONE;
pingStateAndStatusCodes(this._readyUpdate, true, newStatus);
this.#addUpdateToHistory(this._readyUpdate);
this._readyUpdate = null;
this.saveUpdates();
cleanUpReadyUpdateDir();
cleanUpDownloadingUpdateDir();
} else if (status == STATE_DOWNLOADING) {
// The first update we read out of activeUpdates may not be the ready // The first update we read out of activeUpdates may not be the ready
// update, it may be the downloading update. // update, it may be the downloading update.
if (this._downloadingUpdate) { if (this._downloadingUpdate) {
@@ -4595,6 +4593,7 @@ export class UpdateManager {
* See nsIUpdateService.idl * See nsIUpdateService.idl
*/ */
async getHistory() { async getHistory() {
await lazy.AUS.init();
return lazy.UM.internal.getHistory(); return lazy.UM.internal.getHistory();
} }
@@ -4602,6 +4601,7 @@ export class UpdateManager {
* See nsIUpdateService.idl * See nsIUpdateService.idl
*/ */
async getReadyUpdate() { async getReadyUpdate() {
await lazy.AUS.init();
return this._readyUpdate; return this._readyUpdate;
} }
@@ -4609,6 +4609,7 @@ export class UpdateManager {
* See nsIUpdateService.idl * See nsIUpdateService.idl
*/ */
async getDownloadingUpdate() { async getDownloadingUpdate() {
await lazy.AUS.init();
return this._downloadingUpdate; return this._downloadingUpdate;
} }
@@ -4624,6 +4625,7 @@ export class UpdateManager {
* See nsIUpdateService.idl * See nsIUpdateService.idl
*/ */
async addUpdateToHistory(aUpdate) { async addUpdateToHistory(aUpdate) {
await lazy.AUS.init();
this.#addUpdateToHistory(aUpdate); this.#addUpdateToHistory(aUpdate);
} }
@@ -4770,6 +4772,8 @@ export class UpdateManager {
try { try {
LOG("UpdateManager:refreshUpdateStatus - Staging done."); LOG("UpdateManager:refreshUpdateStatus - Staging done.");
await lazy.AUS.init();
var update = this._readyUpdate; var update = this._readyUpdate;
if (!update) { if (!update) {
LOG("UpdateManager:refreshUpdateStatus - Missing ready update?"); LOG("UpdateManager:refreshUpdateStatus - Missing ready update?");
@@ -4936,6 +4940,7 @@ export class UpdateManager {
LOG( LOG(
"UpdateManager:cleanupDownloadingUpdate - cleaning up downloading update." "UpdateManager:cleanupDownloadingUpdate - cleaning up downloading update."
); );
await lazy.AUS.init();
cleanupDownloadingUpdate(); cleanupDownloadingUpdate();
} }
@@ -4944,6 +4949,7 @@ export class UpdateManager {
*/ */
async cleanupReadyUpdate() { async cleanupReadyUpdate() {
LOG("UpdateManager:cleanupReadyUpdate - cleaning up ready update."); LOG("UpdateManager:cleanupReadyUpdate - cleaning up ready update.");
await lazy.AUS.init();
cleanupReadyUpdate(); cleanupReadyUpdate();
} }
@@ -4952,6 +4958,7 @@ export class UpdateManager {
*/ */
async cleanupActiveUpdates() { async cleanupActiveUpdates() {
LOG("UpdateManager:cleanupActiveUpdates - cleaning up active updates."); LOG("UpdateManager:cleanupActiveUpdates - cleaning up active updates.");
await lazy.AUS.init();
cleanupActiveUpdates(); cleanupActiveUpdates();
} }
@@ -4960,6 +4967,8 @@ export class UpdateManager {
*/ */
async doInstallCleanup() { async doInstallCleanup() {
LOG("UpdateManager:doInstallCleanup - cleaning up"); LOG("UpdateManager:doInstallCleanup - cleaning up");
await lazy.AUS.init();
let completionPromises = []; let completionPromises = [];
const delete_or_log = path => const delete_or_log = path =>
@@ -5002,6 +5011,7 @@ export class UpdateManager {
*/ */
async doUninstallCleanup() { async doUninstallCleanup() {
LOG("UpdateManager:doUninstallCleanup - cleaning up."); LOG("UpdateManager:doUninstallCleanup - cleaning up.");
await lazy.AUS.init();
let completionPromises = []; let completionPromises = [];
completionPromises.push( completionPromises.push(
@@ -5209,6 +5219,11 @@ export class CheckerService {
} }
#checkForUpdates(checkType, internal) { #checkForUpdates(checkType, internal) {
// Note that we should run update initialization if it was invoked
// externally (i.e. `internal == false`). But initialization is async and
// the asynchronous work of this function happens in `this.#updateCheck`, so
// we will delay initialization until then.
LOG("CheckerService:checkForUpdates - checkType: " + checkType); LOG("CheckerService:checkForUpdates - checkType: " + checkType);
if (!this.#validUpdateCheckType(checkType)) { if (!this.#validUpdateCheckType(checkType)) {
LOG("CheckerService:checkForUpdates - Invalid checkType"); LOG("CheckerService:checkForUpdates - Invalid checkType");
@@ -5218,7 +5233,7 @@ export class CheckerService {
let checkId = this.#nextUpdateCheckId; let checkId = this.#nextUpdateCheckId;
this.#nextUpdateCheckId += 1; this.#nextUpdateCheckId += 1;
// `checkType == FOREGROUND_CHECK`` can override `canCheckForUpdates`. But // `checkType == FOREGROUND_CHECK` can override `canCheckForUpdates`. But
// nothing should override enterprise policies. // nothing should override enterprise policies.
if (lazy.AUS.disabled) { if (lazy.AUS.disabled) {
LOG("CheckerService:checkForUpdates - disabled by policy"); LOG("CheckerService:checkForUpdates - disabled by policy");
@@ -5280,7 +5295,11 @@ export class CheckerService {
}; };
} }
async #updateCheck(checkType, requestKey, _internal) { async #updateCheck(checkType, requestKey, internal) {
if (!internal) {
await lazy.AUS.init();
}
await waitForOtherInstances(); await waitForOtherInstances();
let url; let url;

View File

@@ -56,54 +56,135 @@ function getUpdateBaseDirNoCreate() {
return FileUtils.getDir(KEY_UPDROOT, []); return FileUtils.getDir(KEY_UPDROOT, []);
} }
export function UpdateServiceStub() { export class UpdateServiceStub {
LOG("UpdateServiceStub - Begin."); #initUpdatePromise;
#initStubHasRun = false;
let updateDir = getUpdateBaseDirNoCreate(); /**
let prefUpdateDirMigrated = * See nsIUpdateService.idl
PREF_PREFIX_UPDATE_DIR_MIGRATED + updateDir.leafName; */
async init() {
await this.#init(false);
}
async initUpdate() {
await this.#init(true);
}
let statusFile = updateDir; #initOnlyStub(updateDir) {
statusFile.append(DIR_UPDATES); // We may need to migrate update data
statusFile.append("0"); let prefUpdateDirMigrated =
statusFile.append(FILE_UPDATE_STATUS); PREF_PREFIX_UPDATE_DIR_MIGRATED + updateDir.leafName;
updateDir = null; // We don't need updateDir anymore, plus now its nsIFile if (
// contains the status file's path AppConstants.platform == "win" &&
!Services.prefs.getBoolPref(prefUpdateDirMigrated, false)
// We may need to migrate update data ) {
if ( Services.prefs.setBoolPref(prefUpdateDirMigrated, true);
AppConstants.platform == "win" && try {
!Services.prefs.getBoolPref(prefUpdateDirMigrated, false) migrateUpdateDirectory();
) { } catch (ex) {
Services.prefs.setBoolPref(prefUpdateDirMigrated, true); // For the most part, migrateUpdateDirectory() catches its own
try { // errors. But there are technically things that could happen that
migrateUpdateDirectory(); // might not be caught, like nsIFile.parent or nsIFile.append could
} catch (ex) { // unexpectedly fail.
// For the most part, migrateUpdateDirectory() catches its own errors. // So we will catch any errors here, just in case.
// But there are technically things that could happen that might not be LOG(
// caught, like nsIFile.parent or nsIFile.append could unexpectedly fail. `UpdateServiceStub:UpdateServiceStub Failed to migrate update ` +
// So we will catch any errors here, just in case. `directory. Exception: ${ex}`
LOG( );
`UpdateServiceStub:UpdateServiceStub Failed to migrate update ` + }
`directory. Exception: ${ex}`
);
} }
} }
// If the update.status file exists then initiate post update processing. async #initUpdate() {
if (statusFile.exists()) { // Ensure that the constructors for the update services have run.
let aus = Cc["@mozilla.org/updates/update-service;1"] const aus = Cc["@mozilla.org/updates/update-service;1"].getService(
.getService(Ci.nsIApplicationUpdateService) Ci.nsIApplicationUpdateService
.QueryInterface(Ci.nsIObserver); );
aus.observe(null, "post-update-processing", ""); Cc["@mozilla.org/updates/update-manager;1"].getService(Ci.nsIUpdateManager);
} Cc["@mozilla.org/updates/update-checker;1"].getService(Ci.nsIUpdateChecker);
}
UpdateServiceStub.prototype = { // Run update service initialization
observe() {}, await aus.internal.init();
classID: Components.ID("{e43b0010-04ba-4da6-b523-1f92580bc150}"), }
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
}; async #init(force_update_init) {
// We call into this from many places to ensure that initialization is done,
// so we want to optimize for the case where initialization is already
// finished.
if (this.#initUpdatePromise) {
try {
await this.#initUpdatePromise;
} catch (ex) {
// This will already have been logged when the error first happened, we
// don't need to log it again now.
}
return;
}
LOG(`UpdateServiceStub - Begin (force_update_init=${force_update_init})`);
let initUpdate = force_update_init;
try {
const updateDir = getUpdateBaseDirNoCreate();
if (!this.#initStubHasRun) {
this.#initStubHasRun = true;
try {
this.#initOnlyStub(updateDir);
} catch (ex) {
LOG(
`UpdateServiceStub - Stub initialization failed: ${ex}: ${ex.stack}`
);
}
}
try {
if (!initUpdate) {
const statusFile = updateDir.clone();
statusFile.append(DIR_UPDATES);
statusFile.append("0");
statusFile.append(FILE_UPDATE_STATUS);
initUpdate = statusFile.exists();
}
} catch (ex) {
LOG(
`UpdateServiceStub - Failed to generate the status file: ${ex}: ${ex.stack}`
);
}
} catch (e) {
LOG(
`UpdateServiceStub - Failed to get update directory: ${e}: ${e.stack}`
);
}
if (initUpdate) {
this.#initUpdatePromise = this.#initUpdate();
try {
await this.#initUpdatePromise;
} catch (ex) {
LOG(`UpdateServiceStub - Init failed: ${ex}: ${ex.stack}`);
}
}
}
async observe(_subject, topic, _data) {
switch (topic) {
// This is sort of the "default" way of being initialized. The
// "@mozilla.org/updates/update-service-stub;1" contract definition in
// `components.conf` registers us for this notification, which we use to
// trigger initialization. Note, however, that this calls only `init` and
// not `initUpdate`.
case "profile-after-change":
await this.init();
break;
}
}
classID = Components.ID("{e43b0010-04ba-4da6-b523-1f92580bc150}");
QueryInterface = ChromeUtils.generateQI([
Ci.nsIApplicationUpdateServiceStub,
Ci.nsIObserver,
]);
}
/** /**
* This function should be called when there are files in the old update * This function should be called when there are files in the old update

View File

@@ -392,6 +392,16 @@ interface nsIUpdateChecker : nsISupports
[scriptable, uuid(7f39bc95-eaf8-4adc-990b-0fc0734f85fa)] [scriptable, uuid(7f39bc95-eaf8-4adc-990b-0fc0734f85fa)]
interface nsIApplicationUpdateServiceInternal : nsISupports interface nsIApplicationUpdateServiceInternal : nsISupports
{ {
/**
* To initialize the update system, use the init method on
* `nsIApplicationUpdateService` or `nsIApplicationUpdateServiceStub`. This
* method is invoked as part of the update initialization process, but is not
* an entry point to it.
*
* @returns Promise<undefined>
*/
Promise init();
/** /**
* These are identical to the corresponding functions in * These are identical to the corresponding functions in
* `nsIApplicationUpdateService`, but these versions do not ensure the update * `nsIApplicationUpdateService`, but these versions do not ensure the update
@@ -399,6 +409,14 @@ interface nsIApplicationUpdateServiceInternal : nsISupports
*/ */
Promise downloadUpdate(in nsIUpdate update); Promise downloadUpdate(in nsIUpdate update);
Promise stopDownload(); Promise stopDownload();
/**
* Runs post update processing, even if it has already run. This should only
* be done in testing.
*
* @returns Promise<undefined>
*/
Promise postUpdateProcessing();
}; };
/** /**
@@ -409,6 +427,17 @@ interface nsIApplicationUpdateServiceInternal : nsISupports
[scriptable, uuid(1107d207-a263-403a-b268-05772ec10757)] [scriptable, uuid(1107d207-a263-403a-b268-05772ec10757)]
interface nsIApplicationUpdateService : nsISupports interface nsIApplicationUpdateService : nsISupports
{ {
/**
* Initializes the update system. It is typically not necessary to call this
* since the public methods that require initialization to be meaningful
* ensure that initialization has been performed. But this can be useful to
* spur the updater to resume or complete any in-progress updates from
* previous browser sessions.
*
* @returns Promise<undefined>
*/
Promise init();
/** /**
* Checks for available updates in the background using the listener provided * Checks for available updates in the background using the listener provided
* by the application update service for background checks. * by the application update service for background checks.
@@ -924,3 +953,31 @@ interface nsIUpdateManager : nsISupports
*/ */
readonly attribute nsIUpdateManagerInternal internal; readonly attribute nsIUpdateManagerInternal internal;
}; };
/**
* A lightweight interface that we can load early in startup that gives us very
* limited access to the update system without the startup costs of loading
* nsIApplicationUpdateService.
*/
[scriptable, uuid(3ca17ada-8501-496f-bbe7-6a9f1c28eb2d)]
interface nsIApplicationUpdateServiceStub : nsISupports
{
/**
* This does the standard initialization of the update service stub. The
* primary effect of this is to initialize the update system (the same effect
* as `initUpdate`), but only if there are updates from a previous browser
* session. This allows us to avoid loading the update system so early if it
* would just be sitting idle until the update timer fires or the user visits
* the update UI.
*
* @returns Promise<undefined>
*/
Promise init();
/**
* This is identical to `nsIApplicationUpdateService.init()`.
*
* @returns Promise<undefined>
*/
Promise initUpdate();
};

View File

@@ -177,7 +177,7 @@ function waitForEvent(topic, status = null) {
/* Triggers post-update processing */ /* Triggers post-update processing */
async function testPostUpdateProcessing() { async function testPostUpdateProcessing() {
gAUS.observe(null, "test-post-update-processing", ""); await gAUS.internal.postUpdateProcessing();
} }
/* Initializes the update service stub */ /* Initializes the update service stub */