michael@0: #filter substitution michael@0: michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 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 michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: Components.utils.import("resource://gre/modules/DownloadUtils.jsm"); michael@0: Components.utils.import("resource://gre/modules/AddonManager.jsm"); michael@0: Components.utils.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: // Firefox's macBrowserOverlay.xul includes scripts that define Cc, Ci, and Cr michael@0: // so we have to use different names. michael@0: const CoC = Components.classes; michael@0: const CoI = Components.interfaces; michael@0: const CoR = Components.results; michael@0: michael@0: const XMLNS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; michael@0: michael@0: const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors"; michael@0: const PREF_APP_UPDATE_BILLBOARD_TEST_URL = "app.update.billboard.test_url"; michael@0: const PREF_APP_UPDATE_CERT_ERRORS = "app.update.cert.errors"; michael@0: const PREF_APP_UPDATE_ENABLED = "app.update.enabled"; michael@0: const PREF_APP_UPDATE_LOG = "app.update.log"; michael@0: const PREF_APP_UPDATE_MANUAL_URL = "app.update.url.manual"; michael@0: const PREF_APP_UPDATE_NEVER_BRANCH = "app.update.never."; michael@0: const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED = "app.update.notifiedUnsupported"; michael@0: const PREF_APP_UPDATE_TEST_LOOP = "app.update.test.loop"; michael@0: const PREF_PLUGINS_UPDATEURL = "plugins.update.url"; michael@0: michael@0: const PREF_EM_HOTFIX_ID = "extensions.hotfix.id"; michael@0: michael@0: const UPDATE_TEST_LOOP_INTERVAL = 2000; michael@0: michael@0: const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties"; michael@0: michael@0: const STATE_DOWNLOADING = "downloading"; michael@0: const STATE_PENDING = "pending"; michael@0: const STATE_PENDING_SVC = "pending-service"; michael@0: const STATE_APPLYING = "applying"; michael@0: const STATE_APPLIED = "applied"; michael@0: const STATE_APPLIED_SVC = "applied-service"; michael@0: const STATE_SUCCEEDED = "succeeded"; michael@0: const STATE_DOWNLOAD_FAILED = "download-failed"; michael@0: const STATE_FAILED = "failed"; michael@0: michael@0: const SRCEVT_FOREGROUND = 1; michael@0: const SRCEVT_BACKGROUND = 2; michael@0: michael@0: const CERT_ATTR_CHECK_FAILED_NO_UPDATE = 100; michael@0: const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101; michael@0: const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110; michael@0: michael@0: #ifdef TOR_BROWSER_VERSION michael@0: # Add double-quotes back on (stripped by JarMaker.py). michael@0: #expand const TOR_BROWSER_VERSION = "__TOR_BROWSER_VERSION__"; michael@0: #endif michael@0: michael@0: var gLogEnabled = false; michael@0: var gUpdatesFoundPageId; michael@0: michael@0: // Notes: michael@0: // 1. use the wizard's goTo method whenever possible to change the wizard michael@0: // page since it is simpler than most other methods and behaves nicely with michael@0: // mochitests. michael@0: // 2. using a page's onPageShow method to then change to a different page will michael@0: // of course call that page's onPageShow method which can make mochitests michael@0: // overly complicated and fragile so avoid doing this if at all possible. michael@0: // This is why a page's next attribute is set prior to the page being shown michael@0: // whenever possible. michael@0: michael@0: /** michael@0: * Logs a string to the error console. michael@0: * @param string michael@0: * The string to write to the error console.. michael@0: */ michael@0: function LOG(module, string) { michael@0: if (gLogEnabled) { michael@0: dump("*** AUS:UI " + module + ":" + string + "\n"); michael@0: Services.console.logStringMessage("AUS:UI " + module + ":" + string); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Opens a URL using the event target's url attribute for the URL. This is a michael@0: * workaround for Bug 263433 which prevents respecting tab browser preferences michael@0: * for where to open a URL. michael@0: */ michael@0: function openUpdateURL(event) { michael@0: if (event.button == 0) michael@0: openURL(event.target.getAttribute("url")); michael@0: } michael@0: michael@0: /** michael@0: * Gets a preference value, handling the case where there is no default. michael@0: * @param func michael@0: * The name of the preference function to call, on nsIPrefBranch michael@0: * @param preference michael@0: * The name of the preference michael@0: * @param defaultValue michael@0: * The default value to return in the event the preference has michael@0: * no setting michael@0: * @returns The value of the preference, or undefined if there was no michael@0: * user or default value. michael@0: */ michael@0: function getPref(func, preference, defaultValue) { michael@0: try { michael@0: return Services.prefs[func](preference); michael@0: } michael@0: catch (e) { michael@0: LOG("General", "getPref - failed to get preference: " + preference); michael@0: } michael@0: return defaultValue; michael@0: } michael@0: michael@0: /** michael@0: * A set of shared data and control functions for the wizard as a whole. michael@0: */ michael@0: var gUpdates = { michael@0: /** michael@0: * The nsIUpdate object being used by this window (either for downloading, michael@0: * notification or both). michael@0: */ michael@0: update: null, michael@0: michael@0: /** michael@0: * List of incompatible add-ons michael@0: */ michael@0: addons: [], michael@0: michael@0: /** michael@0: * The updates.properties element. michael@0: */ michael@0: strings: null, michael@0: michael@0: /** michael@0: * The Application brandShortName (e.g. "Firefox") michael@0: */ michael@0: brandName: null, michael@0: michael@0: /** michael@0: * The element michael@0: */ michael@0: wiz: null, michael@0: michael@0: /** michael@0: * Whether to run the unload handler. This will be set to false when the user michael@0: * exits the wizard via onWizardCancel or onWizardFinish. michael@0: */ michael@0: _runUnload: true, michael@0: michael@0: /** michael@0: * Submit the last page code when the wizard exited. The pageid is used to map michael@0: * to an integer instead of using the pageindex since pages can be added and michael@0: * removed which would change the page's pageindex. michael@0: * @param pageID michael@0: */ michael@0: _sendLastPageCodePing: function(pageID) { michael@0: var pageMap = { invalid: 0, michael@0: dummy: 1, michael@0: checking: 2, michael@0: pluginupdatesfound: 3, michael@0: noupdatesfound: 4, michael@0: manualUpdate: 5, michael@0: unsupported: 6, michael@0: incompatibleCheck: 7, michael@0: updatesfoundbasic: 8, michael@0: updatesfoundbillboard: 9, michael@0: license: 10, michael@0: incompatibleList: 11, michael@0: downloading: 12, michael@0: errors: 13, michael@0: errorextra: 14, michael@0: errorpatching: 15, michael@0: finished: 16, michael@0: finishedBackground: 17, michael@0: installed: 18 }; michael@0: try { michael@0: Services.telemetry.getHistogramById("UPDATER_WIZ_LAST_PAGE_CODE"). michael@0: add(pageMap[pageID] || pageMap.invalid); michael@0: } michael@0: catch (e) { michael@0: Components.utils.reportError(e); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Helper function for setButtons michael@0: * Resets button to original label & accesskey if string is null. michael@0: */ michael@0: _setButton: function(button, string) { michael@0: if (string) { michael@0: var label = this.getAUSString(string); michael@0: if (label.indexOf("%S") != -1) michael@0: label = label.replace(/%S/, this.brandName); michael@0: button.label = label; michael@0: button.setAttribute("accesskey", michael@0: this.getAUSString(string + ".accesskey")); michael@0: } else { michael@0: button.label = button.defaultLabel; michael@0: button.setAttribute("accesskey", button.defaultAccesskey); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Sets the attributes needed for this Wizard's control buttons (labels, michael@0: * disabled, hidden, etc.) michael@0: * @param extra1ButtonString michael@0: * The property in the stringbundle containing the label to put on michael@0: * the first extra button, or null to hide the first extra button. michael@0: * @param extra2ButtonString michael@0: * The property in the stringbundle containing the label to put on michael@0: * the second extra button, or null to hide the second extra button. michael@0: * @param nextFinishButtonString michael@0: * The property in the stringbundle containing the label to put on michael@0: * the Next / Finish button, or null to hide the button. The Next and michael@0: * Finish buttons are never displayed at the same time in a wizard michael@0: * with the the Finish button only being displayed when there are no michael@0: * additional pages to display in the wizard. michael@0: * @param canAdvance michael@0: * true if the wizard can be advanced (e.g. the next / finish button michael@0: * should be enabled), false otherwise. michael@0: * @param showCancel michael@0: * true if the wizard's cancel button should be shown, false michael@0: * otherwise. If not specified this will default to false. michael@0: * michael@0: * Note: michael@0: * Per Bug 324121 the wizard should not look like a wizard and to accomplish michael@0: * this the back button is never displayed and the cancel button is only michael@0: * displayed for the checking and the incompatibleCheck pages. This causes the michael@0: * wizard buttons to be arranged as follows on Windows with the next and michael@0: * finish buttons never being displayed at the same time. michael@0: * +--------------------------------------------------------------+ michael@0: * | [ extra1 ] [ extra2 ] [ next or finish ] | michael@0: * +--------------------------------------------------------------+ michael@0: */ michael@0: setButtons: function(extra1ButtonString, extra2ButtonString, michael@0: nextFinishButtonString, canAdvance, showCancel) { michael@0: this.wiz.canAdvance = canAdvance; michael@0: michael@0: var bnf = this.wiz.getButton(this.wiz.onLastPage ? "finish" : "next"); michael@0: var be1 = this.wiz.getButton("extra1"); michael@0: var be2 = this.wiz.getButton("extra2"); michael@0: var bc = this.wiz.getButton("cancel"); michael@0: michael@0: // Set the labels for the next / finish, extra1, and extra2 buttons michael@0: this._setButton(bnf, nextFinishButtonString); michael@0: this._setButton(be1, extra1ButtonString); michael@0: this._setButton(be2, extra2ButtonString); michael@0: michael@0: bnf.hidden = bnf.disabled = !nextFinishButtonString; michael@0: be1.hidden = be1.disabled = !extra1ButtonString; michael@0: be2.hidden = be2.disabled = !extra2ButtonString; michael@0: bc.hidden = bc.disabled = !showCancel; michael@0: michael@0: // Hide and disable the back button each time setButtons is called michael@0: // (see bug 464765). michael@0: var btn = this.wiz.getButton("back"); michael@0: btn.hidden = btn.disabled = true; michael@0: michael@0: // Hide and disable the finish button if not on the last page or the next michael@0: // button if on the last page each time setButtons is called. michael@0: btn = this.wiz.getButton(this.wiz.onLastPage ? "next" : "finish"); michael@0: btn.hidden = btn.disabled = true; michael@0: }, michael@0: michael@0: getAUSString: function(key, strings) { michael@0: if (strings) michael@0: return this.strings.getFormattedString(key, strings); michael@0: return this.strings.getString(key); michael@0: }, michael@0: michael@0: never: function () { michael@0: // If the user clicks "No Thanks", we should not prompt them to update to michael@0: // this version again unless they manually select "Check for Updates..." michael@0: // which will clear all of the "never" prefs. michael@0: var neverPrefName = PREF_APP_UPDATE_NEVER_BRANCH + this.update.appVersion; michael@0: Services.prefs.setBoolPref(neverPrefName, true); michael@0: }, michael@0: michael@0: /** michael@0: * A hash of |pageid| attribute to page object. Can be used to dispatch michael@0: * function calls to the appropriate page. michael@0: */ michael@0: _pages: { }, michael@0: michael@0: /** michael@0: * Called when the user presses the "Finish" button on the wizard, dispatches michael@0: * the function call to the selected page. michael@0: */ michael@0: onWizardFinish: function() { michael@0: this._runUnload = false; michael@0: var pageid = document.documentElement.currentPage.pageid; michael@0: if ("onWizardFinish" in this._pages[pageid]) michael@0: this._pages[pageid].onWizardFinish(); michael@0: this._sendLastPageCodePing(pageid); michael@0: }, michael@0: michael@0: /** michael@0: * Called when the user presses the "Cancel" button on the wizard, dispatches michael@0: * the function call to the selected page. michael@0: */ michael@0: onWizardCancel: function() { michael@0: this._runUnload = false; michael@0: var pageid = document.documentElement.currentPage.pageid; michael@0: if ("onWizardCancel" in this._pages[pageid]) michael@0: this._pages[pageid].onWizardCancel(); michael@0: this._sendLastPageCodePing(pageid); michael@0: }, michael@0: michael@0: /** michael@0: * Called when the user presses the "Next" button on the wizard, dispatches michael@0: * the function call to the selected page. michael@0: */ michael@0: onWizardNext: function() { michael@0: var cp = document.documentElement.currentPage; michael@0: if (!cp) michael@0: return; michael@0: var pageid = cp.pageid; michael@0: if ("onWizardNext" in this._pages[pageid]) michael@0: this._pages[pageid].onWizardNext(); michael@0: }, michael@0: michael@0: /** michael@0: * The checking process that spawned this update UI. There are two types: michael@0: * SRCEVT_FOREGROUND: michael@0: * Some user-generated event caused this UI to appear, e.g. the Help michael@0: * menu item or the button in preferences. When in this mode, the UI michael@0: * should remain active for the duration of the download. michael@0: * SRCEVT_BACKGROUND: michael@0: * A background update check caused this UI to appear, probably because michael@0: * incompatibilities in Extensions or other addons were discovered and michael@0: * the user's consent to continue was required. When in this mode, the michael@0: * UI will disappear after the user's consent is obtained. michael@0: */ michael@0: sourceEvent: SRCEVT_FOREGROUND, michael@0: michael@0: /** michael@0: * Helper function for onLoad michael@0: * Saves default button label & accesskey for use by _setButton michael@0: */ michael@0: _cacheButtonStrings: function (buttonName) { michael@0: var button = this.wiz.getButton(buttonName); michael@0: button.defaultLabel = button.label; michael@0: button.defaultAccesskey = button.getAttribute("accesskey"); michael@0: }, michael@0: michael@0: /** michael@0: * Called when the wizard UI is loaded. michael@0: */ michael@0: onLoad: function() { michael@0: this.wiz = document.documentElement; michael@0: michael@0: gLogEnabled = getPref("getBoolPref", PREF_APP_UPDATE_LOG, false) michael@0: michael@0: this.strings = document.getElementById("updateStrings"); michael@0: var brandStrings = document.getElementById("brandStrings"); michael@0: this.brandName = brandStrings.getString("brandShortName"); michael@0: michael@0: var pages = this.wiz.childNodes; michael@0: for (var i = 0; i < pages.length; ++i) { michael@0: var page = pages[i]; michael@0: if (page.localName == "wizardpage") michael@0: this._pages[page.pageid] = eval(page.getAttribute("object")); michael@0: } michael@0: michael@0: // Cache the standard button labels in case we need to restore them michael@0: this._cacheButtonStrings("next"); michael@0: this._cacheButtonStrings("finish"); michael@0: this._cacheButtonStrings("extra1"); michael@0: this._cacheButtonStrings("extra2"); michael@0: michael@0: // Advance to the Start page. michael@0: this.getStartPageID(function(startPageID) { michael@0: LOG("gUpdates", "onLoad - setting current page to startpage " + startPageID); michael@0: gUpdates.wiz.currentPage = document.getElementById(startPageID); michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Called when the wizard UI is unloaded. michael@0: */ michael@0: onUnload: function() { michael@0: if (this._runUnload) { michael@0: var cp = this.wiz.currentPage; michael@0: if (cp.pageid != "finished" && cp.pageid != "finishedBackground") michael@0: this.onWizardCancel(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Gets the ID of the object that should be displayed first. This michael@0: * is an asynchronous method that passes the resulting object to a callback michael@0: * function. michael@0: * michael@0: * This is determined by how we were called by the update prompt: michael@0: * michael@0: * Prompt Method: Arg0: Update State: Src Event: Failed: Result: michael@0: * showUpdateAvailable nsIUpdate obj -- background -- see Note below michael@0: * showUpdateDownloaded nsIUpdate obj pending background -- finishedBackground michael@0: * showUpdateInstalled "installed" -- -- -- installed michael@0: * showUpdateError nsIUpdate obj failed either partial errorpatching michael@0: * showUpdateError nsIUpdate obj failed either complete errors michael@0: * checkForUpdates null -- foreground -- checking michael@0: * checkForUpdates null downloading foreground -- downloading michael@0: * michael@0: * Note: the page returned (e.g. Result) for showUpdateAvaulable is as follows: michael@0: * New enabled incompatible add-ons : incompatibleCheck page michael@0: * No new enabled incompatible add-ons: either updatesfoundbasic or michael@0: * updatesfoundbillboard as determined by michael@0: * updatesFoundPageId michael@0: * @param aCallback michael@0: * A callback to pass the object to be displayed first to. michael@0: */ michael@0: getStartPageID: function(aCallback) { michael@0: if ("arguments" in window && window.arguments[0]) { michael@0: var arg0 = window.arguments[0]; michael@0: if (arg0 instanceof CoI.nsIUpdate) { michael@0: // If the first argument is a nsIUpdate object, we are notifying the michael@0: // user that the background checking found an update that requires michael@0: // their permission to install, and it's ready for download. michael@0: this.setUpdate(arg0); michael@0: if (this.update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE || michael@0: this.update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE || michael@0: this.update.errorCode == BACKGROUNDCHECK_MULTIPLE_FAILURES) { michael@0: aCallback("errorextra"); michael@0: return; michael@0: } michael@0: michael@0: if (this.update.unsupported) { michael@0: aCallback("unsupported"); michael@0: return; michael@0: } michael@0: michael@0: var p = this.update.selectedPatch; michael@0: if (p) { michael@0: var state = p.state; michael@0: var patchFailed; michael@0: try { michael@0: patchFailed = this.update.getProperty("patchingFailed"); michael@0: } michael@0: catch (e) { michael@0: } michael@0: if (patchFailed) { michael@0: if (patchFailed == "partial" && this.update.patchCount == 2) { michael@0: // If the system failed to apply the partial patch, show the michael@0: // screen which best describes this condition, which is triggered michael@0: // by the |STATE_FAILED| state. michael@0: state = STATE_FAILED; michael@0: } michael@0: else { michael@0: // Otherwise, if the complete patch failed, which is far less michael@0: // likely, show the error text held by the update object in the michael@0: // generic errors page, triggered by the |STATE_DOWNLOAD_FAILED| michael@0: // state. michael@0: state = STATE_DOWNLOAD_FAILED; michael@0: } michael@0: } michael@0: michael@0: // Now select the best page to start with, given the current state of michael@0: // the Update. michael@0: switch (state) { michael@0: case STATE_PENDING: michael@0: case STATE_PENDING_SVC: michael@0: case STATE_APPLIED: michael@0: case STATE_APPLIED_SVC: michael@0: this.sourceEvent = SRCEVT_BACKGROUND; michael@0: aCallback("finishedBackground"); michael@0: return; michael@0: case STATE_DOWNLOADING: michael@0: aCallback("downloading"); michael@0: return; michael@0: case STATE_FAILED: michael@0: window.getAttention(); michael@0: aCallback("errorpatching"); michael@0: return; michael@0: case STATE_DOWNLOAD_FAILED: michael@0: case STATE_APPLYING: michael@0: aCallback("errors"); michael@0: return; michael@0: } michael@0: } michael@0: if (this.update.licenseURL) michael@0: this.wiz.getPageById(this.updatesFoundPageId).setAttribute("next", "license"); michael@0: michael@0: var self = this; michael@0: this.getShouldCheckAddonCompatibility(function(shouldCheck) { michael@0: if (shouldCheck) { michael@0: var incompatCheckPage = document.getElementById("incompatibleCheck"); michael@0: incompatCheckPage.setAttribute("next", self.updatesFoundPageId); michael@0: aCallback(incompatCheckPage.id); michael@0: } michael@0: else { michael@0: aCallback(self.updatesFoundPageId); michael@0: } michael@0: }); michael@0: return; michael@0: } michael@0: else if (arg0 == "installed") { michael@0: aCallback("installed"); michael@0: return; michael@0: } michael@0: } michael@0: else { michael@0: var um = CoC["@mozilla.org/updates/update-manager;1"]. michael@0: getService(CoI.nsIUpdateManager); michael@0: if (um.activeUpdate) { michael@0: this.setUpdate(um.activeUpdate); michael@0: aCallback("downloading"); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Provide the ability to test the billboard html michael@0: var billboardTestURL = getPref("getCharPref", PREF_APP_UPDATE_BILLBOARD_TEST_URL, null); michael@0: if (billboardTestURL) { michael@0: var updatesFoundBillboardPage = document.getElementById("updatesfoundbillboard"); michael@0: updatesFoundBillboardPage.setAttribute("next", "dummy"); michael@0: gUpdatesFoundBillboardPage.onExtra1 = function(){ gUpdates.wiz.cancel(); }; michael@0: gUpdatesFoundBillboardPage.onExtra2 = function(){ gUpdates.wiz.cancel(); }; michael@0: this.onWizardNext = function() { gUpdates.wiz.cancel(); }; michael@0: this.update = { billboardURL : billboardTestURL, michael@0: brandName : this.brandName, michael@0: displayVersion : "Billboard Test 1.0", michael@0: showNeverForVersion : true, michael@0: type : "major" }; michael@0: aCallback(updatesFoundBillboardPage.id); michael@0: } michael@0: else { michael@0: aCallback("checking"); michael@0: } michael@0: }, michael@0: michael@0: getShouldCheckAddonCompatibility: function(aCallback) { michael@0: // this early return should never happen michael@0: if (!this.update) { michael@0: aCallback(false); michael@0: return; michael@0: } michael@0: michael@0: #ifdef TOR_BROWSER_UPDATE michael@0: var appVersion = TOR_BROWSER_VERSION; michael@0: #else michael@0: var appVersion = Services.appinfo.version; michael@0: #endif michael@0: if (!this.update.appVersion || michael@0: Services.vc.compare(this.update.appVersion, appVersion) == 0) { michael@0: aCallback(false); michael@0: return; michael@0: } michael@0: michael@0: try { michael@0: var hotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID); michael@0: } michael@0: catch (e) { } michael@0: michael@0: var self = this; michael@0: AddonManager.getAllAddons(function(addons) { michael@0: #ifdef TOR_BROWSER_UPDATE michael@0: let compatVersion = self.update.platformVersion; michael@0: #else michael@0: let compatVersion = self.update.appVersion; michael@0: #endif michael@0: self.addons = []; michael@0: addons.forEach(function(addon) { michael@0: // Protect against code that overrides the add-ons manager and doesn't michael@0: // implement the isCompatibleWith or the findUpdates method. michael@0: if (!("isCompatibleWith" in addon) || !("findUpdates" in addon)) { michael@0: let errMsg = "Add-on doesn't implement either the isCompatibleWith " + michael@0: "or the findUpdates method!"; michael@0: if (addon.id) michael@0: errMsg += " Add-on ID: " + addon.id; michael@0: Components.utils.reportError(errMsg); michael@0: return; michael@0: } michael@0: michael@0: // If an add-on isn't appDisabled and isn't userDisabled then it is michael@0: // either active now or the user expects it to be active after the michael@0: // restart. If that is the case and the add-on is not installed by the michael@0: // application and is not compatible with the new application version michael@0: // then the user should be warned that the add-on will become michael@0: // incompatible. If an addon's type equals plugin it is skipped since michael@0: // checking plugins compatibility information isn't supported and michael@0: // getting the scope property of a plugin breaks in some environments michael@0: // (see bug 566787). The hotfix add-on is also ignored as it shouldn't michael@0: // block the user from upgrading. michael@0: try { michael@0: if (addon.type != "plugin" && addon.id != hotfixID && michael@0: !addon.appDisabled && !addon.userDisabled && michael@0: addon.scope != AddonManager.SCOPE_APPLICATION && michael@0: addon.isCompatible && michael@0: !addon.isCompatibleWith(compatVersion, michael@0: self.update.platformVersion)) michael@0: self.addons.push(addon); michael@0: } michael@0: catch (e) { michael@0: Components.utils.reportError(e); michael@0: } michael@0: }); michael@0: michael@0: aCallback(self.addons.length != 0); michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Returns the string page ID for the appropriate updates found page based michael@0: * on the update's metadata. michael@0: */ michael@0: get updatesFoundPageId() { michael@0: if (gUpdatesFoundPageId) michael@0: return gUpdatesFoundPageId; michael@0: return gUpdatesFoundPageId = this.update.billboardURL ? "updatesfoundbillboard" michael@0: : "updatesfoundbasic"; michael@0: }, michael@0: michael@0: /** michael@0: * Sets the Update object for this wizard michael@0: * @param update michael@0: * The update object michael@0: */ michael@0: setUpdate: function(update) { michael@0: this.update = update; michael@0: if (this.update) michael@0: this.update.QueryInterface(CoI.nsIWritablePropertyBag); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * The "Checking for Updates" page. Provides feedback on the update checking michael@0: * process. michael@0: */ michael@0: var gCheckingPage = { michael@0: /** michael@0: * The nsIUpdateChecker that is currently checking for updates. We hold onto michael@0: * this so we can cancel the update check if the user closes the window. michael@0: */ michael@0: _checker: null, michael@0: michael@0: /** michael@0: * Initialize michael@0: */ michael@0: onPageShow: function() { michael@0: gUpdates.setButtons(null, null, null, false, true); michael@0: gUpdates.wiz.getButton("cancel").focus(); michael@0: michael@0: // Clear all of the "never" prefs to handle the scenario where the user michael@0: // clicked "never" for an update, selected "Check for Updates...", and michael@0: // then canceled. If we don't clear the "never" prefs future michael@0: // notifications will never happen. michael@0: Services.prefs.deleteBranch(PREF_APP_UPDATE_NEVER_BRANCH); michael@0: michael@0: // The user will be notified if there is an error so clear the background michael@0: // check error count. michael@0: if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS)) michael@0: Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS); michael@0: michael@0: // The preference will be set back to true if the system is still michael@0: // unsupported. michael@0: if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED)) michael@0: Services.prefs.clearUserPref(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED); michael@0: michael@0: this._checker = CoC["@mozilla.org/updates/update-checker;1"]. michael@0: createInstance(CoI.nsIUpdateChecker); michael@0: this._checker.checkForUpdates(this.updateListener, true); michael@0: }, michael@0: michael@0: /** michael@0: * The user has closed the window, either by pressing cancel or using a Window michael@0: * Manager control, so stop checking for updates. michael@0: */ michael@0: onWizardCancel: function() { michael@0: this._checker.stopChecking(CoI.nsIUpdateChecker.CURRENT_CHECK); michael@0: }, michael@0: michael@0: /** michael@0: * An object implementing nsIUpdateCheckListener that is notified as the michael@0: * update check commences. michael@0: */ michael@0: updateListener: { michael@0: /** michael@0: * See nsIUpdateCheckListener michael@0: */ michael@0: onCheckComplete: function(request, updates, updateCount) { michael@0: var aus = CoC["@mozilla.org/updates/update-service;1"]. michael@0: getService(CoI.nsIApplicationUpdateService); michael@0: gUpdates.setUpdate(aus.selectUpdate(updates, updates.length)); michael@0: if (gUpdates.update) { michael@0: LOG("gCheckingPage", "onCheckComplete - update found"); michael@0: if (gUpdates.update.unsupported) { michael@0: gUpdates.wiz.goTo("unsupported"); michael@0: return; michael@0: } michael@0: michael@0: if (!aus.canApplyUpdates) { michael@0: // Prevent multiple notifications for the same update when the user is michael@0: // unable to apply updates. michael@0: gUpdates.never(); michael@0: gUpdates.wiz.goTo("manualUpdate"); michael@0: return; michael@0: } michael@0: michael@0: if (gUpdates.update.licenseURL) { michael@0: // gUpdates.updatesFoundPageId returns the pageid and not the michael@0: // element's id so use the wizard's getPageById method. michael@0: gUpdates.wiz.getPageById(gUpdates.updatesFoundPageId).setAttribute("next", "license"); michael@0: } michael@0: michael@0: gUpdates.getShouldCheckAddonCompatibility(function(shouldCheck) { michael@0: if (shouldCheck) { michael@0: var incompatCheckPage = document.getElementById("incompatibleCheck"); michael@0: incompatCheckPage.setAttribute("next", gUpdates.updatesFoundPageId); michael@0: gUpdates.wiz.goTo("incompatibleCheck"); michael@0: } michael@0: else { michael@0: gUpdates.wiz.goTo(gUpdates.updatesFoundPageId); michael@0: } michael@0: }); michael@0: return; michael@0: } michael@0: michael@0: LOG("gCheckingPage", "onCheckComplete - no update found"); michael@0: gUpdates.wiz.goTo("noupdatesfound"); michael@0: }, michael@0: michael@0: /** michael@0: * See nsIUpdateCheckListener michael@0: */ michael@0: onError: function(request, update) { michael@0: LOG("gCheckingPage", "onError - proceeding to error page"); michael@0: gUpdates.setUpdate(update); michael@0: if (update.errorCode && michael@0: (update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE || michael@0: update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE)) { michael@0: gUpdates.wiz.goTo("errorextra"); michael@0: } michael@0: else { michael@0: gUpdates.wiz.goTo("errors"); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * See nsISupports.idl michael@0: */ michael@0: QueryInterface: function(aIID) { michael@0: if (!aIID.equals(CoI.nsIUpdateCheckListener) && michael@0: !aIID.equals(CoI.nsISupports)) michael@0: throw CoR.NS_ERROR_NO_INTERFACE; michael@0: return this; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The "You have outdated plugins" page michael@0: */ michael@0: var gPluginsPage = { michael@0: /** michael@0: * URL of the plugin updates page michael@0: */ michael@0: _url: null, michael@0: michael@0: /** michael@0: * Initialize michael@0: */ michael@0: onPageShow: function() { michael@0: var prefs = Services.prefs; michael@0: if (prefs.getPrefType(PREF_PLUGINS_UPDATEURL) == prefs.PREF_INVALID) { michael@0: gUpdates.wiz.goTo("noupdatesfound"); michael@0: return; michael@0: } michael@0: michael@0: this._url = Services.urlFormatter.formatURLPref(PREF_PLUGINS_UPDATEURL); michael@0: var link = document.getElementById("pluginupdateslink"); michael@0: link.setAttribute("href", this._url); michael@0: michael@0: michael@0: var phs = CoC["@mozilla.org/plugin/host;1"]. michael@0: getService(CoI.nsIPluginHost); michael@0: var plugins = phs.getPluginTags(); michael@0: var blocklist = CoC["@mozilla.org/extensions/blocklist;1"]. michael@0: getService(CoI.nsIBlocklistService); michael@0: michael@0: var hasOutdated = false; michael@0: for (let i = 0; i < plugins.length; i++) { michael@0: let pluginState = blocklist.getPluginBlocklistState(plugins[i]); michael@0: if (pluginState == CoI.nsIBlocklistService.STATE_OUTDATED) { michael@0: hasOutdated = true; michael@0: break; michael@0: } michael@0: } michael@0: if (!hasOutdated) { michael@0: gUpdates.wiz.goTo("noupdatesfound"); michael@0: return; michael@0: } michael@0: michael@0: gUpdates.setButtons(null, null, "okButton", true); michael@0: gUpdates.wiz.getButton("finish").focus(); michael@0: }, michael@0: michael@0: /** michael@0: * Finish button clicked. michael@0: */ michael@0: onWizardFinish: function() { michael@0: openURL(this._url); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The "No Updates Are Available" page michael@0: */ michael@0: var gNoUpdatesPage = { michael@0: /** michael@0: * Initialize michael@0: */ michael@0: onPageShow: function() { michael@0: LOG("gNoUpdatesPage", "onPageShow - could not select an appropriate " + michael@0: "update. Either there were no updates or |selectUpdate| failed"); michael@0: michael@0: if (getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true)) michael@0: document.getElementById("noUpdatesAutoEnabled").hidden = false; michael@0: else michael@0: document.getElementById("noUpdatesAutoDisabled").hidden = false; michael@0: michael@0: gUpdates.setButtons(null, null, "okButton", true); michael@0: gUpdates.wiz.getButton("finish").focus(); michael@0: } michael@0: }; michael@0: michael@0: michael@0: /** michael@0: * The page that checks if there are any incompatible add-ons. michael@0: */ michael@0: var gIncompatibleCheckPage = { michael@0: /** michael@0: * Count of incompatible add-ons to check for updates michael@0: */ michael@0: _totalCount: 0, michael@0: michael@0: /** michael@0: * Count of incompatible add-ons that have beend checked for updates michael@0: */ michael@0: _completedCount: 0, michael@0: michael@0: /** michael@0: * The progress bar for this page michael@0: */ michael@0: _pBar: null, michael@0: michael@0: /** michael@0: * Initialize michael@0: */ michael@0: onPageShow: function() { michael@0: LOG("gIncompatibleCheckPage", "onPageShow - checking for updates to " + michael@0: "incompatible add-ons"); michael@0: michael@0: gUpdates.setButtons(null, null, null, false, true); michael@0: gUpdates.wiz.getButton("cancel").focus(); michael@0: this._pBar = document.getElementById("incompatibleCheckProgress"); michael@0: this._totalCount = gUpdates.addons.length; michael@0: michael@0: this._pBar.mode = "normal"; michael@0: #ifdef TOR_BROWSER_UPDATE michael@0: let compatVersion = gUpdates.update.platformVersion; michael@0: #else michael@0: let compatVersion = gUpdates.update.appVersion; michael@0: #endif michael@0: gUpdates.addons.forEach(function(addon) { michael@0: addon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, michael@0: compatVersion, michael@0: gUpdates.update.platformVersion); michael@0: }, this); michael@0: }, michael@0: michael@0: // Addon UpdateListener michael@0: onCompatibilityUpdateAvailable: function(addon) { michael@0: // Remove the add-on from the list of add-ons that will become incompatible michael@0: // with the new version of the application. michael@0: for (var i = 0; i < gUpdates.addons.length; ++i) { michael@0: if (gUpdates.addons[i].id == addon.id) { michael@0: LOG("gIncompatibleCheckPage", "onCompatibilityUpdateAvailable - " + michael@0: "found update for add-on ID: " + addon.id); michael@0: gUpdates.addons.splice(i, 1); michael@0: break; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: onUpdateAvailable: function(addon, install) { michael@0: // If the new version of this add-on is blocklisted for the new application michael@0: // then it isn't a valid update and the user should still be warned that michael@0: // the add-on will become incompatible. michael@0: let bs = CoC["@mozilla.org/extensions/blocklist;1"]. michael@0: getService(CoI.nsIBlocklistService); michael@0: #ifdef TOR_BROWSER_UPDATE michael@0: let compatVersion = gUpdates.update.platformVersion; michael@0: #else michael@0: let compatVersion = gUpdates.update.appVersion; michael@0: #endif michael@0: if (bs.isAddonBlocklisted(addon, michael@0: compatVersion, michael@0: gUpdates.update.platformVersion)) michael@0: return; michael@0: michael@0: // Compatibility or new version updates mean the same thing here. michael@0: this.onCompatibilityUpdateAvailable(addon); michael@0: }, michael@0: michael@0: onUpdateFinished: function(addon) { michael@0: ++this._completedCount; michael@0: this._pBar.value = Math.ceil((this._completedCount / this._totalCount) * 100); michael@0: michael@0: if (this._completedCount < this._totalCount) michael@0: return; michael@0: michael@0: if (gUpdates.addons.length == 0) { michael@0: LOG("gIncompatibleCheckPage", "onUpdateFinished - updates were found " + michael@0: "for all incompatible add-ons"); michael@0: } michael@0: else { michael@0: LOG("gIncompatibleCheckPage", "onUpdateFinished - there are still " + michael@0: "incompatible add-ons"); michael@0: if (gUpdates.update.licenseURL) { michael@0: document.getElementById("license").setAttribute("next", "incompatibleList"); michael@0: } michael@0: else { michael@0: // gUpdates.updatesFoundPageId returns the pageid and not the element's michael@0: // id so use the wizard's getPageById method. michael@0: gUpdates.wiz.getPageById(gUpdates.updatesFoundPageId).setAttribute("next", "incompatibleList"); michael@0: } michael@0: } michael@0: gUpdates.wiz.goTo(gUpdates.updatesFoundPageId); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The "Unable to Update" page. Provides the user information about why they michael@0: * were unable to update and a manual download url. michael@0: */ michael@0: var gManualUpdatePage = { michael@0: onPageShow: function() { michael@0: var manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_MANUAL_URL); michael@0: var manualUpdateLinkLabel = document.getElementById("manualUpdateLinkLabel"); michael@0: manualUpdateLinkLabel.value = manualURL; michael@0: manualUpdateLinkLabel.setAttribute("url", manualURL); michael@0: michael@0: gUpdates.setButtons(null, null, "okButton", true); michael@0: gUpdates.wiz.getButton("finish").focus(); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The "System Unsupported" page. Provides the user with information about their michael@0: * system no longer being supported and an url for more information. michael@0: */ michael@0: var gUnsupportedPage = { michael@0: onPageShow: function() { michael@0: Services.prefs.setBoolPref(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED, true); michael@0: if (gUpdates.update.detailsURL) { michael@0: let unsupportedLinkLabel = document.getElementById("unsupportedLinkLabel"); michael@0: unsupportedLinkLabel.setAttribute("url", gUpdates.update.detailsURL); michael@0: } michael@0: michael@0: gUpdates.setButtons(null, null, "okButton", true); michael@0: gUpdates.wiz.getButton("finish").focus(); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The "Updates Are Available" page. Provides the user information about the michael@0: * available update. michael@0: */ michael@0: var gUpdatesFoundBasicPage = { michael@0: /** michael@0: * Initialize michael@0: */ michael@0: onPageShow: function() { michael@0: gUpdates.wiz.canRewind = false; michael@0: var update = gUpdates.update; michael@0: gUpdates.setButtons("askLaterButton", michael@0: update.showNeverForVersion ? "noThanksButton" : null, michael@0: "updateButton_" + update.type, true); michael@0: var btn = gUpdates.wiz.getButton("next"); michael@0: btn.focus(); michael@0: michael@0: var updateName = update.name; michael@0: if (update.channel == "nightly") { michael@0: updateName = gUpdates.getAUSString("updateNightlyName", michael@0: [gUpdates.brandName, michael@0: update.displayVersion, michael@0: update.buildID]); michael@0: } michael@0: var updateNameElement = document.getElementById("updateName"); michael@0: updateNameElement.value = updateName; michael@0: michael@0: var introText = gUpdates.getAUSString("intro_" + update.type, michael@0: [gUpdates.brandName, update.displayVersion]); michael@0: var introElem = document.getElementById("updatesFoundInto"); michael@0: introElem.setAttribute("severity", update.type); michael@0: introElem.textContent = introText; michael@0: michael@0: var updateMoreInfoURL = document.getElementById("updateMoreInfoURL"); michael@0: if (update.detailsURL) michael@0: updateMoreInfoURL.setAttribute("url", update.detailsURL); michael@0: else michael@0: updateMoreInfoURL.hidden = true; michael@0: michael@0: var updateTitle = gUpdates.getAUSString("updatesfound_" + update.type + michael@0: ".title"); michael@0: document.getElementById("updatesFoundBasicHeader").setAttribute("label", updateTitle); michael@0: }, michael@0: michael@0: onExtra1: function() { michael@0: gUpdates.wiz.cancel(); michael@0: }, michael@0: michael@0: onExtra2: function() { michael@0: gUpdates.never(); michael@0: gUpdates.wiz.cancel(); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The "Updates Are Available" page with a billboard. Provides the user michael@0: * information about the available update. michael@0: */ michael@0: var gUpdatesFoundBillboardPage = { michael@0: /** michael@0: * If this page has been previously loaded michael@0: */ michael@0: _billboardLoaded: false, michael@0: michael@0: /** michael@0: * Initialize michael@0: */ michael@0: onPageShow: function() { michael@0: var update = gUpdates.update; michael@0: gUpdates.setButtons("askLaterButton", michael@0: update.showNeverForVersion ? "noThanksButton" : null, michael@0: "updateButton_" + update.type, true); michael@0: gUpdates.wiz.getButton("next").focus(); michael@0: michael@0: if (this._billboardLoaded) michael@0: return; michael@0: michael@0: var remoteContent = document.getElementById("updateMoreInfoContent"); michael@0: remoteContent.addEventListener("load", michael@0: gUpdatesFoundBillboardPage.onBillboardLoad, michael@0: false); michael@0: // update_name and update_version need to be set before url michael@0: // so that when attempting to download the url, we can show michael@0: // the formatted "Download..." string michael@0: remoteContent.update_name = gUpdates.brandName; michael@0: remoteContent.update_version = update.displayVersion; michael@0: michael@0: var billboardTestURL = getPref("getCharPref", PREF_APP_UPDATE_BILLBOARD_TEST_URL, null); michael@0: if (billboardTestURL) { michael@0: // Allow file urls when testing the billboard and fallback to the michael@0: // normal method if the URL isn't a file. michael@0: var scheme = Services.io.newURI(billboardTestURL, null, null).scheme; michael@0: if (scheme == "file") michael@0: remoteContent.testFileUrl = update.billboardURL; michael@0: else michael@0: remoteContent.url = update.billboardURL; michael@0: } michael@0: else michael@0: remoteContent.url = update.billboardURL; michael@0: michael@0: this._billboardLoaded = true; michael@0: }, michael@0: michael@0: /** michael@0: * When the billboard document has loaded michael@0: */ michael@0: onBillboardLoad: function(aEvent) { michael@0: var remoteContent = document.getElementById("updateMoreInfoContent"); michael@0: // Note: may be called multiple times due to multiple onLoad events. michael@0: var state = remoteContent.getAttribute("state"); michael@0: if (state == "loading" || aEvent.originalTarget != remoteContent) michael@0: return; michael@0: michael@0: remoteContent.removeEventListener("load", gUpdatesFoundBillboardPage.onBillboardLoad, false); michael@0: if (state == "error") { michael@0: gUpdatesFoundPageId = "updatesfoundbasic"; michael@0: var next = gUpdates.wiz.getPageById("updatesfoundbillboard").getAttribute("next"); michael@0: gUpdates.wiz.getPageById(gUpdates.updatesFoundPageId).setAttribute("next", next); michael@0: gUpdates.wiz.goTo(gUpdates.updatesFoundPageId); michael@0: } michael@0: }, michael@0: michael@0: onExtra1: function() { michael@0: this.onWizardCancel(); michael@0: gUpdates.wiz.cancel(); michael@0: }, michael@0: michael@0: onExtra2: function() { michael@0: this.onWizardCancel(); michael@0: gUpdates.never(); michael@0: gUpdates.wiz.cancel(); michael@0: }, michael@0: michael@0: /** michael@0: * When the user cancels the wizard michael@0: */ michael@0: onWizardCancel: function() { michael@0: try { michael@0: var remoteContent = document.getElementById("updateMoreInfoContent"); michael@0: if (remoteContent) michael@0: remoteContent.stopDownloading(); michael@0: } michael@0: catch (e) { michael@0: LOG("gUpdatesFoundBillboardPage", "onWizardCancel - " + michael@0: "moreInfoContent.stopDownloading() failed: " + e); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The page which shows the user a license associated with an update. The michael@0: * user must agree to the terms of the license before continuing to install michael@0: * the update. michael@0: */ michael@0: var gLicensePage = { michael@0: /** michael@0: * If the license url has been previously loaded michael@0: */ michael@0: _licenseLoaded: false, michael@0: michael@0: /** michael@0: * Initialize michael@0: */ michael@0: onPageShow: function() { michael@0: gUpdates.setButtons("backButton", null, "acceptTermsButton", false); michael@0: michael@0: var licenseContent = document.getElementById("licenseContent"); michael@0: if (this._licenseLoaded || licenseContent.getAttribute("state") == "error") { michael@0: this.onAcceptDeclineRadio(); michael@0: var licenseGroup = document.getElementById("acceptDeclineLicense"); michael@0: licenseGroup.focus(); michael@0: return; michael@0: } michael@0: michael@0: gUpdates.wiz.canAdvance = false; michael@0: michael@0: // Disable the license radiogroup until the EULA has been downloaded michael@0: document.getElementById("acceptDeclineLicense").disabled = true; michael@0: gUpdates.update.setProperty("licenseAccepted", "false"); michael@0: michael@0: licenseContent.addEventListener("load", gLicensePage.onLicenseLoad, false); michael@0: // The update_name and update_version need to be set before url so the ui michael@0: // can display the formatted "Download..." string when attempting to michael@0: // download the url. michael@0: licenseContent.update_name = gUpdates.brandName; michael@0: licenseContent.update_version = gUpdates.update.displayVersion; michael@0: licenseContent.url = gUpdates.update.licenseURL; michael@0: }, michael@0: michael@0: /** michael@0: * When the license document has loaded michael@0: */ michael@0: onLicenseLoad: function(aEvent) { michael@0: var licenseContent = document.getElementById("licenseContent"); michael@0: // Disable or enable the radiogroup based on the state attribute of michael@0: // licenseContent. michael@0: // Note: may be called multiple times due to multiple onLoad events. michael@0: var state = licenseContent.getAttribute("state"); michael@0: if (state == "loading" || aEvent.originalTarget != licenseContent) michael@0: return; michael@0: michael@0: licenseContent.removeEventListener("load", gLicensePage.onLicenseLoad, false); michael@0: michael@0: if (state == "error") { michael@0: gUpdates.wiz.goTo("manualUpdate"); michael@0: return; michael@0: } michael@0: michael@0: gLicensePage._licenseLoaded = true; michael@0: document.getElementById("acceptDeclineLicense").disabled = false; michael@0: gUpdates.wiz.getButton("extra1").disabled = false; michael@0: }, michael@0: michael@0: /** michael@0: * When the user changes the state of the accept / decline radio group michael@0: */ michael@0: onAcceptDeclineRadio: function() { michael@0: // Return early if this page hasn't been loaded (bug 405257). This event is michael@0: // fired during the construction of the wizard before gUpdates has received michael@0: // its onload event (bug 452389). michael@0: if (!this._licenseLoaded) michael@0: return; michael@0: michael@0: var selectedIndex = document.getElementById("acceptDeclineLicense") michael@0: .selectedIndex; michael@0: // 0 == Accept, 1 == Decline michael@0: var licenseAccepted = (selectedIndex == 0); michael@0: gUpdates.wiz.canAdvance = licenseAccepted; michael@0: }, michael@0: michael@0: /** michael@0: * The non-standard "Back" button. michael@0: */ michael@0: onExtra1: function() { michael@0: gUpdates.wiz.goTo(gUpdates.updatesFoundPageId); michael@0: }, michael@0: michael@0: /** michael@0: * When the user clicks next after accepting the license michael@0: */ michael@0: onWizardNext: function() { michael@0: try { michael@0: gUpdates.update.setProperty("licenseAccepted", "true"); michael@0: var um = CoC["@mozilla.org/updates/update-manager;1"]. michael@0: getService(CoI.nsIUpdateManager); michael@0: um.saveUpdates(); michael@0: } michael@0: catch (e) { michael@0: LOG("gLicensePage", "onWizardNext - nsIUpdateManager:saveUpdates() " + michael@0: "failed: " + e); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * When the user cancels the wizard michael@0: */ michael@0: onWizardCancel: function() { michael@0: try { michael@0: var licenseContent = document.getElementById("licenseContent"); michael@0: // If the license was downloading, stop it. michael@0: if (licenseContent) michael@0: licenseContent.stopDownloading(); michael@0: } michael@0: catch (e) { michael@0: LOG("gLicensePage", "onWizardCancel - " + michael@0: "licenseContent.stopDownloading() failed: " + e); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The page which shows add-ons that are incompatible and do not have updated michael@0: * compatibility information or a version update available to make them michael@0: * compatible. michael@0: */ michael@0: var gIncompatibleListPage = { michael@0: /** michael@0: * Initialize michael@0: */ michael@0: onPageShow: function() { michael@0: gUpdates.setButtons("backButton", null, "okButton", true); michael@0: var listbox = document.getElementById("incompatibleListbox"); michael@0: if (listbox.children.length > 0) michael@0: return; michael@0: michael@0: var intro = gUpdates.getAUSString("incompatAddons_" + gUpdates.update.type, michael@0: [gUpdates.brandName, michael@0: gUpdates.update.displayVersion]); michael@0: document.getElementById("incompatibleListDesc").textContent = intro; michael@0: michael@0: var addons = gUpdates.addons; michael@0: for (var i = 0; i < addons.length; ++i) { michael@0: var listitem = document.createElement("listitem"); michael@0: var addonLabel = gUpdates.getAUSString("addonLabel", [addons[i].name, michael@0: addons[i].version]); michael@0: listitem.setAttribute("label", addonLabel); michael@0: listbox.appendChild(listitem); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * The non-standard "Back" button. michael@0: */ michael@0: onExtra1: function() { michael@0: gUpdates.wiz.goTo(gUpdates.update.licenseURL ? "license" michael@0: : gUpdates.updatesFoundPageId); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The "Update is Downloading" page - provides feedback for the download michael@0: * process plus a pause/resume UI michael@0: */ michael@0: var gDownloadingPage = { michael@0: /** michael@0: * DOM Elements michael@0: */ michael@0: _downloadStatus: null, michael@0: _downloadProgress: null, michael@0: _pauseButton: null, michael@0: michael@0: /** michael@0: * Whether or not we are currently paused michael@0: */ michael@0: _paused: false, michael@0: michael@0: /** michael@0: * Label cache to hold the 'Connecting' string michael@0: */ michael@0: _label_downloadStatus: null, michael@0: michael@0: /** michael@0: * Member variables for updating download status michael@0: */ michael@0: _lastSec: Infinity, michael@0: _startTime: null, michael@0: _pausedStatus: "", michael@0: michael@0: _hiding: false, michael@0: michael@0: /** michael@0: * Have we registered an observer for a background update being staged michael@0: */ michael@0: _updateApplyingObserver: false, michael@0: michael@0: /** michael@0: * Initialize michael@0: */ michael@0: onPageShow: function() { michael@0: this._downloadStatus = document.getElementById("downloadStatus"); michael@0: this._downloadProgress = document.getElementById("downloadProgress"); michael@0: this._pauseButton = document.getElementById("pauseButton"); michael@0: this._label_downloadStatus = this._downloadStatus.textContent; michael@0: michael@0: this._pauseButton.setAttribute("tooltiptext", michael@0: gUpdates.getAUSString("pauseButtonPause")); michael@0: michael@0: // move focus to the pause/resume button and then disable it (bug #353177) michael@0: this._pauseButton.focus(); michael@0: this._pauseButton.disabled = true; michael@0: michael@0: var aus = CoC["@mozilla.org/updates/update-service;1"]. michael@0: getService(CoI.nsIApplicationUpdateService); michael@0: michael@0: var um = CoC["@mozilla.org/updates/update-manager;1"]. michael@0: getService(CoI.nsIUpdateManager); michael@0: var activeUpdate = um.activeUpdate; michael@0: if (activeUpdate) michael@0: gUpdates.setUpdate(activeUpdate); michael@0: michael@0: if (!gUpdates.update) { michael@0: LOG("gDownloadingPage", "onPageShow - no valid update to download?!"); michael@0: return; michael@0: } michael@0: michael@0: this._startTime = Date.now(); michael@0: michael@0: try { michael@0: // Say that this was a foreground download, not a background download, michael@0: // since the user cared enough to look in on this process. michael@0: gUpdates.update.QueryInterface(CoI.nsIWritablePropertyBag); michael@0: gUpdates.update.setProperty("foregroundDownload", "true"); michael@0: michael@0: // Pause any active background download and restart it as a foreground michael@0: // download. michael@0: aus.pauseDownload(); michael@0: var state = aus.downloadUpdate(gUpdates.update, false); michael@0: if (state == "failed") { michael@0: // We've tried as hard as we could to download a valid update - michael@0: // we fell back from a partial patch to a complete patch and even michael@0: // then we couldn't validate. Show a validation error with instructions michael@0: // on how to manually update. michael@0: this.cleanUp(); michael@0: gUpdates.wiz.goTo("errors"); michael@0: return; michael@0: } michael@0: else { michael@0: // Add this UI as a listener for active downloads michael@0: aus.addDownloadListener(this); michael@0: } michael@0: michael@0: if (activeUpdate) michael@0: this._setUIState(!aus.isDownloading); michael@0: } michael@0: catch(e) { michael@0: LOG("gDownloadingPage", "onPageShow - error: " + e); michael@0: } michael@0: michael@0: gUpdates.setButtons("hideButton", null, null, false); michael@0: gUpdates.wiz.getButton("extra1").focus(); michael@0: }, michael@0: michael@0: /** michael@0: * Updates the text status message michael@0: */ michael@0: _setStatus: function(status) { michael@0: // Don't bother setting the same text more than once. This can happen michael@0: // due to the asynchronous behavior of the downloader. michael@0: if (this._downloadStatus.textContent == status) michael@0: return; michael@0: while (this._downloadStatus.hasChildNodes()) michael@0: this._downloadStatus.removeChild(this._downloadStatus.firstChild); michael@0: this._downloadStatus.appendChild(document.createTextNode(status)); michael@0: }, michael@0: michael@0: /** michael@0: * Update download progress status to show time left, speed, and progress. michael@0: * Also updates the status needed for pausing the download. michael@0: * michael@0: * @param aCurr michael@0: * Current number of bytes transferred michael@0: * @param aMax michael@0: * Total file size of the download michael@0: * @return Current active download status michael@0: */ michael@0: _updateDownloadStatus: function(aCurr, aMax) { michael@0: let status; michael@0: michael@0: // Get the download time left and progress michael@0: let rate = aCurr / (Date.now() - this._startTime) * 1000; michael@0: [status, this._lastSec] = michael@0: DownloadUtils.getDownloadStatus(aCurr, aMax, rate, this._lastSec); michael@0: michael@0: // Get the download progress for pausing michael@0: this._pausedStatus = DownloadUtils.getTransferTotal(aCurr, aMax); michael@0: michael@0: return status; michael@0: }, michael@0: michael@0: /** michael@0: * Adjust UI to suit a certain state of paused-ness michael@0: * @param paused michael@0: * Whether or not the download is paused michael@0: */ michael@0: _setUIState: function(paused) { michael@0: var u = gUpdates.update; michael@0: if (paused) { michael@0: if (this._downloadProgress.mode != "normal") michael@0: this._downloadProgress.mode = "normal"; michael@0: this._pauseButton.setAttribute("tooltiptext", michael@0: gUpdates.getAUSString("pauseButtonResume")); michael@0: this._pauseButton.setAttribute("paused", "true"); michael@0: var p = u.selectedPatch.QueryInterface(CoI.nsIPropertyBag); michael@0: var status = p.getProperty("status"); michael@0: if (status) { michael@0: let pausedStatus = gUpdates.getAUSString("downloadPausedStatus", [status]); michael@0: this._setStatus(pausedStatus); michael@0: } michael@0: } michael@0: else { michael@0: if (this._downloadProgress.mode != "undetermined") michael@0: this._downloadProgress.mode = "undetermined"; michael@0: this._pauseButton.setAttribute("paused", "false"); michael@0: this._pauseButton.setAttribute("tooltiptext", michael@0: gUpdates.getAUSString("pauseButtonPause")); michael@0: this._setStatus(this._label_downloadStatus); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Wait for an update being staged in the background. michael@0: */ michael@0: _setUpdateApplying: function() { michael@0: this._downloadProgress.mode = "undetermined"; michael@0: this._pauseButton.hidden = true; michael@0: let applyingStatus = gUpdates.getAUSString("applyingUpdate"); michael@0: this._setStatus(applyingStatus); michael@0: michael@0: Services.obs.addObserver(this, "update-staged", false); michael@0: this._updateApplyingObserver = true; michael@0: }, michael@0: michael@0: /** michael@0: * Clean up the listener and observer registered for the wizard. michael@0: */ michael@0: cleanUp: function() { michael@0: var aus = CoC["@mozilla.org/updates/update-service;1"]. michael@0: getService(CoI.nsIApplicationUpdateService); michael@0: aus.removeDownloadListener(this); michael@0: michael@0: if (this._updateApplyingObserver) { michael@0: Services.obs.removeObserver(this, "update-staged"); michael@0: this._updateApplyingObserver = false; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * When the user clicks the Pause/Resume button michael@0: */ michael@0: onPause: function() { michael@0: var aus = CoC["@mozilla.org/updates/update-service;1"]. michael@0: getService(CoI.nsIApplicationUpdateService); michael@0: if (this._paused) michael@0: aus.downloadUpdate(gUpdates.update, false); michael@0: else { michael@0: var patch = gUpdates.update.selectedPatch; michael@0: patch.QueryInterface(CoI.nsIWritablePropertyBag); michael@0: patch.setProperty("status", this._pausedStatus); michael@0: aus.pauseDownload(); michael@0: } michael@0: this._paused = !this._paused; michael@0: michael@0: // Update the UI michael@0: this._setUIState(this._paused); michael@0: }, michael@0: michael@0: /** michael@0: * When the user has closed the window using a Window Manager control (this michael@0: * page doesn't have a cancel button) cancel the update in progress. michael@0: */ michael@0: onWizardCancel: function() { michael@0: if (this._hiding) michael@0: return; michael@0: michael@0: this.cleanUp(); michael@0: }, michael@0: michael@0: /** michael@0: * When the user closes the Wizard UI by clicking the Hide button michael@0: */ michael@0: onHide: function() { michael@0: // Set _hiding to true to prevent onWizardCancel from cancelling the update michael@0: // that is in progress. michael@0: this._hiding = true; michael@0: michael@0: // Remove ourself as a download listener so that we don't continue to be michael@0: // fed progress and state notifications after the UI we're updating has michael@0: // gone away. michael@0: this.cleanUp(); michael@0: michael@0: var aus = CoC["@mozilla.org/updates/update-service;1"]. michael@0: getService(CoI.nsIApplicationUpdateService); michael@0: var um = CoC["@mozilla.org/updates/update-manager;1"]. michael@0: getService(CoI.nsIUpdateManager); michael@0: um.activeUpdate = gUpdates.update; michael@0: michael@0: // If the download was paused by the user, ask the user if they want to michael@0: // have the update resume in the background. michael@0: var downloadInBackground = true; michael@0: if (this._paused) { michael@0: var title = gUpdates.getAUSString("resumePausedAfterCloseTitle"); michael@0: var message = gUpdates.getAUSString("resumePausedAfterCloseMsg", michael@0: [gUpdates.brandName]); michael@0: var ps = Services.prompt; michael@0: var flags = ps.STD_YES_NO_BUTTONS; michael@0: // Focus the software update wizard before prompting. This will raise michael@0: // the software update wizard if it is minimized making it more obvious michael@0: // what the prompt is for and will solve the problem of windows michael@0: // obscuring the prompt. See bug #350299 for more details. michael@0: window.focus(); michael@0: var rv = ps.confirmEx(window, title, message, flags, null, null, null, michael@0: null, { }); michael@0: if (rv == CoI.nsIPromptService.BUTTON_POS_0) michael@0: downloadInBackground = false; michael@0: } michael@0: if (downloadInBackground) { michael@0: // Continue download in the background at full speed. michael@0: LOG("gDownloadingPage", "onHide - continuing download in background " + michael@0: "at full speed"); michael@0: aus.downloadUpdate(gUpdates.update, false); michael@0: } michael@0: gUpdates.wiz.cancel(); michael@0: }, michael@0: michael@0: /** michael@0: * When the data transfer begins michael@0: * @param request michael@0: * The nsIRequest object for the transfer michael@0: * @param context michael@0: * Additional data michael@0: */ michael@0: onStartRequest: function(request, context) { michael@0: // This !paused test is necessary because onStartRequest may fire after michael@0: // the download was paused (for those speedy clickers...) michael@0: if (this._paused) michael@0: return; michael@0: michael@0: if (this._downloadProgress.mode != "undetermined") michael@0: this._downloadProgress.mode = "undetermined"; michael@0: this._setStatus(this._label_downloadStatus); michael@0: }, michael@0: michael@0: /** michael@0: * When new data has been downloaded michael@0: * @param request michael@0: * The nsIRequest object for the transfer michael@0: * @param context michael@0: * Additional data michael@0: * @param progress michael@0: * The current number of bytes transferred michael@0: * @param maxProgress michael@0: * The total number of bytes that must be transferred michael@0: */ michael@0: onProgress: function(request, context, progress, maxProgress) { michael@0: let status = this._updateDownloadStatus(progress, maxProgress); michael@0: var currentProgress = Math.round(100 * (progress / maxProgress)); michael@0: michael@0: var p = gUpdates.update.selectedPatch; michael@0: p.QueryInterface(CoI.nsIWritablePropertyBag); michael@0: p.setProperty("progress", currentProgress); michael@0: p.setProperty("status", status); michael@0: michael@0: // This !paused test is necessary because onProgress may fire after michael@0: // the download was paused (for those speedy clickers...) michael@0: if (this._paused) michael@0: return; michael@0: michael@0: if (this._downloadProgress.mode != "normal") michael@0: this._downloadProgress.mode = "normal"; michael@0: if (this._downloadProgress.value != currentProgress) michael@0: this._downloadProgress.value = currentProgress; michael@0: if (this._pauseButton.disabled) michael@0: this._pauseButton.disabled = false; michael@0: michael@0: // If the update has completed downloading and the download status contains michael@0: // the original text return early to avoid an assertion in debug builds. michael@0: // Since the page will advance immmediately due to the update completing the michael@0: // download updating the status is not important. michael@0: // nsTextFrame::GetTrimmedOffsets 'Can only call this on frames that have michael@0: // been reflowed'. michael@0: if (progress == maxProgress && michael@0: this._downloadStatus.textContent == this._label_downloadStatus) michael@0: return; michael@0: michael@0: this._setStatus(status); michael@0: }, michael@0: michael@0: /** michael@0: * When we have new status text michael@0: * @param request michael@0: * The nsIRequest object for the transfer michael@0: * @param context michael@0: * Additional data michael@0: * @param status michael@0: * A status code michael@0: * @param statusText michael@0: * Human readable version of |status| michael@0: */ michael@0: onStatus: function(request, context, status, statusText) { michael@0: this._setStatus(statusText); michael@0: }, michael@0: michael@0: /** michael@0: * When data transfer ceases michael@0: * @param request michael@0: * The nsIRequest object for the transfer michael@0: * @param context michael@0: * Additional data michael@0: * @param status michael@0: * Status code containing the reason for the cessation. michael@0: */ michael@0: onStopRequest: function(request, context, status) { michael@0: if (this._downloadProgress.mode != "normal") michael@0: this._downloadProgress.mode = "normal"; michael@0: michael@0: var u = gUpdates.update; michael@0: switch (status) { michael@0: case CoR.NS_ERROR_CORRUPTED_CONTENT: michael@0: case CoR.NS_ERROR_UNEXPECTED: michael@0: if (u.selectedPatch.state == STATE_DOWNLOAD_FAILED && michael@0: (u.isCompleteUpdate || u.patchCount != 2)) { michael@0: // Verification error of complete patch, informational text is held in michael@0: // the update object. michael@0: this.cleanUp(); michael@0: gUpdates.wiz.goTo("errors"); michael@0: break; michael@0: } michael@0: // Verification failed for a partial patch, complete patch is now michael@0: // downloading so return early and do NOT remove the download listener! michael@0: michael@0: // Reset the progress meter to "undertermined" mode so that we don't michael@0: // show old progress for the new download of the "complete" patch. michael@0: this._downloadProgress.mode = "undetermined"; michael@0: this._pauseButton.disabled = true; michael@0: document.getElementById("verificationFailed").hidden = false; michael@0: break; michael@0: case CoR.NS_BINDING_ABORTED: michael@0: LOG("gDownloadingPage", "onStopRequest - pausing download"); michael@0: // Do not remove UI listener since the user may resume downloading again. michael@0: break; michael@0: case CoR.NS_OK: michael@0: LOG("gDownloadingPage", "onStopRequest - patch verification succeeded"); michael@0: // If the background update pref is set, we should wait until the update michael@0: // is actually staged in the background. michael@0: var aus = CoC["@mozilla.org/updates/update-service;1"]. michael@0: getService(CoI.nsIApplicationUpdateService); michael@0: if (aus.canStageUpdates) { michael@0: this._setUpdateApplying(); michael@0: } else { michael@0: this.cleanUp(); michael@0: gUpdates.wiz.goTo("finished"); michael@0: } michael@0: break; michael@0: default: michael@0: LOG("gDownloadingPage", "onStopRequest - transfer failed"); michael@0: // Some kind of transfer error, die. michael@0: this.cleanUp(); michael@0: gUpdates.wiz.goTo("errors"); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * See nsIObserver.idl michael@0: */ michael@0: observe: function(aSubject, aTopic, aData) { michael@0: if (aTopic == "update-staged") { michael@0: if (aData == STATE_DOWNLOADING) { michael@0: // We've fallen back to downloding the full update because the michael@0: // partial update failed to get staged in the background. michael@0: this._setStatus("downloading"); michael@0: return; michael@0: } michael@0: this.cleanUp(); michael@0: if (aData == STATE_APPLIED || michael@0: aData == STATE_APPLIED_SVC || michael@0: aData == STATE_PENDING || michael@0: aData == STATE_PENDING_SVC) { michael@0: // If the update is successfully applied, or if the updater has michael@0: // fallen back to non-staged updates, go to the finish page. michael@0: gUpdates.wiz.goTo("finished"); michael@0: } else { michael@0: gUpdates.wiz.goTo("errors"); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * See nsISupports.idl michael@0: */ michael@0: QueryInterface: function(iid) { michael@0: if (!iid.equals(CoI.nsIRequestObserver) && michael@0: !iid.equals(CoI.nsIProgressEventSink) && michael@0: !iid.equals(CoI.nsIObserver) && michael@0: !iid.equals(CoI.nsISupports)) michael@0: throw CoR.NS_ERROR_NO_INTERFACE; michael@0: return this; michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The "There was an error during the update" page. michael@0: */ michael@0: var gErrorsPage = { michael@0: /** michael@0: * Initialize michael@0: */ michael@0: onPageShow: function() { michael@0: gUpdates.setButtons(null, null, "okButton", true); michael@0: gUpdates.wiz.getButton("finish").focus(); michael@0: michael@0: var statusText = gUpdates.update.statusText; michael@0: LOG("gErrorsPage" , "onPageShow - update.statusText: " + statusText); michael@0: michael@0: var errorReason = document.getElementById("errorReason"); michael@0: errorReason.value = statusText; michael@0: var manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_MANUAL_URL); michael@0: var errorLinkLabel = document.getElementById("errorLinkLabel"); michael@0: errorLinkLabel.value = manualURL; michael@0: errorLinkLabel.setAttribute("url", manualURL); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The page shown when there is a background check or a certificate attribute michael@0: * error. michael@0: */ michael@0: var gErrorExtraPage = { michael@0: /** michael@0: * Initialize michael@0: */ michael@0: onPageShow: function() { michael@0: gUpdates.setButtons(null, null, "okButton", true); michael@0: gUpdates.wiz.getButton("finish").focus(); michael@0: let secHistogram = CoC["@mozilla.org/base/telemetry;1"]. michael@0: getService(CoI.nsITelemetry). michael@0: getHistogramById("SECURITY_UI"); michael@0: michael@0: if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CERT_ERRORS)) michael@0: Services.prefs.clearUserPref(PREF_APP_UPDATE_CERT_ERRORS); michael@0: michael@0: if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS)) michael@0: Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS); michael@0: michael@0: if (gUpdates.update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE) { michael@0: document.getElementById("errorCertAttrHasUpdateLabel").hidden = false; michael@0: secHistogram.add(CoI.nsISecurityUITelemetry.WARNING_INSECURE_UPDATE); michael@0: } michael@0: else { michael@0: if (gUpdates.update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE){ michael@0: document.getElementById("errorCertCheckNoUpdateLabel").hidden = false; michael@0: secHistogram.add(CoI.nsISecurityUITelemetry.WARNING_NO_SECURE_UPDATE); michael@0: } michael@0: else michael@0: document.getElementById("genericBackgroundErrorLabel").hidden = false; michael@0: var manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_MANUAL_URL); michael@0: var errorLinkLabel = document.getElementById("errorExtraLinkLabel"); michael@0: errorLinkLabel.value = manualURL; michael@0: errorLinkLabel.setAttribute("url", manualURL); michael@0: errorLinkLabel.hidden = false; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The "There was an error applying a partial patch" page. michael@0: */ michael@0: var gErrorPatchingPage = { michael@0: /** michael@0: * Initialize michael@0: */ michael@0: onPageShow: function() { michael@0: gUpdates.setButtons(null, null, "okButton", true); michael@0: }, michael@0: michael@0: onWizardNext: function() { michael@0: switch (gUpdates.update.selectedPatch.state) { michael@0: case STATE_PENDING: michael@0: case STATE_PENDING_SVC: michael@0: gUpdates.wiz.goTo("finished"); michael@0: break; michael@0: case STATE_DOWNLOADING: michael@0: gUpdates.wiz.goTo("downloading"); michael@0: break; michael@0: case STATE_DOWNLOAD_FAILED: michael@0: gUpdates.wiz.goTo("errors"); michael@0: break; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The "Update has been downloaded" page. Shows information about what michael@0: * was downloaded. michael@0: */ michael@0: var gFinishedPage = { michael@0: /** michael@0: * Initialize michael@0: */ michael@0: onPageShow: function() { michael@0: gUpdates.setButtons("restartLaterButton", null, "restartNowButton", michael@0: true); michael@0: gUpdates.wiz.getButton("finish").focus(); michael@0: }, michael@0: michael@0: /** michael@0: * Initialize the Wizard Page for a Background Source Event michael@0: */ michael@0: onPageShowBackground: function() { michael@0: this.onPageShow(); michael@0: var updateFinishedName = document.getElementById("updateFinishedName"); michael@0: updateFinishedName.value = gUpdates.update.name; michael@0: michael@0: var link = document.getElementById("finishedBackgroundLink"); michael@0: if (gUpdates.update.detailsURL) { michael@0: link.setAttribute("url", gUpdates.update.detailsURL); michael@0: // The details link is stealing focus so it is disabled by default and michael@0: // should only be enabled after onPageShow has been called. michael@0: link.disabled = false; michael@0: } michael@0: else michael@0: link.hidden = true; michael@0: michael@0: if (getPref("getBoolPref", PREF_APP_UPDATE_TEST_LOOP, false)) { michael@0: setTimeout(function () { michael@0: gUpdates.wiz.getButton("finish").click(); michael@0: }, UPDATE_TEST_LOOP_INTERVAL); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Called when the wizard finishes, i.e. the "Restart Now" button is michael@0: * clicked. michael@0: */ michael@0: onWizardFinish: function() { michael@0: // Do the restart michael@0: LOG("gFinishedPage" , "onWizardFinish - restarting the application"); michael@0: michael@0: // disable the "finish" (Restart) and "extra1" (Later) buttons michael@0: // because the Software Update wizard is still up at the point, michael@0: // and will remain up until we return and we close the michael@0: // window with a |window.close()| in wizard.xml michael@0: // (it was the firing the "wizardfinish" event that got us here.) michael@0: // This prevents the user from switching back michael@0: // to the Software Update dialog and clicking "Restart" or "Later" michael@0: // when dealing with the "confirm close" prompts. michael@0: // See bug #350299 for more details. michael@0: gUpdates.wiz.getButton("finish").disabled = true; michael@0: gUpdates.wiz.getButton("extra1").disabled = true; michael@0: michael@0: // Notify all windows that an application quit has been requested. michael@0: var cancelQuit = CoC["@mozilla.org/supports-PRBool;1"]. michael@0: createInstance(CoI.nsISupportsPRBool); michael@0: Services.obs.notifyObservers(cancelQuit, "quit-application-requested", michael@0: "restart"); michael@0: michael@0: // Something aborted the quit process. michael@0: if (cancelQuit.data) michael@0: return; michael@0: michael@0: // If already in safe mode restart in safe mode (bug 327119) michael@0: if (Services.appinfo.inSafeMode) { michael@0: let env = CoC["@mozilla.org/process/environment;1"]. michael@0: getService(CoI.nsIEnvironment); michael@0: env.set("MOZ_SAFE_MODE_RESTART", "1"); michael@0: } michael@0: michael@0: // Restart the application michael@0: CoC["@mozilla.org/toolkit/app-startup;1"].getService(CoI.nsIAppStartup). michael@0: quit(CoI.nsIAppStartup.eAttemptQuit | CoI.nsIAppStartup.eRestart); michael@0: }, michael@0: michael@0: /** michael@0: * When the user clicks the "Restart Later" instead of the Restart Now" button michael@0: * in the wizard after an update has been downloaded. michael@0: */ michael@0: onExtra1: function() { michael@0: gUpdates.wiz.cancel(); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The "Update was Installed Successfully" page. michael@0: */ michael@0: var gInstalledPage = { michael@0: /** michael@0: * Initialize michael@0: */ michael@0: onPageShow: function() { michael@0: var branding = document.getElementById("brandStrings"); michael@0: try { michael@0: // whatsNewURL should just be a pref (bug 546609). michael@0: var url = branding.getFormattedString("whatsNewURL", [Services.appinfo.version]); michael@0: var whatsnewLink = document.getElementById("whatsnewLink"); michael@0: whatsnewLink.setAttribute("url", url); michael@0: whatsnewLink.hidden = false; michael@0: } michael@0: catch (e) { michael@0: } michael@0: michael@0: gUpdates.setButtons(null, null, "okButton", true); michael@0: gUpdates.wiz.getButton("finish").focus(); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Callback for the Update Prompt to set the current page if an Update Wizard michael@0: * window is already found to be open. michael@0: * @param pageid michael@0: * The ID of the page to switch to michael@0: */ michael@0: function setCurrentPage(pageid) { michael@0: gUpdates.wiz.currentPage = document.getElementById(pageid); michael@0: }