# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
# ***** 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 Ben Goodger.
# Portions created by the Initial Developer are Copyright (C) 2004
# the Initial Developer. All Rights Reserved.
# 
# Contributor(s):
#   Ben Goodger <ben@bengoodger.com>
# 
# 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 *****

//
// window.arguments[1...] is an array of nsIUpdateItem implementing objects 
// that are to be updated. 
//  * if the array is empty, all items are updated (like a background update
//    check)
//  * if the array contains one or two UpdateItems, with null id fields, 
//    all items of that /type/ are updated.
//
// This UI can be opened from the following places, and in the following modes:
//
// - from the Version Compatibility Checker at startup
//    as the application starts a check is done to see if the application being
//    started is the same version that was started last time. If it isn't, a
//    list of UpdateItems that are incompatible with the verison being 
//    started is generated and this UI opened with that list. This UI then
//    offers to check for updates to those UpdateItems that are compatible
//    with the version of the application being started. 
//    
//    In this mode, the wizard is opened to panel 'mismatch'. 
//
// - from the Extension Manager or Options Dialog or any UI where the user
//   directly requests to check for updates.
//    in this case the user selects UpdateItem(s) to update and this list
//    is passed to this UI. If a single item is sent with the id field set to
//    null but the type set correctly, this UI will check for updates to all 
//    items of that type.
//
//    In this mode, the wizard is opened to panel 'checking'.
//
// - from the Updates Available Status Bar notification.
//    in this case the background update checking has determined there are new
//    updates that can be installed and has prepared a list for the user to see.
//    They can update by clicking on a status bar icon which passes the list
//    to this UI which lets them choose to install updates. 
//
//    In this mode, the wizard is opened to panel 'updatesFound' if the data
//    set is immediately available, or 'checking' if the user closed the browser
//    since the last background check was performed and the check needs to be
//    performed again. 
//

const nsIUpdateItem       = Components.interfaces.nsIUpdateItem;
const nsIUpdateService    = Components.interfaces.nsIUpdateService;

const PREF_UPDATE_APP_UPDATESAVAILABLE          = "app.update.updatesAvailable";
const PREF_UPDATE_APP_PERFORMED                 = "app.update.performed";

const PREF_UPDATE_EXTENSIONS_AUTOUPDATEENABLED  = "extensions.update.autoUpdateEnabled";
const PREF_UPDATE_EXTENSIONS_COUNT              = "extensions.update.count";
const PREF_UPDATE_EXTENSIONS_SEVERITY_THRESHOLD = "extensions.update.severity.threshold";

const PREF_UPDATE_SEVERITY                      = "update.severity";

var gSourceEvent = null;
var gUpdateTypes = null;

