# -*- 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 Extension Manager.
#
# 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@mozilla.org>
#   Robert Strong <robert.bugzilla@gmail.com>
#   Dão Gottwald <dao@design-noir.de>
#   Dave Townsend <dtownsend@oxymoronical.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 *****

///////////////////////////////////////////////////////////////////////////////
// Globals
const nsIExtensionManager    = Components.interfaces.nsIExtensionManager;
const nsIUpdateItem          = Components.interfaces.nsIUpdateItem;
const nsIFilePicker          = Components.interfaces.nsIFilePicker;
const nsIIOService           = Components.interfaces.nsIIOService;
const nsIFileProtocolHandler = Components.interfaces.nsIFileProtocolHandler;
const nsIURL                 = Components.interfaces.nsIURL;
const nsIAppStartup          = Components.interfaces.nsIAppStartup;
const nsIBlocklistService    = Components.interfaces.nsIBlocklistService;
const nsIPrefBranch2         = Components.interfaces.nsIPrefBranch2;

var gView             = null;
var gExtensionManager = null;
var gExtensionsView   = null;
var gExtensionStrings = null;
var gDownloadManager  = null;
var gObserverIndex    = -1;
var gInSafeMode       = false;
var gCheckCompat      = true;
var gCheckUpdateSecurity = true;
var gUpdatesOnly      = false;
var gPluginUpdateUrl  = null;
var gAppID            = "";
var gPref             = null;
var gPriorityCount    = 0;
var gInstalling       = false;
var gPendingActions   = false;
var gPlugins          = null;
var gPluginsDS        = null;
var gSearchDS         = null;
var gLWThemeDS        = null;
var gAddonRepository  = null;
var gShowGetAddonsPane = false;
var gRetrievedResults = false;
var gRecommendedAddons = null;
var gRDF              = null;
var gPendingInstalls  = {};
var gNewAddons        = [];
var gCheckCompatibilityPref;

// The default heavyweight theme for the app.
var gDefaultTheme     = null;
// The heavyweight theme currently in use.
var gCurrentTheme     = null;
// The heavyweight theme to switch to after the application is restarted.
// This will be equal to gCurrentTheme if no theme change is pending.
var gThemeToSelect    = null;

// The lightweight theme to switch to after the application is restarted.
// This will be equal to LightweightThemeManager.currentTheme if no lightweight
// theme change is pending or null if lightweight theme should be turned off
// after the restart.
var gLWThemeToSelect  = null;

const PREF_EM_CHECK_COMPATIBILITY           = "extensions.checkCompatibility";
const PREF_EM_CHECK_UPDATE_SECURITY         = "extensions.checkUpdateSecurity";
const PREF_EXTENSIONS_GETMORETHEMESURL      = "extensions.getMoreThemesURL";
const PREF_EXTENSIONS_GETMOREEXTENSIONSURL  = "extensions.getMoreExtensionsURL";
const PREF_EXTENSIONS_GETMOREPLUGINSURL     = "extensions.getMorePluginsURL";
const PREF_EXTENSIONS_DSS_ENABLED           = "extensions.dss.enabled";
const PREF_EXTENSIONS_DSS_SWITCHPENDING     = "extensions.dss.switchPending";
const PREF_EXTENSIONS_HIDE_INSTALL_BTN      = "extensions.hideInstallButton";
const PREF_DSS_SKIN_TO_SELECT               = "extensions.lastSelectedSkin";
const PREF_LWTHEME_TO_SELECT                = "extensions.lwThemeToSelect";
const PREF_GENERAL_SKINS_SELECTEDSKIN       = "general.skins.selectedSkin";
const PREF_UPDATE_NOTIFYUSER                = "extensions.update.notifyUser";
const PREF_GETADDONS_SHOWPANE               = "extensions.getAddons.showPane";
const PREF_GETADDONS_REPOSITORY             = "extensions.getAddons.repository";
const PREF_GETADDONS_MAXRESULTS             = "extensions.getAddons.maxResults";
const PREF_PLUGINS_UPDATEURL                = "plugins.update.url";

const URI_GENERIC_ICON_XPINSTALL      = "chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png";
const URI_GENERIC_ICON_THEME          = "chrome://mozapps/skin/extensions/themeGeneric.png";

#ifdef MOZ_WIDGET_GTK2
const URI_NOTIFICATION_ICON_INFO      = "moz-icon://stock/gtk-dialog-info?size=menu";
const URI_NOTIFICATION_ICON_WARNING   = "moz-icon://stock/gtk-dialog-warning?size=menu";
#else
const URI_NOTIFICATION_ICON_INFO      = "chrome://global/skin/icons/information-16.png";
const URI_NOTIFICATION_ICON_WARNING   = "chrome://global/skin/icons/warning-16.png";
#endif

const RDFURI_ITEM_ROOT    = "urn:mozilla:item:root";
const PREFIX_ITEM_URI     = "urn:mozilla:item:";
const PREFIX_LWTHEME_URI  = "urn:mozilla:lwtheme:";
const PREFIX_NS_EM        = "http://www.mozilla.org/2004/em-rdf#";
const kXULNSURI           = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const XMLURI_PARSE_ERROR  = "http://www.mozilla.org/newlayout/xml/parsererror.xml"

const OP_NONE                         = "";
const OP_NEEDS_INSTALL                = "needs-install";
const OP_NEEDS_UPGRADE                = "needs-upgrade";
const OP_NEEDS_UNINSTALL              = "needs-uninstall";
const OP_NEEDS_ENABLE                 = "needs-enable";
const OP_NEEDS_DISABLE                = "needs-disable";

Components.utils.import("resource://gre/modules/PluralForm.jsm");
Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm");

var gBranchVersion = /^([^\.]+\.[0-9]+[a-z]*).*/gi;

///////////////////////////////////////////////////////////////////////////////
// Utility Functions
function setElementDisabledByID(aID, aDoDisable) {
  var element = document.getElementById(aID);
  if (element) {
    if (aDoDisable)
      element.setAttribute("disabled", "true");
    else
      element.removeAttribute("disabled");
  }
}

/**
 * This returns the richlistitem element for the theme with the given
 * internalName
 */
function getItemForInternalName(aInternalName) {
  var property = gRDF.GetResource(PREFIX_NS_EM + "internalName");
  var name = gRDF.GetLiteral(aInternalName);
  var id = gExtensionManager.datasource.GetSource(property, name, true)
  if (id && id instanceof Components.interfaces.nsIRDFResource)
    return document.getElementById(id.ValueUTF8);
  return null;
}

function getActivedThemeItem() {
  if (gLWThemeToSelect)
    return document.getElementById(PREFIX_LWTHEME_URI + gLWThemeToSelect.id);
  return getItemForInternalName(gThemeToSelect);
}

function isSafeURI(aURI) {
  try {
    var uri = makeURI(aURI);
    var scheme = uri.scheme;
  } catch (ex) {}
  return (uri && (scheme == "http" || scheme == "https" || scheme == "ftp"));
}

function getBrandShortName() {
  var brandStrings = document.getElementById("brandStrings");
  return brandStrings.getString("brandShortName");
}

function getExtensionString(key, strings) {
  if (strings)
    return gExtensionStrings.getFormattedString(key, strings);
  return gExtensionStrings.getString(key);
}

function MessageButton(aLabel, aAccesskey, aData) {
  this.label = aLabel;
  this.accessKey = aAccesskey;
  this.data = aData || "addons-message-dismiss";
}
MessageButton.prototype = {
  label: null,
  accessKey: null,
  data: null,

  callback: function (aNotification, aButton) {
    var os = Components.classes["@mozilla.org/observer-service;1"]
                       .getService(Components.interfaces.nsIObserverService);
    os.notifyObservers(null, "addons-message-notification", aButton.data);
    aNotification.close();
    return true;
  }
};

function showMessage(aIconURL, aMessage, aButtonLabel, aButtonAccesskey,
                     aShowCloseButton, aNotifyData) {
  var addonsMsg = document.getElementById("addonsMsg");
  var buttons = null;
  if (aButtonLabel)
    buttons = [new MessageButton(aButtonLabel, aButtonAccesskey, aNotifyData)];
  var oldMessage = addonsMsg.getNotificationWithValue(aMessage);
  if (oldMessage && oldMessage.parentNode)
    addonsMsg.removeNotification(oldMessage);
  if (addonsMsg.currentNotification)
    gPriorityCount += 0.0001;
  else
    gPriorityCount = 0;
  addonsMsg.appendNotification(aMessage, aMessage, aIconURL,
                               addonsMsg.PRIORITY_WARNING_LOW + gPriorityCount,
                               buttons).hideclose = !aShowCloseButton;
}

// dynamically creates a template
var AddonsViewBuilder = {
  // if aActionList is null aBindingList will be used to generate actions
  updateView: function(aRulesList, aURIList, aBindingList, aActionList)
  {
    var actionList = aActionList ? aActionList : aBindingList;

    this.clearChildren(gExtensionsView);
    var template = document.createElementNS(kXULNSURI, "template");
    gExtensionsView.appendChild(template);
    for (var i = 0; i < aRulesList.length; ++i)
      template.appendChild(this.createRule(aRulesList[i], aURIList[i], aBindingList[i], actionList[i]));

    gExtensionsView.builder.rebuild();
  },

  clearChildren: function(aEl)
  {
    while (aEl.hasChildNodes())
      aEl.removeChild(aEl.lastChild);
  },

  createRule: function(aTriplesList, aURI, aBindingList, aActionList)
  {
    var rule = document.createElementNS(kXULNSURI, "rule");
    var conditions = document.createElementNS(kXULNSURI, "conditions");
    rule.appendChild(conditions);

    var content = document.createElementNS(kXULNSURI, "content");
    content.setAttribute("uri", "?uri");
    conditions.appendChild(content);

    var member = this.createMember("?uri", "?" + aURI);
    conditions.appendChild(member);

    for (var i = 0; i < aTriplesList.length; ++i)
      conditions.appendChild(this.createTriple("?" + aURI, PREFIX_NS_EM + aTriplesList[i][0], aTriplesList[i][1], aTriplesList[i][2]));

    var bindings = document.createElementNS(kXULNSURI, "bindings");
    rule.appendChild(bindings);
    for (i = 0; i < aBindingList.length; ++i)
      bindings.appendChild(this.createBinding(aBindingList[i], aURI));

    var action = document.createElementNS(kXULNSURI, "action");
    rule.appendChild(action);
    var extension = document.createElementNS(kXULNSURI, aURI);
    action.appendChild(extension);
    extension.setAttribute("uri", "?" + aURI);
    for (i = 0; i < aActionList.length; ++i)
      extension.setAttribute(aActionList[i][0], aActionList[i][1]);

    return rule;
  },

  createTriple: function(aSubject, aPredicate, aObject, aParseType)
  {
    var triple = document.createElementNS(kXULNSURI, "triple");
    triple.setAttribute("subject", aSubject);
    triple.setAttribute("predicate", aPredicate);
    triple.setAttribute("object", aObject);
    if (aParseType)
      triple.setAttribute("parsetype", aParseType);
    return triple;
  },

  createBinding: function(aPredicateObjectList, aURI)
  {
    var binding = document.createElementNS(kXULNSURI, "binding");
    binding.setAttribute("subject", "?" + aURI);
    binding.setAttribute("predicate", PREFIX_NS_EM + aPredicateObjectList[0]);
    binding.setAttribute("object", aPredicateObjectList[1]);
    return binding;
  },

  createMember: function(aContainer, aChild)
  {
    var member = document.createElementNS(kXULNSURI, "member");
    member.setAttribute("container", aContainer);
    member.setAttribute("child", aChild);

    return member;
  }
};

