home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / lib / xulrunner-1.9.0.14 / chrome / toolkit.jar / content / mozapps / update / updates.js < prev    next >
Encoding:
Text File  |  2008-10-10  |  60.5 KB  |  1,716 lines

  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is the Update Service.
  16.  *
  17.  * The Initial Developer of the Original Code is Google Inc.
  18.  * Portions created by the Initial Developer are Copyright (C) 2005
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *  Ben Goodger <ben@mozilla.org> (Original Author)
  23.  *  Asaf Romano <mozilla.mano@sent.com>
  24.  *  Jeff Walden <jwalden+code@mit.edu>
  25.  *
  26.  * Alternatively, the contents of this file may be used under the terms of
  27.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  28.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  29.  * in which case the provisions of the GPL or the LGPL are applicable instead
  30.  * of those above. If you wish to allow use of your version of this file only
  31.  * under the terms of either the GPL or the LGPL, and not to allow others to
  32.  * use your version of this file under the terms of the MPL, indicate your
  33.  * decision by deleting the provisions above and replace them with the notice
  34.  * and other provisions required by the GPL or the LGPL. If you do not delete
  35.  * the provisions above, a recipient may use your version of this file under
  36.  * the terms of any one of the MPL, the GPL or the LGPL.
  37.  *
  38.  * ***** END LICENSE BLOCK ***** */
  39.  
  40. const nsIUpdateItem           = Components.interfaces.nsIUpdateItem;
  41. const nsIIncrementalDownload  = Components.interfaces.nsIIncrementalDownload;
  42.  
  43. const XMLNS_XUL               = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  44.  
  45. const PREF_UPDATE_MANUAL_URL        = "app.update.url.manual";
  46. const PREF_UPDATE_NAGTIMER_RESTART  = "app.update.nagTimer.restart";
  47. const PREF_APP_UPDATE_LOG_BRANCH    = "app.update.log.";
  48. const PREF_UPDATE_TEST_LOOP         = "app.update.test.loop";
  49. const PREF_UPDATE_NEVER_BRANCH      = "app.update.never.";
  50.  
  51. const UPDATE_TEST_LOOP_INTERVAL     = 2000;
  52.  
  53. const URI_UPDATES_PROPERTIES  = "chrome://mozapps/locale/update/updates.properties";
  54.  
  55. const STATE_DOWNLOADING       = "downloading";
  56. const STATE_PENDING           = "pending";
  57. const STATE_APPLYING          = "applying";
  58. const STATE_SUCCEEDED         = "succeeded";
  59. const STATE_DOWNLOAD_FAILED   = "download-failed";
  60. const STATE_FAILED            = "failed";
  61.  
  62. const SRCEVT_FOREGROUND       = 1;
  63. const SRCEVT_BACKGROUND       = 2;
  64.  
  65. var gConsole    = null;
  66. var gPref       = null;
  67. var gLogEnabled = { };
  68.  
  69. /**
  70.  * Logs a string to the error console.
  71.  * @param   string
  72.  *          The string to write to the error console..
  73.  */
  74. function LOG(module, string) {
  75.   if (module in gLogEnabled || "all" in gLogEnabled) {
  76.     dump("*** " + module + ":" + string + "\n");
  77.     gConsole.logStringMessage(string);
  78.   }
  79. }
  80.  
  81. /**
  82.  * Gets a preference value, handling the case where there is no default.
  83.  * @param   func
  84.  *          The name of the preference function to call, on nsIPrefBranch
  85.  * @param   preference
  86.  *          The name of the preference
  87.  * @param   defaultValue
  88.  *          The default value to return in the event the preference has
  89.  *          no setting
  90.  * @returns The value of the preference, or undefined if there was no
  91.  *          user or default value.
  92.  */
  93. function getPref(func, preference, defaultValue) {
  94.   try {
  95.     return gPref[func](preference);
  96.   }
  97.   catch (e) {
  98.     LOG("General", "Failed to get preference " + preference);
  99.   }
  100.   return defaultValue;
  101. }
  102.  
  103. /**
  104.  * A set of shared data and control functions for the wizard as a whole.
  105.  */
  106. var gUpdates = {
  107.   /**
  108.    * The nsIUpdate object being used by this window (either for downloading,
  109.    * notification or both).
  110.    */
  111.   update: null,
  112.  
  113.   /**
  114.    * The updates.properties <stringbundle> element.
  115.    */
  116.   strings: null,
  117.  
  118.   /**
  119.    * The Application brandShortName (e.g. "Firefox")
  120.    */
  121.   brandName: null,
  122.  
  123.   /**
  124.    * The <wizard> element
  125.    */
  126.   wiz: null,
  127.  
  128.   /**
  129.    * Helper function for setButtons
  130.    * Resets button to original label & accesskey if string is null.
  131.    */
  132.   _setButton: function(button, string) {
  133.     if (string) {
  134.       var label = this.strings.getString(string);
  135.       if (label.indexOf("%S") != -1)
  136.         label = label.replace(/%S/, this.brandName);
  137.       button.label = label;
  138.       button.setAttribute("accesskey",
  139.                           this.strings.getString(string + ".accesskey"));
  140.     } else {
  141.       button.label = button.defaultLabel;
  142.       button.setAttribute("accesskey", button.defaultAccesskey);
  143.     }
  144.   },
  145.  
  146.   /**
  147.    * Set the state for the Wizard's control buttons (labels and disabled
  148.    * state).
  149.    * @param   backButtonString
  150.    *          The property in the stringbundle containing the label
  151.    *          to put on the Back button, or null for default.
  152.    * @param   backButtonDisabled
  153.    *          true if the Back button should be disabled, false otherwise
  154.    * @param   nextButtonString
  155.    *          The property in the stringbundle containing the label
  156.    *          to put on the Next button, or null for default.
  157.    * @param   nextButtonDisabled
  158.    *          true if the Next button should be disabled, false otherwise
  159.    * @param   finishButtonString
  160.    *          The property in the stringbundle containing the label
  161.    *          to put on the Finish button, or null for default.
  162.    * @param   finishButtonDisabled
  163.    *          true if the Finish button should be disabled, false otherwise
  164.    * @param   cancelButtonString
  165.    *          The property in the stringbundle containing the label
  166.    *          to put on the Cancel button, or null for default.
  167.    * @param   cancelButtonDisabled
  168.    *          true if the Cancel button should be disabled, false otherwise
  169.    * @param   hideBackAndCancelButtons
  170.    *          true if the Cancel button should be hidden, false otherwise
  171.    * @param   extraButton1String
  172.    *          The property in the stringbundle containing the label to put
  173.    *          on the first Extra button, if present, or null for default.
  174.    * @param   extraButton1Disabled
  175.    *          true if the first Extra button should be disabled,
  176.    *          false otherwise
  177.    * @param   extraButton2String
  178.    *          The property in the stringbundle containing the label to put
  179.    *          on the second Extra button, if present, or null for default.
  180.    * @param   extraButton2Disabled
  181.    *          true if the second Extra button should be disabled,
  182.    *          false otherwise
  183.    */
  184.   setButtons: function(backButtonString, backButtonDisabled,
  185.                        nextButtonString, nextButtonDisabled,
  186.                        finishButtonString, finishButtonDisabled,
  187.                        cancelButtonString, cancelButtonDisabled,
  188.                        hideBackAndCancelButtons,
  189.                        extraButton1String, extraButton1Disabled,
  190.                        extraButton2String, extraButton2Disabled) {
  191.     var bb = this.wiz.getButton("back");
  192.     var bn = this.wiz.getButton("next");
  193.     var bf = this.wiz.getButton("finish");
  194.     var bc = this.wiz.getButton("cancel");
  195.     var be1 = this.wiz.getButton("extra1");
  196.     var be2 = this.wiz.getButton("extra2");
  197.  
  198.     this._setButton(bb, backButtonString);
  199.     this._setButton(bn, nextButtonString);
  200.     this._setButton(bf, finishButtonString);
  201.     this._setButton(bc, cancelButtonString);
  202.     this._setButton(be1, extraButton1String);
  203.     this._setButton(be2, extraButton2String);
  204.  
  205.     // update button state using the wizard commands
  206.     this.wiz.canRewind  = !backButtonDisabled;
  207.     // The Finish and Next buttons are never exposed at the same time
  208.     if (this.wiz.onLastPage)
  209.       this.wiz.canAdvance = !finishButtonDisabled;
  210.     else
  211.       this.wiz.canAdvance = !nextButtonDisabled;
  212.  
  213.     bf.disabled = finishButtonDisabled;
  214.     bc.disabled = cancelButtonDisabled;
  215.     be1.disabled = extraButton1Disabled;
  216.     be2.disabled = extraButton2Disabled;
  217.  
  218.     // Show or hide the cancel and back buttons, since the first Extra
  219.     // button does this job.
  220.     bc.hidden   = hideBackAndCancelButtons;
  221.     bb.hidden   = hideBackAndCancelButtons;
  222.  
  223.     // Show or hide the extra buttons
  224.     be1.hidden = extraButton1String == null;
  225.     be2.hidden = extraButton2String == null;
  226.   },
  227.  
  228.   never: function () {
  229.     // if the user hits "Never", we should not prompt them about this
  230.     // major version again, unless they manually do "Check for Updates..."
  231.     // which, will clear the "never" pref for the version presented
  232.     // so that if they do "Later", we will remind them later.
  233.     //
  234.     // fix for bug #359093
  235.     // version might one day come back from AUS as an
  236.     // arbitrary (and possibly non ascii) string, so we need to encode it
  237.     var neverPrefName = PREF_UPDATE_NEVER_BRANCH + encodeURIComponent(gUpdates.update.version);
  238.     gPref.setBoolPref(neverPrefName, true);
  239.     this.wiz.cancel();
  240.   },
  241.  
  242.   later: function () {
  243.     // The user said "Later", so close the wizard
  244.     this.wiz.cancel();
  245.   },
  246.  
  247.   /**
  248.    * A hash of |pageid| attribute to page object. Can be used to dispatch
  249.    * function calls to the appropriate page.
  250.    */
  251.   _pages: { },
  252.  
  253.   /**
  254.    * Called when the user presses the "Finish" button on the wizard, dispatches
  255.    * the function call to the selected page.
  256.    */
  257.   onWizardFinish: function() {
  258.     var pageid = document.documentElement.currentPage.pageid;
  259.     if ("onWizardFinish" in this._pages[pageid])
  260.       this._pages[pageid].onWizardFinish();
  261.   },
  262.  
  263.   /**
  264.    * Called when the user presses the "Cancel" button on the wizard, dispatches
  265.    * the function call to the selected page.
  266.    */
  267.   onWizardCancel: function() {
  268.     var pageid = document.documentElement.currentPage.pageid;
  269.     if ("onWizardCancel" in this._pages[pageid])
  270.       this._pages[pageid].onWizardCancel();
  271.   },
  272.  
  273.   /**
  274.    * Called when the user presses the "Next" button on the wizard, dispatches
  275.    * the function call to the selected page.
  276.    */
  277.   onWizardNext: function() {
  278.     var cp = document.documentElement.currentPage;
  279.     if (!cp)
  280.       return;
  281.     var pageid = cp.pageid;
  282.     if ("onWizardNext" in this._pages[pageid])
  283.       this._pages[pageid].onWizardNext();
  284.   },
  285.  
  286.   /**
  287.    * The checking process that spawned this update UI. There are two types:
  288.    * SRCEVT_FOREGROUND:
  289.    *   Some user-generated event caused this UI to appear, e.g. the Help
  290.    *   menu item or the button in preferences. When in this mode, the UI
  291.    *   should remain active for the duration of the download.
  292.    * SRCEVT_BACKGROUND:
  293.    *   A background update check caused this UI to appear, probably because
  294.    *   incompatibilities in Extensions or other addons were discovered and
  295.    *   the user's consent to continue was required. When in this mode, the
  296.    *   UI will disappear after the user's consent is obtained.
  297.    */
  298.   sourceEvent: SRCEVT_FOREGROUND,
  299.  
  300.   /**
  301.    * The global error message - the reason the update failed. This is human
  302.    * readable text, used to initialize the error page.
  303.    */
  304.   errorMessage: "",
  305.  
  306.   /**
  307.    * Helper function for onLoad
  308.    * Saves default button label & accesskey for use by _setButton
  309.    */
  310.   _cacheButtonStrings: function (buttonName) {
  311.     var button = this.wiz.getButton(buttonName);
  312.     button.defaultLabel = button.label;
  313.     button.defaultAccesskey = button.getAttribute("accesskey");
  314.   },
  315.  
  316.   /**
  317.    * Called when the wizard UI is loaded.
  318.    */
  319.   onLoad: function() {
  320.     this.wiz = document.documentElement;
  321.  
  322.     gPref = Components.classes["@mozilla.org/preferences-service;1"]
  323.                       .getService(Components.interfaces.nsIPrefBranch2);
  324.     gConsole = Components.classes["@mozilla.org/consoleservice;1"]
  325.                          .getService(Components.interfaces.nsIConsoleService);
  326.     this._initLoggingPrefs();
  327.  
  328.     this.strings = document.getElementById("updateStrings");
  329.     var brandStrings = document.getElementById("brandStrings");
  330.     this.brandName = brandStrings.getString("brandShortName");
  331.  
  332.     var pages = gUpdates.wiz.childNodes;
  333.     for (var i = 0; i < pages.length; ++i) {
  334.       var page = pages[i];
  335.       if (page.localName == "wizardpage")
  336.         this._pages[page.pageid] = eval(page.getAttribute("object"));
  337.     }
  338.  
  339.     // Cache the standard button labels in case we need to restore them
  340.     this._cacheButtonStrings("back");
  341.     this._cacheButtonStrings("next");
  342.     this._cacheButtonStrings("finish");
  343.     this._cacheButtonStrings("cancel");
  344.     this._cacheButtonStrings("extra1");
  345.     this._cacheButtonStrings("extra2");
  346.  
  347.     // Advance to the Start page.
  348.     gUpdates.wiz.currentPage = this.startPage;
  349.   },
  350.  
  351.   /**
  352.    * Initialize Logging preferences, formatted like so:
  353.    *  app.update.log.<moduleName> = <true|false>
  354.    */
  355.   _initLoggingPrefs: function() {
  356.     try {
  357.       var ps = Components.classes["@mozilla.org/preferences-service;1"]
  358.                         .getService(Components.interfaces.nsIPrefService);
  359.       var logBranch = ps.getBranch(PREF_APP_UPDATE_LOG_BRANCH);
  360.       var modules = logBranch.getChildList("", { value: 0 });
  361.  
  362.       for (var i = 0; i < modules.length; ++i) {
  363.         if (logBranch.prefHasUserValue(modules[i]))
  364.           gLogEnabled[modules[i]] = logBranch.getBoolPref(modules[i]);
  365.       }
  366.     }
  367.     catch (e) {
  368.     }
  369.   },
  370.  
  371.   /**
  372.    * Return the <wizardpage> object that should be displayed first.
  373.    *
  374.    * This is determined by how we were called by the update prompt:
  375.    *
  376.    * U'Prompt Method:     Arg0:         Update State: Src Event:  p'Failed: Result:
  377.    * showUpdateAvailable  nsIUpdate obj --            background  --        updatesfound
  378.    * showUpdateDownloaded nsIUpdate obj pending       background  --        finishedBackground
  379.    * showUpdateInstalled  nsIUpdate obj succeeded     either      --        installed
  380.    * showUpdateError      nsIUpdate obj failed        either      partial   errorpatching
  381.    * showUpdateError      nsIUpdate obj failed        either      complete  errors
  382.    * checkForUpdates      null          --            foreground  --        checking
  383.    * checkForUpdates      null          downloading   foreground  --        downloading
  384.    */
  385.   get startPage() {
  386.     if (window.arguments) {
  387.       var arg0 = window.arguments[0];
  388.       if (arg0 instanceof Components.interfaces.nsIUpdate) {
  389.         // If the first argument is a nsIUpdate object, we are notifying the
  390.         // user that the background checking found an update that requires
  391.         // their permission to install, and it's ready for download.
  392.         this.setUpdate(arg0);
  393.         var p = this.update.selectedPatch;
  394.         if (p) {
  395.           var state = p.state;
  396.           if (state == STATE_DOWNLOADING) {
  397.             var patchFailed = false;
  398.             try {
  399.               patchFailed = this.update.getProperty("patchingFailed");
  400.             }
  401.             catch (e) {
  402.             }
  403.             if (patchFailed == "partial") {
  404.               // If the system failed to apply the partial patch, show the
  405.               // screen which best describes this condition, which is triggered
  406.               // by the |STATE_FAILED| state.
  407.               state = STATE_FAILED;
  408.             }
  409.             else if (patchFailed == "complete") {
  410.               // Otherwise, if the complete patch failed, which is far less
  411.               // likely, show the error text held by the update object in the
  412.               // generic errors page, triggered by the |STATE_DOWNLOAD_FAILED|
  413.               // state.
  414.               state = STATE_DOWNLOAD_FAILED;
  415.             }
  416.           }
  417.  
  418.           // Now select the best page to start with, given the current state of
  419.           // the Update.
  420.           switch (state) {
  421.           case STATE_PENDING:
  422.             this.sourceEvent = SRCEVT_BACKGROUND;
  423.             return document.getElementById("finishedBackground");
  424.           case STATE_SUCCEEDED:
  425.             return document.getElementById("installed");
  426.           case STATE_DOWNLOADING:
  427.             return document.getElementById("downloading");
  428.           case STATE_FAILED:
  429.             window.getAttention();
  430.             return document.getElementById("errorpatching");
  431.           case STATE_DOWNLOAD_FAILED:
  432.           case STATE_APPLYING:
  433.             return document.getElementById("errors");
  434.           }
  435.         }
  436.         return document.getElementById("updatesfound");
  437.       }
  438.     }
  439.     else {
  440.       var um =
  441.           Components.classes["@mozilla.org/updates/update-manager;1"].
  442.           getService(Components.interfaces.nsIUpdateManager);
  443.       if (um.activeUpdate) {
  444.         this.setUpdate(um.activeUpdate);
  445.         return document.getElementById("downloading");
  446.       }
  447.     }
  448.     return document.getElementById("checking");
  449.   },
  450.  
  451.   /**
  452.    * Sets the Update object for this wizard
  453.    * @param   update
  454.    *          The update object
  455.    */
  456.   setUpdate: function(update) {
  457.     this.update = update;
  458.     if (this.update)
  459.       this.update.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
  460.   },
  461.  
  462.   /**
  463.    * Registers a timer to nag the user about something relating to update
  464.    * @param   timerID
  465.    *          The ID of the timer to register, used for persistence
  466.    * @param   timerInterval
  467.    *          The interval of the timer
  468.    * @param   methodName
  469.    *          The method to call on the Update Prompter when the timer fires
  470.    */
  471.   registerNagTimer: function(timerID, timerInterval, methodName) {
  472.     // Remind the user to restart their browser in a little bit.
  473.     var tm =
  474.         Components.classes["@mozilla.org/updates/timer-manager;1"].
  475.         getService(Components.interfaces.nsIUpdateTimerManager);
  476.  
  477.     /**
  478.      * An object implementing nsITimerCallback that uses the Update Prompt
  479.      * component to notify the user about some event relating to app update
  480.      * that they should take action on.
  481.      * @param   update
  482.      *          The nsIUpdate object in question
  483.      * @param   methodName
  484.      *          The name of the method on the Update Prompter that should be
  485.      *          called
  486.      * @constructor
  487.      */
  488.     function Callback(update, methodName) {
  489.       this._update = update;
  490.       this._methodName = methodName;
  491.       this._prompter =
  492.         Components.classes["@mozilla.org/updates/update-prompt;1"].
  493.         createInstance(Components.interfaces.nsIUpdatePrompt);
  494.     }
  495.     Callback.prototype = {
  496.       /**
  497.        * The Update we should nag about downloading
  498.        */
  499.       _update: null,
  500.  
  501.       /**
  502.        * The Update prompter we can use to notify the user
  503.        */
  504.       _prompter: null,
  505.  
  506.       /**
  507.        * The method on the update prompt that should be called
  508.        */
  509.       _methodName: "",
  510.  
  511.       /**
  512.        * Called when the timer fires. Notifies the user about whichever event
  513.        * they need to be nagged about (e.g. update available, please restart,
  514.        * etc).
  515.        */
  516.       notify: function(timerCallback) {
  517.         if (methodName in this._prompter)
  518.           this._prompter[methodName](null, this._update);
  519.       }
  520.     }
  521.     tm.registerTimer(timerID, (new Callback(gUpdates.update, methodName)),
  522.                      timerInterval);
  523.   }
  524. }
  525.  
  526. /**
  527.  * The "Checking for Updates" page. Provides feedback on the update checking
  528.  * process.
  529.  */
  530. var gCheckingPage = {
  531.   /**
  532.    * The nsIUpdateChecker that is currently checking for updates. We hold onto
  533.    * this so we can cancel the update check if the user closes the window.
  534.    */
  535.   _checker: null,
  536.  
  537.   /**
  538.    * Starts the update check when the page is shown.
  539.    */
  540.   onPageShow: function() {
  541.     gUpdates.setButtons(null, true, null, true, null, true,
  542.                         null, false, false, null,
  543.                         false, null, false);
  544.     this._checker =
  545.       Components.classes["@mozilla.org/updates/update-checker;1"].
  546.       createInstance(Components.interfaces.nsIUpdateChecker);
  547.     this._checker.checkForUpdates(this.updateListener, true);
  548.   },
  549.  
  550.   /**
  551.    * The user has closed the window, either by pressing cancel or using a Window
  552.    * Manager control, so stop checking for updates.
  553.    */
  554.   onWizardCancel: function() {
  555.     if (this._checker) {
  556.       const nsIUpdateChecker = Components.interfaces.nsIUpdateChecker;
  557.       this._checker.stopChecking(nsIUpdateChecker.CURRENT_CHECK);
  558.     }
  559.   },
  560.  
  561.   /**
  562.    * An object implementing nsIUpdateCheckListener that is notified as the
  563.    * update check commences.
  564.    */
  565.   updateListener: {
  566.     /**
  567.      * See nsIUpdateCheckListener
  568.      */
  569.     onProgress: function(request, position, totalSize) {
  570.       var pm = document.getElementById("checkingProgress");
  571.       checkingProgress.setAttribute("mode", "normal");
  572.       checkingProgress.setAttribute("value", Math.floor(100 * (position/totalSize)));
  573.     },
  574.  
  575.     /**
  576.      * See nsIUpdateCheckListener
  577.      */
  578.     onCheckComplete: function(request, updates, updateCount) {
  579.       var aus = Components.classes["@mozilla.org/updates/update-service;1"]
  580.                           .getService(Components.interfaces.nsIApplicationUpdateService);
  581.       gUpdates.setUpdate(aus.selectUpdate(updates, updates.length));
  582.       if (!gUpdates.update) {
  583.         LOG("UI:CheckingPage",
  584.             "Could not select an appropriate update, either because there " +
  585.             "were none, or |selectUpdate| failed.");
  586.         var checking = document.getElementById("checking");
  587.         checking.setAttribute("next", "noupdatesfound");
  588.       }
  589.       gUpdates.wiz.canAdvance = true;
  590.       gUpdates.wiz.advance();
  591.     },
  592.  
  593.     /**
  594.      * See nsIUpdateCheckListener
  595.      */
  596.     onError: function(request, update) {
  597.       LOG("UI:CheckingPage", "UpdateCheckListener: error");
  598.  
  599.       gUpdates.setUpdate(update);
  600.  
  601.       gUpdates.wiz.currentPage = document.getElementById("errors");
  602.     },
  603.  
  604.     /**
  605.      * See nsISupports.idl
  606.      */
  607.     QueryInterface: function(aIID) {
  608.       if (!aIID.equals(Components.interfaces.nsIUpdateCheckListener) &&
  609.           !aIID.equals(Components.interfaces.nsISupports))
  610.         throw Components.results.NS_ERROR_NO_INTERFACE;
  611.       return this;
  612.     }
  613.   }
  614. };
  615.  
  616. /**
  617.  * The "No Updates Are Available" page
  618.  */
  619. var gNoUpdatesPage = {
  620.   /**
  621.    * Initialize
  622.    */
  623.   onPageShow: function() {
  624.     gUpdates.setButtons(null, true, null, true, null, false, "hideButton",
  625.                         true, false, null, false, null, false);
  626.     gUpdates.wiz.getButton("finish").focus();
  627.   }
  628. };
  629.  
  630. /**
  631.  * The "Updates Are Available" page. Provides the user information about the
  632.  * available update, extensions it might make incompatible, and a means to
  633.  * continue downloading and installing it.
  634.  */
  635. var gUpdatesAvailablePage = {
  636.   /**
  637.    * An array of installed addons incompatible with this update.
  638.    */
  639.   _incompatibleItems: null,
  640.  
  641.   /**
  642.    * The <license> binding (which we are also using for the details content)
  643.    */
  644.   _updateMoreInfoContent: null,
  645.  
  646.   /**
  647.    * Initialize.
  648.    */
  649.   onPageShow: function() {
  650.     // disable the "next" button, as we don't want the user to
  651.     // be able to progress with the update util we show them the details
  652.     // which might include a warning about incompatible add-ons
  653.     // disable the "back" button, as well (note the call to setButtons()
  654.     // below does this, too).  This just leaves the cancel button
  655.     // until we are ready
  656.     gUpdates.wiz.getButton("next").disabled = true;
  657.     gUpdates.wiz.getButton("back").disabled = true;
  658.  
  659.     var updateName = gUpdates.strings.getFormattedString("updateName",
  660.       [gUpdates.brandName, gUpdates.update.version]);
  661.     if (gUpdates.update.channel == "nightly")
  662.       updateName = updateName + " " + gUpdates.update.buildID + " nightly";
  663.     var updateNameElement = document.getElementById("updateName");
  664.     updateNameElement.value = updateName;
  665.     var severity = gUpdates.update.type;
  666.     var updateTypeElement = document.getElementById("updateType");
  667.     updateTypeElement.setAttribute("severity", severity);
  668.  
  669.     var intro;
  670.     if (severity == "major") {
  671.       // for major updates, use the brandName and the version for the intro
  672.       intro = gUpdates.strings.getFormattedString(
  673.         "introType_major_app_and_version",
  674.         [gUpdates.brandName, gUpdates.update.version]);
  675.  
  676.       this._updateMoreInfoContent =
  677.         document.getElementById("updateMoreInfoContent");
  678.  
  679.       // update_name and update_version need to be set before url
  680.       // so that when attempting to download the url, we can show
  681.       // the formatted "Download..." string
  682.       this._updateMoreInfoContent.update_name = gUpdates.brandName;
  683.       this._updateMoreInfoContent.update_version = gUpdates.update.version;
  684.       this._updateMoreInfoContent.url = gUpdates.update.detailsURL;
  685.     }
  686.     else {
  687.       // for minor updates, do not include the version
  688.       // just use the brandName for the intro
  689.       intro = gUpdates.strings.getFormattedString(
  690.         "introType_minor_app", [gUpdates.brandName]);
  691.  
  692.       var updateMoreInfoURL = document.getElementById("updateMoreInfoURL");
  693.       updateMoreInfoURL.href = gUpdates.update.detailsURL;
  694.     }
  695.  
  696.     var updateTitle = gUpdates.strings
  697.                               .getString("updatesfound_" + severity + ".title");
  698.     gUpdates.wiz.currentPage.setAttribute("label", updateTitle);
  699.     // this is necessary to make this change to the label of the current
  700.     // wizard page show up
  701.     gUpdates.wiz._adjustWizardHeader();
  702.  
  703.     while (updateTypeElement.hasChildNodes())
  704.       updateTypeElement.removeChild(updateTypeElement.firstChild);
  705.     updateTypeElement.appendChild(document.createTextNode(intro));
  706.  
  707.     var em = Components.classes["@mozilla.org/extensions/manager;1"]
  708.                        .getService(Components.interfaces.nsIExtensionManager);
  709.     var items = em.getIncompatibleItemList("", gUpdates.update.version,
  710.                                            gUpdates.update.platformVersion,
  711.                                            nsIUpdateItem.TYPE_ANY, false,
  712.                                            { });
  713.     if (items.length > 0) {
  714.       // There are addons that are incompatible with this update, so show the
  715.       // warning message.
  716.       var incompatibleWarning = document.getElementById("incompatibleWarning");
  717.       incompatibleWarning.hidden = false;
  718.  
  719.       this._incompatibleItems = items;
  720.     }
  721.  
  722.     // wait to show the additional details until after we do the check
  723.     // for add-on incompatibilty
  724.     this.onShowMoreDetails();
  725.  
  726.     var licenseAccepted;
  727.     try {
  728.       licenseAccepted = gUpdates.update.getProperty("licenseAccepted");
  729.     }
  730.     catch (e) {
  731.       gUpdates.update.setProperty("licenseAccepted", "false");
  732.       licenseAccepted = false;
  733.     }
  734.  
  735.     // only major updates show EULAs
  736.     if (gUpdates.update.type == "major" &&
  737.         gUpdates.update.licenseURL && !licenseAccepted)
  738.       gUpdates.wiz.currentPage.setAttribute("next", "license");
  739.  
  740.     gUpdates.setButtons(null, true, "downloadButton_" + severity,
  741.                         false, null, false,
  742.                         null, false, true,
  743.                         "notNowButton", false,
  744.                         severity == "major" ? "neverButton" : null, false);
  745.     gUpdates.wiz.getButton("next").focus();
  746.   },
  747.  
  748.   /**
  749.    * User clicked the "More Details..." button
  750.    */
  751.   onShowMoreDetails: function() {
  752.     var updateTypeElement = document.getElementById("updateType");
  753.     var moreInfoURL = document.getElementById("moreInfoURL");
  754.     var moreInfoContent = document.getElementById("moreInfoContent");
  755.  
  756.     if (updateTypeElement.getAttribute("severity") == "major") {
  757.       moreInfoURL.hidden = true;
  758.       moreInfoContent.hidden = false;
  759.       document.getElementById("updateName").hidden = true;
  760.       document.getElementById("updateNameSep").hidden = true;
  761.       document.getElementById("upgradeEvangelism").hidden = true;
  762.       document.getElementById("upgradeEvangelismSep").hidden = true;
  763.  
  764.       // clear the "never" pref for this version.  this is to handle the
  765.       // scenario where the user clicked "never" for a major update
  766.       // and then at a later point, did "Check for Updates..."
  767.       // and then hit "Later".  If we don't clear the "never" pref
  768.       // "Later" will never happen.
  769.       //
  770.       // fix for bug #359093
  771.       // version might one day come back from AUS as an
  772.       // arbitrary (and possibly non ascii) string, so we need to encode it
  773.       var neverPrefName = PREF_UPDATE_NEVER_BRANCH + encodeURIComponent(gUpdates.update.version);
  774.       gPref.setBoolPref(neverPrefName, false);
  775.     }
  776.     else {
  777.       moreInfoURL.hidden = false;
  778.       moreInfoContent.hidden = true;
  779.     }
  780.  
  781.     // in order to prevent showing the blank xul:browser (<license> binding)
  782.     // delay setting the selected index of the detailsDeck until after
  783.     // we've set everything up.  see bug #352400 for more details.
  784.     var detailsDeck = document.getElementById("detailsDeck");
  785.     detailsDeck.selectedIndex = 1;
  786.   },
  787.  
  788.   /**
  789.    * When the user cancels the wizard or they decline the license
  790.    */
  791.   onWizardCancel: function() {
  792.     try {
  793.       // If the _updateMoreInfoContent was downloading, stop it.
  794.       if (this._updateMoreInfoContent)
  795.         this._updateMoreInfoContent.stopDownloading();
  796.     }
  797.     catch (ex) {
  798.       dump("XXX _updateMoreInfoContent.stopDownloading() failed: " + ex + "\n");
  799.     }
  800.   },
  801.  
  802.   /**
  803.    * Show a list of extensions made incompatible by this update.
  804.    */
  805.   showIncompatibleItems: function() {
  806.     openDialog("chrome://mozapps/content/update/incompatible.xul", "",
  807.                "dialog,centerscreen,modal,titlebar", this._incompatibleItems);
  808.   }
  809. };
  810.  
  811. /**
  812.  * The page which shows the user a license associated with an update. The
  813.  * user must agree to the terms of the license before continuing to install
  814.  * the update.
  815.  */
  816. var gLicensePage = {
  817.   /**
  818.    * The <license> element
  819.    */
  820.   _licenseContent: null,
  821.  
  822.   /**
  823.    * Initialize
  824.    */
  825.   onPageShow: function() {
  826.     this._licenseContent = document.getElementById("licenseContent");
  827.  
  828.     // the license radiogroup is disabled until the EULA is downloaded
  829.     document.getElementById("acceptDeclineLicense").disabled = true;
  830.  
  831.     gUpdates.setButtons(null, true, null, true, null, true, null,
  832.                         false, false, null, false, null, false);
  833.  
  834.     this._licenseContent.addEventListener("load", this.onLicenseLoad, false);
  835.     // update_name and update_version need to be set before url
  836.     // so that when attempting to download the url, we can show
  837.     // the formatted "Download..." string
  838.     this._licenseContent.update_name = gUpdates.brandName;
  839.     this._licenseContent.update_version = gUpdates.update.version;
  840.     this._licenseContent.url = gUpdates.update.licenseURL;
  841.   },
  842.  
  843.   /**
  844.    * When the license document has loaded
  845.    */
  846.   onLicenseLoad: function() {
  847.     // on the load event, disable or enable the radiogroup based on
  848.     // the state of the licenseContent.  note, you may get multiple
  849.     // onLoad events
  850.     document.getElementById("acceptDeclineLicense").disabled =
  851.       (gLicensePage._licenseContent.getAttribute("state") == "error");
  852.   },
  853.  
  854.   /**
  855.    * When the user changes the state of the accept / decline radio group
  856.    */
  857.   onAcceptDeclineRadio: function() {
  858.     // Return early if this page hasn't been loaded (bug 405257). This event is
  859.     // fired during the construction of the wizard before gUpdates has received
  860.     // its onload event.
  861.     if (!this._licenseContent)
  862.       return;
  863.  
  864.     var selectedIndex = document.getElementById("acceptDeclineLicense")
  865.                                 .selectedIndex;
  866.     // 0 == Accept, 1 == Decline
  867.     var licenseAccepted = (selectedIndex == 0);
  868.     gUpdates.wiz.getButton("next").disabled = !licenseAccepted;
  869.     gUpdates.wiz.canAdvance = licenseAccepted;
  870.   },
  871.  
  872.   /**
  873.    * When the user accepts the license by hitting "Next"
  874.    */
  875.   onWizardNext: function() {
  876.     try {
  877.       gUpdates.update.setProperty("licenseAccepted", "true");
  878.       var um =
  879.         Components.classes["@mozilla.org/updates/update-manager;1"].
  880.         getService(Components.interfaces.nsIUpdateManager);
  881.       um.saveUpdates();
  882.     }
  883.     catch (ex) {
  884.       dump("XXX ex " + ex + "\n");
  885.     }
  886.   },
  887.  
  888.   /**
  889.    * When the user cancels the wizard or they decline the license
  890.    */
  891.   onWizardCancel: function() {
  892.     try {
  893.       // If the license was downloading, stop it.
  894.       if (this._licenseContent)
  895.         this._licenseContent.stopDownloading();
  896.     }
  897.     catch (ex) {
  898.       dump("XXX _licenseContent.stopDownloading() failed: " + ex + "\n");
  899.     }
  900.   }
  901. };
  902.  
  903. /**
  904.  * Formats status messages for a download operation based on the progress
  905.  * of the download.
  906.  * @constructor
  907.  */
  908. function DownloadStatusFormatter() {
  909.   this._startTime = Math.floor((new Date()).getTime() / 1000);
  910.   this._elapsed = 0;
  911.  
  912.   var us = gUpdates.strings;
  913.   this._statusFormat = us.getString("statusFormat");
  914.  
  915.   this._progressFormat = us.getString("progressFormat");
  916.   this._progressFormatKBMB = us.getString("progressFormatKBMB");
  917.   this._progressFormatKBKB = us.getString("progressFormatKBKB");
  918.   this._progressFormatMBMB = us.getString("progressFormatMBMB");
  919.   this._progressFormatUnknownMB = us.getString("progressFormatUnknownMB");
  920.   this._progressFormatUnknownKB = us.getString("progressFormatUnknownKB");
  921.  
  922.   this._rateFormat = us.getString("rateFormat");
  923.   this._rateFormatKBSec = us.getString("rateFormatKBSec");
  924.   this._rateFormatMBSec = us.getString("rateFormatMBSec");
  925.  
  926.   this._timeFormat = us.getString("timeFormat");
  927.   this._longTimeFormat = us.getString("longTimeFormat");
  928.   this._shortTimeFormat = us.getString("shortTimeFormat");
  929.  
  930.   this._remain = us.getString("remain");
  931.   this._unknownFilesize = us.getString("unknownFilesize");
  932. }
  933. DownloadStatusFormatter.prototype = {
  934.   /**
  935.    * Time when the download started (in seconds since epoch)
  936.    */
  937.   _startTime: 0,
  938.  
  939.   /**
  940.    * Time elapsed since the start of the download operation (in seconds)
  941.    */
  942.   _elapsed: -1,
  943.  
  944.   /**
  945.    * Transfer rate of the download
  946.    */
  947.   _rate: 0,
  948.  
  949.   /**
  950.    * Transfer rate of the download, formatted as text
  951.    */
  952.   _rateFormatted: "",
  953.  
  954.   /**
  955.    * Transfer rate, formatted into text container
  956.    */
  957.   _rateFormattedContainer: "",
  958.  
  959.   /**
  960.    * Number of Kilobytes downloaded so far in the form:
  961.    *  376KB of 9.3MB
  962.    */
  963.   progress: "",
  964.  
  965.   /**
  966.    * Format a human-readable status message based on the current download
  967.    * progress.
  968.    * @param   currSize
  969.    *          The current number of bytes transferred
  970.    * @param   finalSize
  971.    *          The total number of bytes to be transferred
  972.    * @returns A human readable status message, e.g.
  973.    *          "3.4 of 4.7MB; 01:15 remain"
  974.    */
  975.   formatStatus: function(currSize, finalSize) {
  976.     var now = Math.floor((new Date()).getTime() / 1000);
  977.  
  978.     // 1) Determine the Download Progress in Kilobytes
  979.     var total = parseInt(finalSize/1024 + 0.5);
  980.     this.progress = this._formatKBytes(parseInt(currSize/1024 + 0.5), total);
  981.  
  982.     var progress = this._replaceInsert(this._progressFormat, 1, this.progress);
  983.     var rateFormatted = "";
  984.  
  985.     // 2) Determine the Transfer Rate
  986.     var oldElapsed = this._elapsed;
  987.     this._elapsed = now - this._startTime;
  988.     if (oldElapsed != this._elapsed) {
  989.       this._rate = this._elapsed ? Math.floor((currSize / 1024) / this._elapsed) : 0;
  990.       var isKB = true;
  991.       if (parseInt(this._rate / 1024) > 0) {
  992.         this._rate = (this._rate / 1024).toFixed(1);
  993.         isKB = false;
  994.       }
  995.       if (this._rate > 100)
  996.         this._rate = Math.round(this._rate);
  997.  
  998.       if (this._rate) {
  999.         var format = isKB ? this._rateFormatKBSec : this._rateFormatMBSec;
  1000.         this._rateFormatted = this._replaceInsert(format, 1, this._rate);
  1001.         this._rateFormattedContainer = this._replaceInsert(" " + this._rateFormat, 1, this._rateFormatted);
  1002.       }
  1003.     }
  1004.     progress = this._replaceInsert(progress, 2, this._rateFormattedContainer);
  1005.  
  1006.  
  1007.     // 3) Determine the Time Remaining
  1008.     var remainingTime = "";
  1009.     if (this._rate && (finalSize > 0)) {
  1010.       remainingTime = Math.floor(((finalSize - currSize) / 1024) / this._rate);
  1011.       remainingTime = this._formatSeconds(remainingTime);
  1012.       remainingTime = this._replaceInsert(this._timeFormat, 1, remainingTime)
  1013.       remainingTime = this._replaceInsert(remainingTime, 2, this._remain);
  1014.     }
  1015.  
  1016.     //
  1017.     // [statusFormat:
  1018.     //  [progressFormat:
  1019.     //   [[progressFormatKBKB|
  1020.     //     progressFormatKBMB|
  1021.     //     progressFormatMBMB|
  1022.     //     progressFormatUnknownKB|
  1023.     //     progressFormatUnknownMB
  1024.     //    ][rateFormat]]
  1025.     //  ][timeFormat]
  1026.     // ]
  1027.     var status = this._statusFormat;
  1028.     status = this._replaceInsert(status, 1, progress);
  1029.     status = this._replaceInsert(status, 2, remainingTime);
  1030.     return status;
  1031.   },
  1032.  
  1033.   /**
  1034.    * Inserts a string into another string at the specified index, e.g. for
  1035.    * the format string var foo ="#1 #2 #3", |_replaceInsert(foo, 2, "test")|
  1036.    * returns "#1 test #3";
  1037.    * @param   format
  1038.    *          The format string
  1039.    * @param   index
  1040.    *          The Index to insert into
  1041.    * @param   value
  1042.    *          The value to insert
  1043.    * @returns The string with the value inserted.
  1044.    */
  1045.   _replaceInsert: function(format, index, value) {
  1046.     return format.replace(new RegExp("#" + index), value);
  1047.   },
  1048.  
  1049.   /**
  1050.    * Formats progress in the form of kilobytes transfered vs. total to
  1051.    * transfer.
  1052.    * @param   currentKB
  1053.    *          The current amount of data transfered, in kilobytes.
  1054.    * @param   totalKB
  1055.    *          The total amount of data that must be transfered, in kilobytes.
  1056.    * @returns A string representation of the progress, formatted according to:
  1057.    *
  1058.    *            KB           totalKB           returns
  1059.    *            x, < 1MB     y < 1MB           x of y KB
  1060.    *            x, < 1MB     y >= 1MB          x KB of y MB
  1061.    *            x, >= 1MB    y >= 1MB          x of y MB
  1062.    */
  1063.   _formatKBytes: function(currentKB, totalKB) {
  1064.     var progressHasMB = parseInt(currentKB / 1024) > 0;
  1065.     var totalHasMB = parseInt(totalKB / 1024) > 0;
  1066.  
  1067.     var format = "";
  1068.     if (!progressHasMB && !totalHasMB) {
  1069.       if (!totalKB) {
  1070.         format = this._progressFormatUnknownKB;
  1071.         format = this._replaceInsert(format, 1, currentKB);
  1072.       } else {
  1073.         format = this._progressFormatKBKB;
  1074.         format = this._replaceInsert(format, 1, currentKB);
  1075.         format = this._replaceInsert(format, 2, totalKB);
  1076.       }
  1077.     }
  1078.     else if (progressHasMB && totalHasMB) {
  1079.       format = this._progressFormatMBMB;
  1080.       format = this._replaceInsert(format, 1, (currentKB / 1024).toFixed(1));
  1081.       format = this._replaceInsert(format, 2, (totalKB / 1024).toFixed(1));
  1082.     }
  1083.     else if (totalHasMB && !progressHasMB) {
  1084.       format = this._progressFormatKBMB;
  1085.       format = this._replaceInsert(format, 1, currentKB);
  1086.       format = this._replaceInsert(format, 2, (totalKB / 1024).toFixed(1));
  1087.     }
  1088.     else if (progressHasMB && !totalHasMB) {
  1089.       format = this._progressFormatUnknownMB;
  1090.       format = this._replaceInsert(format, 1, (currentKB / 1024).toFixed(1));
  1091.     }
  1092.     return format;
  1093.   },
  1094.  
  1095.   /**
  1096.    * Formats a time in seconds into something human readable.
  1097.    * @param   seconds
  1098.    *          The time to format
  1099.    * @returns A human readable string representing the date.
  1100.    */
  1101.   _formatSeconds: function(seconds) {
  1102.     // Determine number of hours/minutes/seconds
  1103.     var hours = (seconds - (seconds % 3600)) / 3600;
  1104.     seconds -= hours * 3600;
  1105.     var minutes = (seconds - (seconds % 60)) / 60;
  1106.     seconds -= minutes * 60;
  1107.  
  1108.     // Pad single digit values
  1109.     if (hours < 10)
  1110.       hours = "0" + hours;
  1111.     if (minutes < 10)
  1112.       minutes = "0" + minutes;
  1113.     if (seconds < 10)
  1114.       seconds = "0" + seconds;
  1115.  
  1116.     // Insert hours, minutes, and seconds into result string.
  1117.     var result = parseInt(hours) ? this._longTimeFormat : this._shortTimeFormat;
  1118.     result = this._replaceInsert(result, 1, hours);
  1119.     result = this._replaceInsert(result, 2, minutes);
  1120.     result = this._replaceInsert(result, 3, seconds);
  1121.  
  1122.     return result;
  1123.   }
  1124. };
  1125.  
  1126. /**
  1127.  * The "Update is Downloading" page - provides feedback for the download
  1128.  * process plus a pause/resume UI
  1129.  */
  1130. var gDownloadingPage = {
  1131.   /**
  1132.    * DOM Elements
  1133.    */
  1134.   _downloadName     : null,
  1135.   _downloadStatus   : null,
  1136.   _downloadProgress : null,
  1137.   _downloadThrobber : null,
  1138.   _pauseButton      : null,
  1139.  
  1140.   /**
  1141.    * Label cache to hold the 'Connecting' string
  1142.    */
  1143.   _label_downloadStatus : null,
  1144.  
  1145.   /**
  1146.    * An instance of the status formatter object
  1147.    */
  1148.   _statusFormatter  : null,
  1149.   get statusFormatter() {
  1150.     if (!this._statusFormatter)
  1151.       this._statusFormatter = new DownloadStatusFormatter();
  1152.     return this._statusFormatter;
  1153.   },
  1154.  
  1155.   /**
  1156.    * Initialize
  1157.    */
  1158.   onPageShow: function() {
  1159.     this._downloadName = document.getElementById("downloadName");
  1160.     this._downloadStatus = document.getElementById("downloadStatus");
  1161.     this._downloadProgress = document.getElementById("downloadProgress");
  1162.     this._downloadThrobber = document.getElementById("downloadThrobber");
  1163.     this._pauseButton = document.getElementById("pauseButton");
  1164.     this._label_downloadStatus = this._downloadStatus.textContent;
  1165.  
  1166.     var updates =
  1167.         Components.classes["@mozilla.org/updates/update-service;1"].
  1168.         getService(Components.interfaces.nsIApplicationUpdateService);
  1169.  
  1170.     var um =
  1171.         Components.classes["@mozilla.org/updates/update-manager;1"].
  1172.         getService(Components.interfaces.nsIUpdateManager);
  1173.     var activeUpdate = um.activeUpdate;
  1174.     if (activeUpdate)
  1175.       gUpdates.setUpdate(activeUpdate);
  1176.  
  1177.     if (!gUpdates.update) {
  1178.       LOG("UI:DownloadingPage", "onPageShow: no valid update to download?!");
  1179.       return;
  1180.     }
  1181.  
  1182.     try {
  1183.     // Say that this was a foreground download, not a background download,
  1184.     // since the user cared enough to look in on this process.
  1185.     gUpdates.update.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
  1186.     gUpdates.update.setProperty("foregroundDownload", "true");
  1187.  
  1188.     // Pause any active background download and restart it as a foreground
  1189.     // download.
  1190.     updates.pauseDownload();
  1191.     var state = updates.downloadUpdate(gUpdates.update, false);
  1192.     if (state == "failed") {
  1193.       // We've tried as hard as we could to download a valid update -
  1194.       // we fell back from a partial patch to a complete patch and even
  1195.       // then we couldn't validate. Show a validation error with instructions
  1196.       // on how to manually update.
  1197.       this.showVerificationError();
  1198.     }
  1199.     else {
  1200.       // Add this UI as a listener for active downloads
  1201.       updates.addDownloadListener(this);
  1202.     }
  1203.  
  1204.     if (activeUpdate)
  1205.       this._setUIState(!updates.isDownloading);
  1206.  
  1207.     var link = document.getElementById("detailsLink");
  1208.     link.href = gUpdates.update.detailsURL;
  1209.     }
  1210.     catch(ex) {
  1211.       LOG("UI:DownloadingPage", "onPageShow: " + ex);
  1212.     }
  1213.  
  1214.     gUpdates.setButtons(null, true, null, true, null, true, "hideButton",
  1215.                         false, false, null, false, null, false);
  1216.   },
  1217.  
  1218.   /**
  1219.    * Updates the text status message
  1220.    */
  1221.   _setStatus: function(status) {
  1222.     // Don't bother setting the same text more than once. This can happen
  1223.     // due to the asynchronous behavior of the downloader.
  1224.     if (this._downloadStatus.textContent == status)
  1225.       return;
  1226.     while (this._downloadStatus.hasChildNodes())
  1227.       this._downloadStatus.removeChild(this._downloadStatus.firstChild);
  1228.     this._downloadStatus.appendChild(document.createTextNode(status));
  1229.   },
  1230.  
  1231.   /**
  1232.    * Whether or not we are currently paused
  1233.    */
  1234.   _paused       : false,
  1235.  
  1236.   /**
  1237.    * Adjust UI to suit a certain state of paused-ness
  1238.    * @param   paused
  1239.    *          Whether or not the download is paused
  1240.    */
  1241.   _setUIState: function(paused) {
  1242.     var u = gUpdates.update;
  1243.     if (paused) {
  1244.       if (this._downloadThrobber.hasAttribute("state"))
  1245.         this._downloadThrobber.removeAttribute("state");
  1246.       if (this._downloadProgress.mode != "normal")
  1247.         this._downloadProgress.mode = "normal";
  1248.       this._downloadName.value = gUpdates.strings.getFormattedString(
  1249.         "pausedName", [u.name]);
  1250.       this._pauseButton.label = gUpdates.strings.getString("pauseButtonResume");
  1251.       var p = u.selectedPatch.QueryInterface(Components.interfaces.nsIPropertyBag);
  1252.       var status = p.getProperty("status");
  1253.       if (status)
  1254.         this._setStatus(status);
  1255.     }
  1256.     else {
  1257.       if (!(this._downloadThrobber.hasAttribute("state") &&
  1258.            (this._downloadThrobber.getAttribute("state") == "loading")))
  1259.         this._downloadThrobber.setAttribute("state", "loading");
  1260.       if (this._downloadProgress.mode != "undetermined")
  1261.         this._downloadProgress.mode = "undetermined";
  1262.       this._downloadName.value = gUpdates.strings.getFormattedString(
  1263.         "downloadingPrefix", [u.name]);
  1264.       this._pauseButton.label = gUpdates.strings.getString("pauseButtonPause");
  1265.       this._setStatus(this._label_downloadStatus);
  1266.     }
  1267.   },
  1268.  
  1269.   /**
  1270.    * When the user clicks the Pause/Resume button
  1271.    */
  1272.   onPause: function() {
  1273.     var updates =
  1274.         Components.classes["@mozilla.org/updates/update-service;1"].
  1275.         getService(Components.interfaces.nsIApplicationUpdateService);
  1276.     if (this._paused)
  1277.       updates.downloadUpdate(gUpdates.update, false);
  1278.     else {
  1279.       var patch = gUpdates.update.selectedPatch;
  1280.       patch.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
  1281.       patch.setProperty("status",
  1282.         gUpdates.strings.getFormattedString("pausedStatus",
  1283.         [this.statusFormatter.progress]));
  1284.       updates.pauseDownload();
  1285.     }
  1286.     this._paused = !this._paused;
  1287.  
  1288.     // Update the UI
  1289.     this._setUIState(this._paused);
  1290.   },
  1291.  
  1292.   /**
  1293.    * When the user closes the Wizard UI (including by clicking the Hide button)
  1294.    */
  1295.   onWizardCancel: function() {
  1296.     // Remove ourself as a download listener so that we don't continue to be
  1297.     // fed progress and state notifications after the UI we're updating has
  1298.     // gone away.
  1299.     var updates =
  1300.         Components.classes["@mozilla.org/updates/update-service;1"].
  1301.         getService(Components.interfaces.nsIApplicationUpdateService);
  1302.     updates.removeDownloadListener(this);
  1303.  
  1304.     var um =
  1305.         Components.classes["@mozilla.org/updates/update-manager;1"]
  1306.                   .getService(Components.interfaces.nsIUpdateManager);
  1307.     um.activeUpdate = gUpdates.update;
  1308.  
  1309.     // If the download was paused by the user, ask the user if they want to
  1310.     // have the update resume in the background.
  1311.     var downloadInBackground = true;
  1312.     if (this._paused) {
  1313.       var title = gUpdates.strings.getString("resumePausedAfterCloseTitle");
  1314.       var message = gUpdates.strings.getFormattedString(
  1315.         "resumePausedAfterCloseMessage", [gUpdates.brandName]);
  1316.       var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  1317.                         .getService(Components.interfaces.nsIPromptService);
  1318.       var flags = ps.STD_YES_NO_BUTTONS;
  1319.       // focus the software update wizard before prompting.
  1320.       // this will raise the software update wizard if it is minimized
  1321.       // making it more obvious what the prompt is for and will
  1322.       // solve the problem of windows "obscuring" the prompt.
  1323.       // see bug #350299 for more details
  1324.       window.focus();
  1325.       var rv = ps.confirmEx(window, title, message, flags, null, null, null, null, { });
  1326.       if (rv == 1) {
  1327.         downloadInBackground = false;
  1328.       }
  1329.     }
  1330.     if (downloadInBackground) {
  1331.       // Continue download in the background at full speed.
  1332.       LOG("UI:DownloadingPage", "onWizardCancel: continuing download in background at full speed");
  1333.       updates.downloadUpdate(gUpdates.update, false);
  1334.     }
  1335.   },
  1336.  
  1337.   /**
  1338.    * When the data transfer begins
  1339.    * @param   request
  1340.    *          The nsIRequest object for the transfer
  1341.    * @param   context
  1342.    *          Additional data
  1343.    */
  1344.   onStartRequest: function(request, context) {
  1345.     request.QueryInterface(nsIIncrementalDownload);
  1346.     LOG("UI:DownloadingPage", "onStartRequest: " + request.URI.spec);
  1347.  
  1348.     // This !paused test is necessary because onStartRequest may fire after
  1349.     // the download was paused (for those speedy clickers...)
  1350.     if (this._paused)
  1351.       return;
  1352.  
  1353.     if (!(this._downloadThrobber.hasAttribute("state") &&
  1354.           (this._downloadThrobber.getAttribute("state") == "loading")))
  1355.       this._downloadThrobber.setAttribute("state", "loading");
  1356.     if (this._downloadProgress.mode != "undetermined")
  1357.       this._downloadProgress.mode = "undetermined";
  1358.     this._setStatus(this._label_downloadStatus);
  1359.   },
  1360.  
  1361.   /**
  1362.    * When new data has been downloaded
  1363.    * @param   request
  1364.    *          The nsIRequest object for the transfer
  1365.    * @param   context
  1366.    *          Additional data
  1367.    * @param   progress
  1368.    *          The current number of bytes transferred
  1369.    * @param   maxProgress
  1370.    *          The total number of bytes that must be transferred
  1371.    */
  1372.   onProgress: function(request, context, progress, maxProgress) {
  1373.     request.QueryInterface(nsIIncrementalDownload);
  1374.     LOG("UI:DownloadingPage.onProgress", " " + request.URI.spec + ", " + progress +
  1375.         "/" + maxProgress);
  1376.  
  1377.     var name = gUpdates.strings.getFormattedString("downloadingPrefix", [gUpdates.update.name]);
  1378.     var status = this.statusFormatter.formatStatus(progress, maxProgress);
  1379.     var progress = Math.round(100 * (progress/maxProgress));
  1380.  
  1381.     var p = gUpdates.update.selectedPatch;
  1382.     p.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
  1383.     p.setProperty("progress", progress);
  1384.     p.setProperty("status", status);
  1385.  
  1386.     // This !paused test is necessary because onProgress may fire after
  1387.     // the download was paused (for those speedy clickers...)
  1388.     if (this._paused)
  1389.       return;
  1390.  
  1391.     if (!(this._downloadThrobber.hasAttribute("state") &&
  1392.          (this._downloadThrobber.getAttribute("state") == "loading")))
  1393.       this._downloadThrobber.setAttribute("state", "loading");
  1394.     if (this._downloadProgress.mode != "normal")
  1395.       this._downloadProgress.mode = "normal";
  1396.     this._downloadProgress.value = progress;
  1397.     this._pauseButton.disabled = false;
  1398.     this._downloadName.value = name;
  1399.     this._setStatus(status);
  1400.   },
  1401.  
  1402.   /**
  1403.    * When we have new status text
  1404.    * @param   request
  1405.    *          The nsIRequest object for the transfer
  1406.    * @param   context
  1407.    *          Additional data
  1408.    * @param   status
  1409.    *          A status code
  1410.    * @param   statusText
  1411.    *          Human readable version of |status|
  1412.    */
  1413.   onStatus: function(request, context, status, statusText) {
  1414.     request.QueryInterface(nsIIncrementalDownload);
  1415.     LOG("UI:DownloadingPage", "onStatus: " + request.URI.spec + " status = " +
  1416.         status + ", text = " + statusText);
  1417.     this._setStatus(statusText);
  1418.   },
  1419.  
  1420.   /**
  1421.    * When data transfer ceases
  1422.    * @param   request
  1423.    *          The nsIRequest object for the transfer
  1424.    * @param   context
  1425.    *          Additional data
  1426.    * @param   status
  1427.    *          Status code containing the reason for the cessation.
  1428.    */
  1429.   onStopRequest: function(request, context, status) {
  1430.     request.QueryInterface(nsIIncrementalDownload);
  1431.     LOG("UI:DownloadingPage", "onStopRequest: " + request.URI.spec +
  1432.         ", status = " + status);
  1433.  
  1434.     if (this._downloadThrobber.hasAttribute("state"))
  1435.       this._downloadThrobber.removeAttribute("state");
  1436.     if (this._downloadProgress.mode != "normal")
  1437.       this._downloadProgress.mode = "normal";
  1438.  
  1439.     var u = gUpdates.update;
  1440.     const NS_BINDING_ABORTED = 0x804b0002;
  1441.     switch (status) {
  1442.     case Components.results.NS_ERROR_UNEXPECTED:
  1443.       if (u.selectedPatch.state == STATE_DOWNLOAD_FAILED &&
  1444.           u.isCompleteUpdate) {
  1445.         // Verification error of complete patch, informational text is held in
  1446.         // the update object.
  1447.         gUpdates.wiz.currentPage = document.getElementById("errors");
  1448.       }
  1449.       else {
  1450.         // Verification failed for a partial patch, complete patch is now
  1451.         // downloading so return early and do NOT remove the download listener!
  1452.  
  1453.         // Reset the progress meter to "undertermined" mode so that we don't
  1454.         // show old progress for the new download of the "complete" patch.
  1455.         this._downloadProgress.mode = "undetermined";
  1456.         this._pauseButton.disabled = true;
  1457.  
  1458.         var verificationFailed = document.getElementById("verificationFailed");
  1459.         verificationFailed.hidden = false;
  1460.  
  1461.         this._statusFormatter = null;
  1462.         return;
  1463.       }
  1464.       break;
  1465.     case NS_BINDING_ABORTED:
  1466.       LOG("UI:DownloadingPage", "onStopRequest: Pausing Download");
  1467.       // Return early, do not remove UI listener since the user may resume
  1468.       // downloading again.
  1469.       return;
  1470.     case Components.results.NS_OK:
  1471.       LOG("UI:DownloadingPage", "onStopRequest: Patch Verification Succeeded");
  1472.       gUpdates.wiz.canAdvance = true;
  1473.       gUpdates.wiz.advance();
  1474.       break;
  1475.     default:
  1476.       LOG("UI:DownloadingPage", "onStopRequest: Transfer failed");
  1477.       // Some kind of transfer error, die.
  1478.       gUpdates.wiz.currentPage = document.getElementById("errors");
  1479.       break;
  1480.     }
  1481.  
  1482.     var updates =
  1483.         Components.classes["@mozilla.org/updates/update-service;1"].
  1484.         getService(Components.interfaces.nsIApplicationUpdateService);
  1485.     updates.removeDownloadListener(this);
  1486.   },
  1487.  
  1488.   /**
  1489.    * See nsISupports.idl
  1490.    */
  1491.   QueryInterface: function(iid) {
  1492.     if (!iid.equals(Components.interfaces.nsIRequestObserver) &&
  1493.         !iid.equals(Components.interfaces.nsIProgressEventSink) &&
  1494.         !iid.equals(Components.interfaces.nsISupports))
  1495.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1496.     return this;
  1497.   }
  1498. };
  1499.  
  1500. /**
  1501.  * The "There was an error during the update" page.
  1502.  */
  1503. var gErrorsPage = {
  1504.   /**
  1505.    * Initialize
  1506.    */
  1507.   onPageShow: function() {
  1508.     gUpdates.setButtons(null, true, null, true, null, false, "hideButton",
  1509.                         true, false, null, false, null, false);
  1510.     gUpdates.wiz.getButton("finish").focus();
  1511.  
  1512.     var errorReason = document.getElementById("errorReason");
  1513.     errorReason.value = gUpdates.update.statusText;
  1514.     var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
  1515.                               .getService(Components.interfaces.nsIURLFormatter);
  1516.     var manualURL = formatter.formatURLPref(PREF_UPDATE_MANUAL_URL);
  1517.     var errorLinkLabel = document.getElementById("errorLinkLabel");
  1518.     errorLinkLabel.value = manualURL;
  1519.     errorLinkLabel.href = manualURL;
  1520.   },
  1521.  
  1522.   /**
  1523.    * Initialize, for the "Error Applying Patch" case.
  1524.    */
  1525.   onPageShowPatching: function() {
  1526.     gUpdates.wiz.getButton("back").disabled = true;
  1527.     gUpdates.wiz.getButton("cancel").disabled = true;
  1528.     gUpdates.wiz.getButton("next").focus();
  1529.   },
  1530.  
  1531.   /**
  1532.    * Finish button clicked.
  1533.    */
  1534.   onWizardFinish: function() {
  1535.     // XXXjwalden COMPLETE AND TOTAL HACK!!!
  1536.     //
  1537.     // The problem the following code is working around is this: the update
  1538.     // service's API for responding to updates is poor.  Essentially, all
  1539.     // the information we can get is that we've started to request a download or
  1540.     // that the download we've started has finished, with minimal details about
  1541.     // how it finished which aren't described in the API (and which internally
  1542.     // are not entirely useful, either -- mostly unuseful nsresults).  How
  1543.     // do you signal the difference between "this download failed" and "all
  1544.     // downloads failed", particularly if you aim for API compatibility?  The
  1545.     // code in nsApplicationUpdateService only determines the difference *after*
  1546.     // the current request is stopped, and since the subsequent second call to
  1547.     // downloadUpdate doesn't start/stop a request, the download listener is
  1548.     // never notified and whatever code was relying on it just fails without
  1549.     // notification.  The consequence of this is that it's impossible to
  1550.     // properly remove the download listener.
  1551.     //
  1552.     // The code before this patch tried to do the exit after all downloads
  1553.     // failed but was missing a QueryInterface to work; with it, making sure
  1554.     // that the download listener is removed in all cases, including in the case
  1555.     // where the last onStopRequest corresponds to *a* failed download instead
  1556.     // of to *all* failed downloads, simply means that we have to try to remove
  1557.     // that listener in the error page spawned by the update service.  If there
  1558.     // were time and API compat weren't a problem, we'd add an onFinish handler
  1559.     // or something which could signal exactly what happened and not overload
  1560.     // onStopRequest, but there isn't, so we can't.
  1561.     //
  1562.     var updates =
  1563.         Components.classes["@mozilla.org/updates/update-service;1"].
  1564.         getService(Components.interfaces.nsIApplicationUpdateService);
  1565.     updates.removeDownloadListener(gDownloadingPage);
  1566.   }
  1567. };
  1568.  
  1569. /**
  1570.  * The "Update has been downloaded" page. Shows information about what
  1571.  * was downloaded.
  1572.  */
  1573. var gFinishedPage = {
  1574.   /**
  1575.    * Called to initialize the Wizard Page.
  1576.    */
  1577.   onPageShow: function() {
  1578.     gUpdates.setButtons(null, true, null, true, "restartButton", false,
  1579.                         "notNowButton", false, false, null, false, null, false);
  1580.     //XXX bug 426021
  1581.     setTimeout(function () {
  1582.       gUpdates.wiz.getButton("finish").focus();
  1583.     }, 0);
  1584.   },
  1585.  
  1586.   /**
  1587.    * Called to initialize the Wizard Page. (Background Source Event)
  1588.    */
  1589.   onPageShowBackground: function() {
  1590.     var finishedBackground = document.getElementById("finishedBackground");
  1591.     finishedBackground.setAttribute("label", gUpdates.strings.getFormattedString(
  1592.       "updateReadyToInstallHeader", [gUpdates.update.name]));
  1593.     // XXXben - wizard should give us a way to set the page header.
  1594.     gUpdates.wiz._adjustWizardHeader();
  1595.     var updateFinishedName = document.getElementById("updateFinishedName");
  1596.     updateFinishedName.value = gUpdates.update.name;
  1597.  
  1598.     var link = document.getElementById("finishedBackgroundLink");
  1599.     link.href = gUpdates.update.detailsURL;
  1600.  
  1601.     this.onPageShow();
  1602.  
  1603.     if (getPref("getBoolPref", PREF_UPDATE_TEST_LOOP, false)) {
  1604.       setTimeout(function () {
  1605.                    gUpdates.wiz.getButton("finish").click();
  1606.                  }, UPDATE_TEST_LOOP_INTERVAL);
  1607.     }
  1608.   },
  1609.  
  1610.   /**
  1611.    * Called when the wizard finishes, i.e. the "Restart Now" button is
  1612.    * clicked.
  1613.    */
  1614.   onWizardFinish: function() {
  1615.     // Do the restart
  1616.     LOG("UI:FinishedPage" , "onWizardFinish: Restarting Application...");
  1617.  
  1618.     // disable the "finish" (Restart) and "cancel" (Later) buttons
  1619.     // because the Software Update wizard is still up at the point,
  1620.     // and will remain up until we return and we close the
  1621.     // window with a |window.close()| in wizard.xml
  1622.     // (it was the firing the "wizardfinish" event that got us here.)
  1623.     // This prevents the user from switching back
  1624.     // to the Software Update dialog and clicking "Restart" or "Later"
  1625.     // when dealing with the "confirm close" prompts.
  1626.     // See bug #350299 for more details.
  1627.     gUpdates.wiz.getButton("finish").disabled = true;
  1628.     gUpdates.wiz.getButton("cancel").disabled = true;
  1629.  
  1630.     // This process is *extremely* broken. There should be some nice
  1631.     // integrated system for determining whether or not windows are allowed
  1632.     // to close or not, and what happens when that happens. We need to
  1633.     // jump through all these hoops (duplicated from globalOverlay.js) to
  1634.     // ensure that various window types (browser, downloads, etc) all
  1635.     // allow the app to shut down.
  1636.     // bsmedberg?
  1637.  
  1638.     // Notify all windows that an application quit has been requested.
  1639.     var os = Components.classes["@mozilla.org/observer-service;1"]
  1640.                        .getService(Components.interfaces.nsIObserverService);
  1641.     var cancelQuit =
  1642.         Components.classes["@mozilla.org/supports-PRBool;1"].
  1643.         createInstance(Components.interfaces.nsISupportsPRBool);
  1644.     os.notifyObservers(cancelQuit, "quit-application-requested", "restart");
  1645.  
  1646.     // Something aborted the quit process.
  1647.     if (cancelQuit.data)
  1648.       return;
  1649.  
  1650.     var appStartup =
  1651.         Components.classes["@mozilla.org/toolkit/app-startup;1"].
  1652.         getService(Components.interfaces.nsIAppStartup);
  1653.     appStartup.quit(appStartup.eAttemptQuit | appStartup.eRestart);
  1654.   },
  1655.  
  1656.   /**
  1657.    * Called when the wizard is canceled, i.e. when the "Not Now" button is
  1658.    * clicked.
  1659.    */
  1660.   onWizardCancel: function() {
  1661.     var interval = getPref("getIntPref", PREF_UPDATE_NAGTIMER_RESTART, 86400);
  1662.     gUpdates.registerNagTimer("restart-nag-timer", interval,
  1663.                               "showUpdateComplete");
  1664.   }
  1665. };
  1666.  
  1667. /**
  1668.  * The "Update was Installed Successfully" page.
  1669.  */
  1670. var gInstalledPage = {
  1671.   /**
  1672.    * Initialize
  1673.    */
  1674.   onPageShow: function() {
  1675.     var ai =
  1676.         Components.classes["@mozilla.org/xre/app-info;1"].
  1677.         getService(Components.interfaces.nsIXULAppInfo);
  1678.  
  1679.     var branding = document.getElementById("brandStrings");
  1680.     try {
  1681.       var url = branding.getFormattedString("whatsNewURL", [ai.version]);
  1682.       var whatsnewLink = document.getElementById("whatsnewLink");
  1683.       whatsnewLink.href = url;
  1684.       whatsnewLink.hidden = false;
  1685.     }
  1686.     catch (e) {
  1687.     }
  1688.  
  1689.     gUpdates.setButtons(null, true, null, true, null, false, "hideButton",
  1690.                         true, false, null, false, null, false);
  1691.     gUpdates.wiz.getButton("finish").focus();
  1692.   }
  1693. };
  1694.  
  1695. /**
  1696.  * Called as the application shuts down due to being quit from the File->Quit
  1697.  * menu item.
  1698.  * XXXben this API is broken.
  1699.  */
  1700. function tryToClose() {
  1701.   var cp = gUpdates.wiz.currentPage;
  1702.   if (cp.pageid != "finished" && cp.pageid != "finishedBackground")
  1703.     gUpdates.onWizardCancel();
  1704.   return true;
  1705. }
  1706.  
  1707. /**
  1708.  * Callback for the Update Prompt to set the current page if an Update Wizard
  1709.  * window is already found to be open.
  1710.  * @param   pageid
  1711.  *          The ID of the page to switch to
  1712.  */
  1713. function setCurrentPage(pageid) {
  1714.   gUpdates.wiz.currentPage = document.getElementById(pageid);
  1715. }
  1716.