var gUpdateWizard = {
  // The items to check for updates for (e.g. an extension, some subset of extensions, 
  // all extensions, a list of compatible extensions, etc...)
  items: [],
  // The items that we found updates available for
  itemsToUpdate: [],
  // The items that we successfully installed updates for
  updatedCount: 0,
  appUpdatesAvailable: false,

  shouldSuggestAutoChecking: false,
  shouldAutoCheck: false,
  
  updatingApp: false,
  remainingExtensionUpdateCount: 0,
  
  succeeded: true,
  
#ifdef MOZ_PHOENIX
  offeredResetHomepage: false,
#endif

  appComps: {
    upgraded: { 
      core      : [],
      optional  : [],
      languages : [],
    },
    optional: {
      optional  : [],
      languages : [],
    }
  },
  selectedLocaleAvailable: false,

  init: function ()
  {
    gUpdateTypes = window.arguments[0];
    gSourceEvent = window.arguments[1];
    for (var i = 2; i < window.arguments.length; ++i)
      this.items.push(window.arguments[i].QueryInterface(nsIUpdateItem));

    var pref = Components.classes["@mozilla.org/preferences-service;1"]
                         .getService(Components.interfaces.nsIPrefBranch);
    this.shouldSuggestAutoChecking = (gSourceEvent == nsIUpdateService.SOURCE_EVENT_MISMATCH) && 
                                      !pref.getBoolPref(PREF_UPDATE_EXTENSIONS_AUTOUPDATEENABLED);

    if (gSourceEvent != nsIUpdateService.SOURCE_EVENT_MISMATCH)
      document.documentElement.advance();
    else 
      gMismatchPage.init();
  },
  
  uninit: function ()
  {
    // Ensure all observers are unhooked, just in case something goes wrong or the
    // user aborts. 
    gUpdatePage.uninit();  
  },
  
  onWizardFinish: function ()
  {
    var pref = Components.classes["@mozilla.org/preferences-service;1"]
                         .getService(Components.interfaces.nsIPrefBranch);
    if (this.shouldSuggestAutoChecking)
      pref.setBoolPref("update.extensions.enabled", this.shouldAutoCheck); 
    
    if (this.succeeded) {
      if (this.updatingApp) {
        // Clear the "app update available" pref as an interim amnesty assuming
        // the user actually does install the new version. If they don't, a subsequent
        // update check will poke them again.
        this.clearAppUpdatePrefs();
      }
      else {
        // Downloading and Installed Extension
        this.clearExtensionUpdatePrefs();
      }
    }

#ifdef MOZ_PHOENIX
    if (this.offeredResetHomepage) {
      pref.setBoolPref("browser.update.resetHomepage", 
                       document.getElementById("resetHomepage").checked); 
    }
#endif
    
    // Send an event to refresh any FE notification components. 
    var os = Components.classes["@mozilla.org/observer-service;1"]
                       .getService(Components.interfaces.nsIObserverService);
    var userEvt = Components.interfaces.nsIUpdateService.SOURCE_EVENT_USER;
    os.notifyObservers(null, "Update:Ended", userEvt.toString());
  },
  
  clearAppUpdatePrefs: function ()
  {
    var pref = Components.classes["@mozilla.org/preferences-service;1"]
                         .getService(Components.interfaces.nsIPrefBranch);
                         
    // Set the "applied app updates this session" pref so that the update service
    // does not display the update notifier for application updates anymore this
    // session, to give the user a one-cycle amnesty to install the newer version.
    var updates = Components.classes["@mozilla.org/updates/update-service;1"]
                            .getService(Components.interfaces.nsIUpdateService);
    updates.appUpdatesAvailable = false;
    
    pref.setBoolPref(PREF_UPDATE_APP_PERFORMED, true);    

    // Unset prefs used by the update service to signify application updates
    if (pref.prefHasUserValue(PREF_UPDATE_APP_UPDATESAVAILABLE))
      pref.clearUserPref(PREF_UPDATE_APP_UPDATESAVAILABLE);

    // Lower the severity to reflect the fact that there are now only Extension/
    // Theme updates available
    var newCount = pref.getIntPref(PREF_UPDATE_EXTENSIONS_COUNT);
    var threshold = pref.getIntPref(PREF_UPDATE_EXTENSIONS_SEVERITY_THRESHOLD);
    if (newCount >= threshold)
      pref.setIntPref(PREF_UPDATE_SEVERITY, nsIUpdateService.SEVERITY_MEDIUM);
    else
      pref.setIntPref(PREF_UPDATE_SEVERITY, nsIUpdateService.SEVERITY_LOW);
  },
  
  clearExtensionUpdatePrefs: function ()
  {
    var pref = Components.classes["@mozilla.org/preferences-service;1"]
                         .getService(Components.interfaces.nsIPrefBranch);
                         
    // Set the "applied extension updates this session" pref so that the 
    // update service does not display the update notifier for extension
    // updates anymore this session (the updates will be applied at the next
    // start).
    var updates = Components.classes["@mozilla.org/updates/update-service;1"]
                            .getService(Components.interfaces.nsIUpdateService);
    updates.extensionUpdatesAvailable = this.remainingExtensionUpdateCount;
    
    if (pref.prefHasUserValue(PREF_UPDATE_EXTENSIONS_COUNT)) 
      pref.clearUserPref(PREF_UPDATE_EXTENSIONS_COUNT);
  },
  
  _setUpButton: function (aButtonID, aButtonKey, aDisabled)
  {
    var strings = document.getElementById("updateStrings");
    var button = document.documentElement.getButton(aButtonID);
    if (aButtonKey) {
      button.label = strings.getString(aButtonKey);
      try {
        button.accesskey = strings.getString(aButtonKey + "Accesskey");
      }
      catch (e) {
      }
    }
    button.disabled = aDisabled;
  },
  
  setButtonLabels: function (aBackButton, aBackButtonIsDisabled, 
                             aNextButton, aNextButtonIsDisabled,
                             aCancelButton, aCancelButtonIsDisabled)
  {
    
    this._setUpButton("back", aBackButton, aBackButtonIsDisabled);
    this._setUpButton("next", aNextButton, aNextButtonIsDisabled);
    this._setUpButton("cancel", aCancelButton, aCancelButtonIsDisabled);
  },
  
  /////////////////////////////////////////////////////////////////////////////
  // Update Errors
  errorItems: [],
  errorOnApp: false,

  showErrors: function (aState, aErrors)
  {
    openDialog("chrome://mozapps/content/update/errors.xul", "", 
               "modal", { state: aState, errors: aErrors });
  },
  
  showUpdateCheckErrors: function ()
  {
    var errors = [];
    for (var i = 0; i < this.errorItems.length; ++i)
      errors.push({ name: this.errorItems[i].name, error: true, 
                    item: this.errorItems[i] });

    if (this.errorOnApp) {
      var brandShortName = document.getElementById("brandStrings").getString("brandShortName");
      errors.push({ name: brandShortName, error: true });    
    }
    
    this.showErrors("checking", errors);
  },

  checkForErrors: function (aElementIDToShow)
  {
    if (this.errorOnGeneric || this.errorItems.length > 0 || this.errorOnApp)
      document.getElementById(aElementIDToShow).hidden = false;
  },
  
  onWizardClose: function (aEvent)
  {
    if (gInstallingPage._installing) {
      var os = Components.classes["@mozilla.org/observer-service;1"]
                        .getService(Components.interfaces.nsIObserverService);
      os.notifyObservers(null, "xpinstall-progress", "cancel");
      return false;
    }    
    return true;
  }
};