function showView(aView) {
  if (gView == aView)
    return;

  updateLastSelected(aView);
  gView = aView;

  // Using disabled to represent add-on state in regards to the EM causes evil
  // focus behavior when used as an element attribute when the element isn't
  // really disabled.
  var bindingList = [ [ ["aboutURL", "?aboutURL"],
                        ["addonID", "?addonID"],
                        ["availableUpdateURL", "?availableUpdateURL"],
                        ["availableUpdateVersion", "?availableUpdateVersion"],
                        ["blocklisted", "?blocklisted"],
                        ["blocklistedsoft", "?blocklistedsoft"],
                        ["outdated", "?outdated"],
                        ["compatible", "?compatible"],
                        ["description", "?description"],
                        ["downloadURL", "?downloadURL"],
                        ["isDisabled", "?isDisabled"],
                        ["homepageURL", "?homepageURL"],
                        ["iconURL", "?iconURL"],
                        ["internalName", "?internalName"],
                        ["locked", "?locked"],
                        ["lwtheme", "?lwtheme"],
                        ["name", "?name"],
                        ["optionsURL", "?optionsURL"],
                        ["opType", "?opType"],
                        ["plugin", "?plugin"],
                        ["previewImage", "?previewImage"],
                        ["satisfiesDependencies", "?satisfiesDependencies"],
                        ["providesUpdatesSecurely", "?providesUpdatesSecurely"],
                        ["type", "?type"],
                        ["updateable", "?updateable"],
                        ["updateURL", "?updateURL"],
                        ["version", "?version"] ] ];
  var displays = [ "richlistitem" ];
  var direction = "ascending";

  var prefURL;
  var showInstallFile = true;
  try {
    showInstallFile = !gPref.getBoolPref(PREF_EXTENSIONS_HIDE_INSTALL_BTN);
  }
  catch (e) { }
  var showCheckUpdatesAll = true;
  var showInstallUpdatesAll = false;
  var showSkip = false;
  switch (aView) {
    case "search":
      var bindingList = [ [ ["action", "?action"],
                            ["addonID", "?addonID"],
                            ["description", "?description"],
                            ["eula", "?eula"],
                            ["homepageURL", "?homepageURL"],
                            ["iconURL", "?iconURL"],
                            ["name", "?name"],
                            ["previewImage", "?previewImage"],
                            ["rating", "?rating"],
                            ["addonType", "?addonType"],
                            ["thumbnailURL", "?thumbnailURL"],
                            ["version", "?version"],
                            ["xpiHash", "?xpiHash"],
                            ["xpiURL", "?xpiURL"],
                            ["typeName", "searchResult"] ],
                          [ ["type", "?type"],
                            ["typeName", "status"],
                            ["count", "?count"],
                            ["link", "?link" ] ] ];
      var types = [ [ ["searchResult", "true", null] ],
                    [ ["statusMessage", "true", null] ] ];
      var displays = [ "richlistitem", "vbox" ];
      direction = "natural";
      showCheckUpdatesAll = false;
      document.getElementById("searchfield").disabled = isOffline("offlineSearchMsg");
      break;
    case "extensions":
      prefURL = PREF_EXTENSIONS_GETMOREEXTENSIONSURL;
      types = [ [ ["type", nsIUpdateItem.TYPE_EXTENSION, "Integer"] ] ];
      break;
    case "themes":
      prefURL = PREF_EXTENSIONS_GETMORETHEMESURL;
      types = [ [ ["type", nsIUpdateItem.TYPE_THEME, "Integer"] ] ];
      break;
    case "locales":
      types = [ [ ["type", nsIUpdateItem.TYPE_LOCALE, "Integer"] ] ];
      break;
    case "plugins":
      prefURL = PREF_EXTENSIONS_GETMOREPLUGINSURL;
      types = [ [ ["plugin", "true", null] ] ];
      if (!gPluginUpdateUrl)
        showCheckUpdatesAll = false;
      break;
    case "updates":
      document.getElementById("updates-view").hidden = false;
      showInstallFile = false;
      showCheckUpdatesAll = false;
      showInstallUpdatesAll = true;
      if (gUpdatesOnly)
        showSkip = true;
      bindingList = [ [ ["aboutURL", "?aboutURL"],
                        ["availableUpdateURL", "?availableUpdateURL"],
                        ["availableUpdateVersion", "?availableUpdateVersion"],
                        ["availableUpdateInfo", "?availableUpdateInfo"],
                        ["blocklisted", "?blocklisted"],
                        ["blocklistedsoft", "?blocklistedsoft"],
                        ["homepageURL", "?homepageURL"],
                        ["iconURL", "?iconURL"],
                        ["internalName", "?internalName"],
                        ["locked", "?locked"],
                        ["name", "?name"],
                        ["opType", "?opType"],
                        ["previewImage", "?previewImage"],
                        ["satisfiesDependencies", "?satisfiesDependencies"],
                        ["providesUpdatesSecurely", "?providesUpdatesSecurely"],
                        ["type", "?type"],
                        ["updateURL", "?updateURL"],
                        ["version", "?version"],
                        ["typeName", "update"] ] ];
      types = [ [ ["availableUpdateVersion", "?availableUpdateVersion", null],
                  ["updateable", "true", null] ] ];
      break;
    case "installs":
      document.getElementById("installs-view").hidden = false;
      showInstallFile = false;
      showCheckUpdatesAll = false;
      showInstallUpdatesAll = false;
      bindingList = [ [ ["aboutURL", "?aboutURL"],
                        ["addonID", "?addonID"],
                        ["availableUpdateURL", "?availableUpdateURL"],
                        ["availableUpdateVersion", "?availableUpdateVersion"],
                        ["blocklisted", "?blocklisted"],
                        ["blocklistedsoft", "?blocklistedsoft"],
                        ["compatible", "?compatible"],
                        ["description", "?description"],
                        ["downloadURL", "?downloadURL"],
                        ["incompatibleUpdate", "?incompatibleUpdate"],
                        ["isDisabled", "?isDisabled"],
                        ["homepageURL", "?homepageURL"],
                        ["iconURL", "?iconURL"],
                        ["internalName", "?internalName"],
                        ["locked", "?locked"],
                        ["name", "?name"],
                        ["optionsURL", "?optionsURL"],
                        ["opType", "?opType"],
                        ["previewImage", "?previewImage"],
                        ["progress", "?progress"],
                        ["state", "?state"],
                        ["type", "?type"],
                        ["updateable", "?updateable"],
                        ["updateURL", "?updateURL"],
                        ["version", "?version"],
                        ["newVersion", "?newVersion"],
                        ["typeName", "install"] ] ];
      types = [ [ ["state", "?state", null] ] ];
      break;
  }

  var showGetMore = false;
  var getMore = document.getElementById("getMore");
  if (prefURL && gPref.getPrefType(prefURL) != nsIPrefBranch2.PREF_INVALID) {
    try {
      getMore.setAttribute("value", getMore.getAttribute("value" + aView));
      var getMoreURL = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
                                 .getService(Components.interfaces.nsIURLFormatter)
                                 .formatURLPref(prefURL);
      getMore.setAttribute("getMoreURL", getMoreURL);
      showGetMore = getMoreURL == "about:blank" ? false : true;
    }
    catch (e) { }
  }
  getMore.hidden = !showGetMore;

  var isThemes = aView == "themes";

  if (aView == "themes" || aView == "extensions" || aView == "plugins") {
    var tooltipAttr = "";
    if (aView == "extensions")
      tooltipAttr = "tooltiptextaddons";
    else
      tooltipAttr = "tooltiptext" + aView;

    var el = document.getElementById("checkUpdatesAllButton");
    el.setAttribute("tooltiptext", el.getAttribute(tooltipAttr));
    if (aView != "plugins") {
      el = document.getElementById("installFileButton");
      el.setAttribute("tooltiptext", el.getAttribute(tooltipAttr));
    }
  }

  document.getElementById("installFileButton").hidden = !showInstallFile;
  document.getElementById("checkUpdatesAllButton").hidden = !showCheckUpdatesAll;
  document.getElementById("installUpdatesAllButton").hidden = !showInstallUpdatesAll;
  document.getElementById("skipDialogButton").hidden = !showSkip;
  document.getElementById("themePreviewArea").hidden = !isThemes;
  document.getElementById("themeSplitter").hidden = !isThemes;
  document.getElementById("showUpdateInfoButton").hidden = aView != "updates";
  document.getElementById("hideUpdateInfoButton").hidden = true;
  document.getElementById("searchPanel").hidden = aView != "search";

  gExtensionsView.setAttribute("sortDirection", direction);
  AddonsViewBuilder.updateView(types, displays, bindingList, null);

  if (aView == "updates" || aView == "installs")
    gExtensionsView.selectedItem = gExtensionsView.children[0];
  else if (isThemes)
    gExtensionsView.selectedItem = getActivedThemeItem();

  if (showSkip) {
    var button = document.getElementById("installUpdatesAllButton");
    button.setAttribute("default", "true");
    window.setTimeout(function () { button.focus(); }, 0);
  } else
    document.getElementById("installUpdatesAllButton").removeAttribute("default");

  if (isThemes)
    onAddonSelect();
  updateGlobalCommands();
}

// manages the last-selected attribute for the view buttons and richlistbox
function updateLastSelected(aView) {
  var viewGroup = document.getElementById("viewGroup");
  if (viewGroup.hasAttribute("last-selected"))
    var lastSelectedView = viewGroup.getAttribute("last-selected");

  if (lastSelectedView && lastSelectedView == gView) {
    var lastViewButton = document.getElementById(lastSelectedView + "-view");
    if (lastViewButton.hasAttribute("persist")) {
      if (gExtensionsView.selectedItem)
        lastViewButton.setAttribute("last-selected", gExtensionsView.selectedItem.id);
      else if (lastViewButton.hasAttribute("last-selected"))
        lastViewButton.removeAttribute("last-selected");
    }
  }
  var viewButton = document.getElementById(aView + "-view");
  if (viewButton.hasAttribute("last-selected")) {
    gExtensionsView.setAttribute("last-selected", viewButton.getAttribute("last-selected"));
  }
  else if (gExtensionsView.hasAttribute("last-selected")) {
    gExtensionsView.clearSelection();
    gExtensionsView.removeAttribute("last-selected");
  }
  viewGroup.selectedItem = viewButton;
  // Only set last-selected for view buttons that have a persist attribute
  // (e.g. they themselves persist the last selected add-on in the view).
  // This prevents opening updates and installs views when they were the last
  // view selected and instead this will open the previously selected
  // extensions, themes, locales, plug-ins, etc. view.
  if (viewButton.hasAttribute("persist"))
    viewGroup.setAttribute("last-selected", aView);
}

function getIDFromResourceURI(aURI)
{
  if (aURI.substring(0, PREFIX_ITEM_URI.length) == PREFIX_ITEM_URI)
    return aURI.substring(PREFIX_ITEM_URI.length);
  return aURI;
}

function showProgressBar() {
  var progressBox = document.getElementById("progressBox");
  progressBox.parentNode.selectedPanel = progressBox;
}

function flushDataSource()
{
  var rds = gExtensionManager.datasource.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource);
  if (rds)
    rds.Flush();
}

function noUpdatesDismiss(aEvent)
{
  window.removeEventListener("select", noUpdatesDismiss, true);

  var children = gExtensionsView.children;
  for (var i = 0; i < children.length; ++i) {
    var child = children[i];
    if (child.hasAttribute("updateStatus"))
      child.removeAttribute("updateStatus");
  }

  if (aEvent.target.localName != "notification")
    document.getElementById("addonsMsg").removeCurrentNotification();
}

function clearRestartMessage()
{
  var children = gExtensionsView.children;
  for (var i = 0; i < children.length; ++i) {
    var item = children[i];
    if (item.hasAttribute("oldDescription")) {
      item.setAttribute("description", item.getAttribute("oldDescription"));
      item.removeAttribute("oldDescription");
    }
  }
}

function setRestartMessage(aItem)
{
  var themeName = aItem.getAttribute("name");
  var restartMessage = getExtensionString("dssSwitchAfterRestart",
                                          [getBrandShortName()]);
  aItem.setAttribute("oldDescription", aItem.getAttribute("description"));
  aItem.setAttribute("description", restartMessage);
}

// Removes any assertions in the datasource about a given resource
function cleanResource(ds, resource) {
  // Remove outward arcs
  var arcs = ds.ArcLabelsOut(resource);
  while (arcs.hasMoreElements()) {
    var arc = arcs.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
    var targets = ds.GetTargets(resource, arc, true);
    while (targets.hasMoreElements()) {
      var value = targets.getNext().QueryInterface(Components.interfaces.nsIRDFNode);
      if (value)
        ds.Unassert(resource, arc, value);
    }
  }
}

// Wipes the datasource clean of assertions
function cleanDataSource(ds, rootctr) {
  // Remove old entries from the list
  var nodes = rootctr.GetElements();
  while (nodes.hasMoreElements()) {
    var node = nodes.getNext()
                    .QueryInterface(Components.interfaces.nsIRDFResource);
    rootctr.RemoveElement(node, false);
    cleanResource(ds, node);
  }
}

// Displays the search status message
function displaySearchThrobber(aKey) {
  var rdfCU = Components.classes["@mozilla.org/rdf/container-utils;1"]
                        .getService(Components.interfaces.nsIRDFContainerUtils);
  var rootctr = rdfCU.MakeSeq(gSearchDS, gRDF.GetResource(RDFURI_ITEM_ROOT));

  cleanDataSource(gSearchDS, rootctr);

  var labelNode = gRDF.GetResource("urn:mozilla:addons:search:status:header");
  rootctr.AppendElement(labelNode);
  gSearchDS.Assert(labelNode,
                   gRDF.GetResource(PREFIX_NS_EM + "statusMessage"),
                   gRDF.GetLiteral("true"),
                   true);
  gSearchDS.Assert(labelNode,
                   gRDF.GetResource(PREFIX_NS_EM + "type"),
                   gRDF.GetLiteral(aKey),
                   true);
}

// Clears the search box and updates the result list
function resetSearch() {
  var searchfield = document.getElementById("searchfield");
  searchfield.value = "";
  searchfield.focus();
  retrieveRepositoryAddons("");
}

// Searches for results
function retrieveRepositoryAddons(aTerms) {
  if (gAddonRepository.isSearching)
    gAddonRepository.cancelSearch();
  if (aTerms) {
    displaySearchThrobber("retrieve-search");
    gAddonRepository.searchAddons(aTerms,
                                  gPref.getIntPref(PREF_GETADDONS_MAXRESULTS),
                                  AddonSearchResults);
  }
  else {
    if (gRecommendedAddons) {
      displaySearchResults(gRecommendedAddons, -1, true);
    }
    else {
      displaySearchThrobber("retrieve-recommended");
      gAddonRepository.retrieveRecommendedAddons(gPref.getIntPref(PREF_GETADDONS_MAXRESULTS),
                                                 RecommendedSearchResults);
    }
  }
  gRetrievedResults = true;
}

