summaryrefslogtreecommitdiffstats
path: root/b2g/components/UpdatePrompt.js
diff options
context:
space:
mode:
Diffstat (limited to 'b2g/components/UpdatePrompt.js')
-rw-r--r--b2g/components/UpdatePrompt.js783
1 files changed, 783 insertions, 0 deletions
diff --git a/b2g/components/UpdatePrompt.js b/b2g/components/UpdatePrompt.js
new file mode 100644
index 000000000..1df07204c
--- /dev/null
+++ b/b2g/components/UpdatePrompt.js
@@ -0,0 +1,783 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* 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/. */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
+const VERBOSE = 1;
+var log =
+ VERBOSE ?
+ function log_dump(msg) { dump("UpdatePrompt: "+ msg +"\n"); } :
+ function log_noop(msg) { };
+
+const PREF_APPLY_PROMPT_TIMEOUT = "b2g.update.apply-prompt-timeout";
+const PREF_APPLY_IDLE_TIMEOUT = "b2g.update.apply-idle-timeout";
+const PREF_DOWNLOAD_WATCHDOG_TIMEOUT = "b2g.update.download-watchdog-timeout";
+const PREF_DOWNLOAD_WATCHDOG_MAX_RETRIES = "b2g.update.download-watchdog-max-retries";
+
+const NETWORK_ERROR_OFFLINE = 111;
+const HTTP_ERROR_OFFSET = 1000;
+
+const STATE_DOWNLOADING = 'downloading';
+
+XPCOMUtils.defineLazyServiceGetter(Services, "aus",
+ "@mozilla.org/updates/update-service;1",
+ "nsIApplicationUpdateService");
+
+XPCOMUtils.defineLazyServiceGetter(Services, "um",
+ "@mozilla.org/updates/update-manager;1",
+ "nsIUpdateManager");
+
+XPCOMUtils.defineLazyServiceGetter(Services, "idle",
+ "@mozilla.org/widget/idleservice;1",
+ "nsIIdleService");
+
+XPCOMUtils.defineLazyServiceGetter(Services, "settings",
+ "@mozilla.org/settingsService;1",
+ "nsISettingsService");
+
+XPCOMUtils.defineLazyServiceGetter(Services, 'env',
+ '@mozilla.org/process/environment;1',
+ 'nsIEnvironment');
+
+function useSettings() {
+ // When we're running in the real phone, then we can use settings.
+ // But when we're running as part of xpcshell, there is no settings database
+ // and trying to use settings in this scenario causes lots of weird
+ // assertions at shutdown time.
+ if (typeof useSettings.result === "undefined") {
+ useSettings.result = !Services.env.get("XPCSHELL_TEST_PROFILE_DIR");
+ }
+ return useSettings.result;
+}
+
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+ "resource://gre/modules/SystemAppProxy.jsm");
+
+function UpdateCheckListener(updatePrompt) {
+ this._updatePrompt = updatePrompt;
+}
+
+UpdateCheckListener.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateCheckListener]),
+
+ _updatePrompt: null,
+
+ onCheckComplete: function UCL_onCheckComplete(request, updates, updateCount) {
+ if (Services.um.activeUpdate) {
+ // We're actively downloading an update, that's the update the user should
+ // see, even if a newer update is available.
+ this._updatePrompt.setUpdateStatus("active-update");
+ this._updatePrompt.showUpdateAvailable(Services.um.activeUpdate);
+ return;
+ }
+
+ if (updateCount == 0) {
+ this._updatePrompt.setUpdateStatus("no-updates");
+
+ if (this._updatePrompt._systemUpdateListener) {
+ this._updatePrompt._systemUpdateListener.onError("no-updates");
+ }
+
+ return;
+ }
+
+ let update = Services.aus.selectUpdate(updates, updateCount);
+ if (!update) {
+ this._updatePrompt.setUpdateStatus("already-latest-version");
+
+ if (this._updatePrompt._systemUpdateListener) {
+ this._updatePrompt._systemUpdateListener.onError("already-latest-version");
+ }
+
+ return;
+ }
+
+ this._updatePrompt.setUpdateStatus("check-complete");
+ this._updatePrompt.showUpdateAvailable(update);
+ },
+
+ onError: function UCL_onError(request, update) {
+ // nsIUpdate uses a signed integer for errorCode while any platform errors
+ // require all 32 bits.
+ let errorCode = update.errorCode >>> 0;
+ let isNSError = (errorCode >>> 31) == 1;
+ let errorMsg = "check-error-";
+
+ if (errorCode == NETWORK_ERROR_OFFLINE) {
+ errorMsg = "retry-when-online";
+ this._updatePrompt.setUpdateStatus(errorMsg);
+ } else if (isNSError) {
+ errorMsg = "check-error-" + errorCode;
+ this._updatePrompt.setUpdateStatus(errorMsg);
+ } else if (errorCode > HTTP_ERROR_OFFSET) {
+ let httpErrorCode = errorCode - HTTP_ERROR_OFFSET;
+ errorMsg = "check-error-http-" + httpErrorCode;
+ this._updatePrompt.setUpdateStatus(errorMsg);
+ }
+
+ if (this._updatePrompt._systemUpdateListener) {
+ this._updatePrompt._systemUpdateListener.onError(errorMsg);
+ }
+
+ Services.aus.QueryInterface(Ci.nsIUpdateCheckListener);
+ Services.aus.onError(request, update);
+ }
+};
+
+function UpdatePrompt() {
+ this.wrappedJSObject = this;
+ this._updateCheckListener = new UpdateCheckListener(this);
+}
+
+UpdatePrompt.prototype = {
+ classID: Components.ID("{88b3eb21-d072-4e3b-886d-f89d8c49fe59}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePrompt,
+ Ci.nsIUpdateCheckListener,
+ Ci.nsIRequestObserver,
+ Ci.nsIProgressEventSink,
+ Ci.nsIObserver,
+ Ci.nsISystemUpdateProvider]),
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(UpdatePrompt),
+
+ _update: null,
+ _applyPromptTimer: null,
+ _waitingForIdle: false,
+ _updateCheckListner: null,
+ _systemUpdateListener: null,
+ _availableParameters: {
+ "deviceinfo.last_updated": null,
+ "gecko.updateStatus": null,
+ "app.update.channel": null,
+ "app.update.interval": null,
+ "app.update.url": null,
+ },
+ _pendingUpdateAvailablePackageInfo: null,
+ _isPendingUpdateReady: false,
+ _updateErrorQueue: [ ],
+ _receivedUpdatePromptReady: false,
+
+ // nsISystemUpdateProvider
+ checkForUpdate: function() {
+ this.forceUpdateCheck();
+ },
+
+ startDownload: function() {
+ this.downloadUpdate(this._update);
+ },
+
+ stopDownload: function() {
+ this.handleDownloadCancel();
+ },
+
+ applyUpdate: function() {
+ this.handleApplyPromptResult({result: "restart"});
+ },
+
+ setParameter: function(aName, aValue) {
+ if (!this._availableParameters.hasOwnProperty(aName)) {
+ return false;
+ }
+
+ this._availableParameters[aName] = aValue;
+
+ switch (aName) {
+ case "app.update.channel":
+ case "app.update.url":
+ Services.prefs.setCharPref(aName, aValue);
+ break;
+ case "app.update.interval":
+ Services.prefs.setIntPref(aName, parseInt(aValue, 10));
+ break;
+ }
+
+ return true;
+ },
+
+ getParameter: function(aName) {
+ if (!this._availableParameters.hasOwnProperty(aName)) {
+ return null;
+ }
+
+ return this._availableParameters[aName];
+ },
+
+ setListener: function(aListener) {
+ this._systemUpdateListener = aListener;
+
+ // If an update is available or ready, trigger the event right away at this point.
+ if (this._pendingUpdateAvailablePackageInfo) {
+ this._systemUpdateListener.onUpdateAvailable(this._pendingUpdateAvailablePackageInfo.type,
+ this._pendingUpdateAvailablePackageInfo.version,
+ this._pendingUpdateAvailablePackageInfo.description,
+ this._pendingUpdateAvailablePackageInfo.buildDate,
+ this._pendingUpdateAvailablePackageInfo.size);
+ // Set null when the listener is attached.
+ this._pendingUpdateAvailablePackageInfo = null;
+ }
+
+ if (this._isPendingUpdateReady) {
+ this._systemUpdateListener.onUpdateReady();
+ this._isPendingUpdateReady = false;
+ }
+ },
+
+ unsetListener: function(aListener) {
+ this._systemUpdateListener = null;
+ },
+
+ get applyPromptTimeout() {
+ return Services.prefs.getIntPref(PREF_APPLY_PROMPT_TIMEOUT);
+ },
+
+ get applyIdleTimeout() {
+ return Services.prefs.getIntPref(PREF_APPLY_IDLE_TIMEOUT);
+ },
+
+ handleContentStart: function UP_handleContentStart() {
+ SystemAppProxy.addEventListener("mozContentEvent", this);
+ },
+
+ // nsIUpdatePrompt
+
+ // FIXME/bug 737601: we should have users opt-in to downloading
+ // updates when on a billed pipe. Initially, opt-in for 3g, but
+ // that doesn't cover all cases.
+ checkForUpdates: function UP_checkForUpdates() { },
+
+ showUpdateAvailable: function UP_showUpdateAvailable(aUpdate) {
+ let packageInfo = {};
+ packageInfo.version = aUpdate.displayVersion;
+ packageInfo.description = aUpdate.statusText;
+ packageInfo.buildDate = aUpdate.buildID;
+
+ let patch = aUpdate.selectedPatch;
+ if (!patch && aUpdate.patchCount > 0) {
+ // For now we just check the first patch to get size information if a
+ // patch hasn't been selected yet.
+ patch = aUpdate.getPatchAt(0);
+ }
+
+ if (patch) {
+ packageInfo.size = patch.size;
+ packageInfo.type = patch.type;
+ } else {
+ log("Warning: no patches available in update");
+ }
+
+ this._pendingUpdateAvailablePackageInfo = packageInfo;
+
+ if (this._systemUpdateListener) {
+ this._systemUpdateListener.onUpdateAvailable(packageInfo.type,
+ packageInfo.version,
+ packageInfo.description,
+ packageInfo.buildDate,
+ packageInfo.size);
+ // Set null since the event is fired.
+ this._pendingUpdateAvailablePackageInfo = null;
+ }
+
+ if (!this.sendUpdateEvent("update-available", aUpdate)) {
+
+ log("Unable to prompt for available update, forcing download");
+ this.downloadUpdate(aUpdate);
+ }
+ },
+
+ showUpdateDownloaded: function UP_showUpdateDownloaded(aUpdate, aBackground) {
+ if (this._systemUpdateListener) {
+ this._systemUpdateListener.onUpdateReady();
+ } else {
+ this._isPendingUpdateReady = true;
+ }
+
+ // The update has been downloaded and staged. We send the update-downloaded
+ // event right away. After the user has been idle for a while, we send the
+ // update-prompt-restart event, increasing the chances that we can apply the
+ // update quietly without user intervention.
+ this.sendUpdateEvent("update-downloaded", aUpdate);
+
+ if (Services.idle.idleTime >= this.applyIdleTimeout) {
+ this.showApplyPrompt(aUpdate);
+ return;
+ }
+
+ let applyIdleTimeoutSeconds = this.applyIdleTimeout / 1000;
+ // We haven't been idle long enough, so register an observer
+ log("Update is ready to apply, registering idle timeout of " +
+ applyIdleTimeoutSeconds + " seconds before prompting.");
+
+ this._update = aUpdate;
+ this.waitForIdle();
+ },
+
+ storeUpdateError: function UP_storeUpdateError(aUpdate) {
+ log("Storing update error for later use");
+ this._updateErrorQueue.push(aUpdate);
+ },
+
+ sendStoredUpdateError: function UP_sendStoredUpdateError() {
+ log("Sending stored update error");
+ this._updateErrorQueue.forEach(aUpdate => {
+ this.sendUpdateEvent("update-error", aUpdate);
+ });
+ this._updateErrorQueue = [ ];
+ },
+
+ showUpdateError: function UP_showUpdateError(aUpdate) {
+ log("Update error, state: " + aUpdate.state + ", errorCode: " +
+ aUpdate.errorCode);
+ if (this._systemUpdateListener) {
+ this._systemUpdateListener.onError("update-error: " + aUpdate.errorCode + " " + aUpdate.statusText);
+ }
+
+ if (!this._receivedUpdatePromptReady) {
+ this.storeUpdateError(aUpdate);
+ } else {
+ this.sendUpdateEvent("update-error", aUpdate);
+ }
+
+ this.setUpdateStatus(aUpdate.statusText);
+ },
+
+ showUpdateHistory: function UP_showUpdateHistory(aParent) { },
+ showUpdateInstalled: function UP_showUpdateInstalled() {
+ this.setParameter("deviceinfo.last_updated", Date.now());
+
+ if (useSettings()) {
+ let lock = Services.settings.createLock();
+ lock.set("deviceinfo.last_updated", Date.now(), null, null);
+ }
+ },
+
+ // Custom functions
+
+ waitForIdle: function UP_waitForIdle() {
+ if (this._waitingForIdle) {
+ return;
+ }
+
+ this._waitingForIdle = true;
+ Services.idle.addIdleObserver(this, this.applyIdleTimeout / 1000);
+ Services.obs.addObserver(this, "quit-application", false);
+ },
+
+ setUpdateStatus: function UP_setUpdateStatus(aStatus) {
+ this.setParameter("gecko.updateStatus", aStatus);
+
+ if (useSettings()) {
+ log("Setting gecko.updateStatus: " + aStatus);
+
+ let lock = Services.settings.createLock();
+ lock.set("gecko.updateStatus", aStatus, null);
+ }
+ },
+
+ showApplyPrompt: function UP_showApplyPrompt(aUpdate) {
+ // Notify update package is ready to apply
+ if (this._systemUpdateListener) {
+ this._systemUpdateListener.onUpdateReady();
+ } else {
+ // Set the flag to true and fire the onUpdateReady event when the listener is attached.
+ this._isPendingUpdateReady = true;
+ }
+
+ if (!this.sendUpdateEvent("update-prompt-apply", aUpdate)) {
+ log("Unable to prompt, forcing restart");
+ this.restartProcess();
+ return;
+ }
+
+ if (AppConstants.MOZ_B2G_RIL) {
+ let window = Services.wm.getMostRecentWindow("navigator:browser");
+ let pinReq = window.navigator.mozIccManager.getCardLock("pin");
+ pinReq.onsuccess = function(e) {
+ if (e.target.result.enabled) {
+ // The SIM is pin locked. Don't use a fallback timer. This means that
+ // the user has to press Install to apply the update. If we use the
+ // timer, and the timer reboots the phone, then the phone will be
+ // unusable until the SIM is unlocked.
+ log("SIM is pin locked. Not starting fallback timer.");
+ } else {
+ // This means that no pin lock is enabled, so we go ahead and start
+ // the fallback timer.
+ this._applyPromptTimer = this.createTimer(this.applyPromptTimeout);
+ }
+ }.bind(this);
+ pinReq.onerror = function(e) {
+ this._applyPromptTimer = this.createTimer(this.applyPromptTimeout);
+ }.bind(this);
+ } else {
+ // Schedule a fallback timeout in case the UI is unable to respond or show
+ // a prompt for some reason.
+ this._applyPromptTimer = this.createTimer(this.applyPromptTimeout);
+ }
+ },
+
+ _copyProperties: ["appVersion", "buildID", "detailsURL", "displayVersion",
+ "errorCode", "isOSUpdate", "platformVersion",
+ "previousAppVersion", "state", "statusText"],
+
+ sendUpdateEvent: function UP_sendUpdateEvent(aType, aUpdate) {
+ let detail = {};
+ for (let property of this._copyProperties) {
+ detail[property] = aUpdate[property];
+ }
+
+ let patch = aUpdate.selectedPatch;
+ if (!patch && aUpdate.patchCount > 0) {
+ // For now we just check the first patch to get size information if a
+ // patch hasn't been selected yet.
+ patch = aUpdate.getPatchAt(0);
+ }
+
+ if (patch) {
+ detail.size = patch.size;
+ detail.updateType = patch.type;
+ } else {
+ log("Warning: no patches available in update");
+ }
+
+ this._update = aUpdate;
+ return this.sendChromeEvent(aType, detail);
+ },
+
+ sendChromeEvent: function UP_sendChromeEvent(aType, aDetail) {
+ let detail = aDetail || {};
+ detail.type = aType;
+
+ let sent = SystemAppProxy.dispatchEvent(detail);
+ if (!sent) {
+ log("Warning: Couldn't send update event " + aType +
+ ": no content browser. Will send again when content becomes available.");
+ return false;
+ }
+ return true;
+ },
+
+ handleAvailableResult: function UP_handleAvailableResult(aDetail) {
+ // If the user doesn't choose "download", the updater will implicitly call
+ // showUpdateAvailable again after a certain period of time
+ switch (aDetail.result) {
+ case "download":
+ this.downloadUpdate(this._update);
+ break;
+ }
+ },
+
+ handleApplyPromptResult: function UP_handleApplyPromptResult(aDetail) {
+ if (this._applyPromptTimer) {
+ this._applyPromptTimer.cancel();
+ this._applyPromptTimer = null;
+ }
+
+ switch (aDetail.result) {
+ // Battery not okay, do not wait for idle to re-prompt
+ case "low-battery":
+ break;
+ case "wait":
+ // Wait until the user is idle before prompting to apply the update
+ this.waitForIdle();
+ break;
+ case "restart":
+ this.finishUpdate();
+ this._update = null;
+ break;
+ }
+ },
+
+ downloadUpdate: function UP_downloadUpdate(aUpdate) {
+ if (!aUpdate) {
+ aUpdate = Services.um.activeUpdate;
+ if (!aUpdate) {
+ log("No active update found to download");
+ return;
+ }
+ }
+
+ let status = Services.aus.downloadUpdate(aUpdate, true);
+ if (status == STATE_DOWNLOADING) {
+ Services.aus.addDownloadListener(this);
+ return;
+ }
+
+ // If the update has already been downloaded and applied, then
+ // Services.aus.downloadUpdate will return immediately and not
+ // call showUpdateDownloaded, so we detect this.
+ if (aUpdate.state == "applied" && aUpdate.errorCode == 0) {
+ this.showUpdateDownloaded(aUpdate, true);
+ return;
+ }
+
+ log("Error downloading update " + aUpdate.name + ": " + aUpdate.errorCode);
+ let errorCode = aUpdate.errorCode >>> 0;
+ if (errorCode == Cr.NS_ERROR_FILE_TOO_BIG) {
+ aUpdate.statusText = "file-too-big";
+ }
+ this.showUpdateError(aUpdate);
+ },
+
+ handleDownloadCancel: function UP_handleDownloadCancel() {
+ log("Pausing download");
+ Services.aus.pauseDownload();
+ },
+
+ finishUpdate: function UP_finishUpdate() {
+ if (!this._update.isOSUpdate) {
+ // Standard gecko+gaia updates will just need to restart the process
+ this.restartProcess();
+ return;
+ }
+
+ try {
+ Services.aus.applyOsUpdate(this._update);
+ }
+ catch (e) {
+ this._update.errorCode = Cr.NS_ERROR_FAILURE;
+ this.showUpdateError(this._update);
+ }
+ },
+
+ restartProcess: function UP_restartProcess() {
+ log("Update downloaded, restarting to apply it");
+
+ let callbackAfterSet = function() {
+ if (AppConstants.platform !== "gonk") {
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(appStartup.eForceQuit | appStartup.eRestart);
+ } else {
+ // NB: on Gonk, we rely on the system process manager to restart us.
+ let pmService = Cc["@mozilla.org/power/powermanagerservice;1"]
+ .getService(Ci.nsIPowerManagerService);
+ pmService.restart();
+ }
+ }
+
+ if (useSettings()) {
+ // Save current os version in deviceinfo.previous_os
+ let lock = Services.settings.createLock({
+ handle: callbackAfterSet,
+ handleAbort: function(error) {
+ log("Abort callback when trying to set previous_os: " + error);
+ callbackAfterSet();
+ }
+ });
+ lock.get("deviceinfo.os", {
+ handle: function(name, value) {
+ log("Set previous_os to: " + value);
+ lock.set("deviceinfo.previous_os", value, null, null);
+ }
+ });
+ }
+ },
+
+ forceUpdateCheck: function UP_forceUpdateCheck() {
+ log("Forcing update check");
+
+ let checker = Cc["@mozilla.org/updates/update-checker;1"]
+ .createInstance(Ci.nsIUpdateChecker);
+ checker.checkForUpdates(this._updateCheckListener, true);
+ },
+
+ handleEvent: function UP_handleEvent(evt) {
+ if (evt.type !== "mozContentEvent") {
+ return;
+ }
+
+ let detail = evt.detail;
+ if (!detail) {
+ return;
+ }
+
+ switch (detail.type) {
+ case "force-update-check":
+ this.forceUpdateCheck();
+ break;
+ case "update-available-result":
+ this.handleAvailableResult(detail);
+ // If we started the apply prompt timer, this means that we're waiting
+ // for the user to press Later or Install Now. In this situation we
+ // don't want to clear this._update, becuase handleApplyPromptResult
+ // needs it.
+ if (this._applyPromptTimer == null && !this._waitingForIdle) {
+ this._update = null;
+ }
+ break;
+ case "update-download-cancel":
+ this.handleDownloadCancel();
+ break;
+ case "update-prompt-apply-result":
+ this.handleApplyPromptResult(detail);
+ break;
+ case "update-prompt-ready":
+ this._receivedUpdatePromptReady = true;
+ this.sendStoredUpdateError();
+ break;
+ }
+ },
+
+ // nsIObserver
+
+ observe: function UP_observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "idle":
+ this._waitingForIdle = false;
+ this.showApplyPrompt(this._update);
+ // Fall through
+ case "quit-application":
+ Services.idle.removeIdleObserver(this, this.applyIdleTimeout / 1000);
+ Services.obs.removeObserver(this, "quit-application");
+ break;
+ }
+ },
+
+ // nsITimerCallback
+
+ notify: function UP_notify(aTimer) {
+ if (aTimer == this._applyPromptTimer) {
+ log("Timed out waiting for result, restarting");
+ this._applyPromptTimer = null;
+ this.finishUpdate();
+ this._update = null;
+ return;
+ }
+ if (aTimer == this._watchdogTimer) {
+ log("Download watchdog fired");
+ this._watchdogTimer = null;
+ this._autoRestartDownload = true;
+ Services.aus.pauseDownload();
+ return;
+ }
+ },
+
+ createTimer: function UP_createTimer(aTimeoutMs) {
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(this, aTimeoutMs, timer.TYPE_ONE_SHOT);
+ return timer;
+ },
+
+ // nsIRequestObserver
+
+ _startedSent: false,
+
+ _watchdogTimer: null,
+
+ _autoRestartDownload: false,
+ _autoRestartCount: 0,
+
+ startWatchdogTimer: function UP_startWatchdogTimer() {
+ let watchdogTimeout = 120000; // 120 seconds
+ try {
+ watchdogTimeout = Services.prefs.getIntPref(PREF_DOWNLOAD_WATCHDOG_TIMEOUT);
+ } catch (e) {
+ // This means that the preference doesn't exist. watchdogTimeout will
+ // retain its default assigned above.
+ }
+ if (watchdogTimeout <= 0) {
+ // 0 implies don't bother using the watchdog timer at all.
+ this._watchdogTimer = null;
+ return;
+ }
+ if (this._watchdogTimer) {
+ this._watchdogTimer.cancel();
+ } else {
+ this._watchdogTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ }
+ this._watchdogTimer.initWithCallback(this, watchdogTimeout,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ stopWatchdogTimer: function UP_stopWatchdogTimer() {
+ if (this._watchdogTimer) {
+ this._watchdogTimer.cancel();
+ this._watchdogTimer = null;
+ }
+ },
+
+ touchWatchdogTimer: function UP_touchWatchdogTimer() {
+ this.startWatchdogTimer();
+ },
+
+ onStartRequest: function UP_onStartRequest(aRequest, aContext) {
+ // Wait until onProgress to send the update-download-started event, in case
+ // this request turns out to fail for some reason
+ this._startedSent = false;
+ this.startWatchdogTimer();
+ },
+
+ onStopRequest: function UP_onStopRequest(aRequest, aContext, aStatusCode) {
+ this.stopWatchdogTimer();
+ Services.aus.removeDownloadListener(this);
+ let paused = !Components.isSuccessCode(aStatusCode);
+ if (!paused) {
+ // The download was successful, no need to restart
+ this._autoRestartDownload = false;
+ }
+ if (this._autoRestartDownload) {
+ this._autoRestartDownload = false;
+ let watchdogMaxRetries = Services.prefs.getIntPref(PREF_DOWNLOAD_WATCHDOG_MAX_RETRIES);
+ this._autoRestartCount++;
+ if (this._autoRestartCount > watchdogMaxRetries) {
+ log("Download - retry count exceeded - error");
+ // We exceeded the max retries. Treat the download like an error,
+ // which will give the user a chance to restart manually later.
+ this._autoRestartCount = 0;
+ if (Services.um.activeUpdate) {
+ this.showUpdateError(Services.um.activeUpdate);
+ }
+ return;
+ }
+ log("Download - restarting download - attempt " + this._autoRestartCount);
+ this.downloadUpdate(null);
+ return;
+ }
+ this._autoRestartCount = 0;
+ this.sendChromeEvent("update-download-stopped", {
+ paused: paused
+ });
+ },
+
+ // nsIProgressEventSink
+
+ onProgress: function UP_onProgress(aRequest, aContext, aProgress,
+ aProgressMax) {
+ if (this._systemUpdateListener) {
+ this._systemUpdateListener.onProgress(aProgress, aProgressMax);
+ }
+
+ if (aProgress == aProgressMax) {
+ // The update.mar validation done by onStopRequest may take
+ // a while before the onStopRequest callback is made, so stop
+ // the timer now.
+ this.stopWatchdogTimer();
+ } else {
+ this.touchWatchdogTimer();
+ }
+ if (!this._startedSent) {
+ this.sendChromeEvent("update-download-started", {
+ total: aProgressMax
+ });
+ this._startedSent = true;
+ }
+
+ this.sendChromeEvent("update-download-progress", {
+ progress: aProgress,
+ total: aProgressMax
+ });
+ },
+
+ onStatus: function UP_onStatus(aRequest, aUpdate, aStatus, aStatusArg) { }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([UpdatePrompt]);