var gMismatchPage = {
  init: function ()
  {
    var incompatible = document.getElementById("mismatch.incompatible");
    for (var i = 0; i < gUpdateWizard.items.length; ++i) {
      var item = gUpdateWizard.items[i];
      var listitem = document.createElement("listitem");
      listitem.setAttribute("label", item.name + " " + item.version);
      incompatible.appendChild(listitem);
    }
  },
  
  onPageShow: function ()
  {
    gUpdateWizard.setButtonLabels(null, true, 
                                  "mismatchCheckNow", false, 
                                  "mismatchDontCheck", false);
    document.documentElement.getButton("next").focus();
  }
};

var gUpdatePage = {
  _completeCount: 0,
  _messages: ["Update:Extension:Started", 
              "Update:Extension:Ended", 
              "Update:Extension:Item-Started", 
              "Update:Extension:Item-Ended",
              "Update:Extension:Item-Error",
              "Update:App:Ended",
              "Update:Ended"],
  
  onPageShow: function ()
  {
    gUpdateWizard.setButtonLabels(null, true, 
                                  "nextButtonText", true, 
                                  "cancelButtonText", false);
    document.documentElement.getButton("next").focus();

    var os = Components.classes["@mozilla.org/observer-service;1"]
                       .getService(Components.interfaces.nsIObserverService);
    for (var i = 0; i < this._messages.length; ++i)
      os.addObserver(this, this._messages[i], false);

    gUpdateWizard.errorItems = [];

    var updates = Components.classes["@mozilla.org/updates/update-service;1"]
                            .getService(Components.interfaces.nsIUpdateService);
    updates.checkForUpdatesInternal(gUpdateWizard.items, gUpdateWizard.items.length, 
                                    gUpdateTypes, gSourceEvent);
  },

  _destroyed: false,  
  uninit: function ()
  {
    if (this._destroyed)
      return;
  
    var os = Components.classes["@mozilla.org/observer-service;1"]
                       .getService(Components.interfaces.nsIObserverService);
    for (var i = 0; i < this._messages.length; ++i)
      os.removeObserver(this, this._messages[i]);

    this._destroyed = true;
  },

  _totalCount: 0,
  get totalCount()
  {
    if (!this._totalCount) {
      this._totalCount = gUpdateWizard.items.length;
      if (this._totalCount == 0) {
        var em = Components.classes["@mozilla.org/extensions/manager;1"]
                           .getService(Components.interfaces.nsIExtensionManager);
        var extensionCount = em.getItemList(nsIUpdateItem.TYPE_EXTENSION, {}).length;
        var themeCount = em.getItemList(nsIUpdateItem.TYPE_THEME, {}).length;

        this._totalCount = extensionCount + themeCount + 1;
      }
    }
    return this._totalCount;
  },  
  
  observe: function (aSubject, aTopic, aData)
  {
    var canFinish = false;
    switch (aTopic) {
    case "Update:Extension:Started":
      break;
    case "Update:Extension:Item-Started":
      break;
    case "Update:Extension:Item-Ended":
      if (aSubject) {
        var item = aSubject.QueryInterface(Components.interfaces.nsIUpdateItem);
        gUpdateWizard.itemsToUpdate.push(item);
      
        var updateStrings = document.getElementById("updateStrings");
        var status = document.getElementById("checking.status");
        var statusString = updateStrings.getFormattedString("checkingPrefix", [item.name]);
        status.setAttribute("value", statusString);
      }
      ++this._completeCount;

      // Update the Progress Bar            
      var progress = document.getElementById("checking.progress");
      progress.value = Math.ceil((this._completeCount / this.totalCount) * 100);
      
      break;
    case "Update:Extension:Item-Error":
      if (aSubject) {
        var item = aSubject.QueryInterface(Components.interfaces.nsIUpdateItem);
        gUpdateWizard.errorItems.push(item);
      }
      else {
        for (var i = 0; i < gUpdateWizard.items.length; ++i) {
          if (!gUpdateWizard.items[i].updateRDF)
            gUpdateWizard.errorItems.push(gUpdateWizard.items[i]);
        }
      }
      ++this._completeCount;
      var progress = document.getElementById("checking.progress");
      progress.value = Math.ceil((this._completeCount / this.totalCount) * 100);

      break;
    case "Update:Extension:Ended":
      // If we were passed a set of extensions/themes/other to update, this
      // means we're not checking for app updates, so don't wait for the app
      // update to complete before advancing (because there is none).
      // canFinish = gUpdateWizard.items.length > 0;
      // XXXben
      break;
    case "Update:Ended":
      // If we're doing a general update check, (that is, no specific extensions/themes
      // were passed in for us to check for updates to), this notification means both
      // extension and app updates have completed.
      canFinish = true;
      break;
    case "Update:App:Error":
      gUpdateWizard.errorOnApp = true;
      ++this._completeCount;
      var progress = document.getElementById("checking.progress");
      progress.value = Math.ceil((this._completeCount / this.totalCount) * 100);
      break;
    case "Update:App:Ended":
      // The "Updates Found" page of the update wizard needs to know if it there are app 
      // updates so it can list them first. 
      ++this._completeCount;
      var progress = document.getElementById("checking.progress");
      progress.value = Math.ceil((this._completeCount / this.totalCount) * 100);
      break;
    }

    if (canFinish) {
      gUpdatePage.uninit();
      var updates = Components.classes["@mozilla.org/updates/update-service;1"]
                              .getService(Components.interfaces.nsIUpdateService);
      if ((gUpdateTypes & nsIUpdateItem.TYPE_ADDON && gUpdateWizard.itemsToUpdate.length > 0) || 
          (gUpdateTypes & nsIUpdateItem.TYPE_APP && updates.appUpdatesAvailable))
        document.getElementById("checking").setAttribute("next", "found");
      document.documentElement.advance();
    }
  }
};