// Puts search results into the search datasource
function displaySearchResults(addons, count, isRecommended) {
  var rdfCU = Components.classes["@mozilla.org/rdf/container-utils;1"]
                        .getService(Components.interfaces.nsIRDFContainerUtils);
  var rootctr = rdfCU.MakeSeq(gSearchDS, gRDF.GetResource(RDFURI_ITEM_ROOT));

  gSearchDS.beginUpdateBatch();

  cleanDataSource(gSearchDS, rootctr);

  if (isRecommended) {
    var labelNode = gRDF.GetResource("urn:mozilla:addons:search:status:header");
    rootctr.AppendElement(labelNode);
    gSearchDS.Assert(labelNode,
                     gRDF.GetResource(PREFIX_NS_EM + "statusMessage"),
                     gRDF.GetLiteral("true"),
                     true);
    gSearchDS.Assert(labelNode,
                     gRDF.GetResource(PREFIX_NS_EM + "type"),
                     gRDF.GetLiteral("header-recommended"),
                     true);

    // Locale sensitive sort
    function compare(a, b) {
      return String.localeCompare(a.name, b.name);
    }
    addons.sort(compare);
  }
  
  if (addons.length == 0 && (isRecommended || count > 0)) {
    var labelNode = gRDF.GetResource("urn:mozilla:addons:search:status:noresults");
    rootctr.AppendElement(labelNode);
    gSearchDS.Assert(labelNode,
                     gRDF.GetResource(PREFIX_NS_EM + "statusMessage"),
                     gRDF.GetLiteral("true"),
                     true);
    if (isRecommended) {
      gSearchDS.Assert(labelNode,
                       gRDF.GetResource(PREFIX_NS_EM + "type"),
                       gRDF.GetLiteral("message-norecommended"),
                       true);
    }
    else {
      gSearchDS.Assert(labelNode,
                       gRDF.GetResource(PREFIX_NS_EM + "type"),
                       gRDF.GetLiteral("message-nosearchresults"),
                       true);
    }
  }

  var urlproperties = [ "iconURL", "homepageURL", "thumbnailURL", "xpiURL" ];
  var properties = [ "name", "eula", "iconURL", "homepageURL", "thumbnailURL", "xpiURL", "xpiHash" ];
  for (var i = 0; i < addons.length; i++) {
    var addon = addons[i];
    // Strip out any items with potentially unsafe urls
    var unsafe = false;
    for (var j = 0; j < urlproperties.length; j++) {
      if (!isSafeURI(addon[urlproperties[j]])) {
        unsafe = true;
        break;
      }
    }
    if (unsafe)
      continue;

    var resultNode = gRDF.GetResource("urn:mozilla:addons:search:" + addon.xpiURL);
    gSearchDS.Assert(resultNode,
                     gRDF.GetResource(PREFIX_NS_EM + "addonID"),
                     gRDF.GetLiteral(addon.id),
                     true);
    // Use the short summary for our "description"
    gSearchDS.Assert(resultNode,
                     gRDF.GetResource(PREFIX_NS_EM + "description"),
                     gRDF.GetLiteral(addon.summary),
                     true);
    gSearchDS.Assert(resultNode,
                     gRDF.GetResource(PREFIX_NS_EM + "addonType"),
                     gRDF.GetIntLiteral(addon.type),
                     true);
    if (addon.rating >= 0) {
      gSearchDS.Assert(resultNode,
                       gRDF.GetResource(PREFIX_NS_EM + "rating"),
                       gRDF.GetIntLiteral(addon.rating),
                       gRDF.GetIntLiteral(3),
                       true);
    }

    for (var j = 0; j < properties.length; j++) {
      gSearchDS.Assert(resultNode,
                       gRDF.GetResource(PREFIX_NS_EM + properties[j]),
                       gRDF.GetLiteral(addon[properties[j]]),
                       true);
    }
    gSearchDS.Assert(resultNode,
                     gRDF.GetResource(PREFIX_NS_EM + "searchResult"),
                     gRDF.GetLiteral("true"),
                     true);
    rootctr.AppendElement(resultNode);
  }

  labelNode = gRDF.GetResource("urn:mozilla:addons:search:status:footer");
  rootctr.AppendElement(labelNode);
  gSearchDS.Assert(labelNode,
                   gRDF.GetResource(PREFIX_NS_EM + "statusMessage"),
                   gRDF.GetLiteral("true"),
                   true);
  if (isRecommended) {
    gSearchDS.Assert(labelNode,
                     gRDF.GetResource(PREFIX_NS_EM + "type"),
                     gRDF.GetLiteral("footer-recommended"),
                     true);
    var url = gAddonRepository.getRecommendedURL();
  }
  else {
    gSearchDS.Assert(labelNode,
                     gRDF.GetResource(PREFIX_NS_EM + "type"),
                     gRDF.GetLiteral("footer-search"),
                     true);
    gSearchDS.Assert(labelNode,
                     gRDF.GetResource(PREFIX_NS_EM + "count"),
                     gRDF.GetIntLiteral(count),
                     true);
    var searchfield = document.getElementById("searchfield");
    url = gAddonRepository.getSearchURL(searchfield.value);
  }
  gSearchDS.Assert(labelNode,
                   gRDF.GetResource(PREFIX_NS_EM + "link"),
                   gRDF.GetLiteral(url),
                   true);

  gSearchDS.endUpdateBatch();
}

// Displays the search failure status message
function displaySearchFailure(isRecommended) {
  var rdfCU = Components.classes["@mozilla.org/rdf/container-utils;1"]
                        .getService(Components.interfaces.nsIRDFContainerUtils);
  var rootctr = rdfCU.MakeSeq(gSearchDS, gRDF.GetResource(RDFURI_ITEM_ROOT));

  cleanDataSource(gSearchDS, rootctr);

  var labelNode = gRDF.GetResource("urn:mozilla:addons:search:status:header");
  rootctr.AppendElement(labelNode);
  gSearchDS.Assert(labelNode,
                   gRDF.GetResource(PREFIX_NS_EM + "statusMessage"),
                   gRDF.GetLiteral("true"),
                   true);
  gSearchDS.Assert(labelNode,
                   gRDF.GetResource(PREFIX_NS_EM + "type"),
                   gRDF.GetLiteral(isRecommended ? "recommended-failure" : "search-failure"),
                   true);
}

// nsIAddonSearchResultsCallback for the recommended search
var RecommendedSearchResults = {
  searchSucceeded: function(aAddons, aAddonCount, aTotalResults) {
    gRecommendedAddons = aAddons;
    displaySearchResults(aAddons, aTotalResults, true);
  },

  searchFailed: function() {
    displaySearchFailure(true);
  }
}

// nsIAddonSearchResultsCallback for a standard search
var AddonSearchResults = {
  searchSucceeded: function(aAddons, aAddonCount, aTotalResults) {
    displaySearchResults(aAddons, aTotalResults, false);
  },

  searchFailed: function() {
    displaySearchFailure(false);
  }
}

// Initialises the search repository and the results datasource
function initSearchDS() {
  var repository = "@mozilla.org/extensions/addon-repository;1";
  try {
    var repo = gPref.getCharPref(PREF_GETADDONS_REPOSITORY);
    if (repo in Components.classes)
      repository = repo;
  } catch (e) { }
  gAddonRepository = Components.classes[repository]
                               .createInstance(Components.interfaces.nsIAddonRepository);
  var browseAddons = document.getElementById("browseAddons");
  var homepage = gAddonRepository.homepageURL;
  if (homepage)
    browseAddons.setAttribute("homepageURL", homepage);
  else
    browseAddons.hidden = true;
  gSearchDS = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]
                        .createInstance(Components.interfaces.nsIRDFDataSource);
  gExtensionsView.database.AddDataSource(gSearchDS);
  var ioService = Components.classes["@mozilla.org/network/io-service;1"]
                            .getService(nsIIOService);
  if (!ioService.offline)
    retrieveRepositoryAddons(document.getElementById("searchfield").value);
}

function initLWThemeDS() {
  gLWThemeDS = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]
                         .createInstance(Components.interfaces.nsIRDFDataSource);
  rebuildLWThemeDS();
}

function rebuildLWThemeDS() {
  var rdfCU = Components.classes["@mozilla.org/rdf/container-utils;1"]
                        .getService(Components.interfaces.nsIRDFContainerUtils);
  var rootctr = rdfCU.MakeSeq(gLWThemeDS, gRDF.GetResource(RDFURI_ITEM_ROOT));
  var themes = LightweightThemeManager.usedThemes;

  // Running in a batch stops the template builder from running
  gLWThemeDS.beginUpdateBatch();

  cleanDataSource(gLWThemeDS, rootctr);

  for (var i = 0; i < themes.length; i++) {
    var theme = themes[i];

    if (!("id" in theme))
      continue;

    var themeNode = gRDF.GetResource(PREFIX_LWTHEME_URI + theme.id);
    rootctr.AppendElement(themeNode);
    gLWThemeDS.Assert(themeNode,
                      gRDF.GetResource(PREFIX_NS_EM + "name"),
                      gRDF.GetLiteral(theme.name || ""),
                      true);
    gLWThemeDS.Assert(themeNode,
                      gRDF.GetResource(PREFIX_NS_EM + "addonID"),
                      gRDF.GetLiteral(theme.id),
                      true);
    gLWThemeDS.Assert(themeNode,
                      gRDF.GetResource(PREFIX_NS_EM + "isDisabled"),
                      gRDF.GetLiteral("false"),
                      true);
    gLWThemeDS.Assert(themeNode,
                      gRDF.GetResource(PREFIX_NS_EM + "blocklisted"),
                      gRDF.GetLiteral("false"),
                      true);
    gLWThemeDS.Assert(themeNode,
                      gRDF.GetResource(PREFIX_NS_EM + "blocklistedsoft"),
                      gRDF.GetLiteral("false"),
                      true);
    gLWThemeDS.Assert(themeNode,
                      gRDF.GetResource(PREFIX_NS_EM + "compatible"),
                      gRDF.GetLiteral("true"),
                      true);
    gLWThemeDS.Assert(themeNode,
                      gRDF.GetResource(PREFIX_NS_EM + "lwtheme"),
                      gRDF.GetLiteral("true"),
                      true);
    gLWThemeDS.Assert(themeNode,
                      gRDF.GetResource(PREFIX_NS_EM + "type"),
                      gRDF.GetIntLiteral(nsIUpdateItem.TYPE_THEME),
                      true);
    if (theme.author) {
      gLWThemeDS.Assert(themeNode,
                        gRDF.GetResource(PREFIX_NS_EM + "description"),
                        gRDF.GetLiteral(getExtensionString("lightweightThemeDescription",
                                                           [theme.author])),
                        true);
      gLWThemeDS.Assert(themeNode,
                        gRDF.GetResource(PREFIX_NS_EM + "creator"),
                        gRDF.GetLiteral(theme.author),
                        true);
    }
    if (theme.description) {
      gLWThemeDS.Assert(themeNode,
                        gRDF.GetResource(PREFIX_NS_EM + "lwdescription"),
                        gRDF.GetLiteral(theme.description),
                        true);
    }
    if (theme.homepageURL) {
      gLWThemeDS.Assert(themeNode,
                        gRDF.GetResource(PREFIX_NS_EM + "homepageURL"),
                        gRDF.GetLiteral(theme.homepageURL),
                        true);
    }
    if (theme.previewURL) {
      gLWThemeDS.Assert(themeNode,
                        gRDF.GetResource(PREFIX_NS_EM + "previewImage"),
                        gRDF.GetLiteral(theme.previewURL),
                        true);
    }
    if (theme.iconURL) {
      gLWThemeDS.Assert(themeNode,
                        gRDF.GetResource(PREFIX_NS_EM + "iconURL"),
                        gRDF.GetLiteral(theme.iconURL),
                        true);
    }
  }

  gLWThemeDS.endUpdateBatch();
}

function initPluginsDS()
{
  gPluginsDS = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]
                         .createInstance(Components.interfaces.nsIRDFDataSource);
  rebuildPluginsDS();
}

