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