/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Update Service. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2005 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Ben Goodger (Original Author) * Jeff Walden * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ const nsIUpdateItem = Components.interfaces.nsIUpdateItem; const nsIIncrementalDownload = Components.interfaces.nsIIncrementalDownload; const XMLNS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; const PREF_UPDATE_MANUAL_URL = "app.update.url.manual"; const PREF_UPDATE_NAGTIMER_DL = "app.update.nagTimer.download"; const PREF_UPDATE_NAGTIMER_RESTART = "app.update.nagTimer.restart"; const PREF_APP_UPDATE_LOG_BRANCH = "app.update.log."; const PREF_UPDATE_TEST_LOOP = "app.update.test.loop"; const PREF_UPDATE_NEVER_BRANCH = "app.update.never." const UPDATE_TEST_LOOP_INTERVAL = 2000; const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties"; const STATE_DOWNLOADING = "downloading"; const STATE_PENDING = "pending"; const STATE_APPLYING = "applying"; const STATE_SUCCEEDED = "succeeded"; const STATE_DOWNLOAD_FAILED = "download-failed"; const STATE_FAILED = "failed"; const SRCEVT_FOREGROUND = 1; const SRCEVT_BACKGROUND = 2; var gConsole = null; var gPref = null; var gLogEnabled = { }; /** * Logs a string to the error console. * @param string * The string to write to the error console.. */ function LOG(module, string) { if (module in gLogEnabled || "all" in gLogEnabled) { dump("*** " + module + ":" + string + "\n"); gConsole.logStringMessage(string); } } /** * Gets a preference value, handling the case where there is no default. * @param func * The name of the preference function to call, on nsIPrefBranch * @param preference * The name of the preference * @param defaultValue * The default value to return in the event the preference has * no setting * @returns The value of the preference, or undefined if there was no * user or default value. */ function getPref(func, preference, defaultValue) { try { return gPref[func](preference); } catch (e) { LOG("General", "Failed to get preference " + preference); } return defaultValue; } /** * A set of shared data and control functions for the wizard as a whole. */ var gUpdates = { /** * The nsIUpdate object being used by this window (either for downloading, * notification or both). */ update: null, /** * The updates.properties element. */ strings: null, /** * The Application brandShortName (e.g. "Firefox") */ brandName: null, /** * The element */ wiz: null, /** * Helper function for setButtons * Resets button to original label & accesskey if string is null. */ _setButton: function(button, string) { if (string) { var label = this.strings.getString(string); if (label.indexOf("%S") != -1) label = label.replace(/%S/, this.brandName); button.label = label; button.setAttribute("accesskey", this.strings.getString(string + ".accesskey")); } else { button.label = button.defaultLabel; button.setAttribute("accesskey", button.defaultAccesskey); } }, /** * Set the state for the Wizard's control buttons (labels and disabled * state). * @param backButtonString * The property in the stringbundle containing the label * to put on the Back button, or null for default. * @param backButtonDisabled * true if the Back button should be disabled, false otherwise * @param nextButtonString * The property in the stringbundle containing the label * to put on the Next button, or null for default. * @param nextButtonDisabled * true if the Next button should be disabled, false otherwise * @param finishButtonString * The property in the stringbundle containing the label * to put on the Finish button, or null for default. * @param finishButtonDisabled * true if the Finish button should be disabled, false otherwise * @param cancelButtonString * The property in the stringbundle containing the label * to put on the Cancel button, or null for default. * @param cancelButtonDisabled * true if the Cancel button should be disabled, false otherwise * @param hideBackAndCancelButtons * true if the Cancel button should be hidden, false otherwise * @param extraButton1String * The property in the stringbundle containing the label to put * on the first Extra button, if present, or null for default. * @param extraButton1Disabled * true if the first Extra button should be disabled, * false otherwise * @param extraButton2String * The property in the stringbundle containing the label to put * on the second Extra button, if present, or null for default. * @param extraButton2Disabled * true if the second Extra button should be disabled, * false otherwise */ setButtons: function(backButtonString, backButtonDisabled, nextButtonString, nextButtonDisabled, finishButtonString, finishButtonDisabled, cancelButtonString, cancelButtonDisabled, hideBackAndCancelButtons, extraButton1String, extraButton1Disabled, extraButton2String, extraButton2Disabled) { var bb = this.wiz.getButton("back"); var bn = this.wiz.getButton("next"); var bf = this.wiz.getButton("finish"); var bc = this.wiz.getButton("cancel"); var be1 = this.wiz.getButton("extra1"); var be2 = this.wiz.getButton("extra2"); this._setButton(bb, backButtonString); this._setButton(bn, nextButtonString); this._setButton(bf, finishButtonString); this._setButton(bc, cancelButtonString); this._setButton(be1, extraButton1String); this._setButton(be2, extraButton2String); // update button state using the wizard commands this.wiz.canRewind = !backButtonDisabled; // The Finish and Next buttons are never exposed at the same time if (this.wiz.onLastPage) this.wiz.canAdvance = !finishButtonDisabled; else this.wiz.canAdvance = !nextButtonDisabled; bf.disabled = finishButtonDisabled; bc.disabled = cancelButtonDisabled; be1.disabled = extraButton1Disabled; be2.disabled = extraButton2Disabled; // Show or hide the cancel and back buttons, since the first Extra // button does this job. bc.hidden = hideBackAndCancelButtons; bb.hidden = hideBackAndCancelButtons; // Show or hide the extra buttons be1.hidden = extraButton1String == null; be2.hidden = extraButton2String == null; }, never: function () { // if the user hits "Never", we should not prompt them about this // major version again, unless they manually do "Check for Updates..." // which, will clear the "never" pref for the version presented // so that if they do "Later", we will remind them later. var neverPrefName = PREF_UPDATE_NEVER_BRANCH + gUpdates.update.version; gPref.setBoolPref(neverPrefName, true); this.wiz.cancel(); }, later: function () { // The user said "Later", so close the wizard this.wiz.cancel(); }, /** * A hash of |pageid| attribute to page object. Can be used to dispatch * function calls to the appropriate page. */ _pages: { }, /** * Called when the user presses the "Finish" button on the wizard, dispatches * the function call to the selected page. */ onWizardFinish: function() { var pageid = document.documentElement.currentPage.pageid; if ("onWizardFinish" in this._pages[pageid]) this._pages[pageid].onWizardFinish(); }, /** * Called when the user presses the "Cancel" button on the wizard, dispatches * the function call to the selected page. */ onWizardCancel: function() { var pageid = document.documentElement.currentPage.pageid; if ("onWizardCancel" in this._pages[pageid]) this._pages[pageid].onWizardCancel(); }, /** * Called when the user presses the "Next" button on the wizard, dispatches * the function call to the selected page. */ onWizardNext: function() { var cp = document.documentElement.currentPage; if (!cp) return; var pageid = cp.pageid; if ("onWizardNext" in this._pages[pageid]) this._pages[pageid].onWizardNext(); }, /** * The checking process that spawned this update UI. There are two types: * SRCEVT_FOREGROUND: * Some user-generated event caused this UI to appear, e.g. the Help * menu item or the button in preferences. When in this mode, the UI * should remain active for the duration of the download. * SRCEVT_BACKGROUND: * A background update check caused this UI to appear, probably because * incompatibilities in Extensions or other addons were discovered and * the user's consent to continue was required. When in this mode, the * UI will disappear after the user's consent is obtained. */ sourceEvent: SRCEVT_FOREGROUND, /** * The global error message - the reason the update failed. This is human * readable text, used to initialize the error page. */ errorMessage: "", /** * Helper function for onLoad * Saves default button label & accesskey for use by _setButton */ _cacheButtonStrings: function (buttonName) { var button = this.wiz.getButton(buttonName); button.defaultLabel = button.label; button.defaultAccesskey = button.getAttribute("accesskey"); }, /** * Called when the wizard UI is loaded. */ onLoad: function() { this.wiz = document.documentElement; gPref = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch2); gConsole = Components.classes["@mozilla.org/consoleservice;1"] .getService(Components.interfaces.nsIConsoleService); this._initLoggingPrefs(); this.strings = document.getElementById("updateStrings"); var brandStrings = document.getElementById("brandStrings"); this.brandName = brandStrings.getString("brandShortName"); var pages = gUpdates.wiz.childNodes; for (var i = 0; i < pages.length; ++i) { var page = pages[i]; if (page.localName == "wizardpage") this._pages[page.pageid] = eval(page.getAttribute("object")); } // Cache the standard button labels in case we need to restore them this._cacheButtonStrings("back"); this._cacheButtonStrings("next"); this._cacheButtonStrings("finish"); this._cacheButtonStrings("cancel"); this._cacheButtonStrings("extra1"); this._cacheButtonStrings("extra2"); // Advance to the Start page. gUpdates.wiz.currentPage = this.startPage; }, /** * Initialize Logging preferences, formatted like so: * app.update.log. = */ _initLoggingPrefs: function() { try { var ps = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService); var logBranch = ps.getBranch(PREF_APP_UPDATE_LOG_BRANCH); var modules = logBranch.getChildList("", { value: 0 }); for (var i = 0; i < modules.length; ++i) { if (logBranch.prefHasUserValue(modules[i])) gLogEnabled[modules[i]] = logBranch.getBoolPref(modules[i]); } } catch (e) { } }, /** * Return the object that should be displayed first. * * This is determined by how we were called by the update prompt: * * U'Prompt Method: Arg0: Update State: Src Event: p'Failed: Result: * showUpdateAvailable nsIUpdate obj -- background -- updatesfound * showUpdateDownloaded nsIUpdate obj pending background -- finishedBackground * showUpdateInstalled nsIUpdate obj succeeded either -- installed * showUpdateError nsIUpdate obj failed either partial errorpatching * showUpdateError nsIUpdate obj failed either complete errors * checkForUpdates null -- foreground -- checking * checkForUpdates null downloading foreground -- downloading */ get startPage() { if (window.arguments) { var arg0 = window.arguments[0]; if (arg0 instanceof Components.interfaces.nsIUpdate) { // If the first argument is a nsIUpdate object, we are notifying the // user that the background checking found an update that requires // their permission to install, and it's ready for download. this.setUpdate(arg0); var p = this.update.selectedPatch; if (p) { var state = p.state; if (state == STATE_DOWNLOADING) { var patchFailed = false; try { patchFailed = this.update.getProperty("patchingFailed"); } catch (e) { } if (patchFailed == "partial") { // If the system failed to apply the partial patch, show the // screen which best describes this condition, which is triggered // by the |STATE_FAILED| state. state = STATE_FAILED; } else if (patchFailed == "complete") { // Otherwise, if the complete patch failed, which is far less // likely, show the error text held by the update object in the // generic errors page, triggered by the |STATE_DOWNLOAD_FAILED| // state. state = STATE_DOWNLOAD_FAILED; } } // Now select the best page to start with, given the current state of // the Update. switch (state) { case STATE_PENDING: this.sourceEvent = SRCEVT_BACKGROUND; return document.getElementById("finishedBackground"); case STATE_SUCCEEDED: return document.getElementById("installed"); case STATE_DOWNLOADING: return document.getElementById("downloading"); case STATE_FAILED: window.getAttention(); return document.getElementById("errorpatching"); case STATE_DOWNLOAD_FAILED: case STATE_APPLYING: return document.getElementById("errors"); } } return document.getElementById("updatesfound"); } } else { var um = Components.classes["@mozilla.org/updates/update-manager;1"]. getService(Components.interfaces.nsIUpdateManager); if (um.activeUpdate) { this.setUpdate(um.activeUpdate); return document.getElementById("downloading"); } } return document.getElementById("checking"); }, /** * Sets the Update object for this wizard * @param update * The update object */ setUpdate: function(update) { this.update = update; if (this.update) this.update.QueryInterface(Components.interfaces.nsIWritablePropertyBag); }, /** * Registers a timer to nag the user about something relating to update * @param timerID * The ID of the timer to register, used for persistence * @param timerInterval * The interval of the timer * @param methodName * The method to call on the Update Prompter when the timer fires */ registerNagTimer: function(timerID, timerInterval, methodName) { // Remind the user to restart their browser in a little bit. var tm = Components.classes["@mozilla.org/updates/timer-manager;1"]. getService(Components.interfaces.nsIUpdateTimerManager); /** * An object implementing nsITimerCallback that uses the Update Prompt * component to notify the user about some event relating to app update * that they should take action on. * @param update * The nsIUpdate object in question * @param methodName * The name of the method on the Update Prompter that should be * called * @constructor */ function Callback(update, methodName) { this._update = update; this._methodName = methodName; this._prompter = Components.classes["@mozilla.org/updates/update-prompt;1"]. createInstance(Components.interfaces.nsIUpdatePrompt); } Callback.prototype = { /** * The Update we should nag about downloading */ _update: null, /** * The Update prompter we can use to notify the user */ _prompter: null, /** * The method on the update prompt that should be called */ _methodName: "", /** * Called when the timer fires. Notifies the user about whichever event * they need to be nagged about (e.g. update available, please restart, * etc). */ notify: function(timerCallback) { if (methodName in this._prompter) this._prompter[methodName](null, this._update); } } tm.registerTimer(timerID, (new Callback(gUpdates.update, methodName)), timerInterval); }, } /** * The "Checking for Updates" page. Provides feedback on the update checking * process. */ var gCheckingPage = { /** * The nsIUpdateChecker that is currently checking for updates. We hold onto * this so we can cancel the update check if the user closes the window. */ _checker: null, /** * Starts the update check when the page is shown. */ onPageShow: function() { gUpdates.setButtons(null, true, null, true, null, true, null, false, false, null, false, null, false); this._checker = Components.classes["@mozilla.org/updates/update-checker;1"]. createInstance(Components.interfaces.nsIUpdateChecker); this._checker.checkForUpdates(this.updateListener, true); }, /** * The user has closed the window, either by pressing cancel or using a Window * Manager control, so stop checking for updates. */ onWizardCancel: function() { if (this._checker) { const nsIUpdateChecker = Components.interfaces.nsIUpdateChecker; this._checker.stopChecking(nsIUpdateChecker.CURRENT_CHECK); } }, /** * An object implementing nsIUpdateCheckListener that is notified as the * update check commences. */ updateListener: { /** * See nsIUpdateCheckListener */ onProgress: function(request, position, totalSize) { var pm = document.getElementById("checkingProgress"); checkingProgress.setAttribute("mode", "normal"); checkingProgress.setAttribute("value", Math.floor(100 * (position/totalSize))); }, /** * See nsIUpdateCheckListener */ onCheckComplete: function(request, updates, updateCount) { var aus = Components.classes["@mozilla.org/updates/update-service;1"] .getService(Components.interfaces.nsIApplicationUpdateService); gUpdates.setUpdate(aus.selectUpdate(updates, updates.length)); if (!gUpdates.update) { LOG("UI:CheckingPage", "Could not select an appropriate update, either because there " + "were none, or |selectUpdate| failed."); var checking = document.getElementById("checking"); checking.setAttribute("next", "noupdatesfound"); } gUpdates.wiz.canAdvance = true; gUpdates.wiz.advance(); }, /** * See nsIUpdateCheckListener */ onError: function(request, update) { LOG("UI:CheckingPage", "UpdateCheckListener: error"); gUpdates.setUpdate(update); gUpdates.wiz.currentPage = document.getElementById("errors"); }, /** * 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; } } }; /** * The "No Updates Are Available" page */ var gNoUpdatesPage = { /** * Initialize */ onPageShow: function() { gUpdates.setButtons(null, true, null, true, null, false, "hideButton", true, false, null, false, null, false); gUpdates.wiz.getButton("finish").focus(); } }; /** * The "Updates Are Available" page. Provides the user information about the * available update, extensions it might make incompatible, and a means to * continue downloading and installing it. */ var gUpdatesAvailablePage = { /** * An array of installed addons incompatible with this update. */ _incompatibleItems: null, /** * The binding (which we are also using for the details content) */ _updateMoreInfoContent: null, /** * Initialize. */ onPageShow: function() { // disable the "next" button, as we don't want the user to // be able to progress with the update util we show them the details // which might include a warning about incompatible add-ons // disable the "back" button, as well (note the call to setButtons() // below does this, too). This just leaves the cancel button // until we are ready gUpdates.wiz.getButton("next").disabled = true; gUpdates.wiz.getButton("back").disabled = true; var update = gUpdates.update.QueryInterface( Components.interfaces.nsIUpdate_MOZILLA_1_8_BRANCH); var updateName = gUpdates.strings.getFormattedString("updateName", [gUpdates.brandName, update.version]); if (update.channel == "nightly") updateName = updateName + " nightly (" + update.buildID + ")"; var updateNameElement = document.getElementById("updateName"); updateNameElement.value = updateName; var severity = update.type; var updateTypeElement = document.getElementById("updateType"); updateTypeElement.setAttribute("severity", severity); var intro; if (severity == "major") { // for major updates, use the brandName and the version for the intro intro = gUpdates.strings.getFormattedString( "introType_" + severity + "_app_and_version", [gUpdates.brandName, update.version]); this._updateMoreInfoContent = document.getElementById("updateMoreInfoContent"); // update_name and update_version need to be set before url // so that when attempting to download the url, we can show // the formatted "Download..." string this._updateMoreInfoContent.update_name = gUpdates.brandName; this._updateMoreInfoContent.update_version = update.version; this._updateMoreInfoContent.url = update.detailsURL; } else { // for minor updates, do not include the version // just use the brandName for the intro intro = gUpdates.strings.getFormattedString( "introType_" + severity + "_app", [gUpdates.brandName]); var updateMoreInfoURL = document.getElementById("updateMoreInfoURL"); updateMoreInfoURL.href = update.detailsURL; } var updateTitle = gUpdates.strings .getString("updatesfound_" + severity + ".title"); gUpdates.wiz.currentPage.setAttribute("label", updateTitle); // this is necessary to make this change to the label of the current // wizard page show up gUpdates.wiz._adjustWizardHeader(); while (updateTypeElement.hasChildNodes()) updateTypeElement.removeChild(updateTypeElement.firstChild); updateTypeElement.appendChild(document.createTextNode(intro)); var em = Components.classes["@mozilla.org/extensions/manager;1"] .getService(Components.interfaces.nsIExtensionManager); var items = em.getIncompatibleItemList("", update.version, nsIUpdateItem.TYPE_ADDON, false, { }); if (items.length > 0) { // There are addons that are incompatible with this update, so show the // warning message. var incompatibleWarning = document.getElementById("incompatibleWarning"); incompatibleWarning.hidden = false; this._incompatibleItems = items; } // wait to show the additional details until after we do the check // for add-on incompatibilty this.onShowMoreDetails(); var licenseAccepted; try { licenseAccepted = update.getProperty("licenseAccepted"); } catch (e) { gUpdates.update.setProperty("licenseAccepted", "false"); licenseAccepted = false; } // only major updates show EULAs if (gUpdates.update.type == "major" && gUpdates.update.licenseURL && !licenseAccepted) gUpdates.wiz.currentPage.setAttribute("next", "license"); gUpdates.setButtons(null, true, "downloadButton_" + severity, false, null, false, null, false, true, "laterButton", false, severity == "major" ? "neverButton" : null, false); gUpdates.wiz.getButton("next").focus(); }, /** * User clicked the "More Details..." button */ onShowMoreDetails: function() { var updateTypeElement = document.getElementById("updateType"); var moreInfoURL = document.getElementById("moreInfoURL"); var moreInfoContent = document.getElementById("moreInfoContent"); if (updateTypeElement.getAttribute("severity") == "major") { moreInfoURL.hidden = true; moreInfoContent.hidden = false; document.getElementById("updateName").hidden = true; document.getElementById("updateNameSep").hidden = true; document.getElementById("upgradeEvangelism").hidden = true; document.getElementById("upgradeEvangelismSep").hidden = true; // clear the "never" pref for this version. this is to handle the // scenario where the user clicked "never" for a major update // and then at a later point, did "Check for Updates..." // and then hit "Later". If we don't clear the "never" pref // "Later" will never happen. var neverPrefName = PREF_UPDATE_NEVER_BRANCH + gUpdates.update.version; gPref.setBoolPref(neverPrefName, false); } else { moreInfoURL.hidden = false; moreInfoContent.hidden = true; } // in order to prevent showing the blank xul:browser ( binding) // delay setting the selected index of the detailsDeck until after // we've set everything up. see bug #352400 for more details. var detailsDeck = document.getElementById("detailsDeck"); detailsDeck.selectedIndex = 1; }, /** * When the user cancels the wizard or they decline the license */ onWizardCancel: function() { try { // If the _updateMoreInfoContent was downloading, stop it. if (this._updateMoreInfoContent) this._updateMoreInfoContent.stopDownloading(); } catch (ex) { dump("XXX _updateMoreInfoContent.stopDownloading() failed: " + ex + "\n"); } }, /** * Show a list of extensions made incompatible by this update. */ showIncompatibleItems: function() { openDialog("chrome://mozapps/content/update/incompatible.xul", "", "dialog,centerscreen,modal,resizable,titlebar", this._incompatibleItems); } }; /** * The page which shows the user a license associated with an update. The * user must agree to the terms of the license before continuing to install * the update. */ var gLicensePage = { /** * The element */ _licenseContent: null, /** * Initialize */ onPageShow: function() { this._licenseContent = document.getElementById("licenseContent"); // the license radiogroup is disabled until the EULA is downloaded document.getElementById("acceptDeclineLicense").disabled = true; gUpdates.setButtons(null, true, null, true, null, true, null, false, false, null, false, null, false); this._licenseContent.addEventListener("load", this.onLicenseLoad, false); // update_name and update_version need to be set before url // so that when attempting to download the url, we can show // the formatted "Download..." string this._licenseContent.update_name = gUpdates.brandName; this._licenseContent.update_version = gUpdates.update.version; this._licenseContent.url = gUpdates.update.licenseURL; }, /** * When the license document has loaded */ onLicenseLoad: function() { // on the load event, disable or enable the radiogroup based on // the state of the licenseContent. note, you may get multiple // onLoad events document.getElementById("acceptDeclineLicense").disabled = (gLicensePage._licenseContent.getAttribute("state") == "error"); }, /** * When the user changes the state of the accept / decline radio group */ onAcceptDeclineRadio: function() { var selectedIndex = document.getElementById("acceptDeclineLicense") .selectedIndex; // 0 == Accept, 1 == Decline var licenseAccepted = (selectedIndex == 0); gUpdates.wiz.getButton("next").disabled = !licenseAccepted; gUpdates.wiz.canAdvance = licenseAccepted; }, /** * When the user accepts the license by hitting "Next" */ onWizardNext: function() { try { gUpdates.update.setProperty("licenseAccepted", "true"); var um = Components.classes["@mozilla.org/updates/update-manager;1"]. getService(Components.interfaces.nsIUpdateManager); um.saveUpdates(); } catch (ex) { dump("XXX ex " + ex + "\n"); } }, /** * When the user cancels the wizard or they decline the license */ onWizardCancel: function() { try { // If the license was downloading, stop it. if (this._licenseContent) this._licenseContent.stopDownloading(); } catch (ex) { dump("XXX _licenseContent.stopDownloading() failed: " + ex + "\n"); } } }; /** * Formats status messages for a download operation based on the progress * of the download. * @constructor */ function DownloadStatusFormatter() { this._startTime = Math.floor((new Date()).getTime() / 1000); this._elapsed = 0; var us = gUpdates.strings; this._statusFormat = us.getString("statusFormat"); this._progressFormat = us.getString("progressFormat"); this._progressFormatKBMB = us.getString("progressFormatKBMB"); this._progressFormatKBKB = us.getString("progressFormatKBKB"); this._progressFormatMBMB = us.getString("progressFormatMBMB"); this._progressFormatUnknownMB = us.getString("progressFormatUnknownMB"); this._progressFormatUnknownKB = us.getString("progressFormatUnknownKB"); this._rateFormat = us.getString("rateFormat"); this._rateFormatKBSec = us.getString("rateFormatKBSec"); this._rateFormatMBSec = us.getString("rateFormatMBSec"); this._timeFormat = us.getString("timeFormat"); this._longTimeFormat = us.getString("longTimeFormat"); this._shortTimeFormat = us.getString("shortTimeFormat"); this._remain = us.getString("remain"); this._unknownFilesize = us.getString("unknownFilesize"); } DownloadStatusFormatter.prototype = { /** * Time when the download started (in seconds since epoch) */ _startTime: 0, /** * Time elapsed since the start of the download operation (in seconds) */ _elapsed: -1, /** * Transfer rate of the download */ _rate: 0, /** * Transfer rate of the download, formatted as text */ _rateFormatted: "", /** * Transfer rate, formatted into text container */ _rateFormattedContainer: "", /** * Number of Kilobytes downloaded so far in the form: * 376KB of 9.3MB */ progress: "", /** * Format a human-readable status message based on the current download * progress. * @param currSize * The current number of bytes transferred * @param finalSize * The total number of bytes to be transferred * @returns A human readable status message, e.g. * "3.4 of 4.7MB; 01:15 remain" */ formatStatus: function(currSize, finalSize) { var now = Math.floor((new Date()).getTime() / 1000); // 1) Determine the Download Progress in Kilobytes var total = parseInt(finalSize/1024 + 0.5); this.progress = this._formatKBytes(parseInt(currSize/1024 + 0.5), total); var progress = this._replaceInsert(this._progressFormat, 1, this.progress); var rateFormatted = ""; // 2) Determine the Transfer Rate var oldElapsed = this._elapsed; this._elapsed = now - this._startTime; if (oldElapsed != this._elapsed) { this._rate = this._elapsed ? Math.floor((currSize / 1024) / this._elapsed) : 0; var isKB = true; if (parseInt(this._rate / 1024) > 0) { this._rate = (this._rate / 1024).toFixed(1); isKB = false; } if (this._rate > 100) this._rate = Math.round(this._rate); if (this._rate) { var format = isKB ? this._rateFormatKBSec : this._rateFormatMBSec; this._rateFormatted = this._replaceInsert(format, 1, this._rate); this._rateFormattedContainer = this._replaceInsert(" " + this._rateFormat, 1, this._rateFormatted); } } progress = this._replaceInsert(progress, 2, this._rateFormattedContainer); // 3) Determine the Time Remaining var remainingTime = ""; if (this._rate && (finalSize > 0)) { remainingTime = Math.floor(((finalSize - currSize) / 1024) / this._rate); remainingTime = this._formatSeconds(remainingTime); remainingTime = this._replaceInsert(this._timeFormat, 1, remainingTime) remainingTime = this._replaceInsert(remainingTime, 2, this._remain); } // // [statusFormat: // [progressFormat: // [[progressFormatKBKB| // progressFormatKBMB| // progressFormatMBMB| // progressFormatUnknownKB| // progressFormatUnknownMB // ][rateFormat]] // ][timeFormat] // ] var status = this._statusFormat; status = this._replaceInsert(status, 1, progress); status = this._replaceInsert(status, 2, remainingTime); return status; }, /** * Inserts a string into another string at the specified index, e.g. for * the format string var foo ="#1 #2 #3", |_replaceInsert(foo, 2, "test")| * returns "#1 test #3"; * @param format * The format string * @param index * The Index to insert into * @param value * The value to insert * @returns The string with the value inserted. */ _replaceInsert: function(format, index, value) { return format.replace(new RegExp("#" + index), value); }, /** * Formats progress in the form of kilobytes transfered vs. total to * transfer. * @param currentKB * The current amount of data transfered, in kilobytes. * @param totalKB * The total amount of data that must be transfered, in kilobytes. * @returns A string representation of the progress, formatted according to: * * KB totalKB returns * x, < 1MB y < 1MB x of y KB * x, < 1MB y >= 1MB x KB of y MB * x, >= 1MB y >= 1MB x of y MB */ _formatKBytes: function(currentKB, totalKB) { var progressHasMB = parseInt(currentKB / 1024) > 0; var totalHasMB = parseInt(totalKB / 1024) > 0; var format = ""; if (!progressHasMB && !totalHasMB) { if (!totalKB) { format = this._progressFormatUnknownKB; format = this._replaceInsert(format, 1, currentKB); } else { format = this._progressFormatKBKB; format = this._replaceInsert(format, 1, currentKB); format = this._replaceInsert(format, 2, totalKB); } } else if (progressHasMB && totalHasMB) { format = this._progressFormatMBMB; format = this._replaceInsert(format, 1, (currentKB / 1024).toFixed(1)); format = this._replaceInsert(format, 2, (totalKB / 1024).toFixed(1)); } else if (totalHasMB && !progressHasMB) { format = this._progressFormatKBMB; format = this._replaceInsert(format, 1, currentKB); format = this._replaceInsert(format, 2, (totalKB / 1024).toFixed(1)); } else if (progressHasMB && !totalHasMB) { format = this._progressFormatUnknownMB; format = this._replaceInsert(format, 1, (currentKB / 1024).toFixed(1)); } return format; }, /** * Formats a time in seconds into something human readable. * @param seconds * The time to format * @returns A human readable string representing the date. */ _formatSeconds: function(seconds) { // Determine number of hours/minutes/seconds var hours = (seconds - (seconds % 3600)) / 3600; seconds -= hours * 3600; var minutes = (seconds - (seconds % 60)) / 60; seconds -= minutes * 60; // Pad single digit values if (hours < 10) hours = "0" + hours; if (minutes < 10) minutes = "0" + minutes; if (seconds < 10) seconds = "0" + seconds; // Insert hours, minutes, and seconds into result string. var result = parseInt(hours) ? this._longTimeFormat : this._shortTimeFormat; result = this._replaceInsert(result, 1, hours); result = this._replaceInsert(result, 2, minutes); result = this._replaceInsert(result, 3, seconds); return result; } }; /** * The "Update is Downloading" page - provides feedback for the download * process plus a pause/resume UI */ var gDownloadingPage = { /** * DOM Elements */ _downloadName : null, _downloadStatus : null, _downloadProgress : null, _downloadThrobber : null, _pauseButton : null, /** * Label cache to hold the 'Connecting' string */ _label_downloadStatus : null, /** * An instance of the status formatter object */ _statusFormatter : null, get statusFormatter() { if (!this._statusFormatter) this._statusFormatter = new DownloadStatusFormatter(); return this._statusFormatter; }, /** * Initialize */ onPageShow: function() { this._downloadName = document.getElementById("downloadName"); this._downloadStatus = document.getElementById("downloadStatus"); this._downloadProgress = document.getElementById("downloadProgress"); this._downloadThrobber = document.getElementById("downloadThrobber"); this._pauseButton = document.getElementById("pauseButton"); this._label_downloadStatus = this._downloadStatus.textContent; var updates = Components.classes["@mozilla.org/updates/update-service;1"]. getService(Components.interfaces.nsIApplicationUpdateService); var um = Components.classes["@mozilla.org/updates/update-manager;1"]. getService(Components.interfaces.nsIUpdateManager); var activeUpdate = um.activeUpdate; if (activeUpdate) gUpdates.setUpdate(activeUpdate); if (!gUpdates.update) { LOG("UI:DownloadingPage", "onPageShow: no valid update to download?!"); return; } try { // Say that this was a foreground download, not a background download, // since the user cared enough to look in on this process. gUpdates.update.QueryInterface(Components.interfaces.nsIWritablePropertyBag); gUpdates.update.setProperty("foregroundDownload", "true"); // Pause any active background download and restart it as a foreground // download. updates.pauseDownload(); var state = updates.downloadUpdate(gUpdates.update, false); if (state == "failed") { // We've tried as hard as we could to download a valid update - // we fell back from a partial patch to a complete patch and even // then we couldn't validate. Show a validation error with instructions // on how to manually update. this.showVerificationError(); } else { // Add this UI as a listener for active downloads updates.addDownloadListener(this); } if (activeUpdate) this._setUIState(!updates.isDownloading); var link = document.getElementById("detailsLink"); link.href = gUpdates.update.detailsURL; } catch(ex) { LOG("UI:DownloadingPage", "onPageShow: " + ex); } gUpdates.setButtons(null, true, null, true, null, true, "hideButton", false, false, null, false, null, false); }, /** * Updates the text status message */ _setStatus: function(status) { // Don't bother setting the same text more than once. This can happen // due to the asynchronous behavior of the downloader. if (this._downloadStatus.textContent == status) return; while (this._downloadStatus.hasChildNodes()) this._downloadStatus.removeChild(this._downloadStatus.firstChild); this._downloadStatus.appendChild(document.createTextNode(status)); }, /** * Whether or not we are currently paused */ _paused : false, /** * Adjust UI to suit a certain state of paused-ness * @param paused * Whether or not the download is paused */ _setUIState: function(paused) { var u = gUpdates.update; if (paused) { if (this._downloadThrobber.hasAttribute("state")) this._downloadThrobber.removeAttribute("state"); if (this._downloadProgress.mode != "normal") this._downloadProgress.mode = "normal"; this._downloadName.value = gUpdates.strings.getFormattedString( "pausedName", [u.name]); this._pauseButton.label = gUpdates.strings.getString("pauseButtonResume"); var p = u.selectedPatch.QueryInterface(Components.interfaces.nsIPropertyBag); var status = p.getProperty("status"); if (status) this._setStatus(status); } else { if (!(this._downloadThrobber.hasAttribute("state") && (this._downloadThrobber.getAttribute("state") == "loading"))) this._downloadThrobber.setAttribute("state", "loading"); if (this._downloadProgress.mode != "undetermined") this._downloadProgress.mode = "undetermined"; this._downloadName.value = gUpdates.strings.getFormattedString( "downloadingPrefix", [u.name]); this._pauseButton.label = gUpdates.strings.getString("pauseButtonPause"); this._setStatus(this._label_downloadStatus); } }, /** * When the user clicks the Pause/Resume button */ onPause: function() { var updates = Components.classes["@mozilla.org/updates/update-service;1"]. getService(Components.interfaces.nsIApplicationUpdateService); if (this._paused) updates.downloadUpdate(gUpdates.update, false); else { var patch = gUpdates.update.selectedPatch; patch.QueryInterface(Components.interfaces.nsIWritablePropertyBag); patch.setProperty("status", gUpdates.strings.getFormattedString("pausedStatus", [this.statusFormatter.progress])); updates.pauseDownload(); } this._paused = !this._paused; // Update the UI this._setUIState(this._paused); }, /** * When the user closes the Wizard UI (including by clicking the Hide button) */ onWizardCancel: function() { // Remove ourself as a download listener so that we don't continue to be // fed progress and state notifications after the UI we're updating has // gone away. var updates = Components.classes["@mozilla.org/updates/update-service;1"]. getService(Components.interfaces.nsIApplicationUpdateService); updates.removeDownloadListener(this); var um = Components.classes["@mozilla.org/updates/update-manager;1"] .getService(Components.interfaces.nsIUpdateManager); um.activeUpdate = gUpdates.update; // If the download was paused by the user, ask the user if they want to // have the update resume in the background. var downloadInBackground = true; if (this._paused) { var title = gUpdates.strings.getString("resumePausedAfterCloseTitle"); var message = gUpdates.strings.getFormattedString( "resumePausedAfterCloseMessage", [gUpdates.brandName]); var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] .getService(Components.interfaces.nsIPromptService); var flags = ps.STD_YES_NO_BUTTONS; // focus the software update wizard before prompting. // this will raise the software update wizard if it is minimized // making it more obvious what the prompt is for and will // solve the problem of windows "obscuring" the prompt. // see bug #350299 for more details window.focus(); var rv = ps.confirmEx(window, title, message, flags, null, null, null, null, { }); if (rv == 1) { downloadInBackground = false; } } if (downloadInBackground) { // Continue download in the background at full speed. LOG("UI:DownloadingPage", "onWizardCancel: continuing download in background at full speed"); updates.downloadUpdate(gUpdates.update, false); } }, /** * When the data transfer begins * @param request * The nsIRequest object for the transfer * @param context * Additional data */ onStartRequest: function(request, context) { request.QueryInterface(nsIIncrementalDownload); LOG("UI:DownloadingPage", "onStartRequest: " + request.URI.spec); // This !paused test is necessary because onStartRequest may fire after // the download was paused (for those speedy clickers...) if (this._paused) return; if (!(this._downloadThrobber.hasAttribute("state") && (this._downloadThrobber.getAttribute("state") == "loading"))) this._downloadThrobber.setAttribute("state", "loading"); if (this._downloadProgress.mode != "undetermined") this._downloadProgress.mode = "undetermined"; this._setStatus(this._label_downloadStatus); }, /** * When new data has been downloaded * @param request * The nsIRequest object for the transfer * @param context * Additional data * @param progress * The current number of bytes transferred * @param maxProgress * The total number of bytes that must be transferred */ onProgress: function(request, context, progress, maxProgress) { request.QueryInterface(nsIIncrementalDownload); LOG("UI:DownloadingPage.onProgress", " " + request.URI.spec + ", " + progress + "/" + maxProgress); var name = gUpdates.strings.getFormattedString("downloadingPrefix", [gUpdates.update.name]); var status = this.statusFormatter.formatStatus(progress, maxProgress); var progress = Math.round(100 * (progress/maxProgress)); var p = gUpdates.update.selectedPatch; p.QueryInterface(Components.interfaces.nsIWritablePropertyBag); p.setProperty("progress", progress); p.setProperty("status", status); // This !paused test is necessary because onProgress may fire after // the download was paused (for those speedy clickers...) if (this._paused) return; if (!(this._downloadThrobber.hasAttribute("state") && (this._downloadThrobber.getAttribute("state") == "loading"))) this._downloadThrobber.setAttribute("state", "loading"); if (this._downloadProgress.mode != "normal") this._downloadProgress.mode = "normal"; this._downloadProgress.value = progress; this._pauseButton.disabled = false; this._downloadName.value = name; this._setStatus(status); }, /** * When we have new status text * @param request * The nsIRequest object for the transfer * @param context * Additional data * @param status * A status code * @param statusText * Human readable version of |status| */ onStatus: function(request, context, status, statusText) { request.QueryInterface(nsIIncrementalDownload); LOG("UI:DownloadingPage", "onStatus: " + request.URI.spec + " status = " + status + ", text = " + statusText); this._setStatus(statusText); }, /** * When data transfer ceases * @param request * The nsIRequest object for the transfer * @param context * Additional data * @param status * Status code containing the reason for the cessation. */ onStopRequest: function(request, context, status) { request.QueryInterface(nsIIncrementalDownload); LOG("UI:DownloadingPage", "onStopRequest: " + request.URI.spec + ", status = " + status); if (this._downloadThrobber.hasAttribute("state")) this._downloadThrobber.removeAttribute("state"); if (this._downloadProgress.mode != "normal") this._downloadProgress.mode = "normal"; var u = gUpdates.update; const NS_BINDING_ABORTED = 0x804b0002; switch (status) { case Components.results.NS_ERROR_UNEXPECTED: if (u.selectedPatch.state == STATE_DOWNLOAD_FAILED && u.isCompleteUpdate) { // Verification error of complete patch, informational text is held in // the update object. gUpdates.wiz.currentPage = document.getElementById("errors"); } else { // Verification failed for a partial patch, complete patch is now // downloading so return early and do NOT remove the download listener! // Reset the progress meter to "undertermined" mode so that we don't // show old progress for the new download of the "complete" patch. this._downloadProgress.mode = "undetermined"; this._pauseButton.disabled = true; var verificationFailed = document.getElementById("verificationFailed"); verificationFailed.hidden = false; this._statusFormatter = null; return; } break; case NS_BINDING_ABORTED: LOG("UI:DownloadingPage", "onStopRequest: Pausing Download"); // Return early, do not remove UI listener since the user may resume // downloading again. return; case Components.results.NS_OK: LOG("UI:DownloadingPage", "onStopRequest: Patch Verification Succeeded"); gUpdates.wiz.canAdvance = true; gUpdates.wiz.advance(); break; default: LOG("UI:DownloadingPage", "onStopRequest: Transfer failed"); // Some kind of transfer error, die. gUpdates.wiz.currentPage = document.getElementById("errors"); break; } var updates = Components.classes["@mozilla.org/updates/update-service;1"]. getService(Components.interfaces.nsIApplicationUpdateService); updates.removeDownloadListener(this); }, /** * See nsISupports.idl */ QueryInterface: function(iid) { if (!iid.equals(Components.interfaces.nsIRequestObserver) && !iid.equals(Components.interfaces.nsIProgressEventSink) && !iid.equals(Components.interfaces.nsISupports)) throw Components.results.NS_ERROR_NO_INTERFACE; return this; } }; /** * The "There was an error during the update" page. */ var gErrorsPage = { /** * Initialize */ onPageShow: function() { gUpdates.setButtons(null, true, null, true, null, false, "hideButton", true, false, null, false, null, false); gUpdates.wiz.getButton("finish").focus(); var errorReason = document.getElementById("errorReason"); errorReason.value = gUpdates.update.statusText; var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"] .getService(Components.interfaces.nsIURLFormatter); var manualURL = formatter.formatURLPref(PREF_UPDATE_MANUAL_URL); var errorLinkLabel = document.getElementById("errorLinkLabel"); errorLinkLabel.value = manualURL; errorLinkLabel.href = manualURL; }, /** * Initialize, for the "Error Applying Patch" case. */ onPageShowPatching: function() { gUpdates.wiz.getButton("back").disabled = true; gUpdates.wiz.getButton("cancel").disabled = true; gUpdates.wiz.getButton("next").focus(); }, /** * Finish button clicked. */ onWizardFinish: function() { // XXXjwalden COMPLETE AND TOTAL HACK!!! // // The problem the following code is working around is this: the update // service's API for responding to updates is poor. Essentially, all // the information we can get is that we've started to request a download or // that the download we've started has finished, with minimal details about // how it finished which aren't described in the API (and which internally // are not entirely useful, either -- mostly unuseful nsresults). How // do you signal the difference between "this download failed" and "all // downloads failed", particularly if you aim for API compatibility? The // code in nsApplicationUpdateService only determines the difference *after* // the current request is stopped, and since the subsequent second call to // downloadUpdate doesn't start/stop a request, the download listener is // never notified and whatever code was relying on it just fails without // notification. The consequence of this is that it's impossible to // properly remove the download listener. // // The code before this patch tried to do the exit after all downloads // failed but was missing a QueryInterface to work; with it, making sure // that the download listener is removed in all cases, including in the case // where the last onStopRequest corresponds to *a* failed download instead // of to *all* failed downloads, simply means that we have to try to remove // that listener in the error page spawned by the update service. If there // were time and API compat weren't a problem, we'd add an onFinish handler // or something which could signal exactly what happened and not overload // onStopRequest, but there isn't, so we can't. // var updates = Components.classes["@mozilla.org/updates/update-service;1"]. getService(Components.interfaces.nsIApplicationUpdateService); updates.removeDownloadListener(gDownloadingPage); } }; /** * The "Update has been downloaded" page. Shows information about what * was downloaded. */ var gFinishedPage = { /** * Called to initialize the Wizard Page. * @param aDelayRestart * true if the Restart Application button should be disabled for a * short delay */ onPageShow: function(aDelayRestart) { gUpdates.setButtons(null, true, null, true, "restartButton", aDelayRestart, "laterButton", false, false, null, false, null, false); if (aDelayRestart) setTimeout(this._enableRestartButton, 2000); else gUpdates.wiz.getButton("finish").focus(); }, /** * Called to initialize the Wizard Page. (Background Source Event) */ onPageShowBackground: function() { var finishedBackground = document.getElementById("finishedBackground"); finishedBackground.setAttribute("label", gUpdates.strings.getFormattedString( "updateReadyToInstallHeader", [gUpdates.update.name])); // XXXben - wizard should give us a way to set the page header. gUpdates.wiz._adjustWizardHeader(); var updateFinishedName = document.getElementById("updateFinishedName"); updateFinishedName.value = gUpdates.update.name; var link = document.getElementById("finishedBackgroundLink"); link.href = gUpdates.update.detailsURL; this.onPageShow(true); if (getPref("getBoolPref", PREF_UPDATE_TEST_LOOP, false)) { window.restart = function () { gUpdates.wiz.getButton("finish").click(); } setTimeout("restart();", UPDATE_TEST_LOOP_INTERVAL); } }, /** * Re-enables and focuses the Restart Application button */ _enableRestartButton: function() { gUpdates.wiz.canAdvance = true; var finishButton = gUpdates.wiz.getButton("finish"); finishButton.disabled = false; finishButton.focus(); }, /** * Called when the wizard finishes, i.e. the "Restart Now" button is * clicked. */ onWizardFinish: function() { // Do the restart LOG("UI:FinishedPage" , "onWizardFinish: Restarting Application..."); // disable the "finish" (Restart) and "cancel" (Later) buttons // because the Software Update wizard is still up at the point, // and will remain up until we return and we close the // window with a |window.close()| in wizard.xml // (it was the firing the "wizardfinish" event that got us here.) // This prevents the user from switching back // to the Software Update dialog and clicking "Restart" or "Later" // when dealing with the "confirm close" prompts. // See bug #350299 for more details. gUpdates.wiz.getButton("finish").disabled = true; gUpdates.wiz.getButton("cancel").disabled = true; // This process is *extremely* retarded. There should be some nice // integrated system for determining whether or not windows are allowed // to close or not, and what happens when that happens. We need to // jump through all these hoops (duplicated from globalOverlay.js) to // ensure that various window types (browser, downloads, etc) all // allow the app to shut down. // bsmedberg? // Notify all windows that an application quit has been requested. var os = Components.classes["@mozilla.org/observer-service;1"] .getService(Components.interfaces.nsIObserverService); var cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]. createInstance(Components.interfaces.nsISupportsPRBool); os.notifyObservers(cancelQuit, "quit-application-requested", null); // Something aborted the quit process. if (cancelQuit.data) return; // Notify all windows that an application quit has been granted. os.notifyObservers(null, "quit-application-granted", null); // Enumerate all windows and call shutdown handlers var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]. getService(Components.interfaces.nsIWindowMediator); var windows = wm.getEnumerator(null); while (windows.hasMoreElements()) { var win = windows.getNext(); if (("tryToClose" in win) && !win.tryToClose()) return; } var appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]. getService(Components.interfaces.nsIAppStartup); appStartup.quit(appStartup.eAttemptQuit | appStartup.eRestart); }, /** * Called when the wizard is canceled, i.e. when the "Later" button is * clicked. */ onWizardCancel: function() { var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] .getService(Components.interfaces.nsIPromptService); var message = gUpdates.strings.getFormattedString("restartLaterMsg", [gUpdates.brandName]); ps.alert(window, gUpdates.strings.getString("restartLaterTitle"), message); var interval = getPref("getIntPref", PREF_UPDATE_NAGTIMER_RESTART, 18000000); gUpdates.registerNagTimer("restart-nag-timer", interval, "showUpdateComplete"); }, }; /** * The "Update was Installed Successfully" page. */ var gInstalledPage = { /** * Initialize */ onPageShow: function() { var ai = Components.classes["@mozilla.org/xre/app-info;1"]. getService(Components.interfaces.nsIXULAppInfo); var branding = document.getElementById("brandStrings"); try { var url = branding.getFormattedString("whatsNewURL", [ai.version]); var whatsnewLink = document.getElementById("whatsnewLink"); whatsnewLink.href = url; whatsnewLink.hidden = false; } catch (e) { } gUpdates.setButtons(null, true, null, true, null, false, "hideButton", true, false, null, false, null, false); gUpdates.wiz.getButton("finish").focus(); }, }; /** * Called as the application shuts down due to being quit from the File->Quit * menu item. * XXXben this API is retarded. */ function tryToClose() { var cp = gUpdates.wiz.currentPage; if (cp.pageid != "finished" && cp.pageid != "finishedBackground") gUpdates.onWizardCancel(); return true; } /** * Callback for the Update Prompt to set the current page if an Update Wizard * window is already found to be open. * @param pageid * The ID of the page to switch to */ function setCurrentPage(pageid) { gUpdates.wiz.currentPage = document.getElementById(pageid); }