function rebuildPluginsDS()
{
  var blocklist = Components.classes["@mozilla.org/extensions/blocklist;1"]
                            .getService(nsIBlocklistService);
  var phs = Components.classes["@mozilla.org/plugin/host;1"]
                      .getService(Components.interfaces.nsIPluginHost);
  var plugins = phs.getPluginTags({ });
  var rdfCU = Components.classes["@mozilla.org/rdf/container-utils;1"]
                        .getService(Components.interfaces.nsIRDFContainerUtils);
  var rootctr = rdfCU.MakeSeq(gPluginsDS, gRDF.GetResource(RDFURI_ITEM_ROOT));
  gPlugins = { };
  
  // Running in a batch stops the template builder from running
  gPluginsDS.beginUpdateBatch();

  cleanDataSource(gPluginsDS, rootctr);

  for (var i = 0; i < plugins.length; i++) {
    var plugin = plugins[i];
    var name = plugin.name;
    if (!(name in gPlugins))
      gPlugins[name] = { };

    // Removes all html markup in a plugin's description
    var desc = plugin.description.replace(/<\/?[a-z][^>]*>/gi, " ");
    if (!(desc in gPlugins[name])) {
      var homepageURL = null;
      // Some plugins (e.g. QuickTime) add an anchor to their description to
      // provide a link to the plugin's homepage in about:plugins. This can be
      // used to provide access to a plugins homepage in the add-ons mgr.
      if (/<A\s+HREF=[^>]*>/i.test(plugin.description))
        homepageURL = /<A\s+HREF=["']?([^>"'\s]*)/i.exec(plugin.description)[1];

      gPlugins[name][desc] = { filename       : plugin.filename,
                               version        : plugin.version,
                               homepageURL    : homepageURL,
                               blocklistState : blocklist.getPluginBlocklistState(plugin),
                               disabled       : plugin.disabled,
                               blocklisted    : plugin.blocklisted,
                               plugins        : [] };
    }
    gPlugins[name][desc].plugins.push(plugin);
  }

  for (var pluginName in gPlugins) {
    for (var pluginDesc in gPlugins[pluginName]) {
      plugin = gPlugins[pluginName][pluginDesc];
      var pluginNode = gRDF.GetResource(PREFIX_ITEM_URI + plugin.filename);
      rootctr.AppendElement(pluginNode);
      gPluginsDS.Assert(pluginNode,
                        gRDF.GetResource(PREFIX_NS_EM + "name"),
                        gRDF.GetLiteral(pluginName),
                        true);
      gPluginsDS.Assert(pluginNode,
                        gRDF.GetResource(PREFIX_NS_EM + "version"),
                        gRDF.GetLiteral(plugin.version),
                        true);
      gPluginsDS.Assert(pluginNode,
                        gRDF.GetResource(PREFIX_NS_EM + "addonID"),
                        gRDF.GetLiteral(plugin.filename),
                        true);
      gPluginsDS.Assert(pluginNode,
                        gRDF.GetResource(PREFIX_NS_EM + "description"),
                        gRDF.GetLiteral(pluginDesc),
                        true);
      if (plugin.homepageURL)
        gPluginsDS.Assert(pluginNode,
                          gRDF.GetResource(PREFIX_NS_EM + "homepageURL"),
                          gRDF.GetLiteral(plugin.homepageURL),
                          true);
      gPluginsDS.Assert(pluginNode,
                        gRDF.GetResource(PREFIX_NS_EM + "isDisabled"),
                        gRDF.GetLiteral((plugin.disabled ||
                                         plugin.blocklisted) ? "true" : "false"),
                        true);
      gPluginsDS.Assert(pluginNode,
                        gRDF.GetResource(PREFIX_NS_EM + "blocklisted"),
                        gRDF.GetLiteral(plugin.blocklisted ? "true" : "false"),
                        true);
      var softblocked = plugin.blocklistState == nsIBlocklistService.STATE_SOFTBLOCKED;
      gPluginsDS.Assert(pluginNode,
                        gRDF.GetResource(PREFIX_NS_EM + "blocklistedsoft"),
                        gRDF.GetLiteral(softblocked ? "true" : "false"),
                        true);
      var outdated = plugin.blocklistState == nsIBlocklistService.STATE_OUTDATED;
      gPluginsDS.Assert(pluginNode,
                        gRDF.GetResource(PREFIX_NS_EM + "outdated"),
                        gRDF.GetLiteral((outdated && gPluginUpdateUrl) ? "true" : "false"),
                        true);
      gPluginsDS.Assert(pluginNode,
                        gRDF.GetResource(PREFIX_NS_EM + "compatible"),
                        gRDF.GetLiteral("true"),
                        true);
      gPluginsDS.Assert(pluginNode,
                        gRDF.GetResource(PREFIX_NS_EM + "plugin"),
                        gRDF.GetLiteral("true"),
                        true);
    }
  }
  
  gPluginsDS.endUpdateBatch();
}

function togglePluginDisabled(aName, aDesc)
{
  var plugin = gPlugins[aName][aDesc];
  plugin.disabled = !plugin.disabled;
  for (var i = 0; i < plugin.plugins.length; ++i)
    plugin.plugins[i].disabled = plugin.disabled;
  var isDisabled = plugin.disabled || plugin.blocklisted;
  gPluginsDS.Change(gRDF.GetResource(PREFIX_ITEM_URI + plugin.filename),
                    gRDF.GetResource(PREFIX_NS_EM + "isDisabled"),
                    gRDF.GetLiteral(isDisabled ? "false" : "true"),
                    gRDF.GetLiteral(isDisabled ? "true" : "false"));
  gExtensionsViewController.onCommandUpdate();
  gExtensionsView.selectedItem.focus();
}

///////////////////////////////////////////////////////////////////////////////
// Startup, Shutdown
function Startup()
{
  gExtensionStrings = document.getElementById("extensionsStrings");
  gPref = Components.classes["@mozilla.org/preferences-service;1"]
                    .getService(nsIPrefBranch2);
  var defaultPref = gPref.QueryInterface(Components.interfaces.nsIPrefService)
                         .getDefaultBranch(null);
  try {
    gThemeToSelect = gCurrentTheme = gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
    gDefaultTheme = defaultPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
    if (gPref.getBoolPref(PREF_EXTENSIONS_DSS_SWITCHPENDING))
      gThemeToSelect = gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
  }
  catch (e) { }

  if (gPref.prefHasUserValue(PREF_LWTHEME_TO_SELECT)) {
    var id = gPref.getCharPref(PREF_LWTHEME_TO_SELECT);
    if (id)
      gLWThemeToSelect = LightweightThemeManager.getUsedTheme(id);
    else
      gLWThemeToSelect = null;
  }
  else {
    gLWThemeToSelect = LightweightThemeManager.currentTheme;
  }

  gExtensionsView = document.getElementById("extensionsView");
  gExtensionManager = Components.classes["@mozilla.org/extensions/manager;1"]
                                .getService(nsIExtensionManager);
  var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
                          .getService(Components.interfaces.nsIXULAppInfo)
                          .QueryInterface(Components.interfaces.nsIXULRuntime);
  gInSafeMode = appInfo.inSafeMode;
  gAppID = appInfo.ID;

  var version = appInfo.version.replace(gBranchVersion, "$1");
  gCheckCompatibilityPref = PREF_EM_CHECK_COMPATIBILITY + "." + version;

  try {
    gCheckCompat = gPref.getBoolPref(gCheckCompatibilityPref);
  } catch(e) { }

  try {
    gCheckUpdateSecurity = gPref.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY);
  } catch(e) { }

  if (gPref.getPrefType(PREF_PLUGINS_UPDATEURL) != gPref.PREF_INVALID) {
    var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
                              .getService(Components.interfaces.nsIURLFormatter);
    gPluginUpdateUrl = formatter.formatURLPref(PREF_PLUGINS_UPDATEURL);
  }

  gPref.addObserver(PREF_DSS_SKIN_TO_SELECT, gPrefObserver, false);
  gPref.addObserver(PREF_GENERAL_SKINS_SELECTEDSKIN, gPrefObserver, false);

  try {
    gShowGetAddonsPane = gPref.getBoolPref(PREF_GETADDONS_SHOWPANE);
  } catch(e) { }

  // Extension Command Updating is handled by a command controller.
  gExtensionsView.controllers.appendController(gExtensionsViewController);
  gExtensionsView.addEventListener("select", onAddonSelect, false);

  gRDF = Components.classes["@mozilla.org/rdf/rdf-service;1"]
                   .getService(Components.interfaces.nsIRDFService);

  initPluginsDS();
  gExtensionsView.database.AddDataSource(gPluginsDS);
  initLWThemeDS();
  gExtensionsView.database.AddDataSource(gLWThemeDS);
  if (gShowGetAddonsPane)
    initSearchDS();
  gExtensionsView.database.AddDataSource(gExtensionManager.datasource);
  gExtensionsView.setAttribute("ref", RDFURI_ITEM_ROOT);

  document.getElementById("search-view").hidden = !gShowGetAddonsPane;
  updateOptionalViews();

  var viewGroup = document.getElementById("viewGroup");

  gExtensionsView.focus();
  gExtensionsViewController.onCommandUpdate();

  // Now look and see if we're being opened by XPInstall
  gDownloadManager = new XPInstallDownloadManager();
  var os = Components.classes["@mozilla.org/observer-service;1"]
                     .getService(Components.interfaces.nsIObserverService);
  os.addObserver(gDownloadManager, "xpinstall-download-started", false);
  os.addObserver(gAddonsMsgObserver, "addons-message-notification", false);
  os.addObserver(gPluginObserver, "plugins-list-updated", false);
  os.addObserver(gLWThemeObserver, "lightweight-theme-list-changed", false);
  os.addObserver(gLWThemeObserver, "lightweight-theme-changed", false);

  gObserverIndex = gExtensionManager.addInstallListener(gDownloadManager);

  if (!gCheckCompat) {
    var msgText = getExtensionString("disabledCompatMsg");
    var buttonLabel = getExtensionString("enableButtonLabel");
    var buttonAccesskey = getExtensionString("enableButtonAccesskey");
    var notifyData = "addons-enable-compatibility";
    showMessage(URI_NOTIFICATION_ICON_WARNING,
                msgText, buttonLabel, buttonAccesskey,
                true, notifyData);
  }
  if (!gCheckUpdateSecurity) {
    var defaultCheckSecurity = true;
    try {
      defaultCheckSecurity = defaultPref.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY);
    } catch (e) { }

    // App has update security checking enabled by default so show warning
    if (defaultCheckSecurity) {
      var msgText = getExtensionString("disabledUpdateSecurityMsg");
      var buttonLabel = getExtensionString("enableButtonLabel");
      var buttonAccesskey = getExtensionString("enableButtonAccesskey");
      var notifyData = "addons-enable-updatesecurity";
      showMessage(URI_NOTIFICATION_ICON_WARNING,
                  msgText, buttonLabel, buttonAccesskey,
                  true, notifyData);
    }
  }
  if (gInSafeMode) {
    showMessage(URI_NOTIFICATION_ICON_INFO,
                getExtensionString("safeModeMsg"),
                null, null, true, null);
  }

  gExtensionsView.builder.addListener(TemplateBuilderListener);

  if ("arguments" in window) {
    try {
      var params = window.arguments[0].QueryInterface(Components.interfaces.nsIDialogParamBlock);
      var manager = window.arguments[1].QueryInterface(Components.interfaces.nsIObserver);
      gDownloadManager.addDownloads(params, manager);
    }
    catch (e) {
      if (window.arguments[0] == "updates-only") {
        gUpdatesOnly = true;
#ifdef MOZ_PHOENIX
        // If we are Firefox when updating on startup don't display context
        // menuitems that can open a browser window.
        gUpdateContextMenus = gUpdateContextMenusNoBrowser;
#endif
        document.getElementById("topBar").hidden = true;
        document.getElementById("extensionsView").setAttribute("norestart", "");
        showView("updates");
        showMessage(URI_NOTIFICATION_ICON_INFO,
                    getExtensionString("newUpdatesAvailableMsg"),
                    null, null, true, null);
        document.title = getExtensionString("newUpdateWindowTitle", [getBrandShortName()]);
      }
      else if (window.arguments.length == 2) {
        gNewAddons = window.arguments[1].split(",");
        var installMsg = PluralForm.get(gNewAddons.length, getExtensionString("newAddonsNotificationMsg2"));
        installMsg = installMsg.replace("%S", gNewAddons.length);
        showMessage(URI_NOTIFICATION_ICON_INFO,
                    installMsg, null, null, true, null);
        var extensionCount = 0;
        var themeCount = 0;
        var localeCount = 0;
        for (var i = 0; i < gNewAddons.length; i++) {
          var item = gExtensionManager.getItemForID(gNewAddons[i]);
          switch (item.type) {
            case Components.interfaces.nsIUpdateItem.TYPE_EXTENSION:
              extensionCount++;
              break;
            case Components.interfaces.nsIUpdateItem.TYPE_THEME:
              themeCount++;
              break;
            case Components.interfaces.nsIUpdateItem.TYPE_LOCALE:
              localeCount++;
              break;
          }
        }
        if (themeCount > extensionCount && themeCount > localeCount)
          showView("themes");
        else if (localeCount > extensionCount && localeCount > themeCount)
          showView("locales");
        else
          showView("extensions");
      }
      else
        showView(window.arguments[0]);
    }
  }
  else if (viewGroup.hasAttribute("last-selected") &&
           document.getElementById(viewGroup.getAttribute("last-selected") + "-view") &&
           !document.getElementById(viewGroup.getAttribute("last-selected") + "-view").hidden)
    showView(viewGroup.getAttribute("last-selected"));
  else
    showView(gShowGetAddonsPane ? "search" : "extensions");

  if (gExtensionsView.selectedItem)
    gExtensionsView.scrollBoxObject.scrollToElement(gExtensionsView.selectedItem);

  gPref.setBoolPref(PREF_UPDATE_NOTIFYUSER, false);

  if (gUpdatesOnly && gExtensionsView.children.length == 0) {
    window.close();
    return;
  }

  // Left/right switches panes, up/down/pageUp/pageDown/home/end switches items in
  // the current pane, whenever either the radiogroup or the richlistbox is focused.
  window.addEventListener("keypress", function (event) {
    if (event.target != viewGroup &&
        event.target != gExtensionsView)
      return;

    var contextMenu = document.getElementById("addonContextMenu");
    if (contextMenu.state == "open" ||
        contextMenu.state == "showing")
      return;

    switch (event.keyCode) {
      case event.DOM_VK_LEFT:
      case event.DOM_VK_RIGHT:
        let nextFlag = (event.keyCode == event.DOM_VK_RIGHT);
        if (getComputedStyle(viewGroup, "").direction == "rtl")
          nextFlag = !nextFlag;
        viewGroup.checkAdjacentElement(nextFlag);
        break;
      case event.DOM_VK_UP:
        gExtensionsView._moveByOffsetFromUserEvent(-1, event);
        break;
      case event.DOM_VK_DOWN:
        gExtensionsView._moveByOffsetFromUserEvent(1, event);
        break;
      case event.DOM_VK_PAGE_UP:
        gExtensionsView._moveByOffsetFromUserEvent(gExtensionsView.scrollOnePage(-1), event);
        break;
      case event.DOM_VK_PAGE_DOWN:
        gExtensionsView._moveByOffsetFromUserEvent(gExtensionsView.scrollOnePage(1), event);
        break;
      case event.DOM_VK_HOME:
        gExtensionsView._moveByOffsetFromUserEvent(-gExtensionsView.currentIndex, event);
        break;
      case event.DOM_VK_END:
        gExtensionsView._moveByOffsetFromUserEvent(gExtensionsView.getRowCount() -
                                                   gExtensionsView.currentIndex - 1, event);
        break;
      default:
        return; // don't consume the event
    }
    event.stopPropagation();
    event.preventDefault();
  }, true);
}

function Shutdown()
{
  gExtensionsView.builder.removeListener(TemplateBuilderListener);

  gPref.removeObserver(PREF_DSS_SKIN_TO_SELECT, gPrefObserver);
  gPref.removeObserver(PREF_GENERAL_SKINS_SELECTEDSKIN, gPrefObserver);
  if (gAddonRepository && gAddonRepository.isSearching)
    gAddonRepository.cancelSearch();

  gRDF = null;
  gPref = null;
  gExtensionsView.removeEventListener("select", onAddonSelect, false);
  gExtensionsView.database.RemoveDataSource(gExtensionManager.datasource);

  gExtensionManager.removeInstallListenerAt(gObserverIndex);

  var os = Components.classes["@mozilla.org/observer-service;1"]
                     .getService(Components.interfaces.nsIObserverService);
  os.removeObserver(gAddonsMsgObserver, "addons-message-notification");
  os.removeObserver(gDownloadManager, "xpinstall-download-started");
  os.removeObserver(gPluginObserver, "plugins-list-updated");
  os.removeObserver(gLWThemeObserver, "lightweight-theme-list-changed");
  os.removeObserver(gLWThemeObserver, "lightweight-theme-changed");
  var currentNotification = document.getElementById("addonsMsg").currentNotification;
  if (currentNotification && currentNotification.value == "addons-no-updates")
    window.removeEventListener("select", noUpdatesDismiss, true);
}

var TemplateBuilderListener = {
  willRebuild: function(aBuilder) {
  },

  didRebuild: function(aBuilder) {
    // Display has been rebuilt, update necessary attributes
    if (gView == "extensions" || gView == "themes" || gView == "locales") {
      for (var i = 0; i < gNewAddons.length; i++) {
        var item = document.getElementById(PREFIX_ITEM_URI + gNewAddons[i]);
        if (item)
          item.setAttribute("newAddon", "true");
      }
    }

    if (gView == "themes") {
      if (gPref.getBoolPref(PREF_EXTENSIONS_DSS_SWITCHPENDING)) {
        var item = getActivedThemeItem();
        if (item)
          setRestartMessage(item);
      }
    }
  },

  QueryInterface: function (aIID)
  {
    if (!aIID.equals(Components.interfaces.nsIXULBuilderListener) &&
        !aIID.equals(Components.interfaces.nsISupports))
      throw Components.results.NS_ERROR_NO_INTERFACE;
    return this;
  }
};

///////////////////////////////////////////////////////////////////////////////
//
// XPInstall
//

function getURLSpecFromFile(aFile)
{
  var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
                         .getService(nsIIOService);
  var fph = ioServ.getProtocolHandler("file").QueryInterface(Components.interfaces.nsIFileProtocolHandler);
  return fph.getURLSpecFromFile(aFile);
}

function XPInstallDownloadManager()
{
}