var gFoundPage = {
  _appUpdateExists: false,
  _appSelected: false, 
  _appItem: null,
  _nonAppItems: [],
  
  _newestInfo: null,
  _currentInfo: null,
  
  buildAddons: function ()
  {
    var hasExtensions = false;
    var foundAddonsList = document.getElementById("found.addons.list");
    var uri = Components.classes["@mozilla.org/network/standard-url;1"]
                        .createInstance(Components.interfaces.nsIURI);
    var itemCount = gUpdateWizard.itemsToUpdate.length;
    for (var i = 0; i < itemCount; ++i) {
      var item = gUpdateWizard.itemsToUpdate[i];
      var checkbox = document.createElement("checkbox");
      foundAddonsList.appendChild(checkbox);
      checkbox.setAttribute("type", "update");
      checkbox.label        = item.name + " " + item.version;
      checkbox.URL          = item.xpiURL;
      checkbox.infoURL      = "";
      checkbox.internalName = "";
      uri.spec              = item.xpiURL;
      checkbox.source       = uri.host;
      checkbox.checked      = true;
      hasExtensions         = true;
    }

    if (hasExtensions) {
      var addonsHeader = document.getElementById("addons");
      var strings = document.getElementById("updateStrings");
      addonsHeader.label = strings.getFormattedString("updateTypeExtensions", [itemCount]);
      addonsHeader.collapsed = false;
    }
  },

  buildPatches: function (aPatches)
  {
    var needsPatching = false;
    var critical = document.getElementById("found.criticalUpdates.list");
    var uri = Components.classes["@mozilla.org/network/standard-url;1"]
                        .createInstance(Components.interfaces.nsIURI);
    var count = 0;
    for (var i = 0; i < aPatches.length; ++i) {
      var ver = InstallTrigger.getVersion(aPatches[i].internalName);
      if (InstallTrigger.getVersion(aPatches[i].internalName)) {
        // The user has already installed this patch since info
        // about it exists in the Version Registry. Skip. 
        continue;
      }
      
      var checkbox = document.createElement("checkbox");
      critical.appendChild(checkbox);
      checkbox.setAttribute("type", "update");
      checkbox.label        = aPatches[i].name;
      checkbox.URL          = aPatches[i].URL;
      checkbox.infoURL      = aPatches[i].infoURL;
      checkbox.internalName = aPatches[i].internalName;
      uri.spec              = checkbox.URL;
      checkbox.source       = uri.host;
      checkbox.checked      = true;
      needsPatching         = true;
      ++count;
    }
    
    if (needsPatching) {
      var patchesHeader = document.getElementById("patches");
      var strings = document.getElementById("updateStrings");
      patchesHeader.label = strings.getFormattedString("updateTypePatches", [count]);
      patchesHeader.collapsed = false;
    }
  },
  
  buildApp: function (aFiles)
  {
    // A New version of the app is available. 
    var app = document.getElementById("app");
    var strings = document.getElementById("updateStrings");
    var brandStrings = document.getElementById("brandStrings");
    var brandShortName = brandStrings.getString("brandShortName");
    app.label = strings.getFormattedString("appNameAndVersionFormat", 
                                            [brandShortName, 
                                             this._newestInfo.updateDisplayVersion]);
    app.accesskey = brandShortName.charAt(0);
    app.collapsed = false;

    var foundAppLabel = document.getElementById("found.app.label");
    var text = strings.getFormattedString("foundAppLabel",
                                          [brandShortName, 
                                           this._newestInfo.updateDisplayVersion])
    foundAppLabel.appendChild(document.createTextNode(text));

    var features = this._newestInfo.getCollection("features", { });
    if (features) {
      var foundAppFeatures = document.getElementById("found.app.features");
      foundAppFeatures.hidden = false;
      text = strings.getFormattedString("foundAppFeatures", 
                                        [brandShortName, 
                                         this._newestInfo.updateDisplayVersion]);
      foundAppFeatures.appendChild(document.createTextNode(text));

      var foundAppFeaturesList = document.getElementById("found.app.featuresList");
      for (var i = 0; i < features.length; ++i) {
        var feature = document.createElement("label");
        foundAppFeaturesList.appendChild(feature);
        feature.setAttribute("value", features[i].name);
      }
    }
    
    var foundAppInfoLink = document.getElementById("found.app.infoLink");
    foundAppInfoLink.href = this._newestInfo.updateInfoURL;
  },
  
  buildOptional: function (aComponents)
  {
    var needsOptional = false;
    var critical = document.getElementById("found.components.list");
    var uri = Components.classes["@mozilla.org/network/standard-url;1"]
                        .createInstance(Components.interfaces.nsIURI);
    var count = 0;
    for (var i = 0; i < aComponents.length; ++i) {
      if (InstallTrigger.getVersion(aComponents[i].internalName)) {
        // The user has already installed this patch since info
        // about it exists in the Version Registry. Skip. 
        continue;
      }
      
      var checkbox = document.createElement("checkbox");
      critical.appendChild(checkbox);
      checkbox.setAttribute("type", "update");
      checkbox.label        = aComponents[i].name;
      checkbox.URL          = aComponents[i].URL;
      checkbox.infoURL      = aComponents[i].infoURL;
      checkbox.internalName = aComponents[i].internalName;
      uri.spec              = checkbox.URL;
      checkbox.source       = uri.host;
      needsOptional         = true;
      ++count;
    }
    
    if (needsOptional) {
      var optionalHeader = document.getElementById("components");
      var strings = document.getElementById("updateStrings");
      optionalHeader.label = strings.getFormattedString("updateTypeComponents", [count]);
      optionalHeader.collapsed = false;
    }
  },

  buildLanguages: function (aLanguages)
  {
    var hasLanguages = false;
    var languageList = document.getElementById("found.languages.list");
    var uri = Components.classes["@mozilla.org/network/standard-url;1"]
                        .createInstance(Components.interfaces.nsIURI);
    var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
                       .getService(Components.interfaces.nsIXULChromeRegistry);
    var selectedLocale = cr.getSelectedLocale("global");
    var count = 0;
    for (var i = 0; i < aLanguages.length; ++i) {
      if (aLanguages[i].internalName == selectedLocale)
        continue;
      var checkbox = document.createElement("checkbox");
      languageList.appendChild(checkbox);
      checkbox.setAttribute("type", "update");
      checkbox.label        = aLanguages[i].name;
      checkbox.URL          = aLanguages[i].URL;
      checkbox.infoURL      = aLanguages[i].infoURL;
      checkbox.internalName = aLanguages[i].internalName;
      uri.spec              = checkbox.URL;
      checkbox.source       = uri.host;
      hasLanguages          = true;
      ++count;
    }
    
    if (hasLanguages) {
      var languagesHeader = document.getElementById("found.languages.header");
      var strings = document.getElementById("updateStrings");
      languagesHeader.label = strings.getFormattedString("updateTypeLangPacks", [count]);
      languagesHeader.collapsed = false;
    }
  },

  _initialized: false,
  onPageShow: function ()
  {
    gUpdateWizard.setButtonLabels(null, true, 
                                  "installButtonText", false, 
                                  null, false);
    document.documentElement.getButton("next").focus();
    
    var updates = document.getElementById("found.updates");
    if (!this._initialized) {
      this._initialized = true;
      
      updates.computeSizes();

      // Don't show the app update option or critical updates if the user has 
      // already installed an app update but has not yet restarted. 
      var pref = Components.classes["@mozilla.org/preferences-service;1"]
                          .getService(Components.interfaces.nsIPrefBranch);
      var updatePerformed = pref.getBoolPref(PREF_UPDATE_APP_PERFORMED);
      if (gUpdateTypes & nsIUpdateItem.TYPE_APP) {
        var updatesvc = Components.classes["@mozilla.org/updates/update-service;1"]
                                  .getService(Components.interfaces.nsIUpdateService);
        this._currentInfo = updatesvc.currentVersion;
        if (this._currentInfo) {
          var patches = this._currentInfo.getCollection("patches", { });
          if (patches.length > 0 && !updatePerformed)
            this.buildPatches(patches);

          // Turning this off until we can better determine what is and is not installed. 
          // var components = this._currentInfo.getCollection("optional", { });
          // if (components.length > 0)
          //   this.buildOptional(components);
 
          var languages = this._currentInfo.getCollection("languages", { });
          if (languages.length > 0)
            this.buildLanguages(languages);
        }
        
        this._newestInfo = updatesvc.newestVersion;
        if (this._newestInfo) {
          var languages = this._newestInfo.getCollection("languages", { });
          var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
                            .getService(Components.interfaces.nsIXULChromeRegistry);
          var selectedLocale = cr.getSelectedLocale("global");
          var haveLanguage = false;
          for (var i = 0; i < languages.length; ++i) {
            if (languages[i].internalName == selectedLocale)
              haveLanguage = true;
          }

          var files = this._newestInfo.getCollection("files", { });
#ifndef XP_MACOSX
          if (files.length > 0 && haveLanguage && !updatePerformed) 
            this.buildApp(files);
#endif
            
          // When the user upgrades the application, any optional components that
          // they have installed are automatically installed. If there are remaining
          // optional components that are not currently installed, then these
          // are offered as an option.
          var components = this._newestInfo.getCollection("optional", { });
          for (var i = 0; i < components.length; ++i) {
            if (InstallTrigger.getVersion(components[i].internalName))
              gUpdateWizard.appComps.upgraded.optional.push(components[i]);
            else
              gUpdateWizard.appComps.optional.optional.push(components[i]);
          }
          
          var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
                            .getService(Components.interfaces.nsIXULChromeRegistry);
          var selectedLocale = cr.getSelectedLocale("global");
          gUpdateWizard.selectedLocaleAvailable = false;
          var languages = this._newestInfo.getCollection("languages", { });
          for (i = 0; i < languages.length; ++i) {
            if (languages[i].internalName == selectedLocale) {
              gUpdateWizard.selectedLocaleAvailable = true;
              gUpdateWizard.appComps.upgraded.languages.push(languages[i]);
            }
          }
          
          if (!gUpdateWizard.selectedLocaleAvailable)
            gUpdateWizard.appComps.optional.languages = gUpdateWizard.appComps.optional.languages.concat(languages);
            
          gUpdateWizard.appComps.upgraded.core = gUpdateWizard.appComps.upgraded.core.concat(files);
        }
      }

      if (gUpdateTypes & nsIUpdateItem.TYPE_ADDON)
        this.buildAddons();
    }
        
    var kids = updates._getRadioChildren();
    for (var i = 0; i < kids.length; ++i) {
      if (kids[i].collapsed == false) {
        updates.selectedIndex = i;
        break;
      }
    }
  },
    
  onSelect: function (aEvent)
  {
    var updates = document.getElementById("found.updates");
    var oneChecked = true;
    if (updates.selectedItem.id != "app") {
      oneChecked = false;
      var items = updates.selectedItem.getElementsByTagName("checkbox");
      for (var i = 0; i < items.length; ++i) {  
        if (items[i].checked) {
          oneChecked = true;
          break;
        }
      }
    }

    var strings = document.getElementById("updateStrings");
    var text;
    if (aEvent.target.selectedItem.id == "app") {
      if (gUpdateWizard.appComps.optional.optional.length > 0) {
        gUpdateWizard.setButtonLabels(null, true, 
                                      "nextButtonText", true, 
                                      null, false);
          
        text = strings.getString("foundInstructionsAppComps");
        document.getElementById("found").setAttribute("next", "optional"); 
      }
      gUpdateWizard.updatingApp = true;
    }
    else {
      gUpdateWizard.setButtonLabels(null, true, 
                                    "installButtonText", true, 
                                    null, false);
      text = strings.getString("foundInstructions");
      document.getElementById("found").setAttribute("next", "installing"); 
      
      gUpdateWizard.updatingApp = false;
    }
        
    document.documentElement.getButton("next").disabled = !oneChecked;

    var foundInstructions = document.getElementById("foundInstructions");
    while (foundInstructions.hasChildNodes())
      foundInstructions.removeChild(foundInstructions.firstChild);
    foundInstructions.appendChild(document.createTextNode(text));
  }
};

