/* 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 visibility of icons * in the #icons element. * * @param aChildID * The id of the deck's child to select, e.g. "apply". */ selectPanel(aChildID) { let panel = document.getElementById(aChildID); let icons = document.getElementById("icons"); if (icons) { icons.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(); }, };