XPInstallDownloadManager.prototype = {
  observe: function (aSubject, aTopic, aData)
  {
    switch (aTopic) {
      case "xpinstall-download-started":
        var params = aSubject.QueryInterface(Components.interfaces.nsISupportsArray);
        var paramBlock = params.GetElementAt(0).QueryInterface(Components.interfaces.nsISupportsInterfacePointer);
        paramBlock = paramBlock.data.QueryInterface(Components.interfaces.nsIDialogParamBlock);
        var manager = params.GetElementAt(1).QueryInterface(Components.interfaces.nsISupportsInterfacePointer);
        manager = manager.data.QueryInterface(Components.interfaces.nsIObserver);
        this.addDownloads(paramBlock, manager);
        break;
    }
  },

  addDownloads: function (aParams, aManager)
  {
    var numXPInstallItems = aParams.GetInt(1);
    var items = [];
    var switchPane = false;
    for (var i = 0; i < numXPInstallItems;) {
      var displayName = aParams.GetString(i++);
      var url = aParams.GetString(i++);
      var iconURL = aParams.GetString(i++);
      var uri = Components.classes["@mozilla.org/network/io-service;1"]
                          .getService(nsIIOService).newURI(url, null, null);
      var isTheme = uri.QueryInterface(nsIURL).fileExtension.toLowerCase() == "jar";
      if (!iconURL) {
        iconURL = isTheme ? "chrome://mozapps/skin/extensions/themeGeneric.png" :
                            "chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png";
      }

      var type = isTheme ? nsIUpdateItem.TYPE_THEME : nsIUpdateItem.TYPE_EXTENSION;
      var item = Components.classes["@mozilla.org/updates/item;1"]
                           .createInstance(Components.interfaces.nsIUpdateItem);
      item.init(url, " ", "app-profile", "", "", displayName, url, "", iconURL, "", "", type, "");
      items.push(item);

      // Advance the enumerator
      var certName = aParams.GetString(i++);

      // Check whether the install was triggered from the Get Add-ons pane.
      if (url in gPendingInstalls) {
        // Update the installation status
        gSearchDS.Assert(gRDF.GetResource(gPendingInstalls[url]),
                         gRDF.GetResource(PREFIX_NS_EM + "action"),
                         gRDF.GetLiteral("installing"),
                         true);
        delete gPendingInstalls[url];
      }
      else {
        switchPane = true;
      }
    }

    gInstalling = true;
    gExtensionManager.addDownloads(items, items.length, aManager);
    updateOptionalViews();
    updateGlobalCommands();
    // Only switch to the installs pane if there was an not started by the
    // Get Add-ons pane
    if (switchPane)
      showView("installs");
  },

  getElementForAddon: function(aAddon)
  {
    var element = document.getElementById(PREFIX_ITEM_URI + aAddon.id);
    if (aAddon.id == aAddon.xpiURL)
      element = document.getElementById(aAddon.xpiURL);
    return element;
  },

  /////////////////////////////////////////////////////////////////////////////
  // nsIAddonInstallListener
  onDownloadStarted: function(aAddon)
  {
  },

  onDownloadEnded: function(aAddon)
  {
  },

  onInstallStarted: function(aAddon)
  {
  },
  
  onCompatibilityCheckStarted: function(aAddon)
  {
  },
  
  onCompatibilityCheckEnded: function(aAddon, aStatus)
  {
  },

  _failed: false,
  onInstallEnded: function(aAddon, aStatus)
  {
    if (aStatus < 0)
      this._failed = true;

    // From nsInstall.h
    // USER_CANCELLED = -210
    // All other xpinstall errors are <= -200
    // Any errors from the EM will have been displayed directly by the EM
    if (aStatus > -200 || aStatus == -210)
      return;

    var xpinstallStrings = document.getElementById("xpinstallStrings");
    try {
      var msg = xpinstallStrings.getString("error" + aStatus);
    }
    catch (e) {
      msg = xpinstallStrings.getFormattedString("unknown.error", [aStatus]);
    }
    var title = getExtensionString("errorInstallTitle");
    var message = getExtensionString("errorInstallMsg", [getBrandShortName(),
                                                         aAddon.xpiURL, msg]);
    var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
                       .getService(Components.interfaces.nsIPromptService);
    ps.alert(window, title, message + "\n" + aStatus);
  },

  onInstallsCompleted: function()
  {
    gInstalling = false;
    if (gUpdatesOnly) {
      if (this._failed) {
        let continueButton = document.getElementById("continueDialogButton");
        setElementDisabledByID("cmd_continue", false);
        continueButton.hidden = false;
        continueButton.setAttribute("default", "true");
        continueButton.focus();
      } else {
        setTimeout(closeEM, 2000);
      }
    }
    else {
      updateOptionalViews();
      updateGlobalCommands();
    }
  },

  _urls: { },
  onDownloadProgress: function (aAddon, aValue, aMaxValue)
  {
    var element = this.getElementForAddon(aAddon);
    if (!element)
      return;
    var percent = Math.round((aValue / aMaxValue) * 100);
    if (percent > 1 && !(aAddon.xpiURL in this._urls))
      this._urls[aAddon.xpiURL] = true;

    var statusPrevious = element.getAttribute("status");
    var statusCurrent = DownloadUtils.getTransferTotal(aValue, aMaxValue);
    if (statusCurrent != statusPrevious)
      element.setAttribute("status", statusCurrent);
  },

  /////////////////////////////////////////////////////////////////////////////
  // nsISupports
  QueryInterface: function (aIID)
  {
    if (!aIID.equals(Components.interfaces.nsIAddonInstallListener) &&
        !aIID.equals(Components.interfaces.nsISupports))
      throw Components.results.NS_ERROR_NO_INTERFACE;
    return this;
  }
};

///////////////////////////////////////////////////////////////////////////////
//
// Update Listener
//
function UpdateCheckListener() {
}
UpdateCheckListener.prototype = {
  _updateFound: false,
  _totalCount: 0,
  _completedCount: 0,

  /**
   * See nsIExtensionManager.idl
   */
  onUpdateStarted: function() {
    gExtensionsView.setAttribute("update-operation", "checking");
    gExtensionsViewController.onCommandUpdate();
    updateGlobalCommands();
    this._totalCount = gExtensionsView.children.length;
  },

  /**
   * See nsIExtensionManager.idl
   */
  onUpdateEnded: function() {
    if (!document)
      return;
    var viewGroup = document.getElementById("viewGroup").parentNode;
    viewGroup.parentNode.selectedPanel = viewGroup;
    gExtensionsView.removeAttribute("update-operation");
    gExtensionsViewController.onCommandUpdate();
    updateOptionalViews();
    updateGlobalCommands();
    if (this._updateFound)
      showView("updates");
    else {
      showMessage(URI_NOTIFICATION_ICON_INFO,
                  getExtensionString("noUpdatesMsg"),
                  null, null, true, "addons-no-updates");
      window.addEventListener("select", noUpdatesDismiss, true);
    }
  },

  /**
   * See nsIExtensionManager.idl
   */
  onAddonUpdateStarted: function(addon) {
    if (!document)
      return;
    var element = document.getElementById(PREFIX_ITEM_URI + addon.id);
    element.setAttribute("loading", "true");
    element.setAttribute("updateStatus", getExtensionString("updatingMsg"));
  },

  /**
   * See nsIExtensionManager.idl
   */
  onAddonUpdateEnded: function(addon, status) {
    if (!document)
      return;
    var element = document.getElementById(PREFIX_ITEM_URI + addon.id);
    element.removeAttribute("loading");
    const nsIAUCL = Components.interfaces.nsIAddonUpdateCheckListener;
    switch (status) {
    case nsIAUCL.STATUS_UPDATE:
      var statusMsg = getExtensionString("updateAvailableMsg", [addon.version]);
      this._updateFound = true;
      break;
    case nsIAUCL.STATUS_VERSIONINFO:
      statusMsg = getExtensionString("updateCompatibilityMsg");
      break;
    case nsIAUCL.STATUS_FAILURE:
      var name = element.getAttribute("name");
      statusMsg = getExtensionString("updateErrorMessage", [name]);
      break;
    case nsIAUCL.STATUS_DISABLED:
      name = element.getAttribute("name");
      statusMsg = getExtensionString("updateDisabledMessage", [name]);
      break;
    case nsIAUCL.STATUS_APP_MANAGED:
    case nsIAUCL.STATUS_NO_UPDATE:
      statusMsg = getExtensionString("updateNoUpdateMsg");
      break;
    case nsIAUCL.STATUS_NOT_MANAGED:
      statusMsg = getExtensionString("updateNotManagedMessage", [getBrandShortName()]);
      break;
    case nsIAUCL.STATUS_READ_ONLY:
      statusMsg = getExtensionString("updateReadOnlyMessage");
      break;
    default:
      statusMsg = getExtensionString("updateNoUpdateMsg");
    }
    element.setAttribute("updateStatus", statusMsg);
    ++this._completedCount;
    document.getElementById("progressStatus").value = getExtensionString("finishedUpdateCheck", [addon.name]);
    document.getElementById("addonsProgress").value = Math.ceil((this._completedCount / this._totalCount) * 100);
  },

  /**
   * See nsISupports.idl
   */
  QueryInterface: function(iid) {
    if (!iid.equals(Components.interfaces.nsIAddonUpdateCheckListener) &&
        !iid.equals(Components.interfaces.nsISupports))
      throw Components.results.NS_ERROR_NO_INTERFACE;
    return this;
  }
};


///////////////////////////////////////////////////////////////////////////////
//
// View Event Handlers
//
function onViewDoubleClick(aEvent)
{
  if (aEvent.button != 0 || !gExtensionsView.selectedItem)
    return;

  switch (gView) {
    case "extensions":
      gExtensionsViewController.doCommand('cmd_options');
      break;
    case "themes":
      gExtensionsViewController.doCommand('cmd_useTheme');
      break;
    case "updates":
      gExtensionsViewController.doCommand('cmd_includeUpdate');
      break;
  }
}

function onAddonSelect(aEvent)
{
  var viewButton = document.getElementById("viewGroup").selectedItem;
  if (viewButton.hasAttribute("persist") && gExtensionsView.selectedItem)
    viewButton.setAttribute("last-selected", gExtensionsView.selectedItem.id);

  if (!document.getElementById("themePreviewArea").hidden) {
    var previewImageDeck = document.getElementById("previewImageDeck");
    if (gView == "themes") {
      var previewImage = document.getElementById("previewImage");
      if (!gExtensionsView.selectedItem) {
        previewImageDeck.selectedIndex = 0;
        if (previewImage.hasAttribute("src"))
          previewImage.removeAttribute("src");
      }
      else {
        var url = gExtensionsView.selectedItem.getAttribute("previewImage");
        if (url) {
          previewImageDeck.selectedIndex = 2;
          previewImage.setAttribute("src", url);
        }
        else {
          previewImageDeck.selectedIndex = 1;
          if (previewImage.hasAttribute("src"))
            previewImage.removeAttribute("src");
        }
      }
    }
    else if (gView == "updates") {
      UpdateInfoLoader.cancelLoad();
      if (!gExtensionsView.selectedItem) {
        previewImageDeck.selectedIndex = 3;
      }
      else {
        var uri = gExtensionsView.selectedItem.getAttribute("availableUpdateInfo");
        if (isSafeURI(uri))
          UpdateInfoLoader.loadInfo(uri);
        else
          previewImageDeck.selectedIndex = 4;
      }
    }
  }
}

function onPreviewImageError(aEvent) {
  var previewImageDeck = document.getElementById("previewImageDeck");
  var previewImage = document.getElementById("previewImage");
  previewImageDeck.selectedIndex = 1;
  previewImage.removeAttribute("src");
}

/**
 * Manages the retrieval of update information and the xsl stylesheet
 * used to format the inforation into chrome.
 */
var UpdateInfoLoader = {
  _stylesheet: null,
  _styleRequest: null,
  _infoDocument: null,
  _infoRequest: null,
  
  // Called once both stylesheet and info requests have completed
  displayInfo: function()
  {
    var processor = Components.classes["@mozilla.org/document-transformer;1?type=xslt"]
                              .createInstance(Components.interfaces.nsIXSLTProcessor);
    processor.flags |= Components.interfaces.nsIXSLTProcessorPrivate.DISABLE_ALL_LOADS;
    
    processor.importStylesheet(this._stylesheet);
    var fragment = processor.transformToFragment(this._infoDocument, document);
    document.getElementById("infoDisplay").appendChild(fragment);
    document.getElementById("previewImageDeck").selectedIndex = 7;
  },
  
  onStylesheetLoaded: function(event)
  {
    var request = event.target;
    this._styleRequest = null;
    this._stylesheet = request.responseXML;

    if (!this._stylesheet ||
        this._stylesheet.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
        (request.status != 200 && request.status != 0)) {
      // The stylesheet load failing is a bad sign
      document.getElementById("previewImageDeck").selectedIndex = 6;
      return;
    }

    if (this._infoDocument)
      this.displayInfo();
  },
  
  onInfoLoaded: function(event)
  {
    var request = event.target;
    this._infoRequest = null;
    this._infoDocument = request.responseXML;
    
    if (!this._infoDocument ||
        this._infoDocument.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
        (request.status != 200 && request.status != 0)) {
      // Should attempt to parse request.responseText with the html parser
      document.getElementById("previewImageDeck").selectedIndex = 6;
      return;
    }

    if (this._stylesheet)
      this.displayInfo();
  },
  
  onError: function(event)
  {
    if (event.request == this._infoRequest)
      this._infoRequest = null;
    else // Means the stylesheet load has failed which is pretty bad news
      this.cancelRequest();

    document.getElementById("previewImageDeck").selectedIndex = 6;
  },
  
  loadInfo: function(url)
  {
    this.cancelLoad();
    this._infoDocument = null;
    document.getElementById("previewImageDeck").selectedIndex = 5;
    var display = document.getElementById("infoDisplay");
    while (display.lastChild)
      display.removeChild(display.lastChild);

    this._infoRequest = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                                  .createInstance(Components.interfaces.nsIXMLHttpRequest);
    this._infoRequest.open("GET", url, true);
    
    var self = this;
    this._infoRequest.onerror = function(event) { self.onError(event); };
    this._infoRequest.onload = function(event) { self.onInfoLoaded(event); };
    this._infoRequest.send(null);

    // We may have the stylesheet cached from a previous load, or may still be
    // loading it.
    if (this._stylesheet || this._styleRequest)
      return;

    this._styleRequest = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                                   .createInstance(Components.interfaces.nsIXMLHttpRequest);
    this._styleRequest.open("GET", "chrome://mozapps/content/extensions/updateinfo.xsl", true);
    this._styleRequest.overrideMimeType("text/xml");
    
    this._styleRequest.onerror = function(event) { self.onError(event); };
    this._styleRequest.onload = function(event) { self.onStylesheetLoaded(event); };
    this._styleRequest.send(null);
  },
  
  cancelLoad: function()
  {
    // Leave the stylesheet loader running, there's a good chance we'll need it
    if (this._infoRequest)
      this._infoRequest.abort();
    this._infoRequest = null;
  }
}