var gOptionalPage = {
  onPageShow: function ()
  {
    gUpdateWizard.setButtonLabels(null, false, 
                                  "installButtonText", false, 
                                  null, false);

    var optionalItemsList = document.getElementById("optionalItemsList");
    while (optionalItemsList.hasChildNodes())
      optionalItemsList.removeChild(optionalItemsList.firstChild);

    for (var i = 0; i < gUpdateWizard.appComps.optional.optional.length; ++i) {
      var checkbox = document.createElement("checkbox");
      checkbox.setAttribute("label", gUpdateWizard.appComps.optional.optional[i].name);
      checkbox.setAttribute("index", i);
      optionalItemsList.appendChild(checkbox);
    }
    
    document.documentElement.getButton("next").focus(); 
  },
  
  onCommand: function (aEvent)
  {
    if (aEvent.target.localName == "checkbox") {
      gUpdateWizard.appComps.upgraded.optional = [];
      var optionalItemsList = document.getElementById("optionalItemsList");
      var checkboxes = optionalItemsList.getElementsByTagName("checkbox");
      for (var i = 0; i < checkboxes.length; ++i) {
        if (checkboxes[i].checked) {
          var index = parseInt(checkboxes[i].getAttribute("index"));
          var item = gUpdateWizard.appComps.optional.optional[index];
          gUpdateWizard.appComps.upgraded.optional.push(item);
        }
      }
    }
  },
  
  onListMouseOver: function (aEvent)
  {
    if (aEvent.target.localName == "checkbox") {
      var index = parseInt(aEvent.target.getAttribute("index"));
      var desc = gUpdateWizard.appComps.optional.optional[index].description;
      var optionalDescription = document.getElementById("optionalDescription");
      while (optionalDescription.hasChildNodes()) 
        optionalDescription.removeChild(optionalDescription.firstChild);
      optionalDescription.appendChild(document.createTextNode(desc));
    }
  },
  
  onListMouseOut: function (aEvent)
  {
    if (aEvent.target.localName == "vbox") {
      var optionalDescription = document.getElementById("optionalDescription");
      while (optionalDescription.hasChildNodes()) 
        optionalDescription.removeChild(optionalDescription.firstChild);
    }
  }
};

