Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Note: this file is included in aboutDialog.xhtml and preferences/advanced.xhtml
// if MOZ_UPDATER is defined.
/* import-globals-from aboutDialog.js */
var { XPCOMUtils } = ChromeUtils.importESModule(
"resource://gre/modules/XPCOMUtils.sys.mjs"
);
ChromeUtils.defineESModuleGetters(this, {
AppUpdater: "resource://gre/modules/AppUpdater.sys.mjs",
DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs",
UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
});
XPCOMUtils.defineLazyServiceGetter(
this,
"AUS",
"@mozilla.org/updates/update-service;1",
"nsIApplicationUpdateService"
);
var UPDATING_MIN_DISPLAY_TIME_MS = 1500;
var gAppUpdater;
function onUnload(_aEvent) {
if (gAppUpdater) {
gAppUpdater.destroy();
gAppUpdater = null;
}
}
function appUpdater(options = {}) {
this._appUpdater = new AppUpdater();
this._appUpdateListener = (status, ...args) => {
this._onAppUpdateStatus(status, ...args);
};
this._appUpdater.addListener(this._appUpdateListener);
this.options = options;
this.updatingMinDisplayTimerId = null;
this.updateDeck = document.getElementById("updateDeck");
this.bundle = Services.strings.createBundle(
"chrome://browser/locale/browser.properties"
);
try {
let manualURL = new URL(
Services.urlFormatter.formatURLPref("app.update.url.manual")
);
for (const manualLink of document.querySelectorAll(".manualLink")) {
// Strip hash and search parameters for display text.
let displayUrl = manualURL.origin + manualURL.pathname;
manualLink.href = manualURL.href;
document.l10n.setArgs(manualLink.closest("[data-l10n-id]"), {
displayUrl,
});
}
document.getElementById("failedLink").href = manualURL.href;
} catch (e) {
console.error("Invalid manual update url.", e);
}
this._appUpdater.check();
}
appUpdater.prototype = {
destroy() {
this.stopCurrentCheck();
if (this.updatingMinDisplayTimerId) {
clearTimeout(this.updatingMinDisplayTimerId);
}
},
stopCurrentCheck() {
this._appUpdater.removeListener(this._appUpdateListener);
this._appUpdater.stop();
},
get update() {
return this._appUpdater.update;
},
get selectedPanel() {
return this.updateDeck.selectedPanel;
},
_onAppUpdateStatus(status, ...args) {
switch (status) {
case AppUpdater.STATUS.UPDATE_DISABLED_BY_POLICY:
this.selectPanel("policyDisabled");
break;
case AppUpdater.STATUS.READY_FOR_RESTART:
this.selectPanel("apply");
break;
case AppUpdater.STATUS.OTHER_INSTANCE_HANDLING_UPDATES:
this.selectPanel("otherInstanceHandlingUpdates");
break;
case AppUpdater.STATUS.DOWNLOADING: {
const downloadStatus = document.getElementById("downloading");
if (!args.length) {
// Very early in the DOWNLOADING state, `selectedPatch` may not be
// available yet. But this function will be called again when it is
// available. A `maxSize < 0` indicates that the max size is not yet
// available.
let maxSize = -1;
if (this.update.selectedPatch) {
maxSize = this.update.selectedPatch.size;
}
const transfer = DownloadUtils.getTransferTotal(0, maxSize);
document.l10n.setArgs(downloadStatus, { transfer });
this.selectPanel("downloading");
} else {
let [progress, max] = args;
const transfer = DownloadUtils.getTransferTotal(progress, max);
document.l10n.setArgs(downloadStatus, { transfer });
}
break;
}
case AppUpdater.STATUS.STAGING:
this.selectPanel("applying");
break;
case AppUpdater.STATUS.CHECKING: {
this.checkingForUpdatesDelayPromise = new Promise(resolve => {
this.updatingMinDisplayTimerId = setTimeout(
resolve,
UPDATING_MIN_DISPLAY_TIME_MS
);
});
if (Services.policies.isAllowed("appUpdate")) {
this.selectPanel("checkingForUpdates");
} else {
this.selectPanel("policyDisabled");
}
break;
}
case AppUpdater.STATUS.CHECKING_FAILED:
this.selectPanel("checkingFailed");
break;
case AppUpdater.STATUS.NO_UPDATES_FOUND:
this.checkingForUpdatesDelayPromise.then(() => {
if (Services.policies.isAllowed("appUpdate")) {
this.selectPanel("noUpdatesFound");
} else {
this.selectPanel("policyDisabled");
}
});
break;
case AppUpdater.STATUS.UNSUPPORTED_SYSTEM:
if (this.update.detailsURL) {
let unsupportedLink = document.getElementById("unsupportedLink");
unsupportedLink.href = this.update.detailsURL;
}
this.selectPanel("unsupportedSystem");
break;
case AppUpdater.STATUS.MANUAL_UPDATE:
this.selectPanel("manualUpdate");
break;
case AppUpdater.STATUS.DOWNLOAD_AND_INSTALL:
this.selectPanel("downloadAndInstall");
break;
case AppUpdater.STATUS.DOWNLOAD_FAILED:
this.selectPanel("downloadFailed");
break;
case AppUpdater.STATUS.INTERNAL_ERROR:
this.selectPanel("internalError");
break;
case AppUpdater.STATUS.NEVER_CHECKED:
this.selectPanel("checkForUpdates");
break;
case AppUpdater.STATUS.NO_UPDATER:
default:
this.selectPanel("noUpdater");
break;
}
},
/**
* Sets the panel of the updateDeck and the icon class.
*
* @param aChildID
* The id of the deck's child to select, e.g. "apply".
*/
selectPanel(aChildID) {
let panel = document.getElementById(aChildID);
let icon = document.getElementById("updateIcon");
if (icon) {
icon.className = aChildID;
}
// Make sure to select the panel before potentially auto-focusing the button.
this.updateDeck.selectedPanel = panel;
let button = panel.querySelector("button");
if (button) {
if (aChildID == "downloadAndInstall") {
let updateVersion = gAppUpdater.update.displayVersion;
// Include the build ID if this is an "a#" (nightly or aurora) build
if (/a\d+$/.test(updateVersion)) {
let buildID = gAppUpdater.update.buildID;
let year = buildID.slice(0, 4);
let month = buildID.slice(4, 6);
let day = buildID.slice(6, 8);
updateVersion += ` (${year}-${month}-${day})`;
}
button.label = this.bundle.formatStringFromName(
"update.downloadAndInstallButton.label",
[updateVersion]
);
button.accessKey = this.bundle.GetStringFromName(
"update.downloadAndInstallButton.accesskey"
);
}
if (this.options.buttonAutoFocus) {
let promise = Promise.resolve();
if (document.readyState != "complete") {
promise = new Promise(resolve =>
window.addEventListener("load", resolve, { once: true })
);
}
promise.then(() => {
if (
!document.commandDispatcher.focusedElement || // don't steal the focus
// except from the other buttons
document.commandDispatcher.focusedElement.localName == "button"
) {
button.focus();
}
});
}
}
},
/**
* Check for updates
*/
checkForUpdates() {
this._appUpdater.check();
},
/**
* Handles oncommand for the "Restart to Update" button
* which is presented after the download has been downloaded.
*/
buttonRestartAfterDownload() {
if (AUS.currentState != Ci.nsIApplicationUpdateService.STATE_PENDING) {
return;
}
gAppUpdater.selectPanel("restarting");
// Notify all windows that an application quit has been requested.
let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
Ci.nsISupportsPRBool
);
Services.obs.notifyObservers(
cancelQuit,
"quit-application-requested",
"restart"
);
// Something aborted the quit process.
if (cancelQuit.data) {
gAppUpdater.selectPanel("apply");
return;
}
// If already in safe mode restart in safe mode (bug 327119)
if (Services.appinfo.inSafeMode) {
Services.startup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit);
return;
}
if (
!Services.startup.quit(
Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart
)
) {
// Either the user or the hidden window aborted the quit process.
gAppUpdater.selectPanel("apply");
}
},
/**
* Starts the download of an update mar.
*/
startDownload() {
this._appUpdater.allowUpdateDownload();
},
};