///////////////////////////////////////////////////////////////////////////////
// View Context Menus
var gAddonContextMenus = ["menuitem_useTheme", "menuitem_options", "menuitem_homepage",
                          "menuitem_about",  "menuseparator_1", "menuitem_uninstall",
                          "menuitem_cancelUninstall", "menuitem_cancelInstall",
                          "menuitem_cancelUpgrade", "menuitem_checkUpdate",
                          "menuitem_enable", "menuitem_disable"];
var gUpdateContextMenus = ["menuitem_homepage", "menuitem_about", "menuseparator_1",
                           "menuitem_installUpdate", "menuitem_includeUpdate"];
// For Firefox don't display context menuitems that can open a browser window.
var gUpdateContextMenusNoBrowser = ["menuitem_installUpdate", "menuitem_includeUpdate"];
var gInstallContextMenus = ["menuitem_homepage", "menuitem_about", "menuseparator_1",
                            "menuitem_cancelUpgrade", "menuitem_cancelInstall"];
var gSearchContextMenus = ["menuitem_learnMore", "menuitem_installSearchResult",
                           "menuseparator_1", "menuitem_cancelInstall"];

function buildContextMenu(aEvent)
{
  var popup = document.getElementById("addonContextMenu");
  var selectedItem = gExtensionsView.selectedItem;
  if (aEvent.target !== popup || !selectedItem)
    return false;

  while (popup.hasChildNodes())
    popup.removeChild(popup.firstChild);

  switch (gView) {
  case "search":
    var menus = gSearchContextMenus;
    break;
  case "extensions":
  case "themes":
  case "locales":
  case "plugins":
    menus = gAddonContextMenus;
    break;
  case "updates":
    menus = gUpdateContextMenus;
    break;
  case "installs":
    menus = gInstallContextMenus;
    break;
  }

  for (var i = 0; i < menus.length; ++i) {
    var clonedMenu = document.getElementById(menus[i]).cloneNode(true);
    clonedMenu.id = clonedMenu.id + "_clone";
    popup.appendChild(clonedMenu);
  }

  // All views (but search and plugins) support about
  if (gView != "search" && gView != "plugins") {
    var menuitem_about = document.getElementById("menuitem_about_clone");
    var name = selectedItem ? selectedItem.getAttribute("name") : "";
    menuitem_about.setAttribute("label", getExtensionString("aboutAddon", [name]));
  }

  // Make sure all commands are up to date
  gExtensionsViewController.onCommandUpdate();

  // Some flags needed later
  var canCancelInstall = gExtensionsViewController.isCommandEnabled("cmd_cancelInstall");
  var canCancelUpgrade = gExtensionsViewController.isCommandEnabled("cmd_cancelUpgrade");
  var canReallyEnable = gExtensionsViewController.isCommandEnabled("cmd_reallyEnable");
  var canCancelUninstall = gExtensionsViewController.isCommandEnabled("cmd_cancelUninstall");

  /* When an update or install is pending allow canceling the update or install
     and don't allow uninstall. When an uninstall is pending allow canceling the
     uninstall.*/
  if (gView != "updates") {
    document.getElementById("menuitem_cancelInstall_clone").hidden = !canCancelInstall;

    if (gView != "installs" && gView != "search") {
      document.getElementById("menuitem_cancelUninstall_clone").hidden = !canCancelUninstall;
      document.getElementById("menuitem_uninstall_clone").hidden = canCancelUninstall ||
                                                                   canCancelInstall ||
                                                                   canCancelUpgrade;
    }

    if (gView != "search")
      document.getElementById("menuitem_cancelUpgrade_clone").hidden = !canCancelUpgrade;
  }

  switch (gView) {
  case "extensions":
    document.getElementById("menuitem_enable_clone").hidden = !canReallyEnable;
    document.getElementById("menuitem_disable_clone").hidden = canReallyEnable;
    document.getElementById("menuitem_useTheme_clone").hidden = true;
    break;
  case "themes":
    var enableMenu = document.getElementById("menuitem_enable_clone");
    if (!selectedItem.isCompatible || selectedItem.isBlocklisted ||
        !selectedItem.satisfiesDependencies || selectedItem.isDisabled)
      // don't let the user activate incompatible themes, but show a (disabled) Enable
      // menuitem to give visual feedback; it's disabled because cmd_enable returns false
      enableMenu.hidden = false;
    else
      enableMenu.hidden = true;
    document.getElementById("menuitem_options_clone").hidden = true;
    document.getElementById("menuitem_disable_clone").hidden = true;
    break;
  case "plugins":
    document.getElementById("menuitem_about_clone").hidden = true;
    document.getElementById("menuitem_uninstall_clone").hidden = true;
    document.getElementById("menuitem_checkUpdate_clone").hidden = true;
  case "locales":
    document.getElementById("menuitem_enable_clone").hidden = !canReallyEnable;
    document.getElementById("menuitem_disable_clone").hidden = canReallyEnable;
    document.getElementById("menuitem_useTheme_clone").hidden = true;
    document.getElementById("menuitem_options_clone").hidden = true;
    break;
  case "updates":
    var includeUpdate = document.getAnonymousElementByAttribute(selectedItem, "anonid", "includeUpdate");
    var menuitem_includeUpdate = document.getElementById("menuitem_includeUpdate_clone");
    menuitem_includeUpdate.setAttribute("checked", includeUpdate.checked ? "true" : "false");
    break;
  case "installs":
    // Hides the separator if nothing is below it
    document.getElementById("menuseparator_1_clone").hidden = !canCancelInstall && !canCancelUpgrade;
    break;
  case "search":
    var canInstall = gExtensionsViewController.isCommandEnabled("cmd_installSearchResult");
    document.getElementById("menuitem_installSearchResult_clone").hidden = !canInstall;
    // Hides the separator if nothing is below it
    document.getElementById("menuseparator_1_clone").hidden = !canCancelInstall;
    break;
  }

  return true;
}

///////////////////////////////////////////////////////////////////////////////
// Drag and Drop

var gExtensionsDNDObserver =
{
  _ioServ: null,

  _ensureServices: function ()
  {
    if (!this._ioServ)
      this._ioServ = Components.classes["@mozilla.org/network/io-service;1"]
                               .getService(nsIIOService);
  },

  // returns a JS object whose properties are used by xpinstall
  _getDragData: function (dataTransfer, aIndex)
  {
    var fileData = { };
    // if this fails we do not have valid data to drop
    try {
      var url = dataTransfer.mozGetDataAt("text/uri-list", aIndex);
      if (!url) {
        url = dataTransfer.mozGetDataAt("text/x-moz-url", aIndex);
        url = url ? url.split("\n")[0] : null;
        if (!url) {
          var file = dataTransfer.mozGetDataAt("application/x-moz-file", aIndex);

          var ioService = Components.classes["@mozilla.org/network/io-service;1"]
                                     .getService(Components.interfaces.nsIIOService);
          var fileHandler = this._ioServ.getProtocolHandler("file")
                                .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
          url = fileHandler.getURLSpecFromFile(file);
        }
      }

      if (!url) {
        url = dataTransfer.mozGetDataAt("text/plain", aIndex)
        if (!url)
          return null;
      }

      fileData.fileURL = url;

      var uri = this._ioServ.newURI(url, null, null).QueryInterface(nsIURL);
      fileData.fileName = uri.fileName;

      switch (uri.fileExtension) {
        case "xpi":
          fileData.type = nsIUpdateItem.TYPE_EXTENSION;
          break;
        case "jar":
          fileData.type = nsIUpdateItem.TYPE_THEME;
          break;
        default:
          return null;
      }
    }
    catch (e) {
      return null;
    }

    return fileData;
  },

  onDragOver: function (aEvent)
  {
    var types = aEvent.dataTransfer.types;
    if (types.contains("text/uri-list") ||
        types.contains("text/x-moz-url") ||
        types.contains("application/x-moz-file"))
      aEvent.preventDefault();
  },

  onDrop: function(aEvent)
  {
    if (!isXPInstallEnabled())
      return;

    this._ensureServices();

    var xpinstallObj = { };
    var themes = { };
    var xpiCount = 0;
    var themeCount = 0;

    var dataTransfer = aEvent.dataTransfer; 
    var count = dataTransfer.mozItemCount;
    for (var i = 0; i < count; ++i) {
      var fileData = this._getDragData(dataTransfer, i);
      if (!fileData)
        continue;

      xpinstallObj[fileData.fileName] = {
        URL: fileData.fileURL,
        toString: function() { return this.URL; }
      };
      ++xpiCount;
      if (fileData.type == nsIUpdateItem.TYPE_THEME)
        xpinstallObj[fileData.fileName].IconURL = URI_GENERIC_ICON_THEME;
      else
        xpinstallObj[fileData.fileName].IconURL = URI_GENERIC_ICON_XPINSTALL;
    }

    if (xpiCount > 0)
      InstallTrigger.install(xpinstallObj);
  }
};

///////////////////////////////////////////////////////////////////////////////
// Notification Messages
const gAddonsMsgObserver = {
  observe: function (aSubject, aTopic, aData)
  {
    switch (aData) {
    case "addons-enable-xpinstall":
      gPref.setBoolPref("xpinstall.enabled", true);
      break;
    case "addons-enable-compatibility":
      gPref.clearUserPref(gCheckCompatibilityPref);
      gCheckCompat = true;
      break;
    case "addons-enable-updatesecurity":
      gPref.clearUserPref(PREF_EM_CHECK_UPDATE_SECURITY);
      gCheckUpdateSecurity = true;
      break;
    case "addons-no-updates":
      var children = gExtensionsView.children;
      for (var i = 0; i < children.length; ++i) {
        var child = children[i];
        if (child.hasAttribute("updateStatus"))
          child.removeAttribute("updateStatus");
      }
      break;
    case "addons-go-online":
      var ioService = Components.classes["@mozilla.org/network/io-service;1"]
                                .getService(nsIIOService);
      ioService.offline = false;
      // If no results have been retrieved start pulling some
      if (!gRetrievedResults)
        retrieveRepositoryAddons(document.getElementById("searchfield").value);
      if (gView == "search")
        document.getElementById("searchfield").disabled = false;
      break;
    case "addons-message-dismiss":
      break;
    case "addons-restart-app":
      restartApp();
      break;
    }
    if (gExtensionsView.selectedItem)
      gExtensionsView.selectedItem.focus();
  }
};

const gPrefObserver = {
  observe: function (aSubject, aTopic, aData)
  {
    if (aData == PREF_GENERAL_SKINS_SELECTEDSKIN) {
      // Changed as the result of a dynamic theme switch
      gThemeToSelect = gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
    }
    else if (aData == PREF_DSS_SKIN_TO_SELECT) {
      // Either a new skin has been selected or the switch has been cancelled
      if (gPref.getBoolPref(PREF_EXTENSIONS_DSS_SWITCHPENDING))
        gThemeToSelect = gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
      else
        gThemeToSelect = gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
      updateOptionalViews();
      updateGlobalCommands();
    }
  }
};

const gPluginObserver = {
  observe: function (aSubject, aTopic, aData)
  {
    rebuildPluginsDS();
  }
};

const gLWThemeObserver = {
  observe: function (aSubject, aTopic, aData) {
    if (aTopic == "lightweight-theme-list-changed") {
      rebuildLWThemeDS();
    }
    else if (aTopic == "lightweight-theme-changed") {
      gLWThemeToSelect = LightweightThemeManager.currentTheme;
      if (gPref.prefHasUserValue(PREF_LWTHEME_TO_SELECT))
          gPref.clearUserPref(PREF_LWTHEME_TO_SELECT);
    }
  }
};

function isXPInstallEnabled() {
  var enabled = false;
  var locked = false;
  try {
    enabled = gPref.getBoolPref("xpinstall.enabled");
    if (enabled)
      return true;
    locked = gPref.prefIsLocked("xpinstall.enabled");
  }
  catch (e) { }

  var msgText = getExtensionString(locked ? "xpinstallDisabledMsgLocked" :
                                            "xpinstallDisabledMsg");
  var buttonLabel = locked ? null : getExtensionString("enableButtonLabel");
  var buttonAccesskey = locked ? null : getExtensionString("enableButtonAccesskey");
  var notifyData = locked ? null : "addons-enable-xpinstall";
  showMessage(URI_NOTIFICATION_ICON_WARNING,
              msgText, buttonLabel, buttonAccesskey,
              !locked, notifyData);
  return false;
}

function isOffline(messageKey) {
  var ioService = Components.classes["@mozilla.org/network/io-service;1"]
                            .getService(nsIIOService);
  if (ioService.offline) {
    showMessage(URI_NOTIFICATION_ICON_WARNING,
                getExtensionString(messageKey, [getBrandShortName()]),
                getExtensionString("goOnlineButtonLabel"),
                getExtensionString("goOnlineButtonAccesskey"),
                true, "addons-go-online");
  }
  return ioService.offline;
}

///////////////////////////////////////////////////////////////////////////////
// Command Updating and Command Handlers

function canWriteToLocation(element)
{
  var installLocation = null;
  if (element) {
    var id = getIDFromResourceURI(element.id)
    installLocation = gExtensionManager.getInstallLocation(id);
  }
  return installLocation ? installLocation.canAccess : false;
}

function enableRestartButton() {
  var addonsMsg = document.getElementById("addonsMsg");
  var notification = addonsMsg.getNotificationWithValue("restart-app");
  if (!notification) {
    var appname = getBrandShortName();
    var message = getExtensionString("restartMessage", [appname]);
    var buttons = [ new MessageButton(getExtensionString("restartButton", [appname]),
                                      getExtensionString("restartAccessKey"),
                                      "addons-restart-app") ];
    addonsMsg.appendNotification(message, "restart-app",
                                 URI_NOTIFICATION_ICON_INFO,
                                 addonsMsg.PRIORITY_WARNING_HIGH, buttons);
  }
}

function disableRestartButton() {
  var addonsMsg = document.getElementById("addonsMsg");
  var notification = addonsMsg.getNotificationWithValue("restart-app");
  if (notification)
    notification.close();
}