var gInstallingPage = {
  _installing       : false,
  _restartRequired  : false,
  _objs             : [],
  
  onPageShow: function ()
  {
    gUpdateWizard.setButtonLabels(null, true, 
                                  "nextButtonText", true, 
                                  null, true);

    // Get XPInstallManager and kick off download/install 
    // process, registering us as an observer. 
    var items = [];
    this._objs = [];
    
    this._restartRequired = false;
    
    gUpdateWizard.remainingExtensionUpdateCount = gUpdateWizard.itemsToUpdate.length;

    var updates = document.getElementById("found.updates");
    if (updates.selectedItem.id != "app") {
      var checkboxes = updates.selectedItem.getElementsByTagName("checkbox");
      for (var i = 0; i < checkboxes.length; ++i) {
        if (checkboxes[i].type == "update" && checkboxes[i].checked) {
          items.push(checkboxes[i].URL);
          this._objs.push({ name: checkboxes[i].label });
        }
      }
    }
    else {
      // To install an app update we need to collect together the following
      // sets of files:
      // - core files
      // - optional components (we need to show another page) 
      // - selected language, if the available language set does not match
      //   the one currently selected for the "global" package
      // Order is *probably* important here.      
      for (var i = 0; i < gUpdateWizard.appComps.upgraded.core.length; ++i) {
        items.push(gUpdateWizard.appComps.upgraded.core[i].URL);
        this._objs.push({ name: gUpdateWizard.appComps.upgraded.core[i].name });
      }
      for (var i = 0; i < gUpdateWizard.appComps.upgraded.languages.length; ++i) {
        items.push(gUpdateWizard.appComps.upgraded.languages[i].URL);
        this._objs.push({ name: gUpdateWizard.appComps.upgraded.languages[i].name });
      }
      for (var i = 0; i < gUpdateWizard.appComps.upgraded.optional.length; ++i) {
        items.push(gUpdateWizard.appComps.upgraded.optional[i].URL);
        this._objs.push({ name: gUpdateWizard.appComps.upgraded.optional[i].name });
      }
    }
    
    var xpimgr = Components.classes["@mozilla.org/xpinstall/install-manager;1"]
                           .createInstance(Components.interfaces.nsIXPInstallManager);
    xpimgr.initManagerFromChrome(items, items.length, this);
  },
  
  /////////////////////////////////////////////////////////////////////////////
  // nsIXPIProgressDialog
  onStateChange: function (aIndex, aState, aValue)
  {
    var strings = document.getElementById("updateStrings");

    const nsIXPIProgressDialog = Components.interfaces.nsIXPIProgressDialog;
    switch (aState) {
    case nsIXPIProgressDialog.DOWNLOAD_START:
      var label = strings.getFormattedString("downloadingPrefix", [this._objs[aIndex].name]);
      var actionItem = document.getElementById("actionItem");
      actionItem.value = label;
      break;
    case nsIXPIProgressDialog.DOWNLOAD_DONE:
    case nsIXPIProgressDialog.INSTALL_START:
      var label = strings.getFormattedString("installingPrefix", [this._objs[aIndex].name]);
      var actionItem = document.getElementById("actionItem");
      actionItem.value = label;
      this._installing = true;
      break;
    case nsIXPIProgressDialog.INSTALL_DONE:
      switch (aValue) {
      case 999: 
        this._restartRequired = true;
        break;
      case 0: 
        --gUpdateWizard.remainingExtensionUpdateCount;
        break;
      default:
        // XXXben ignore chrome registration errors hack!
        if (!(aValue == -239 && gUpdateWizard.updatingApp)) {
          this._objs[aIndex].error = aValue;
          this._errors = true;
        }
        break;
      }
      break;
    case nsIXPIProgressDialog.DIALOG_CLOSE:
      this._installing = false;
      var nextPage = this._errors ? "errors" : (this._restartRequired ? "restart" : "finished");
      document.getElementById("installing").setAttribute("next", nextPage);
      document.documentElement.advance();
      break;
    }
  },
  
  _objs: [],
  _errors: false,
  
  onProgress: function (aIndex, aValue, aMaxValue)
  {
    var downloadProgress = document.getElementById("downloadProgress");
    downloadProgress.value = Math.ceil((aValue/aMaxValue) * 100);
  }
};

