aboutDialog-appUpdater.js (9086B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 // Note: this file is included in aboutDialog.xhtml and preferences/advanced.xhtml 6 // if MOZ_UPDATER is defined. 7 8 /* import-globals-from aboutDialog.js */ 9 10 var { XPCOMUtils } = ChromeUtils.importESModule( 11 "resource://gre/modules/XPCOMUtils.sys.mjs" 12 ); 13 14 ChromeUtils.defineESModuleGetters(this, { 15 AppUpdater: "resource://gre/modules/AppUpdater.sys.mjs", 16 DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs", 17 UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs", 18 }); 19 20 XPCOMUtils.defineLazyServiceGetter( 21 this, 22 "AUS", 23 "@mozilla.org/updates/update-service;1", 24 Ci.nsIApplicationUpdateService 25 ); 26 27 var UPDATING_MIN_DISPLAY_TIME_MS = 1500; 28 29 var gAppUpdater; 30 31 function onUnload(_aEvent) { 32 if (gAppUpdater) { 33 gAppUpdater.destroy(); 34 gAppUpdater = null; 35 } 36 } 37 38 function appUpdater(options = {}) { 39 this._appUpdater = new AppUpdater(); 40 41 this._appUpdateListener = (status, ...args) => { 42 this._onAppUpdateStatus(status, ...args); 43 }; 44 this._appUpdater.addListener(this._appUpdateListener); 45 46 this.options = options; 47 this.updatingMinDisplayTimerId = null; 48 this.updateDeck = document.getElementById("updateDeck"); 49 50 this.bundle = Services.strings.createBundle( 51 "chrome://browser/locale/browser.properties" 52 ); 53 54 try { 55 let manualURL = new URL( 56 Services.urlFormatter.formatURLPref("app.update.url.manual") 57 ); 58 59 for (const manualLink of document.querySelectorAll(".manualLink")) { 60 // Strip hash and search parameters for display text. 61 let displayUrl = manualURL.origin + manualURL.pathname; 62 manualLink.href = manualURL.href; 63 document.l10n.setArgs(manualLink.closest("[data-l10n-id]"), { 64 displayUrl, 65 }); 66 } 67 68 document.getElementById("failedLink").href = manualURL.href; 69 } catch (e) { 70 console.error("Invalid manual update url.", e); 71 } 72 73 this._appUpdater.check(); 74 } 75 76 appUpdater.prototype = { 77 destroy() { 78 this.stopCurrentCheck(); 79 if (this.updatingMinDisplayTimerId) { 80 clearTimeout(this.updatingMinDisplayTimerId); 81 } 82 }, 83 84 stopCurrentCheck() { 85 this._appUpdater.removeListener(this._appUpdateListener); 86 this._appUpdater.stop(); 87 }, 88 89 get update() { 90 return this._appUpdater.update; 91 }, 92 93 get selectedPanel() { 94 return this.updateDeck.selectedPanel; 95 }, 96 97 _onAppUpdateStatus(status, ...args) { 98 switch (status) { 99 case AppUpdater.STATUS.UPDATE_DISABLED_BY_POLICY: 100 this.selectPanel("policyDisabled"); 101 break; 102 case AppUpdater.STATUS.READY_FOR_RESTART: 103 this.selectPanel("apply"); 104 break; 105 case AppUpdater.STATUS.OTHER_INSTANCE_HANDLING_UPDATES: 106 this.selectPanel("otherInstanceHandlingUpdates"); 107 break; 108 case AppUpdater.STATUS.DOWNLOADING: { 109 const downloadStatus = document.getElementById("downloading"); 110 if (!args.length) { 111 // Very early in the DOWNLOADING state, `selectedPatch` may not be 112 // available yet. But this function will be called again when it is 113 // available. A `maxSize < 0` indicates that the max size is not yet 114 // available. 115 let maxSize = -1; 116 if (this.update.selectedPatch) { 117 maxSize = this.update.selectedPatch.size; 118 } 119 const transfer = DownloadUtils.getTransferTotal(0, maxSize); 120 document.l10n.setArgs(downloadStatus, { transfer }); 121 this.selectPanel("downloading"); 122 } else { 123 let [progress, max] = args; 124 const transfer = DownloadUtils.getTransferTotal(progress, max); 125 document.l10n.setArgs(downloadStatus, { transfer }); 126 } 127 break; 128 } 129 case AppUpdater.STATUS.STAGING: 130 this.selectPanel("applying"); 131 break; 132 case AppUpdater.STATUS.CHECKING: { 133 this.checkingForUpdatesDelayPromise = new Promise(resolve => { 134 this.updatingMinDisplayTimerId = setTimeout( 135 resolve, 136 UPDATING_MIN_DISPLAY_TIME_MS 137 ); 138 }); 139 if (Services.policies.isAllowed("appUpdate")) { 140 this.selectPanel("checkingForUpdates"); 141 } else { 142 this.selectPanel("policyDisabled"); 143 } 144 break; 145 } 146 case AppUpdater.STATUS.CHECKING_FAILED: 147 this.selectPanel("checkingFailed"); 148 break; 149 case AppUpdater.STATUS.NO_UPDATES_FOUND: 150 this.checkingForUpdatesDelayPromise.then(() => { 151 if (Services.policies.isAllowed("appUpdate")) { 152 this.selectPanel("noUpdatesFound"); 153 } else { 154 this.selectPanel("policyDisabled"); 155 } 156 }); 157 break; 158 case AppUpdater.STATUS.UNSUPPORTED_SYSTEM: 159 if (this.update.detailsURL) { 160 let unsupportedLink = document.getElementById("unsupportedLink"); 161 unsupportedLink.href = this.update.detailsURL; 162 } 163 this.selectPanel("unsupportedSystem"); 164 break; 165 case AppUpdater.STATUS.MANUAL_UPDATE: 166 this.selectPanel("manualUpdate"); 167 break; 168 case AppUpdater.STATUS.DOWNLOAD_AND_INSTALL: 169 this.selectPanel("downloadAndInstall"); 170 break; 171 case AppUpdater.STATUS.DOWNLOAD_FAILED: 172 this.selectPanel("downloadFailed"); 173 break; 174 case AppUpdater.STATUS.INTERNAL_ERROR: 175 this.selectPanel("internalError"); 176 break; 177 case AppUpdater.STATUS.NEVER_CHECKED: 178 this.selectPanel("checkForUpdates"); 179 break; 180 case AppUpdater.STATUS.NO_UPDATER: 181 default: 182 this.selectPanel("noUpdater"); 183 break; 184 } 185 }, 186 187 /** 188 * Sets the panel of the updateDeck and the icon class. 189 * 190 * @param aChildID 191 * The id of the deck's child to select, e.g. "apply". 192 */ 193 selectPanel(aChildID) { 194 let panel = document.getElementById(aChildID); 195 let icon = document.getElementById("updateIcon"); 196 if (icon) { 197 icon.className = aChildID; 198 } 199 200 // Make sure to select the panel before potentially auto-focusing the button. 201 this.updateDeck.selectedPanel = panel; 202 203 let button = panel.querySelector("button"); 204 if (button) { 205 if (aChildID == "downloadAndInstall") { 206 let updateVersion = gAppUpdater.update.displayVersion; 207 // Include the build ID if this is an "a#" (nightly or aurora) build 208 if (!AppConstants.BASE_BROWSER_UPDATE && /a\d+$/.test(updateVersion)) { 209 let buildID = gAppUpdater.update.buildID; 210 let year = buildID.slice(0, 4); 211 let month = buildID.slice(4, 6); 212 let day = buildID.slice(6, 8); 213 updateVersion += ` (${year}-${month}-${day})`; 214 } 215 button.label = this.bundle.formatStringFromName( 216 "update.downloadAndInstallButton.label", 217 [updateVersion] 218 ); 219 button.accessKey = this.bundle.GetStringFromName( 220 "update.downloadAndInstallButton.accesskey" 221 ); 222 } 223 if (this.options.buttonAutoFocus) { 224 let promise = Promise.resolve(); 225 if (document.readyState != "complete") { 226 promise = new Promise(resolve => 227 window.addEventListener("load", resolve, { once: true }) 228 ); 229 } 230 promise.then(() => { 231 if ( 232 !document.commandDispatcher.focusedElement || // don't steal the focus 233 // except from the other buttons 234 document.commandDispatcher.focusedElement.localName == "button" 235 ) { 236 button.focus(); 237 } 238 }); 239 } 240 } 241 }, 242 243 /** 244 * Check for updates 245 */ 246 checkForUpdates() { 247 this._appUpdater.check(); 248 }, 249 250 /** 251 * Handles oncommand for the "Restart to Update" button 252 * which is presented after the download has been downloaded. 253 */ 254 buttonRestartAfterDownload() { 255 if (AUS.currentState != Ci.nsIApplicationUpdateService.STATE_PENDING) { 256 return; 257 } 258 259 gAppUpdater.selectPanel("restarting"); 260 261 // Notify all windows that an application quit has been requested. 262 let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance( 263 Ci.nsISupportsPRBool 264 ); 265 Services.obs.notifyObservers( 266 cancelQuit, 267 "quit-application-requested", 268 "restart" 269 ); 270 271 // Something aborted the quit process. 272 if (cancelQuit.data) { 273 gAppUpdater.selectPanel("apply"); 274 return; 275 } 276 277 // If already in safe mode restart in safe mode (bug 327119) 278 if (Services.appinfo.inSafeMode) { 279 Services.startup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit); 280 return; 281 } 282 283 if ( 284 !Services.startup.quit( 285 Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart 286 ) 287 ) { 288 // Either the user or the hidden window aborted the quit process. 289 gAppUpdater.selectPanel("apply"); 290 } 291 }, 292 293 /** 294 * Starts the download of an update mar. 295 */ 296 startDownload() { 297 this._appUpdater.allowUpdateDownload(); 298 }, 299 };