function updateOptionalViews() {
  var ds = gExtensionsView.database;
  var ctr = Components.classes["@mozilla.org/rdf/container;1"]
                      .createInstance(Components.interfaces.nsIRDFContainer);
  ctr.Init(ds, gRDF.GetResource(RDFURI_ITEM_ROOT));
  var elements = ctr.GetElements();
  var showLocales = false;
  var showUpdates = false;
  var showThemes = false;
  var showInstalls = gInstalling;
  gPendingActions = false;

  var stateArc = gRDF.GetResource(PREFIX_NS_EM + "state");
  var opTypeArc = gRDF.GetResource(PREFIX_NS_EM + "opType");

  while (elements.hasMoreElements()) {
    var e = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
    if (!showLocales || !showThemes) {
      var typeArc = gRDF.GetResource(PREFIX_NS_EM + "type");
      var type = ds.GetTarget(e, typeArc, true);
      if (type && type instanceof Components.interfaces.nsIRDFInt) {
        if (type.Value & nsIUpdateItem.TYPE_LOCALE)
          showLocales = true;
        else if (type.Value & nsIUpdateItem.TYPE_THEME)
          showThemes = true;
      }
    }

    if (!gInstalling || !showInstalls) {
      var state = ds.GetTarget(e, stateArc, true);
      if (state) {
        showInstalls = true;
        if (state instanceof Components.interfaces.nsIRDFLiteral &&
            state.Value != "success" && state.Value != "failure")
          gInstalling = true;
      }
    }

    if (!gPendingActions) {
      var opType = ds.GetTarget(e, opTypeArc, true);
      if (opType) {
        if (opType instanceof Components.interfaces.nsIRDFLiteral &&
            opType.Value != OP_NONE)
          gPendingActions = true;
      }
    }

    if (!showUpdates) {
      var updateURLArc = gRDF.GetResource(PREFIX_NS_EM + "availableUpdateURL");
      var updateURL = ds.GetTarget(e, updateURLArc, true);
      if (updateURL) {
        var updateableArc = gRDF.GetResource(PREFIX_NS_EM + "updateable");
        var updateable = ds.GetTarget(e, updateableArc, true);
        updateable = updateable.QueryInterface(Components.interfaces.nsIRDFLiteral);
        if (updateable.Value == "true")
          showUpdates = true;
      }
    }
  }

  document.getElementById("locales-view").hidden = !showLocales;
  document.getElementById("updates-view").hidden = !showUpdates;
  document.getElementById("installs-view").hidden = !showInstalls;
  document.getElementById("themes-view").hidden = !showThemes;
  updateVisibilityFlags();

  // fall back to the previously selected view or "search" since "installs" became hidden
  if (!showInstalls && gView == "installs") {
    var viewGroup = document.getElementById("viewGroup");
    var lastSelectedView = "search";

    if (viewGroup.hasAttribute("last-selected"))
      lastSelectedView = viewGroup.getAttribute("last-selected");

    showView(lastSelectedView);
  }
}

function updateVisibilityFlags() {
  let children = document.getElementById("installs-view").parentNode._getRadioChildren();
  let firstVisible = null, lastVisible = null;
  children.forEach(function(child) {
    child.removeAttribute("first-visible");
    child.removeAttribute("last-visible");
    if (!child.hidden) {
      firstVisible = firstVisible || child;
      lastVisible = child;
    }
  });
  if (firstVisible) {
    firstVisible.setAttribute("first-visible", "true");
    lastVisible.setAttribute("last-visible", "true");
  }
}

function updateGlobalCommands() {
  var disableInstallFile = false;
  var disableUpdateCheck = true;
  var disableInstallUpdate = true;

  if (gExtensionsView.hasAttribute("update-operation")) {
    disableInstallFile = true;
    disableRestartButton();
  }
  else if (gView == "updates") {
    disableInstallUpdate = false;
    disableRestartButton();
  }
  else if (gView == "plugins") {
    if (gPluginUpdateUrl)
      disableUpdateCheck = false;
  }
  else {
    var children = gExtensionsView.children;
    for (var i = 0; i < children.length; ++i) {
      if (children[i].getAttribute("updateable") == "true") {
        disableUpdateCheck = false;
        break;
      }
    }

    if (!gInstalling &&
        (gPendingActions || gPref.getBoolPref(PREF_EXTENSIONS_DSS_SWITCHPENDING)))
      enableRestartButton();
    else
      disableRestartButton();
  }

  setElementDisabledByID("cmd_checkUpdatesAll", disableUpdateCheck);
  setElementDisabledByID("cmd_installUpdatesAll", disableInstallUpdate);
  setElementDisabledByID("cmd_installFile", disableInstallFile);
}

function showUpdateInfo()
{
  document.getElementById("themePreviewArea").hidden = false;
  document.getElementById("themeSplitter").hidden = false;
  document.getElementById("showUpdateInfoButton").hidden = true;
  document.getElementById("hideUpdateInfoButton").hidden = false;
  onAddonSelect();
}

function hideUpdateInfo()
{
  UpdateInfoLoader.cancelLoad();
  document.getElementById("themePreviewArea").hidden = true;
  document.getElementById("themeSplitter").hidden = true;
  document.getElementById("showUpdateInfoButton").hidden = false;
  document.getElementById("hideUpdateInfoButton").hidden = true;
}

function checkUpdatesAll() {
  if (isOffline("offlineUpdateMsg2"))
    return;
  
  if (gView == "plugins") {
    openURL(gPluginUpdateUrl);
    return;
  }

  if (!isXPInstallEnabled())
    return;

  // To support custom views we check the add-ons displayed in the list
  var items = [];
  var children = gExtensionsView.children;
  for (var i = 0; i < children.length; ++i) {
    if (children[i].getAttribute("updateable") != "false")
      items.push(gExtensionManager.getItemForID(getIDFromResourceURI(children[i].id)));
  }

  if (items.length > 0) {
    showProgressBar();
    var listener = new UpdateCheckListener();
    gExtensionManager.update(items, items.length,
                             nsIExtensionManager.UPDATE_CHECK_NEWVERSION,
                             listener,
                             nsIExtensionManager.UPDATE_WHEN_USER_REQUESTED);
  }
  if (gExtensionsView.selectedItem)
    gExtensionsView.selectedItem.focus();

  gPref.setBoolPref(PREF_UPDATE_NOTIFYUSER, false);
}

function installUpdatesAll() {
  if (isOffline("offlineUpdateMsg2"))
    return;

  if (!isXPInstallEnabled())
    return;

  if (gUpdatesOnly) {
    var notifications = document.getElementById("addonsMsg");
    if (notifications.currentNotification)
      notifications.removeCurrentNotification();
  }

  var items = [];
  var children = gExtensionsView.children;
  for (var i = 0; i < children.length; ++i) {
    var includeUpdate = document.getAnonymousElementByAttribute(children[i], "anonid", "includeUpdate");
    if (includeUpdate && includeUpdate.checked)
      items.push(gExtensionManager.getItemForID(getIDFromResourceURI(children[i].id)));
  }
  if (items.length > 0) {
    gInstalling = true;
    gExtensionManager.addDownloads(items, items.length, null);
    showView("installs");
    // Remove the updates view if there are no add-ons left to update
    updateOptionalViews();
    updateGlobalCommands();
  }
}

function restartApp() {

  // Notify all windows that an application quit has been requested.
  var os = Components.classes["@mozilla.org/observer-service;1"]
                     .getService(Components.interfaces.nsIObserverService);
  var cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]
                             .createInstance(Components.interfaces.nsISupportsPRBool);
  os.notifyObservers(cancelQuit, "quit-application-requested", "restart");

  // Something aborted the quit process.
  if (cancelQuit.data)
    return;

  Components.classes["@mozilla.org/toolkit/app-startup;1"].getService(nsIAppStartup)
            .quit(nsIAppStartup.eRestart | nsIAppStartup.eAttemptQuit);
}

function installWithFilePicker() {
  if (!isXPInstallEnabled())
    return;

  if (gView == "themes")
    installSkin();
  else
    installExtension();
}

function closeEM() {
  closeWindow(true);
}

function confirmOperation(aName, aTitle, aQueryMsg, aAcceptBtn, aCancelBtn,
                          aWarnMsg, aDependantItems) {
  var params = {
    message2: getExtensionString(aQueryMsg, [aName]),
    title: getExtensionString(aTitle, [aName]),
    buttons: {
      accept: { label: getExtensionString(aAcceptBtn) },
      cancel: { label: getExtensionString(aCancelBtn), focused: true }
    }
  }
  if (aDependantItems.length > 0)
    params.message1 = getExtensionString(aWarnMsg, [aName]);
  var names = [];
  for (var i = 0; i < aDependantItems.length; ++i)
    names.push(aDependantItems[i].name + " " + aDependantItems[i].version);

  window.openDialog("chrome://mozapps/content/extensions/list.xul", "",
                    "titlebar,modal,centerscreen", names, params);
  return params.result == "accept";
}

function installCallback(item, status) {
  var resultNode = gRDF.GetResource(item.id);
  var actionArc = gRDF.GetResource(PREFIX_NS_EM + "action");

  // Strip out old status
  var targets = gSearchDS.GetTargets(resultNode, actionArc, true);
  while (targets.hasMoreElements()) {
    var value = targets.getNext().QueryInterface(Components.interfaces.nsIRDFNode);
    if (value)
      gSearchDS.Unassert(resultNode, actionArc, value);
  }

  if (status == -210) {
    // User cancelled
    if (item.getAttribute("xpiURL") in gPendingInstalls)
      delete gPendingInstalls[item.getAttribute("xpiURL")];
    return;
  }
  if (status < 0) {
    // Some other failure
    gSearchDS.Assert(resultNode,
                     actionArc,
                     gRDF.GetLiteral("failed"),
                     true);
  }
  else {
    // Success
    gSearchDS.Assert(resultNode,
                     actionArc,
                     gRDF.GetLiteral("installed"),
                     true);
  }
}