var gErrorsPage = {
  onPageShow: function ()
  {
    document.documentElement.getButton("finish").focus();
    gUpdateWizard.succeeded = false;
  },
  
  onShowErrors: function ()
  {
    gUpdateWizard.showErrors("install", gInstallingPage._objs);
  }  
};

var gFinishedPage = {
  onPageShow: function ()
  {
    gUpdateWizard.setButtonLabels(null, true, null, true, null, true);
    document.documentElement.getButton("finish").focus();
    
    var iR = document.getElementById("incompatibleRemaining");
    var iR2 = document.getElementById("incompatibleRemaining2");
    var fEC = document.getElementById("finishedEnableChecking");

    if (gUpdateWizard.shouldSuggestAutoChecking) {
      iR.hidden = true;
      iR2.hidden = false;
      fEC.hidden = false;
      fEC.click();
    }
    else {
      iR.hidden = false;
      iR2.hidden = true;
      fEC.hidden = true;
    }
    
    if (gSourceEvent == nsIUpdateService.SOURCE_EVENT_MISMATCH) {
      document.getElementById("finishedMismatch").hidden = false;
      document.getElementById("incompatibleAlert").hidden = false;
    }
  }
};

var gRestartPage = {
  onPageShow: function ()
  {
    gUpdateWizard.setButtonLabels(null, true, null, true, null, true);
    
    // XXXben - we should really have a way to restart the app now from here!
    
#ifdef MOZ_PHOENIX
    gUpdateWizard.offeredResetHomepage = true;
#endif

    document.documentElement.getButton("finish").focus();

    // Create a re-reg chrome marker to tell the Chrome Registry to rebuild the
    // Chrome Reg Datasource and overlayinfo directory on the next start since 
    // we've upgraded in-place (and thus chrome.rdf's mtime is newer than the
    // installed-chrome.txt file that was installed by the update process since
    // chrome.rdf was flushed when the browser started, AFTER installed-chrome.txt
    // was created.
    var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"]
                                .getService(Components.interfaces.nsIProperties);
    var file = fileLocator.get("AChrom", Components.interfaces.nsIFile);
    file.append(".reregchrome");
    if (!file.exists())
      file.create(Components.interfaces.nsIFile.FILE_TYPE, 0644);
  }
};

var gNoUpdatesPage = {
  onPageShow: function (aEvent)
  {
    gUpdateWizard.setButtonLabels(null, true, null, true, null, true);
    document.documentElement.getButton("finish").focus();
    if (gSourceEvent == nsIUpdateService.SOURCE_EVENT_MISMATCH) {
      document.getElementById("introUser").hidden = true;
      document.getElementById("introMismatch").hidden = false;
      document.getElementById("mismatchNoUpdates").hidden = false;
        
      if (gUpdateWizard.shouldSuggestAutoChecking) {
        document.getElementById("mismatchIncompatibleRemaining").hidden = true;
        document.getElementById("mismatchIncompatibleRemaining2").hidden = false;
        document.getElementById("mismatchFinishedEnableChecking").hidden = false;
      }
    }

    gUpdateWizard.succeeded = false;
    gUpdateWizard.checkForErrors("updateCheckErrorNotFound");
  }
};

