diff options
Diffstat (limited to 'browser/base/content/aboutDialog-appUpdater.js')
-rw-r--r-- | browser/base/content/aboutDialog-appUpdater.js | 428 |
1 files changed, 428 insertions, 0 deletions
diff --git a/browser/base/content/aboutDialog-appUpdater.js b/browser/base/content/aboutDialog-appUpdater.js new file mode 100644 index 000000000..4b4fc6618 --- /dev/null +++ b/browser/base/content/aboutDialog-appUpdater.js @@ -0,0 +1,428 @@ +/* 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.xul if MOZ_UPDATER is defined. + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/DownloadUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils", + "resource://gre/modules/UpdateUtils.jsm"); + +const PREF_APP_UPDATE_CANCELATIONS_OSX = "app.update.cancelations.osx"; +const PREF_APP_UPDATE_ELEVATE_NEVER = "app.update.elevate.never"; + +var gAppUpdater; + +function onUnload(aEvent) { + if (gAppUpdater.isChecking) + gAppUpdater.checker.stopChecking(Components.interfaces.nsIUpdateChecker.CURRENT_CHECK); + // Safe to call even when there isn't a download in progress. + gAppUpdater.removeDownloadListener(); + gAppUpdater = null; +} + + +function appUpdater() +{ + XPCOMUtils.defineLazyServiceGetter(this, "aus", + "@mozilla.org/updates/update-service;1", + "nsIApplicationUpdateService"); + XPCOMUtils.defineLazyServiceGetter(this, "checker", + "@mozilla.org/updates/update-checker;1", + "nsIUpdateChecker"); + XPCOMUtils.defineLazyServiceGetter(this, "um", + "@mozilla.org/updates/update-manager;1", + "nsIUpdateManager"); + + this.updateDeck = document.getElementById("updateDeck"); + + // Hide the update deck when the update window is already open and it's not + // already applied, to avoid syncing issues between them. Applied updates + // don't have any information to sync between the windows as they both just + // show the "Restart to continue"-type button. + if (Services.wm.getMostRecentWindow("Update:Wizard") && + !this.isApplied) { + this.updateDeck.hidden = true; + return; + } + + this.bundle = Services.strings. + createBundle("chrome://browser/locale/browser.properties"); + + let manualURL = Services.urlFormatter.formatURLPref("app.update.url.manual"); + let manualLink = document.getElementById("manualLink"); + manualLink.value = manualURL; + manualLink.href = manualURL; + document.getElementById("failedLink").href = manualURL; + + if (this.updateDisabledAndLocked) { + this.selectPanel("adminDisabled"); + return; + } + + if (this.isPending || this.isApplied) { + this.selectPanel("apply"); + return; + } + + if (this.aus.isOtherInstanceHandlingUpdates) { + this.selectPanel("otherInstanceHandlingUpdates"); + return; + } + + if (this.isDownloading) { + this.startDownload(); + // selectPanel("downloading") is called from setupDownloadingUI(). + return; + } + + // Honor the "Never check for updates" option by not only disabling background + // update checks, but also in the About dialog, by presenting a + // "Check for updates" button. + // If updates are found, the user is then asked if he wants to "Update to <version>". + if (!this.updateEnabled || + Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) { + this.selectPanel("checkForUpdates"); + return; + } + + // That leaves the options + // "Check for updates, but let me choose whether to install them", and + // "Automatically install updates". + // In both cases, we check for updates without asking. + // In the "let me choose" case, we ask before downloading though, in onCheckComplete. + this.checkForUpdates(); +} + +appUpdater.prototype = +{ + // true when there is an update check in progress. + isChecking: false, + + // true when there is an update already staged / ready to be applied. + get isPending() { + if (this.update) { + return this.update.state == "pending" || + this.update.state == "pending-service" || + this.update.state == "pending-elevate"; + } + return this.um.activeUpdate && + (this.um.activeUpdate.state == "pending" || + this.um.activeUpdate.state == "pending-service" || + this.um.activeUpdate.state == "pending-elevate"); + }, + + // true when there is an update already installed in the background. + get isApplied() { + if (this.update) + return this.update.state == "applied" || + this.update.state == "applied-service"; + return this.um.activeUpdate && + (this.um.activeUpdate.state == "applied" || + this.um.activeUpdate.state == "applied-service"); + }, + + // true when there is an update download in progress. + get isDownloading() { + if (this.update) + return this.update.state == "downloading"; + return this.um.activeUpdate && + this.um.activeUpdate.state == "downloading"; + }, + + // true when updating is disabled by an administrator. + get updateDisabledAndLocked() { + return !this.updateEnabled && + Services.prefs.prefIsLocked("app.update.enabled"); + }, + + // true when updating is enabled. + get updateEnabled() { + try { + return Services.prefs.getBoolPref("app.update.enabled"); + } + catch (e) { } + return true; // Firefox default is true + }, + + // true when updating in background is enabled. + get backgroundUpdateEnabled() { + return this.updateEnabled && + gAppUpdater.aus.canStageUpdates; + }, + + // true when updating is automatic. + get updateAuto() { + try { + return Services.prefs.getBoolPref("app.update.auto"); + } + catch (e) { } + return true; // Firefox default is true + }, + + /** + * Sets the panel of the updateDeck. + * + * @param aChildID + * The id of the deck's child to select, e.g. "apply". + */ + selectPanel: function(aChildID) { + let panel = document.getElementById(aChildID); + + let button = panel.querySelector("button"); + if (button) { + if (aChildID == "downloadAndInstall") { + let updateVersion = gAppUpdater.update.displayVersion; + button.label = this.bundle.formatStringFromName("update.downloadAndInstallButton.label", [updateVersion], 1); + button.accessKey = this.bundle.GetStringFromName("update.downloadAndInstallButton.accesskey"); + } + this.updateDeck.selectedPanel = panel; + if (!document.commandDispatcher.focusedElement || // don't steal the focus + document.commandDispatcher.focusedElement.localName == "button") // except from the other buttons + button.focus(); + + } else { + this.updateDeck.selectedPanel = panel; + } + }, + + /** + * Check for updates + */ + checkForUpdates: function() { + // Clear prefs that could prevent a user from discovering available updates. + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS_OSX)) { + Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX); + } + if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) { + Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER); + } + this.selectPanel("checkingForUpdates"); + this.isChecking = true; + this.checker.checkForUpdates(this.updateCheckListener, true); + // after checking, onCheckComplete() is called + }, + + /** + * Handles oncommand for the "Restart to Update" button + * which is presented after the download has been downloaded. + */ + buttonRestartAfterDownload: function() { + if (!this.isPending && !this.isApplied) { + return; + } + + gAppUpdater.selectPanel("restarting"); + + // Notify all windows that an application quit has been requested. + let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]. + createInstance(Components.interfaces.nsISupportsPRBool); + Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart"); + + // Something aborted the quit process. + if (cancelQuit.data) { + gAppUpdater.selectPanel("apply"); + return; + } + + let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]. + getService(Components.interfaces.nsIAppStartup); + + // If already in safe mode restart in safe mode (bug 327119) + if (Services.appinfo.inSafeMode) { + appStartup.restartInSafeMode(Components.interfaces.nsIAppStartup.eAttemptQuit); + return; + } + + appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit | + Components.interfaces.nsIAppStartup.eRestart); + }, + + /** + * Implements nsIUpdateCheckListener. The methods implemented by + * nsIUpdateCheckListener are in a different scope from nsIIncrementalDownload + * to make it clear which are used by each interface. + */ + updateCheckListener: { + /** + * See nsIUpdateService.idl + */ + onCheckComplete: function(aRequest, aUpdates, aUpdateCount) { + gAppUpdater.isChecking = false; + gAppUpdater.update = gAppUpdater.aus. + selectUpdate(aUpdates, aUpdates.length); + if (!gAppUpdater.update) { + gAppUpdater.selectPanel("noUpdatesFound"); + return; + } + + if (gAppUpdater.update.unsupported) { + if (gAppUpdater.update.detailsURL) { + let unsupportedLink = document.getElementById("unsupportedLink"); + unsupportedLink.href = gAppUpdater.update.detailsURL; + } + gAppUpdater.selectPanel("unsupportedSystem"); + return; + } + + if (!gAppUpdater.aus.canApplyUpdates) { + gAppUpdater.selectPanel("manualUpdate"); + return; + } + + if (gAppUpdater.updateAuto) // automatically download and install + gAppUpdater.startDownload(); + else // ask + gAppUpdater.selectPanel("downloadAndInstall"); + }, + + /** + * See nsIUpdateService.idl + */ + onError: function(aRequest, aUpdate) { + // Errors in the update check are treated as no updates found. If the + // update check fails repeatedly without a success the user will be + // notified with the normal app update user interface so this is safe. + gAppUpdater.isChecking = false; + gAppUpdater.selectPanel("noUpdatesFound"); + }, + + /** + * See nsISupports.idl + */ + QueryInterface: function(aIID) { + if (!aIID.equals(Components.interfaces.nsIUpdateCheckListener) && + !aIID.equals(Components.interfaces.nsISupports)) + throw Components.results.NS_ERROR_NO_INTERFACE; + return this; + } + }, + + /** + * Starts the download of an update mar. + */ + startDownload: function() { + if (!this.update) + this.update = this.um.activeUpdate; + this.update.QueryInterface(Components.interfaces.nsIWritablePropertyBag); + this.update.setProperty("foregroundDownload", "true"); + + this.aus.pauseDownload(); + let state = this.aus.downloadUpdate(this.update, false); + if (state == "failed") { + this.selectPanel("downloadFailed"); + return; + } + + this.setupDownloadingUI(); + }, + + /** + * Switches to the UI responsible for tracking the download. + */ + setupDownloadingUI: function() { + this.downloadStatus = document.getElementById("downloadStatus"); + this.downloadStatus.value = + DownloadUtils.getTransferTotal(0, this.update.selectedPatch.size); + this.selectPanel("downloading"); + this.aus.addDownloadListener(this); + }, + + removeDownloadListener: function() { + if (this.aus) { + this.aus.removeDownloadListener(this); + } + }, + + /** + * See nsIRequestObserver.idl + */ + onStartRequest: function(aRequest, aContext) { + }, + + /** + * See nsIRequestObserver.idl + */ + onStopRequest: function(aRequest, aContext, aStatusCode) { + switch (aStatusCode) { + case Components.results.NS_ERROR_UNEXPECTED: + if (this.update.selectedPatch.state == "download-failed" && + (this.update.isCompleteUpdate || this.update.patchCount != 2)) { + // Verification error of complete patch, informational text is held in + // the update object. + this.removeDownloadListener(); + this.selectPanel("downloadFailed"); + break; + } + // Verification failed for a partial patch, complete patch is now + // downloading so return early and do NOT remove the download listener! + break; + case Components.results.NS_BINDING_ABORTED: + // Do not remove UI listener since the user may resume downloading again. + break; + case Components.results.NS_OK: + this.removeDownloadListener(); + if (this.backgroundUpdateEnabled) { + this.selectPanel("applying"); + let self = this; + Services.obs.addObserver(function (aSubject, aTopic, aData) { + // Update the UI when the background updater is finished + let status = aData; + if (status == "applied" || status == "applied-service" || + status == "pending" || status == "pending-service" || + status == "pending-elevate") { + // If the update is successfully applied, or if the updater has + // fallen back to non-staged updates, show the "Restart to Update" + // button. + self.selectPanel("apply"); + } else if (status == "failed") { + // Background update has failed, let's show the UI responsible for + // prompting the user to update manually. + self.selectPanel("downloadFailed"); + } else if (status == "downloading") { + // We've fallen back to downloading the full update because the + // partial update failed to get staged in the background. + // Therefore we need to keep our observer. + self.setupDownloadingUI(); + return; + } + Services.obs.removeObserver(arguments.callee, "update-staged"); + }, "update-staged", false); + } else { + this.selectPanel("apply"); + } + break; + default: + this.removeDownloadListener(); + this.selectPanel("downloadFailed"); + break; + } + }, + + /** + * See nsIProgressEventSink.idl + */ + onStatus: function(aRequest, aContext, aStatus, aStatusArg) { + }, + + /** + * See nsIProgressEventSink.idl + */ + onProgress: function(aRequest, aContext, aProgress, aProgressMax) { + this.downloadStatus.value = + DownloadUtils.getTransferTotal(aProgress, aProgressMax); + }, + + /** + * See nsISupports.idl + */ + QueryInterface: function(aIID) { + if (!aIID.equals(Components.interfaces.nsIProgressEventSink) && + !aIID.equals(Components.interfaces.nsIRequestObserver) && + !aIID.equals(Components.interfaces.nsISupports)) + throw Components.results.NS_ERROR_NO_INTERFACE; + return this; + } +}; |