var gExtensionsViewController = {
  supportsCommand: function (aCommand)
  {
    var commandNode = document.getElementById(aCommand);
    return commandNode && (commandNode.parentNode == document.getElementById("extensionsCommands"));
  },

  isCommandEnabled: function (aCommand)
  {
    var selectedItem = gExtensionsView.selectedItem;
    if (!selectedItem)
      return false;

    if (selectedItem.hasAttribute("downloadURL") &&
        selectedItem.getAttribute("downloadURL") != "") {
      if (aCommand == "cmd_uninstall")
        return true;
      return false;
    }
    switch (aCommand) {
    case "cmd_installSearchResult":
      return selectedItem.getAttribute("action") == "" ||
             selectedItem.getAttribute("action") == "failed";
    case "cmd_useTheme":
      if (selectedItem.hasAttribute("lwtheme"))
        return !gLWThemeToSelect ||
               selectedItem.getAttribute("addonID") != gLWThemeToSelect.id;
      return selectedItem.type == nsIUpdateItem.TYPE_THEME &&
             !selectedItem.isDisabled &&
             selectedItem.opType != OP_NEEDS_UNINSTALL &&
             (gLWThemeToSelect ||
              gThemeToSelect != selectedItem.getAttribute("internalName"));
    case "cmd_options":
      return selectedItem.type == nsIUpdateItem.TYPE_EXTENSION &&
             !selectedItem.isDisabled &&
             !gInSafeMode &&
             !selectedItem.opType &&
             selectedItem.getAttribute("optionsURL") != "";
    case "cmd_about":
      return selectedItem.opType != OP_NEEDS_INSTALL &&
             selectedItem.getAttribute("plugin") != "true";
    case "cmd_homepage":
      return selectedItem.getAttribute("homepageURL") != "";
    case "cmd_uninstall":
      if (selectedItem.hasAttribute("lwtheme"))
        return true;
      return (selectedItem.type != nsIUpdateItem.TYPE_THEME ||
             selectedItem.type == nsIUpdateItem.TYPE_THEME &&
             selectedItem.getAttribute("internalName") != gDefaultTheme) &&
             selectedItem.opType != OP_NEEDS_UNINSTALL &&
             selectedItem.getAttribute("locked") != "true" &&
             canWriteToLocation(selectedItem) &&
             !gExtensionsView.hasAttribute("update-operation");
    case "cmd_cancelUninstall":
      return selectedItem.opType == OP_NEEDS_UNINSTALL;
    case "cmd_cancelInstall":
      return selectedItem.getAttribute("action") == "installed" &&
             gView == "search" || selectedItem.opType == OP_NEEDS_INSTALL;
    case "cmd_cancelUpgrade":
      return selectedItem.opType == OP_NEEDS_UPGRADE;
    case "cmd_checkUpdate":
      return selectedItem.getAttribute("updateable") != "false" &&
             !gExtensionsView.hasAttribute("update-operation");
    case "cmd_installUpdate":
      return selectedItem.hasAttribute("availableUpdateURL") &&
             !gExtensionsView.hasAttribute("update-operation");
    case "cmd_includeUpdate":
      return selectedItem.hasAttribute("availableUpdateURL") &&
             !gExtensionsView.hasAttribute("update-operation");
    case "cmd_reallyEnable":
    // controls whether to show Enable or Disable in extensions' context menu
      return selectedItem.isDisabled &&
             selectedItem.opType != OP_NEEDS_ENABLE ||
             selectedItem.opType == OP_NEEDS_DISABLE;
    case "cmd_enable":
      return selectedItem.type != nsIUpdateItem.TYPE_THEME &&
             (selectedItem.isDisabled ||
             (!selectedItem.opType ||
             selectedItem.opType == OP_NEEDS_DISABLE)) &&
             !selectedItem.isBlocklisted &&
             (!gCheckUpdateSecurity || selectedItem.providesUpdatesSecurely) &&
             (!gCheckCompat || selectedItem.isCompatible) &&
             selectedItem.satisfiesDependencies &&
             !gExtensionsView.hasAttribute("update-operation");
    case "cmd_disable":
      return selectedItem.type != nsIUpdateItem.TYPE_THEME &&
             (!selectedItem.isDisabled &&
             !selectedItem.opType ||
             selectedItem.opType == OP_NEEDS_ENABLE) &&
             !selectedItem.isBlocklisted &&
             selectedItem.satisfiesDependencies &&
             !gExtensionsView.hasAttribute("update-operation");
    }
    return false;
  },

  doCommand: function (aCommand)
  {
    if (this.isCommandEnabled(aCommand))
      this.commands[aCommand](gExtensionsView.selectedItem);
  },

  onCommandUpdate: function ()
  {
    var extensionsCommands = document.getElementById("extensionsCommands");
    for (var i = 0; i < extensionsCommands.childNodes.length; ++i)
      this.updateCommand(extensionsCommands.childNodes[i]);
  },

  updateCommand: function (command)
  {
    if (this.isCommandEnabled(command.id))
      command.removeAttribute("disabled");
    else
      command.setAttribute("disabled", "true");
  },

  commands: {
    cmd_installSearchResult: function (aSelectedItem)
    {
      if (!isXPInstallEnabled())
        return;

      if (aSelectedItem.hasAttribute("eula")) {
        var eula = {
          name: aSelectedItem.getAttribute("name"),
          text: aSelectedItem.getAttribute("eula"),
          accepted: false
        };
        window.openDialog("chrome://mozapps/content/extensions/eula.xul", "_blank",
                          "chrome,dialog,modal,centerscreen,resizable=no", eula);
        if (!eula.accepted)
          return;
      }

      var details = {
        URL: aSelectedItem.getAttribute("xpiURL"),
        Hash: aSelectedItem.getAttribute("xpiHash"),
        IconURL: aSelectedItem.getAttribute("iconURL"),
        toString: function () { return this.URL; }
      };
      var params = [];
      params[aSelectedItem.getAttribute("name")] = details;

      gSearchDS.Assert(gRDF.GetResource(aSelectedItem.id),
                       gRDF.GetResource(PREFIX_NS_EM + "action"),
                       gRDF.GetLiteral("connecting"),
                       true);
      // Remember this install so we can update the status when install starts
      gPendingInstalls[details.URL] = aSelectedItem.id;
      InstallTrigger.install(params, function(url, status) { installCallback(aSelectedItem, status); });
    },

    cmd_close: function (aSelectedItem)
    {
      closeWindow(true);
    },

    cmd_useTheme: function (aSelectedItem)
    {
      if (aSelectedItem.hasAttribute("lwtheme")) {
        let newTheme = LightweightThemeManager.getUsedTheme(aSelectedItem.getAttribute("addonID"));
        LightweightThemeManager.currentTheme = gLWThemeToSelect = newTheme;

        if (gPref.prefHasUserValue(PREF_LWTHEME_TO_SELECT)) {
          clearRestartMessage();
          setRestartMessage(aSelectedItem);
        }
      }
      else {
        gThemeToSelect = aSelectedItem.getAttribute("internalName");

        // If choosing the current skin just reset the pending change
        if (gThemeToSelect == gCurrentTheme) {
          if (gPref.prefHasUserValue(PREF_EXTENSIONS_DSS_SWITCHPENDING))
            gPref.clearUserPref(PREF_EXTENSIONS_DSS_SWITCHPENDING);
          if (gPref.prefHasUserValue(PREF_DSS_SKIN_TO_SELECT))
            gPref.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
          gLWThemeToSelect = LightweightThemeManager.currentTheme = null;
          clearRestartMessage();
        }
        else {
          if (gPref.getBoolPref(PREF_EXTENSIONS_DSS_ENABLED)) {
            gPref.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, gThemeToSelect);
            gLWThemeToSelect = LightweightThemeManager.currentTheme = null;
          }
          else {
            // Theme change will happen on next startup, this flag tells
            // the Theme Manager that it needs to show "This theme will
            // be selected after a restart" text in the selected theme
            // item.
            gPref.setBoolPref(PREF_EXTENSIONS_DSS_SWITCHPENDING, true);
            gPref.setCharPref(PREF_DSS_SKIN_TO_SELECT, gThemeToSelect);
            if (gLWThemeToSelect) {
              gLWThemeToSelect = null;
              gPref.setCharPref(PREF_LWTHEME_TO_SELECT, "");
            }
            clearRestartMessage();
            setRestartMessage(aSelectedItem);
          }
        }
      }

      // Flush preference change to disk
      gPref.QueryInterface(Components.interfaces.nsIPrefService)
           .savePrefFile(null);

      // disable the useThemeButton
      gExtensionsViewController.onCommandUpdate();
    },

    cmd_options: function (aSelectedItem)
    {
      if (!aSelectedItem) return;
      var optionsURL = aSelectedItem.getAttribute("optionsURL");
      if (optionsURL == "")
        return;

      var windows = Components.classes['@mozilla.org/appshell/window-mediator;1']
                              .getService(Components.interfaces.nsIWindowMediator)
                              .getEnumerator(null);
      while (windows.hasMoreElements()) {
        var win = windows.getNext();
        if (win.document.documentURI == optionsURL) {
          win.focus();
          return;
        }
      }

      var features;
      try {
        var instantApply = gPref.getBoolPref("browser.preferences.instantApply");
        features = "chrome,titlebar,toolbar,centerscreen" + (instantApply ? ",dialog=no" : ",modal");
      }
      catch (e) {
        features = "chrome,titlebar,toolbar,centerscreen,modal";
      }
      openDialog(optionsURL, "", features);
    },

    cmd_homepage: function (aSelectedItem)
    {
      if (!aSelectedItem) return;
      var homepageURL = aSelectedItem.getAttribute("homepageURL");
      // only allow http(s) homepages
      if (isSafeURI(homepageURL))
        openURL(homepageURL);
    },

    cmd_about: function (aSelectedItem)
    {
      if (!aSelectedItem) return;
      var aboutURL = aSelectedItem.getAttribute("aboutURL");
      if (aboutURL != "")
        openDialog(aboutURL, "", "chrome,centerscreen,modal");
      else
        openDialog("chrome://mozapps/content/extensions/about.xul", "", "chrome,centerscreen,modal", aSelectedItem.id, gExtensionsView.database);
    },

    cmd_checkUpdate: function (aSelectedItem)
    {
      if (isOffline("offlineUpdateMsg2"))
        return;

      if (!isXPInstallEnabled())
        return;

      var id = getIDFromResourceURI(aSelectedItem.id);
      var items = [gExtensionManager.getItemForID(id)];
      var listener = new UpdateCheckListener();
      gExtensionManager.update(items, items.length,
                               nsIExtensionManager.UPDATE_CHECK_NEWVERSION,
                               listener,
                               nsIExtensionManager.UPDATE_WHEN_USER_REQUESTED);
    },

    cmd_installUpdate: function (aSelectedItem)
    {
      if (isOffline("offlineUpdateMsg2"))
        return;

      if (!isXPInstallEnabled())
        return;

      showView("installs");
      var item = gExtensionManager.getItemForID(getIDFromResourceURI(aSelectedItem.id));
      gInstalling = true;
      gExtensionManager.addDownloads([item], 1, null);
      // Remove the updates view if there are no add-ons left to update
      updateOptionalViews();
      updateGlobalCommands();
    },

    cmd_includeUpdate: function (aSelectedItem)
    {
      var includeUpdate = document.getAnonymousElementByAttribute(aSelectedItem, "anonid", "includeUpdate");
      includeUpdate.checked = !includeUpdate.checked;
    },

    cmd_uninstall: function (aSelectedItem)
    {
      // Confirm the uninstall
      var name = aSelectedItem.getAttribute("name");
      var dependentItems = [];
      if (!aSelectedItem.hasAttribute("lwtheme")) {
        var id = getIDFromResourceURI(aSelectedItem.id);
        dependentItems = gExtensionManager.getDependentItemListForID(id, true, { });
      }
      var result = confirmOperation(name, "uninstallTitle", "uninstallQueryMessage",
                                    "uninstallButton", "cancelButton",
                                    "uninstallWarnDependMsg", dependentItems);
      if (!result)
        return;

      if (aSelectedItem.hasAttribute("lwtheme")) {
        let lwid = aSelectedItem.getAttribute("addonID");
        LightweightThemeManager.forgetUsedTheme(lwid);
        if (gLWThemeToSelect && lwid == gLWThemeToSelect.id) {
          gLWThemeToSelect = LightweightThemeManager.currentTheme;
          gPref.clearUserPref(PREF_LWTHEME_TO_SELECT);
        }
        return;
      }

      if (aSelectedItem.type == nsIUpdateItem.TYPE_THEME) {
        var theme = aSelectedItem.getAttribute("internalName");
        var selectedTheme = gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
        if (theme == gThemeToSelect) {
          if (gPref.getBoolPref(PREF_EXTENSIONS_DSS_SWITCHPENDING)) {
            var item = getItemForInternalName(selectedTheme);
            if (item && item.getAttribute("opType") == OP_NEEDS_UNINSTALL) {
              // We're uninstalling the theme to be switched to, but the current
              // theme is already marked for uninstall so switch to the default
              // theme
              this.cmd_useTheme(document.getElementById(PREFIX_ITEM_URI + "{972ce4c6-7e08-4474-a285-3208198ce6fd}"));
            }
            else {
              // The theme being uninstalled is the theme to be changed to on
              // restart so clear the pending theme change.
              gPref.clearUserPref(PREF_EXTENSIONS_DSS_SWITCHPENDING);
              gPref.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
              clearRestartMessage();
            }
          }
          else {
            // The theme being uninstalled is the current theme, we need to reselect
            // the default.
            this.cmd_useTheme(document.getElementById(PREFIX_ITEM_URI + "{972ce4c6-7e08-4474-a285-3208198ce6fd}"));
          }
        }

        // If the theme is not the current theme then it will vanish and nothing
        // will be selected so update the selection now.
        if (theme != selectedTheme) {
          if (!gExtensionsView.goDown())
            gExtensionsView.goUp();
        }
      }
      gExtensionManager.uninstallItem(getIDFromResourceURI(aSelectedItem.id));
      gExtensionsViewController.onCommandUpdate();
      if (gExtensionsView.selectedItem)
        gExtensionsView.selectedItem.focus();
      updateOptionalViews();
      updateGlobalCommands();
    },

    cmd_cancelUninstall: function (aSelectedItem)
    {
      gExtensionManager.cancelUninstallItem(getIDFromResourceURI(aSelectedItem.id));
      gExtensionsViewController.onCommandUpdate();
      gExtensionsView.selectedItem.focus();
      updateOptionalViews();
      updateGlobalCommands();
    },

    cmd_cancelInstall: function (aSelectedItem)
    {
      var name = aSelectedItem.getAttribute("name");
      var result = false;
      var opType = aSelectedItem.opType;

      // A search result with an action "installed" is equivalent to a pending install
      if (aSelectedItem.getAttribute("action") == "installed" && gView == "search")
        opType = OP_NEEDS_INSTALL;

      // Confirm cancelling the operation
      switch (opType)
      {
        case OP_NEEDS_INSTALL:
          result = confirmOperation(name, "cancelInstallTitle", "cancelInstallQueryMessage",
                                    "cancelInstallButton", "cancelCancelInstallButton",
                                    null, []);
          break;
        case OP_NEEDS_UPGRADE:
          result = confirmOperation(name, "cancelUpgradeTitle", "cancelUpgradeQueryMessage",
                                    "cancelUpgradeButton", "cancelCancelUpgradeButton",
                                    null, []);
          break;
      }
      if (!result)
        return;

      var id = aSelectedItem.getAttribute("addonID");
      gExtensionManager.cancelInstallItem(id);
      if (gSearchDS) {
        // Check for a search result for this entry
        var searchResult = gSearchDS.GetSource(gRDF.GetResource(PREFIX_NS_EM + "addonID"),
                                               gRDF.GetLiteral(id),
                                               true);
        if (searchResult) {
          // Remove the installed status
          gSearchDS.Unassert(searchResult,
                             gRDF.GetResource(PREFIX_NS_EM + "action"),
                             gRDF.GetLiteral("installed"),
                             true);
        }
      }
      gExtensionsViewController.onCommandUpdate();
      gExtensionsView.selectedItem.focus();
      updateOptionalViews();
      updateGlobalCommands();
    },

    cmd_cancelUpgrade: function (aSelectedItem)
    {
      this.cmd_cancelInstall(aSelectedItem);
    },

    cmd_disable: function (aSelectedItem)
    {
      if (aSelectedItem.getAttribute("plugin") == "true") {
        var name = aSelectedItem.getAttribute("name");
        var desc = aSelectedItem.getAttribute("description");
        togglePluginDisabled(name, desc);
        return;
      }

      var id = getIDFromResourceURI(aSelectedItem.id);
      var dependentItems = gExtensionManager.getDependentItemListForID(id, false, { });

      if (dependentItems.length > 0) {
        name = aSelectedItem.getAttribute("name");
        var result = confirmOperation(name, "disableTitle", "disableQueryMessage",
                                      "disableButton", "cancelButton",
                                      "disableWarningDependMessage", dependentItems);
        if (!result)
          return;
      }
      gExtensionManager.disableItem(id);
      gExtensionsViewController.onCommandUpdate();
      gExtensionsView.selectedItem.focus();
      updateOptionalViews();
      updateGlobalCommands();
    },

    cmd_enable: function (aSelectedItem)
    {
      if (aSelectedItem.getAttribute("plugin") == "true") {
        var name = aSelectedItem.getAttribute("name");
        var desc = aSelectedItem.getAttribute("description");
        togglePluginDisabled(name, desc);
        return;
      }

      gExtensionManager.enableItem(getIDFromResourceURI(aSelectedItem.id));
      gExtensionsViewController.onCommandUpdate();
      gExtensionsView.selectedItem.focus();
      updateOptionalViews();
      updateGlobalCommands();
    }
  }
};

//////////////////////////////////////////////////////////////////////////
// functions to support installing of themes in apps other than firefox //
//////////////////////////////////////////////////////////////////////////
function installSkin()
{
  // 1) Prompt the user for the location of the theme to install.
  var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
  fp.init(window, getExtensionString("installThemePickerTitle"), nsIFilePicker.modeOpen);
  try {
    fp.appendFilter(getExtensionString("themesFilter"), "*.jar");
    fp.appendFilters(nsIFilePicker.filterAll);
  } catch (e) { }

  var ret = fp.show();
  if (ret == nsIFilePicker.returnOK)
  {
    var ioService = Components.classes['@mozilla.org/network/io-service;1'].getService(nsIIOService);
    var fileProtocolHandler =
    ioService.getProtocolHandler("file").QueryInterface(nsIFileProtocolHandler);
    var url = fileProtocolHandler.newFileURI(fp.file).QueryInterface(nsIURL);
    InstallTrigger.installChrome(InstallTrigger.SKIN, url.spec, decodeURIComponent(url.fileBaseName));
  }
}

function installExtension()
{
  var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
  fp.init(window, getExtensionString("installExtensionPickerTitle"), nsIFilePicker.modeOpen);
  try {
    fp.appendFilter(getExtensionString("extensionFilter"), "*.xpi");
    fp.appendFilters(nsIFilePicker.filterAll);
  } catch (e) { }

  var ret = fp.show();
  if (ret == nsIFilePicker.returnOK)
  {
    var ioService = Components.classes['@mozilla.org/network/io-service;1'].getService(nsIIOService);
    var fileProtocolHandler =
    ioService.getProtocolHandler("file").QueryInterface(nsIFileProtocolHandler);
    var url = fileProtocolHandler.newFileURI(fp.file).QueryInterface(nsIURL);
    var xpi = {};
    xpi[decodeURIComponent(url.fileBaseName)] = url.spec;
    InstallTrigger.install(xpi);
  }
}
