Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- Mode: Java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
michael@0 | 2 | * vim: sw=2 ts=8 et : |
michael@0 | 3 | */ |
michael@0 | 4 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 6 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 7 | |
michael@0 | 8 | const Cc = Components.classes; |
michael@0 | 9 | const Ci = Components.interfaces; |
michael@0 | 10 | const Cu = Components.utils; |
michael@0 | 11 | const Cr = Components.results; |
michael@0 | 12 | |
michael@0 | 13 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 14 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 15 | Cu.import("resource://gre/modules/WebappsUpdater.jsm"); |
michael@0 | 16 | |
michael@0 | 17 | const VERBOSE = 1; |
michael@0 | 18 | let log = |
michael@0 | 19 | VERBOSE ? |
michael@0 | 20 | function log_dump(msg) { dump("UpdatePrompt: "+ msg +"\n"); } : |
michael@0 | 21 | function log_noop(msg) { }; |
michael@0 | 22 | |
michael@0 | 23 | const PREF_APPLY_PROMPT_TIMEOUT = "b2g.update.apply-prompt-timeout"; |
michael@0 | 24 | const PREF_APPLY_IDLE_TIMEOUT = "b2g.update.apply-idle-timeout"; |
michael@0 | 25 | const PREF_DOWNLOAD_WATCHDOG_TIMEOUT = "b2g.update.download-watchdog-timeout"; |
michael@0 | 26 | const PREF_DOWNLOAD_WATCHDOG_MAX_RETRIES = "b2g.update.download-watchdog-max-retries"; |
michael@0 | 27 | |
michael@0 | 28 | const NETWORK_ERROR_OFFLINE = 111; |
michael@0 | 29 | const HTTP_ERROR_OFFSET = 1000; |
michael@0 | 30 | |
michael@0 | 31 | const STATE_DOWNLOADING = 'downloading'; |
michael@0 | 32 | |
michael@0 | 33 | XPCOMUtils.defineLazyServiceGetter(Services, "aus", |
michael@0 | 34 | "@mozilla.org/updates/update-service;1", |
michael@0 | 35 | "nsIApplicationUpdateService"); |
michael@0 | 36 | |
michael@0 | 37 | XPCOMUtils.defineLazyServiceGetter(Services, "um", |
michael@0 | 38 | "@mozilla.org/updates/update-manager;1", |
michael@0 | 39 | "nsIUpdateManager"); |
michael@0 | 40 | |
michael@0 | 41 | XPCOMUtils.defineLazyServiceGetter(Services, "idle", |
michael@0 | 42 | "@mozilla.org/widget/idleservice;1", |
michael@0 | 43 | "nsIIdleService"); |
michael@0 | 44 | |
michael@0 | 45 | XPCOMUtils.defineLazyServiceGetter(Services, "settings", |
michael@0 | 46 | "@mozilla.org/settingsService;1", |
michael@0 | 47 | "nsISettingsService"); |
michael@0 | 48 | |
michael@0 | 49 | XPCOMUtils.defineLazyServiceGetter(Services, 'env', |
michael@0 | 50 | '@mozilla.org/process/environment;1', |
michael@0 | 51 | 'nsIEnvironment'); |
michael@0 | 52 | |
michael@0 | 53 | function useSettings() { |
michael@0 | 54 | // When we're running in the real phone, then we can use settings. |
michael@0 | 55 | // But when we're running as part of xpcshell, there is no settings database |
michael@0 | 56 | // and trying to use settings in this scenario causes lots of weird |
michael@0 | 57 | // assertions at shutdown time. |
michael@0 | 58 | if (typeof useSettings.result === "undefined") { |
michael@0 | 59 | useSettings.result = !Services.env.get("XPCSHELL_TEST_PROFILE_DIR"); |
michael@0 | 60 | } |
michael@0 | 61 | return useSettings.result; |
michael@0 | 62 | } |
michael@0 | 63 | |
michael@0 | 64 | XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy", |
michael@0 | 65 | "resource://gre/modules/SystemAppProxy.jsm"); |
michael@0 | 66 | |
michael@0 | 67 | function UpdateCheckListener(updatePrompt) { |
michael@0 | 68 | this._updatePrompt = updatePrompt; |
michael@0 | 69 | } |
michael@0 | 70 | |
michael@0 | 71 | UpdateCheckListener.prototype = { |
michael@0 | 72 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateCheckListener]), |
michael@0 | 73 | |
michael@0 | 74 | _updatePrompt: null, |
michael@0 | 75 | |
michael@0 | 76 | onCheckComplete: function UCL_onCheckComplete(request, updates, updateCount) { |
michael@0 | 77 | if (Services.um.activeUpdate) { |
michael@0 | 78 | // We're actively downloading an update, that's the update the user should |
michael@0 | 79 | // see, even if a newer update is available. |
michael@0 | 80 | this._updatePrompt.setUpdateStatus("active-update"); |
michael@0 | 81 | this._updatePrompt.showUpdateAvailable(Services.um.activeUpdate); |
michael@0 | 82 | return; |
michael@0 | 83 | } |
michael@0 | 84 | |
michael@0 | 85 | if (updateCount == 0) { |
michael@0 | 86 | this._updatePrompt.setUpdateStatus("no-updates"); |
michael@0 | 87 | return; |
michael@0 | 88 | } |
michael@0 | 89 | |
michael@0 | 90 | let update = Services.aus.selectUpdate(updates, updateCount); |
michael@0 | 91 | if (!update) { |
michael@0 | 92 | this._updatePrompt.setUpdateStatus("already-latest-version"); |
michael@0 | 93 | return; |
michael@0 | 94 | } |
michael@0 | 95 | |
michael@0 | 96 | this._updatePrompt.setUpdateStatus("check-complete"); |
michael@0 | 97 | this._updatePrompt.showUpdateAvailable(update); |
michael@0 | 98 | }, |
michael@0 | 99 | |
michael@0 | 100 | onError: function UCL_onError(request, update) { |
michael@0 | 101 | // nsIUpdate uses a signed integer for errorCode while any platform errors |
michael@0 | 102 | // require all 32 bits. |
michael@0 | 103 | let errorCode = update.errorCode >>> 0; |
michael@0 | 104 | let isNSError = (errorCode >>> 31) == 1; |
michael@0 | 105 | |
michael@0 | 106 | if (errorCode == NETWORK_ERROR_OFFLINE) { |
michael@0 | 107 | this._updatePrompt.setUpdateStatus("retry-when-online"); |
michael@0 | 108 | } else if (isNSError) { |
michael@0 | 109 | this._updatePrompt.setUpdateStatus("check-error-" + errorCode); |
michael@0 | 110 | } else if (errorCode > HTTP_ERROR_OFFSET) { |
michael@0 | 111 | let httpErrorCode = errorCode - HTTP_ERROR_OFFSET; |
michael@0 | 112 | this._updatePrompt.setUpdateStatus("check-error-http-" + httpErrorCode); |
michael@0 | 113 | } |
michael@0 | 114 | |
michael@0 | 115 | Services.aus.QueryInterface(Ci.nsIUpdateCheckListener); |
michael@0 | 116 | Services.aus.onError(request, update); |
michael@0 | 117 | } |
michael@0 | 118 | }; |
michael@0 | 119 | |
michael@0 | 120 | function UpdatePrompt() { |
michael@0 | 121 | this.wrappedJSObject = this; |
michael@0 | 122 | this._updateCheckListener = new UpdateCheckListener(this); |
michael@0 | 123 | Services.obs.addObserver(this, "update-check-start", false); |
michael@0 | 124 | } |
michael@0 | 125 | |
michael@0 | 126 | UpdatePrompt.prototype = { |
michael@0 | 127 | classID: Components.ID("{88b3eb21-d072-4e3b-886d-f89d8c49fe59}"), |
michael@0 | 128 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePrompt, |
michael@0 | 129 | Ci.nsIUpdateCheckListener, |
michael@0 | 130 | Ci.nsIRequestObserver, |
michael@0 | 131 | Ci.nsIProgressEventSink, |
michael@0 | 132 | Ci.nsIObserver]), |
michael@0 | 133 | _xpcom_factory: XPCOMUtils.generateSingletonFactory(UpdatePrompt), |
michael@0 | 134 | |
michael@0 | 135 | _update: null, |
michael@0 | 136 | _applyPromptTimer: null, |
michael@0 | 137 | _waitingForIdle: false, |
michael@0 | 138 | _updateCheckListner: null, |
michael@0 | 139 | |
michael@0 | 140 | get applyPromptTimeout() { |
michael@0 | 141 | return Services.prefs.getIntPref(PREF_APPLY_PROMPT_TIMEOUT); |
michael@0 | 142 | }, |
michael@0 | 143 | |
michael@0 | 144 | get applyIdleTimeout() { |
michael@0 | 145 | return Services.prefs.getIntPref(PREF_APPLY_IDLE_TIMEOUT); |
michael@0 | 146 | }, |
michael@0 | 147 | |
michael@0 | 148 | handleContentStart: function UP_handleContentStart() { |
michael@0 | 149 | SystemAppProxy.addEventListener("mozContentEvent", this); |
michael@0 | 150 | }, |
michael@0 | 151 | |
michael@0 | 152 | // nsIUpdatePrompt |
michael@0 | 153 | |
michael@0 | 154 | // FIXME/bug 737601: we should have users opt-in to downloading |
michael@0 | 155 | // updates when on a billed pipe. Initially, opt-in for 3g, but |
michael@0 | 156 | // that doesn't cover all cases. |
michael@0 | 157 | checkForUpdates: function UP_checkForUpdates() { }, |
michael@0 | 158 | |
michael@0 | 159 | showUpdateAvailable: function UP_showUpdateAvailable(aUpdate) { |
michael@0 | 160 | if (!this.sendUpdateEvent("update-available", aUpdate)) { |
michael@0 | 161 | |
michael@0 | 162 | log("Unable to prompt for available update, forcing download"); |
michael@0 | 163 | this.downloadUpdate(aUpdate); |
michael@0 | 164 | } |
michael@0 | 165 | }, |
michael@0 | 166 | |
michael@0 | 167 | showUpdateDownloaded: function UP_showUpdateDownloaded(aUpdate, aBackground) { |
michael@0 | 168 | // The update has been downloaded and staged. We send the update-downloaded |
michael@0 | 169 | // event right away. After the user has been idle for a while, we send the |
michael@0 | 170 | // update-prompt-restart event, increasing the chances that we can apply the |
michael@0 | 171 | // update quietly without user intervention. |
michael@0 | 172 | this.sendUpdateEvent("update-downloaded", aUpdate); |
michael@0 | 173 | |
michael@0 | 174 | if (Services.idle.idleTime >= this.applyIdleTimeout) { |
michael@0 | 175 | this.showApplyPrompt(aUpdate); |
michael@0 | 176 | return; |
michael@0 | 177 | } |
michael@0 | 178 | |
michael@0 | 179 | let applyIdleTimeoutSeconds = this.applyIdleTimeout / 1000; |
michael@0 | 180 | // We haven't been idle long enough, so register an observer |
michael@0 | 181 | log("Update is ready to apply, registering idle timeout of " + |
michael@0 | 182 | applyIdleTimeoutSeconds + " seconds before prompting."); |
michael@0 | 183 | |
michael@0 | 184 | this._update = aUpdate; |
michael@0 | 185 | this.waitForIdle(); |
michael@0 | 186 | }, |
michael@0 | 187 | |
michael@0 | 188 | showUpdateError: function UP_showUpdateError(aUpdate) { |
michael@0 | 189 | log("Update error, state: " + aUpdate.state + ", errorCode: " + |
michael@0 | 190 | aUpdate.errorCode); |
michael@0 | 191 | this.sendUpdateEvent("update-error", aUpdate); |
michael@0 | 192 | this.setUpdateStatus(aUpdate.statusText); |
michael@0 | 193 | }, |
michael@0 | 194 | |
michael@0 | 195 | showUpdateHistory: function UP_showUpdateHistory(aParent) { }, |
michael@0 | 196 | showUpdateInstalled: function UP_showUpdateInstalled() { |
michael@0 | 197 | if (useSettings()) { |
michael@0 | 198 | let lock = Services.settings.createLock(); |
michael@0 | 199 | lock.set("deviceinfo.last_updated", Date.now(), null, null); |
michael@0 | 200 | } |
michael@0 | 201 | }, |
michael@0 | 202 | |
michael@0 | 203 | // Custom functions |
michael@0 | 204 | |
michael@0 | 205 | waitForIdle: function UP_waitForIdle() { |
michael@0 | 206 | if (this._waitingForIdle) { |
michael@0 | 207 | return; |
michael@0 | 208 | } |
michael@0 | 209 | |
michael@0 | 210 | this._waitingForIdle = true; |
michael@0 | 211 | Services.idle.addIdleObserver(this, this.applyIdleTimeout / 1000); |
michael@0 | 212 | Services.obs.addObserver(this, "quit-application", false); |
michael@0 | 213 | }, |
michael@0 | 214 | |
michael@0 | 215 | setUpdateStatus: function UP_setUpdateStatus(aStatus) { |
michael@0 | 216 | if (useSettings()) { |
michael@0 | 217 | log("Setting gecko.updateStatus: " + aStatus); |
michael@0 | 218 | |
michael@0 | 219 | let lock = Services.settings.createLock(); |
michael@0 | 220 | lock.set("gecko.updateStatus", aStatus, null); |
michael@0 | 221 | } |
michael@0 | 222 | }, |
michael@0 | 223 | |
michael@0 | 224 | showApplyPrompt: function UP_showApplyPrompt(aUpdate) { |
michael@0 | 225 | if (!this.sendUpdateEvent("update-prompt-apply", aUpdate)) { |
michael@0 | 226 | log("Unable to prompt, forcing restart"); |
michael@0 | 227 | this.restartProcess(); |
michael@0 | 228 | return; |
michael@0 | 229 | } |
michael@0 | 230 | |
michael@0 | 231 | #ifdef MOZ_B2G_RIL |
michael@0 | 232 | let window = Services.wm.getMostRecentWindow("navigator:browser"); |
michael@0 | 233 | let pinReq = window.navigator.mozIccManager.getCardLock("pin"); |
michael@0 | 234 | pinReq.onsuccess = function(e) { |
michael@0 | 235 | if (e.target.result.enabled) { |
michael@0 | 236 | // The SIM is pin locked. Don't use a fallback timer. This means that |
michael@0 | 237 | // the user has to press Install to apply the update. If we use the |
michael@0 | 238 | // timer, and the timer reboots the phone, then the phone will be |
michael@0 | 239 | // unusable until the SIM is unlocked. |
michael@0 | 240 | log("SIM is pin locked. Not starting fallback timer."); |
michael@0 | 241 | } else { |
michael@0 | 242 | // This means that no pin lock is enabled, so we go ahead and start |
michael@0 | 243 | // the fallback timer. |
michael@0 | 244 | this._applyPromptTimer = this.createTimer(this.applyPromptTimeout); |
michael@0 | 245 | } |
michael@0 | 246 | }.bind(this); |
michael@0 | 247 | pinReq.onerror = function(e) { |
michael@0 | 248 | this._applyPromptTimer = this.createTimer(this.applyPromptTimeout); |
michael@0 | 249 | }.bind(this); |
michael@0 | 250 | #else |
michael@0 | 251 | // Schedule a fallback timeout in case the UI is unable to respond or show |
michael@0 | 252 | // a prompt for some reason. |
michael@0 | 253 | this._applyPromptTimer = this.createTimer(this.applyPromptTimeout); |
michael@0 | 254 | #endif |
michael@0 | 255 | }, |
michael@0 | 256 | |
michael@0 | 257 | _copyProperties: ["appVersion", "buildID", "detailsURL", "displayVersion", |
michael@0 | 258 | "errorCode", "isOSUpdate", "platformVersion", |
michael@0 | 259 | "previousAppVersion", "state", "statusText"], |
michael@0 | 260 | |
michael@0 | 261 | sendUpdateEvent: function UP_sendUpdateEvent(aType, aUpdate) { |
michael@0 | 262 | let detail = {}; |
michael@0 | 263 | for each (let property in this._copyProperties) { |
michael@0 | 264 | detail[property] = aUpdate[property]; |
michael@0 | 265 | } |
michael@0 | 266 | |
michael@0 | 267 | let patch = aUpdate.selectedPatch; |
michael@0 | 268 | if (!patch && aUpdate.patchCount > 0) { |
michael@0 | 269 | // For now we just check the first patch to get size information if a |
michael@0 | 270 | // patch hasn't been selected yet. |
michael@0 | 271 | patch = aUpdate.getPatchAt(0); |
michael@0 | 272 | } |
michael@0 | 273 | |
michael@0 | 274 | if (patch) { |
michael@0 | 275 | detail.size = patch.size; |
michael@0 | 276 | detail.updateType = patch.type; |
michael@0 | 277 | } else { |
michael@0 | 278 | log("Warning: no patches available in update"); |
michael@0 | 279 | } |
michael@0 | 280 | |
michael@0 | 281 | this._update = aUpdate; |
michael@0 | 282 | return this.sendChromeEvent(aType, detail); |
michael@0 | 283 | }, |
michael@0 | 284 | |
michael@0 | 285 | sendChromeEvent: function UP_sendChromeEvent(aType, aDetail) { |
michael@0 | 286 | let detail = aDetail || {}; |
michael@0 | 287 | detail.type = aType; |
michael@0 | 288 | |
michael@0 | 289 | let sent = SystemAppProxy.dispatchEvent(detail); |
michael@0 | 290 | if (!sent) { |
michael@0 | 291 | log("Warning: Couldn't send update event " + aType + |
michael@0 | 292 | ": no content browser. Will send again when content becomes available."); |
michael@0 | 293 | return false; |
michael@0 | 294 | } |
michael@0 | 295 | return true; |
michael@0 | 296 | }, |
michael@0 | 297 | |
michael@0 | 298 | handleAvailableResult: function UP_handleAvailableResult(aDetail) { |
michael@0 | 299 | // If the user doesn't choose "download", the updater will implicitly call |
michael@0 | 300 | // showUpdateAvailable again after a certain period of time |
michael@0 | 301 | switch (aDetail.result) { |
michael@0 | 302 | case "download": |
michael@0 | 303 | this.downloadUpdate(this._update); |
michael@0 | 304 | break; |
michael@0 | 305 | } |
michael@0 | 306 | }, |
michael@0 | 307 | |
michael@0 | 308 | handleApplyPromptResult: function UP_handleApplyPromptResult(aDetail) { |
michael@0 | 309 | if (this._applyPromptTimer) { |
michael@0 | 310 | this._applyPromptTimer.cancel(); |
michael@0 | 311 | this._applyPromptTimer = null; |
michael@0 | 312 | } |
michael@0 | 313 | |
michael@0 | 314 | switch (aDetail.result) { |
michael@0 | 315 | case "wait": |
michael@0 | 316 | // Wait until the user is idle before prompting to apply the update |
michael@0 | 317 | this.waitForIdle(); |
michael@0 | 318 | break; |
michael@0 | 319 | case "restart": |
michael@0 | 320 | this.finishUpdate(); |
michael@0 | 321 | this._update = null; |
michael@0 | 322 | break; |
michael@0 | 323 | } |
michael@0 | 324 | }, |
michael@0 | 325 | |
michael@0 | 326 | downloadUpdate: function UP_downloadUpdate(aUpdate) { |
michael@0 | 327 | if (!aUpdate) { |
michael@0 | 328 | aUpdate = Services.um.activeUpdate; |
michael@0 | 329 | if (!aUpdate) { |
michael@0 | 330 | log("No active update found to download"); |
michael@0 | 331 | return; |
michael@0 | 332 | } |
michael@0 | 333 | } |
michael@0 | 334 | |
michael@0 | 335 | let status = Services.aus.downloadUpdate(aUpdate, true); |
michael@0 | 336 | if (status == STATE_DOWNLOADING) { |
michael@0 | 337 | Services.aus.addDownloadListener(this); |
michael@0 | 338 | return; |
michael@0 | 339 | } |
michael@0 | 340 | |
michael@0 | 341 | // If the update has already been downloaded and applied, then |
michael@0 | 342 | // Services.aus.downloadUpdate will return immediately and not |
michael@0 | 343 | // call showUpdateDownloaded, so we detect this. |
michael@0 | 344 | if (aUpdate.state == "applied" && aUpdate.errorCode == 0) { |
michael@0 | 345 | this.showUpdateDownloaded(aUpdate, true); |
michael@0 | 346 | return; |
michael@0 | 347 | } |
michael@0 | 348 | |
michael@0 | 349 | log("Error downloading update " + aUpdate.name + ": " + aUpdate.errorCode); |
michael@0 | 350 | let errorCode = aUpdate.errorCode >>> 0; |
michael@0 | 351 | if (errorCode == Cr.NS_ERROR_FILE_TOO_BIG) { |
michael@0 | 352 | aUpdate.statusText = "file-too-big"; |
michael@0 | 353 | } |
michael@0 | 354 | this.showUpdateError(aUpdate); |
michael@0 | 355 | }, |
michael@0 | 356 | |
michael@0 | 357 | handleDownloadCancel: function UP_handleDownloadCancel() { |
michael@0 | 358 | log("Pausing download"); |
michael@0 | 359 | Services.aus.pauseDownload(); |
michael@0 | 360 | }, |
michael@0 | 361 | |
michael@0 | 362 | finishUpdate: function UP_finishUpdate() { |
michael@0 | 363 | if (!this._update.isOSUpdate) { |
michael@0 | 364 | // Standard gecko+gaia updates will just need to restart the process |
michael@0 | 365 | this.restartProcess(); |
michael@0 | 366 | return; |
michael@0 | 367 | } |
michael@0 | 368 | |
michael@0 | 369 | try { |
michael@0 | 370 | Services.aus.applyOsUpdate(this._update); |
michael@0 | 371 | } |
michael@0 | 372 | catch (e) { |
michael@0 | 373 | this._update.errorCode = Cr.NS_ERROR_FAILURE; |
michael@0 | 374 | this.showUpdateError(this._update); |
michael@0 | 375 | } |
michael@0 | 376 | }, |
michael@0 | 377 | |
michael@0 | 378 | restartProcess: function UP_restartProcess() { |
michael@0 | 379 | log("Update downloaded, restarting to apply it"); |
michael@0 | 380 | |
michael@0 | 381 | let callbackAfterSet = function() { |
michael@0 | 382 | #ifndef MOZ_WIDGET_GONK |
michael@0 | 383 | let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"] |
michael@0 | 384 | .getService(Ci.nsIAppStartup); |
michael@0 | 385 | appStartup.quit(appStartup.eForceQuit | appStartup.eRestart); |
michael@0 | 386 | #else |
michael@0 | 387 | // NB: on Gonk, we rely on the system process manager to restart us. |
michael@0 | 388 | let pmService = Cc["@mozilla.org/power/powermanagerservice;1"] |
michael@0 | 389 | .getService(Ci.nsIPowerManagerService); |
michael@0 | 390 | pmService.restart(); |
michael@0 | 391 | #endif |
michael@0 | 392 | } |
michael@0 | 393 | |
michael@0 | 394 | if (useSettings()) { |
michael@0 | 395 | // Save current os version in deviceinfo.previous_os |
michael@0 | 396 | let lock = Services.settings.createLock({ |
michael@0 | 397 | handle: callbackAfterSet, |
michael@0 | 398 | handleAbort: function(error) { |
michael@0 | 399 | log("Abort callback when trying to set previous_os: " + error); |
michael@0 | 400 | callbackAfterSet(); |
michael@0 | 401 | } |
michael@0 | 402 | }); |
michael@0 | 403 | lock.get("deviceinfo.os", { |
michael@0 | 404 | handle: function(name, value) { |
michael@0 | 405 | log("Set previous_os to: " + value); |
michael@0 | 406 | lock.set("deviceinfo.previous_os", value, null, null); |
michael@0 | 407 | } |
michael@0 | 408 | }); |
michael@0 | 409 | } |
michael@0 | 410 | }, |
michael@0 | 411 | |
michael@0 | 412 | forceUpdateCheck: function UP_forceUpdateCheck() { |
michael@0 | 413 | log("Forcing update check"); |
michael@0 | 414 | |
michael@0 | 415 | let checker = Cc["@mozilla.org/updates/update-checker;1"] |
michael@0 | 416 | .createInstance(Ci.nsIUpdateChecker); |
michael@0 | 417 | checker.checkForUpdates(this._updateCheckListener, true); |
michael@0 | 418 | }, |
michael@0 | 419 | |
michael@0 | 420 | handleEvent: function UP_handleEvent(evt) { |
michael@0 | 421 | if (evt.type !== "mozContentEvent") { |
michael@0 | 422 | return; |
michael@0 | 423 | } |
michael@0 | 424 | |
michael@0 | 425 | let detail = evt.detail; |
michael@0 | 426 | if (!detail) { |
michael@0 | 427 | return; |
michael@0 | 428 | } |
michael@0 | 429 | |
michael@0 | 430 | switch (detail.type) { |
michael@0 | 431 | case "force-update-check": |
michael@0 | 432 | this.forceUpdateCheck(); |
michael@0 | 433 | break; |
michael@0 | 434 | case "update-available-result": |
michael@0 | 435 | this.handleAvailableResult(detail); |
michael@0 | 436 | // If we started the apply prompt timer, this means that we're waiting |
michael@0 | 437 | // for the user to press Later or Install Now. In this situation we |
michael@0 | 438 | // don't want to clear this._update, becuase handleApplyPromptResult |
michael@0 | 439 | // needs it. |
michael@0 | 440 | if (this._applyPromptTimer == null && !this._waitingForIdle) { |
michael@0 | 441 | this._update = null; |
michael@0 | 442 | } |
michael@0 | 443 | break; |
michael@0 | 444 | case "update-download-cancel": |
michael@0 | 445 | this.handleDownloadCancel(); |
michael@0 | 446 | break; |
michael@0 | 447 | case "update-prompt-apply-result": |
michael@0 | 448 | this.handleApplyPromptResult(detail); |
michael@0 | 449 | break; |
michael@0 | 450 | } |
michael@0 | 451 | }, |
michael@0 | 452 | |
michael@0 | 453 | // nsIObserver |
michael@0 | 454 | |
michael@0 | 455 | observe: function UP_observe(aSubject, aTopic, aData) { |
michael@0 | 456 | switch (aTopic) { |
michael@0 | 457 | case "idle": |
michael@0 | 458 | this._waitingForIdle = false; |
michael@0 | 459 | this.showApplyPrompt(this._update); |
michael@0 | 460 | // Fall through |
michael@0 | 461 | case "quit-application": |
michael@0 | 462 | Services.idle.removeIdleObserver(this, this.applyIdleTimeout / 1000); |
michael@0 | 463 | Services.obs.removeObserver(this, "quit-application"); |
michael@0 | 464 | break; |
michael@0 | 465 | case "update-check-start": |
michael@0 | 466 | WebappsUpdater.updateApps(); |
michael@0 | 467 | break; |
michael@0 | 468 | } |
michael@0 | 469 | }, |
michael@0 | 470 | |
michael@0 | 471 | // nsITimerCallback |
michael@0 | 472 | |
michael@0 | 473 | notify: function UP_notify(aTimer) { |
michael@0 | 474 | if (aTimer == this._applyPromptTimer) { |
michael@0 | 475 | log("Timed out waiting for result, restarting"); |
michael@0 | 476 | this._applyPromptTimer = null; |
michael@0 | 477 | this.finishUpdate(); |
michael@0 | 478 | this._update = null; |
michael@0 | 479 | return; |
michael@0 | 480 | } |
michael@0 | 481 | if (aTimer == this._watchdogTimer) { |
michael@0 | 482 | log("Download watchdog fired"); |
michael@0 | 483 | this._watchdogTimer = null; |
michael@0 | 484 | this._autoRestartDownload = true; |
michael@0 | 485 | Services.aus.pauseDownload(); |
michael@0 | 486 | return; |
michael@0 | 487 | } |
michael@0 | 488 | }, |
michael@0 | 489 | |
michael@0 | 490 | createTimer: function UP_createTimer(aTimeoutMs) { |
michael@0 | 491 | let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); |
michael@0 | 492 | timer.initWithCallback(this, aTimeoutMs, timer.TYPE_ONE_SHOT); |
michael@0 | 493 | return timer; |
michael@0 | 494 | }, |
michael@0 | 495 | |
michael@0 | 496 | // nsIRequestObserver |
michael@0 | 497 | |
michael@0 | 498 | _startedSent: false, |
michael@0 | 499 | |
michael@0 | 500 | _watchdogTimer: null, |
michael@0 | 501 | |
michael@0 | 502 | _autoRestartDownload: false, |
michael@0 | 503 | _autoRestartCount: 0, |
michael@0 | 504 | |
michael@0 | 505 | startWatchdogTimer: function UP_startWatchdogTimer() { |
michael@0 | 506 | let watchdogTimeout = 120000; // 120 seconds |
michael@0 | 507 | try { |
michael@0 | 508 | watchdogTimeout = Services.prefs.getIntPref(PREF_DOWNLOAD_WATCHDOG_TIMEOUT); |
michael@0 | 509 | } catch (e) { |
michael@0 | 510 | // This means that the preference doesn't exist. watchdogTimeout will |
michael@0 | 511 | // retain its default assigned above. |
michael@0 | 512 | } |
michael@0 | 513 | if (watchdogTimeout <= 0) { |
michael@0 | 514 | // 0 implies don't bother using the watchdog timer at all. |
michael@0 | 515 | this._watchdogTimer = null; |
michael@0 | 516 | return; |
michael@0 | 517 | } |
michael@0 | 518 | if (this._watchdogTimer) { |
michael@0 | 519 | this._watchdogTimer.cancel(); |
michael@0 | 520 | } else { |
michael@0 | 521 | this._watchdogTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); |
michael@0 | 522 | } |
michael@0 | 523 | this._watchdogTimer.initWithCallback(this, watchdogTimeout, |
michael@0 | 524 | Ci.nsITimer.TYPE_ONE_SHOT); |
michael@0 | 525 | }, |
michael@0 | 526 | |
michael@0 | 527 | stopWatchdogTimer: function UP_stopWatchdogTimer() { |
michael@0 | 528 | if (this._watchdogTimer) { |
michael@0 | 529 | this._watchdogTimer.cancel(); |
michael@0 | 530 | this._watchdogTimer = null; |
michael@0 | 531 | } |
michael@0 | 532 | }, |
michael@0 | 533 | |
michael@0 | 534 | touchWatchdogTimer: function UP_touchWatchdogTimer() { |
michael@0 | 535 | this.startWatchdogTimer(); |
michael@0 | 536 | }, |
michael@0 | 537 | |
michael@0 | 538 | onStartRequest: function UP_onStartRequest(aRequest, aContext) { |
michael@0 | 539 | // Wait until onProgress to send the update-download-started event, in case |
michael@0 | 540 | // this request turns out to fail for some reason |
michael@0 | 541 | this._startedSent = false; |
michael@0 | 542 | this.startWatchdogTimer(); |
michael@0 | 543 | }, |
michael@0 | 544 | |
michael@0 | 545 | onStopRequest: function UP_onStopRequest(aRequest, aContext, aStatusCode) { |
michael@0 | 546 | this.stopWatchdogTimer(); |
michael@0 | 547 | Services.aus.removeDownloadListener(this); |
michael@0 | 548 | let paused = !Components.isSuccessCode(aStatusCode); |
michael@0 | 549 | if (!paused) { |
michael@0 | 550 | // The download was successful, no need to restart |
michael@0 | 551 | this._autoRestartDownload = false; |
michael@0 | 552 | } |
michael@0 | 553 | if (this._autoRestartDownload) { |
michael@0 | 554 | this._autoRestartDownload = false; |
michael@0 | 555 | let watchdogMaxRetries = Services.prefs.getIntPref(PREF_DOWNLOAD_WATCHDOG_MAX_RETRIES); |
michael@0 | 556 | this._autoRestartCount++; |
michael@0 | 557 | if (this._autoRestartCount > watchdogMaxRetries) { |
michael@0 | 558 | log("Download - retry count exceeded - error"); |
michael@0 | 559 | // We exceeded the max retries. Treat the download like an error, |
michael@0 | 560 | // which will give the user a chance to restart manually later. |
michael@0 | 561 | this._autoRestartCount = 0; |
michael@0 | 562 | if (Services.um.activeUpdate) { |
michael@0 | 563 | this.showUpdateError(Services.um.activeUpdate); |
michael@0 | 564 | } |
michael@0 | 565 | return; |
michael@0 | 566 | } |
michael@0 | 567 | log("Download - restarting download - attempt " + this._autoRestartCount); |
michael@0 | 568 | this.downloadUpdate(null); |
michael@0 | 569 | return; |
michael@0 | 570 | } |
michael@0 | 571 | this._autoRestartCount = 0; |
michael@0 | 572 | this.sendChromeEvent("update-download-stopped", { |
michael@0 | 573 | paused: paused |
michael@0 | 574 | }); |
michael@0 | 575 | }, |
michael@0 | 576 | |
michael@0 | 577 | // nsIProgressEventSink |
michael@0 | 578 | |
michael@0 | 579 | onProgress: function UP_onProgress(aRequest, aContext, aProgress, |
michael@0 | 580 | aProgressMax) { |
michael@0 | 581 | if (aProgress == aProgressMax) { |
michael@0 | 582 | // The update.mar validation done by onStopRequest may take |
michael@0 | 583 | // a while before the onStopRequest callback is made, so stop |
michael@0 | 584 | // the timer now. |
michael@0 | 585 | this.stopWatchdogTimer(); |
michael@0 | 586 | } else { |
michael@0 | 587 | this.touchWatchdogTimer(); |
michael@0 | 588 | } |
michael@0 | 589 | if (!this._startedSent) { |
michael@0 | 590 | this.sendChromeEvent("update-download-started", { |
michael@0 | 591 | total: aProgressMax |
michael@0 | 592 | }); |
michael@0 | 593 | this._startedSent = true; |
michael@0 | 594 | } |
michael@0 | 595 | |
michael@0 | 596 | this.sendChromeEvent("update-download-progress", { |
michael@0 | 597 | progress: aProgress, |
michael@0 | 598 | total: aProgressMax |
michael@0 | 599 | }); |
michael@0 | 600 | }, |
michael@0 | 601 | |
michael@0 | 602 | onStatus: function UP_onStatus(aRequest, aUpdate, aStatus, aStatusArg) { } |
michael@0 | 603 | }; |
michael@0 | 604 | |
michael@0 | 605 | this.NSGetFactory = XPCOMUtils.generateNSGetFactory([UpdatePrompt]); |